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#include "qdockwidget.h"
5
6#include <qaction.h>
7#include <qapplication.h>
8#include <qdrawutil.h>
9#include <qevent.h>
10#include <qfontmetrics.h>
11#include <qproxystyle.h>
12#include <qwindow.h>
13#include <qscreen.h>
14#include <qmainwindow.h>
15#include <qstylepainter.h>
16#include <qtoolbutton.h>
17#include <qdebug.h>
18
19#include <private/qwidgetresizehandler_p.h>
20#include <private/qstylesheetstyle_p.h>
21#include <qpa/qplatformtheme.h>
22
23#include <private/qhighdpiscaling_p.h>
24#include "qdockwidget_p.h"
25#include "qmainwindowlayout_p.h"
26
27QT_BEGIN_NAMESPACE
28
29using namespace Qt::StringLiterals;
30
31extern QString qt_setWindowTitle_helperHelper(const QString&, const QWidget*); // qwidget.cpp
32
33// qmainwindow.cpp
34extern QMainWindowLayout *qt_mainwindow_layout(const QMainWindow *window);
35
36static const QMainWindow *mainwindow_from_dock(const QDockWidget *dock)
37{
38 for (const QWidget *p = dock->parentWidget(); p; p = p->parentWidget()) {
39 if (const QMainWindow *window = qobject_cast<const QMainWindow*>(object: p))
40 return window;
41 }
42 return nullptr;
43}
44
45static inline QMainWindowLayout *qt_mainwindow_layout_from_dock(const QDockWidget *dock)
46{
47 auto mainWindow = mainwindow_from_dock(dock);
48 return mainWindow ? qt_mainwindow_layout(window: mainWindow) : nullptr;
49}
50
51static inline bool hasFeature(const QDockWidgetPrivate *priv, QDockWidget::DockWidgetFeature feature)
52{ return (priv->features & feature) == feature; }
53
54static inline bool hasFeature(const QDockWidget *dockwidget, QDockWidget::DockWidgetFeature feature)
55{ return (dockwidget->features() & feature) == feature; }
56
57
58/*
59 A Dock Window:
60
61 [+] is the float button
62 [X] is the close button
63
64 +-------------------------------+
65 | Dock Window Title [+][X]|
66 +-------------------------------+
67 | |
68 | place to put the single |
69 | QDockWidget child (this space |
70 | does not yet have a name) |
71 | |
72 | |
73 | |
74 | |
75 | |
76 | |
77 | |
78 | |
79 | |
80 +-------------------------------+
81
82*/
83
84/******************************************************************************
85** QDockWidgetTitleButton
86*/
87
88class QDockWidgetTitleButton : public QAbstractButton
89{
90 Q_OBJECT
91
92public:
93 QDockWidgetTitleButton(QDockWidget *dockWidget);
94
95 QSize sizeHint() const override;
96 QSize minimumSizeHint() const override
97 { return sizeHint(); }
98
99 void enterEvent(QEnterEvent *event) override;
100 void leaveEvent(QEvent *event) override;
101 void paintEvent(QPaintEvent *event) override;
102
103protected:
104 bool event(QEvent *event) override;
105
106private:
107 QSize dockButtonIconSize() const;
108
109 mutable int m_iconSize = -1;
110};
111
112QDockWidgetTitleButton::QDockWidgetTitleButton(QDockWidget *dockWidget)
113 : QAbstractButton(dockWidget)
114{
115 setFocusPolicy(Qt::NoFocus);
116}
117
118bool QDockWidgetTitleButton::event(QEvent *event)
119{
120 switch (event->type()) {
121 case QEvent::StyleChange:
122 case QEvent::DevicePixelRatioChange:
123 m_iconSize = -1;
124 break;
125 default:
126 break;
127 }
128 return QAbstractButton::event(e: event);
129}
130
131QSize QDockWidgetTitleButton::dockButtonIconSize() const
132{
133 if (m_iconSize < 0) {
134 m_iconSize = style()->pixelMetric(metric: QStyle::PM_SmallIconSize, option: nullptr, widget: this);
135 if (style()->styleHint(stylehint: QStyle::SH_DockWidget_ButtonsHaveFrame, opt: nullptr, widget: this))
136 m_iconSize = (m_iconSize * 5) / 8; // 16 -> 10
137 }
138 return QSize(m_iconSize, m_iconSize);
139}
140
141QSize QDockWidgetTitleButton::sizeHint() const
142{
143 ensurePolished();
144
145 int size = 2*style()->pixelMetric(metric: QStyle::PM_DockWidgetTitleBarButtonMargin, option: nullptr, widget: this);
146 if (!icon().isNull()) {
147 const QSize sz = icon().actualSize(size: dockButtonIconSize());
148 size += qMax(a: sz.width(), b: sz.height());
149 }
150
151 return QSize(size, size);
152}
153
154void QDockWidgetTitleButton::enterEvent(QEnterEvent *event)
155{
156 if (isEnabled()) update();
157 QAbstractButton::enterEvent(event);
158}
159
160void QDockWidgetTitleButton::leaveEvent(QEvent *event)
161{
162 if (isEnabled()) update();
163 QAbstractButton::leaveEvent(event);
164}
165
166void QDockWidgetTitleButton::paintEvent(QPaintEvent *)
167{
168 QStylePainter p(this);
169
170 QStyleOptionToolButton opt;
171 opt.initFrom(w: this);
172 opt.state |= QStyle::State_AutoRaise;
173
174 if (style()->styleHint(stylehint: QStyle::SH_DockWidget_ButtonsHaveFrame, opt: nullptr, widget: this)) {
175 if (isEnabled() && underMouse() && !isChecked() && !isDown())
176 opt.state |= QStyle::State_Raised;
177 if (isChecked())
178 opt.state |= QStyle::State_On;
179 if (isDown())
180 opt.state |= QStyle::State_Sunken;
181 p.drawPrimitive(pe: QStyle::PE_PanelButtonTool, opt);
182 } else if (isDown() || isChecked()) {
183 // no frame, but the icon might have explicit pixmaps for QIcon::On
184 opt.state |= QStyle::State_On | QStyle::State_Sunken;
185 }
186
187 opt.icon = icon();
188 opt.subControls = { };
189 opt.activeSubControls = { };
190 opt.features = QStyleOptionToolButton::None;
191 opt.arrowType = Qt::NoArrow;
192 opt.iconSize = dockButtonIconSize();
193 p.drawComplexControl(cc: QStyle::CC_ToolButton, opt);
194}
195
196/******************************************************************************
197** QDockWidgetLayout
198*/
199
200QDockWidgetLayout::QDockWidgetLayout(QWidget *parent)
201 : QLayout(parent), verticalTitleBar(false), item_list(RoleCount, 0)
202{
203}
204
205QDockWidgetLayout::~QDockWidgetLayout()
206{
207 qDeleteAll(c: item_list);
208}
209
210/*! \internal
211 Returns true if the dock widget managed by this layout should have a native
212 window decoration or if Qt needs to draw it.
213 */
214bool QDockWidgetLayout::nativeWindowDeco() const
215{
216 bool floating = parentWidget()->isWindow();
217#if QT_CONFIG(tabbar)
218 if (auto groupWindow =
219 qobject_cast<const QDockWidgetGroupWindow *>(object: parentWidget()->parentWidget()))
220 floating = floating || groupWindow->tabLayoutInfo();
221#endif
222 return nativeWindowDeco(floating);
223}
224
225/*! \internal
226 Returns true if the window manager can draw natively the windows decoration
227 of a dock widget
228 */
229bool QDockWidgetLayout::wmSupportsNativeWindowDeco()
230{
231#if defined(Q_OS_ANDROID)
232 return false;
233#else
234 static const bool xcb = !QGuiApplication::platformName().compare(other: "xcb"_L1, cs: Qt::CaseInsensitive);
235 static const bool wayland =
236 QGuiApplication::platformName().startsWith(s: "wayland"_L1, cs: Qt::CaseInsensitive);
237 return !(xcb || wayland);
238#endif
239}
240
241/*! \internal
242 Returns true if the dock widget managed by this layout should have a native
243 window decoration or if Qt needs to draw it. The \a floating parameter
244 overrides the floating current state of the dock widget.
245 */
246bool QDockWidgetLayout::nativeWindowDeco(bool floating) const
247{
248 return wmSupportsNativeWindowDeco() && floating && item_list.at(i: QDockWidgetLayout::TitleBar) == nullptr;
249}
250
251
252void QDockWidgetLayout::addItem(QLayoutItem*)
253{
254 qWarning(msg: "QDockWidgetLayout::addItem(): please use QDockWidgetLayout::setWidget()");
255 return;
256}
257
258QLayoutItem *QDockWidgetLayout::itemAt(int index) const
259{
260 int cnt = 0;
261 for (int i = 0; i < item_list.size(); ++i) {
262 QLayoutItem *item = item_list.at(i);
263 if (item == nullptr)
264 continue;
265 if (index == cnt++)
266 return item;
267 }
268 return nullptr;
269}
270
271QLayoutItem *QDockWidgetLayout::takeAt(int index)
272{
273 int j = 0;
274 for (int i = 0; i < item_list.size(); ++i) {
275 QLayoutItem *item = item_list.at(i);
276 if (item == nullptr)
277 continue;
278 if (index == j) {
279 item_list[i] = 0;
280 invalidate();
281 return item;
282 }
283 ++j;
284 }
285 return nullptr;
286}
287
288int QDockWidgetLayout::count() const
289{
290 int result = 0;
291 for (int i = 0; i < item_list.size(); ++i) {
292 if (item_list.at(i))
293 ++result;
294 }
295 return result;
296}
297
298QSize QDockWidgetLayout::sizeFromContent(const QSize &content, bool floating) const
299{
300 QSize result = content;
301
302 if (verticalTitleBar) {
303 result.setHeight(qMax(a: result.height(), b: minimumTitleWidth()));
304 result.setWidth(qMax(a: content.width(), b: 0));
305 } else {
306 result.setHeight(qMax(a: result.height(), b: 0));
307 result.setWidth(qMax(a: content.width(), b: minimumTitleWidth()));
308 }
309
310 QDockWidget *w = qobject_cast<QDockWidget*>(object: parentWidget());
311 const bool nativeDeco = nativeWindowDeco(floating);
312
313 int fw = floating && !nativeDeco
314 ? w->style()->pixelMetric(metric: QStyle::PM_DockWidgetFrameWidth, option: nullptr, widget: w)
315 : 0;
316
317 const int th = titleHeight();
318 if (!nativeDeco) {
319 if (verticalTitleBar)
320 result += QSize(th + 2*fw, 2*fw);
321 else
322 result += QSize(2*fw, th + 2*fw);
323 }
324
325 result.setHeight(qMin(a: result.height(), b: (int) QWIDGETSIZE_MAX));
326 result.setWidth(qMin(a: result.width(), b: (int) QWIDGETSIZE_MAX));
327
328 if (content.width() < 0)
329 result.setWidth(-1);
330 if (content.height() < 0)
331 result.setHeight(-1);
332
333 const QMargins margins = w->contentsMargins();
334 //we need to subtract the contents margin (it will be added by the caller)
335 QSize min = w->minimumSize().shrunkBy(m: margins);
336 QSize max = w->maximumSize().shrunkBy(m: margins);
337
338 /* A floating dockwidget will automatically get its minimumSize set to the layout's
339 minimum size + deco. We're *not* interested in this, we only take minimumSize()
340 into account if the user set it herself. Otherwise we end up expanding the result
341 of a calculation for a non-floating dock widget to a floating dock widget's
342 minimum size + window decorations. */
343
344 uint explicitMin = 0;
345 uint explicitMax = 0;
346 if (w->d_func()->extra != nullptr) {
347 explicitMin = w->d_func()->extra->explicitMinSize;
348 explicitMax = w->d_func()->extra->explicitMaxSize;
349 }
350
351 if (!(explicitMin & Qt::Horizontal) || min.width() == 0)
352 min.setWidth(-1);
353 if (!(explicitMin & Qt::Vertical) || min.height() == 0)
354 min.setHeight(-1);
355
356 if (!(explicitMax & Qt::Horizontal))
357 max.setWidth(QWIDGETSIZE_MAX);
358 if (!(explicitMax & Qt::Vertical))
359 max.setHeight(QWIDGETSIZE_MAX);
360
361 return result.boundedTo(otherSize: max).expandedTo(otherSize: min);
362}
363
364QSize QDockWidgetLayout::sizeHint() const
365{
366 QDockWidget *w = qobject_cast<QDockWidget*>(object: parentWidget());
367
368 QSize content(-1, -1);
369 if (item_list[Content] != 0)
370 content = item_list[Content]->sizeHint();
371
372 return sizeFromContent(content, floating: w->isFloating());
373}
374
375QSize QDockWidgetLayout::maximumSize() const
376{
377 if (item_list[Content] != 0) {
378 const QSize content = item_list[Content]->maximumSize();
379 return sizeFromContent(content, floating: parentWidget()->isWindow());
380 } else {
381 return parentWidget()->maximumSize();
382 }
383
384}
385
386QSize QDockWidgetLayout::minimumSize() const
387{
388 QDockWidget *w = qobject_cast<QDockWidget*>(object: parentWidget());
389
390 QSize content(0, 0);
391 if (item_list[Content] != 0)
392 content = item_list[Content]->minimumSize();
393
394 return sizeFromContent(content, floating: w->isFloating());
395}
396
397QWidget *QDockWidgetLayout::widgetForRole(Role r) const
398{
399 QLayoutItem *item = item_list.at(i: r);
400 return item == nullptr ? nullptr : item->widget();
401}
402
403QLayoutItem *QDockWidgetLayout::itemForRole(Role r) const
404{
405 return item_list.at(i: r);
406}
407
408void QDockWidgetLayout::setWidgetForRole(Role r, QWidget *w)
409{
410 QWidget *old = widgetForRole(r);
411 if (old != nullptr) {
412 old->hide();
413 removeWidget(w: old);
414 }
415
416 if (w != nullptr) {
417 addChildWidget(w);
418 item_list[r] = new QWidgetItemV2(w);
419 w->show();
420 } else {
421 item_list[r] = 0;
422 }
423
424 invalidate();
425}
426
427static inline int pick(bool vertical, const QSize &size)
428{
429 return vertical ? size.height() : size.width();
430}
431
432static inline int perp(bool vertical, const QSize &size)
433{
434 return vertical ? size.width() : size.height();
435}
436
437int QDockWidgetLayout::minimumTitleWidth() const
438{
439 QDockWidget *q = qobject_cast<QDockWidget*>(object: parentWidget());
440
441 if (QWidget *title = widgetForRole(r: TitleBar))
442 return pick(vertical: verticalTitleBar, size: title->minimumSizeHint());
443
444 QSize closeSize(0, 0);
445 QSize floatSize(0, 0);
446 if (hasFeature(dockwidget: q, feature: QDockWidget::DockWidgetClosable)) {
447 if (QLayoutItem *item = item_list[CloseButton])
448 closeSize = item->widget()->sizeHint();
449 }
450 if (hasFeature(dockwidget: q, feature: QDockWidget::DockWidgetFloatable)) {
451 if (QLayoutItem *item = item_list[FloatButton])
452 floatSize = item->widget()->sizeHint();
453 }
454
455 int titleHeight = this->titleHeight();
456
457 int mw = q->style()->pixelMetric(metric: QStyle::PM_DockWidgetTitleMargin, option: nullptr, widget: q);
458 int fw = q->style()->pixelMetric(metric: QStyle::PM_DockWidgetFrameWidth, option: nullptr, widget: q);
459
460 return pick(vertical: verticalTitleBar, size: closeSize)
461 + pick(vertical: verticalTitleBar, size: floatSize)
462 + titleHeight + 2*fw + 3*mw;
463}
464
465int QDockWidgetLayout::titleHeight() const
466{
467 QDockWidget *q = qobject_cast<QDockWidget*>(object: parentWidget());
468
469 if (QWidget *title = widgetForRole(r: TitleBar))
470 return perp(vertical: verticalTitleBar, size: title->sizeHint());
471
472 QSize closeSize(0, 0);
473 QSize floatSize(0, 0);
474 if (QLayoutItem *item = item_list[CloseButton])
475 closeSize = item->widget()->sizeHint();
476 if (QLayoutItem *item = item_list[FloatButton])
477 floatSize = item->widget()->sizeHint();
478
479 int buttonHeight = qMax(a: perp(vertical: verticalTitleBar, size: closeSize),
480 b: perp(vertical: verticalTitleBar, size: floatSize));
481
482 QFontMetrics titleFontMetrics = q->fontMetrics();
483 int mw = q->style()->pixelMetric(metric: QStyle::PM_DockWidgetTitleMargin, option: nullptr, widget: q);
484
485 return qMax(a: buttonHeight + 2, b: titleFontMetrics.height() + 2*mw);
486}
487
488void QDockWidgetLayout::setGeometry(const QRect &geometry)
489{
490 QDockWidget *q = qobject_cast<QDockWidget*>(object: parentWidget());
491
492 bool nativeDeco = nativeWindowDeco();
493
494 int fw = q->isFloating() && !nativeDeco
495 ? q->style()->pixelMetric(metric: QStyle::PM_DockWidgetFrameWidth, option: nullptr, widget: q)
496 : 0;
497
498 if (nativeDeco) {
499 if (QLayoutItem *item = item_list[Content])
500 item->setGeometry(geometry);
501 } else {
502 int titleHeight = this->titleHeight();
503
504 if (verticalTitleBar) {
505 _titleArea = QRect(QPoint(fw, fw),
506 QSize(titleHeight, geometry.height() - (fw * 2)));
507 } else {
508 _titleArea = QRect(QPoint(fw, fw),
509 QSize(geometry.width() - (fw * 2), titleHeight));
510 }
511
512 if (QLayoutItem *item = item_list[TitleBar]) {
513 item->setGeometry(_titleArea);
514 } else {
515 QStyleOptionDockWidget opt;
516 q->initStyleOption(option: &opt);
517
518 if (QLayoutItem *item = item_list[CloseButton]) {
519 if (!item->isEmpty()) {
520 QRect r = q->style()
521 ->subElementRect(subElement: QStyle::SE_DockWidgetCloseButton,
522 option: &opt, widget: q);
523 if (!r.isNull())
524 item->setGeometry(r);
525 }
526 }
527
528 if (QLayoutItem *item = item_list[FloatButton]) {
529 if (!item->isEmpty()) {
530 QRect r = q->style()
531 ->subElementRect(subElement: QStyle::SE_DockWidgetFloatButton,
532 option: &opt, widget: q);
533 if (!r.isNull())
534 item->setGeometry(r);
535 }
536 }
537 }
538
539 if (QLayoutItem *item = item_list[Content]) {
540 QRect r = geometry;
541 if (verticalTitleBar) {
542 r.setLeft(_titleArea.right() + 1);
543 r.adjust(dx1: 0, dy1: fw, dx2: -fw, dy2: -fw);
544 } else {
545 r.setTop(_titleArea.bottom() + 1);
546 r.adjust(dx1: fw, dy1: 0, dx2: -fw, dy2: -fw);
547 }
548 item->setGeometry(r);
549 }
550 }
551}
552
553void QDockWidgetLayout::setVerticalTitleBar(bool b)
554{
555 if (b == verticalTitleBar)
556 return;
557 verticalTitleBar = b;
558 invalidate();
559 parentWidget()->update();
560}
561
562/******************************************************************************
563** QDockWidgetItem
564*/
565
566QDockWidgetItem::QDockWidgetItem(QDockWidget *dockWidget)
567 : QWidgetItem(dockWidget)
568{
569}
570
571QSize QDockWidgetItem::minimumSize() const
572{
573 QSize widgetMin(0, 0);
574 if (QLayoutItem *item = dockWidgetChildItem())
575 widgetMin = item->minimumSize();
576 return dockWidgetLayout()->sizeFromContent(content: widgetMin, floating: false);
577}
578
579QSize QDockWidgetItem::maximumSize() const
580{
581 if (QLayoutItem *item = dockWidgetChildItem()) {
582 return dockWidgetLayout()->sizeFromContent(content: item->maximumSize(), floating: false);
583 } else {
584 return QSize(QWIDGETSIZE_MAX, QWIDGETSIZE_MAX);
585 }
586}
587
588
589QSize QDockWidgetItem::sizeHint() const
590{
591 if (QLayoutItem *item = dockWidgetChildItem()) {
592 return dockWidgetLayout()->sizeFromContent(content: item->sizeHint(), floating: false);
593 } else {
594 return QWidgetItem::sizeHint();
595 }
596}
597
598/******************************************************************************
599** QDockWidgetPrivate
600*/
601
602void QDockWidgetPrivate::init()
603{
604 Q_Q(QDockWidget);
605
606 QDockWidgetLayout *layout = new QDockWidgetLayout(q);
607 layout->setSizeConstraint(QLayout::SetMinAndMaxSize);
608
609 QAbstractButton *button = new QDockWidgetTitleButton(q);
610 button->setObjectName("qt_dockwidget_floatbutton"_L1);
611 QObjectPrivate::connect(sender: button, signal: &QAbstractButton::clicked,
612 receiverPrivate: this, slot: &QDockWidgetPrivate::toggleTopLevel);
613 layout->setWidgetForRole(r: QDockWidgetLayout::FloatButton, w: button);
614
615 button = new QDockWidgetTitleButton(q);
616 button->setObjectName("qt_dockwidget_closebutton"_L1);
617 QObject::connect(sender: button, signal: &QAbstractButton::clicked, context: q, slot: &QDockWidget::close);
618 layout->setWidgetForRole(r: QDockWidgetLayout::CloseButton, w: button);
619
620 font = QApplication::font(className: "QDockWidgetTitle");
621
622#ifndef QT_NO_ACTION
623 toggleViewAction = new QAction(q);
624 toggleViewAction->setCheckable(true);
625 toggleViewAction->setMenuRole(QAction::NoRole);
626 fixedWindowTitle = qt_setWindowTitle_helperHelper(q->windowTitle(), q);
627 toggleViewAction->setText(fixedWindowTitle);
628 QObjectPrivate::connect(sender: toggleViewAction, signal: &QAction::triggered,
629 receiverPrivate: this, slot: &QDockWidgetPrivate::toggleView);
630#endif
631
632 updateButtons();
633}
634
635/*!
636 Initialize \a option with the values from this QDockWidget. This method
637 is useful for subclasses when they need a QStyleOptionDockWidget, but don't want
638 to fill in all the information themselves.
639
640 \sa QStyleOption::initFrom()
641*/
642void QDockWidget::initStyleOption(QStyleOptionDockWidget *option) const
643{
644 Q_D(const QDockWidget);
645
646 if (!option)
647 return;
648 QDockWidgetLayout *dwlayout = qobject_cast<QDockWidgetLayout*>(object: layout());
649
650 QDockWidgetGroupWindow *floatingTab = qobject_cast<QDockWidgetGroupWindow*>(object: parent());
651 // If we are in a floating tab, init from the parent because the attributes and the geometry
652 // of the title bar should be taken from the floating window.
653 option->initFrom(w: floatingTab && !isFloating() ? parentWidget() : this);
654 option->rect = dwlayout->titleArea();
655 option->title = d->fixedWindowTitle;
656 option->closable = hasFeature(dockwidget: this, feature: QDockWidget::DockWidgetClosable);
657 option->movable = hasFeature(dockwidget: this, feature: QDockWidget::DockWidgetMovable);
658 option->floatable = hasFeature(dockwidget: this, feature: QDockWidget::DockWidgetFloatable);
659
660 QDockWidgetLayout *l = qobject_cast<QDockWidgetLayout*>(object: layout());
661 option->verticalTitleBar = l->verticalTitleBar;
662}
663
664void QDockWidgetPrivate::toggleView(bool b)
665{
666 Q_Q(QDockWidget);
667 if (b == q->isHidden()) {
668 if (b)
669 q->show();
670 else
671 q->close();
672 }
673}
674
675void QDockWidgetPrivate::updateButtons()
676{
677 Q_Q(QDockWidget);
678 QDockWidgetLayout *dwLayout = qobject_cast<QDockWidgetLayout*>(object: layout);
679
680 QStyleOptionDockWidget opt;
681 q->initStyleOption(option: &opt);
682
683 bool customTitleBar = dwLayout->widgetForRole(r: QDockWidgetLayout::TitleBar) != nullptr;
684 bool nativeDeco = dwLayout->nativeWindowDeco();
685 bool hideButtons = nativeDeco || customTitleBar;
686
687 bool canClose = hasFeature(priv: this, feature: QDockWidget::DockWidgetClosable);
688 bool canFloat = hasFeature(priv: this, feature: QDockWidget::DockWidgetFloatable);
689
690 QAbstractButton *button
691 = qobject_cast<QAbstractButton*>(object: dwLayout->widgetForRole(r: QDockWidgetLayout::FloatButton));
692 button->setIcon(q->style()->standardIcon(standardIcon: QStyle::SP_TitleBarNormalButton, option: &opt, widget: q));
693 button->setVisible(canFloat && !hideButtons);
694#if QT_CONFIG(accessibility)
695 //: Accessible name for button undocking a dock widget (floating state)
696 button->setAccessibleName(QDockWidget::tr(s: "Float"));
697 button->setAccessibleDescription(QDockWidget::tr(s: "Undocks and re-attaches the dock widget"));
698#endif
699 button
700 = qobject_cast <QAbstractButton*>(object: dwLayout->widgetForRole(r: QDockWidgetLayout::CloseButton));
701 button->setIcon(q->style()->standardIcon(standardIcon: QStyle::SP_TitleBarCloseButton, option: &opt, widget: q));
702 button->setVisible(canClose && !hideButtons);
703#if QT_CONFIG(accessibility)
704 //: Accessible name for button closing a dock widget
705 button->setAccessibleName(QDockWidget::tr(s: "Close"));
706 button->setAccessibleDescription(QDockWidget::tr(s: "Closes the dock widget"));
707#endif
708
709 layout->invalidate();
710}
711
712void QDockWidgetPrivate::toggleTopLevel()
713{
714 Q_Q(QDockWidget);
715 q->setFloating(!q->isFloating());
716}
717
718/*! \internal
719 Initialize the drag state structure and remember the position of the click.
720 This is called when the mouse is pressed, but the dock is not yet dragged out.
721
722 \a nca specify that the event comes from NonClientAreaMouseButtonPress
723 */
724void QDockWidgetPrivate::initDrag(const QPoint &pos, bool nca)
725{
726 Q_Q(QDockWidget);
727
728 if (state != nullptr)
729 return;
730
731 QMainWindowLayout *layout = qt_mainwindow_layout_from_dock(dock: q);
732 Q_ASSERT(layout != nullptr);
733 if (layout->pluggingWidget != nullptr) // the main window is animating a docking operation
734 return;
735
736 state = new QDockWidgetPrivate::DragState;
737 state->pressPos = pos;
738 state->globalPressPos = q->mapToGlobal(pos);
739 state->widgetInitialPos = q->isFloating() ? q->pos() : q->mapToGlobal(QPoint(0, 0));
740 state->dragging = false;
741 state->widgetItem = nullptr;
742 state->ownWidgetItem = false;
743 state->nca = nca;
744 state->ctrlDrag = false;
745}
746
747/*! \internal
748 Actually start the drag and detach the dockwidget.
749 The \a group parameter is true when we should potentially drag a group of
750 tabbed widgets, and false if the dock widget should always be dragged
751 alone.
752 */
753void QDockWidgetPrivate::startDrag(DragScope scope)
754{
755 Q_Q(QDockWidget);
756
757 if (state == nullptr || state->dragging)
758 return;
759
760 QMainWindowLayout *layout = qt_mainwindow_layout_from_dock(dock: q);
761 Q_ASSERT(layout != nullptr);
762
763#if QT_CONFIG(draganddrop)
764 bool wasFloating = q->isFloating();
765#endif
766
767 state->widgetItem = layout->unplug(widget: q, scope);
768 if (state->widgetItem == nullptr) {
769 /* Dock widget has a QMainWindow parent, but was never inserted with
770 QMainWindow::addDockWidget, so the QMainWindowLayout has no
771 widget item for it. It will be newly created and deleted if it doesn't
772 get dropped into a dock area. */
773 QDockWidgetGroupWindow *floatingTab = qobject_cast<QDockWidgetGroupWindow*>(object: parent);
774 if (floatingTab && !q->isFloating())
775 state->widgetItem = new QDockWidgetGroupWindowItem(floatingTab);
776 else
777 state->widgetItem = new QDockWidgetItem(q);
778 state->ownWidgetItem = true;
779 }
780
781 if (state->ctrlDrag)
782 layout->restore();
783
784 state->dragging = true;
785
786#if QT_CONFIG(draganddrop)
787 if (QMainWindowLayout::needsPlatformDrag()) {
788 Qt::DropAction result =
789 layout->performPlatformWidgetDrag(widgetItem: state->widgetItem, pressPosition: state->pressPos);
790 if (result == Qt::IgnoreAction && !wasFloating) {
791 layout->revert(widgetItem: state->widgetItem);
792 delete state;
793 state = nullptr;
794 } else {
795 endDrag(mode: QDockWidgetPrivate::EndDragMode::LocationChange);
796 }
797 }
798#endif
799}
800
801/*! \internal
802 Ends the drag end drop operation of the QDockWidget.
803 The \a abort parameter specifies that it ends because of programmatic state
804 reset rather than mouse release event.
805 */
806void QDockWidgetPrivate::endDrag(EndDragMode mode)
807{
808 Q_Q(QDockWidget);
809 Q_ASSERT(state != nullptr);
810
811 q->releaseMouse();
812
813 if (state->dragging) {
814 const QMainWindow *mainWindow = mainwindow_from_dock(dock: q);
815 Q_ASSERT(mainWindow != nullptr);
816 QMainWindowLayout *mwLayout = qt_mainwindow_layout(window: mainWindow);
817
818 // if mainWindow is being deleted in an ongoing drag, make it a no-op instead of crashing
819 if (!mwLayout)
820 return;
821
822 if (mode == EndDragMode::Abort || !mwLayout->plug(widgetItem: state->widgetItem)) {
823 if (hasFeature(priv: this, feature: QDockWidget::DockWidgetFloatable)) {
824 // This QDockWidget will now stay in the floating state.
825 if (state->ownWidgetItem) {
826 delete state->widgetItem;
827 state->widgetItem = nullptr;
828 }
829 mwLayout->restore();
830 QDockWidgetLayout *dwLayout = qobject_cast<QDockWidgetLayout*>(object: layout);
831 if (!dwLayout->nativeWindowDeco()) {
832 // get rid of the X11BypassWindowManager window flag and activate the resizer
833 Qt::WindowFlags flags = q->windowFlags();
834 flags &= ~Qt::X11BypassWindowManagerHint;
835 q->setWindowFlags(flags);
836 setResizerActive(q->isFloating());
837 q->show();
838 } else {
839 setResizerActive(false);
840 }
841 if (q->isFloating()) { // Might not be floating when dragging a QDockWidgetGroupWindow
842 undockedGeometry = q->geometry();
843#if QT_CONFIG(tabwidget)
844 // is the widget located within the mainwindow?
845 const Qt::DockWidgetArea area = mainWindow->dockWidgetArea(dockwidget: q);
846 if (area != Qt::NoDockWidgetArea) {
847 tabPosition = mwLayout->tabPosition(area);
848 } else if (auto dwgw = qobject_cast<QDockWidgetGroupWindow *>(object: q->parent())) {
849 // DockWidget wasn't found in one of the docks within mainwindow
850 // => derive tabPosition from parent
851 tabPosition = mwLayout->tabPosition(area: toDockWidgetArea(pos: dwgw->layoutInfo()->dockPos));
852 }
853#endif
854 // Reparent, if the drag was out of a dock widget group window
855 if (mode == EndDragMode::LocationChange) {
856 if (auto *groupWindow = qobject_cast<QDockWidgetGroupWindow *>(object: q->parentWidget()))
857 groupWindow->reparent(dockWidget: q);
858 }
859 }
860 q->activateWindow();
861 } else {
862 // The tab was not plugged back in the QMainWindow but the QDockWidget cannot
863 // stay floating, revert to the previous state.
864 mwLayout->revert(widgetItem: state->widgetItem);
865 }
866 }
867 }
868 delete state;
869 state = nullptr;
870}
871
872Qt::DockWidgetArea QDockWidgetPrivate::toDockWidgetArea(QInternal::DockPosition pos)
873{
874 switch (pos) {
875 case QInternal::LeftDock: return Qt::LeftDockWidgetArea;
876 case QInternal::RightDock: return Qt::RightDockWidgetArea;
877 case QInternal::TopDock: return Qt::TopDockWidgetArea;
878 case QInternal::BottomDock: return Qt::BottomDockWidgetArea;
879 default: break;
880 }
881 return Qt::NoDockWidgetArea;
882}
883
884void QDockWidgetPrivate::setResizerActive(bool active)
885{
886 Q_Q(QDockWidget);
887 const auto *dwLayout = qobject_cast<QDockWidgetLayout *>(object: layout);
888 if (dwLayout->nativeWindowDeco(floating: q->isFloating()))
889 return;
890
891 if (active && !resizer)
892 resizer = new QWidgetResizeHandler(q);
893 if (resizer)
894 resizer->setEnabled(active);
895}
896
897bool QDockWidgetPrivate::isAnimating() const
898{
899 Q_Q(const QDockWidget);
900
901 QMainWindowLayout *mainWinLayout = qt_mainwindow_layout_from_dock(dock: q);
902 if (mainWinLayout == nullptr)
903 return false;
904
905 return (const void*)mainWinLayout->pluggingWidget == (const void*)q;
906}
907
908bool QDockWidgetPrivate::mousePressEvent(QMouseEvent *event)
909{
910#if QT_CONFIG(mainwindow)
911 Q_Q(QDockWidget);
912
913 QDockWidgetLayout *dwLayout
914 = qobject_cast<QDockWidgetLayout*>(object: layout);
915
916 if (!dwLayout->nativeWindowDeco()) {
917 QRect titleArea = dwLayout->titleArea();
918
919 QDockWidgetGroupWindow *floatingTab = qobject_cast<QDockWidgetGroupWindow*>(object: parent);
920
921 if (event->button() != Qt::LeftButton ||
922 !titleArea.contains(p: event->position().toPoint()) ||
923 // check if the tool window is movable... do nothing if it
924 // is not (but allow moving if the window is floating)
925 (!hasFeature(priv: this, feature: QDockWidget::DockWidgetMovable) && !q->isFloating()) ||
926 (qobject_cast<QMainWindow*>(object: parent) == nullptr && !floatingTab) ||
927 isAnimating() || state != nullptr) {
928 return false;
929 }
930
931 initDrag(pos: event->position().toPoint(), nca: false);
932
933 if (state)
934 state->ctrlDrag = (hasFeature(priv: this, feature: QDockWidget::DockWidgetFloatable) && event->modifiers() & Qt::ControlModifier) ||
935 (!hasFeature(priv: this, feature: QDockWidget::DockWidgetMovable) && q->isFloating());
936
937 return true;
938 }
939
940#endif // QT_CONFIG(mainwindow)
941 return false;
942}
943
944bool QDockWidgetPrivate::mouseDoubleClickEvent(QMouseEvent *event)
945{
946 QDockWidgetLayout *dwLayout = qobject_cast<QDockWidgetLayout*>(object: layout);
947
948 if (!dwLayout->nativeWindowDeco()) {
949 QRect titleArea = dwLayout->titleArea();
950
951 if (event->button() == Qt::LeftButton && titleArea.contains(p: event->position().toPoint()) &&
952 hasFeature(priv: this, feature: QDockWidget::DockWidgetFloatable)) {
953 toggleTopLevel();
954 return true;
955 }
956 }
957 return false;
958}
959
960bool QDockWidgetPrivate::isTabbed() const
961{
962 Q_Q(const QDockWidget);
963 QDockWidget *that = const_cast<QDockWidget *>(q);
964 auto *mwLayout = qt_mainwindow_layout_from_dock(dock: that);
965 Q_ASSERT(mwLayout);
966 return mwLayout->isDockWidgetTabbed(dockWidget: q);
967}
968
969bool QDockWidgetPrivate::mouseMoveEvent(QMouseEvent *event)
970{
971 bool ret = false;
972#if QT_CONFIG(mainwindow)
973 Q_Q(QDockWidget);
974
975 if (!state)
976 return ret;
977
978 QDockWidgetLayout *dwlayout
979 = qobject_cast<QDockWidgetLayout *>(object: layout);
980 QMainWindowLayout *mwlayout = qt_mainwindow_layout_from_dock(dock: q);
981 if (!dwlayout->nativeWindowDeco()) {
982 if (!state->dragging
983 && mwlayout->pluggingWidget == nullptr
984 && (event->position().toPoint() - state->pressPos).manhattanLength()
985 > QApplication::startDragDistance()) {
986
987#ifdef Q_OS_MACOS
988 if (windowHandle() && !q->isFloating()) {
989 // When using native widgets on mac, we have not yet been successful in
990 // starting a drag on an NSView that belongs to one window (QMainWindow),
991 // but continue the drag on another (QDockWidget). This is what happens if
992 // we try to make this widget floating during a drag. So as a fall back
993 // solution, we simply make this widget floating instead, when we would
994 // otherwise start a drag.
995 q->setFloating(true);
996 } else
997#endif
998 {
999 const DragScope scope = isTabbed() ? DragScope::Group : DragScope::Widget;
1000 startDrag(scope);
1001 q->grabMouse();
1002 ret = true;
1003 }
1004 }
1005 }
1006
1007 if (state && state->dragging && !state->nca) {
1008 QMargins windowMargins = q->window()->windowHandle()->frameMargins();
1009 QPoint windowMarginOffset = QPoint(windowMargins.left(), windowMargins.top());
1010
1011 // TODO maybe use QScreen API (if/when available) to simplify the below code.
1012 const QScreen *orgWdgScreen = QGuiApplication::screenAt(point: state->widgetInitialPos);
1013 const QScreen *screenFrom = QGuiApplication::screenAt(point: state->globalPressPos);
1014 const QScreen *screenTo = QGuiApplication::screenAt(point: event->globalPosition().toPoint());
1015 const QScreen *wdgScreen = q->screen();
1016
1017 QPoint pos;
1018 if (Q_LIKELY(screenFrom && screenTo && wdgScreen && orgWdgScreen)) {
1019 const QPoint nativeWdgOrgPos = QHighDpiScaling::mapPositionToNative(
1020 pos: state->widgetInitialPos, platformScreen: orgWdgScreen->handle());
1021 const QPoint nativeTo = QHighDpiScaling::mapPositionToNative(
1022 pos: event->globalPosition().toPoint(), platformScreen: screenTo->handle());
1023 const QPoint nativeFrom = QHighDpiScaling::mapPositionToNative(pos: state->globalPressPos,
1024 platformScreen: screenFrom->handle());
1025
1026 // Calculate new nativePos based on startPos + mouse delta move.
1027 const QPoint nativeNewPos = nativeWdgOrgPos + (nativeTo - nativeFrom);
1028 pos = QHighDpiScaling::mapPositionFromNative(pos: nativeNewPos, platformScreen: wdgScreen->handle())
1029 - windowMarginOffset;
1030 } else {
1031 // Fallback in the unlikely case that source and target screens could not be established
1032 qCDebug(lcQpaDockWidgets)
1033 << "QDockWidget failed to find relevant screen info. screenFrom:" << screenFrom
1034 << "screenTo:" << screenTo << " wdgScreen:" << wdgScreen << "orgWdgScreen"
1035 << orgWdgScreen;
1036 pos = event->globalPosition().toPoint() - state->pressPos - windowMarginOffset;
1037 }
1038
1039 // If the newly floating dock widget has got a native title bar,
1040 // offset the position by the native title bar's height or width
1041 const int dx = q->geometry().x() - q->x();
1042 const int dy = q->geometry().y() - q->y();
1043 pos.rx() += dx;
1044 pos.ry() += dy;
1045
1046 QDockWidgetGroupWindow *floatingTab = qobject_cast<QDockWidgetGroupWindow*>(object: parent);
1047 if (floatingTab && !q->isFloating())
1048 floatingTab->move(pos);
1049 else
1050 q->move(pos);
1051 if (state && !state->ctrlDrag)
1052 mwlayout->hover(hoverTarget: state->widgetItem, mousePos: event->globalPosition().toPoint());
1053
1054 ret = true;
1055 }
1056
1057#endif // QT_CONFIG(mainwindow)
1058 return ret;
1059}
1060
1061bool QDockWidgetPrivate::mouseReleaseEvent(QMouseEvent *event)
1062{
1063#if QT_CONFIG(mainwindow)
1064#if QT_CONFIG(draganddrop)
1065 // if we are peforming a platform drag ignore the release here and end the drag when the actual
1066 // drag ends.
1067 if (QMainWindowLayout::needsPlatformDrag())
1068 return false;
1069#endif
1070
1071 if (event->button() == Qt::LeftButton && state && !state->nca) {
1072 endDrag(mode: EndDragMode::LocationChange);
1073 return true; //filter out the event
1074 }
1075
1076#endif // QT_CONFIG(mainwindow)
1077 return false;
1078}
1079
1080void QDockWidgetPrivate::nonClientAreaMouseEvent(QMouseEvent *event)
1081{
1082 Q_Q(QDockWidget);
1083
1084 int fw = q->style()->pixelMetric(metric: QStyle::PM_DockWidgetFrameWidth, option: nullptr, widget: q);
1085
1086 QWidget *tl = q->topLevelWidget();
1087 QRect geo = tl->geometry();
1088 QRect titleRect = tl->frameGeometry();
1089 {
1090 titleRect.setLeft(geo.left());
1091 titleRect.setRight(geo.right());
1092 titleRect.setBottom(geo.top() - 1);
1093 titleRect.adjust(dx1: 0, dy1: fw, dx2: 0, dy2: 0);
1094 }
1095
1096 switch (event->type()) {
1097 case QEvent::NonClientAreaMouseButtonPress:
1098 if (!titleRect.contains(p: event->globalPosition().toPoint()))
1099 break;
1100 if (state != nullptr)
1101 break;
1102 if (qobject_cast<QMainWindow*>(object: parent) == nullptr && qobject_cast<QDockWidgetGroupWindow*>(object: parent) == nullptr)
1103 break;
1104 if (isAnimating())
1105 break;
1106 initDrag(pos: event->position().toPoint(), nca: true);
1107 if (state == nullptr)
1108 break;
1109 state->ctrlDrag = (event->modifiers() & Qt::ControlModifier) ||
1110 (!hasFeature(priv: this, feature: QDockWidget::DockWidgetMovable) && q->isFloating());
1111 startDrag(scope: DragScope::Group);
1112 break;
1113 case QEvent::NonClientAreaMouseMove:
1114 if (state == nullptr || !state->dragging)
1115 break;
1116
1117#if !defined(Q_OS_MAC) && !defined(Q_OS_WASM)
1118 if (state->nca)
1119 endDrag(mode: EndDragMode::LocationChange);
1120#endif
1121 break;
1122 case QEvent::NonClientAreaMouseButtonRelease:
1123#if defined(Q_OS_MAC) || defined(Q_OS_WASM)
1124 if (state)
1125 endDrag(EndDragMode::LocationChange);
1126#endif
1127 break;
1128 case QEvent::NonClientAreaMouseButtonDblClick:
1129 toggleTopLevel();
1130 break;
1131 default:
1132 break;
1133 }
1134}
1135
1136void QDockWidgetPrivate::recalculatePressPos(QResizeEvent *event)
1137{
1138 qreal ratio = event->oldSize().width() / (1.0 * event->size().width());
1139 state->pressPos.setX(state->pressPos.x() / ratio);
1140}
1141
1142/*! \internal
1143 Called when the QDockWidget or the QDockWidgetGroupWindow is moved
1144 */
1145void QDockWidgetPrivate::moveEvent(QMoveEvent *event)
1146{
1147 Q_Q(QDockWidget);
1148
1149 if (state == nullptr || !state->dragging || !state->nca)
1150 return;
1151
1152 if (!q->isWindow() && qobject_cast<QDockWidgetGroupWindow*>(object: parent) == nullptr)
1153 return;
1154
1155 // When the native window frame is being dragged, all we get is these mouse
1156 // move events.
1157
1158 if (state->ctrlDrag)
1159 return;
1160
1161 QMainWindowLayout *layout = qt_mainwindow_layout_from_dock(dock: q);
1162 Q_ASSERT(layout != nullptr);
1163
1164 QPoint globalMousePos = event->pos() + state->pressPos;
1165 layout->hover(hoverTarget: state->widgetItem, mousePos: globalMousePos);
1166}
1167
1168void QDockWidgetPrivate::unplug(const QRect &rect)
1169{
1170 Q_Q(QDockWidget);
1171 QRect r = rect;
1172 r.moveTopLeft(p: q->mapToGlobal(QPoint(0, 0)));
1173 QDockWidgetLayout *dwLayout = qobject_cast<QDockWidgetLayout*>(object: layout);
1174 if (dwLayout->nativeWindowDeco(floating: true))
1175 r.adjust(dx1: 0, dy1: dwLayout->titleHeight(), dx2: 0, dy2: 0);
1176 setWindowState(floating: true, unplug: true, rect: r);
1177}
1178
1179void QDockWidgetPrivate::plug(const QRect &rect)
1180{
1181 setWindowState(floating: false, unplug: false, rect);
1182}
1183
1184void QDockWidgetPrivate::setWindowState(bool floating, bool unplug, const QRect &rect)
1185{
1186 Q_Q(QDockWidget);
1187
1188 if (!floating && parent) {
1189 QMainWindowLayout *mwlayout = qt_mainwindow_layout_from_dock(dock: q);
1190 if (mwlayout && mwlayout->dockWidgetArea(widget: q) == Qt::NoDockWidgetArea
1191 && !qobject_cast<QDockWidgetGroupWindow *>(object: parent))
1192 return; // this dockwidget can't be redocked
1193 }
1194
1195 const bool wasFloating = q->isFloating();
1196 if (wasFloating) // Prevent repetitive unplugging from nested invocations (QTBUG-42818)
1197 unplug = false;
1198 const bool hidden = q->isHidden();
1199
1200 if (q->isVisible())
1201 q->hide();
1202
1203 Qt::WindowFlags flags = floating ? Qt::Tool : Qt::Widget;
1204
1205 QDockWidgetLayout *dwLayout = qobject_cast<QDockWidgetLayout*>(object: layout);
1206 const bool nativeDeco = dwLayout->nativeWindowDeco(floating);
1207
1208 if (nativeDeco) {
1209 flags |= Qt::CustomizeWindowHint | Qt::WindowTitleHint;
1210 if (hasFeature(priv: this, feature: QDockWidget::DockWidgetClosable))
1211 flags |= Qt::WindowCloseButtonHint;
1212 } else {
1213 flags |= Qt::FramelessWindowHint;
1214 }
1215
1216#if QT_CONFIG(draganddrop)
1217 // If we are performing a platform drag the flag is not needed and we want to avoid recreating
1218 // the platform window when it would be removed later
1219 if (unplug && !QMainWindowLayout::needsPlatformDrag())
1220 flags |= Qt::X11BypassWindowManagerHint;
1221#endif
1222
1223 q->setWindowFlags(flags);
1224
1225
1226 if (!rect.isNull())
1227 q->setGeometry(rect);
1228
1229 updateButtons();
1230
1231 if (!hidden)
1232 q->show();
1233
1234 if (floating != wasFloating) {
1235 emit q->topLevelChanged(topLevel: floating);
1236 if (!floating && parent) {
1237 QMainWindowLayout *mwlayout = qt_mainwindow_layout_from_dock(dock: q);
1238 if (mwlayout)
1239 emit q->dockLocationChanged(area: mwlayout->dockWidgetArea(widget: q));
1240 } else {
1241 emit q->dockLocationChanged(area: Qt::NoDockWidgetArea);
1242 }
1243 }
1244
1245 setResizerActive(!unplug && floating && !nativeDeco);
1246}
1247
1248/*!
1249 \class QDockWidget
1250
1251 \brief The QDockWidget class provides a widget that can be docked
1252 inside a QMainWindow or floated as a top-level window on the
1253 desktop.
1254
1255 \ingroup mainwindow-classes
1256 \inmodule QtWidgets
1257
1258 QDockWidget provides the concept of dock widgets, also know as
1259 tool palettes or utility windows. Dock windows are secondary
1260 windows placed in the \e {dock widget area} around the
1261 \l{QMainWindow::centralWidget()}{central widget} in a
1262 QMainWindow.
1263
1264 \image mainwindow-docks.png
1265
1266 Dock windows can be moved inside their current area, moved into
1267 new areas and floated (e.g., undocked) by the end-user. The
1268 QDockWidget API allows the programmer to restrict the dock widgets
1269 ability to move, float and close, as well as the areas in which
1270 they can be placed.
1271
1272 \section1 Appearance
1273
1274 A QDockWidget consists of a title bar and the content area. The
1275 title bar displays the dock widgets
1276 \l{QWidget::windowTitle()}{window title},
1277 a \e float button and a \e close button.
1278 Depending on the state of the QDockWidget, the \e float and \e
1279 close buttons may be either disabled or not shown at all.
1280
1281 The visual appearance of the title bar and buttons is dependent
1282 on the \l{QStyle}{style} in use.
1283
1284 A QDockWidget acts as a wrapper for its child widget, set with setWidget().
1285 Custom size hints, minimum and maximum sizes and size policies should be
1286 implemented in the child widget. QDockWidget will respect them, adjusting
1287 its own constraints to include the frame and title. Size constraints
1288 should not be set on the QDockWidget itself, because they change depending
1289 on whether it is docked; a docked QDockWidget has no frame and a smaller title
1290 bar.
1291
1292 \note On macOS, if the QDockWidget has a native window handle (for example,
1293 if winId() is called on it or the child widget), then due to a limitation it will not be
1294 possible to drag the dock widget when undocking. Starting the drag will undock
1295 the dock widget, but a second drag will be needed to move the dock widget itself.
1296
1297 \sa QMainWindow
1298*/
1299
1300/*!
1301 \enum QDockWidget::DockWidgetFeature
1302
1303 \value DockWidgetClosable The dock widget can be closed.
1304 \value DockWidgetMovable The dock widget can be moved between docks
1305 by the user.
1306 \value DockWidgetFloatable The dock widget can be detached from the
1307 main window, and floated as an independent
1308 window.
1309 \value DockWidgetVerticalTitleBar The dock widget displays a vertical title
1310 bar on its left side. This can be used to
1311 increase the amount of vertical space in
1312 a QMainWindow.
1313 \value NoDockWidgetFeatures The dock widget cannot be closed, moved,
1314 or floated.
1315
1316 \omitvalue DockWidgetFeatureMask
1317 \omitvalue Reserved
1318*/
1319
1320/*!
1321 \property QDockWidget::windowTitle
1322 \brief the dock widget title (caption)
1323
1324 By default, this property contains an empty string.
1325*/
1326
1327/*!
1328 Constructs a QDockWidget with parent \a parent and window flags \a
1329 flags. The dock widget will be placed in the left dock widget
1330 area.
1331*/
1332QDockWidget::QDockWidget(QWidget *parent, Qt::WindowFlags flags)
1333 : QWidget(*new QDockWidgetPrivate, parent, flags)
1334{
1335 Q_D(QDockWidget);
1336 d->init();
1337}
1338
1339/*!
1340 Constructs a QDockWidget with parent \a parent and window flags \a
1341 flags. The dock widget will be placed in the left dock widget
1342 area.
1343
1344 The window title is set to \a title. This title is used when the
1345 QDockWidget is docked and undocked. It is also used in the context
1346 menu provided by QMainWindow.
1347
1348 \sa setWindowTitle()
1349*/
1350QDockWidget::QDockWidget(const QString &title, QWidget *parent, Qt::WindowFlags flags)
1351 : QDockWidget(parent, flags)
1352{
1353 setWindowTitle(title);
1354}
1355
1356/*!
1357 Destroys the dock widget.
1358*/
1359QDockWidget::~QDockWidget()
1360{ }
1361
1362/*!
1363 Returns the widget for the dock widget. This function returns zero
1364 if the widget has not been set.
1365
1366 \sa setWidget()
1367*/
1368QWidget *QDockWidget::widget() const
1369{
1370 QDockWidgetLayout *layout = qobject_cast<QDockWidgetLayout*>(object: this->layout());
1371 return layout->widgetForRole(r: QDockWidgetLayout::Content);
1372}
1373
1374/*!
1375 Sets the widget for the dock widget to \a widget.
1376
1377 If the dock widget is visible when \a widget is added, you must
1378 \l{QWidget::}{show()} it explicitly.
1379
1380 Note that you must add the layout of the \a widget before you call
1381 this function; if not, the \a widget will not be visible.
1382
1383 \sa widget()
1384*/
1385void QDockWidget::setWidget(QWidget *widget)
1386{
1387 QDockWidgetLayout *layout = qobject_cast<QDockWidgetLayout*>(object: this->layout());
1388 layout->setWidgetForRole(r: QDockWidgetLayout::Content, w: widget);
1389}
1390
1391/*!
1392 \property QDockWidget::features
1393 \brief whether the dock widget is movable, closable, and floatable
1394
1395 By default, this property is set to a combination of DockWidgetClosable,
1396 DockWidgetMovable and DockWidgetFloatable.
1397
1398 \sa DockWidgetFeature
1399*/
1400
1401void QDockWidget::setFeatures(QDockWidget::DockWidgetFeatures features)
1402{
1403 Q_D(QDockWidget);
1404 features &= DockWidgetFeatureMask;
1405 if (d->features == features)
1406 return;
1407 const bool closableChanged = (d->features ^ features) & DockWidgetClosable;
1408 d->features = features;
1409 QDockWidgetLayout *layout
1410 = qobject_cast<QDockWidgetLayout*>(object: this->layout());
1411 layout->setVerticalTitleBar(features & DockWidgetVerticalTitleBar);
1412 d->updateButtons();
1413 d->toggleViewAction->setEnabled((d->features & DockWidgetClosable) == DockWidgetClosable);
1414 emit featuresChanged(features: d->features);
1415 update();
1416 if (closableChanged && layout->nativeWindowDeco()) {
1417 QDockWidgetGroupWindow *floatingTab = qobject_cast<QDockWidgetGroupWindow *>(object: parent());
1418 if (floatingTab && !isFloating())
1419 floatingTab->adjustFlags();
1420 else
1421 d->setWindowState(floating: true /*floating*/, unplug: true /*unplug*/); //this ensures the native decoration is drawn
1422 }
1423}
1424
1425QDockWidget::DockWidgetFeatures QDockWidget::features() const
1426{
1427 Q_D(const QDockWidget);
1428 return d->features;
1429}
1430
1431/*!
1432 \property QDockWidget::floating
1433 \brief whether the dock widget is floating
1434
1435 A floating dock widget is presented to the user as a single, independent
1436 window "on top" of its parent QMainWindow, instead of being docked
1437 either in the QMainWindow, or in a group of tabbed dock widgets.
1438
1439 Floating dock widgets can be individually positioned and resized, both
1440 programmatically or by mouse interaction.
1441
1442 By default, this property is \c true.
1443
1444 When this property changes, the \c {topLevelChanged()} signal is emitted.
1445
1446 \sa isWindow(), topLevelChanged()
1447*/
1448void QDockWidget::setFloating(bool floating)
1449{
1450 Q_D(QDockWidget);
1451 d->setFloating(floating);
1452}
1453
1454/*!
1455 \internal implementation of setFloating
1456 */
1457void QDockWidgetPrivate::setFloating(bool floating)
1458{
1459 Q_Q(QDockWidget);
1460 // the initial click of a double-click may have started a drag...
1461 if (state != nullptr)
1462 endDrag(mode: QDockWidgetPrivate::EndDragMode::Abort);
1463
1464 // Keep position when undocking for the first time.
1465 QRect r = undockedGeometry;
1466 if (floating && q->isVisible() && !r.isValid())
1467 r = QRect(q->mapToGlobal(QPoint(0, 0)), q->size());
1468
1469 // Reparent, if setFloating() was called on a floating tab
1470 // Reparenting has to happen before setWindowState.
1471 // The reparented dock widget will inherit visibility from the floating tab.
1472 // => Remember visibility and the necessity to update it.
1473 enum class VisibilityRule {
1474 NoUpdate,
1475 Show,
1476 Hide,
1477 };
1478
1479 VisibilityRule updateRule = VisibilityRule::NoUpdate;
1480
1481 if (floating && !q->isFloating()) {
1482 if (auto *groupWindow = qobject_cast<QDockWidgetGroupWindow *>(object: q->parentWidget())) {
1483 updateRule = groupWindow->isVisible() ? VisibilityRule::Show : VisibilityRule::Hide;
1484 q->setParent(groupWindow->parentWidget());
1485 }
1486 }
1487
1488 setWindowState(floating, unplug: false, rect: floating ? r : QRect());
1489
1490 if (floating && r.isNull()) {
1491 if (q->x() < 0 || q->y() < 0) //may happen if we have been hidden
1492 q->move(QPoint());
1493 q->setAttribute(Qt::WA_Moved, on: false); //we want it at the default position
1494 }
1495
1496 switch (updateRule) {
1497 case VisibilityRule::NoUpdate:
1498 break;
1499 case VisibilityRule::Show:
1500 q->show();
1501 break;
1502 case VisibilityRule::Hide:
1503 q->hide();
1504 break;
1505 }
1506}
1507
1508/*!
1509 \property QDockWidget::allowedAreas
1510 \brief areas where the dock widget may be placed
1511
1512 The default is Qt::AllDockWidgetAreas.
1513
1514 \sa Qt::DockWidgetArea
1515*/
1516
1517void QDockWidget::setAllowedAreas(Qt::DockWidgetAreas areas)
1518{
1519 Q_D(QDockWidget);
1520 areas &= Qt::DockWidgetArea_Mask;
1521 if (areas == d->allowedAreas)
1522 return;
1523 d->allowedAreas = areas;
1524 emit allowedAreasChanged(allowedAreas: d->allowedAreas);
1525}
1526
1527Qt::DockWidgetAreas QDockWidget::allowedAreas() const
1528{
1529 Q_D(const QDockWidget);
1530 return d->allowedAreas;
1531}
1532
1533/*!
1534 \fn bool QDockWidget::isAreaAllowed(Qt::DockWidgetArea area) const
1535
1536 Returns \c true if this dock widget can be placed in the given \a area;
1537 otherwise returns \c false.
1538*/
1539
1540/*! \reimp */
1541void QDockWidget::changeEvent(QEvent *event)
1542{
1543 Q_D(QDockWidget);
1544 QDockWidgetLayout *layout = qobject_cast<QDockWidgetLayout*>(object: this->layout());
1545
1546 switch (event->type()) {
1547 case QEvent::WindowTitleChange:
1548 if (isFloating() && windowHandle() && d->topData() && windowHandle()->isVisible()) {
1549 // From QWidget::setWindowTitle(): Propagate window title without signal emission
1550 d->topData()->caption = windowHandle()->title();
1551 d->setWindowTitle_helper(windowHandle()->title());
1552 }
1553 Q_FALLTHROUGH();
1554 case QEvent::ModifiedChange:
1555 update(layout->titleArea());
1556#ifndef QT_NO_ACTION
1557 d->fixedWindowTitle = qt_setWindowTitle_helperHelper(windowTitle(), this);
1558 d->toggleViewAction->setText(d->fixedWindowTitle);
1559#endif
1560#if QT_CONFIG(tabbar)
1561 {
1562 if (QMainWindowLayout *winLayout = qt_mainwindow_layout_from_dock(dock: this)) {
1563 if (QDockAreaLayoutInfo *info = winLayout->layoutState.dockAreaLayout.info(widget: this))
1564 info->updateTabBar();
1565 }
1566 }
1567#endif // QT_CONFIG(tabbar)
1568 break;
1569 default:
1570 break;
1571 }
1572 QWidget::changeEvent(event);
1573}
1574
1575/*! \reimp */
1576void QDockWidget::closeEvent(QCloseEvent *event)
1577{
1578 Q_D(QDockWidget);
1579 if (d->state)
1580 d->endDrag(mode: QDockWidgetPrivate::EndDragMode::Abort);
1581
1582 // For non-closable widgets, don't allow closing, except when the mainwindow
1583 // is hidden, as otherwise an application wouldn't be able to be shut down.
1584 const QMainWindow *win = qobject_cast<QMainWindow*>(object: parentWidget());
1585 const bool canClose = (d->features & DockWidgetClosable)
1586 || (!win || !win->isVisible());
1587 event->setAccepted(canClose);
1588}
1589
1590/*! \reimp */
1591void QDockWidget::paintEvent(QPaintEvent *event)
1592{
1593 Q_UNUSED(event);
1594 Q_D(QDockWidget);
1595
1596 QDockWidgetLayout *layout
1597 = qobject_cast<QDockWidgetLayout*>(object: this->layout());
1598 bool customTitleBar = layout->widgetForRole(r: QDockWidgetLayout::TitleBar) != nullptr;
1599 bool nativeDeco = layout->nativeWindowDeco();
1600
1601 if (!nativeDeco && !customTitleBar) {
1602 QStylePainter p(this);
1603 // ### Add PixelMetric to change spacers, so style may show border
1604 // when not floating.
1605 if (isFloating()) {
1606 QStyleOptionFrame framOpt;
1607 framOpt.initFrom(w: this);
1608 p.drawPrimitive(pe: QStyle::PE_FrameDockWidget, opt: framOpt);
1609 }
1610
1611 // Title must be painted after the frame, since the areas overlap, and
1612 // the title may wish to extend out to all sides (eg. Vista style)
1613 QStyleOptionDockWidget titleOpt;
1614 initStyleOption(option: &titleOpt);
1615 if (font() == QApplication::font(className: "QDockWidget")) {
1616 titleOpt.fontMetrics = QFontMetrics(d->font);
1617 p.setFont(d->font);
1618 }
1619
1620 p.drawControl(ce: QStyle::CE_DockWidgetTitle, opt: titleOpt);
1621 }
1622}
1623
1624/*! \reimp */
1625bool QDockWidget::event(QEvent *event)
1626{
1627 Q_D(QDockWidget);
1628
1629 QMainWindow *win = qobject_cast<QMainWindow*>(object: parentWidget());
1630 QMainWindowLayout *layout = qt_mainwindow_layout_from_dock(dock: this);
1631
1632 switch (event->type()) {
1633#ifndef QT_NO_ACTION
1634 case QEvent::Hide:
1635 if (layout != nullptr)
1636 layout->keepSize(w: this);
1637 d->toggleViewAction->setChecked(false);
1638 emit visibilityChanged(visible: false);
1639 break;
1640 case QEvent::Show: {
1641 d->toggleViewAction->setChecked(true);
1642 QPoint parentTopLeft(0, 0);
1643 if (isWindow()) {
1644 const QScreen *screen = d->associatedScreen();
1645 parentTopLeft = screen
1646 ? screen->availableVirtualGeometry().topLeft()
1647 : QGuiApplication::primaryScreen()->availableVirtualGeometry().topLeft();
1648 }
1649 emit visibilityChanged(visible: geometry().right() >= parentTopLeft.x() && geometry().bottom() >= parentTopLeft.y());
1650}
1651 break;
1652#endif
1653 case QEvent::ApplicationLayoutDirectionChange:
1654 case QEvent::LayoutDirectionChange:
1655 case QEvent::StyleChange:
1656 case QEvent::ParentChange:
1657 d->updateButtons();
1658 break;
1659 case QEvent::ZOrderChange: {
1660 bool onTop = false;
1661 if (win != nullptr) {
1662 const QObjectList &siblings = win->children();
1663 onTop = siblings.size() > 0 && siblings.last() == (QObject*)this;
1664 }
1665#if QT_CONFIG(tabbar)
1666 if (!isFloating() && layout != nullptr && onTop)
1667 layout->raise(widget: this);
1668#endif
1669 break;
1670 }
1671 case QEvent::WindowActivate:
1672 case QEvent::WindowDeactivate:
1673 update(qobject_cast<QDockWidgetLayout *>(object: this->layout())->titleArea());
1674 break;
1675 case QEvent::ContextMenu:
1676 if (d->state) {
1677 event->accept();
1678 return true;
1679 }
1680 break;
1681 // return true after calling the handler since we don't want
1682 // them to be passed onto the default handlers
1683 case QEvent::MouseButtonPress:
1684 if (d->mousePressEvent(event: static_cast<QMouseEvent *>(event)))
1685 return true;
1686 break;
1687 case QEvent::MouseButtonDblClick:
1688 if (d->mouseDoubleClickEvent(event: static_cast<QMouseEvent *>(event)))
1689 return true;
1690 break;
1691 case QEvent::MouseMove:
1692 if (d->mouseMoveEvent(event: static_cast<QMouseEvent *>(event)))
1693 return true;
1694 break;
1695 case QEvent::MouseButtonRelease:
1696 if (d->mouseReleaseEvent(event: static_cast<QMouseEvent *>(event)))
1697 return true;
1698 break;
1699 case QEvent::NonClientAreaMouseMove:
1700 case QEvent::NonClientAreaMouseButtonPress:
1701 case QEvent::NonClientAreaMouseButtonRelease:
1702 case QEvent::NonClientAreaMouseButtonDblClick:
1703 d->nonClientAreaMouseEvent(event: static_cast<QMouseEvent*>(event));
1704 return true;
1705 case QEvent::Move:
1706 d->moveEvent(event: static_cast<QMoveEvent*>(event));
1707 break;
1708 case QEvent::Resize:
1709 // if the mainwindow is plugging us, we don't want to update undocked geometry
1710 if (isFloating() && layout != nullptr && layout->pluggingWidget != this)
1711 d->undockedGeometry = geometry();
1712
1713 // Usually the window won't get resized while it's being moved, but it can happen,
1714 // for example on Windows when moving to a screen with bigger scale factor
1715 // If that happens we should update state->pressPos, otherwise it will be outside
1716 // the window when the window shrinks.
1717 if (d->state && d->state->dragging)
1718 d->recalculatePressPos(event: static_cast<QResizeEvent*>(event));
1719 break;
1720 default:
1721 break;
1722 }
1723 return QWidget::event(event);
1724}
1725
1726#ifndef QT_NO_ACTION
1727/*!
1728 Returns a checkable action that can be added to menus and toolbars so that
1729 the user can show or close this dock widget.
1730
1731 The action's text is set to the dock widget's window title.
1732
1733 The QAction object is owned by the QDockWidget. It will be automatically
1734 deleted when the QDockWidget is destroyed.
1735
1736 \note The action can not be used to programmatically show or hide the dock
1737 widget. Use the \l visible property for that.
1738
1739 \sa QAction::text, QWidget::windowTitle
1740 */
1741QAction * QDockWidget::toggleViewAction() const
1742{
1743 Q_D(const QDockWidget);
1744 return d->toggleViewAction;
1745}
1746#endif // QT_NO_ACTION
1747
1748/*!
1749 \fn void QDockWidget::featuresChanged(QDockWidget::DockWidgetFeatures features)
1750
1751 This signal is emitted when the \l features property changes. The
1752 \a features parameter gives the new value of the property.
1753*/
1754
1755/*!
1756 \fn void QDockWidget::topLevelChanged(bool topLevel)
1757
1758 This signal is emitted when the \l floating property changes.
1759 The \a topLevel parameter is true if the dock widget is now floating;
1760 otherwise it is false.
1761
1762 \sa isWindow()
1763*/
1764
1765/*!
1766 \fn void QDockWidget::allowedAreasChanged(Qt::DockWidgetAreas allowedAreas)
1767
1768 This signal is emitted when the \l allowedAreas property changes. The
1769 \a allowedAreas parameter gives the new value of the property.
1770*/
1771
1772/*!
1773 \fn void QDockWidget::visibilityChanged(bool visible)
1774 \since 4.3
1775
1776 This signal is emitted when the dock widget becomes \a visible (or
1777 invisible). This happens when the widget is hidden or shown, as
1778 well as when it is docked in a tabbed dock area and its tab
1779 becomes selected or unselected.
1780
1781 \note The signal can differ from QWidget::isVisible(). This can be the case, if
1782 a dock widget is minimized or tabified and associated to a non-selected or
1783 inactive tab.
1784*/
1785
1786/*!
1787 \fn void QDockWidget::dockLocationChanged(Qt::DockWidgetArea area)
1788 \since 4.3
1789
1790 This signal is emitted when the dock widget is moved to another
1791 dock \a area, or is moved to a different location in its current
1792 dock area. This happens when the dock widget is moved
1793 programmatically or is dragged to a new location by the user.
1794*/
1795
1796/*!
1797 \since 4.3
1798
1799 Sets an arbitrary \a widget as the dock widget's title bar. If \a widget
1800 is \nullptr, any custom title bar widget previously set on the dock widget
1801 is removed, but not deleted, and the default title bar will be used
1802 instead.
1803
1804 If a title bar widget is set, QDockWidget will not use native window
1805 decorations when it is floated.
1806
1807 Here are some tips for implementing custom title bars:
1808
1809 \list
1810 \li Mouse events that are not explicitly handled by the title bar widget
1811 must be ignored by calling QMouseEvent::ignore(). These events then
1812 propagate to the QDockWidget parent, which handles them in the usual
1813 manner, moving when the title bar is dragged, docking and undocking
1814 when it is double-clicked, etc.
1815
1816 \li When DockWidgetVerticalTitleBar is set on QDockWidget, the title
1817 bar widget is repositioned accordingly. In resizeEvent(), the title
1818 bar should check what orientation it should assume:
1819 \snippet code/src_gui_widgets_qdockwidget.cpp 0
1820
1821 \li The title bar widget must have a valid QWidget::sizeHint() and
1822 QWidget::minimumSizeHint(). These functions should take into account
1823 the current orientation of the title bar.
1824
1825 \li It is not possible to remove a title bar from a dock widget. However,
1826 a similar effect can be achieved by setting a default constructed
1827 QWidget as the title bar widget.
1828 \endlist
1829
1830 Using qobject_cast() as shown above, the title bar widget has full access
1831 to its parent QDockWidget. Hence it can perform such operations as docking
1832 and hiding in response to user actions.
1833
1834 \sa titleBarWidget(), DockWidgetVerticalTitleBar
1835*/
1836
1837void QDockWidget::setTitleBarWidget(QWidget *widget)
1838{
1839 Q_D(QDockWidget);
1840 QDockWidgetLayout *layout
1841 = qobject_cast<QDockWidgetLayout*>(object: this->layout());
1842 layout->setWidgetForRole(r: QDockWidgetLayout::TitleBar, w: widget);
1843 d->updateButtons();
1844 if (isWindow()) {
1845 //this ensures the native decoration is drawn
1846 d->setWindowState(floating: true /*floating*/, unplug: true /*unplug*/);
1847 }
1848}
1849
1850/*!
1851 \since 4.3
1852 Returns the custom title bar widget set on the QDockWidget, or
1853 \nullptr if no custom title bar has been set.
1854
1855 \sa setTitleBarWidget()
1856*/
1857
1858QWidget *QDockWidget::titleBarWidget() const
1859{
1860 QDockWidgetLayout *layout
1861 = qobject_cast<QDockWidgetLayout*>(object: this->layout());
1862 return layout->widgetForRole(r: QDockWidgetLayout::TitleBar);
1863}
1864
1865#ifndef QT_NO_DEBUG_STREAM
1866QDebug operator<<(QDebug dbg, const QDockWidget *dockWidget)
1867{
1868 QDebugStateSaver saver(dbg);
1869 dbg.nospace();
1870
1871 if (!dockWidget) {
1872 dbg << "QDockWidget(0x0)";
1873 return dbg;
1874 }
1875
1876 dbg << "QDockWidget(" << static_cast<const void *>(dockWidget);
1877 dbg << "->(ObjectName=" << dockWidget->objectName();
1878 dbg << "; floating=" << dockWidget->isFloating();
1879 dbg << "; features=" << dockWidget->features();
1880 dbg << ";))";
1881 return dbg;
1882}
1883#endif // QT_NO_DEBUG_STREAM
1884
1885QT_END_NAMESPACE
1886
1887#include "qdockwidget.moc"
1888#include "moc_qdockwidget.cpp"
1889#include "moc_qdockwidget_p.cpp"
1890

Provided by KDAB

Privacy Policy
Learn to use CMake with our Intro Training
Find out more

source code of qtbase/src/widgets/widgets/qdockwidget.cpp