1 | #include "QPinnableTabWidget.h" |
2 | |
3 | #include "FakeCloseButton.h" |
4 | #include "RealCloseButton.h" |
5 | |
6 | #include <QMenu> |
7 | #include <QMouseEvent> |
8 | #include <QTabBar> |
9 | #include <QStyle> |
10 | |
11 | namespace |
12 | { |
13 | class TabBarPin : public QTabBar |
14 | { |
15 | public: |
16 | explicit TabBarPin(QWidget *parent = nullptr) |
17 | : QTabBar(parent) |
18 | { |
19 | } |
20 | |
21 | protected: |
22 | void mousePressEvent(QMouseEvent *event) override |
23 | { |
24 | mIndexToMove = indexAtPos(event->pos()); |
25 | mDistToStart = event->x() - tabRect(mIndexToMove).x(); |
26 | |
27 | QTabBar::mousePressEvent(event); |
28 | } |
29 | |
30 | void mouseMoveEvent(QMouseEvent *event) override |
31 | { |
32 | const auto pinnableWidget = dynamic_cast<QPinnableTabWidget *>(parentWidget()); |
33 | |
34 | if (pinnableWidget) |
35 | { |
36 | const auto currentPinned = pinnableWidget->isPinned(mIndexToMove); |
37 | const auto newPosIsPinned = pinnableWidget->isPinned(indexAtPos(event->pos())); |
38 | const auto lastPinnedTab = pinnableWidget->getLastPinnedTabIndex(); |
39 | |
40 | if (!currentPinned && !newPosIsPinned && (event->pos().x() - mDistToStart) > tabRect(lastPinnedTab).right()) |
41 | QTabBar::mouseMoveEvent(event); |
42 | } |
43 | } |
44 | |
45 | void mouseReleaseEvent(QMouseEvent *event) override |
46 | { |
47 | mIndexToMove = -1; |
48 | mDistToStart = 0; |
49 | |
50 | QTabBar::mouseReleaseEvent(event); |
51 | } |
52 | |
53 | private: |
54 | int mIndexToMove = -1; |
55 | int mDistToStart = 0; |
56 | |
57 | int indexAtPos(const QPoint &p) |
58 | { |
59 | if (tabRect(currentIndex()).contains(p)) |
60 | return currentIndex(); |
61 | |
62 | for (int i = 0; i < count(); ++i) |
63 | if (isTabEnabled(i) && tabRect(i).contains(p)) |
64 | return i; |
65 | |
66 | return -1; |
67 | } |
68 | }; |
69 | } |
70 | |
71 | QPinnableTabWidget::QPinnableTabWidget(QWidget *parent) |
72 | : QTabWidget(parent) |
73 | { |
74 | setTabBar(new TabBarPin()); |
75 | |
76 | setTabsClosable(true); |
77 | setMovable(true); |
78 | connect(this, &QTabWidget::tabCloseRequested, this, &QPinnableTabWidget::removeTab, Qt::QueuedConnection); |
79 | connect(this, &QTabWidget::tabBarClicked, this, &QPinnableTabWidget::clickRequested); |
80 | } |
81 | |
82 | int QPinnableTabWidget::addPinnedTab(QWidget *page, const QString &label) |
83 | { |
84 | const auto tabIndex = addTab(page, label); |
85 | tabBar()->setTabButton( |
86 | tabIndex, |
87 | static_cast<QTabBar::ButtonPosition>(style()->styleHint(QStyle::SH_TabBar_CloseButtonPosition, 0, this)), |
88 | new FakeCloseButton()); |
89 | |
90 | tabBar()->moveTab(tabIndex, mLastPinTab); |
91 | |
92 | mTabState.insert(mLastPinTab++, true); |
93 | |
94 | return tabIndex; |
95 | } |
96 | |
97 | int QPinnableTabWidget::addPinnedTab(QWidget *page, const QIcon &icon, const QString &label) |
98 | { |
99 | const auto tabIndex = addTab(page, icon, label); |
100 | tabBar()->setTabButton( |
101 | tabIndex, |
102 | static_cast<QTabBar::ButtonPosition>(style()->styleHint(QStyle::SH_TabBar_CloseButtonPosition, 0, this)), |
103 | new FakeCloseButton()); |
104 | |
105 | tabBar()->moveTab(tabIndex, mLastPinTab); |
106 | |
107 | mTabState.insert(mLastPinTab++, true); |
108 | |
109 | return tabIndex; |
110 | } |
111 | |
112 | int QPinnableTabWidget::addTab(QWidget *widget, const QString &s) |
113 | { |
114 | return QTabWidget::addTab(widget, s); |
115 | } |
116 | |
117 | int QPinnableTabWidget::addTab(QWidget *widget, const QIcon &icon, const QString &label) |
118 | { |
119 | return QTabWidget::addTab(widget, icon, label); |
120 | } |
121 | |
122 | int QPinnableTabWidget::insertTab(int index, QWidget *widget, const QString &s) |
123 | { |
124 | if (index <= mLastPinTab) |
125 | index = mLastPinTab + 1; |
126 | |
127 | return QTabWidget::insertTab(index, widget, s); |
128 | } |
129 | |
130 | int QPinnableTabWidget::insertTab(int index, QWidget *widget, const QIcon &icon, const QString &label) |
131 | { |
132 | if (index <= mLastPinTab) |
133 | index = mLastPinTab + 1; |
134 | |
135 | return QTabWidget::insertTab(index, widget, icon, label); |
136 | } |
137 | |
138 | void QPinnableTabWidget::removeTab(int index) |
139 | { |
140 | QTabWidget::removeTab(index); |
141 | |
142 | if (mTabState.value(index)) |
143 | --mLastPinTab; |
144 | |
145 | mTabState.remove(index); |
146 | } |
147 | |
148 | void QPinnableTabWidget::clear() |
149 | { |
150 | QTabWidget::clear(); |
151 | mLastPinnedTab = -1; |
152 | mTabState.clear(); |
153 | mPrepareMenu = false; |
154 | mClickedTab = -1; |
155 | mLastPinTab = 0; |
156 | } |
157 | |
158 | bool QPinnableTabWidget::isPinned(int index) |
159 | { |
160 | return mTabState.contains(index); |
161 | } |
162 | |
163 | int QPinnableTabWidget::getLastPinnedTabIndex() const |
164 | { |
165 | return mLastPinTab - 1; |
166 | } |
167 | |
168 | void QPinnableTabWidget::mouseReleaseEvent(QMouseEvent *event) |
169 | { |
170 | if (event->button() == Qt::RightButton) |
171 | showContextMenu(); |
172 | else |
173 | mClickedTab = -1; |
174 | } |
175 | |
176 | void QPinnableTabWidget::clickRequested(int index) |
177 | { |
178 | mPrepareMenu = true; |
179 | mClickedTab = index; |
180 | } |
181 | |
182 | void QPinnableTabWidget::() |
183 | { |
184 | if (!mPrepareMenu) |
185 | return; |
186 | |
187 | const auto actions = new QMenu(this); |
188 | |
189 | if (mTabState.value(mClickedTab)) |
190 | connect(actions->addAction("Unpin" ), &QAction::triggered, this, &QPinnableTabWidget::unpinTab); |
191 | else |
192 | connect(actions->addAction("Pin" ), &QAction::triggered, this, &QPinnableTabWidget::pinTab); |
193 | |
194 | connect(actions->addAction("Close" ), &QAction::triggered, this, [this]() { emit tabCloseRequested(mClickedTab); }); |
195 | |
196 | actions->exec(QCursor::pos()); |
197 | } |
198 | |
199 | void QPinnableTabWidget::pinTab() |
200 | { |
201 | tabBar()->setTabButton( |
202 | mClickedTab, |
203 | static_cast<QTabBar::ButtonPosition>(style()->styleHint(QStyle::SH_TabBar_CloseButtonPosition, 0, this)), |
204 | new FakeCloseButton()); |
205 | |
206 | if (mClickedTab != mLastPinTab) |
207 | tabBar()->moveTab(mClickedTab, mLastPinTab); |
208 | |
209 | mTabState.insert(mLastPinTab, true); |
210 | |
211 | mLastPinTab = mTabState.count(); |
212 | |
213 | mClickedTab = -1; |
214 | } |
215 | |
216 | void QPinnableTabWidget::unpinTab() |
217 | { |
218 | const auto closeBtn = new RealCloseButton(); |
219 | |
220 | tabBar()->setTabButton( |
221 | mClickedTab, |
222 | static_cast<QTabBar::ButtonPosition>(style()->styleHint(QStyle::SH_TabBar_CloseButtonPosition, 0, this)), |
223 | closeBtn); |
224 | |
225 | mTabState.remove(mClickedTab); |
226 | |
227 | mLastPinTab = mTabState.count(); |
228 | |
229 | auto deletions = false; |
230 | |
231 | for (auto pair : mTabState.toStdMap()) |
232 | { |
233 | if (pair.first > mClickedTab) |
234 | { |
235 | mTabState[pair.first - 1] = pair.second; |
236 | deletions = true; |
237 | } |
238 | } |
239 | |
240 | if (deletions) |
241 | mTabState.remove(mTabState.lastKey()); |
242 | |
243 | tabBar()->moveTab(mClickedTab, mLastPinTab); |
244 | |
245 | connect(closeBtn, &RealCloseButton::clicked, this, [this]() { emit tabBar()->tabCloseRequested(mLastPinTab); }); |
246 | |
247 | mClickedTab = -1; |
248 | } |
249 | |