/* * Cantata * * Copyright (c) 2011-2022 Craig Drummond */ /************************************************************************** ** ** This file is part of Qt Creator ** ** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies). ** ** Contact: Nokia Corporation (qt-info@nokia.com) ** ** Commercial Usage ** ** Licensees holding valid Qt Commercial licenses may use this file in ** accordance with the Qt Commercial License Agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and Nokia. ** ** GNU Lesser General Public License Usage ** ** Alternatively, this file may be used under the terms of the GNU Lesser ** General Public License version 2.1 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPL included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 2.1 requirements ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. ** ** If you are unsure which license is appropriate for your use, please ** contact the sales department at http://qt.nokia.com/contact. ** **************************************************************************/ #include "fancytabwidget.h" #include "icon.h" #include "action.h" #include "utils.h" #include "config.h" #ifdef Q_OS_MAC #include "osxstyle.h" #endif #include #include #include #include #include #include #include #include #include #include #include #include #include static inline int sidebarSpacing(bool withText) { int sp=Utils::scaleForDpi(12); if (!withText) { sp*=1.25; } return sp; } static inline Qt::TextElideMode elideMode() { return Qt::LeftToRight==QApplication::layoutDirection() ? Qt::ElideRight : Qt::ElideLeft; } static int largeIconSize=32; static int smallIconSize=16; void FancyTabWidget::setup() { largeIconSize=Icon::stdSize(Utils::scaleForDpi(32)); smallIconSize=Icon::stdSize(Utils::scaleForDpi(16)); } int FancyTabWidget::iconSize(bool large) { return large ? largeIconSize : smallIconSize; } static void drawIcon(const QIcon &icon, const QRect &r, QPainter *p, const QSize &iconSize, bool selected) { #ifdef Q_OS_WIN Q_UNUSED(selected); QPixmap px = icon.pixmap(iconSize, QIcon::Normal); #else QPixmap px = icon.pixmap(iconSize, selected ? QIcon::Selected : QIcon::Normal); #endif QSize layoutSize = px.size() / px.DEVICE_PIXEL_RATIO(); p->drawPixmap(r.x()+(r.width()-layoutSize.width())/2.0, r.y()+(r.height()-layoutSize.height())/2.0, layoutSize.width(), layoutSize.height(), px); } void FancyTabProxyStyle::drawPrimitive(PrimitiveElement element, const QStyleOption *option, QPainter *p, const QWidget *widget) const { if (PE_FrameTabBarBase!=element) { QProxyStyle::drawPrimitive(element, option, p, widget); } } void FancyTabProxyStyle::drawControl(ControlElement element, const QStyleOption *option, QPainter *p, const QWidget *widget) const { const QStyleOptionTab *tabOpt = qstyleoption_cast(option); if (element != CE_TabBarTab || !tabOpt) { QProxyStyle::drawControl(element, option, p, widget); return; } const QRect rect = tabOpt->rect; const bool selected = tabOpt->state&State_Selected; const bool underMouse = tabOpt->state&State_MouseOver; bool active = tabOpt->state&State_Active; const bool verticalTabs = tabOpt->shape == QTabBar::RoundedWest; const QString text = tabOpt->text; QTransform m; if (verticalTabs) { m = QTransform::fromTranslate(rect.left(), rect.bottom()); m.rotate(-90); } else { m = QTransform::fromTranslate(rect.left(), rect.top()); } const QRect draw_rect(QPoint(0, 0), m.mapRect(rect).size()); p->save(); p->setTransform(m); QRect iconRect(QPoint(8, 0), tabOpt->iconSize); QRect textRect(iconRect.topRight() + QPoint(4, 0), draw_rect.size()); textRect.setRight(draw_rect.width()); iconRect.translate(0, (draw_rect.height() - iconRect.height()) / 2); if (selected || underMouse) { #ifdef Q_OS_MAC QColor col = OSXStyle::self()->viewPalette().highlight().color(); #elif defined Q_OS_WIN QColor col = active ? option->palette.highlight().color() : QColor(96, 96, 96); col.setAlphaF(0.25); #else QColor col = option->palette.highlight().color(); #endif if (selected) { #ifndef Q_OS_MAC if (!active && option->palette.highlight().color()==QApplication::palette().color(QPalette::Active, QPalette::Highlight)) { active = true; } #endif } else { #if defined Q_OS_WIN col.setAlphaF(0.1); #else col.setAlphaF(0.2); #endif } p->fillRect(draw_rect, col); } int textFlags = Qt::AlignTop | Qt::AlignVCenter; #ifdef Q_OS_MAC p->setPen(selected && active ? OSXStyle::self()->viewPalette().highlightedText().color() : OSXStyle::self()->viewPalette().windowText().color()); #elif defined Q_OS_WIN p->setPen(QApplication::palette().windowText().color()); #else p->setPen(selected && active ? QApplication::palette().highlightedText().color() : QApplication::palette().windowText().color()); #endif drawIcon(tabOpt->icon, iconRect, p, tabOpt->iconSize, selected && active); QString txt=text; txt.replace("&", ""); txt=p->fontMetrics().elidedText(txt, elideMode(), textRect.width()); p->drawText(textRect.translated(0, -1), textFlags, txt); p->restore(); } void FancyTabProxyStyle::polish(QWidget* widget) { if (QLatin1String("QTabBar")==QString(widget->metaObject()->className())) { widget->setMouseTracking(true); widget->installEventFilter(this); } QProxyStyle::polish(widget); } void FancyTabProxyStyle::polish(QApplication* app) { QProxyStyle::polish(app); } void FancyTabProxyStyle::polish(QPalette &palette) { QProxyStyle::polish(palette); } bool FancyTabProxyStyle::eventFilter(QObject* o, QEvent* e) { #ifndef Q_OS_MAC QTabBar *bar = qobject_cast(o); if (bar && (e->type() == QEvent::MouseMove || e->type() == QEvent::Leave)) { QMouseEvent *event = static_cast(e); const QString oldHoveredTab = bar->property("tab_hover").toString(); const QString hoveredTab = e->type() == QEvent::Leave ? QString() : bar->tabText(bar->tabAt(event->pos())); bar->setProperty("tab_hover", hoveredTab); if (oldHoveredTab != hoveredTab) { bar->update(); } } #endif return false; } FancyTab::FancyTab(FancyTabBar* tabbar) : QWidget(tabbar), underMouse(false), tabbar(tabbar) { setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Maximum); setAttribute(Qt::WA_Hover, true); } FancyTabBar::FancyTabBar(QWidget *parent, bool text, int iSize, Pos pos) : QWidget(parent) , withText(text) , pos(pos) , icnSize(iSize) { setFont(Utils::smallFont(font())); setAttribute(Qt::WA_Hover, true); setFocusPolicy(Qt::NoFocus); setMouseTracking(true); // Needed for hover events triggerTimer.setSingleShot(true); QBoxLayout* layout=nullptr; if (Side!=pos) { setMinimumHeight(tabSizeHint().height()); setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); layout=new QHBoxLayout; } else { setMinimumWidth(tabSizeHint().width()); setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding); layout=new QVBoxLayout; layout->addSpacerItem(new QSpacerItem(0, 0, QSizePolicy::Fixed, QSizePolicy::MinimumExpanding)); } layout->setSpacing(0); layout->setContentsMargins(0, 0, 0, 0); setLayout(layout); // We use a zerotimer to keep the sidebar responsive connect(&triggerTimer, SIGNAL(timeout()), this, SLOT(emitCurrentIndex())); } FancyTabBar::~FancyTabBar() { } QSize FancyTab::sizeHint() const { int iconSize = tabbar->iconSize(); bool withText = tabbar->showText(); int spacing = sidebarSpacing(withText); int padding = FancyTabBar::Side==tabbar->position() ? Utils::scaleForDpi(12) : 0; if (withText) { QFontMetrics fm(font()); int textWidth = fm.horizontalAdvance(text)*1.1; int width = qMax(iconSize, qMin(3*iconSize, textWidth)) + spacing; return QSize(width, iconSize + spacing + fm.height() + padding); } else { return QSize(iconSize + spacing + padding, iconSize + spacing + padding); } } QSize FancyTabBar::tabSizeHint() const { int spacing = sidebarSpacing(withText); int padding = Side==pos ? Utils::scaleForDpi(12) : 0; if (withText) { QFontMetrics fm(font()); int maxTw=0; for (FancyTab *tab: tabs) { maxTw=qMax(maxTw, tab->sizeHint().width()); } return QSize(qMax(icnSize + spacing, maxTw), icnSize + spacing + fm.height() + padding); } else { return QSize(icnSize + spacing + padding, icnSize + spacing + padding); } } void FancyTabBar::paintEvent(QPaintEvent *event) { Q_UNUSED(event) QPainter p(this); for (int i = 0; i < count(); ++i) { if (i != currentIndex()) { paintTab(&p, i); } } // paint active tab last, since it overlaps the neighbors if (currentIndex() != -1) { paintTab(&p, currentIndex()); } } void FancyTab::enterEvent(QEvent*) { underMouse = true; } void FancyTab::leaveEvent(QEvent*) { underMouse = false; } QSize FancyTabBar::sizeHint() const { QSize sh = tabSizeHint(); return Side!=pos ? QSize(sh.width() * tabs.count(), sh.height()) : QSize(sh.width(), sh.height() * tabs.count()); } QSize FancyTabBar::minimumSizeHint() const { QSize sh = tabSizeHint(); return Side!=pos ? QSize(sh.width() * tabs.count(), sh.height()) : QSize(sh.width(), sh.height() * tabs.count()); } QRect FancyTabBar::tabRect(int index) const { return tabs[index]->geometry(); } QString FancyTabBar::tabToolTip(int index) const { return tabs[index]->toolTip(); } void FancyTabBar::setTabToolTip(int index, const QString &toolTip) { tabs[index]->setToolTip(toolTip); } // This keeps the sidebar responsive since // we get a repaint before loading the // mode itself void FancyTabBar::emitCurrentIndex() { emit currentChanged(currentIdx); } void FancyTabBar::mousePressEvent(QMouseEvent *e) { if (Qt::LeftButton!=e->button()) { return; } e->accept(); for (int index = 0; index < tabs.count(); ++index) { if (tabRect(index).contains(e->pos())) { currentIdx = index; update(); triggerTimer.start(0); break; } } } void FancyTabBar::wheelEvent(QWheelEvent *ev) { int numDegrees = ev->angleDelta().y() / 8; int numSteps = numDegrees / -15; int prevIndex = currentIdx; if (numSteps>0) { currentIdx=qMin(currentIdx+numSteps, tabs.size()-1); } else if (numSteps<0) { currentIdx=qMax(currentIdx+numSteps, 0); } if (currentIdx!=prevIndex) { update(); triggerTimer.start(0); } ev->accept(); } void FancyTabBar::addTab(const QIcon &icon, const QString &label, const QString &tt) { FancyTab *tab = new FancyTab(this); tab->icon = icon; tab->text = label; tabs.append(tab); if (!tt.isEmpty()) { tab->setToolTip(tt); } else if (!withText) { tab->setToolTip(label); } qobject_cast(layout())->insertWidget(layout()->count()-(Side==pos ? 1 : 0), tab); } void FancyTabBar::addSpacer(int size) { qobject_cast(layout())->insertSpacerItem(layout()->count()-1, new QSpacerItem(0, size, QSizePolicy::Fixed, QSizePolicy::Maximum)); } void FancyTabBar::paintTab(QPainter *painter, int tabIndex) const { if (!validIndex(tabIndex)) { qWarning("invalid index"); return; } painter->save(); QRect rect = tabRect(tabIndex); bool selected = (tabIndex == currentIdx); bool underMouse = tabs[tabIndex]->underMouse; if (selected || underMouse) { #ifdef Q_OS_MAC QColor col = OSXStyle::self()->viewPalette().highlight().color(); #elif defined Q_OS_WIN QColor col = palette().currentColorGroup()==QPalette::Active ? palette().highlight().color() : QColor(96, 96, 96); col.setAlphaF(0.25); #else QColor col = palette().highlight().color(); #endif if (!selected) { #if defined Q_OS_WIN col.setAlphaF(0.1); #else col.setAlphaF(0.2); #endif } painter->fillRect(rect, col); } selected = selected && (palette().currentColorGroup()==QPalette::Active || (palette().highlightedText().color()==palette().brush(QPalette::Active, QPalette::HighlightedText).color())); if (withText) { QString tabText(painter->fontMetrics().elidedText(this->tabText(tabIndex), elideMode(), width())); QRect tabTextRect(tabRect(tabIndex)); QRect tabIconRect(tabTextRect); tabIconRect.adjust(+4, +4, -4, -4); tabTextRect.translate(0, -2); #ifdef Q_OS_MAC painter->setPen(selected ? OSXStyle::self()->viewPalette().highlightedText().color() : OSXStyle::self()->viewPalette().windowText().color()); #elif defined Q_OS_WIN painter->setPen(QApplication::palette().windowText().color()); #else painter->setPen(selected ? QApplication::palette().highlightedText().color() : QApplication::palette().windowText().color()); #endif int textFlags = Qt::AlignCenter | Qt::AlignBottom; painter->drawText(tabTextRect, textFlags, tabText); const int textHeight = painter->fontMetrics().height(); tabIconRect.adjust(0, 4, 0, -textHeight); drawIcon(tabIcon(tabIndex), tabIconRect, painter, QSize(icnSize, icnSize), selected); } else { drawIcon(tabIcon(tabIndex), rect, painter, QSize(icnSize, icnSize), selected); } painter->restore(); } void FancyTabBar::setCurrentIndex(int index) { currentIdx = index; update(); emit currentChanged(currentIdx); } FancyTabWidget::FancyTabWidget(QWidget *parent) : QWidget(parent) , styleSetting(0) , tabBar(nullptr) , stack_(new QStackedWidget(this)) , sideWidget(new QWidget) , sideLayout(new QVBoxLayout) , topLayout(new QVBoxLayout) , proxyStyle(new FancyTabProxyStyle) { sideLayout->setSpacing(0); sideLayout->setMargin(0); sideLayout->addSpacerItem(new QSpacerItem(0, 0, QSizePolicy::Fixed, QSizePolicy::Expanding)); sideWidget->setLayout(sideLayout); sideWidget->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Expanding); topLayout->setMargin(0); topLayout->setSpacing(0); topLayout->addWidget(stack_); QHBoxLayout *mainLayout = new QHBoxLayout; mainLayout->setMargin(0); mainLayout->setSpacing(0); mainLayout->addWidget(sideWidget); mainLayout->addLayout(topLayout); setLayout(mainLayout); } void FancyTabWidget::addTab(QWidget *tab, const QIcon &icon, const QString &label, const QString &tt, bool enabled) { stack_->addWidget(tab); items << Item(icon, label, tt, enabled); setMinimumWidth(128); } int FancyTabWidget::currentIndex() const { return stack_->currentIndex(); } QWidget * FancyTabWidget::currentWidget() const { return stack_->currentWidget(); } QWidget * FancyTabWidget::widget(int index) const { return stack_->widget(index); } int FancyTabWidget::count() const { return stack_->count(); } int FancyTabWidget::visibleCount() const { int c=0; for (const Item &i: items) { if (i.enabled) { c++; } } return c; } QSize FancyTabWidget::tabSize() const { if (FancyTabBar *bar = qobject_cast(tabBar)) { return bar->tabSizeHint(); } return QSize(); } void FancyTabWidget::setCurrentIndex(int idx) { if (!isEnabled(idx)) { return; } int index=IndexToTab(idx); if (FancyTabBar* bar = qobject_cast(tabBar)) { bar->setCurrentIndex(index); } else if (QTabBar* bar = qobject_cast(tabBar)) { bar->setCurrentIndex(index); showWidget(index); } else { stack_->setCurrentIndex(idx); // ?? IS this *ever* called??? } } void FancyTabWidget::showWidget(int index) { int idx=tabToIndex(index); stack_->setCurrentIndex(idx); emit currentChanged(idx); } void FancyTabWidget::setStyle(int s) { if (s==styleSetting && tabBar) { return; } // Remove previous tab bar delete tabBar; tabBar = nullptr; // use_background_ = false; // Create new tab bar if (Tab==(s&Style_Mask) || (Small==(s&Style_Mask) && !(s&IconOnly))) { switch (s&Position_Mask) { default: case Side: makeTabBar(QApplication::isRightToLeft() ? QTabBar::RoundedEast : QTabBar::RoundedWest, !(s&IconOnly), true, Small==(s&Style_Mask)); break; case Top: makeTabBar(QTabBar::RoundedNorth, !(s&IconOnly), true, Small==(s&Style_Mask)); break; case Bot: makeTabBar(QTabBar::RoundedSouth, !(s&IconOnly), true, Small==(s&Style_Mask)); break; } } else { FancyTabBar* bar = new FancyTabBar(this, !(s&IconOnly), Small==(s&Style_Mask) ? smallIconSize : largeIconSize, Side==(s&Position_Mask) ? FancyTabBar::Side : (Top==(s&Position_Mask) ? FancyTabBar::Top : FancyTabBar::Bot)); switch (s&Position_Mask) { default: case Side: sideLayout->insertWidget(0, bar); break; case Bot: topLayout->insertWidget(1, bar); break; case Top: topLayout->insertWidget(0, bar); break; } tabBar = bar; int index=0; QList::Iterator it=items.begin(); QList::Iterator end=items.end(); for (; it!=end; ++it) { if (!(*it).enabled) { (*it).index=-1; continue; } if (Item::Type_Spacer==(*it).type) { bar->addSpacer((*it).spacerSize); } else { bar->addTab((*it).tabIcon, (*it).tabLabel, (*it).tabTooltip); } (*it).index=index++; } bar->setCurrentIndex(IndexToTab(stack_->currentIndex())); connect(bar, SIGNAL(currentChanged(int)), SLOT(showWidget(int))); } tabBar->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); styleSetting = s; emit styleChanged(s); update(); } void FancyTabWidget::toggleTab(int tab, bool show) { if (tab>=0 && tabsetShape(shape); bar->setDocumentMode(true); bar->setUsesScrollButtons(true); if (QTabBar::RoundedWest==shape) { bar->setIconSize(QSize(smallIconSize, smallIconSize)); } if (fancy) { bar->setStyle(proxyStyle.data()); } if (QTabBar::RoundedNorth==shape) { topLayout->insertWidget(0, bar); } else if (QTabBar::RoundedSouth==shape) { topLayout->insertWidget(1, bar); } else { sideLayout->insertWidget(0, bar); } int index=0; QList::Iterator it=items.begin(); QList::Iterator end=items.end(); for (; it!=end; ++it) { if ((*it).type != Item::Type_Tab || !(*it).enabled) { (*it).index=-1; continue; } QString label = (*it).tabLabel; if (QTabBar::RoundedWest==shape) { label = QFontMetrics(font()).elidedText(label, elideMode(), 120); } int tabId = -1; if (icons && text) { tabId = bar->addTab((*it).tabIcon, label); } else if (icons) { tabId = bar->addTab((*it).tabIcon, QString()); } else if (text) { tabId = bar->addTab(label); } if (!(*it).tabTooltip.isEmpty()) { bar->setTabToolTip(tabId, (*it).tabTooltip); } else if (!text) { bar->setTabToolTip(tabId, (*it).tabLabel); } (*it).index=index++; } bar->setCurrentIndex(IndexToTab(stack_->currentIndex())); connect(bar, SIGNAL(currentChanged(int)), SLOT(showWidget(int))); tabBar = bar; } int FancyTabWidget::tabToIndex(int tab) const { for (int i=0; i-1 && index-1 && index(tabBar)) { static_cast(tabBar)->setTabToolTip(item.index, item.tabTooltip); } else { static_cast(tabBar)->setTabToolTip(item.index, item.tabTooltip); } } } } void FancyTabWidget::recreate() { int s=styleSetting; styleSetting=0; setStyle(s); } void FancyTabWidget::setHiddenPages(const QStringList &hidden) { QSet h = Utils::listToSet(hidden); if (h==Utils::listToSet(hiddenPages())) { return; } bool needToRecreate=false; bool needToSetCurrent=false; for (int i=0; imetaObject()->className())) { items[i].enabled=!items[i].enabled; emit tabToggled(i); needToRecreate=true; if (i==currentIndex()) { needToSetCurrent=true; } } } if (needToRecreate) { recreate(); } if (needToSetCurrent) { for (int i=0; imetaObject()->className(); } } } return pages; } #include "moc_fancytabwidget.cpp"