KDDockWidgets API Documentation 1.7
Loading...
Searching...
No Matches
TitleBar.cpp
Go to the documentation of this file.
1/*
2 This file is part of KDDockWidgets.
3
4 SPDX-FileCopyrightText: 2019-2023 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
12#include "TitleBar_p.h"
13#include "Frame_p.h"
14#include "FloatingWindow_p.h"
15#include "Logging_p.h"
16#include "WindowBeingDragged_p.h"
17#include "Utils_p.h"
19#include "Config.h"
20#include "MainWindowBase.h"
21#include "MDILayoutWidget_p.h"
22#include "TabWidget_p.h"
23#include "DockWidgetBase_p.h"
24#include "DockRegistry_p.h"
25
26#include <QTimer>
27#include <QWindowStateChangeEvent>
28
29using namespace KDDockWidgets;
30
31TitleBar::TitleBar(Frame *parent)
32 : QWidgetAdapter(parent)
33 , Draggable(this)
34 , m_frame(parent)
35 , m_floatingWindow(nullptr)
36 , m_genericWidget(nullptr)
37 , m_supportsAutoHide((Config::self().flags() & Config::Flag_AutoHideSupport) == Config::Flag_AutoHideSupport)
38{
39 connect(m_frame, &Frame::numDockWidgetsChanged, this, &TitleBar::updateCloseButton);
40 connect(m_frame, &Frame::isFocusedChanged, this, &TitleBar::isFocusedChanged);
41 connect(m_frame, &Frame::isInMainWindowChanged, this, &TitleBar::updateAutoHideButton);
42
43 init();
44
46 setFocusPolicy(Qt::StrongFocus);
47}
48
49TitleBar::TitleBar(FloatingWindow *parent)
50 : QWidgetAdapter(parent)
51 , Draggable(this)
52 , m_frame(nullptr)
53 , m_floatingWindow(parent)
54 , m_genericWidget(nullptr)
55 , m_supportsAutoHide((Config::self().flags() & Config::Flag_AutoHideSupport) == Config::Flag_AutoHideSupport)
56{
57 connect(m_floatingWindow, &FloatingWindow::numFramesChanged, this, &TitleBar::updateButtons);
58 connect(m_floatingWindow, &FloatingWindow::windowStateChanged, this, &TitleBar::updateMaximizeButton);
59 connect(m_floatingWindow, &FloatingWindow::activatedChanged, this, &TitleBar::isFocusedChanged);
60 init();
61}
62
63TitleBar::TitleBar(WidgetType *parent)
64 : QWidgetAdapter(parent)
65 , Draggable(this, /*enabled=*/false) // We don't allow dragging generic windows at this time
66 , m_frame(nullptr)
67 , m_floatingWindow(nullptr)
68 , m_genericWidget(parent)
69 , m_supportsAutoHide(false)
70{
71 init();
72}
73
74void TitleBar::init()
75{
77
78 connect(this, &TitleBar::isFocusedChanged, this, [this] {
79 // repaint
80 update();
81 });
82
83 updateButtons();
84 QTimer::singleShot(0, this, &TitleBar::updateAutoHideButton); // have to wait after the frame is
85 // constructed
86}
87
88TitleBar::~TitleBar()
89{
90}
91
92bool TitleBar::onDoubleClicked()
93{
94 if ((Config::self().flags() & Config::Flag_DoubleClickMaximizes) && m_floatingWindow) {
95 // Not using isFloating(), as that can be a dock widget nested in a floating window. By convention it's floating, but it's not the title bar of the top-level window.
96 toggleMaximized();
97 return true;
98 } else if (supportsFloatingButton()) {
99 onFloatClicked();
100 return true;
101 }
102
103 return false;
104}
105
106MainWindowBase *TitleBar::mainWindow() const
107{
108 if (m_floatingWindow || m_genericWidget)
109 return nullptr;
110
111 if (m_frame)
112 return m_frame->mainWindow();
113
114 qWarning() << Q_FUNC_INFO << "null frame and null floating window";
115 return nullptr;
116}
117
118bool TitleBar::isMDI() const
119{
120 QObject *p = const_cast<TitleBar *>(this);
121 while (p) {
122 if (qobject_cast<const QWindow *>(p)) {
123 // Ignore QObject hierarchies spanning though multiple windows
124 return false;
125 }
126
127 if (qobject_cast<MDILayoutWidget *>(p))
128 return true;
129
130 if (qobject_cast<DropArea *>(p)) {
131 // Note that the TitleBar can be inside a DropArea that's inside a MDIArea
132 // so we need this additional check
133 return false;
134 }
135
136 p = p->parent();
137 }
138
139 return false;
140}
141
142void TitleBar::updateButtons()
143{
144 updateCloseButton();
145 updateFloatButton();
146 updateMaximizeButton();
147 updateMinimizeButton();
148 updateAutoHideButton();
149}
150
151void TitleBar::updateCloseButton()
152{
153
154 const bool anyNonClosable = frame() ? frame()->anyNonClosable()
155 : (floatingWindow() ? floatingWindow()->anyNonClosable()
156 : false);
157
158 setCloseButtonEnabled(!anyNonClosable);
159}
160
161void TitleBar::toggleMaximized()
162{
163 if (!m_floatingWindow)
164 return;
165
166 if (m_floatingWindow->isMaximizedOverride())
167 m_floatingWindow->showNormal();
168 else
169 m_floatingWindow->showMaximized();
170}
171
172void TitleBar::focusInEvent(QFocusEvent *ev)
173{
174 QWidgetAdapter::focusInEvent(ev);
175
176 if (!m_frame || !(Config::self().flags() & Config::Flag_TitleBarIsFocusable))
177 return;
178
179 // For some reason QWidget::setFocusProxy() isn't working, so forward manually
180 m_frame->FocusScope::focus(ev->reason());
181}
182
183bool TitleBar::isOverlayed() const
184{
185 return m_frame && m_frame->isOverlayed();
186}
187
188void TitleBar::setCloseButtonEnabled(bool enabled)
189{
190 if (enabled != m_closeButtonEnabled) {
191 m_closeButtonEnabled = enabled;
192 Q_EMIT closeButtonEnabledChanged(enabled);
193 }
194}
195
196void TitleBar::setFloatButtonVisible(bool visible)
197{
198 if (visible != m_floatButtonVisible) {
199 m_floatButtonVisible = visible;
200 Q_EMIT floatButtonVisibleChanged(visible);
201 }
202}
203
204void TitleBar::setFloatButtonToolTip(const QString &tip)
205{
206 if (tip != m_floatButtonToolTip) {
207 m_floatButtonToolTip = tip;
208 Q_EMIT floatButtonToolTipChanged(tip);
209 }
210}
211
212void TitleBar::setTitle(const QString &title)
213{
214 if (title != m_title) {
215 m_title = title;
216 update();
217 Q_EMIT titleChanged();
218 }
219}
220
221void TitleBar::setIcon(const QIcon &icon)
222{
223 m_icon = icon;
224 Q_EMIT iconChanged();
225}
226
227std::unique_ptr<WindowBeingDragged> TitleBar::makeWindow()
228{
229 if (m_genericWidget)
230 return {}; // not applicable
231
232 if (!isVisible() && window()->isVisible() && !(Config::self().flags() & Config::Flag_ShowButtonsOnTabBarIfTitleBarHidden)) {
233
234 // When using Flag_ShowButtonsOnTabBarIfTitleBarHidden we forward the call from the tab bar's
235 // buttons to the title bar's buttons, just to reuse logic
236
237 qWarning() << "TitleBar::makeWindow shouldn't be called on invisible title bar"
238 << this << window()->isVisible();
239 if (m_frame) {
240 qWarning() << "this=" << this << "; actual=" << m_frame->actualTitleBar();
241 } else if (m_floatingWindow) {
242 qWarning() << "Has floating window with titlebar=" << m_floatingWindow->titleBar()
243 << "; fw->isVisible=" << m_floatingWindow->isVisible();
244 }
245
246 Q_ASSERT(false);
247 return {};
248 }
249
250 if (m_floatingWindow) {
251 // We're already a floating window, no detach needed
252 return std::unique_ptr<WindowBeingDragged>(new WindowBeingDragged(m_floatingWindow, this));
253 }
254
255 if (FloatingWindow *fw = QWidgetAdapter::floatingWindow()) { // Already floating
256 if (m_frame->isTheOnlyFrame()) { // We don't detach. This one drags the entire window instead.
257 qCDebug(hovering) << "TitleBar::makeWindow no detach needed";
258 return std::unique_ptr<WindowBeingDragged>(new WindowBeingDragged(fw, this));
259 }
260 }
261
262 QRect r = m_frame->QWidgetAdapter::geometry();
263 r.moveTopLeft(m_frame->mapToGlobal(QPoint(0, 0)));
264
265 auto floatingWindow = Config::self().frameworkWidgetFactory()->createFloatingWindow(m_frame);
266 floatingWindow->setSuggestedGeometry(r, SuggestedGeometryHint_GeometryIsFromDocked);
267 floatingWindow->show();
268
269 auto draggable = KDDockWidgets::usesNativeTitleBar() ? static_cast<Draggable *>(floatingWindow)
270 : static_cast<Draggable *>(this);
271 return std::unique_ptr<WindowBeingDragged>(new WindowBeingDragged(floatingWindow, draggable));
272}
273
274DockWidgetBase *TitleBar::singleDockWidget() const
275{
276 const DockWidgetBase::List dockWidgets = this->dockWidgets();
277 return dockWidgets.isEmpty() ? nullptr : dockWidgets.first();
278}
279
280bool TitleBar::supportsFloatingButton() const
281{
282 if (m_genericWidget)
283 return {}; // not applicable
284
286 // Apps having a maximize/restore button traditionally don't have a floating one,
287 // QDockWidget style only has floating and no maximize/restore.
288 // We can add an option later if we need them to co-exist
289 return false;
290 }
291
293 // Was explicitly disabled
294 return false;
295 }
296
297 if (DockWidgetBase *dw = singleDockWidget()) {
298 // Don't show the dock/undock button if the window is not dockable
299 if (dw->options() & DockWidgetBase::Option_NotDockable)
300 return false;
301 }
302
303 // If we have a floating window with nested dock widgets we can't re-attach, because we don't
304 // know where to
305 return !m_floatingWindow || m_floatingWindow->hasSingleFrame();
306}
307
308bool TitleBar::supportsMaximizeButton() const
309{
310 return m_floatingWindow && m_floatingWindow->supportsMaximizeButton();
311}
312
313bool TitleBar::supportsMinimizeButton() const
314{
315 return m_floatingWindow && m_floatingWindow->supportsMinimizeButton();
316}
317
318bool TitleBar::supportsAutoHideButton() const
319{
320 // Only dock widgets docked into the MainWindow can minimize
321 return m_supportsAutoHide && m_frame && (m_frame->isInMainWindow() || m_frame->isOverlayed());
322}
323
324bool TitleBar::hasIcon() const
325{
326 return !m_icon.isNull();
327}
328
329bool TitleBar::isFocused() const
330{
331 if (m_frame)
332 return m_frame->isFocused();
333 else if (m_floatingWindow)
334 return m_floatingWindow->isActiveWindow();
335#ifdef KDDOCKWIDGETS_QTWIDGETS
336 // Remove ifdef in 2.0 branch.
337 else if (m_genericWidget)
338 return m_genericWidget->isActiveWindow();
339#endif
340 return false;
341}
342
343QIcon TitleBar::icon() const
344{
345 return m_icon;
346}
347
348void TitleBar::onCloseClicked()
349{
350 const bool closeOnlyCurrentTab = Config::self().flags() & Config::Flag_CloseOnlyCurrentTab;
351
352 if (m_frame) {
353 if (closeOnlyCurrentTab) {
354 if (DockWidgetBase *dw = m_frame->currentDockWidget()) {
355 dw->close();
356 } else {
357 // Doesn't happen
358 qWarning() << Q_FUNC_INFO << "Frame with no dock widgets";
359 }
360 } else {
361 if (m_frame->isTheOnlyFrame() && m_frame->isInFloatingWindow()) {
362 m_frame->window()->close();
363 } else {
364 m_frame->close();
365 }
366 }
367 } else if (m_floatingWindow) {
368
369 if (closeOnlyCurrentTab) {
370 if (Frame *f = m_floatingWindow->singleFrame()) {
371 if (DockWidgetBase *dw = f->currentDockWidget()) {
372 dw->close();
373 } else {
374 // Doesn't happen
375 qWarning() << Q_FUNC_INFO << "Frame with no dock widgets";
376 }
377 } else {
378 m_floatingWindow->close();
379 }
380 } else {
381 m_floatingWindow->close();
382 }
383 } else if (m_genericWidget) {
384 if (auto window = m_genericWidget->window())
385 window->close();
386 }
387}
388
389bool TitleBar::isFloating() const
390{
391 if (m_floatingWindow)
392 return true;
393
394 if (m_frame)
395 return m_frame->isFloating();
396
397 if (m_genericWidget)
398 return false; // Not applicable
399
400 qWarning() << "TitleBar::isFloating: shouldn't happen";
401 return false;
402}
403
404DockWidgetBase::List TitleBar::dockWidgets() const
405{
406 if (m_floatingWindow) {
408 for (Frame *f : m_floatingWindow->frames()) {
409 result << f->dockWidgets();
410 }
411 return result;
412 }
413
414 if (m_frame)
415 return m_frame->dockWidgets();
416
417 if (m_genericWidget)
418 return {}; // Not applicable
419
420 qWarning() << "TitleBar::dockWidget: shouldn't happen";
421 return {};
422}
423
424void TitleBar::onFloatClicked()
425{
426 const DockWidgetBase::List dockWidgets = this->dockWidgets();
427 if (isFloating()) {
428 // Let's dock it
429
430 if (dockWidgets.isEmpty()) {
431 qWarning() << "TitleBar::onFloatClicked: empty list. Shouldn't happen";
432 return;
433 }
434
435 if (dockWidgets.size() == 1) {
436 // Case 1: Single dockwidget floating
437 dockWidgets[0]->setFloating(false);
438 } else {
439 // Case 2: Multiple dockwidgets are tabbed together and floating
440 // TODO: Just reuse the whole frame and put it back. The frame currently doesn't remember the position in the main window
441 // so use an hack for now
442 if (!dockWidgets.isEmpty()) { // could be empty during destruction, maybe
443 if (!dockWidgets.constFirst()->hasPreviousDockedLocation()) {
444 // Don't attempt, there's no previous docked location
445 return;
446 }
447
448 int i = 0;
449 DockWidgetBase *current = nullptr;
450 for (auto dock : qAsConst(dockWidgets)) {
451
452 if (!current && dock->isCurrentTab())
453 current = dock;
454
455 dock->setFloating(true);
456 dock->dptr()->m_lastPosition->m_tabIndex = i;
457 dock->setFloating(false);
458 ++i;
459 }
460
461 // Restore the current tab
462 if (current)
463 current->setAsCurrentTab();
464 }
465 }
466 } else {
467 // Let's float it
468 if (dockWidgets.size() == 1) {
469 // If there's a single dock widget, just call DockWidget::setFloating(true). The only difference
470 // is that it has logic for using the last used geometry for the floating window
471 dockWidgets[0]->setFloating(true);
472 } else {
473 makeWindow();
474 }
475 }
476}
477
478void TitleBar::onMaximizeClicked()
479{
480 toggleMaximized();
481}
482
483void TitleBar::onMinimizeClicked()
484{
485 if (!m_floatingWindow)
486 return;
487
488 if (m_floatingWindow->isUtilityWindow()) {
489 // Qt::Tool windows don't appear in the task bar.
490 // Unless someone tells me a good reason to allow this situation.
491 return;
492 }
493
494 m_floatingWindow->showMinimized();
495}
496
497void TitleBar::onAutoHideClicked()
498{
499 if (!m_frame) {
500 // Doesn't happen
501 qWarning() << Q_FUNC_INFO << "Minimize not supported on floating windows";
502 return;
503 }
504
505 const auto dockwidgets = m_frame->dockWidgets();
506
507 if (isOverlayed() && dockwidgets.size() != 1) {
508 // Doesn't happen
509 qWarning() << Q_FUNC_INFO << "There can only be a single dock widget per titlebar overlayed";
510 return;
511 }
512
513 const bool groupedAutoHide = Config::self().flags() & Config::Flag_AutoHideAsTabGroups;
514 const auto currentDw = m_frame->currentDockWidget();
515 auto registry = DockRegistry::self();
516
517 if (isOverlayed()) { // Restore it:
518 auto dw = dockwidgets.first();
519 MainWindowBase *mainWindow = dw->mainWindow();
520 auto sideBarGroup = groupedAutoHide ? registry->sideBarGroupingFor(dw) : DockWidgetBase::List();
521 if (sideBarGroup.isEmpty()) {
522 mainWindow->restoreFromSideBar(dw);
523 } else {
524 // Config::Flag_AutoHideAsTabGroups case. Restore its friends too
525 for (auto it = sideBarGroup.rbegin(); it != sideBarGroup.rend(); ++it) {
526 mainWindow->restoreFromSideBar(*it);
527 }
528 dw->setAsCurrentTab();
529 registry->removeSideBarGrouping(sideBarGroup);
530 }
531 } else { // Overlay it:
532
533 if (groupedAutoHide)
534 registry->addSideBarGrouping(dockwidgets);
535
536 for (DockWidgetBase *dw : dockwidgets) {
537 if (groupedAutoHide || dw == currentDw)
538 dw->moveToSideBar();
539 }
540 }
541}
542
543bool TitleBar::closeButtonEnabled() const
544{
545 return m_closeButtonEnabled;
546}
547
548bool TitleBar::floatButtonVisible() const
549{
550 return m_floatButtonVisible;
551}
552
553QString TitleBar::floatButtonToolTip() const
554{
555 return m_floatButtonToolTip;
556}
557
558void TitleBar::updateFloatButton()
559{
560 setFloatButtonToolTip(floatingWindow() ? tr("Dock window") : tr("Undock window"));
561 setFloatButtonVisible(supportsFloatingButton());
562}
563
564bool TitleBar::isWindow() const
565{
566 return m_floatingWindow != nullptr;
567}
568
569TabBar *TitleBar::tabBar() const
570{
571 if (m_floatingWindow && m_floatingWindow->hasSingleFrame()) {
572 if (Frame *frame = m_floatingWindow->singleFrame()) {
573 return frame->tabWidget()->tabBar();
574 } else {
575 // Shouldn't happen
576 qWarning() << Q_FUNC_INFO << "Expected a frame";
577 }
578
579 } else if (m_frame) {
580 return m_frame->tabWidget()->tabBar();
581 }
582
583 return nullptr;
584}
Application-wide config to tune certain behaviours of the framework.
A factory class for allowing the user to customize some internal widgets.
The MainWindow base-class that's shared between QtWidgets and QtQuick stack.
Singleton to allow to choose certain behaviours of the framework.
Definition Config.h:75
FrameworkWidgetFactory * frameworkWidgetFactory() const
getter for the framework widget factory
Definition Config.cpp:145
static Config & self()
returns the singleton Config instance
Definition Config.cpp:84
Flags flags() const
returns the chosen flags
Definition Config.cpp:95
@ Flag_TitleBarNoFloatButton
The TitleBar won't show the float button.
Definition Config.h:102
@ Flag_ShowButtonsOnTabBarIfTitleBarHidden
When using Flag_HideTitleBarWhenTabsVisible the close/float buttons disappear with the title bar....
Definition Config.h:107
@ Flag_TitleBarHasMaximizeButton
The title bar will have a maximize/restore button when floating. This is mutually-exclusive with the ...
Definition Config.h:97
@ Flag_DoubleClickMaximizes
Double clicking the titlebar will maximize a floating window instead of re-docking it.
Definition Config.h:96
@ Flag_CloseOnlyCurrentTab
The TitleBar's close button will only close the current tab, instead of all of them.
Definition Config.h:106
@ Flag_AutoHideAsTabGroups
If tabbed dockwidgets are sent to/from sidebar, they're all sent and restored together.
Definition Config.h:109
@ Flag_TitleBarIsFocusable
You can click the title bar and it will focus the last focused widget in the focus scope....
Definition Config.h:98
The DockWidget base-class. DockWidget and DockWidgetBase are only split in two so we can share some c...
Q_INVOKABLE void setAsCurrentTab()
Makes this dock widget current in its tab group.
bool setFloating(bool floats)
setter to make the dock widget float or dock.
QVector< DockWidgetBase * > List
@ Option_NotDockable
The DockWidget can't be docked, it's always floating.
virtual FloatingWindow * createFloatingWindow(MainWindowBase *parent=nullptr, FloatingWindowFlags=FloatingWindowFlag::FromGlobalConfig) const =0
Called internally by the framework to create a FloatingWindow Override to provide your own FloatingWi...
The MainWindow base-class. MainWindow and MainWindowBase are only split in two so we can share some c...
Q_INVOKABLE void restoreFromSideBar(KDDockWidgets::DockWidgetBase *)
Removes the dock widget from the sidebar and docks it into the main window again.
@ SuggestedGeometryHint_GeometryIsFromDocked
Qt::FocusReason reason() const const
QObject * parent() const const
void moveTopLeft(const QPoint &position)
StrongFocus
const T & constFirst() const const
T & first()
bool isEmpty() const const
int size() const const

© 2019-2023 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 on Wed Nov 1 2023 00:02:31 for KDDockWidgets API Documentation by doxygen 1.9.8