strawberry-audio-player-win.../src/widgets/fancytabwidget.cpp

569 lines
17 KiB
C++
Raw Normal View History

/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2018, Vikram Ambrose <ambroseworks@gmail.com>
* Copyright 2018, Jonas Kvinge <jonas@jkvinge.net>
*
* Strawberry 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.
*
* Strawberry 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 Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
2018-02-27 18:06:05 +01:00
#include "fancytabwidget.h"
2019-04-25 00:01:26 +02:00
#include "core/stylehelper.h"
#include "core/logging.h"
#include <QDebug>
2018-02-27 18:06:05 +01:00
#include <QObject>
#include <QTabBar>
#include <QWidget>
#include <QMap>
#include <QHash>
#include <QString>
#include <QIcon>
#include <QMenu>
#include <QPainter>
#include <QColor>
#include <QRect>
#include <QFont>
2018-02-27 18:06:05 +01:00
#include <QSignalMapper>
#include <QStylePainter>
#include <QTimer>
#include <QLayout>
#include <QVBoxLayout>
#include <QMouseEvent>
#include <QSettings>
#include <QSize>
#include <QPoint>
#include <QTransform>
#include <QAction>
const QSize FancyTabWidget::IconSize_LargeSidebar = QSize(24, 24);
const QSize FancyTabWidget::IconSize_SmallSidebar = QSize(22, 22);
const QSize FancyTabWidget::TabSize_LargeSidebar = QSize(70, 47);
2018-02-27 18:06:05 +01:00
class FancyTabBar: public QTabBar {
2018-02-27 18:06:05 +01:00
private:
int mouseHoverTabIndex = -1;
2018-10-20 22:16:22 +02:00
QMap<QWidget*, QString> labelCache;
QMap<int, QWidget*> spacers;
2018-02-27 18:06:05 +01:00
public:
explicit FancyTabBar(QWidget* parent=0) : QTabBar(parent) {
setMouseTracking(true);
}
2018-02-27 18:06:05 +01:00
QSize sizeHint() const {
2018-02-27 18:06:05 +01:00
QSize size(QTabBar::sizeHint());
2018-02-27 18:06:05 +01:00
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;
2018-02-27 18:06:05 +01:00
}
int width() {
return tabSizeHint(0).width();
2018-02-27 18:06:05 +01:00
}
protected:
QSize tabSizeHint(int index) const {
2018-02-27 18:06:05 +01:00
FancyTabWidget *tabWidget = (FancyTabWidget*) parentWidget();
QSize size = FancyTabWidget::TabSize_LargeSidebar;
2018-02-27 18:06:05 +01:00
2019-04-23 23:03:59 +02:00
if (tabWidget->mode() != FancyTabWidget::Mode_LargeSidebar) {
size = QTabBar::tabSizeHint(index);
2018-02-27 18:06:05 +01:00
}
2019-04-23 23:03:59 +02:00
size.setWidth(std::max(size.width(), 37));
2018-02-27 18:06:05 +01:00
return size;
2018-02-27 18:06:05 +01:00
}
2018-02-27 18:06:05 +01:00
void leaveEvent(QEvent *event) {
mouseHoverTabIndex = -1;
update();
2018-02-27 18:06:05 +01:00
}
void mouseMoveEvent(QMouseEvent *event) {
2018-02-27 18:06:05 +01:00
QPoint pos = event->pos();
2018-02-27 18:06:05 +01:00
mouseHoverTabIndex = tabAt(pos);
if (mouseHoverTabIndex > -1)
update();
QTabBar::mouseMoveEvent(event);
2018-02-27 18:06:05 +01:00
}
void paintEvent(QPaintEvent *pe) {
2018-02-27 18:06:05 +01:00
FancyTabWidget *tabWidget = (FancyTabWidget*) parentWidget();
2018-02-27 18:06:05 +01:00
bool verticalTextTabs = false;
2018-02-27 18:06:05 +01:00
if (tabWidget->mode() == FancyTabWidget::Mode_SmallSidebar)
verticalTextTabs = true;
2018-02-27 18:06:05 +01:00
// if LargeSidebar, restore spacers
2018-10-20 22:16:22 +02:00
if (tabWidget->mode() == FancyTabWidget::Mode_LargeSidebar && spacers.count() > 0) {
for (int index : spacers.keys()) {
tabWidget->insertTab(index, spacers[index], QIcon(), QString());
tabWidget->setTabEnabled(index, false);
}
spacers.clear();
}
2018-10-20 22:16:22 +02:00
else if (tabWidget->mode() != FancyTabWidget::Mode_LargeSidebar) {
// traverse in the opposite order to save indices of spacers
for (int i = count() - 1; i >= 0; --i) {
// spacers are disabled tabs
if (!isTabEnabled(i) && !spacers.contains(i)) {
spacers[i] = tabWidget->widget(i);
tabWidget->removeTab(i);
--i;
}
}
}
2018-02-27 18:06:05 +01:00
// Restore any label text that was hidden/cached for the IconOnlyTabs mode
if (labelCache.count() > 0 && tabWidget->mode() != FancyTabWidget::Mode_IconOnlyTabs) {
for (int i =0; i < count(); i++) {
setTabText(i, labelCache[tabWidget->widget(i)]);
}
labelCache.clear();
}
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 && labelCache.count() == 0) {
for(int i =0; i < count(); i++) {
labelCache[tabWidget->widget(i)] = tabText(i);
setTabText(i, "");
}
}
QTabBar::paintEvent(pe);
return;
}
2018-02-27 18:06:05 +01:00
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));
2018-02-27 18:06:05 +01:00
}
2018-02-27 18:06:05 +01:00
// 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();
}
2018-02-27 18:06:05 +01:00
// 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());
2019-04-25 00:01:26 +02:00
boldFont.setPointSizeF(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);
2019-04-25 00:01:26 +02:00
p.setPen(selected ? QColor(60, 60, 60) : 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();
}
}
}
2018-02-27 18:06:05 +01:00
};
2018-02-27 18:06:05 +01:00
class TabData : public QObject {
public:
2019-05-03 17:32:55 +02:00
TabData(QWidget *widget_view, const QString name, const QIcon icon, const QString label, const bool enabled, const int idx, QWidget *parent) :
QObject(parent),
widget_view_(widget_view),
name_(name), icon_(icon),
label_(label),
2019-05-03 17:32:55 +02:00
enabled_(enabled),
index_(idx),
page_(new QWidget()) {
// 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(page_);
layout->setSpacing(0);
layout->setContentsMargins(0, 0, 0, 0);
layout->addWidget(widget_view_);
page_->setLayout(layout);
}
~TabData() {
//delete page_;
}
QWidget *widget_view() { return widget_view_; }
QString name() { return name_; }
QIcon icon() { return icon_; }
QString label() { return label_; }
QWidget *page() { return page_; }
bool enabled() { return enabled_; }
2019-05-03 17:32:55 +02:00
int index() { return index_; }
void set_enabled(bool enabled) { enabled_ = enabled; }
private:
QWidget *widget_view_;
QString name_;
QIcon icon_;
QString label_;
bool enabled_;
2019-05-03 17:32:55 +02:00
int index_;
QWidget *page_;
};
FancyTabWidget::~FancyTabWidget() {}
// Spacers are just disabled pages
void FancyTabWidget::addSpacer() {
2018-02-27 18:06:05 +01:00
2018-10-20 22:16:22 +02:00
QWidget *spacer = new QWidget(this);
const int index = insertTab(count(), spacer, QIcon(), QString());
setTabEnabled(index, false);
2018-02-27 18:06:05 +01:00
}
void FancyTabWidget::setBackgroundPixmap(const QPixmap& pixmap) {
background_pixmap_ = pixmap;
update();
2018-02-27 18:06:05 +01:00
}
void FancyTabWidget::setCurrentIndex(int index) {
2018-02-27 18:06:05 +01:00
if (index >= count()) index = 0;
2018-02-27 18:06:05 +01:00
QWidget *currentPage = widget(index);
QLayout *layout = currentPage->layout();
if (bottom_widget_) layout->addWidget(bottom_widget_);
QTabWidget::setCurrentIndex(index);
2018-02-27 18:06:05 +01:00
}
void FancyTabWidget::currentTabChanged(int index) {
2018-02-27 18:06:05 +01:00
QWidget *currentPage = currentWidget();
QLayout *layout = currentPage->layout();
if (bottom_widget_) layout->addWidget(bottom_widget_);
emit CurrentChanged(index);
2018-02-27 18:06:05 +01:00
}
FancyTabWidget::FancyTabWidget(QWidget* parent) : QTabWidget(parent),
menu_(nullptr),
mode_(Mode_None),
bottom_widget_(nullptr)
{
2018-02-27 18:06:05 +01:00
FancyTabBar *tabBar = new FancyTabBar(this);
setTabBar(tabBar);
setTabPosition(QTabWidget::West);
setMovable(true);
connect(tabBar, SIGNAL(currentChanged(int)), this, SLOT(currentTabChanged(int)));
2018-02-27 18:06:05 +01:00
}
void FancyTabWidget::Load(const QString &kSettingsGroup) {
2018-02-27 18:06:05 +01:00
QSettings s;
s.beginGroup(kSettingsGroup);
2019-05-03 17:32:55 +02:00
QMultiMap <int, TabData*> tabs;
for (TabData *tab : tabs_) {
2019-05-03 17:32:55 +02:00
const int idx = s.value("tab_" + tab->name(), tab->index()).toInt();
tabs.insert(idx, tab);
}
s.endGroup();
2019-05-03 17:32:55 +02:00
QMultiMap <int, TabData*> ::iterator i;
for (i = tabs.begin() ; i != tabs.end() ; ++i) {
TabData *tab = i.value();
const int actualIndex = insertTab(i.key(), tab->page(), tab->icon(), tab->label());
tabBar()->setTabData(actualIndex, QVariant(tab->name()));
tab->set_enabled(true);
2018-02-27 18:06:05 +01:00
}
}
2018-02-27 18:06:05 +01:00
int FancyTabWidget::insertTab(int index, QWidget *page, const QIcon &icon, const QString &label) {
return QTabWidget::insertTab(index, page, icon, label);
2018-02-27 18:06:05 +01:00
}
void FancyTabWidget::SaveSettings(const QString &kSettingsGroup) {
QSettings s;
s.beginGroup(kSettingsGroup);
2018-02-27 18:06:05 +01:00
s.setValue("tab_mode", mode_);
s.setValue("current_tab", currentIndex());
2018-02-27 18:06:05 +01:00
for (int i = 0 ; i < count() ; i++) {
QString k = "tab_" + tabBar()->tabData(i).toString().toLower();
s.setValue(k, i);
2018-02-27 18:06:05 +01:00
}
s.endGroup();
2018-02-27 18:06:05 +01:00
}
void FancyTabWidget::addBottomWidget(QWidget *widget_view) {
bottom_widget_ = widget_view;
2018-02-27 18:06:05 +01:00
}
void FancyTabWidget::AddTab(QWidget *widget_view, const QString &name, const QIcon &icon, const QString &label) {
2018-02-27 18:06:05 +01:00
2019-05-03 17:32:55 +02:00
TabData *tab = new TabData(widget_view, name, icon, label, false, tabs_.count(), this);
tabs_.insert(widget_view, tab);
}
2018-02-27 18:06:05 +01:00
bool FancyTabWidget::EnableTab(QWidget *widget_view) {
2018-02-27 18:06:05 +01:00
if (!tabs_.contains(widget_view)) return false;
TabData *tab = tabs_.value(widget_view);
if (tab->enabled()) return true;
const int actualIndex = QTabWidget::insertTab(count(), tab->page(), tab->icon(), tab->label());
tabBar()->setTabData(actualIndex, QVariant(tab->name()));
tab->set_enabled(true);
return true;
2018-02-27 18:06:05 +01:00
}
bool FancyTabWidget::DisableTab(QWidget *widget_view) {
if (!tabs_.contains(widget_view)) return false;
TabData *tab = tabs_.value(widget_view);
2018-02-27 18:06:05 +01:00
for (int i = 0 ; i < count() ; i++) {
if (tabBar()->tabData(i).toString() == tab->name()) {
2018-10-20 22:16:22 +02:00
removeTab(i);
tab->set_enabled(false);
return true;
2018-10-20 22:16:22 +02:00
}
}
return false;
2018-02-27 18:06:05 +01:00
}
void FancyTabWidget::paintEvent(QPaintEvent *pe) {
2018-02-27 18:06:05 +01:00
if (mode() != FancyTabWidget::Mode_LargeSidebar && mode() != FancyTabWidget::Mode_SmallSidebar) {
QTabWidget::paintEvent(pe);
return;
}
QStylePainter p(this);
2018-02-27 18:06:05 +01:00
// The brown color (Ubuntu) you see on the background gradient
QColor baseColor = StyleHelper::baseColor();
2018-02-27 18:06:05 +01:00
QRect backgroundRect = rect();
2019-04-08 18:46:11 +02:00
backgroundRect.setWidth(tabBar()->width());
p.fillRect(backgroundRect, baseColor);
2018-02-27 18:06:05 +01:00
// Horizontal gradient over the sidebar from transparent to dark
2019-04-25 00:01:26 +02:00
StyleHelper::verticalGradient(&p, backgroundRect, backgroundRect, false);
2018-02-27 18:06:05 +01:00
// Draw the translucent png graphics over the gradient fill
{
if (!background_pixmap_.isNull()) {
QRect pixmap_rect(background_pixmap_.rect());
pixmap_rect.moveTo(backgroundRect.topLeft());
2018-02-27 18:06:05 +01:00
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);
}
}
}
2018-02-27 18:06:05 +01:00
// 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());
2019-04-25 00:01:26 +02:00
p.setPen(StyleHelper::borderColor());
p.drawLine(backgroundRect.topRight(), backgroundRect.bottomRight());
2018-02-27 18:06:05 +01:00
}
}
2018-02-27 18:06:05 +01:00
void FancyTabWidget::tabBarUpdateGeometry() {
tabBar()->updateGeometry();
2018-02-27 18:06:05 +01:00
}
void FancyTabWidget::SetMode(FancyTabWidget::Mode mode) {
2018-02-27 18:06:05 +01:00
mode_ = mode;
2018-02-27 18:06:05 +01:00
if (mode == FancyTabWidget::Mode_Tabs || mode == FancyTabWidget::Mode_IconOnlyTabs) {
setTabPosition(QTabWidget::North);
}
else {
setTabPosition(QTabWidget::West);
2018-02-27 18:06:05 +01:00
}
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);
2018-02-27 18:06:05 +01:00
}
void FancyTabWidget::addMenuItem(QSignalMapper* mapper, QActionGroup* group, const QString& text, Mode mode) {
2018-02-27 18:06:05 +01:00
QAction* action = group->addAction(text);
action->setCheckable(true);
mapper->setMapping(action, mode);
connect(action, SIGNAL(triggered()), mapper, SLOT(map()));
if (mode == mode_) action->setChecked(true);
}
2018-02-27 18:06:05 +01:00
void FancyTabWidget::contextMenuEvent(QContextMenuEvent* e) {
2018-02-27 18:06:05 +01:00
if (!menu_) {
2018-02-27 18:06:05 +01:00
menu_ = new QMenu(this);
QSignalMapper* mapper = new QSignalMapper(this);
QActionGroup* group = new QActionGroup(this);
addMenuItem(mapper, group, tr("Large sidebar"), Mode_LargeSidebar);
addMenuItem(mapper, group, tr("Small sidebar"), Mode_SmallSidebar);
addMenuItem(mapper, group, tr("Plain sidebar"), Mode_PlainSidebar);
addMenuItem(mapper, group, tr("Tabs on top"), Mode_Tabs);
addMenuItem(mapper, group, tr("Icons on top"), Mode_IconOnlyTabs);
menu_->addActions(group->actions());
connect(mapper, SIGNAL(mapped(int)), SLOT(SetMode(int)));
2018-02-27 18:06:05 +01:00
}
menu_->popup(e->globalPos());
2018-02-27 18:06:05 +01:00
}