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"