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

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