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
11namespace
12{
13class TabBarPin : public QTabBar
14{
15public:
16 explicit TabBarPin(QWidget *parent = nullptr)
17 : QTabBar(parent)
18 {
19 }
20
21protected:
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
53private:
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
71QPinnableTabWidget::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
82int 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
97int 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
112int QPinnableTabWidget::addTab(QWidget *widget, const QString &s)
113{
114 return QTabWidget::addTab(widget, s);
115}
116
117int QPinnableTabWidget::addTab(QWidget *widget, const QIcon &icon, const QString &label)
118{
119 return QTabWidget::addTab(widget, icon, label);
120}
121
122int 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
130int 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
138void QPinnableTabWidget::removeTab(int index)
139{
140 QTabWidget::removeTab(index);
141
142 if (mTabState.value(index))
143 --mLastPinTab;
144
145 mTabState.remove(index);
146}
147
148void QPinnableTabWidget::clear()
149{
150 QTabWidget::clear();
151 mLastPinnedTab = -1;
152 mTabState.clear();
153 mPrepareMenu = false;
154 mClickedTab = -1;
155 mLastPinTab = 0;
156}
157
158bool QPinnableTabWidget::isPinned(int index)
159{
160 return mTabState.contains(index);
161}
162
163int QPinnableTabWidget::getLastPinnedTabIndex() const
164{
165 return mLastPinTab - 1;
166}
167
168void QPinnableTabWidget::mouseReleaseEvent(QMouseEvent *event)
169{
170 if (event->button() == Qt::RightButton)
171 showContextMenu();
172 else
173 mClickedTab = -1;
174}
175
176void QPinnableTabWidget::clickRequested(int index)
177{
178 mPrepareMenu = true;
179 mClickedTab = index;
180}
181
182void QPinnableTabWidget::showContextMenu()
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
199void 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
216void 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