/* * Strawberry Music Player * This file was part of Clementine. * Copyright 2018, Vikram Ambrose * Copyright 2018-2024, Jonas Kvinge * * 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 . * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "fancytabwidget.h" #include "fancytabbar.h" #include "fancytabdata.h" #include "core/stylehelper.h" #include "core/settings.h" #include "settings/appearancesettingspage.h" using namespace std::chrono_literals; namespace { constexpr int IconSize_LargeSidebar = 40; constexpr int IconSize_SmallSidebar = 32; } // namespace FancyTabWidget::FancyTabWidget(QWidget *parent) : QTabWidget(parent), menu_(nullptr), mode_(Mode::None), bottom_widget_(nullptr), bg_color_system_(true), bg_gradient_(true), iconsize_smallsidebar_(IconSize_SmallSidebar), iconsize_largesidebar_(IconSize_LargeSidebar) { FancyTabBar *tabBar = new FancyTabBar(this); setTabBar(tabBar); setTabPosition(QTabWidget::West); setMovable(true); setElideMode(Qt::ElideNone); setUsesScrollButtons(true); QObject::connect(tabBar, &FancyTabBar::currentChanged, this, &FancyTabWidget::CurrentTabChangedSlot); } FancyTabWidget::~FancyTabWidget() {} void FancyTabWidget::AddTab(QWidget *widget_view, const QString &name, const QIcon &icon, const QString &label) { FancyTabData *tab = new FancyTabData(widget_view, name, icon, label, static_cast(tabs_.count()), this); tabs_.insert(widget_view, tab); } bool FancyTabWidget::EnableTab(QWidget *widget_view) { if (!tabs_.contains(widget_view)) return false; FancyTabData *tab = tabs_.value(widget_view); if (QTabWidget::indexOf(tab->page()) >= 0) return true; const int idx = QTabWidget::insertTab(count(), tab->page(), tab->icon(), tab->label()); tabBar()->setTabData(idx, QVariant(tab->name())); return true; } bool FancyTabWidget::DisableTab(QWidget *widget_view) { if (!tabs_.contains(widget_view)) return false; FancyTabData *tab = tabs_.value(widget_view); int idx = QTabWidget::indexOf(tab->page()); if (idx < 0) return false; removeTab(idx); return true; } void FancyTabWidget::LoadSettings(const QString &settings_group) { Settings s; s.beginGroup(settings_group); QMultiMap tabs; for (FancyTabData *tab : std::as_const(tabs_)) { int idx = s.value(QStringLiteral("tab_") + tab->name(), tab->index()).toInt(); while (tabs.contains(idx)) { ++idx; } tabs.insert(idx, tab); } s.endGroup(); QMultiMap ::iterator i; for (i = tabs.begin(); i != tabs.end(); ++i) { FancyTabData *tab = i.value(); const int idx = insertTab(i.key(), tab->page(), tab->icon(), tab->label()); tabBar()->setTabData(idx, QVariant::fromValue(tab)); } } void FancyTabWidget::SaveSettings(const QString &settings_group) { Settings s; s.beginGroup(settings_group); s.setValue("tab_mode", static_cast(mode_)); s.setValue("current_tab", currentIndex()); for (FancyTabData *tab : std::as_const(tabs_)) { QString k = QStringLiteral("tab_") + tab->name(); int idx = QTabWidget::indexOf(tab->page()); if (idx < 0) { if (s.contains(k)) s.remove(k); } else { s.setValue(k, idx); } } s.endGroup(); } void FancyTabWidget::ReloadSettings() { Settings s; s.beginGroup(AppearanceSettingsPage::kSettingsGroup); bg_color_system_ = s.value(AppearanceSettingsPage::kTabBarSystemColor, false).toBool(); bg_gradient_ = s.value(AppearanceSettingsPage::kTabBarGradient, true).toBool(); bg_color_ = AppearanceSettingsPage::DefaultTabbarBgColor(); if (!bg_color_system_) { bg_color_ = s.value(AppearanceSettingsPage::kTabBarColor, bg_color_).value(); } iconsize_smallsidebar_ = s.value(AppearanceSettingsPage::kIconSizeTabbarSmallMode, IconSize_SmallSidebar).toInt(); iconsize_largesidebar_ = s.value(AppearanceSettingsPage::kIconSizeTabbarLargeMode, IconSize_LargeSidebar).toInt(); s.endGroup(); #ifndef Q_OS_MACOS if (mode() == Mode::LargeSidebar) { setIconSize(QSize(iconsize_largesidebar_, iconsize_largesidebar_)); } else { setIconSize(QSize(iconsize_smallsidebar_, iconsize_smallsidebar_)); } #endif update(); TabBarUpdateGeometry(); } void FancyTabWidget::SetMode(const Mode mode) { const Mode previous_mode = mode_; mode_ = mode; if (mode == Mode::Tabs || mode == Mode::IconOnlyTabs) { setTabPosition(QTabWidget::North); } else { setTabPosition(QTabWidget::West); } #ifndef Q_OS_MACOS if (mode_ == Mode::LargeSidebar) { setIconSize(QSize(iconsize_largesidebar_, iconsize_largesidebar_)); } else { setIconSize(QSize(iconsize_smallsidebar_, iconsize_smallsidebar_)); } #endif if (previous_mode == Mode::IconOnlyTabs && mode != Mode::IconOnlyTabs) { for (int i = 0; i < count(); ++i) { tabBar()->setTabText(i, tabBar()->tabData(i).value()->label()); tabBar()->setTabToolTip(i, QLatin1String("")); } } else if (previous_mode != Mode::IconOnlyTabs && mode == Mode::IconOnlyTabs) { for (int i = 0; i < count(); ++i) { tabBar()->setTabText(i, QLatin1String("")); tabBar()->setTabToolTip(i, tabBar()->tabData(i).value()->label()); } } 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(1ms, this, &FancyTabWidget::TabBarUpdateGeometry); Q_EMIT ModeChanged(mode); } int FancyTabWidget::InsertTab(const int idx, QWidget *page, const QIcon &icon, const QString &label) { return QTabWidget::insertTab(idx, page, icon, label); } void FancyTabWidget::AddSpacer() { QWidget *spacer = new QWidget(this); const int idx = insertTab(count(), spacer, QIcon(), QString()); setTabEnabled(idx, false); } void FancyTabWidget::AddBottomWidget(QWidget *widget_view) { bottom_widget_ = widget_view; } void FancyTabWidget::SetBackgroundPixmap(const QPixmap &pixmap) { background_pixmap_ = pixmap; update(); } void FancyTabWidget::SetCurrentIndex(int idx) { Q_ASSERT(count() > 0); if (idx >= count() || idx < 0) idx = 0; QWidget *currentPage = widget(idx); QLayout *layout = currentPage->layout(); if (bottom_widget_) layout->addWidget(bottom_widget_); QTabWidget::setCurrentIndex(idx); } void FancyTabWidget::CurrentTabChangedSlot(const int idx) { QWidget *currentPage = currentWidget(); QLayout *layout = currentPage->layout(); if (bottom_widget_) layout->addWidget(bottom_widget_); Q_EMIT CurrentTabChanged(idx); } int FancyTabWidget::IndexOfTab(QWidget *widget) { if (!tabs_.contains(widget)) return -1; QWidget *page = tabs_.value(widget)->page(); return QTabWidget::indexOf(page); } void FancyTabWidget::paintEvent(QPaintEvent *pe) { if (mode() != Mode::LargeSidebar && mode() != Mode::SmallSidebar) { QTabWidget::paintEvent(pe); return; } QStylePainter painter(this); QRect backgroundRect = rect(); backgroundRect.setWidth(tabBar()->width()); 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)); QPixmap pixmap; if (!QPixmapCache::find(key, &pixmap)) { pixmap = QPixmap(backgroundRect.size()); QPainter p(&pixmap); p.fillRect(backgroundRect, bg_color_); // Draw the gradient fill. if (bg_gradient_) { QRect rect(0, 0, backgroundRect.width(), backgroundRect.height()); QColor shadow = StyleHelper::shadowColor(false); QLinearGradient grad(backgroundRect.topRight(), backgroundRect.topLeft()); grad.setColorAt(0, bg_color_.lighter(117)); grad.setColorAt(1, shadow.darker(109)); p.fillRect(rect, grad); QColor light(255, 255, 255, 80); p.setPen(light); p.drawLine(rect.topRight() - QPoint(1, 0), rect.bottomRight() - QPoint(1, 0)); QColor dark(0, 0, 0, 90); p.setPen(dark); p.drawLine(rect.topLeft(), rect.bottomLeft()); } // 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(StyleHelper::borderColor()); p.drawLine(backgroundRect.topRight(), backgroundRect.bottomRight()); p.end(); QPixmapCache::insert(key, pixmap); } painter.drawPixmap(backgroundRect.topLeft(), pixmap); } void FancyTabWidget::TabBarUpdateGeometry() { tabBar()->updateGeometry(); } void FancyTabWidget::addMenuItem(QActionGroup *group, const QString &text, Mode mode) { QAction *action = group->addAction(text); action->setCheckable(true); QObject::connect(action, &QAction::triggered, this, [this, mode]() { SetMode(mode); }); if (mode == mode_) action->setChecked(true); } void FancyTabWidget::contextMenuEvent(QContextMenuEvent *e) { if (!QRect(mapToGlobal(pos()), tabBar()->size()).contains(e->globalPos())) { QTabWidget::contextMenuEvent(e); return; } 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()); }