413 lines
12 KiB
C++

/*
Gwenview: an image viewer
Copyright 2007 Aurélien Gâteau <agateau@kde.org>
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "sidebar.h"
// Qt
#include <QAction>
#include <QIcon>
#include <QLabel>
#include <QMenu>
#include <QStyle>
#include <QStyleOptionTab>
#include <QToolButton>
#include <QVBoxLayout>
// KF
#include <KIconLoader>
// Local
#include <lib/gwenviewconfig.h>
#include <lib/signalblocker.h>
namespace Gwenview
{
/**
* A button which always leave room for an icon, even if there is none, so that
* all button texts are correctly aligned.
*/
class SideBarButton : public QToolButton
{
protected:
void paintEvent(QPaintEvent *event) override
{
forceIcon();
QToolButton::paintEvent(event);
}
QSize sizeHint() const override
{
const_cast<SideBarButton *>(this)->forceIcon();
return QToolButton::sizeHint();
}
private:
void forceIcon()
{
if (!icon().isNull()) {
return;
}
// Assign an empty icon to the button if there is no icon associated
// with the action so that all button texts are correctly aligned.
QSize wantedSize = iconSize();
if (mEmptyIcon.isNull() || mEmptyIcon.actualSize(wantedSize) != wantedSize) {
QPixmap pix(wantedSize);
pix.fill(Qt::transparent);
mEmptyIcon.addPixmap(pix);
}
setIcon(mEmptyIcon);
}
QIcon mEmptyIcon;
};
//- SideBarGroup ---------------------------------------------------------------
struct SideBarGroupPrivate {
QFrame *mContainer = nullptr;
QLabel *mTitleLabel = nullptr;
};
SideBarGroup::SideBarGroup(const QString &title)
: QFrame()
, d(new SideBarGroupPrivate)
{
d->mContainer = nullptr;
d->mTitleLabel = new QLabel(this);
d->mTitleLabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
QFont font(d->mTitleLabel->font());
font.setPointSizeF(font.pointSizeF() + 1);
d->mTitleLabel->setFont(font);
d->mTitleLabel->setText(title);
d->mTitleLabel->setVisible(!d->mTitleLabel->text().isEmpty());
auto layout = new QVBoxLayout(this);
layout->addWidget(d->mTitleLabel);
layout->setContentsMargins(0, 0, 0, 0);
clear();
}
SideBarGroup::~SideBarGroup()
{
delete d;
}
void SideBarGroup::addWidget(QWidget *widget)
{
widget->setParent(d->mContainer);
d->mContainer->layout()->addWidget(widget);
}
void SideBarGroup::clear()
{
if (d->mContainer) {
d->mContainer->deleteLater();
}
d->mContainer = new QFrame(this);
auto containerLayout = new QVBoxLayout(d->mContainer);
containerLayout->setContentsMargins(0, 0, 0, 0);
containerLayout->setSpacing(0);
layout()->addWidget(d->mContainer);
}
void SideBarGroup::addAction(QAction *action)
{
int size = KIconLoader::global()->currentSize(KIconLoader::Small);
QToolButton *button = new SideBarButton();
button->setFocusPolicy(Qt::NoFocus);
button->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed);
button->setAutoRaise(true);
button->setDefaultAction(action);
button->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
button->setIconSize(QSize(size, size));
if (action->menu()) {
button->setPopupMode(QToolButton::InstantPopup);
}
addWidget(button);
}
//- SideBarPage ----------------------------------------------------------------
struct SideBarPagePrivate {
QIcon mIcon;
QString mTitle;
QVBoxLayout *mLayout = nullptr;
};
SideBarPage::SideBarPage(const QIcon &icon, const QString &title)
: QWidget()
, d(new SideBarPagePrivate)
{
d->mIcon = icon;
d->mTitle = title;
d->mLayout = new QVBoxLayout(this);
QMargins margins = d->mLayout->contentsMargins();
margins.setRight(qMax(0, margins.right() - style()->pixelMetric(QStyle::PM_SplitterWidth)));
d->mLayout->setContentsMargins(margins);
}
SideBarPage::~SideBarPage()
{
delete d;
}
const QIcon &SideBarPage::icon() const
{
return d->mIcon;
}
const QString &SideBarPage::title() const
{
return d->mTitle;
}
void SideBarPage::addWidget(QWidget *widget)
{
d->mLayout->addWidget(widget);
}
void SideBarPage::addStretch()
{
d->mLayout->addStretch();
}
//- SideBarTabBar --------------------------------------------------------------
struct SideBarTabBarPrivate {
SideBarTabBar::TabButtonStyle tabButtonStyle = SideBarTabBar::TabButtonTextBesideIcon;
};
SideBarTabBar::SideBarTabBar(QWidget *parent)
: QTabBar(parent)
, d(new SideBarTabBarPrivate)
{
setIconSize(QSize(KIconLoader::SizeSmallMedium, KIconLoader::SizeSmallMedium));
}
SideBarTabBar::~SideBarTabBar() = default;
SideBarTabBar::TabButtonStyle SideBarTabBar::tabButtonStyle() const
{
return d->tabButtonStyle;
}
QSize SideBarTabBar::sizeHint(const TabButtonStyle tabButtonStyle) const
{
QRect tabBarRect(0, 0, 0, 0);
for (int i = 0; i < count(); ++i) {
const QRect tabRect(tabBarRect.topRight(), tabSizeHint(i, tabButtonStyle));
tabBarRect = tabBarRect.united(tabRect);
}
return tabBarRect.size();
}
QSize SideBarTabBar::sizeHint() const
{
return sizeHint(d->tabButtonStyle);
}
QSize SideBarTabBar::minimumSizeHint() const
{
return sizeHint(TabButtonIconOnly);
}
QSize SideBarTabBar::tabContentSize(const int index, const TabButtonStyle tabButtonStyle, const QStyleOptionTab &opt) const
{
if (index < 0 || index > count() - 1) {
return QSize();
}
const int textWidth = opt.fontMetrics.size(Qt::TextShowMnemonic, tabText(index)).width();
if (tabButtonStyle == TabButtonIconOnly) {
return QSize(opt.iconSize.width(), qMax(opt.iconSize.height(), opt.fontMetrics.height()));
}
if (tabButtonStyle == TabButtonTextOnly) {
return QSize(textWidth, qMax(opt.iconSize.height(), opt.fontMetrics.height()));
}
// 4 is the hardcoded spacing between icons and text used in Qt Widgets
const int spacing = !opt.icon.isNull() ? 4 : 0;
return QSize(opt.iconSize.width() + spacing + textWidth, qMax(opt.iconSize.height(), opt.fontMetrics.height()));
}
QSize SideBarTabBar::tabSizeHint(const int index, const TabButtonStyle tabButtonStyle) const
{
if (index < 0 || index > count() - 1) {
return QSize();
}
QStyleOptionTab opt;
initStyleOption(&opt, index);
if (tabButtonStyle == TabButtonIconOnly) {
opt.text.clear();
} else if (tabButtonStyle == TabButtonTextOnly) {
opt.icon = QIcon();
}
const QSize contentSize = tabContentSize(index, tabButtonStyle, opt);
const int buttonMargin = style()->pixelMetric(QStyle::PM_ButtonMargin);
const int toolBarMargin = style()->pixelMetric(QStyle::PM_ToolBarFrameWidth) + style()->pixelMetric(QStyle::PM_ToolBarItemMargin);
return QSize(contentSize.width() + buttonMargin * 2 + toolBarMargin * 2, contentSize.height() + buttonMargin * 2 + toolBarMargin * 2);
}
QSize SideBarTabBar::tabSizeHint(const int index) const
{
return tabSizeHint(index, d->tabButtonStyle);
}
QSize SideBarTabBar::minimumTabSizeHint(int index) const
{
return tabSizeHint(index, TabButtonIconOnly);
}
void SideBarTabBar::tabLayoutChange()
{
const int width = this->width();
if (width < sizeHint(TabButtonTextOnly).width()) {
d->tabButtonStyle = TabButtonIconOnly;
} else if (width < sizeHint(TabButtonTextBesideIcon).width()) {
d->tabButtonStyle = TabButtonTextOnly;
} else {
d->tabButtonStyle = TabButtonTextBesideIcon;
}
}
void SideBarTabBar::paintEvent(QPaintEvent *event)
{
Q_UNUSED(event)
QStylePainter painter(this);
// Don't need to draw PE_FrameTabBarBase because it's in a QTabWidget
const int selected = currentIndex();
for (int i = 0; i < this->count(); ++i) {
if (i == selected) {
continue;
}
drawTab(i, painter);
}
// draw selected tab last so it appears on top
if (selected >= 0) {
drawTab(selected, painter);
}
}
void SideBarTabBar::drawTab(int index, QStylePainter &painter) const
{
QStyleOptionTab opt;
QTabBar::initStyleOption(&opt, index);
// draw background before doing anything else
painter.drawControl(QStyle::CE_TabBarTabShape, opt);
const TabButtonStyle tabButtonStyle = this->tabButtonStyle();
if (tabButtonStyle == TabButtonTextOnly) {
opt.icon = QIcon();
} else if (tabButtonStyle == TabButtonIconOnly) {
opt.text.clear();
}
const bool hasIcon = !opt.icon.isNull();
const bool hasText = !opt.text.isEmpty();
int flags = Qt::TextShowMnemonic;
flags |= hasIcon && hasText ? Qt::AlignLeft | Qt::AlignVCenter : Qt::AlignCenter;
if (!style()->styleHint(QStyle::SH_UnderlineShortcut, &opt, this)) {
flags |= Qt::TextHideMnemonic;
}
const QSize contentSize = tabContentSize(index, tabButtonStyle, opt);
const QRect contentRect = QStyle::alignedRect(this->layoutDirection(), Qt::AlignCenter, contentSize, opt.rect);
if (hasIcon) {
painter.drawItemPixmap(contentRect, flags, opt.icon.pixmap(opt.iconSize));
}
if (hasText) {
// The available space to draw the text depends on wether we already drew an icon into our contentRect.
const QSize availableSizeForText = !hasIcon ? contentSize : QSize(contentSize.width() - opt.iconSize.width() - 4, contentSize.height());
// The '4' above is the hardcoded spacing between icons and text used in Qt Widgets.
const QRect availableRectForText =
!hasIcon ? contentRect : QStyle::alignedRect(this->layoutDirection(), Qt::AlignRight, availableSizeForText, contentRect);
painter.drawItemText(availableRectForText, flags, opt.palette, opt.state & QStyle::State_Enabled, opt.text, QPalette::WindowText);
}
}
//- SideBar --------------------------------------------------------------------
struct SideBarPrivate {
};
SideBar::SideBar(QWidget *parent)
: QTabWidget(parent)
, d(new SideBarPrivate)
{
setTabBar(new SideBarTabBar(this));
tabBar()->setDocumentMode(true);
tabBar()->setUsesScrollButtons(false);
tabBar()->setFocusPolicy(Qt::NoFocus);
tabBar()->setExpanding(true);
setTabPosition(QTabWidget::South);
connect(tabBar(), &QTabBar::currentChanged, [=]() {
GwenviewConfig::setSideBarPage(currentPage());
});
}
SideBar::~SideBar()
{
delete d;
}
QSize SideBar::sizeHint() const
{
return QSize(200, 200);
}
void SideBar::addPage(SideBarPage *page)
{
// Prevent emitting currentChanged() while populating pages
SignalBlocker blocker(tabBar());
addTab(page, page->icon(), page->title());
const int thisTabIndex = this->count() - 1;
this->setTabToolTip(thisTabIndex, page->title());
}
QString SideBar::currentPage() const
{
return currentWidget()->objectName();
}
void SideBar::setCurrentPage(const QString &name)
{
for (int index = 0; index < count(); ++index) {
if (widget(index)->objectName() == name) {
setCurrentIndex(index);
}
}
}
void SideBar::loadConfig()
{
setCurrentPage(GwenviewConfig::sideBarPage());
}
} // namespace
#include "moc_sidebar.cpp"