1 /* This file is part of Clementine.
2 Copyright 2018, Vikram Ambrose <ambroseworks@gmail.com>
3
4 Clementine is free software: you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation, either version 3 of the License, or
7 (at your option) any later version.
8
9 Clementine is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
13
14 You should have received a copy of the GNU General Public License
15 along with Clementine. If not, see <http://www.gnu.org/licenses/>.
16 */
17
18
19 #include "fancytabwidget.h"
20 #include "stylehelper.h"
21 #include "core/logging.h"
22
23 #include <QDebug>
24
25 #include <QMenu>
26 #include <QMouseEvent>
27 #include <QPainter>
28 #include <QTabBar>
29 #include <QStylePainter>
30 #include <QTimer>
31 #include <QVBoxLayout>
32 #include <QSettings>
33
34 const QSize FancyTabWidget::IconSize_LargeSidebar = QSize(24,24);
35 const QSize FancyTabWidget::IconSize_SmallSidebar = QSize(22,22);
36
37 const QSize FancyTabWidget::TabSize_LargeSidebar = QSize(70,47);
38
39 class FancyTabBar: public QTabBar {
40
41 private:
42 int mouseHoverTabIndex = -1;
43 QMap<QWidget*,QString> labelCache;
44
45 public:
FancyTabBar(QWidget * parent=0)46 explicit FancyTabBar(QWidget* parent=0) : QTabBar(parent) {
47 setMouseTracking(true);
48 }
49
sizeHint() const50 QSize sizeHint() const {
51 QSize size(QTabBar::sizeHint());
52
53 FancyTabWidget *tabWidget = (FancyTabWidget*) parentWidget();
54 if(tabWidget->mode() == FancyTabWidget::Mode_Tabs ||
55 tabWidget->mode() == FancyTabWidget::Mode_IconOnlyTabs)
56 return size;
57
58 QSize tabSize(tabSizeHint(0));
59 size.setWidth(tabSize.width());
60 int guessHeight = tabSize.height()*count();
61 if(guessHeight > size.height())
62 size.setHeight(guessHeight);
63 return size;
64 }
65
width()66 int width() {
67 return tabSizeHint(0).width();
68 }
69
70 protected:
tabSizeHint(int index) const71 QSize tabSizeHint(int index) const {
72 FancyTabWidget *tabWidget = (FancyTabWidget*) parentWidget();
73 QSize size = FancyTabWidget::TabSize_LargeSidebar;
74
75 if(tabWidget->mode() != FancyTabWidget::Mode_LargeSidebar) {
76 size = QTabBar::tabSizeHint(index);
77 }
78
79 return size;
80 }
81
leaveEvent(QEvent * event)82 void leaveEvent(QEvent * event) {
83 mouseHoverTabIndex = -1;
84 update();
85 }
86
mouseMoveEvent(QMouseEvent * event)87 void mouseMoveEvent(QMouseEvent * event) {
88 QPoint pos = event->pos();
89
90 mouseHoverTabIndex = tabAt(pos);
91 if(mouseHoverTabIndex > -1)
92 update();
93 QTabBar::mouseMoveEvent(event);
94 }
95
paintEvent(QPaintEvent * pe)96 void paintEvent(QPaintEvent *pe) {
97 FancyTabWidget *tabWidget = (FancyTabWidget*) parentWidget();
98
99 bool verticalTextTabs = false;
100
101 if(tabWidget->mode() == FancyTabWidget::Mode_SmallSidebar)
102 verticalTextTabs = true;
103
104 // Restore any label text that was hidden/cached for the IconOnlyTabs mode
105 if(labelCache.count() > 0 && tabWidget->mode() != FancyTabWidget::Mode_IconOnlyTabs) {
106 for(int i =0; i < count(); i++) {
107 setTabText(i,labelCache[tabWidget->widget(i)]);
108 }
109 labelCache.clear();
110 }
111 if(tabWidget->mode() != FancyTabWidget::Mode_LargeSidebar &&
112 tabWidget->mode() != FancyTabWidget::Mode_SmallSidebar) {
113 // Cache and hide label text for IconOnlyTabs mode
114 if(tabWidget->mode() == FancyTabWidget::Mode_IconOnlyTabs && labelCache.count() == 0) {
115 for(int i =0; i < count(); i++) {
116 labelCache[tabWidget->widget(i)] = tabText(i);
117 setTabText(i,"");
118 }
119 }
120 QTabBar::paintEvent(pe);
121 return;
122 }
123
124 QStylePainter p(this);
125
126
127 for (int index = 0; index < count(); index++) {
128 const bool selected = tabWidget->currentIndex() == index;;
129
130 QRect tabrect = tabRect(index);
131
132 QRect selectionRect = tabrect;
133
134 if(selected) {
135 // Selection highlight
136 p.save();
137 QLinearGradient grad(selectionRect.topLeft(), selectionRect.topRight());
138 grad.setColorAt(0, QColor(255, 255, 255, 140));
139 grad.setColorAt(1, QColor(255, 255, 255, 210));
140 p.fillRect(selectionRect.adjusted(0,0,0,-1), grad);
141 p.restore();
142
143 // shadow lines
144 p.setPen(QColor(0, 0, 0, 110));
145 p.drawLine(selectionRect.topLeft() + QPoint(1, -1), selectionRect.topRight() - QPoint(0, 1));
146 p.drawLine(selectionRect.bottomLeft(), selectionRect.bottomRight());
147 p.setPen(QColor(0, 0, 0, 40));
148 p.drawLine(selectionRect.topLeft(), selectionRect.bottomLeft());
149
150 // highlights
151 p.setPen(QColor(255, 255, 255, 50));
152 p.drawLine(selectionRect.topLeft() + QPoint(0, -2), selectionRect.topRight() - QPoint(0, 2));
153 p.drawLine(selectionRect.bottomLeft() + QPoint(0, 1), selectionRect.bottomRight() + QPoint(0, 1));
154 p.setPen(QColor(255, 255, 255, 40));
155 p.drawLine(selectionRect.topLeft() + QPoint(0, 0), selectionRect.topRight());
156 p.drawLine(selectionRect.topRight() + QPoint(0, 1), selectionRect.bottomRight() - QPoint(0, 1));
157 p.drawLine(selectionRect.bottomLeft() + QPoint(0, -1), selectionRect.bottomRight() - QPoint(0, 1));
158
159 }
160
161 // Mouse hover effect
162 if(!selected && index == mouseHoverTabIndex && isTabEnabled(index))
163 {
164 p.save();
165 QLinearGradient grad(selectionRect.topLeft(), selectionRect.topRight());
166 grad.setColorAt(0, Qt::transparent);
167 grad.setColorAt(0.5, QColor(255, 255, 255, 40));
168 grad.setColorAt(1, Qt::transparent);
169 p.fillRect(selectionRect, grad);
170 p.setPen(QPen(grad, 1.0));
171 p.drawLine(selectionRect.topLeft(), selectionRect.topRight());
172 p.drawLine(selectionRect.bottomRight(), selectionRect.bottomLeft());
173 p.restore();
174 }
175
176 // Label (Icon and Text)
177 {
178 p.save();
179 QTransform m;
180 int textFlags;
181 Qt::Alignment iconFlags;
182
183 QRect tabrectText;
184 QRect tabrectLabel;
185
186 if (verticalTextTabs) {
187 m = QTransform::fromTranslate(tabrect.left(), tabrect.bottom());
188 m.rotate(-90);
189 textFlags = Qt::AlignLeft | Qt::AlignVCenter ;
190 iconFlags = Qt::AlignLeft | Qt::AlignVCenter;
191
192 tabrectLabel = QRect(QPoint(0, 0), m.mapRect(tabrect).size());
193
194 tabrectText = tabrectLabel;
195 tabrectText.translate(30,0);
196 } else {
197 m = QTransform::fromTranslate(tabrect.left(), tabrect.top());
198 textFlags = Qt::AlignHCenter | Qt::AlignBottom ;
199 iconFlags = Qt::AlignHCenter | Qt::AlignTop;
200
201 tabrectLabel = QRect(QPoint(0, 0), m.mapRect(tabrect).size());
202
203 tabrectText = tabrectLabel;
204 tabrectText.translate(0,-5);
205 }
206
207 p.setTransform(m);
208
209 QFont boldFont(p.font());
210 boldFont.setPointSizeF(Utils::StyleHelper::sidebarFontSize());
211 boldFont.setBold(true);
212 p.setFont(boldFont);
213
214 // Text drop shadow color
215 p.setPen(selected ? QColor(255,255,255,160) : QColor(0,0,0,110) );
216 p.translate(0, 3);
217 p.drawText(tabrectText, textFlags, tabText(index));
218
219 // Text foreground color
220 p.translate(0, -1);
221 p.setPen(selected ? QColor(60, 60, 60) : Utils::StyleHelper::panelTextColor());
222 p.drawText(tabrectText, textFlags, tabText(index));
223
224
225 // Draw the icon
226 QRect tabrectIcon;
227 const int PADDING = 5;
228 if(verticalTextTabs) {
229 tabrectIcon = tabrectLabel;
230 tabrectIcon.setSize(FancyTabWidget::IconSize_SmallSidebar);
231 tabrectIcon.translate(PADDING,PADDING);
232 } else {
233 tabrectIcon = tabrectLabel;
234 tabrectIcon.setSize(FancyTabWidget::IconSize_LargeSidebar);
235 // Center the icon
236 const int moveRight = (FancyTabWidget::TabSize_LargeSidebar.width() -
237 FancyTabWidget::IconSize_LargeSidebar.width() -1)/2;
238 tabrectIcon.translate(moveRight,PADDING);
239 }
240 tabIcon(index).paint(&p, tabrectIcon, iconFlags);
241 p.restore();
242 }
243 }
244 }
245 };
246
247 // Spacers are just disabled pages
addSpacer()248 void FancyTabWidget::addSpacer() {
249 QWidget *spacer = new QWidget();
250 const int index = addTab(spacer,QIcon(),QString());
251 setTabEnabled(index,false);
252 }
253
setBackgroundPixmap(const QPixmap & pixmap)254 void FancyTabWidget::setBackgroundPixmap(const QPixmap& pixmap) {
255 background_pixmap_ = pixmap;
256 update();
257 }
258
setCurrentIndex(int index)259 void FancyTabWidget::setCurrentIndex(int index) {
260 QWidget* currentPage = widget(index);
261
262 QLayout *layout = currentPage->layout();
263 if(bottom_widget_ != nullptr)
264 layout->addWidget(bottom_widget_);
265
266 QTabWidget::setCurrentIndex(index);
267 }
268
269 // Slot
currentTabChanged(int index)270 void FancyTabWidget::currentTabChanged(int index) {
271 QWidget* currentPage = currentWidget();
272
273 QLayout *layout = currentPage->layout();
274 if(bottom_widget_ != nullptr)
275 layout->addWidget(bottom_widget_);
276 emit CurrentChanged(index);
277 }
278
FancyTabWidget(QWidget * parent)279 FancyTabWidget::FancyTabWidget(QWidget* parent) : QTabWidget(parent),
280 menu_(nullptr),
281 mode_(Mode_None),
282 bottom_widget_(nullptr) {
283 FancyTabBar *tabBar = new FancyTabBar(this);
284
285 setTabBar(tabBar);
286 setTabPosition(QTabWidget::West);
287 setMovable(true);
288
289 connect(tabBar, SIGNAL(currentChanged(int)), this, SLOT(currentTabChanged(int)));
290 }
291
loadSettings(const QSettings & settings)292 void FancyTabWidget::loadSettings(const QSettings& settings) {
293 for (int i = 0; i < count(); i++) {
294 int originalIndex = tabBar()->tabData(i).toInt();
295 QString k = "tab_index_" + QString::number(originalIndex);
296
297 int newIndex = settings.value(k, i).toInt();
298
299 if (newIndex >= 0)
300 tabBar()->moveTab(i, newIndex);
301 else
302 removeTab(i); // Does not delete page
303 }
304 }
305
saveSettings(QSettings * settings)306 void FancyTabWidget::saveSettings(QSettings* settings) {
307 for (int i = 0; i < count(); i++) {
308 int originalIndex = tabBar()->tabData(i).toInt();
309 QString k = "tab_index_" + QString::number(originalIndex);
310
311 settings->setValue(k, i);
312 }
313 }
314
315
addBottomWidget(QWidget * widget)316 void FancyTabWidget::addBottomWidget(QWidget* widget) {
317 bottom_widget_ = widget;
318 }
319
addTab(QWidget * page,const QIcon & icon,const QString & label)320 int FancyTabWidget::addTab(QWidget * page, const QIcon & icon, const QString & label) {
321 return insertTab(count(),page,icon,label);
322 }
323
insertTab(int index,QWidget * page,const QIcon & icon,const QString & label)324 int FancyTabWidget::insertTab(int index, QWidget * page, const QIcon & icon, const QString & label) {
325 // In order to achieve the same effect as the "Bottom Widget" of the
326 // old Nokia based FancyTabWidget a VBoxLayout is used on each page
327 QVBoxLayout *layout = new QVBoxLayout();
328 layout->setSpacing(0);
329 layout->setContentsMargins(0,0,0,0);
330 layout->addWidget(page);
331
332 QWidget *newPage = new QWidget();
333 newPage->setLayout(layout);
334
335 const int actualIndex = QTabWidget::insertTab(index,newPage,icon,label);
336
337 // Remember the original index. Needed to save order of tabs
338 tabBar()->setTabData(actualIndex,QVariant(actualIndex));
339 return actualIndex;
340 }
341
paintEvent(QPaintEvent * pe)342 void FancyTabWidget::paintEvent(QPaintEvent *pe) {
343 if(mode() != FancyTabWidget::Mode_LargeSidebar &&
344 mode() != FancyTabWidget::Mode_SmallSidebar) {
345 QTabWidget::paintEvent(pe);
346 return;
347 }
348 QStylePainter p(this);
349
350 // The brown color (Ubuntu) you see on the background gradient
351 QColor baseColor = StyleHelper::baseColor();
352
353 QRect backgroundRect = rect();
354 backgroundRect.setWidth(((FancyTabBar*)tabBar())->width());
355 p.fillRect(backgroundRect,baseColor);
356
357 // Horizontal gradient over the sidebar from transparent to dark
358 Utils::StyleHelper::verticalGradient(&p,backgroundRect,backgroundRect,false);
359
360 // Draw the translucent png graphics over the gradient fill
361 {
362 if (!background_pixmap_.isNull()) {
363 QRect pixmap_rect(background_pixmap_.rect());
364 pixmap_rect.moveTo(backgroundRect.topLeft());
365
366 while (pixmap_rect.top() < backgroundRect.bottom()) {
367 QRect source_rect(pixmap_rect.intersected(backgroundRect));
368 source_rect.moveTo(0, 0);
369 p.drawPixmap(pixmap_rect.topLeft(), background_pixmap_,source_rect);
370 pixmap_rect.moveTop(pixmap_rect.bottom() - 10);
371 }
372 }
373 }
374
375 // Shadow effect of the background
376 {
377 QColor light(255, 255, 255, 80);
378 p.setPen(light);
379 p.drawLine(backgroundRect.topRight() - QPoint(1, 0), backgroundRect.bottomRight() - QPoint(1, 0));
380 QColor dark(0, 0, 0, 90);
381 p.setPen(dark);
382 p.drawLine(backgroundRect.topLeft(), backgroundRect.bottomLeft());
383
384 p.setPen(Utils::StyleHelper::borderColor());
385 p.drawLine(backgroundRect.topRight(), backgroundRect.bottomRight());
386 }
387
388 }
389
tabBarUpdateGeometry()390 void FancyTabWidget::tabBarUpdateGeometry() {
391 tabBar()->updateGeometry();
392 }
393
SetMode(FancyTabWidget::Mode mode)394 void FancyTabWidget::SetMode(FancyTabWidget::Mode mode) {
395 mode_ = mode;
396
397 if(mode == FancyTabWidget::Mode_Tabs ||
398 mode == FancyTabWidget::Mode_IconOnlyTabs) {
399 setTabPosition(QTabWidget::North);
400 } else {
401 setTabPosition(QTabWidget::West);
402 }
403
404 tabBar()->updateGeometry();
405 updateGeometry();
406
407 // There appears to be a bug in QTabBar which causes tabSizeHint
408 // to be ignored thus the need for this second shot repaint
409 QTimer::singleShot(1,this,SLOT(tabBarUpdateGeometry()));
410
411 emit ModeChanged(mode);
412 }
413
addMenuItem(QActionGroup * group,const QString & text,Mode mode)414 void FancyTabWidget::addMenuItem(QActionGroup* group, const QString& text,
415 Mode mode) {
416 QAction* action = group->addAction(text);
417 action->setCheckable(true);
418 connect(action, &QAction::triggered, [this, mode]() { SetMode(mode); });
419
420 if (mode == mode_) action->setChecked(true);
421 }
422
423
contextMenuEvent(QContextMenuEvent * e)424 void FancyTabWidget::contextMenuEvent(QContextMenuEvent* e) {
425 if (!menu_) {
426 menu_ = new QMenu(this);
427
428 QActionGroup* group = new QActionGroup(this);
429 addMenuItem(group, tr("Large sidebar"), Mode_LargeSidebar);
430 addMenuItem(group, tr("Small sidebar"), Mode_SmallSidebar);
431 addMenuItem(group, tr("Plain sidebar"), Mode_PlainSidebar);
432 addMenuItem(group, tr("Tabs on top"), Mode_Tabs);
433 addMenuItem(group, tr("Icons on top"), Mode_IconOnlyTabs);
434 menu_->addActions(group->actions());
435 }
436
437 menu_->popup(e->globalPos());
438 }
439