| 1 | // Copyright (C) 2016 The Qt Company Ltd. |
| 2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only |
| 3 | |
| 4 | #ifndef QTABBAR_P_H |
| 5 | #define QTABBAR_P_H |
| 6 | |
| 7 | // |
| 8 | // W A R N I N G |
| 9 | // ------------- |
| 10 | // |
| 11 | // This file is not part of the Qt API. It exists purely as an |
| 12 | // implementation detail. This header file may change from version to |
| 13 | // version without notice, or even be removed. |
| 14 | // |
| 15 | // We mean it. |
| 16 | // |
| 17 | |
| 18 | #include <QtWidgets/private/qtwidgetsglobal_p.h> |
| 19 | #include "qtabbar.h" |
| 20 | #include "private/qwidget_p.h" |
| 21 | |
| 22 | #include <qicon.h> |
| 23 | #include <qtoolbutton.h> |
| 24 | #include <qbasictimer.h> |
| 25 | #if QT_CONFIG(animation) |
| 26 | #include <qvariantanimation.h> |
| 27 | #endif |
| 28 | |
| 29 | #define ANIMATION_DURATION 250 |
| 30 | |
| 31 | #include <qstyleoption.h> |
| 32 | #include <utility> |
| 33 | |
| 34 | QT_REQUIRE_CONFIG(tabbar); |
| 35 | |
| 36 | QT_BEGIN_NAMESPACE |
| 37 | |
| 38 | class QMovableTabWidget : public QWidget |
| 39 | { |
| 40 | public: |
| 41 | explicit QMovableTabWidget(QWidget *parent = nullptr); |
| 42 | void setPixmap(const QPixmap &pixmap); |
| 43 | |
| 44 | protected: |
| 45 | void paintEvent(QPaintEvent *e) override; |
| 46 | |
| 47 | private: |
| 48 | QPixmap m_pixmap; |
| 49 | }; |
| 50 | |
| 51 | class Q_WIDGETS_EXPORT QTabBarPrivate : public QWidgetPrivate |
| 52 | { |
| 53 | Q_DECLARE_PUBLIC(QTabBar) |
| 54 | public: |
| 55 | QTabBarPrivate() |
| 56 | : layoutDirty(false), drawBase(true), elideModeSetByUser(false), useScrollButtons(false), |
| 57 | useScrollButtonsSetByUser(false), expanding(true), closeButtonOnTabs(false), |
| 58 | paintWithOffsets(true), movable(false), dragInProgress(false), documentMode(false), |
| 59 | autoHide(false), changeCurrentOnDrag(false) |
| 60 | {} |
| 61 | ~QTabBarPrivate() |
| 62 | { |
| 63 | qDeleteAll(c: tabList); |
| 64 | } |
| 65 | |
| 66 | QRect hoverRect; |
| 67 | QPoint dragStartPosition; |
| 68 | QPoint mousePosition = {-1, -1}; |
| 69 | #if QT_CONFIG(wheelevent) |
| 70 | QPoint accumulatedAngleDelta; |
| 71 | #endif |
| 72 | QSize iconSize; |
| 73 | QToolButton* rightB = nullptr; // right or bottom |
| 74 | QToolButton* leftB = nullptr; // left or top |
| 75 | QMovableTabWidget *movingTab = nullptr; |
| 76 | int hoverIndex = -1; |
| 77 | int switchTabCurrentIndex = -1; |
| 78 | QBasicTimer switchTabTimer; |
| 79 | Qt::TextElideMode elideMode = Qt::ElideNone; |
| 80 | QTabBar::SelectionBehavior selectionBehaviorOnRemove = QTabBar::SelectRightTab; |
| 81 | QTabBar::Shape shape = QTabBar::RoundedNorth; |
| 82 | Qt::MouseButtons mouseButtons = Qt::NoButton; |
| 83 | |
| 84 | int currentIndex = -1; |
| 85 | int pressedIndex = -1; |
| 86 | int firstVisible = 0; |
| 87 | int lastVisible = -1; |
| 88 | int scrollOffset = 0; |
| 89 | |
| 90 | bool layoutDirty : 1; |
| 91 | bool drawBase : 1; |
| 92 | bool elideModeSetByUser : 1; |
| 93 | bool useScrollButtons : 1; |
| 94 | bool useScrollButtonsSetByUser : 1; |
| 95 | bool expanding : 1; |
| 96 | bool closeButtonOnTabs : 1; |
| 97 | bool paintWithOffsets : 1; |
| 98 | bool movable : 1; |
| 99 | bool dragInProgress : 1; |
| 100 | bool documentMode : 1; |
| 101 | bool autoHide : 1; |
| 102 | bool changeCurrentOnDrag : 1; |
| 103 | |
| 104 | struct Tab { |
| 105 | inline Tab(const QIcon &ico, const QString &txt) |
| 106 | : text(txt), icon(ico), enabled(true), visible(true), measuringMinimum(false) |
| 107 | { |
| 108 | } |
| 109 | /* |
| 110 | Tabs are managed by instance; they are not the same even |
| 111 | if all properties are the same. |
| 112 | */ |
| 113 | Q_DISABLE_COPY_MOVE(Tab); |
| 114 | |
| 115 | QString text; |
| 116 | #if QT_CONFIG(tooltip) |
| 117 | QString toolTip; |
| 118 | #endif |
| 119 | #if QT_CONFIG(whatsthis) |
| 120 | QString whatsThis; |
| 121 | #endif |
| 122 | #if QT_CONFIG(accessibility) |
| 123 | QString accessibleName; |
| 124 | #endif |
| 125 | QIcon icon; |
| 126 | QRect rect; |
| 127 | QRect minRect; |
| 128 | QRect maxRect; |
| 129 | |
| 130 | QColor textColor; |
| 131 | QVariant data; |
| 132 | QWidget *leftWidget = nullptr; |
| 133 | QWidget *rightWidget = nullptr; |
| 134 | int shortcutId = 0; |
| 135 | int lastTab = -1; |
| 136 | int dragOffset = 0; |
| 137 | uint enabled : 1; |
| 138 | uint visible : 1; |
| 139 | uint measuringMinimum : 1; |
| 140 | |
| 141 | #if QT_CONFIG(animation) |
| 142 | struct TabBarAnimation : public QVariantAnimation { |
| 143 | TabBarAnimation(Tab *t, QTabBarPrivate *_priv) : tab(t), priv(_priv) |
| 144 | { setEasingCurve(QEasingCurve::InOutQuad); } |
| 145 | |
| 146 | void updateCurrentValue(const QVariant ¤t) override; |
| 147 | |
| 148 | void updateState(State newState, State) override; |
| 149 | private: |
| 150 | //these are needed for the callbacks |
| 151 | Tab *tab; |
| 152 | QTabBarPrivate *priv; |
| 153 | }; |
| 154 | std::unique_ptr<TabBarAnimation> animation; |
| 155 | |
| 156 | void startAnimation(QTabBarPrivate *priv, int duration) { |
| 157 | if (!priv->isAnimated()) { |
| 158 | priv->moveTabFinished(index: priv->tabList.indexOf(t: this)); |
| 159 | return; |
| 160 | } |
| 161 | if (!animation) |
| 162 | animation = std::make_unique<TabBarAnimation>(args: this, args&: priv); |
| 163 | animation->setStartValue(dragOffset); |
| 164 | animation->setEndValue(0); |
| 165 | animation->setDuration(duration); |
| 166 | animation->start(); |
| 167 | } |
| 168 | #else |
| 169 | void startAnimation(QTabBarPrivate *priv, int duration) |
| 170 | { Q_UNUSED(duration); priv->moveTabFinished(priv->tabList.indexOf(this)); } |
| 171 | #endif // animation |
| 172 | }; |
| 173 | QList<Tab*> tabList; |
| 174 | mutable QHash<QString, QSize> textSizes; |
| 175 | |
| 176 | void calculateFirstLastVisible(int index, bool visible, bool remove); |
| 177 | int selectNewCurrentIndexFrom(int currentIndex); |
| 178 | int calculateNewPosition(int from, int to, int index) const; |
| 179 | void slide(int from, int to); |
| 180 | void init(); |
| 181 | |
| 182 | inline Tab *at(int index) { return tabList.value(i: index, defaultValue: nullptr); } |
| 183 | inline const Tab *at(int index) const { return tabList.value(i: index, defaultValue: nullptr); } |
| 184 | |
| 185 | int indexAtPos(const QPoint &p) const; |
| 186 | |
| 187 | inline bool isAnimated() const { Q_Q(const QTabBar); return q->style()->styleHint(stylehint: QStyle::SH_Widget_Animation_Duration, opt: nullptr, widget: q) > 0; } |
| 188 | inline bool validIndex(int index) const { return index >= 0 && index < tabList.size(); } |
| 189 | void setCurrentNextEnabledIndex(int offset); |
| 190 | |
| 191 | void scrollTabs(); |
| 192 | void closeTab(); |
| 193 | void moveTab(int index, int offset); |
| 194 | void moveTabFinished(int index); |
| 195 | |
| 196 | void refresh(); |
| 197 | void layoutTabs(); |
| 198 | void layoutWidgets(int start = 0); |
| 199 | void layoutTab(int index); |
| 200 | void updateMacBorderMetrics(); |
| 201 | bool isTabInMacUnifiedToolbarArea() const; |
| 202 | void setupMovableTab(); |
| 203 | void autoHideTabs(); |
| 204 | QRect normalizedScrollRect(int index = -1); |
| 205 | int hoveredTabIndex() const; |
| 206 | |
| 207 | void initBasicStyleOption(QStyleOptionTab *option, int tabIndex) const; |
| 208 | |
| 209 | void makeVisible(int index); |
| 210 | |
| 211 | // shared by tabwidget and qtabbar |
| 212 | static void initStyleBaseOption(QStyleOptionTabBarBase *optTabBase, QTabBar *tabbar, QSize size) |
| 213 | { |
| 214 | QStyleOptionTab tabOverlap; |
| 215 | tabOverlap.shape = tabbar->shape(); |
| 216 | int overlap = tabbar->style()->pixelMetric(metric: QStyle::PM_TabBarBaseOverlap, option: &tabOverlap, widget: tabbar); |
| 217 | QWidget *theParent = tabbar->parentWidget(); |
| 218 | optTabBase->initFrom(w: tabbar); |
| 219 | optTabBase->shape = tabbar->shape(); |
| 220 | optTabBase->documentMode = tabbar->documentMode(); |
| 221 | if (theParent && overlap > 0) { |
| 222 | QRect rect; |
| 223 | switch (tabOverlap.shape) { |
| 224 | case QTabBar::RoundedNorth: |
| 225 | case QTabBar::TriangularNorth: |
| 226 | rect.setRect(ax: 0, ay: size.height()-overlap, aw: size.width(), ah: overlap); |
| 227 | break; |
| 228 | case QTabBar::RoundedSouth: |
| 229 | case QTabBar::TriangularSouth: |
| 230 | rect.setRect(ax: 0, ay: 0, aw: size.width(), ah: overlap); |
| 231 | break; |
| 232 | case QTabBar::RoundedEast: |
| 233 | case QTabBar::TriangularEast: |
| 234 | rect.setRect(ax: 0, ay: 0, aw: overlap, ah: size.height()); |
| 235 | break; |
| 236 | case QTabBar::RoundedWest: |
| 237 | case QTabBar::TriangularWest: |
| 238 | rect.setRect(ax: size.width() - overlap, ay: 0, aw: overlap, ah: size.height()); |
| 239 | break; |
| 240 | } |
| 241 | optTabBase->rect = rect; |
| 242 | } |
| 243 | } |
| 244 | |
| 245 | void killSwitchTabTimer(); |
| 246 | |
| 247 | }; |
| 248 | |
| 249 | constexpr inline bool verticalTabs(QTabBar::Shape shape) noexcept |
| 250 | { |
| 251 | return shape == QTabBar::RoundedWest |
| 252 | || shape == QTabBar::RoundedEast |
| 253 | || shape == QTabBar::TriangularWest |
| 254 | || shape == QTabBar::TriangularEast; |
| 255 | } |
| 256 | |
| 257 | QT_END_NAMESPACE |
| 258 | |
| 259 | #endif |
| 260 | |