File indexing completed on 2024-05-05 04:19:19

0001 /*
0002 Gwenview: an image viewer
0003 Copyright 2007 Aurélien Gâteau <agateau@kde.org>
0004 
0005 This program is free software; you can redistribute it and/or
0006 modify it under the terms of the GNU General Public License
0007 as published by the Free Software Foundation; either version 2
0008 of the License, or (at your option) any later version.
0009 
0010 This program is distributed in the hope that it will be useful,
0011 but WITHOUT ANY WARRANTY; without even the implied warranty of
0012 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
0013 GNU General Public License for more details.
0014 
0015 You should have received a copy of the GNU General Public License
0016 along with this program; if not, write to the Free Software
0017 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
0018 
0019 */
0020 #include "sidebar.h"
0021 
0022 // Qt
0023 #include <QAction>
0024 #include <QIcon>
0025 #include <QLabel>
0026 #include <QMenu>
0027 #include <QStyle>
0028 #include <QStyleOptionTab>
0029 #include <QToolButton>
0030 #include <QVBoxLayout>
0031 
0032 // KF
0033 #include <KIconLoader>
0034 
0035 // Local
0036 #include <lib/gwenviewconfig.h>
0037 #include <lib/signalblocker.h>
0038 
0039 namespace Gwenview
0040 {
0041 /**
0042  * A button which always leave room for an icon, even if there is none, so that
0043  * all button texts are correctly aligned.
0044  */
0045 class SideBarButton : public QToolButton
0046 {
0047 protected:
0048     void paintEvent(QPaintEvent *event) override
0049     {
0050         forceIcon();
0051         QToolButton::paintEvent(event);
0052     }
0053 
0054     QSize sizeHint() const override
0055     {
0056         const_cast<SideBarButton *>(this)->forceIcon();
0057         return QToolButton::sizeHint();
0058     }
0059 
0060 private:
0061     void forceIcon()
0062     {
0063         if (!icon().isNull()) {
0064             return;
0065         }
0066 
0067         // Assign an empty icon to the button if there is no icon associated
0068         // with the action so that all button texts are correctly aligned.
0069         QSize wantedSize = iconSize();
0070         if (mEmptyIcon.isNull() || mEmptyIcon.actualSize(wantedSize) != wantedSize) {
0071             QPixmap pix(wantedSize);
0072             pix.fill(Qt::transparent);
0073             mEmptyIcon.addPixmap(pix);
0074         }
0075         setIcon(mEmptyIcon);
0076     }
0077 
0078     QIcon mEmptyIcon;
0079 };
0080 
0081 //- SideBarGroup ---------------------------------------------------------------
0082 struct SideBarGroupPrivate {
0083     QFrame *mContainer = nullptr;
0084     QLabel *mTitleLabel = nullptr;
0085 };
0086 
0087 SideBarGroup::SideBarGroup(const QString &title)
0088     : QFrame()
0089     , d(new SideBarGroupPrivate)
0090 {
0091     d->mContainer = nullptr;
0092     d->mTitleLabel = new QLabel(this);
0093     d->mTitleLabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
0094     QFont font(d->mTitleLabel->font());
0095     font.setPointSizeF(font.pointSizeF() + 1);
0096     d->mTitleLabel->setFont(font);
0097     d->mTitleLabel->setText(title);
0098     d->mTitleLabel->setVisible(!d->mTitleLabel->text().isEmpty());
0099 
0100     auto layout = new QVBoxLayout(this);
0101     layout->addWidget(d->mTitleLabel);
0102     layout->setContentsMargins(0, 0, 0, 0);
0103     clear();
0104 }
0105 
0106 SideBarGroup::~SideBarGroup()
0107 {
0108     delete d;
0109 }
0110 
0111 void SideBarGroup::addWidget(QWidget *widget)
0112 {
0113     widget->setParent(d->mContainer);
0114     d->mContainer->layout()->addWidget(widget);
0115 }
0116 
0117 void SideBarGroup::clear()
0118 {
0119     if (d->mContainer) {
0120         d->mContainer->deleteLater();
0121     }
0122 
0123     d->mContainer = new QFrame(this);
0124     auto containerLayout = new QVBoxLayout(d->mContainer);
0125     containerLayout->setContentsMargins(0, 0, 0, 0);
0126     containerLayout->setSpacing(0);
0127 
0128     layout()->addWidget(d->mContainer);
0129 }
0130 
0131 void SideBarGroup::addAction(QAction *action)
0132 {
0133     int size = KIconLoader::global()->currentSize(KIconLoader::Small);
0134     QToolButton *button = new SideBarButton();
0135     button->setFocusPolicy(Qt::NoFocus);
0136     button->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed);
0137     button->setAutoRaise(true);
0138     button->setDefaultAction(action);
0139     button->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
0140     button->setIconSize(QSize(size, size));
0141     if (action->menu()) {
0142         button->setPopupMode(QToolButton::InstantPopup);
0143     }
0144     addWidget(button);
0145 }
0146 
0147 //- SideBarPage ----------------------------------------------------------------
0148 struct SideBarPagePrivate {
0149     QIcon mIcon;
0150     QString mTitle;
0151     QVBoxLayout *mLayout = nullptr;
0152 };
0153 
0154 SideBarPage::SideBarPage(const QIcon &icon, const QString &title)
0155     : QWidget()
0156     , d(new SideBarPagePrivate)
0157 {
0158     d->mIcon = icon;
0159     d->mTitle = title;
0160     d->mLayout = new QVBoxLayout(this);
0161     QMargins margins = d->mLayout->contentsMargins();
0162     margins.setRight(qMax(0, margins.right() - style()->pixelMetric(QStyle::PM_SplitterWidth)));
0163     d->mLayout->setContentsMargins(margins);
0164 }
0165 
0166 SideBarPage::~SideBarPage()
0167 {
0168     delete d;
0169 }
0170 
0171 const QIcon &SideBarPage::icon() const
0172 {
0173     return d->mIcon;
0174 }
0175 
0176 const QString &SideBarPage::title() const
0177 {
0178     return d->mTitle;
0179 }
0180 
0181 void SideBarPage::addWidget(QWidget *widget)
0182 {
0183     d->mLayout->addWidget(widget);
0184 }
0185 
0186 void SideBarPage::addStretch()
0187 {
0188     d->mLayout->addStretch();
0189 }
0190 
0191 //- SideBarTabBar --------------------------------------------------------------
0192 struct SideBarTabBarPrivate {
0193     SideBarTabBar::TabButtonStyle tabButtonStyle = SideBarTabBar::TabButtonTextBesideIcon;
0194 };
0195 
0196 SideBarTabBar::SideBarTabBar(QWidget *parent)
0197     : QTabBar(parent)
0198     , d(new SideBarTabBarPrivate)
0199 {
0200     setIconSize(QSize(KIconLoader::SizeSmallMedium, KIconLoader::SizeSmallMedium));
0201 }
0202 
0203 SideBarTabBar::~SideBarTabBar() = default;
0204 
0205 SideBarTabBar::TabButtonStyle SideBarTabBar::tabButtonStyle() const
0206 {
0207     return d->tabButtonStyle;
0208 }
0209 
0210 QSize SideBarTabBar::sizeHint(const TabButtonStyle tabButtonStyle) const
0211 {
0212     QRect tabBarRect(0, 0, 0, 0);
0213     for (int i = 0; i < count(); ++i) {
0214         const QRect tabRect(tabBarRect.topRight(), tabSizeHint(i, tabButtonStyle));
0215         tabBarRect = tabBarRect.united(tabRect);
0216     }
0217     return tabBarRect.size();
0218 }
0219 
0220 QSize SideBarTabBar::sizeHint() const
0221 {
0222     return sizeHint(d->tabButtonStyle);
0223 }
0224 
0225 QSize SideBarTabBar::minimumSizeHint() const
0226 {
0227     return sizeHint(TabButtonIconOnly);
0228 }
0229 
0230 QSize SideBarTabBar::tabContentSize(const int index, const TabButtonStyle tabButtonStyle, const QStyleOptionTab &opt) const
0231 {
0232     if (index < 0 || index > count() - 1) {
0233         return QSize();
0234     }
0235 
0236     const int textWidth = opt.fontMetrics.size(Qt::TextShowMnemonic, tabText(index)).width();
0237 
0238     if (tabButtonStyle == TabButtonIconOnly) {
0239         return QSize(opt.iconSize.width(), qMax(opt.iconSize.height(), opt.fontMetrics.height()));
0240     }
0241     if (tabButtonStyle == TabButtonTextOnly) {
0242         return QSize(textWidth, qMax(opt.iconSize.height(), opt.fontMetrics.height()));
0243     }
0244     // 4 is the hardcoded spacing between icons and text used in Qt Widgets
0245     const int spacing = !opt.icon.isNull() ? 4 : 0;
0246     return QSize(opt.iconSize.width() + spacing + textWidth, qMax(opt.iconSize.height(), opt.fontMetrics.height()));
0247 }
0248 
0249 QSize SideBarTabBar::tabSizeHint(const int index, const TabButtonStyle tabButtonStyle) const
0250 {
0251     if (index < 0 || index > count() - 1) {
0252         return QSize();
0253     }
0254 
0255     QStyleOptionTab opt;
0256     initStyleOption(&opt, index);
0257     if (tabButtonStyle == TabButtonIconOnly) {
0258         opt.text.clear();
0259     } else if (tabButtonStyle == TabButtonTextOnly) {
0260         opt.icon = QIcon();
0261     }
0262     const QSize contentSize = tabContentSize(index, tabButtonStyle, opt);
0263     const int buttonMargin = style()->pixelMetric(QStyle::PM_ButtonMargin);
0264     const int toolBarMargin = style()->pixelMetric(QStyle::PM_ToolBarFrameWidth) + style()->pixelMetric(QStyle::PM_ToolBarItemMargin);
0265     return QSize(contentSize.width() + buttonMargin * 2 + toolBarMargin * 2, contentSize.height() + buttonMargin * 2 + toolBarMargin * 2);
0266 }
0267 
0268 QSize SideBarTabBar::tabSizeHint(const int index) const
0269 {
0270     return tabSizeHint(index, d->tabButtonStyle);
0271 }
0272 
0273 QSize SideBarTabBar::minimumTabSizeHint(int index) const
0274 {
0275     return tabSizeHint(index, TabButtonIconOnly);
0276 }
0277 
0278 void SideBarTabBar::tabLayoutChange()
0279 {
0280     const int width = this->width();
0281     if (width < sizeHint(TabButtonTextOnly).width()) {
0282         d->tabButtonStyle = TabButtonIconOnly;
0283     } else if (width < sizeHint(TabButtonTextBesideIcon).width()) {
0284         d->tabButtonStyle = TabButtonTextOnly;
0285     } else {
0286         d->tabButtonStyle = TabButtonTextBesideIcon;
0287     }
0288 }
0289 
0290 void SideBarTabBar::paintEvent(QPaintEvent *event)
0291 {
0292     Q_UNUSED(event)
0293     QStylePainter painter(this);
0294     // Don't need to draw PE_FrameTabBarBase because it's in a QTabWidget
0295 
0296     const int selected = currentIndex();
0297 
0298     for (int i = 0; i < this->count(); ++i) {
0299         if (i == selected) {
0300             continue;
0301         }
0302         drawTab(i, painter);
0303     }
0304 
0305     // draw selected tab last so it appears on top
0306     if (selected >= 0) {
0307         drawTab(selected, painter);
0308     }
0309 }
0310 
0311 void SideBarTabBar::drawTab(int index, QStylePainter &painter) const
0312 {
0313     QStyleOptionTab opt;
0314     QTabBar::initStyleOption(&opt, index);
0315 
0316     // draw background before doing anything else
0317     painter.drawControl(QStyle::CE_TabBarTabShape, opt);
0318 
0319     const TabButtonStyle tabButtonStyle = this->tabButtonStyle();
0320     if (tabButtonStyle == TabButtonTextOnly) {
0321         opt.icon = QIcon();
0322     } else if (tabButtonStyle == TabButtonIconOnly) {
0323         opt.text.clear();
0324     }
0325     const bool hasIcon = !opt.icon.isNull();
0326     const bool hasText = !opt.text.isEmpty();
0327 
0328     int flags = Qt::TextShowMnemonic;
0329     flags |= hasIcon && hasText ? Qt::AlignLeft | Qt::AlignVCenter : Qt::AlignCenter;
0330     if (!style()->styleHint(QStyle::SH_UnderlineShortcut, &opt, this)) {
0331         flags |= Qt::TextHideMnemonic;
0332     }
0333 
0334     const QSize contentSize = tabContentSize(index, tabButtonStyle, opt);
0335     const QRect contentRect = QStyle::alignedRect(this->layoutDirection(), Qt::AlignCenter, contentSize, opt.rect);
0336 
0337     if (hasIcon) {
0338         painter.drawItemPixmap(contentRect, flags, opt.icon.pixmap(opt.iconSize));
0339     }
0340 
0341     if (hasText) {
0342         // The available space to draw the text depends on wether we already drew an icon into our contentRect.
0343         const QSize availableSizeForText = !hasIcon ? contentSize : QSize(contentSize.width() - opt.iconSize.width() - 4, contentSize.height());
0344         // The '4' above is the hardcoded spacing between icons and text used in Qt Widgets.
0345         const QRect availableRectForText =
0346             !hasIcon ? contentRect : QStyle::alignedRect(this->layoutDirection(), Qt::AlignRight, availableSizeForText, contentRect);
0347 
0348         painter.drawItemText(availableRectForText, flags, opt.palette, opt.state & QStyle::State_Enabled, opt.text, QPalette::WindowText);
0349     }
0350 }
0351 
0352 //- SideBar --------------------------------------------------------------------
0353 struct SideBarPrivate {
0354 };
0355 
0356 SideBar::SideBar(QWidget *parent)
0357     : QTabWidget(parent)
0358     , d(new SideBarPrivate)
0359 {
0360     setTabBar(new SideBarTabBar(this));
0361     tabBar()->setDocumentMode(true);
0362     tabBar()->setUsesScrollButtons(false);
0363     tabBar()->setFocusPolicy(Qt::NoFocus);
0364     tabBar()->setExpanding(true);
0365     setTabPosition(QTabWidget::South);
0366 
0367     connect(tabBar(), &QTabBar::currentChanged, [=]() {
0368         GwenviewConfig::setSideBarPage(currentPage());
0369     });
0370 }
0371 
0372 SideBar::~SideBar()
0373 {
0374     delete d;
0375 }
0376 
0377 QSize SideBar::sizeHint() const
0378 {
0379     return QSize(200, 200);
0380 }
0381 
0382 void SideBar::addPage(SideBarPage *page)
0383 {
0384     // Prevent emitting currentChanged() while populating pages
0385     SignalBlocker blocker(tabBar());
0386     addTab(page, page->icon(), page->title());
0387     const int thisTabIndex = this->count() - 1;
0388     this->setTabToolTip(thisTabIndex, page->title());
0389 }
0390 
0391 QString SideBar::currentPage() const
0392 {
0393     return currentWidget()->objectName();
0394 }
0395 
0396 void SideBar::setCurrentPage(const QString &name)
0397 {
0398     for (int index = 0; index < count(); ++index) {
0399         if (widget(index)->objectName() == name) {
0400             setCurrentIndex(index);
0401         }
0402     }
0403 }
0404 
0405 void SideBar::loadConfig()
0406 {
0407     setCurrentPage(GwenviewConfig::sideBarPage());
0408 }
0409 
0410 } // namespace
0411 
0412 #include "moc_sidebar.cpp"