Clementine-audio-player-Mac.../src/widgets/fancytabwidget.cpp

442 lines
14 KiB
C++

/* This file is part of Clementine.
Copyright 2018, Vikram Ambrose <ambroseworks@gmail.com>
Clementine 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 3 of the License, or
(at your option) any later version.
Clementine 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 Clementine. If not, see <http://www.gnu.org/licenses/>.
*/
#include "fancytabwidget.h"
#include <QDebug>
#include <QMenu>
#include <QMouseEvent>
#include <QPainter>
#include <QSettings>
#include <QStylePainter>
#include <QTabBar>
#include <QTimer>
#include <QVBoxLayout>
#include "core/logging.h"
#include "stylehelper.h"
const QSize FancyTabWidget::IconSize_LargeSidebar = QSize(24, 24);
const QSize FancyTabWidget::IconSize_SmallSidebar = QSize(22, 22);
const QSize FancyTabWidget::TabSize_LargeSidebar = QSize(70, 47);
class FancyTabBar : public QTabBar {
private:
int mouseHoverTabIndex = -1;
bool isTextHiddenInToolTip = false;
public:
explicit FancyTabBar(QWidget* parent = 0) : QTabBar(parent) {
setMouseTracking(true);
}
QSize sizeHint() const {
QSize size(QTabBar::sizeHint());
FancyTabWidget* tabWidget = (FancyTabWidget*)parentWidget();
if (tabWidget->mode() == FancyTabWidget::Mode_Tabs ||
tabWidget->mode() == FancyTabWidget::Mode_IconOnlyTabs)
return size;
QSize tabSize(tabSizeHint(0));
size.setWidth(tabSize.width());
int guessHeight = tabSize.height() * count();
if (guessHeight > size.height()) size.setHeight(guessHeight);
return size;
}
int width() { return tabSizeHint(0).width(); }
protected:
QSize tabSizeHint(int index) const {
FancyTabWidget* tabWidget = (FancyTabWidget*)parentWidget();
QSize size = FancyTabWidget::TabSize_LargeSidebar;
if (tabWidget->mode() != FancyTabWidget::Mode_LargeSidebar) {
size = QTabBar::tabSizeHint(index);
}
return size;
}
void leaveEvent(QEvent* event) {
mouseHoverTabIndex = -1;
update();
}
void mouseMoveEvent(QMouseEvent* event) {
QPoint pos = event->pos();
mouseHoverTabIndex = tabAt(pos);
if (mouseHoverTabIndex > -1) update();
QTabBar::mouseMoveEvent(event);
}
void paintEvent(QPaintEvent* pe) {
FancyTabWidget* tabWidget = (FancyTabWidget*)parentWidget();
bool verticalTextTabs = false;
if (tabWidget->mode() == FancyTabWidget::Mode_SmallSidebar)
verticalTextTabs = true;
// Restore any label text that was hidden/cached for the IconOnlyTabs mode
if (isTextHiddenInToolTip &&
tabWidget->mode() != FancyTabWidget::Mode_IconOnlyTabs) {
for (int i = 0; i < count(); i++) {
setTabText(i, tabToolTip(i));
setTabToolTip(i, "");
}
isTextHiddenInToolTip = false;
}
if (tabWidget->mode() != FancyTabWidget::Mode_LargeSidebar &&
tabWidget->mode() != FancyTabWidget::Mode_SmallSidebar) {
// Cache and hide label text for IconOnlyTabs mode
if (tabWidget->mode() == FancyTabWidget::Mode_IconOnlyTabs &&
!isTextHiddenInToolTip) {
for (int i = 0; i < count(); i++) {
isTextHiddenInToolTip = true;
setTabToolTip(i, tabText(i));
setTabText(i, "");
}
}
QTabBar::paintEvent(pe);
return;
}
QStylePainter p(this);
for (int index = 0; index < count(); index++) {
const bool selected = tabWidget->currentIndex() == index;
;
QRect tabrect = tabRect(index);
QRect selectionRect = tabrect;
if (selected) {
// Selection highlight
p.save();
QLinearGradient grad(selectionRect.topLeft(), selectionRect.topRight());
grad.setColorAt(0, QColor(255, 255, 255, 140));
grad.setColorAt(1, QColor(255, 255, 255, 210));
p.fillRect(selectionRect.adjusted(0, 0, 0, -1), grad);
p.restore();
// shadow lines
p.setPen(QColor(0, 0, 0, 110));
p.drawLine(selectionRect.topLeft() + QPoint(1, -1),
selectionRect.topRight() - QPoint(0, 1));
p.drawLine(selectionRect.bottomLeft(), selectionRect.bottomRight());
p.setPen(QColor(0, 0, 0, 40));
p.drawLine(selectionRect.topLeft(), selectionRect.bottomLeft());
// highlights
p.setPen(QColor(255, 255, 255, 50));
p.drawLine(selectionRect.topLeft() + QPoint(0, -2),
selectionRect.topRight() - QPoint(0, 2));
p.drawLine(selectionRect.bottomLeft() + QPoint(0, 1),
selectionRect.bottomRight() + QPoint(0, 1));
p.setPen(QColor(255, 255, 255, 40));
p.drawLine(selectionRect.topLeft() + QPoint(0, 0),
selectionRect.topRight());
p.drawLine(selectionRect.topRight() + QPoint(0, 1),
selectionRect.bottomRight() - QPoint(0, 1));
p.drawLine(selectionRect.bottomLeft() + QPoint(0, -1),
selectionRect.bottomRight() - QPoint(0, 1));
}
// Mouse hover effect
if (!selected && index == mouseHoverTabIndex && isTabEnabled(index)) {
p.save();
QLinearGradient grad(selectionRect.topLeft(), selectionRect.topRight());
grad.setColorAt(0, Qt::transparent);
grad.setColorAt(0.5, QColor(255, 255, 255, 40));
grad.setColorAt(1, Qt::transparent);
p.fillRect(selectionRect, grad);
p.setPen(QPen(grad, 1.0));
p.drawLine(selectionRect.topLeft(), selectionRect.topRight());
p.drawLine(selectionRect.bottomRight(), selectionRect.bottomLeft());
p.restore();
}
// Label (Icon and Text)
{
p.save();
QTransform m;
int textFlags;
Qt::Alignment iconFlags;
QRect tabrectText;
QRect tabrectLabel;
if (verticalTextTabs) {
m = QTransform::fromTranslate(tabrect.left(), tabrect.bottom());
m.rotate(-90);
textFlags = Qt::AlignLeft | Qt::AlignVCenter;
iconFlags = Qt::AlignLeft | Qt::AlignVCenter;
tabrectLabel = QRect(QPoint(0, 0), m.mapRect(tabrect).size());
tabrectText = tabrectLabel;
tabrectText.translate(30, 0);
} else {
m = QTransform::fromTranslate(tabrect.left(), tabrect.top());
textFlags = Qt::AlignHCenter | Qt::AlignBottom;
iconFlags = Qt::AlignHCenter | Qt::AlignTop;
tabrectLabel = QRect(QPoint(0, 0), m.mapRect(tabrect).size());
tabrectText = tabrectLabel;
tabrectText.translate(0, -5);
}
p.setTransform(m);
QFont boldFont(p.font());
boldFont.setPointSizeF(Utils::StyleHelper::sidebarFontSize());
boldFont.setBold(true);
p.setFont(boldFont);
// Text drop shadow color
p.setPen(selected ? QColor(255, 255, 255, 160) : QColor(0, 0, 0, 110));
p.translate(0, 3);
p.drawText(tabrectText, textFlags, tabText(index));
// Text foreground color
p.translate(0, -1);
p.setPen(selected ? QColor(60, 60, 60)
: Utils::StyleHelper::panelTextColor());
p.drawText(tabrectText, textFlags, tabText(index));
// Draw the icon
QRect tabrectIcon;
const int PADDING = 5;
if (verticalTextTabs) {
tabrectIcon = tabrectLabel;
tabrectIcon.setSize(FancyTabWidget::IconSize_SmallSidebar);
tabrectIcon.translate(PADDING, PADDING);
} else {
tabrectIcon = tabrectLabel;
tabrectIcon.setSize(FancyTabWidget::IconSize_LargeSidebar);
// Center the icon
const int moveRight =
(FancyTabWidget::TabSize_LargeSidebar.width() -
FancyTabWidget::IconSize_LargeSidebar.width() - 1) /
2;
tabrectIcon.translate(moveRight, PADDING);
}
tabIcon(index).paint(&p, tabrectIcon, iconFlags);
p.restore();
}
}
}
};
// Spacers are just disabled pages
void FancyTabWidget::addSpacer() {
QWidget* spacer = new QWidget();
const int index = addTab(spacer, QIcon(), QString());
setTabEnabled(index, false);
}
void FancyTabWidget::setBackgroundPixmap(const QPixmap& pixmap) {
background_pixmap_ = pixmap;
update();
}
void FancyTabWidget::setCurrentIndex(int index) {
QWidget* currentPage = widget(index);
QLayout* layout = currentPage->layout();
if (bottom_widget_ != nullptr) layout->addWidget(bottom_widget_);
QTabWidget::setCurrentIndex(index);
}
// Slot
void FancyTabWidget::currentTabChanged(int index) {
QWidget* currentPage = currentWidget();
QLayout* layout = currentPage->layout();
if (bottom_widget_ != nullptr) layout->addWidget(bottom_widget_);
emit CurrentChanged(index);
}
FancyTabWidget::FancyTabWidget(QWidget* parent)
: QTabWidget(parent),
menu_(nullptr),
mode_(Mode_None),
bottom_widget_(nullptr) {
FancyTabBar* tabBar = new FancyTabBar(this);
setTabBar(tabBar);
setTabPosition(QTabWidget::West);
setMovable(true);
connect(tabBar, SIGNAL(currentChanged(int)), this,
SLOT(currentTabChanged(int)));
}
void FancyTabWidget::loadSettings(const QSettings& settings) {
for (int i = 0; i < count(); i++) {
int originalIndex = tabBar()->tabData(i).toInt();
QString k = "tab_index_" + QString::number(originalIndex);
int newIndex = settings.value(k, i).toInt();
if (newIndex >= 0)
tabBar()->moveTab(i, newIndex);
else
removeTab(i); // Does not delete page
}
}
void FancyTabWidget::saveSettings(QSettings* settings) {
for (int i = 0; i < count(); i++) {
int originalIndex = tabBar()->tabData(i).toInt();
QString k = "tab_index_" + QString::number(originalIndex);
settings->setValue(k, i);
}
}
void FancyTabWidget::addBottomWidget(QWidget* widget) {
bottom_widget_ = widget;
}
int FancyTabWidget::addTab(QWidget* page, const QIcon& icon,
const QString& label) {
return insertTab(count(), page, icon, label);
}
int FancyTabWidget::insertTab(int index, QWidget* page, const QIcon& icon,
const QString& label) {
// In order to achieve the same effect as the "Bottom Widget" of the
// old Nokia based FancyTabWidget a VBoxLayout is used on each page
QVBoxLayout* layout = new QVBoxLayout();
layout->setSpacing(0);
layout->setContentsMargins(0, 0, 0, 0);
layout->addWidget(page);
QWidget* newPage = new QWidget();
newPage->setLayout(layout);
const int actualIndex = QTabWidget::insertTab(index, newPage, icon, label);
// Remember the original index. Needed to save order of tabs
tabBar()->setTabData(actualIndex, QVariant(actualIndex));
return actualIndex;
}
void FancyTabWidget::paintEvent(QPaintEvent* pe) {
if (mode() != FancyTabWidget::Mode_LargeSidebar &&
mode() != FancyTabWidget::Mode_SmallSidebar) {
QTabWidget::paintEvent(pe);
return;
}
QStylePainter p(this);
// The brown color (Ubuntu) you see on the background gradient
QColor baseColor = StyleHelper::baseColor();
QRect backgroundRect = rect();
backgroundRect.setWidth(((FancyTabBar*)tabBar())->width());
p.fillRect(backgroundRect, baseColor);
// Horizontal gradient over the sidebar from transparent to dark
Utils::StyleHelper::verticalGradient(&p, backgroundRect, backgroundRect,
false);
// Draw the translucent png graphics over the gradient fill
{
if (!background_pixmap_.isNull()) {
QRect pixmap_rect(background_pixmap_.rect());
pixmap_rect.moveTo(backgroundRect.topLeft());
while (pixmap_rect.top() < backgroundRect.bottom()) {
QRect source_rect(pixmap_rect.intersected(backgroundRect));
source_rect.moveTo(0, 0);
p.drawPixmap(pixmap_rect.topLeft(), background_pixmap_, source_rect);
pixmap_rect.moveTop(pixmap_rect.bottom() - 10);
}
}
}
// Shadow effect of the background
{
QColor light(255, 255, 255, 80);
p.setPen(light);
p.drawLine(backgroundRect.topRight() - QPoint(1, 0),
backgroundRect.bottomRight() - QPoint(1, 0));
QColor dark(0, 0, 0, 90);
p.setPen(dark);
p.drawLine(backgroundRect.topLeft(), backgroundRect.bottomLeft());
p.setPen(Utils::StyleHelper::borderColor());
p.drawLine(backgroundRect.topRight(), backgroundRect.bottomRight());
}
}
void FancyTabWidget::tabBarUpdateGeometry() { tabBar()->updateGeometry(); }
void FancyTabWidget::SetMode(FancyTabWidget::Mode mode) {
mode_ = mode;
if (mode == FancyTabWidget::Mode_Tabs ||
mode == FancyTabWidget::Mode_IconOnlyTabs) {
setTabPosition(QTabWidget::North);
} else {
setTabPosition(QTabWidget::West);
}
tabBar()->updateGeometry();
updateGeometry();
// There appears to be a bug in QTabBar which causes tabSizeHint
// to be ignored thus the need for this second shot repaint
QTimer::singleShot(1, this, SLOT(tabBarUpdateGeometry()));
emit ModeChanged(mode);
}
void FancyTabWidget::addMenuItem(QActionGroup* group, const QString& text,
Mode mode) {
QAction* action = group->addAction(text);
action->setCheckable(true);
connect(action, &QAction::triggered, [this, mode]() { SetMode(mode); });
if (mode == mode_) action->setChecked(true);
}
void FancyTabWidget::contextMenuEvent(QContextMenuEvent* e) {
if (!menu_) {
menu_ = new QMenu(this);
QActionGroup* group = new QActionGroup(this);
addMenuItem(group, tr("Large sidebar"), Mode_LargeSidebar);
addMenuItem(group, tr("Small sidebar"), Mode_SmallSidebar);
addMenuItem(group, tr("Plain sidebar"), Mode_PlainSidebar);
addMenuItem(group, tr("Tabs on top"), Mode_Tabs);
addMenuItem(group, tr("Icons on top"), Mode_IconOnlyTabs);
menu_->addActions(group->actions());
}
menu_->popup(e->globalPos());
}