KDDockWidgets API Documentation  1.4
FloatingWindow.cpp
Go to the documentation of this file.
1 /*
2  This file is part of KDDockWidgets.
3 
4  SPDX-FileCopyrightText: 2019-2021 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com>
5  Author: SĂ©rgio Martins <sergio.martins@kdab.com>
6 
7  SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only
8 
9  Contact KDAB at <info@kdab.com> for commercial licensing options.
10 */
11 
12 #include "FloatingWindow_p.h"
13 #include "MainWindowBase.h"
14 #include "Logging_p.h"
15 #include "Frame_p.h"
16 #include "TitleBar_p.h"
17 #include "WindowBeingDragged_p.h"
18 #include "Utils_p.h"
19 #include "WidgetResizeHandler_p.h"
20 #include "DockRegistry_p.h"
21 #include "Config.h"
22 #include "FrameworkWidgetFactory.h"
23 #include "DragController_p.h"
24 #include "LayoutSaver_p.h"
25 
26 #include <QCloseEvent>
27 #include <QScopedValueRollback>
28 #include <QTimer>
29 #include <QWindow>
30 
31 #if defined(Q_OS_WIN)
32 #include <windows.h>
33 #include <dwmapi.h>
34 #endif
35 
36 using namespace KDDockWidgets;
37 
39 Qt::WindowFlags FloatingWindow::s_windowFlagsOverride = {};
40 
42 {
43  if (FloatingWindow::s_windowFlagsOverride) {
44  // The user specifically set different flags.
45  return FloatingWindow::s_windowFlagsOverride;
46  }
47 
48  if (KDDockWidgets::usesNativeDraggingAndResizing())
49  return Qt::Window;
50 
52  return Qt::Window;
53 
54  return Qt::Tool;
55 }
56 
57 static MainWindowBase *hackFindParentHarder(Frame *frame, MainWindowBase *candidateParent)
58 {
60  return nullptr;
61  }
62 
63  // TODO: Using a parent helps the floating windows stay in front of the main window always.
64  // We're not receiving the parent via ctor argument as the app can have multiple-main windows,
65  // so use a hack here.
66  // Not quite clear what to do if the app supports multiple main windows though.
67 
68  if (candidateParent)
69  return candidateParent;
70 
71  const MainWindowBase::List windows = DockRegistry::self()->mainwindows();
72 
73  if (windows.isEmpty())
74  return nullptr;
75 
76  if (windows.size() == 1) {
77  return windows.first();
78  } else {
79  const QStringList affinities = frame ? frame->affinities() : QStringList();
80  const MainWindowBase::List mainWindows = DockRegistry::self()->mainWindowsWithAffinity(affinities);
81 
82  if (mainWindows.isEmpty()) {
83  qWarning() << Q_FUNC_INFO << "No window with affinity" << affinities << "found";
84  return nullptr;
85  } else {
86  return mainWindows.first();
87  }
88  }
89 }
90 
92 {
94  ? nullptr
95  : candidate;
96 }
97 
98 FloatingWindow::FloatingWindow(QRect suggestedGeometry, MainWindowBase *parent)
99  : QWidgetAdapter(actualParent(parent), windowFlagsToUse())
100  , Draggable(this, KDDockWidgets::usesNativeDraggingAndResizing()) // FloatingWindow is only draggable when using a native title bar. Otherwise the KDDockWidgets::TitleBar is the draggable
101  , m_dropArea(new DropArea(this))
102  , m_titleBar(Config::self().frameworkWidgetFactory()->createTitleBar(this))
103 {
104  if (!suggestedGeometry.isNull())
105  setGeometry(suggestedGeometry);
106 
107  if (kddwUsesQtWidgets()) {
108  // For QtQuick we do it a bit later, once we have the QQuickWindow
109 #ifdef Q_OS_WIN
110  create();
111 #ifdef KDDOCKWIDGETS_QTWIDGETS
112  m_nchittestFilter = new NCHITTESTEventFilter(this);
113  qApp->installNativeEventFilter(m_nchittestFilter);
114 #endif
115  WidgetResizeHandler::setupWindow(windowHandle());
116 #endif
117  }
118 
119  DockRegistry::self()->registerFloatingWindow(this);
120 
121  if (Config::self().flags() & Config::Flag_KeepAboveIfNotUtilityWindow)
122  setWindowFlag(Qt::WindowStaysOnTopHint, true);
123 
124  if (kddwUsesQtWidgets()) {
125  // QtQuick will do it a bit later, once it has a QWindow
126  maybeCreateResizeHandler();
127  }
128 
129  updateTitleBarVisibility();
130  connect(m_dropArea, &LayoutWidget::visibleWidgetCountChanged, this,
131  &FloatingWindow::onFrameCountChanged);
132  connect(m_dropArea, &LayoutWidget::visibleWidgetCountChanged, this,
133  &FloatingWindow::numFramesChanged);
134  connect(m_dropArea, &LayoutWidget::visibleWidgetCountChanged, this,
135  &FloatingWindow::onVisibleFrameCountChanged);
136  m_layoutDestroyedConnection = connect(m_dropArea, &QObject::destroyed, this, &FloatingWindow::scheduleDeleteLater);
137 }
138 
139 FloatingWindow::FloatingWindow(Frame *frame, QRect suggestedGeometry, MainWindowBase *parent)
140  : FloatingWindow(suggestedGeometry, hackFindParentHarder(frame, parent))
141 {
142  m_disableSetVisible = true;
143  // Adding a widget will trigger onFrameCountChanged, which triggers a setVisible(true).
144  // The problem with setVisible(true) will forget about or requested geometry and place the window at 0,0
145  // So disable the setVisible(true) call while in the ctor.
146  m_dropArea->addWidget(frame, KDDockWidgets::Location_OnTop, {});
147  m_disableSetVisible = false;
148 }
149 
150 FloatingWindow::~FloatingWindow()
151 {
152  m_inDtor = true;
153  disconnect(m_layoutDestroyedConnection);
154  delete m_nchittestFilter;
155 
156  DockRegistry::self()->unregisterFloatingWindow(this);
157 }
158 
159 #if defined(Q_OS_WIN) && defined(KDDOCKWIDGETS_QTWIDGETS)
160 bool FloatingWindow::nativeEvent(const QByteArray &eventType, void *message, Qt5Qt6Compat::qintptr *result)
161 {
162  if (m_inDtor || m_deleteScheduled)
163  return QWidget::nativeEvent(eventType, message, result);
164 
165  if (KDDockWidgets::usesAeroSnapWithCustomDecos()) {
166  // To enable aero snap we need to tell Windows where's our custom title bar
167  if (WidgetResizeHandler::handleWindowsNativeEvent(this, eventType, message, result))
168  return true;
169  } else if (KDDockWidgets::usesNativeTitleBar()) {
170  auto msg = static_cast<MSG *>(message);
171  if (msg->message == WM_SIZING) {
172  // Cancel any drag if we're resizing
173  Q_EMIT DragController::instance()->dragCanceled();
174  }
175  }
176 
177  return QWidget::nativeEvent(eventType, message, result);
178 }
179 #endif
180 
181 void FloatingWindow::maybeCreateResizeHandler()
182 {
183  if (!KDDockWidgets::usesNativeDraggingAndResizing()) {
184  setFlag(Qt::FramelessWindowHint, true);
185  setWidgetResizeHandler(new WidgetResizeHandler(/*topLevel=*/true, this));
186  }
187 }
188 
189 std::unique_ptr<WindowBeingDragged> FloatingWindow::makeWindow()
190 {
191  return std::unique_ptr<WindowBeingDragged>(new WindowBeingDragged(this, this));
192 }
193 
194 DockWidgetBase *FloatingWindow::singleDockWidget() const
195 {
196  const Frame::List frames = this->frames();
197  if (frames.size() == 1) {
198  Frame *frame = frames.first();
199  if (frame->hasSingleDockWidget())
200  return frame->dockWidgetAt(0);
201  }
202 
203  return nullptr;
204 }
205 
206 const DockWidgetBase::List FloatingWindow::dockWidgets() const
207 {
208  return m_dropArea->dockWidgets();
209 }
210 
211 const Frame::List FloatingWindow::frames() const
212 {
213  Q_ASSERT(m_dropArea);
214  return m_dropArea->frames();
215 }
216 
217 QSize FloatingWindow::maxSizeHint() const
218 {
219  QSize result = Layouting::Item::hardcodedMaximumSize;
220 
221  if (!m_dropArea) {
222  // Still early, no layout set
223  return result;
224  }
225 
226  const Frame::List frames = this->frames();
227  if (frames.size() == 1) {
228  // Let's honour max-size when we have a single-frame.
229  // multi-frame cases are more complicated and we're not sure if we want the window to
230  // bounce around. single-frame is the most common case, like floating a dock widget, so
231  // let's do that first, it's also easy.
232  Frame *frame = frames[0];
233  if (frame->dockWidgetCount() == 1) { // We don't support if there's tabbing
234  const QSize waste = (minimumSize() - frame->minSize()).expandedTo(QSize(0, 0));
235  result = frame->maxSizeHint() + waste;
236  }
237  }
238 
239  // Semantically the result is fine, but bound it so we don't get:
240  // QWidget::setMaximumSize: (/KDDockWidgets::FloatingWindowWidget) The largest allowed size is (16777215,16777215)
241  return result.boundedTo(Layouting::Item::hardcodedMaximumSize);
242 }
243 
244 void FloatingWindow::setSuggestedGeometry(QRect suggestedRect, SuggestedGeometryHints hint)
245 {
246  const QSize maxSize = maxSizeHint();
247  const bool hasMaxSize = maxSize != Layouting::Item::hardcodedMaximumSize;
248  if (hasMaxSize) {
249  // Resize to new size but preserve center
250  const QPoint originalCenter = suggestedRect.center();
251  suggestedRect.setSize(maxSize.boundedTo(suggestedRect.size()));
252 
254  && (Config::self().flags() & Config::Flag_NativeTitleBar)) {
255  const QMargins margins = contentMargins();
256  suggestedRect.setHeight(suggestedRect.height() - m_titleBar->height() + margins.top()
257  + margins.bottom());
258  }
259 
261  suggestedRect.moveCenter(originalCenter);
262  }
263 
264  setGeometry(suggestedRect);
265 }
266 
267 void FloatingWindow::scheduleDeleteLater()
268 {
269  m_deleteScheduled = true;
270  DockRegistry::self()->unregisterFloatingWindow(this);
271  deleteLater();
272 }
273 
274 MultiSplitter *FloatingWindow::multiSplitter() const
275 {
276  return m_dropArea;
277 }
278 
279 LayoutWidget *FloatingWindow::layoutWidget() const
280 {
281  return m_dropArea;
282 }
283 
284 bool FloatingWindow::isInDragArea(QPoint globalPoint) const
285 {
286 #ifdef Q_OS_WIN
287  // A click near the border will still send a Qt::NonClientMousePressEvent. We shouldn't
288  // interpret that as a drag, as it's for a native resize.
289  // Keep track of how we handled the WM_NCHITTEST
290  if (usesAeroSnapWithCustomDecos())
291  return m_lastHitTest == HTCAPTION;
292 #endif
293 
294  return dragRect().contains(globalPoint);
295 }
296 
297 bool FloatingWindow::anyNonClosable() const
298 {
299  for (Frame *frame : frames()) {
300  if (frame->anyNonClosable())
301  return true;
302  }
303  return false;
304 }
305 
306 bool FloatingWindow::anyNonDockable() const
307 {
308  for (Frame *frame : frames()) {
309  if (frame->anyNonDockable())
310  return true;
311  }
312  return false;
313 }
314 
315 bool FloatingWindow::hasSingleFrame() const
316 {
317  return m_dropArea->visibleCount() == 1;
318 }
319 
320 bool FloatingWindow::hasSingleDockWidget() const
321 {
322  const Frame::List frames = this->frames();
323  if (frames.size() != 1)
324  return false;
325 
326  Frame *frame = frames.first();
327  return frame->dockWidgetCount() == 1;
328 }
329 
330 Frame *FloatingWindow::singleFrame() const
331 {
332  const Frame::List frames = this->frames();
333 
334  return frames.isEmpty() ? nullptr
335  : frames.first();
336 }
337 
338 bool FloatingWindow::beingDeleted() const
339 {
340  if (m_deleteScheduled || m_inDtor)
341  return true;
342 
343  // TODO: Confusing logic
344  for (Frame *f : frames()) {
345  if (!f->beingDeletedLater())
346  return false;
347  }
348 
349  return true;
350 }
351 
352 void FloatingWindow::onFrameCountChanged(int count)
353 {
354  if (count == 0) {
355  scheduleDeleteLater();
356  } else {
357  updateTitleBarVisibility();
358  if (count == 1) // if something was removed, then our single dock widget is floating, we need to check the QAction
359  dropArea()->updateFloatingActions();
360  }
361 }
362 
363 void FloatingWindow::onVisibleFrameCountChanged(int count)
364 {
365  if (m_disableSetVisible)
366  return;
367 
368  updateSizeConstraints();
369  setVisible(count > 0);
370 }
371 
372 void FloatingWindow::updateTitleBarVisibility()
373 {
374  if (m_updatingTitleBarVisibility)
375  return; // Break recursion
376 
377  QScopedValueRollback<bool> guard(m_updatingTitleBarVisibility, true);
378  updateTitleAndIcon();
379 
380  bool visible = true;
381 
382  for (Frame *frame : frames())
383  frame->updateTitleBarVisibility();
384 
385  if (KDDockWidgets::usesClientTitleBar()) {
386  const auto flags = Config::self().flags();
387  if ((flags & Config::Flag_HideTitleBarWhenTabsVisible) && !(flags & Config::Flag_AlwaysTitleBarWhenFloating)) {
388  if (hasSingleFrame()) {
389  visible = !frames().first()->hasTabsVisible();
390  }
391  }
392 
393  m_titleBar->updateButtons();
394  } else {
395  visible = false;
396  }
397 
398  m_titleBar->setVisible(visible);
399 }
400 
401 QStringList FloatingWindow::affinities() const
402 {
403  auto frames = this->frames();
404  return frames.isEmpty() ? QStringList() : frames.constFirst()->affinities();
405 }
406 
407 void FloatingWindow::updateTitleAndIcon()
408 {
409  QString title;
410  QIcon icon;
411  if (hasSingleFrame()) {
412  const Frame *frame = frames().constFirst();
413  title = frame->title();
414  icon = frame->icon();
415  } else {
416  title = qApp->applicationName();
417  }
418  m_titleBar->setTitle(title);
419  m_titleBar->setIcon(icon);
420 
421  // Even without a native title bar it's nice to set the window title/icon, so it shows
422  // in the taskbar (when minimization is supported), or Alt-Tab (in supporting Window Managers)
423  setWindowTitle(title);
424  setWindowIcon(icon);
425 }
426 
427 void FloatingWindow::onCloseEvent(QCloseEvent *e)
428 {
429  if (e->spontaneous() && anyNonClosable()) {
430  // Event from the window system won't close us
431  e->ignore();
432  return;
433  }
434 
435  e->accept(); // Accepted by default (will close unless ignored)
436 
437  const Frame::List frames = this->frames();
438  for (Frame *frame : frames) {
439  qApp->sendEvent(frame, e);
440  if (!e->isAccepted())
441  break; // Stop when the first frame prevents closing
442  }
443 }
444 
445 bool FloatingWindow::deserialize(const LayoutSaver::FloatingWindow &fw)
446 {
447  if (dropArea()->deserialize(fw.multiSplitterLayout)) {
448  updateTitleBarVisibility();
449  show();
450  return true;
451  } else {
452  return false;
453  }
454 }
455 
456 LayoutSaver::FloatingWindow FloatingWindow::serialize() const
457 {
458  LayoutSaver::FloatingWindow fw;
459 
460  fw.geometry = geometry();
461  fw.isVisible = isVisible();
462  fw.multiSplitterLayout = dropArea()->serialize();
463  fw.screenIndex = screenNumberForWidget(this);
464  fw.screenSize = screenSizeForWidget(this);
465  fw.affinities = affinities();
466 
467  auto mainWindow = qobject_cast<MainWindowBase *>(parentWidget());
468  fw.parentIndex = mainWindow ? DockRegistry::self()->mainwindows().indexOf(mainWindow)
469  : -1;
470 
471  return fw;
472 }
473 
474 QRect FloatingWindow::dragRect() const
475 {
476  QRect rect;
477  if (m_titleBar->isVisible()) {
478  rect = m_titleBar->rect();
479  rect.moveTopLeft(m_titleBar->mapToGlobal(QPoint(0, 0)));
480  } else if (hasSingleFrame()) {
481  rect = frames().constFirst()->dragRect();
482  } else {
483  qWarning() << Q_FUNC_INFO << "Expected a title bar";
484  }
485 
486  return rect;
487 }
488 
489 bool FloatingWindow::event(QEvent *ev)
490 {
491  if (ev->type() == QEvent::ActivationChange) {
492  // Since QWidget is missing a signal for window activation
493  Q_EMIT activatedChanged();
494  } else if (ev->type() == QEvent::StatusTip && parent()) {
495  // show status tips in the main window
496  return parent()->event(ev);
497  } else if (ev->type() == QEvent::LayoutRequest) {
498  updateSizeConstraints();
499  }
500 
501  return QWidgetAdapter::event(ev);
502 }
503 
504 bool FloatingWindow::allDockWidgetsHave(DockWidgetBase::Option option) const
505 {
506  const Frame::List frames = this->frames();
507  return std::all_of(frames.begin(), frames.end(), [option](Frame *frame) {
508  return frame->allDockWidgetsHave(option);
509  });
510 }
511 
512 bool FloatingWindow::anyDockWidgetsHas(DockWidgetBase::Option option) const
513 {
514  const Frame::List frames = this->frames();
515  return std::any_of(frames.begin(), frames.end(), [option](Frame *frame) {
516  return frame->anyDockWidgetsHas(option);
517  });
518 }
519 
520 bool FloatingWindow::allDockWidgetsHave(DockWidgetBase::LayoutSaverOption option) const
521 {
522  const Frame::List frames = this->frames();
523  return std::all_of(frames.begin(), frames.end(), [option](Frame *frame) {
524  return frame->allDockWidgetsHave(option);
525  });
526 }
527 
528 bool FloatingWindow::anyDockWidgetsHas(DockWidgetBase::LayoutSaverOption option) const
529 {
530  const Frame::List frames = this->frames();
531  return std::any_of(frames.begin(), frames.end(), [option](Frame *frame) {
532  return frame->anyDockWidgetsHas(option);
533  });
534 }
535 
536 void FloatingWindow::addDockWidget(DockWidgetBase *dw, Location location,
537  DockWidgetBase *relativeTo, InitialOption option)
538 {
539  m_dropArea->addDockWidget(dw, location, relativeTo, option);
540 }
541 
542 bool FloatingWindow::isMDI() const
543 {
544  return false;
545 }
546 
547 bool FloatingWindow::isWindow() const
548 {
549  return true;
550 }
551 
552 MainWindowBase *FloatingWindow::mainWindow() const
553 {
554  return qobject_cast<MainWindowBase *>(parent());
555 }
556 
557 QMargins FloatingWindow::contentMargins() const
558 {
559  return { 4, 4, 4, 4 };
560 }
561 
562 int FloatingWindow::userType() const
563 {
564  if (Frame *f = singleFrame())
565  return f->userType();
566  return 0;
567 }
568 
569 void FloatingWindow::updateSizeConstraints()
570 {
571  // Doing a delayed call to make sure the layout has completled any ongoing operation.
572  QTimer::singleShot(0, this, [this] {
573  // Not simply using layout's max-size support because
574  // 1) that's not portable to QtQuick
575  // 2) QStackedLayout (from tab-widget) doesn't propagate size constraints up
576  // Doing it manually instead.
577  setMaximumSize(maxSizeHint());
578  });
579 }
QMainWindow::event
virtual bool event(QEvent *event) override
KDDockWidgets::SuggestedGeometryHint_PreserveCenter
@ SuggestedGeometryHint_PreserveCenter
Definition: KDDockWidgets.h:201
QRect::setSize
void setSize(const QSize &size)
QRect::moveTopLeft
void moveTopLeft(const QPoint &position)
QVector::isEmpty
bool isEmpty() const const
QMargins::bottom
int bottom() const const
QEvent::ActivationChange
ActivationChange
QRect::size
QSize size() const const
QRect
QWidget::nativeEvent
virtual bool nativeEvent(const QByteArray &eventType, void *message, long *result)
KDDockWidgets::InitialOption
Struct describing the preferred dock widget size and visibility when adding it to a layout.
Definition: KDDockWidgets.h:103
QTimer::singleShot
singleShot
KDDockWidgets::Location_OnTop
@ Location_OnTop
Left docking location
Definition: KDDockWidgets.h:48
KDDockWidgets::Location
Location
Definition: KDDockWidgets.h:45
QSize
QVector::first
T & first()
MainWindowBase.h
The MainWindow base-class that's shared between QtWidgets and QtQuick stack.
QObject::destroyed
void destroyed(QObject *obj)
Qt::WindowFlags
typedef WindowFlags
QCloseEvent
QRect::setHeight
void setHeight(int height)
QString
QEvent::isAccepted
bool isAccepted() const const
KDDockWidgets::DockWidgetBase::LayoutSaverOption
LayoutSaverOption
Options which will affect LayoutSaver save/restore.
Definition: DockWidgetBase.h:86
KDDockWidgets::Config
Singleton to allow to choose certain behaviours of the framework.
Definition: Config.h:55
QSize::boundedTo
QSize boundedTo(const QSize &otherSize) const const
QRect::center
QPoint center() const const
QIcon
QRect::isNull
bool isNull() const const
QEvent::spontaneous
bool spontaneous() const const
QMargins
QRect::moveCenter
void moveCenter(const QPoint &position)
Config.h
Application-wide config to tune certain behaviours of the framework.
QRect::height
int height() const const
QEvent::type
QEvent::Type type() const const
QEvent
QEvent::ignore
void ignore()
KDDockWidgets::DockWidgetBase
The DockWidget base-class. DockWidget and DockWidgetBase are only split in two so we can share some c...
Definition: DockWidgetBase.h:61
KDDockWidgets
Definition: Config.cpp:36
QScopedValueRollback
QVector::size
int size() const const
Draggable
KDDockWidgets::MainWindowBase
The MainWindow base-class. MainWindow and MainWindowBase are only split in two so we can share some c...
Definition: MainWindowBase.h:56
QVector
KDDockWidgets::DockWidgetBase::Option
Option
DockWidget options to pass at construction time.
Definition: DockWidgetBase.h:75
hackFindParentHarder
static MainWindowBase * hackFindParentHarder(Frame *frame, MainWindowBase *candidateParent)
Definition: FloatingWindow.cpp:57
windowFlagsToUse
static Qt::WindowFlags windowFlagsToUse()
Definition: FloatingWindow.cpp:41
KDDockWidgets::SuggestedGeometryHint_GeometryIsFromDocked
@ SuggestedGeometryHint_GeometryIsFromDocked
Definition: KDDockWidgets.h:202
QMargins::top
int top() const const
KDDockWidgets::Config::InternalFlag_DontUseQtToolWindowsForFloatingWindows
@ InternalFlag_DontUseQtToolWindowsForFloatingWindows
FloatingWindows will use Qt::Window instead of Qt::Tool.
Definition: Config.h:112
QPoint
KDDockWidgets::Config::InternalFlag_DontUseParentForFloatingWindows
@ InternalFlag_DontUseParentForFloatingWindows
FloatingWindows won't have a parent top-level.
Definition: Config.h:111
QByteArray
QEvent::accept
void accept()
KDDockWidgets::Config::self
static Config & self()
returns the singleton Config instance
Definition: Config.cpp:82
FrameworkWidgetFactory.h
A factory class for allowing the user to customize some internal widgets.
QStringList
actualParent
MainWindowBase * actualParent(MainWindowBase *candidate)
Definition: FloatingWindow.cpp:91

© 2019-2021 Klarälvdalens Datakonsult AB (KDAB)
"The Qt, C++ and OpenGL Experts"
https://www.kdab.com/
KDDockWidgets
Advanced Dock Widget Framework for Qt
https://www.kdab.com/development-resources/qt-tools/kddockwidgets/
Generated on Mon Nov 15 2021 00:17:20 for KDDockWidgets API Documentation by doxygen 1.8.20