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 <qdebug.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 | int switchTabTimerId = 0; |
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) |
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 | |
140 | #if QT_CONFIG(animation) |
141 | struct TabBarAnimation : public QVariantAnimation { |
142 | TabBarAnimation(Tab *t, QTabBarPrivate *_priv) : tab(t), priv(_priv) |
143 | { setEasingCurve(QEasingCurve::InOutQuad); } |
144 | |
145 | void updateCurrentValue(const QVariant ¤t) override; |
146 | |
147 | void updateState(State newState, State) override; |
148 | private: |
149 | //these are needed for the callbacks |
150 | Tab *tab; |
151 | QTabBarPrivate *priv; |
152 | }; |
153 | std::unique_ptr<TabBarAnimation> animation; |
154 | |
155 | void startAnimation(QTabBarPrivate *priv, int duration) { |
156 | if (!priv->isAnimated()) { |
157 | priv->moveTabFinished(index: priv->tabList.indexOf(t: this)); |
158 | return; |
159 | } |
160 | if (!animation) |
161 | animation = std::make_unique<TabBarAnimation>(args: this, args&: priv); |
162 | animation->setStartValue(dragOffset); |
163 | animation->setEndValue(0); |
164 | animation->setDuration(duration); |
165 | animation->start(); |
166 | } |
167 | #else |
168 | void startAnimation(QTabBarPrivate *priv, int duration) |
169 | { Q_UNUSED(duration); priv->moveTabFinished(priv->tabList.indexOf(this)); } |
170 | #endif // animation |
171 | }; |
172 | QList<Tab*> tabList; |
173 | mutable QHash<QString, QSize> textSizes; |
174 | |
175 | void calculateFirstLastVisible(int index, bool visible, bool remove); |
176 | int selectNewCurrentIndexFrom(int currentIndex); |
177 | int calculateNewPosition(int from, int to, int index) const; |
178 | void slide(int from, int to); |
179 | void init(); |
180 | |
181 | inline Tab *at(int index) { return tabList.value(i: index, defaultValue: nullptr); } |
182 | inline const Tab *at(int index) const { return tabList.value(i: index, defaultValue: nullptr); } |
183 | |
184 | int indexAtPos(const QPoint &p) const; |
185 | |
186 | inline bool isAnimated() const { Q_Q(const QTabBar); return q->style()->styleHint(stylehint: QStyle::SH_Widget_Animation_Duration, opt: nullptr, widget: q) > 0; } |
187 | inline bool validIndex(int index) const { return index >= 0 && index < tabList.size(); } |
188 | void setCurrentNextEnabledIndex(int offset); |
189 | |
190 | void scrollTabs(); |
191 | void closeTab(); |
192 | void moveTab(int index, int offset); |
193 | void moveTabFinished(int index); |
194 | |
195 | void refresh(); |
196 | void layoutTabs(); |
197 | void layoutWidgets(int start = 0); |
198 | void layoutTab(int index); |
199 | void updateMacBorderMetrics(); |
200 | bool isTabInMacUnifiedToolbarArea() const; |
201 | void setupMovableTab(); |
202 | void autoHideTabs(); |
203 | QRect normalizedScrollRect(int index = -1); |
204 | int hoveredTabIndex() const; |
205 | |
206 | void initBasicStyleOption(QStyleOptionTab *option, int tabIndex) const; |
207 | |
208 | void makeVisible(int index); |
209 | |
210 | // shared by tabwidget and qtabbar |
211 | static void initStyleBaseOption(QStyleOptionTabBarBase *optTabBase, QTabBar *tabbar, QSize size) |
212 | { |
213 | QStyleOptionTab tabOverlap; |
214 | tabOverlap.shape = tabbar->shape(); |
215 | int overlap = tabbar->style()->pixelMetric(metric: QStyle::PM_TabBarBaseOverlap, option: &tabOverlap, widget: tabbar); |
216 | QWidget *theParent = tabbar->parentWidget(); |
217 | optTabBase->initFrom(w: tabbar); |
218 | optTabBase->shape = tabbar->shape(); |
219 | optTabBase->documentMode = tabbar->documentMode(); |
220 | if (theParent && overlap > 0) { |
221 | QRect rect; |
222 | switch (tabOverlap.shape) { |
223 | case QTabBar::RoundedNorth: |
224 | case QTabBar::TriangularNorth: |
225 | rect.setRect(ax: 0, ay: size.height()-overlap, aw: size.width(), ah: overlap); |
226 | break; |
227 | case QTabBar::RoundedSouth: |
228 | case QTabBar::TriangularSouth: |
229 | rect.setRect(ax: 0, ay: 0, aw: size.width(), ah: overlap); |
230 | break; |
231 | case QTabBar::RoundedEast: |
232 | case QTabBar::TriangularEast: |
233 | rect.setRect(ax: 0, ay: 0, aw: overlap, ah: size.height()); |
234 | break; |
235 | case QTabBar::RoundedWest: |
236 | case QTabBar::TriangularWest: |
237 | rect.setRect(ax: size.width() - overlap, ay: 0, aw: overlap, ah: size.height()); |
238 | break; |
239 | } |
240 | optTabBase->rect = rect; |
241 | } |
242 | } |
243 | |
244 | void killSwitchTabTimer(); |
245 | |
246 | }; |
247 | |
248 | constexpr inline bool verticalTabs(QTabBar::Shape shape) noexcept |
249 | { |
250 | return shape == QTabBar::RoundedWest |
251 | || shape == QTabBar::RoundedEast |
252 | || shape == QTabBar::TriangularWest |
253 | || shape == QTabBar::TriangularEast; |
254 | } |
255 | |
256 | QT_END_NAMESPACE |
257 | |
258 | #endif |
259 | |