KDDockWidgets API Documentation 2.1
Loading...
Searching...
No Matches
qtquick/views/TabBar.cpp
Go to the documentation of this file.
1/*
2 This file is part of KDDockWidgets.
3
4 SPDX-FileCopyrightText: 2019 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com>
5 Author: SĂ©rgio Martins <sergio.martins@kdab.com>
6
7 SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only
8
9 Contact KDAB at <info@kdab.com> for commercial licensing options.
10*/
11
20#include "TabBar.h"
21#include "Stack.h"
22#include "core/DockWidget_p.h"
23#include "kddockwidgets/core/TabBar.h"
24#include "kddockwidgets/core/Stack.h"
25#include "core/ScopedValueRollback_p.h"
26#include "core/TabBar_p.h"
27#include "core/Stack_p.h"
28#include "core/Logging_p.h"
30
31#include <QMetaObject>
32#include <QMouseEvent>
33#include <QDebug>
34
35#include "kdbindings/signal.h"
36
37#include <unordered_map>
38
39using namespace KDDockWidgets;
40using namespace KDDockWidgets::QtQuick;
41
42class QtQuick::TabBar::Private
43{
44public:
45 Private(Core::TabBar *controller, TabBar *q)
46 : m_dockWidgetModel(new DockWidgetModel(controller, q))
47 {
48 }
49
50 int m_hoveredTabIndex = -1;
51 QPointer<QQuickItem> m_tabBarQmlItem;
52 DockWidgetModel *const m_dockWidgetModel;
53 KDBindings::ScopedConnection m_tabBarAutoHideChanged;
54};
55
56class DockWidgetModel::Private
57{
58public:
59 explicit Private(Core::TabBar *tabBar)
60 : m_tabBar(tabBar)
61 {
62 }
63
64 Core::TabBar *const m_tabBar = nullptr;
65 QVector<Core::DockWidget *> m_dockWidgets;
67 m_connections;
68
69 std::unordered_map<Core::DockWidget *, KDBindings::ScopedConnection>
70 m_connections2;
71
72 bool m_removeGuard = false;
73 Core::DockWidget *m_currentDockWidget = nullptr;
74};
75
76TabBar::TabBar(Core::TabBar *controller, QQuickItem *parent)
77 : View(controller, Core::ViewType::TabBar, parent)
78 , TabBarViewInterface(controller)
79 , d(new Private(controller, this))
80{
81 connect(d->m_dockWidgetModel, &DockWidgetModel::countChanged, this,
82 [controller] { controller->dptr()->countChanged.emit(); });
83}
84
86{
87 delete d;
88}
89
91{
92 d->m_tabBarAutoHideChanged = m_tabBar->stack()->d->tabBarAutoHideChanged.connect(
93 [this] { Q_EMIT tabBarAutoHideChanged(); });
94}
95
96int TabBar::tabAt(QPoint localPt) const
97{
98 // QtQuick's TabBar doesn't provide any API for this.
99 // Also note that the ListView's flickable has bogus contentX, so instead just iterate through
100 // the tabs
101
102 if (!d->m_tabBarQmlItem) {
103 qWarning() << Q_FUNC_INFO << "No visual tab bar item yet";
104 return -1;
105 }
106
107 const QPointF globalPos = d->m_tabBarQmlItem->mapToGlobal(localPt);
108
109 QVariant index;
110 const bool res =
111 QMetaObject::invokeMethod(d->m_tabBarQmlItem, "getTabIndexAtPosition",
112 Q_RETURN_ARG(QVariant, index), Q_ARG(QVariant, globalPos));
113
114 if (res)
115 return index.toInt();
116
117 return -1;
118}
119
120QQuickItem *TabBar::tabBarQmlItem() const
121{
122 return d->m_tabBarQmlItem;
123}
124
125void TabBar::setTabBarQmlItem(QQuickItem *item)
126{
127 if (d->m_tabBarQmlItem == item) {
128 qWarning() << Q_FUNC_INFO << "Should be called only once";
129 return;
130 }
131
132 d->m_tabBarQmlItem = item;
133 Q_EMIT tabBarQmlItemChanged();
134}
135
136QString TabBar::text(int index) const
137{
138 if (QQuickItem *item = tabAt(index))
139 return item->property("text").toString();
140
141 return {};
142}
143
144QRect TabBar::rectForTab(int index) const
145{
146 if (QQuickItem *item = tabAt(index))
147 return item->boundingRect().toRect();
148
149 return {};
150}
151
153{
154 if (QQuickItem *item = tabAt(index)) {
155 QRect r = item->boundingRect().toRect();
156 r.moveTopLeft(item->mapToGlobal(r.topLeft()).toPoint());
157 return r;
158 }
159
160 return {};
161}
162
164{
165 switch (ev->type()) {
168 if (d->m_tabBarQmlItem) {
169 auto me = static_cast<QMouseEvent *>(ev);
170 const int idx = tabAt(me->pos());
171 if (idx != -1)
172 d->m_tabBarQmlItem->setProperty("currentTabIndex", idx);
173
174 if (ev->type() == QEvent::MouseButtonPress)
175 m_tabBar->onMousePress(me->pos());
176 else
177 m_tabBar->onMouseDoubleClick(me->pos());
178
179 // Don't call base class, it might have been deleted
180 return true;
181 }
182
183 break;
184 }
185 default:
186 break;
187 }
188
189 return View::event(ev);
190}
191
192QQuickItem *TabBar::tabAt(int index) const
193{
194 QVariant result;
195 const bool res = QMetaObject::invokeMethod(
196 d->m_tabBarQmlItem, "getTabAtIndex", Q_RETURN_ARG(QVariant, result), Q_ARG(QVariant, index));
197
198 if (res)
199 return result.value<QQuickItem *>();
200
201 qWarning() << Q_FUNC_INFO << "Could not find tab for index" << index;
202 return nullptr;
203}
204
205// NOLINTNEXTLINE(bugprone-easily-swappable-parameters)
206void TabBar::moveTabTo(int from, int to)
207{
208 Q_UNUSED(from);
209 Q_UNUSED(to);
210 // Not implemented yet
211}
212
214{
215 return m_tabBar->stack()->tabBarAutoHide();
216}
217
219{
220 if (auto tw = dynamic_cast<Stack *>(m_tabBar->stack()->view()))
221 return tw;
222
223 qWarning() << Q_FUNC_INFO << "Unexpected null Stack_qtquick";
224 return nullptr;
225}
226
228{
229 d->m_dockWidgetModel->setCurrentIndex(index);
230}
231
232bool TabBar::closeAtIndex(int index)
233{
234 if (auto dw = d->m_dockWidgetModel->dockWidgetAt(index))
235 return dw->close();
236
237 return false;
238}
239
240void TabBar::renameTab(int, const QString &)
241{
244}
245
246void TabBar::changeTabIcon(int, const QIcon &)
247{
248 // Not implemented for QtQuick
249}
250
252{
253 d->m_dockWidgetModel->remove(dw);
254}
255
256void TabBar::insertDockWidget(int index, Core::DockWidget *dw, const QIcon &icon,
257 const QString &title)
258{
259 Q_UNUSED(title);
260 Q_UNUSED(icon);
261
262 d->m_dockWidgetModel->insert(dw, index);
263}
264
266{
267 return d->m_dockWidgetModel;
268}
269
271{
272 if (ev->type() == QEvent::HoverLeave) {
273 setHoveredTabIndex(-1);
274 } else {
275 setHoveredTabIndex(indexForTabPos(globalPos));
276 }
277}
278
280{
281 const int count = d->m_dockWidgetModel->count();
282 for (int i = 0; i < count; i++) {
283 const QRect tabRect = globalRectForTab(i);
284 if (tabRect.contains(globalPt))
285 return i;
286 }
287
288 return -1;
289}
290
291void TabBar::setHoveredTabIndex(int idx)
292{
293 if (idx == d->m_hoveredTabIndex)
294 return;
295
296 d->m_hoveredTabIndex = idx;
297 Q_EMIT hoveredTabIndexChanged(idx);
298}
299
301{
302 return d->m_hoveredTabIndex;
303}
304
305void TabBar::addDockWidgetAsTab(QQuickItem *other,
307{
308 if (!other) {
309 qWarning() << Q_FUNC_INFO << "Refusing to add null dock widget";
310 return;
311 }
312
313 Core::DockWidget *existingDw = d->m_dockWidgetModel->dockWidgetAt(0);
314 if (!existingDw) {
315 // We require an existing tab, as all this goes through DockWidget::addDockWidgetAsTab()
316 // This error can't happen, as KDDW doesn't allow users to have empty tab bars.
317 // But let's return early in any case.
318 qWarning() << Q_FUNC_INFO << "No existing tab was found";
319 return;
320 }
321
322 if (auto dwi = qobject_cast<DockWidgetInstantiator *>(other)) {
323 if (QtQuick::DockWidget *dwView = dwi->dockWidget())
324 existingDw->addDockWidgetAsTab(dwView->dockWidget(), opt);
325 } else if (auto dwView = qobject_cast<QtQuick::DockWidget *>(other)) {
326 existingDw->addDockWidgetAsTab(dwView->dockWidget(), opt);
327 } else if (auto dw = qobject_cast<Core::DockWidget *>(other)) {
328 dw->addDockWidgetAsTab(dw, opt);
329 } else {
330 qWarning() << Q_FUNC_INFO << "Could not understand what is" << other;
331 }
332}
333
335 : QAbstractListModel(parent)
336 , d(new Private(tabBar))
337{
338}
339
341{
342 delete d;
343}
344
346{
347 return d->m_dockWidgets.size();
348}
349
351{
352 return parent.isValid() ? 0 : d->m_dockWidgets.size();
353}
354
355QVariant DockWidgetModel::data(const QModelIndex &index, int role) const
356{
357 const int row = index.row();
358 if (row < 0 || row >= d->m_dockWidgets.size())
359 return {};
360
361 if (role == Role_Title) {
362 Core::DockWidget *dw = d->m_dockWidgets.at(row);
363 return dw->title();
364 }
365
366 return {};
367}
368
370{
371 if (index < 0 || index >= d->m_dockWidgets.size()) {
372 // Can happen. Benign.
373 return nullptr;
374 }
375
376 return d->m_dockWidgets.at(index);
377}
378
380{
381 return d->m_dockWidgets.contains(dw);
382}
383
385{
386 return d->m_currentDockWidget;
387}
388
390{
391
392 if (d->m_currentDockWidget && !d->m_currentDockWidget->inDtor())
393 d->m_currentDockWidget->setVisible(false);
394
395 d->m_currentDockWidget = dw;
397 if (d->m_currentDockWidget) {
398 ScopedValueRollback guard(d->m_currentDockWidget->d->m_isSettingCurrent, true);
399 d->m_currentDockWidget->setVisible(true);
400 }
401}
402
404{
405 return { { Role_Title, "title" } };
406}
407
408void DockWidgetModel::emitDataChangedFor(Core::DockWidget *dw)
409{
410 const int row = indexOf(dw);
411 if (row == -1) {
412 qWarning() << Q_FUNC_INFO << "Couldn't find" << dw;
413 } else {
414 QModelIndex index = this->index(row, 0);
416 }
417}
418
420{
421 ScopedValueRollback guard(d->m_removeGuard, true);
422 const int row = indexOf(dw);
423
424 if (row == -1) {
425 if (!d->m_removeGuard) {
426 // can happen if there's reentrancy. Some user code reacting
427 // to the signals and call remove for whatever reason.
428 qWarning() << Q_FUNC_INFO << "Nothing to remove"
429 << static_cast<void *>(dw); // Print address only, as it might be deleted
430 // already
431 }
432 } else {
433 disconnect(d->m_connections.take(dw));
434 auto it = d->m_connections2.find(dw);
435 if (it != d->m_connections2.end())
436 d->m_connections2.erase(it);
437
438 beginRemoveRows(QModelIndex(), row, row);
439 d->m_dockWidgets.removeOne(dw);
441
444 }
445}
446
448{
449 return d->m_dockWidgets.indexOf(const_cast<Core::DockWidget *>(dw));
450}
451
453{
454 if (!d->m_currentDockWidget)
455 return -1;
456
457 const int index = d->m_dockWidgets.indexOf(d->m_currentDockWidget);
458
459 if (index == -1)
460 qWarning() << Q_FUNC_INFO << "Unexpected null index for" << d->m_currentDockWidget << this
461 << "; count=" << count();
462
463 return index;
464}
465
467{
469
470 if (d->m_currentDockWidget != dw) {
471 const bool wasFocused = d->m_currentDockWidget && d->m_currentDockWidget->isFocused();
473 if (wasFocused && dw) {
474 // Because QtQuick doesn't do this for us, while QtWidgets does
476 }
477
478 // NOLINTNEXTLINE(clang-analyzer-core.CallAndMessage)
479 d->m_tabBar->setCurrentIndex(index);
480 }
481}
482
484{
485 if (d->m_dockWidgets.contains(dw)) {
486 qWarning() << Q_FUNC_INFO << "Shouldn't happen";
487 return false;
488 }
489
490 KDBindings::ScopedConnection titleChangedConnection = dw->d->titleChanged.connect([dw, this] { emitDataChangedFor(dw); });
491
492 QMetaObject::Connection destroyedConnection =
493 connect(dw, &QObject::destroyed, this, [dw, this] { remove(dw); });
494
495 d->m_connections[dw] = destroyedConnection;
496 d->m_connections2[dw] = std::move(titleChangedConnection);
497
499 d->m_dockWidgets.insert(index, dw);
501
503 return true;
504}
A ScopedConnection is a RAII-style way to make sure a Connection is disconnected.
Definition signal.h:533
View * view() const
Returns the view associated with this controller, if any.
The DockWidget base-class. DockWidget and Core::DockWidget are only split in two so we can share some...
void addDockWidgetAsTab(KDDockWidgets::Core::DockWidget *other, const KDDockWidgets::InitialOption &initialOption={})
docks other widget into this one. Tabs will be shown if not already.
QString title() const
Returns the dock widget's title. This title is visible in title bars and tab bars.
void onMouseDoubleClick(Point localPos)
void onMousePress(Point localPos)
virtual void setFocus(Qt::FocusReason)=0
Controller * controller() const
Returns this view's controller.
QVariant data(const QModelIndex &index, int role) const override
bool contains(Core::DockWidget *dw) const
QHash< int, QByteArray > roleNames() const override
DockWidgetModel(Core::TabBar *, QObject *parent)
Core::DockWidget * dockWidgetAt(int index) const
bool insert(Core::DockWidget *dw, int index)
int rowCount(const QModelIndex &parent) const override
void onHoverEvent(QHoverEvent *, QPoint globalPos) override
Override in case you want to have different styling on hover.
QRect rectForTab(int index) const override
TabBar(Core::TabBar *controller, QQuickItem *parent=nullptr)
Q_INVOKABLE void addDockWidgetAsTab(QQuickItem *other, KDDockWidgets::InitialVisibilityOption={})
void renameTab(int index, const QString &) override
Q_INVOKABLE bool closeAtIndex(int index)
void changeTabIcon(int index, const QIcon &icon) override
QString text(int index) const override
Returns the tab text for the specified index This is only used by tests, to make sure your tab's text...
void hoveredTabIndexChanged(int index)
Emitted when the hovered tab changes In case you want to style it differently.
void insertDockWidget(int index, Core::DockWidget *, const QIcon &, const QString &title) override
void moveTabTo(int from, int to) override
Q_INVOKABLE void setCurrentIndex(int index) override
int tabAt(QPoint localPos) const override
void removeDockWidget(Core::DockWidget *) override
Class to abstract QAction, so code still works with QtQuick and Flutter.
void beginInsertRows(const QModelIndex &parent, int first, int last)
void beginRemoveRows(const QModelIndex &parent, int first, int last)
void dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector< int > &roles)
virtual QModelIndex index(int row, int column, const QModelIndex &parent) const const override
MouseButtonDblClick
QEvent::Type type() const const
bool invokeMethod(QObject *obj, const char *member, Qt::ConnectionType type, QGenericReturnArgument ret, QGenericArgument val0, QGenericArgument val1, QGenericArgument val2, QGenericArgument val3, QGenericArgument val4, QGenericArgument val5, QGenericArgument val6, QGenericArgument val7, QGenericArgument val8, QGenericArgument val9)
int row() const const
Q_EMITQ_EMIT
QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
void destroyed(QObject *obj)
bool disconnect(const QObject *sender, const char *signal, const QObject *receiver, const char *method)
QObject * parent() const const
bool contains(const QRect &rectangle, bool proper) const const
void moveTopLeft(const QPoint &position)
QPoint topLeft() const const
OtherFocusReason
int toInt(bool *ok) const const
T value() const const

© Klarälvdalens Datakonsult AB (KDAB)
"The Qt, C++ and OpenGL Experts"
https://www.kdab.com/
KDDockWidgets
Advanced Dock Widget Framework for Qt
https://www.kdab.com/development-resources/qt-tools/kddockwidgets/
Generated by doxygen 1.9.8