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