1 /*
2  * Strawberry Music Player
3  * This file was part of Clementine.
4  * Copyright 2018, Vikram Ambrose <ambroseworks@gmail.com>
5  * Copyright 2018, Jonas Kvinge <jonas@jkvinge.net>
6  *
7  * Strawberry is free software: you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation, either version 3 of the License, or
10  * (at your option) any later version.
11  *
12  * Strawberry is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with Strawberry.  If not, see <http://www.gnu.org/licenses/>.
19  *
20  */
21 
22 #include "config.h"
23 
24 #include <algorithm>
25 #include <utility>
26 #include <chrono>
27 
28 #include <QtGlobal>
29 #include <QObject>
30 #include <QApplication>
31 #include <QTabBar>
32 #include <QWidget>
33 #include <QTimer>
34 #include <QList>
35 #include <QMap>
36 #include <QHash>
37 #include <QVariant>
38 #include <QString>
39 #include <QIcon>
40 #include <QPainter>
41 #include <QStylePainter>
42 #include <QColor>
43 #include <QRect>
44 #include <QFont>
45 #include <QFontMetrics>
46 #include <QSize>
47 #include <QPoint>
48 #include <QBrush>
49 #include <QPen>
50 #include <QTransform>
51 #include <QMenu>
52 #include <QAction>
53 #include <QActionGroup>
54 #include <QSettings>
55 #include <QPixmapCache>
56 #include <QLayout>
57 #include <QBoxLayout>
58 #include <QtEvents>
59 #include <QStyle>
60 #include <QCommonStyle>
61 #include <QProxyStyle>
62 #include <QStyleOption>
63 #include <QStyleOptionComplex>
64 
65 #include "fancytabwidget.h"
66 #include "core/stylehelper.h"
67 #include "settings/appearancesettingspage.h"
68 
69 using namespace std::chrono_literals;
70 
71 const int FancyTabWidget::IconSize_LargeSidebar = 40;
72 const int FancyTabWidget::IconSize_SmallSidebar = 32;
73 const int FancyTabWidget::TabSize_LargeSidebarMinWidth = 70;
74 
75 class FancyTabBar : public QTabBar {  // clazy:exclude=missing-qobject-macro
76 
77  private:
78   int mouseHoverTabIndex = -1;
79   QHash<QWidget*, QString> labelCache;
80   QMap<int, QWidget*> spacers;
81 
82  public:
FancyTabBar(QWidget * parent=nullptr)83   explicit FancyTabBar(QWidget *parent = nullptr) : QTabBar(parent) {
84     setMouseTracking(true);
85   }
86 
sizeHint() const87   QSize sizeHint() const override {
88 
89     FancyTabWidget *tabWidget = qobject_cast<FancyTabWidget*>(parentWidget());
90     if (tabWidget->mode() == FancyTabWidget::Mode_Tabs || tabWidget->mode() == FancyTabWidget::Mode_IconOnlyTabs) {
91       return QTabBar::sizeHint();
92     }
93 
94     QSize size;
95     int h = 0;
96     for (int i = 0; i < count(); ++i) {
97       if (tabSizeHint(i).width() > size.width()) size.setWidth(tabSizeHint(i).width());
98       h += tabSizeHint(i).height();
99     }
100     size.setHeight(h);
101 
102     return size;
103 
104   }
105 
width() const106   int width() const {
107     FancyTabWidget *tabWidget = qobject_cast<FancyTabWidget*>(parentWidget());
108     if (tabWidget->mode() == FancyTabWidget::Mode_LargeSidebar || tabWidget->mode() == FancyTabWidget::Mode_SmallSidebar) {
109       int w = 0;
110       for (int i = 0; i < count(); ++i) {
111         if (tabSizeHint(i).width() > w) w = tabSizeHint(i).width();
112       }
113       return w;
114     }
115     else {
116       return QTabBar::width();
117     }
118   }
119 
120  protected:
tabSizeHint(int index) const121   QSize tabSizeHint(int index) const override {
122 
123     FancyTabWidget *tabWidget = qobject_cast<FancyTabWidget*>(parentWidget());
124 
125     QSize size;
126     if (tabWidget->mode() == FancyTabWidget::Mode_LargeSidebar) {
127 
128       QFont bold_font(font());
129       bold_font.setBold(true);
130       QFontMetrics fm(bold_font);
131 
132       // If the text of any tab is wider than the set width then use that instead.
133       int w = std::max(FancyTabWidget::TabSize_LargeSidebarMinWidth, tabWidget->iconsize_largesidebar() + 22);
134       for (int i = 0; i < count(); ++i) {
135         QRect rect = fm.boundingRect(QRect(0, 0, std::max(FancyTabWidget::TabSize_LargeSidebarMinWidth, tabWidget->iconsize_largesidebar() + 22), height()), Qt::TextWordWrap, QTabBar::tabText(i));
136         rect.setWidth(rect.width() + 10);
137         if (rect.width() > w) w = rect.width();
138       }
139 
140       QRect rect = fm.boundingRect(QRect(0, 0, w, height()), Qt::TextWordWrap, QTabBar::tabText(index));
141       size = QSize(w, tabWidget->iconsize_largesidebar() + rect.height() + 10);
142     }
143     else if (tabWidget->mode() == FancyTabWidget::Mode_SmallSidebar) {
144 
145       QFont bold_font(font());
146       bold_font.setBold(true);
147       QFontMetrics fm(bold_font);
148 
149       QRect rect = fm.boundingRect(QRect(0, 0, 100, tabWidget->height()), Qt::AlignHCenter, QTabBar::tabText(index));
150       int w = std::max(tabWidget->iconsize_smallsidebar(), rect.height()) + 15;
151       int h = tabWidget->iconsize_smallsidebar() + rect.width() + 20;
152       size = QSize(w, h);
153     }
154     else {
155       size = QTabBar::tabSizeHint(index);
156     }
157 
158     return size;
159 
160   }
161 
leaveEvent(QEvent * event)162   void leaveEvent(QEvent *event) override {
163     Q_UNUSED(event);
164     mouseHoverTabIndex = -1;
165     update();
166   }
167 
mouseMoveEvent(QMouseEvent * event)168   void mouseMoveEvent(QMouseEvent *event) override {
169 
170     QPoint pos = event->pos();
171 
172     mouseHoverTabIndex = tabAt(pos);
173     if (mouseHoverTabIndex > -1) {
174       update();
175     }
176     QTabBar::mouseMoveEvent(event);
177 
178   }
179 
paintEvent(QPaintEvent * pe)180   void paintEvent(QPaintEvent *pe) override {
181 
182     FancyTabWidget *tabWidget = qobject_cast<FancyTabWidget*>(parentWidget());
183 
184     bool verticalTextTabs = false;
185 
186     if (tabWidget->mode() == FancyTabWidget::Mode_SmallSidebar) {
187       verticalTextTabs = true;
188     }
189 
190     // if LargeSidebar, restore spacers
191     if (tabWidget->mode() == FancyTabWidget::Mode_LargeSidebar && spacers.count() > 0) {
192       QList<int> keys = spacers.keys();
193       for (const int index : keys) {
194         tabWidget->insertTab(index, spacers[index], QIcon(), QString());
195         tabWidget->setTabEnabled(index, false);
196       }
197       spacers.clear();
198     }
199     else if (tabWidget->mode() != FancyTabWidget::Mode_LargeSidebar) {
200       // traverse in the opposite order to save indices of spacers
201       for (int i = count() - 1; i >= 0; --i) {
202         // spacers are disabled tabs
203         if (!isTabEnabled(i) && !spacers.contains(i)) {
204           spacers[i] = tabWidget->widget(i);
205           tabWidget->removeTab(i);
206           --i;
207         }
208       }
209     }
210 
211     // Restore any label text that was hidden/cached for the IconOnlyTabs mode
212     if (labelCache.count() > 0 && tabWidget->mode() != FancyTabWidget::Mode_IconOnlyTabs) {
213       for (int i = 0; i < count(); ++i) {
214         setTabToolTip(i, "");
215         setTabText(i, labelCache[tabWidget->widget(i)]);
216       }
217       labelCache.clear();
218     }
219     if (tabWidget->mode() != FancyTabWidget::Mode_LargeSidebar && tabWidget->mode() != FancyTabWidget::Mode_SmallSidebar) {
220       // Cache and hide label text for IconOnlyTabs mode
221       if (tabWidget->mode() == FancyTabWidget::Mode_IconOnlyTabs && labelCache.count() == 0) {
222         for(int i = 0; i < count(); ++i) {
223           labelCache[tabWidget->widget(i)] = tabText(i);
224           setTabToolTip(i, tabText(i));
225           setTabText(i, "");
226         }
227       }
228       QTabBar::paintEvent(pe);
229       return;
230     }
231 
232     QStylePainter p(this);
233 
234     for (int index = 0; index < count(); ++index) {
235       const bool selected = tabWidget->currentIndex() == index;
236       QRect tabrect = tabRect(index);
237       QRect selectionRect = tabrect;
238       if (selected) {
239         // Selection highlight
240         p.save();
241         QLinearGradient grad(selectionRect.topLeft(), selectionRect.topRight());
242         grad.setColorAt(0, QColor(255, 255, 255, 140));
243         grad.setColorAt(1, QColor(255, 255, 255, 210));
244         p.fillRect(selectionRect.adjusted(0,0,0,-1), grad);
245         p.restore();
246 
247         // shadow lines
248         p.setPen(QColor(0, 0, 0, 110));
249         p.drawLine(selectionRect.topLeft()    + QPoint(1, -1), selectionRect.topRight()    - QPoint(0, 1));
250         p.drawLine(selectionRect.bottomLeft(), selectionRect.bottomRight());
251         p.setPen(QColor(0, 0, 0, 40));
252         p.drawLine(selectionRect.topLeft(),    selectionRect.bottomLeft());
253 
254         // highlights
255         p.setPen(QColor(255, 255, 255, 50));
256         p.drawLine(selectionRect.topLeft()    + QPoint(0, -2), selectionRect.topRight()    - QPoint(0, 2));
257         p.drawLine(selectionRect.bottomLeft() + QPoint(0, 1),  selectionRect.bottomRight() + QPoint(0, 1));
258         p.setPen(QColor(255, 255, 255, 40));
259         p.drawLine(selectionRect.topLeft()    + QPoint(0, 0),  selectionRect.topRight());
260         p.drawLine(selectionRect.topRight()   + QPoint(0, 1),  selectionRect.bottomRight() - QPoint(0, 1));
261         p.drawLine(selectionRect.bottomLeft() + QPoint(0, -1), selectionRect.bottomRight() - QPoint(0, 1));
262 
263       }
264 
265       // Mouse hover effect
266       if (!selected && index == mouseHoverTabIndex && isTabEnabled(index)) {
267         p.save();
268         QLinearGradient grad(selectionRect.topLeft(),  selectionRect.topRight());
269         grad.setColorAt(0, Qt::transparent);
270         grad.setColorAt(0.5, QColor(255, 255, 255, 40));
271         grad.setColorAt(1, Qt::transparent);
272         p.fillRect(selectionRect, grad);
273         p.setPen(QPen(grad, 1.0));
274         p.drawLine(selectionRect.topLeft(),     selectionRect.topRight());
275         p.drawLine(selectionRect.bottomRight(), selectionRect.bottomLeft());
276         p.restore();
277       }
278 
279       // Label (Icon and Text)
280       {
281         p.save();
282         QTransform m;
283         int textFlags = 0;
284         Qt::Alignment iconFlags;
285 
286         QRect tabrectText;
287         QRect tabrectLabel;
288 
289         if (verticalTextTabs) {
290           m = QTransform::fromTranslate(tabrect.left(), tabrect.bottom());
291           m.rotate(-90);
292           textFlags = Qt::AlignVCenter;
293           iconFlags = Qt::AlignVCenter;
294 
295           tabrectLabel = QRect(QPoint(0, 0), m.mapRect(tabrect).size());
296 
297           tabrectText = tabrectLabel;
298           tabrectText.translate(tabWidget->iconsize_smallsidebar() + 8, 0);
299         }
300         else {
301           m = QTransform::fromTranslate(tabrect.left(), tabrect.top());
302           textFlags = Qt::AlignHCenter | Qt::AlignBottom | Qt::TextWordWrap;
303           iconFlags = Qt::AlignHCenter | Qt::AlignTop;
304 
305           tabrectLabel = QRect(QPoint(0, 0), m.mapRect(tabrect).size());
306 
307           tabrectText = tabrectLabel;
308           tabrectText.translate(0, -5);
309         }
310 
311         p.setTransform(m);
312 
313         QFont boldFont(p.font());
314         boldFont.setBold(true);
315         p.setFont(boldFont);
316 
317         // Text drop shadow color
318         p.setPen(selected ? QColor(255, 255, 255, 160) : QColor(0, 0, 0, 110) );
319         p.translate(0, 3);
320         p.drawText(tabrectText, textFlags, tabText(index));
321 
322         // Text foreground color
323         p.translate(0, -1);
324         p.setPen(selected ? QColor(60, 60, 60) : StyleHelper::panelTextColor());
325         p.drawText(tabrectText, textFlags, tabText(index));
326 
327 
328         // Draw the icon
329         QRect tabrectIcon;
330         if (verticalTextTabs) {
331           tabrectIcon = tabrectLabel;
332           tabrectIcon.setSize(QSize(tabWidget->iconsize_smallsidebar(), tabWidget->iconsize_smallsidebar()));
333           // Center the icon
334           const int moveRight = (QTabBar::width() - tabWidget->iconsize_smallsidebar()) / 2;
335           tabrectIcon.translate(5, moveRight);
336         }
337         else {
338           tabrectIcon = tabrectLabel;
339           tabrectIcon.setSize(QSize(tabWidget->iconsize_largesidebar(), tabWidget->iconsize_largesidebar()));
340           // Center the icon
341           const int moveRight = (QTabBar::width() - tabWidget->iconsize_largesidebar() -1) / 2;
342           tabrectIcon.translate(moveRight, 5);
343         }
344         tabIcon(index).paint(&p, tabrectIcon, iconFlags);
345         p.restore();
346       }
347     }
348   }
349 
350 };
351 
352 class TabData : public QObject {  // clazy:exclude=missing-qobject-macro
353  public:
TabData(QWidget * widget_view,const QString & name,const QIcon & icon,const QString & label,const int idx,QWidget * parent)354   TabData(QWidget *widget_view, const QString &name, const QIcon &icon, const QString &label, const int idx, QWidget *parent)
355     : QObject(parent),
356       widget_view_(widget_view),
357       name_(name), icon_(icon),
358       label_(label),
359       index_(idx),
360       page_(new QWidget()) {
361     // In order to achieve the same effect as the "Bottom Widget" of the old Nokia based FancyTabWidget a VBoxLayout is used on each page
362     QVBoxLayout *layout = new QVBoxLayout(page_);
363     layout->setSpacing(0);
364     layout->setContentsMargins(0, 0, 0, 0);
365     layout->addWidget(widget_view_);
366     page_->setLayout(layout);
367   }
368 
widget_view() const369   QWidget *widget_view() const { return widget_view_; }
name() const370   QString name() const { return name_; }
icon() const371   QIcon icon() const { return icon_; }
label() const372   QString label() const { return label_; }
page() const373   QWidget *page() const { return page_; }
index() const374   int index() const { return index_; }
375 
376  private:
377   QWidget *widget_view_;
378   QString name_;
379   QIcon icon_;
380   QString label_;
381   int index_;
382   QWidget *page_;
383 
384 };
385 
386 // Spacers are just disabled pages
addSpacer()387 void FancyTabWidget::addSpacer() {
388 
389   QWidget *spacer = new QWidget(this);
390   const int idx = insertTab(count(), spacer, QIcon(), QString());
391   setTabEnabled(idx, false);
392 
393 }
394 
setBackgroundPixmap(const QPixmap & pixmap)395 void FancyTabWidget::setBackgroundPixmap(const QPixmap &pixmap) {
396 
397   background_pixmap_ = pixmap;
398   update();
399 
400 }
401 
setCurrentIndex(int idx)402 void FancyTabWidget::setCurrentIndex(int idx) {
403 
404   Q_ASSERT(count() > 0);
405 
406   if (idx >= count() || idx < 0) idx = 0;
407 
408   QWidget *currentPage = widget(idx);
409   QLayout *layout = currentPage->layout();
410   if (bottom_widget_) layout->addWidget(bottom_widget_);
411   QTabWidget::setCurrentIndex(idx);
412 
413 }
414 
currentTabChanged(const int idx)415 void FancyTabWidget::currentTabChanged(const int idx) {
416 
417   QWidget *currentPage = currentWidget();
418   QLayout *layout = currentPage->layout();
419   if (bottom_widget_) layout->addWidget(bottom_widget_);
420   emit CurrentChanged(idx);
421 
422 }
423 
424 // Override QStyle::subElementRect() and use QCommonStyle to fix a problem with the adwaita style.
425 // The adwaita style is causing the contents of the tabbar to be stretched from top to bottom with space between icons and text.
426 // You can see this on the default Fedora (Gnome) installation.
427 
428 class FancyTabWidgetProxyStyle : public QProxyStyle {  // clazy:exclude=missing-qobject-macro
429 
430  public:
FancyTabWidgetProxyStyle(QStyle * style)431   explicit FancyTabWidgetProxyStyle(QStyle *style) : QProxyStyle(style), common_style_(new QCommonStyle()) {}
~FancyTabWidgetProxyStyle()432   ~FancyTabWidgetProxyStyle() override { common_style_->deleteLater(); }
433 
subElementRect(QStyle::SubElement element,const QStyleOption * option,const QWidget * widget=nullptr) const434   QRect subElementRect(QStyle::SubElement element, const QStyleOption *option, const QWidget *widget = nullptr) const override {
435     if (element == QStyle::SE_TabWidgetTabBar) {
436       QRect proxy_style_rect = QProxyStyle::subElementRect(element, option, widget);
437       // Fix stretched tabbar (adwaita style issue).
438       proxy_style_rect.setHeight(common_style_->subElementRect(element, option, widget).height());
439       return proxy_style_rect;
440     }
441     else {
442       return QProxyStyle::subElementRect(element, option, widget);
443     }
444   }
445 
446  private:
447   QCommonStyle *common_style_;
448 };
449 
FancyTabWidget(QWidget * parent)450 FancyTabWidget::FancyTabWidget(QWidget *parent)
451     : QTabWidget(parent),
452       style_(nullptr),
453       menu_(nullptr),
454       mode_(Mode_None),
455       bottom_widget_(nullptr),
456       bg_color_system_(true),
457       bg_gradient_(true),
458       iconsize_smallsidebar_(FancyTabWidget::IconSize_SmallSidebar),
459       iconsize_largesidebar_(FancyTabWidget::IconSize_LargeSidebar) {
460 
461   FancyTabBar *tabBar = new FancyTabBar(this);
462   setTabBar(tabBar);
463   setTabPosition(QTabWidget::West);
464   setMovable(true);
465   setElideMode(Qt::ElideNone);
466   setUsesScrollButtons(true);
467   if (QApplication::style() && QApplication::style()->objectName().contains(QRegularExpression("^adwaita.*$", QRegularExpression::CaseInsensitiveOption))) {
468     style_ = new FancyTabWidgetProxyStyle(style());
469     setStyle(style_);
470   }
471 
472   QObject::connect(tabBar, &FancyTabBar::currentChanged, this, &FancyTabWidget::currentTabChanged);
473 
474 }
475 
~FancyTabWidget()476 FancyTabWidget::~FancyTabWidget() {
477   if (style_) style_->deleteLater();
478 }
479 
Load(const QString & kSettingsGroup)480 void FancyTabWidget::Load(const QString &kSettingsGroup) {
481 
482   QSettings s;
483   s.beginGroup(kSettingsGroup);
484   QMultiMap <int, TabData*> tabs;
485   for (TabData *tab : std::as_const(tabs_)) {
486     int idx = s.value("tab_" + tab->name(), tab->index()).toInt();
487     while (tabs.contains(idx)) { ++idx; }
488     tabs.insert(idx, tab);
489   }
490   s.endGroup();
491 
492   QMultiMap <int, TabData*> ::iterator i;
493   for (i = tabs.begin(); i != tabs.end(); ++i) {
494     TabData *tab = i.value();
495     const int idx = insertTab(i.key(), tab->page(), tab->icon(), tab->label());
496     tabBar()->setTabData(idx, QVariant(tab->name()));
497   }
498 
499 }
500 
insertTab(const int idx,QWidget * page,const QIcon & icon,const QString & label)501 int FancyTabWidget::insertTab(const int idx, QWidget *page, const QIcon &icon, const QString &label) {
502   return QTabWidget::insertTab(idx, page, icon, label);
503 }
504 
SaveSettings(const QString & kSettingsGroup)505 void FancyTabWidget::SaveSettings(const QString &kSettingsGroup) {
506 
507   QSettings s;
508   s.beginGroup(kSettingsGroup);
509 
510   s.setValue("tab_mode", mode_);
511   s.setValue("current_tab", currentIndex());
512 
513   for (TabData *tab : std::as_const(tabs_)) {
514     QString k = "tab_" + tab->name();
515     int idx = QTabWidget::indexOf(tab->page());
516     if (idx < 0) {
517       if (s.contains(k)) s.remove(k);
518     }
519     else {
520       s.setValue(k, idx);
521     }
522   }
523 
524   s.endGroup();
525 
526 }
527 
ReloadSettings()528 void FancyTabWidget::ReloadSettings() {
529 
530   QSettings s;
531   s.beginGroup(AppearanceSettingsPage::kSettingsGroup);
532   bg_color_system_ = s.value(AppearanceSettingsPage::kTabBarSystemColor, false).toBool();
533   bg_gradient_ = s.value(AppearanceSettingsPage::kTabBarGradient, true).toBool();
534   bg_color_ = AppearanceSettingsPage::DefaultTabbarBgColor();
535   if (!bg_color_system_) {
536     bg_color_ = s.value(AppearanceSettingsPage::kTabBarColor, bg_color_).value<QColor>();
537   }
538   iconsize_smallsidebar_ = s.value(AppearanceSettingsPage::kIconSizeTabbarSmallMode, FancyTabWidget::IconSize_SmallSidebar).toInt();
539   iconsize_largesidebar_ = s.value(AppearanceSettingsPage::kIconSizeTabbarLargeMode, FancyTabWidget::IconSize_LargeSidebar).toInt();
540   s.endGroup();
541 
542 #ifndef Q_OS_MACOS
543   if (mode() == FancyTabWidget::Mode_LargeSidebar) {
544     setIconSize(QSize(iconsize_largesidebar_, iconsize_largesidebar_));
545   }
546   else {
547     setIconSize(QSize(iconsize_smallsidebar_, iconsize_smallsidebar_));
548   }
549 #endif
550 
551   update();
552   tabBarUpdateGeometry();
553 
554 }
555 
addBottomWidget(QWidget * widget_view)556 void FancyTabWidget::addBottomWidget(QWidget *widget_view) {
557   bottom_widget_ = widget_view;
558 }
559 
AddTab(QWidget * widget_view,const QString & name,const QIcon & icon,const QString & label)560 void FancyTabWidget::AddTab(QWidget *widget_view, const QString &name, const QIcon &icon, const QString &label) {
561 
562   TabData *tab = new TabData(widget_view, name, icon, label, tabs_.count(), this);
563   tabs_.insert(widget_view, tab);
564 
565 }
566 
EnableTab(QWidget * widget_view)567 bool FancyTabWidget::EnableTab(QWidget *widget_view) {
568 
569   if (!tabs_.contains(widget_view)) return false;
570   TabData *tab = tabs_.value(widget_view);
571 
572   if (QTabWidget::indexOf(tab->page()) >= 0) return true;
573   const int idx = QTabWidget::insertTab(count(), tab->page(), tab->icon(), tab->label());
574   tabBar()->setTabData(idx, QVariant(tab->name()));
575 
576   return true;
577 
578 }
579 
DisableTab(QWidget * widget_view)580 bool FancyTabWidget::DisableTab(QWidget *widget_view) {
581 
582   if (!tabs_.contains(widget_view)) return false;
583   TabData *tab = tabs_.value(widget_view);
584 
585   int idx = QTabWidget::indexOf(tab->page());
586   if (idx < 0) return false;
587 
588   removeTab(idx);
589 
590   return true;
591 
592 }
593 
IndexOfTab(QWidget * widget)594 int FancyTabWidget::IndexOfTab(QWidget *widget) {
595   if (!tabs_.contains(widget)) return -1;
596   return QTabWidget::indexOf(tabs_[widget]->page());
597 }
598 
paintEvent(QPaintEvent * pe)599 void FancyTabWidget::paintEvent(QPaintEvent *pe) {
600 
601   if (mode() != FancyTabWidget::Mode_LargeSidebar && mode() != FancyTabWidget::Mode_SmallSidebar) {
602     QTabWidget::paintEvent(pe);
603     return;
604   }
605 
606   QStylePainter painter(this);
607   QRect backgroundRect = rect();
608   backgroundRect.setWidth(tabBar()->width());
609 
610   QString key = QString::asprintf("mh_vertical %d %d %d %d %d", backgroundRect.width(), backgroundRect.height(), bg_color_.rgb(), (bg_gradient_ ? 1 : 0), (background_pixmap_.isNull() ? 0 : 1));
611 
612   QPixmap pixmap;
613   if (!QPixmapCache::find(key, &pixmap)) {
614 
615     pixmap = QPixmap(backgroundRect.size());
616     QPainter p(&pixmap);
617     p.fillRect(backgroundRect, bg_color_);
618 
619     // Draw the gradient fill.
620     if (bg_gradient_) {
621 
622       QRect rect(0, 0, backgroundRect.width(), backgroundRect.height());
623 
624       QColor shadow = StyleHelper::shadowColor(false);
625       QLinearGradient grad(backgroundRect.topRight(), backgroundRect.topLeft());
626       grad.setColorAt(0, bg_color_.lighter(117));
627       grad.setColorAt(1, shadow.darker(109));
628       p.fillRect(rect, grad);
629 
630       QColor light(255, 255, 255, 80);
631       p.setPen(light);
632       p.drawLine(rect.topRight() - QPoint(1, 0), rect.bottomRight() - QPoint(1, 0));
633       QColor dark(0, 0, 0, 90);
634       p.setPen(dark);
635       p.drawLine(rect.topLeft(), rect.bottomLeft());
636 
637     }
638 
639     // Draw the translucent png graphics over the gradient fill
640     if (!background_pixmap_.isNull()) {
641       QRect pixmap_rect(background_pixmap_.rect());
642       pixmap_rect.moveTo(backgroundRect.topLeft());
643 
644       while (pixmap_rect.top() < backgroundRect.bottom()) {
645         QRect source_rect(pixmap_rect.intersected(backgroundRect));
646         source_rect.moveTo(0, 0);
647         p.drawPixmap(pixmap_rect.topLeft(), background_pixmap_, source_rect);
648         pixmap_rect.moveTop(pixmap_rect.bottom() - 10);
649       }
650     }
651 
652     // Shadow effect of the background
653     QColor light(255, 255, 255, 80);
654     p.setPen(light);
655     p.drawLine(backgroundRect.topRight() - QPoint(1, 0), backgroundRect.bottomRight() - QPoint(1, 0));
656     QColor dark(0, 0, 0, 90);
657     p.setPen(dark);
658     p.drawLine(backgroundRect.topLeft(), backgroundRect.bottomLeft());
659 
660     p.setPen(StyleHelper::borderColor());
661     p.drawLine(backgroundRect.topRight(), backgroundRect.bottomRight());
662 
663     p.end();
664 
665     QPixmapCache::insert(key, pixmap);
666 
667   }
668 
669   painter.drawPixmap(backgroundRect.topLeft(), pixmap);
670 
671 }
672 
tabBarUpdateGeometry()673 void FancyTabWidget::tabBarUpdateGeometry() {
674   tabBar()->updateGeometry();
675 }
676 
SetMode(FancyTabWidget::Mode mode)677 void FancyTabWidget::SetMode(FancyTabWidget::Mode mode) {
678 
679   mode_ = mode;
680 
681   if (mode == FancyTabWidget::Mode_Tabs || mode == FancyTabWidget::Mode_IconOnlyTabs) {
682     setTabPosition(QTabWidget::North);
683   }
684   else {
685     setTabPosition(QTabWidget::West);
686   }
687 
688 #ifndef Q_OS_MACOS
689   if (mode_ == FancyTabWidget::Mode_LargeSidebar) {
690     setIconSize(QSize(iconsize_largesidebar_, iconsize_largesidebar_));
691   }
692   else {
693     setIconSize(QSize(iconsize_smallsidebar_, iconsize_smallsidebar_));
694   }
695 #endif
696 
697   tabBar()->updateGeometry();
698   updateGeometry();
699 
700   // There appears to be a bug in QTabBar which causes tabSizeHint to be ignored thus the need for this second shot repaint
701   QTimer::singleShot(1ms, this, &FancyTabWidget::tabBarUpdateGeometry);
702 
703   emit ModeChanged(mode);
704 
705 }
706 
addMenuItem(QActionGroup * group,const QString & text,Mode mode)707 void FancyTabWidget::addMenuItem(QActionGroup *group, const QString &text, Mode mode) {
708 
709   QAction *action = group->addAction(text);
710   action->setCheckable(true);
711   QObject::connect(action, &QAction::triggered, this, [this, mode]() { SetMode(mode); });
712 
713   if (mode == mode_) action->setChecked(true);
714 
715 }
716 
contextMenuEvent(QContextMenuEvent * e)717 void FancyTabWidget::contextMenuEvent(QContextMenuEvent *e) {
718 
719   if (!menu_) {
720     menu_ = new QMenu(this);
721     QActionGroup *group = new QActionGroup(this);
722     addMenuItem(group, tr("Large sidebar"), Mode_LargeSidebar);
723     addMenuItem(group, tr("Small sidebar"), Mode_SmallSidebar);
724     addMenuItem(group, tr("Plain sidebar"), Mode_PlainSidebar);
725     addMenuItem(group, tr("Tabs on top"), Mode_Tabs);
726     addMenuItem(group, tr("Icons on top"), Mode_IconOnlyTabs);
727     menu_->addActions(group->actions());
728   }
729 
730   menu_->popup(e->globalPos());
731 
732 }
733