KDDockWidgets API Documentation 1.7
Loading...
Searching...
No Matches
DropArea.cpp
Go to the documentation of this file.
1/*
2 This file is part of KDDockWidgets.
3
4 SPDX-FileCopyrightText: 2019-2023 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com>
5 Author: SĂ©rgio Martins <sergio.martins@kdab.com>
6
7 SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only
8
9 Contact KDAB at <info@kdab.com> for commercial licensing options.
10*/
11
12#include "DropArea_p.h"
13#include "Config.h"
14#include "DockRegistry_p.h"
15#include "DockWidgetBase.h"
16#include "DockWidgetBase_p.h"
17#include "Draggable_p.h"
18#include "DropIndicatorOverlayInterface_p.h"
19#include "FloatingWindow_p.h"
20#include "Frame_p.h"
22#include "Logging_p.h"
23#include "MainWindowBase.h"
24#include "Utils_p.h"
25#include "multisplitter/Item_p.h"
26#include "WindowBeingDragged_p.h"
27
28using namespace KDDockWidgets;
29
36DropArea::DropArea(QWidgetOrQuick *parent, bool isMDIWrapper)
37 : MultiSplitter(parent)
38 , m_isMDIWrapper(isMDIWrapper)
39 , m_dropIndicatorOverlay(Config::self().frameworkWidgetFactory()->createDropIndicatorOverlay(this))
40{
41 qCDebug(creation) << "DropArea";
42 if (isWayland()) {
43#ifdef KDDOCKWIDGETS_QTWIDGETS
44 setAcceptDrops(true);
45#else
46 qWarning() << "Dropping not implement for QtQuick on Wayland yet!";
47#endif
48 }
49
50 if (m_isMDIWrapper) {
51 connect(this, &MultiSplitter::visibleWidgetCountChanged, this, [this] {
52 auto dw = mdiDockWidgetWrapper();
53 if (!dw) {
54 qWarning() << Q_FUNC_INFO << "Unexpected null wrapper dock widget";
55 return;
56 }
57
58 if (visibleCount() > 0) {
59 // The title of our MDI frame will need to change to the app name if we have more than 1 dock widget nested
60 Q_EMIT dw->titleChanged(dw->title());
61 } else {
62 // Our wrapeper isn't needed anymore
63 dw->deleteLater();
64 }
65 });
66 }
67}
68
69DropArea::~DropArea()
70{
71 m_inDestructor = true;
72 qCDebug(creation) << "~DropArea";
73}
74
75Frame::List DropArea::frames() const
76{
77 return findChildren<Frame *>(QString(), Qt::FindDirectChildrenOnly);
78}
79
80Frame *DropArea::frameContainingPos(QPoint globalPos) const
81{
82 const Layouting::Item::List &items = this->items();
83 for (Layouting::Item *item : items) {
84 auto frame = static_cast<Frame *>(item->guestAsQObject());
85 if (!frame || !frame->QWidgetAdapter::isVisible()) {
86 continue;
87 }
88
89 if (frame->containsMouse(globalPos))
90 return frame;
91 }
92 return nullptr;
93}
94
95void DropArea::updateFloatingActions()
96{
97 const Frame::List frames = this->frames();
98 for (Frame *frame : frames)
99 frame->updateFloatingActions();
100}
101
102Layouting::Item *DropArea::centralFrame() const
103{
104 for (Layouting::Item *item : this->items()) {
105 if (auto f = static_cast<Frame *>(item->guestAsQObject())) {
106 if (f->isCentralFrame())
107 return item;
108 }
109 }
110 return nullptr;
111}
112
113void DropArea::addDockWidget(DockWidgetBase *dw, Location location,
114 DockWidgetBase *relativeTo, InitialOption option)
115{
116 if (!dw || dw == relativeTo || location == Location_None) {
117 qWarning() << Q_FUNC_INFO << "Invalid parameters" << dw << relativeTo << location;
118 return;
119 }
120
121 if ((option.visibility == InitialVisibilityOption::StartHidden) && dw->d->frame() != nullptr) {
122 // StartHidden is just to be used at startup, not to moving stuff around
123 qWarning() << Q_FUNC_INFO << "Dock widget already exists in the layout";
124 return;
125 }
126
127 if (!validateAffinity(dw))
128 return;
129
130 Frame *frame = nullptr;
131 Frame *relativeToFrame = relativeTo ? relativeTo->d->frame() : nullptr;
132
133 dw->d->saveLastFloatingGeometry();
134
135 const bool hadSingleFloatingFrame = hasSingleFloatingFrame();
136
137 // Check if the dock widget already exists in the layout
138 if (containsDockWidget(dw)) {
139 Frame *oldFrame = dw->d->frame();
140 if (oldFrame->hasSingleDockWidget()) {
141 Q_ASSERT(oldFrame->containsDockWidget(dw));
142 // The frame only has this dock widget, and the frame is already in the layout. So move the frame instead
143 frame = oldFrame;
144 } else {
146 frame->addWidget(dw);
147 }
148 } else {
150 frame->addWidget(dw);
151 }
152
153 if (option.startsHidden()) {
154 addWidget(dw, location, relativeToFrame, option);
155 } else {
156 addWidget(frame, location, relativeToFrame, option);
157 }
158
159 if (hadSingleFloatingFrame && !hasSingleFloatingFrame()) {
160 // The dock widgets that already existed in our layout need to have their floatAction() updated
161 // otherwise it's still checked. Only the dropped dock widget got updated
162 updateFloatingActions();
163 }
164}
165
166bool DropArea::containsDockWidget(DockWidgetBase *dw) const
167{
168 return dw->d->frame() && LayoutWidget::containsFrame(dw->d->frame());
169}
170
171bool DropArea::hasSingleFloatingFrame() const
172{
173 const Frame::List frames = this->frames();
174 return frames.size() == 1 && frames.first()->isFloating();
175}
176
177bool DropArea::hasSingleFrame() const
178{
179 return visibleCount() == 1;
180}
181
182QStringList DropArea::affinities() const
183{
184 if (auto mw = mainWindow()) {
185 return mw->affinities();
186 } else if (auto fw = floatingWindow()) {
187 return fw->affinities();
188 }
189
190 return {};
191}
192
193void DropArea::layoutParentContainerEqually(DockWidgetBase *dw)
194{
195 Layouting::Item *item = itemForFrame(dw->d->frame());
196 if (!item) {
197 qWarning() << Q_FUNC_INFO << "Item not found for" << dw << dw->d->frame();
198 return;
199 }
200
201 layoutEqually(item->parentBoxContainer());
202}
203
204DropLocation DropArea::hover(WindowBeingDragged *draggedWindow, QPoint globalPos)
205{
206 if (Config::self().dropIndicatorsInhibited() || !validateAffinity(draggedWindow))
207 return DropLocation_None;
208
209 if (!m_dropIndicatorOverlay) {
210 qWarning() << Q_FUNC_INFO << "The frontend is missing a drop indicator overlay";
211 return DropLocation_None;
212 }
213
214 Frame *frame = frameContainingPos(globalPos); // Frame is nullptr if MainWindowOption_HasCentralFrame isn't set
215 m_dropIndicatorOverlay->setWindowBeingDragged(true);
216 m_dropIndicatorOverlay->setHoveredFrame(frame);
217 return m_dropIndicatorOverlay->hover(globalPos);
218}
219
220static bool isOutterLocation(DropLocation location)
221{
222 switch (location) {
227 return true;
228 default:
229 return false;
230 }
231}
232
233bool DropArea::drop(WindowBeingDragged *droppedWindow, QPoint globalPos)
234{
235 FloatingWindow *floatingWindow = droppedWindow->floatingWindow();
236
237 if (floatingWindow == window()) {
238 qWarning() << "Refusing to drop onto itself"; // Doesn't happen
239 return false;
240 }
241
242 if (m_dropIndicatorOverlay->currentDropLocation() == DropLocation_None) {
243 qCDebug(hovering) << "DropArea::drop: bailing out, drop location = none";
244 return false;
245 }
246
247 qCDebug(dropping) << "DropArea::drop:" << droppedWindow;
248
249 hover(droppedWindow, globalPos);
250 auto droploc = m_dropIndicatorOverlay->currentDropLocation();
251 Frame *acceptingFrame = m_dropIndicatorOverlay->hoveredFrame();
252 if (!(acceptingFrame || isOutterLocation(droploc))) {
253 qWarning() << "DropArea::drop: asserted with frame=" << acceptingFrame
254 << "; Location=" << droploc;
255 return false;
256 }
257
258 return drop(droppedWindow, acceptingFrame, droploc);
259}
260
261bool DropArea::drop(WindowBeingDragged *draggedWindow, Frame *acceptingFrame,
262 DropLocation droploc)
263{
264 FloatingWindow *droppedWindow = draggedWindow ? draggedWindow->floatingWindow()
265 : nullptr;
266
267 if (isWayland() && !droppedWindow) {
268 // This is the Wayland special case.
269 // With other platforms, when detaching a tab or dock widget we create the FloatingWindow immediately.
270 // With Wayland we delay the floating window until we drop it.
271 // Ofc, we could just dock the dockwidget without the temporary FloatingWindow, but this way we reuse
272 // 99% of the rest of the code, without adding more wayland special cases
273 droppedWindow = draggedWindow ? draggedWindow->draggable()->makeWindow()->floatingWindow()
274 : nullptr;
275 if (!droppedWindow) {
276 // Doesn't happen
277 qWarning() << Q_FUNC_INFO << "Wayland: Expected window" << draggedWindow;
278 return false;
279 }
280 }
281
282 bool result = true;
283 const bool needToFocusNewlyDroppedWidgets = Config::self().flags() & Config::Flag_TitleBarIsFocusable;
284 const DockWidgetBase::List droppedDockWidgets = needToFocusNewlyDroppedWidgets
285 ? droppedWindow->layoutWidget()->dockWidgets()
286 : DockWidgetBase::List(); // just so save some memory allocations for the case where this
287 // variable isn't used
288
289 switch (droploc) {
291 case DropLocation_Top:
294 result = drop(droppedWindow, DropIndicatorOverlayInterface::multisplitterLocationFor(droploc), acceptingFrame);
295 break;
300 result = drop(droppedWindow, DropIndicatorOverlayInterface::multisplitterLocationFor(droploc), nullptr);
301 break;
303 qCDebug(hovering) << "Tabbing" << droppedWindow << "into" << acceptingFrame;
304 if (!validateAffinity(droppedWindow, acceptingFrame))
305 return false;
306 acceptingFrame->addWidget(droppedWindow);
307 break;
308
309 default:
310 qWarning() << "DropArea::drop: Unexpected drop location" << m_dropIndicatorOverlay->currentDropLocation();
311 result = false;
312 break;
313 }
314
315 if (result) {
316 // Window receiving the drop gets raised.
317 // Exception: Under EGLFS we don't raise the fullscreen main window, as then all floating windows would
318 // go behind. It's also unneeded to raise, as it's fullscreen.
319
320 const bool isEGLFSRootWindow = isEGLFS() && (window()->isFullScreen() || window()->isMaximized());
321 if (!isEGLFSRootWindow)
322 raiseAndActivate();
323
324 if (needToFocusNewlyDroppedWidgets) {
325 // Let's also focus the newly dropped dock widget
326 if (!droppedDockWidgets.isEmpty()) {
327 // If more than 1 was dropped, we only focus the first one
328 Frame *frame = droppedDockWidgets.first()->d->frame();
329 frame->FocusScope::focus(Qt::MouseFocusReason);
330 } else {
331 // Doesn't happen.
332 qWarning() << Q_FUNC_INFO << "Nothing was dropped?";
333 }
334 }
335 }
336
337 return result;
338}
339
340bool DropArea::drop(QWidgetOrQuick *droppedWindow, KDDockWidgets::Location location, Frame *relativeTo)
341{
342 qCDebug(docking) << "DropArea::addFrame";
343
344 if (auto dock = qobject_cast<DockWidgetBase *>(droppedWindow)) {
345 if (!validateAffinity(dock))
346 return false;
347
349 frame->addWidget(dock);
350 addWidget(frame, location, relativeTo, DefaultSizeMode::FairButFloor);
351 } else if (auto floatingWindow = qobject_cast<FloatingWindow *>(droppedWindow)) {
352 if (!validateAffinity(floatingWindow))
353 return false;
354
355 const bool hadSingleFloatingFrame = hasSingleFloatingFrame();
356 addMultiSplitter(floatingWindow->dropArea(), location, relativeTo,
357 DefaultSizeMode::FairButFloor);
358 if (hadSingleFloatingFrame != hasSingleFloatingFrame())
359 updateFloatingActions();
360
361 floatingWindow->scheduleDeleteLater();
362 return true;
363 } else {
364 qWarning() << "Unknown dropped widget" << droppedWindow;
365 return false;
366 }
367
368 return true;
369}
370
371void DropArea::removeHover()
372{
373 m_dropIndicatorOverlay->removeHover();
374}
375
376template<typename T>
377bool DropArea::validateAffinity(T *window, Frame *acceptingFrame) const
378{
379 if (!DockRegistry::self()->affinitiesMatch(window->affinities(), affinities())) {
380 return false;
381 }
382
383 if (acceptingFrame) {
384 // We're dropping into another frame (as tabbed), so also check the affinity of the frame
385 // not only of the main window, which might be more forgiving
386 if (!DockRegistry::self()->affinitiesMatch(window->affinities(), acceptingFrame->affinities())) {
387 return false;
388 }
389 }
390
391 return true;
392}
393
394bool DropArea::isMDIWrapper() const
395{
396 return m_isMDIWrapper;
397}
398
399DockWidgetBase *DropArea::mdiDockWidgetWrapper() const
400{
401 if (m_isMDIWrapper)
402 return qobject_cast<DockWidgetBase *>(QWidgetAdapter::parent());
403
404 return nullptr;
405}
Application-wide config to tune certain behaviours of the framework.
The DockWidget base-class that's shared between QtWidgets and QtQuick stack.
static bool isOutterLocation(DropLocation location)
Definition DropArea.cpp:220
A factory class for allowing the user to customize some internal widgets.
The MainWindow base-class that's shared between QtWidgets and QtQuick stack.
Singleton to allow to choose certain behaviours of the framework.
Definition Config.h:75
FrameworkWidgetFactory * frameworkWidgetFactory() const
getter for the framework widget factory
Definition Config.cpp:145
static Config & self()
returns the singleton Config instance
Definition Config.cpp:84
Flags flags() const
returns the chosen flags
Definition Config.cpp:95
@ Flag_TitleBarIsFocusable
You can click the title bar and it will focus the last focused widget in the focus scope....
Definition Config.h:98
The DockWidget base-class. DockWidget and DockWidgetBase are only split in two so we can share some c...
QVector< DockWidgetBase * > List
virtual Frame * createFrame(QWidgetOrQuick *parent=nullptr, FrameOptions options=FrameOption_None) const =0
Called internally by the framework to create a Frame class Override to provide your own Frame sub-cla...
DropLocation
Enum describing the different drop indicator types.
FindDirectChildrenOnly
MouseFocusReason
T & first()
bool isEmpty() const const
Struct describing the preferred dock widget size and visibility when adding it to a layout.
InitialVisibilityOption visibility
Allows a dock widget to be docked as hidden.

© 2019-2023 Klarälvdalens Datakonsult AB (KDAB)
"The Qt, C++ and OpenGL Experts"
https://www.kdab.com/
KDDockWidgets
Advanced Dock Widget Framework for Qt
https://www.kdab.com/development-resources/qt-tools/kddockwidgets/
Generated on Wed Nov 1 2023 00:02:31 for KDDockWidgets API Documentation by doxygen 1.9.8