// Copyright (C) 2020 Uwe Kindler // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-2.1-or-later OR GPL-3.0-or-later #include "floatingdockcontainer.h" #include "ads_globals_p.h" #include "dockareawidget.h" #include "dockcontainerwidget.h" #include "dockmanager.h" #include "dockoverlay.h" #include "dockwidget.h" #include #ifdef Q_OS_WIN #include #ifdef _MSC_VER #pragma comment(lib, "User32.lib") #endif #endif #if defined(Q_OS_UNIX) && !defined(Q_OS_MACOS) #include "linux/floatingwidgettitlebar.h" #endif #include #include #include #include #include #include #include #include #include namespace ADS { #ifdef Q_OS_WIN #if 0 // set to 1 if you need this function for debugging /** * Just for debugging to convert windows message identifiers to strings */ static const char* windowsMessageString(int messageId) { switch (messageId) { case 0: return "WM_NULL"; case 1: return "WM_CREATE"; case 2: return "WM_DESTROY"; case 3: return "WM_MOVE"; case 5: return "WM_SIZE"; case 6: return "WM_ACTIVATE"; case 7: return "WM_SETFOCUS"; case 8: return "WM_KILLFOCUS"; case 10: return "WM_ENABLE"; case 11: return "WM_SETREDRAW"; case 12: return "WM_SETTEXT"; case 13: return "WM_GETTEXT"; case 14: return "WM_GETTEXTLENGTH"; case 15: return "WM_PAINT"; case 16: return "WM_CLOSE"; case 17: return "WM_QUERYENDSESSION"; case 18: return "WM_QUIT"; case 19: return "WM_QUERYOPEN"; case 20: return "WM_ERASEBKGND"; case 21: return "WM_SYSCOLORCHANGE"; case 22: return "WM_ENDSESSION"; case 24: return "WM_SHOWWINDOW"; case 25: return "WM_CTLCOLOR"; case 26: return "WM_WININICHANGE"; case 27: return "WM_DEVMODECHANGE"; case 28: return "WM_ACTIVATEAPP"; case 29: return "WM_FONTCHANGE"; case 30: return "WM_TIMECHANGE"; case 31: return "WM_CANCELMODE"; case 32: return "WM_SETCURSOR"; case 33: return "WM_MOUSEACTIVATE"; case 34: return "WM_CHILDACTIVATE"; case 35: return "WM_QUEUESYNC"; case 36: return "WM_GETMINMAXINFO"; case 38: return "WM_PAINTICON"; case 39: return "WM_ICONERASEBKGND"; case 40: return "WM_NEXTDLGCTL"; case 42: return "WM_SPOOLERSTATUS"; case 43: return "WM_DRAWITEM"; case 44: return "WM_MEASUREITEM"; case 45: return "WM_DELETEITEM"; case 46: return "WM_VKEYTOITEM"; case 47: return "WM_CHARTOITEM"; case 48: return "WM_SETFONT"; case 49: return "WM_GETFONT"; case 50: return "WM_SETHOTKEY"; case 51: return "WM_GETHOTKEY"; case 55: return "WM_QUERYDRAGICON"; case 57: return "WM_COMPAREITEM"; case 61: return "WM_GETOBJECT"; case 65: return "WM_COMPACTING"; case 68: return "WM_COMMNOTIFY"; case 70: return "WM_WINDOWPOSCHANGING"; case 71: return "WM_WINDOWPOSCHANGED"; case 72: return "WM_POWER"; case 73: return "WM_COPYGLOBALDATA"; case 74: return "WM_COPYDATA"; case 75: return "WM_CANCELJOURNAL"; case 78: return "WM_NOTIFY"; case 80: return "WM_INPUTLANGCHANGEREQUEST"; case 81: return "WM_INPUTLANGCHANGE"; case 82: return "WM_TCARD"; case 83: return "WM_HELP"; case 84: return "WM_USERCHANGED"; case 85: return "WM_NOTIFYFORMAT"; case 123: return "WM_CONTEXTMENU"; case 124: return "WM_STYLECHANGING"; case 125: return "WM_STYLECHANGED"; case 126: return "WM_DISPLAYCHANGE"; case 127: return "WM_GETICON"; case 128: return "WM_SETICON"; case 129: return "WM_NCCREATE"; case 130: return "WM_NCDESTROY"; case 131: return "WM_NCCALCSIZE"; case 132: return "WM_NCHITTEST"; case 133: return "WM_NCPAINT"; case 134: return "WM_NCACTIVATE"; case 135: return "WM_GETDLGCODE"; case 136: return "WM_SYNCPAINT"; case 160: return "WM_NCMOUSEMOVE"; case 161: return "WM_NCLBUTTONDOWN"; case 162: return "WM_NCLBUTTONUP"; case 163: return "WM_NCLBUTTONDBLCLK"; case 164: return "WM_NCRBUTTONDOWN"; case 165: return "WM_NCRBUTTONUP"; case 166: return "WM_NCRBUTTONDBLCLK"; case 167: return "WM_NCMBUTTONDOWN"; case 168: return "WM_NCMBUTTONUP"; case 169: return "WM_NCMBUTTONDBLCLK"; case 171: return "WM_NCXBUTTONDOWN"; case 172: return "WM_NCXBUTTONUP"; case 173: return "WM_NCXBUTTONDBLCLK"; case 176: return "EM_GETSEL"; case 177: return "EM_SETSEL"; case 178: return "EM_GETRECT"; case 179: return "EM_SETRECT"; case 180: return "EM_SETRECTNP"; case 181: return "EM_SCROLL"; case 182: return "EM_LINESCROLL"; case 183: return "EM_SCROLLCARET"; case 185: return "EM_GETMODIFY"; case 187: return "EM_SETMODIFY"; case 188: return "EM_GETLINECOUNT"; case 189: return "EM_LINEINDEX"; case 190: return "EM_SETHANDLE"; case 191: return "EM_GETHANDLE"; case 192: return "EM_GETTHUMB"; case 193: return "EM_LINELENGTH"; case 194: return "EM_REPLACESEL"; case 195: return "EM_SETFONT"; case 196: return "EM_GETLINE"; case 197: return "EM_LIMITTEXT / EM_SETLIMITTEXT"; case 198: return "EM_CANUNDO"; case 199: return "EM_UNDO"; case 200: return "EM_FMTLINES"; case 201: return "EM_LINEFROMCHAR"; case 202: return "EM_SETWORDBREAK"; case 203: return "EM_SETTABSTOPS"; case 204: return "EM_SETPASSWORDCHAR"; case 205: return "EM_EMPTYUNDOBUFFER"; case 206: return "EM_GETFIRSTVISIBLELINE"; case 207: return "EM_SETREADONLY"; case 209: return "EM_SETWORDBREAKPROC / EM_GETWORDBREAKPROC"; case 210: return "EM_GETPASSWORDCHAR"; case 211: return "EM_SETMARGINS"; case 212: return "EM_GETMARGINS"; case 213: return "EM_GETLIMITTEXT"; case 214: return "EM_POSFROMCHAR"; case 215: return "EM_CHARFROMPOS"; case 216: return "EM_SETIMESTATUS"; case 217: return "EM_GETIMESTATUS"; case 224: return "SBM_SETPOS"; case 225: return "SBM_GETPOS"; case 226: return "SBM_SETRANGE"; case 227: return "SBM_GETRANGE"; case 228: return "SBM_ENABLE_ARROWS"; case 230: return "SBM_SETRANGEREDRAW"; case 233: return "SBM_SETSCROLLINFO"; case 234: return "SBM_GETSCROLLINFO"; case 235: return "SBM_GETSCROLLBARINFO"; case 240: return "BM_GETCHECK"; case 241: return "BM_SETCHECK"; case 242: return "BM_GETSTATE"; case 243: return "BM_SETSTATE"; case 244: return "BM_SETSTYLE"; case 245: return "BM_CLICK"; case 246: return "BM_GETIMAGE"; case 247: return "BM_SETIMAGE"; case 248: return "BM_SETDONTCLICK"; case 255: return "WM_INPUT"; case 256: return "WM_KEYDOWN"; case 257: return "WM_KEYUP"; case 258: return "WM_CHAR"; case 259: return "WM_DEADCHAR"; case 260: return "WM_SYSKEYDOWN"; case 261: return "WM_SYSKEYUP"; case 262: return "WM_SYSCHAR"; case 263: return "WM_SYSDEADCHAR"; case 265: return "WM_UNICHAR / WM_WNT_CONVERTREQUESTEX"; case 266: return "WM_CONVERTREQUEST"; case 267: return "WM_CONVERTRESULT"; case 268: return "WM_INTERIM"; case 269: return "WM_IME_STARTCOMPOSITION"; case 270: return "WM_IME_ENDCOMPOSITION"; case 272: return "WM_INITDIALOG"; case 273: return "WM_COMMAND"; case 274: return "WM_SYSCOMMAND"; case 275: return "WM_TIMER"; case 276: return "WM_HSCROLL"; case 277: return "WM_VSCROLL"; case 278: return "WM_INITMENU"; case 279: return "WM_INITMENUPOPUP"; case 280: return "WM_SYSTIMER"; case 287: return "WM_MENUSELECT"; case 288: return "WM_MENUCHAR"; case 289: return "WM_ENTERIDLE"; case 290: return "WM_MENURBUTTONUP"; case 291: return "WM_MENUDRAG"; case 292: return "WM_MENUGETOBJECT"; case 293: return "WM_UNINITMENUPOPUP"; case 294: return "WM_MENUCOMMAND"; case 295: return "WM_CHANGEUISTATE"; case 296: return "WM_UPDATEUISTATE"; case 297: return "WM_QUERYUISTATE"; case 306: return "WM_CTLCOLORMSGBOX"; case 307: return "WM_CTLCOLOREDIT"; case 308: return "WM_CTLCOLORLISTBOX"; case 309: return "WM_CTLCOLORBTN"; case 310: return "WM_CTLCOLORDLG"; case 311: return "WM_CTLCOLORSCROLLBAR"; case 312: return "WM_CTLCOLORSTATIC"; case 512: return "WM_MOUSEMOVE"; case 513: return "WM_LBUTTONDOWN"; case 514: return "WM_LBUTTONUP"; case 515: return "WM_LBUTTONDBLCLK"; case 516: return "WM_RBUTTONDOWN"; case 517: return "WM_RBUTTONUP"; case 518: return "WM_RBUTTONDBLCLK"; case 519: return "WM_MBUTTONDOWN"; case 520: return "WM_MBUTTONUP"; case 521: return "WM_MBUTTONDBLCLK"; case 522: return "WM_MOUSEWHEEL"; case 523: return "WM_XBUTTONDOWN"; case 524: return "WM_XBUTTONUP"; case 525: return "WM_XBUTTONDBLCLK"; case 528: return "WM_PARENTNOTIFY"; case 529: return "WM_ENTERMENULOOP"; case 530: return "WM_EXITMENULOOP"; case 531: return "WM_NEXTMENU"; case 532: return "WM_SIZING"; case 533: return "WM_CAPTURECHANGED"; case 534: return "WM_MOVING"; case 536: return "WM_POWERBROADCAST"; case 537: return "WM_DEVICECHANGE"; case 544: return "WM_MDICREATE"; case 545: return "WM_MDIDESTROY"; case 546: return "WM_MDIACTIVATE"; case 547: return "WM_MDIRESTORE"; case 548: return "WM_MDINEXT"; case 549: return "WM_MDIMAXIMIZE"; case 550: return "WM_MDITILE"; case 551: return "WM_MDICASCADE"; case 552: return "WM_MDIICONARRANGE"; case 553: return "WM_MDIGETACTIVE"; case 560: return "WM_MDISETMENU"; case 561: return "WM_ENTERSIZEMOVE"; case 562: return "WM_EXITSIZEMOVE"; case 563: return "WM_DROPFILES"; case 564: return "WM_MDIREFRESHMENU"; case 640: return "WM_IME_REPORT"; case 641: return "WM_IME_SETCONTEXT"; case 642: return "WM_IME_NOTIFY"; case 643: return "WM_IME_CONTROL"; case 644: return "WM_IME_COMPOSITIONFULL"; case 645: return "WM_IME_SELECT"; case 646: return "WM_IME_CHAR"; case 648: return "WM_IME_REQUEST"; case 656: return "WM_IME_KEYDOWN"; case 657: return "WM_IME_KEYUP"; case 672: return "WM_NCMOUSEHOVER"; case 673: return "WM_MOUSEHOVER"; case 674: return "WM_NCMOUSELEAVE"; case 675: return "WM_MOUSELEAVE"; case 768: return "WM_CUT"; case 769: return "WM_COPY"; case 770: return "WM_PASTE"; case 771: return "WM_CLEAR"; case 772: return "WM_UNDO"; case 773: return "WM_RENDERFORMAT"; case 774: return "WM_RENDERALLFORMATS"; case 775: return "WM_DESTROYCLIPBOARD"; case 776: return "WM_DRAWCLIPBOARD"; case 777: return "WM_PAINTCLIPBOARD"; case 778: return "WM_VSCROLLCLIPBOARD"; case 779: return "WM_SIZECLIPBOARD"; case 780: return "WM_ASKCBFORMATNAME"; case 781: return "WM_CHANGECBCHAIN"; case 782: return "WM_HSCROLLCLIPBOARD"; case 783: return "WM_QUERYNEWPALETTE"; case 784: return "WM_PALETTEISCHANGING"; case 785: return "WM_PALETTECHANGED"; case 786: return "WM_HOTKEY"; case 791: return "WM_PRINT"; case 792: return "WM_PRINTCLIENT"; case 793: return "WM_APPCOMMAND"; case 856: return "WM_HANDHELDFIRST"; case 863: return "WM_HANDHELDLAST"; case 864: return "WM_AFXFIRST"; case 895: return "WM_AFXLAST"; case 896: return "WM_PENWINFIRST"; case 897: return "WM_RCRESULT"; case 898: return "WM_HOOKRCRESULT"; case 899: return "WM_GLOBALRCCHANGE / WM_PENMISCINFO"; case 900: return "WM_SKB"; case 901: return "WM_HEDITCTL / WM_PENCTL"; case 902: return "WM_PENMISC"; case 903: return "WM_CTLINIT"; case 904: return "WM_PENEVENT"; case 911: return "WM_PENWINLAST"; default: return "unknown WM_ message"; } return "unknown WM_ message"; } #endif #endif AbstractFloatingWidget::~AbstractFloatingWidget() = default; static unsigned int zOrderCounter = 0; /** * Private data class of FloatingDockContainer class (pimpl) */ class FloatingDockContainerPrivate { public: FloatingDockContainer *q; DockContainerWidget *m_dockContainer = nullptr; unsigned int m_zOrderIndex = ++zOrderCounter; QPointer m_dockManager; eDragState m_draggingState = DraggingInactive; QPoint m_dragStartMousePosition; DockContainerWidget *m_dropContainer = nullptr; DockAreaWidget *m_singleDockArea = nullptr; QPoint m_dragStartPos; bool m_hiding = false; bool m_autoHideChildren = true; #if defined(Q_OS_UNIX) && !defined(Q_OS_MACOS) QWidget *m_mouseEventHandler = nullptr; FloatingWidgetTitleBar *m_titleBar = nullptr; bool m_isResizing = false; bool m_mousePressed = false; #endif /** * Private data constructor */ FloatingDockContainerPrivate(FloatingDockContainer *parent); void titleMouseReleaseEvent(); void updateDropOverlays(const QPoint &globalPosition); /** * Returns true if the given config flag is set */ static bool testConfigFlag(DockManager::eConfigFlag flag) { return DockManager::testConfigFlag(flag); } /** * Tests is a certain state is active */ bool isState(eDragState stateId) const { return stateId == m_draggingState; } /** * Sets the dragging state and posts a FloatingWidgetDragStartEvent if dragging starts. */ void setState(eDragState stateId) { if (m_draggingState == stateId) return; m_draggingState = stateId; if (m_draggingState == DraggingFloatingWidget) qApp->postEvent(q, new QEvent((QEvent::Type) internal::g_floatingWidgetDragStartEvent)); } void setWindowTitle(const QString &text) { #if defined(Q_OS_UNIX) && !defined(Q_OS_MACOS) if (m_titleBar) m_titleBar->setTitle(text); #else q->setWindowTitle(text); #endif } /** * Reflect the current dock widget title in the floating widget windowTitle() * depending on the DockManager::FloatingContainerHasWidgetTitle flag. */ void reflectCurrentWidget(DockWidget *currentWidget) { // Reflect currentWidget's title if configured to do so, otherwise display application name as window title if (testConfigFlag(DockManager::FloatingContainerHasWidgetTitle)) setWindowTitle(currentWidget->windowTitle()); else setWindowTitle(floatingContainersTitle()); // Reflect currentWidget's icon if configured to do so, otherwise display application icon as window icon QIcon currentWidgetIcon = currentWidget->icon(); if (testConfigFlag(DockManager::FloatingContainerHasWidgetIcon) && !currentWidgetIcon.isNull()) q->setWindowIcon(currentWidget->icon()); else q->setWindowIcon(QApplication::windowIcon()); } /** * Handles escape key press when dragging around the floating widget. */ void handleEscapeKey(); /** * Returns the title used by all FloatingContainer that do not reflect the title of the * current dock widget. * * If no title was set with DockManager::setFloatingContainersTitle(), * it returns QGuiApplication::applicationDisplayName(). */ static QString floatingContainersTitle() { return DockManager::floatingContainersTitle(); } }; // class FloatingDockContainerPrivate FloatingDockContainerPrivate::FloatingDockContainerPrivate(FloatingDockContainer *parent) : q(parent) {} void FloatingDockContainerPrivate::titleMouseReleaseEvent() { setState(DraggingInactive); if (!m_dropContainer) return; if (m_dockManager->dockAreaOverlay()->dropAreaUnderCursor() != InvalidDockWidgetArea || m_dockManager->containerOverlay()->dropAreaUnderCursor() != InvalidDockWidgetArea) { DockOverlay *overlay = m_dockManager->containerOverlay(); if (!overlay->dropOverlayRect().isValid()) overlay = m_dockManager->dockAreaOverlay(); // Do not resize if we drop into an autohide sidebar area to preserve the dock area size // for the initial size of the auto hide area. if (!internal::isSideBarArea(overlay->dropAreaUnderCursor())) { // Resize the floating widget to the size of the highlighted drop area rectangle QRect rect = overlay->dropOverlayRect(); int frameWidth = (q->frameSize().width() - q->rect().width()) / 2; int titleBarHeight = q->frameSize().height() - q->rect().height() - frameWidth; if (rect.isValid()) { QPoint topLeft = overlay->mapToGlobal(rect.topLeft()); topLeft.ry() += titleBarHeight; q->setGeometry(QRect(topLeft, QSize(rect.width(), rect.height() - titleBarHeight))); QApplication::processEvents(); } } m_dropContainer->dropFloatingWidget(q, QCursor::pos()); } m_dockManager->containerOverlay()->hideOverlay(); m_dockManager->dockAreaOverlay()->hideOverlay(); } void FloatingDockContainerPrivate::updateDropOverlays(const QPoint &globalPosition) { if (!q->isVisible() || !m_dockManager) return; #if defined(Q_OS_UNIX) && !defined(Q_OS_MACOS) // Prevent display of drop overlays and docking as long as a modal dialog is active if (qApp->activeModalWidget()) return; #endif auto containers = m_dockManager->dockContainers(); DockContainerWidget *topContainer = nullptr; for (auto containerWidget : containers) { if (!containerWidget->isVisible()) continue; if (m_dockContainer == containerWidget) continue; QPoint mappedPosition = containerWidget->mapFromGlobal(globalPosition); if (containerWidget->rect().contains(mappedPosition)) { if (!topContainer || containerWidget->isInFrontOf(topContainer)) topContainer = containerWidget; } } m_dropContainer = topContainer; auto containerOverlay = m_dockManager->containerOverlay(); auto dockAreaOverlay = m_dockManager->dockAreaOverlay(); if (!topContainer) { containerOverlay->hideOverlay(); dockAreaOverlay->hideOverlay(); return; } int visibleDockAreas = topContainer->visibleDockAreaCount(); DockWidgetAreas allowedContainerAreas = (visibleDockAreas > 1) ? OuterDockAreas : AllDockAreas; auto dockArea = topContainer->dockAreaAt(globalPosition); // If the dock container contains only one single DockArea, then we need to respect the allowed // areas - only the center area is relevant here because all other allowed areas are from the // container. if (visibleDockAreas == 1 && dockArea) allowedContainerAreas.setFlag(CenterDockWidgetArea, dockArea->allowedAreas().testFlag(CenterDockWidgetArea)); if (m_dockContainer->features().testFlag(DockWidget::DockWidgetPinnable)) allowedContainerAreas |= AutoHideDockAreas; containerOverlay->setAllowedAreas(allowedContainerAreas); DockWidgetArea containerArea = containerOverlay->showOverlay(topContainer); containerOverlay->enableDropPreview(containerArea != InvalidDockWidgetArea); if (dockArea && dockArea->isVisible() && visibleDockAreas > 0) { dockAreaOverlay->enableDropPreview(true); dockAreaOverlay->setAllowedAreas((visibleDockAreas == 1) ? NoDockWidgetArea : dockArea->allowedAreas()); DockWidgetArea area = dockAreaOverlay->showOverlay(dockArea); // A CenterDockWidgetArea for the dockAreaOverlay() indicates that the mouse is in // the title bar. If the ContainerArea is valid then we ignore the dock area of the // dockAreaOverlay() and disable the drop preview. if ((area == CenterDockWidgetArea) && (containerArea != InvalidDockWidgetArea)) { dockAreaOverlay->enableDropPreview(false); containerOverlay->enableDropPreview(true); } else { containerOverlay->enableDropPreview(InvalidDockWidgetArea == area); } } else { dockAreaOverlay->hideOverlay(); } } void FloatingDockContainerPrivate::handleEscapeKey() { qCInfo(adsLog) << Q_FUNC_INFO; setState(DraggingInactive); m_dockManager->containerOverlay()->hideOverlay(); m_dockManager->dockAreaOverlay()->hideOverlay(); } FloatingDockContainer::FloatingDockContainer(DockManager *dockManager) : FloatingWidgetBaseType(dockManager) , d(new FloatingDockContainerPrivate(this)) { d->m_dockManager = dockManager; d->m_dockContainer = new DockContainerWidget(dockManager, this); connect(d->m_dockContainer, &DockContainerWidget::dockAreasAdded, this, &FloatingDockContainer::onDockAreasAddedOrRemoved); connect(d->m_dockContainer, &DockContainerWidget::dockAreasRemoved, this, &FloatingDockContainer::onDockAreasAddedOrRemoved); #if defined(Q_OS_UNIX) && !defined(Q_OS_MACOS) // Order here is really important. setWindowFlags() must come first otherwise the resize handles // on linux are missing from floating dock widgets. setWindowFlags(Qt::Window | Qt::WindowMinMaxButtonsHint | Qt::FramelessWindowHint | Qt::Tool); QDockWidget::setWidget(d->m_dockContainer); QDockWidget::setFeatures(QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable | QDockWidget::DockWidgetFloatable); d->m_titleBar = new FloatingWidgetTitleBar(this); setTitleBarWidget(d->m_titleBar); d->m_titleBar->enableCloseButton(isClosable()); d->m_titleBar->setMaximizedIcon(windowState() == Qt::WindowMaximized); connect(d->m_titleBar, &FloatingWidgetTitleBar::closeRequested, this, &FloatingDockContainer::close); connect(d->m_titleBar, &FloatingWidgetTitleBar::maximizeRequested, this, &FloatingDockContainer::onMaximizeRequest); #else setWindowFlags(Qt::Window | Qt::WindowMaximizeButtonHint | Qt::WindowCloseButtonHint | Qt::Tool); QBoxLayout *boxLayout = new QBoxLayout(QBoxLayout::TopToBottom); boxLayout->setContentsMargins(0, 0, 0, 0); boxLayout->setSpacing(0); setLayout(boxLayout); boxLayout->addWidget(d->m_dockContainer); #endif dockManager->registerFloatingWidget(this); } FloatingDockContainer::FloatingDockContainer(DockAreaWidget *dockArea) : FloatingDockContainer(dockArea->dockManager()) { d->m_dockContainer->addDockArea(dockArea); if (auto dw = topLevelDockWidget()) dw->emitTopLevelChanged(true); d->m_dockManager->notifyWidgetOrAreaRelocation(dockArea); } FloatingDockContainer::FloatingDockContainer(DockWidget *dockWidget) : FloatingDockContainer(dockWidget->dockManager()) { d->m_dockContainer->addDockWidget(CenterDockWidgetArea, dockWidget); if (auto dw = topLevelDockWidget()) dw->emitTopLevelChanged(true); d->m_dockManager->notifyWidgetOrAreaRelocation(dockWidget); } FloatingDockContainer::~FloatingDockContainer() { qCInfo(adsLog) << Q_FUNC_INFO; if (d->m_dockManager) d->m_dockManager->removeFloatingWidget(this); delete d; } QSize FloatingDockContainer::minimumSizeHint() const { return d->m_dockContainer->minimumSizeHint(); } DockContainerWidget *FloatingDockContainer::dockContainer() const { return d->m_dockContainer; } void FloatingDockContainer::changeEvent(QEvent *event) { Super::changeEvent(event); switch (event->type()) { case QEvent::ActivationChange: if (isActiveWindow()) { qCInfo(adsLog) << Q_FUNC_INFO << "Event::ActivationChange"; d->m_zOrderIndex = ++zOrderCounter; #if defined(Q_OS_UNIX) && !defined(Q_OS_MACOS) if (d->m_draggingState == DraggingFloatingWidget) { d->titleMouseReleaseEvent(); d->m_draggingState = DraggingInactive; } #endif } break; case QEvent::WindowStateChange: // If the DockManager window is restored from minimized on Windows then the FloatingWidgets // are not properly restored to maximized but to normal state. // We simply check here, if the FloatingWidget was maximized before and if the DockManager // is just leaving the minimized state. In this case, we restore the maximized state of // this floating widget. if (d->m_dockManager->isLeavingMinimizedState()) { QWindowStateChangeEvent *ev = static_cast(event); if (ev->oldState().testFlag(Qt::WindowMaximized)) showMaximized(); } break; default: break; // do nothing } } #ifdef Q_OS_WIN bool FloatingDockContainer::nativeEvent(const QByteArray &eventType, void *message, qintptr *result) { QWidget::nativeEvent(eventType, message, result); MSG *msg = static_cast(message); switch (msg->message) { case WM_MOVING: { if (d->isState(DraggingFloatingWidget)) d->updateDropOverlays(QCursor::pos()); } break; case WM_NCLBUTTONDOWN: if (msg->wParam == HTCAPTION && d->isState(DraggingInactive)) { qCInfo(adsLog) << Q_FUNC_INFO << "WM_NCLBUTTONDOWN" << eventType; d->m_dragStartPos = pos(); d->setState(DraggingMousePressed); } break; case WM_NCLBUTTONDBLCLK: d->setState(DraggingInactive); break; case WM_ENTERSIZEMOVE: if (d->isState(DraggingMousePressed)) { qCInfo(adsLog) << Q_FUNC_INFO << "WM_ENTERSIZEMOVE" << eventType; d->setState(DraggingFloatingWidget); d->updateDropOverlays(QCursor::pos()); } break; case WM_EXITSIZEMOVE: if (d->isState(DraggingFloatingWidget)) { qCInfo(adsLog) << Q_FUNC_INFO << "WM_EXITSIZEMOVE" << eventType; if (GetAsyncKeyState(VK_ESCAPE) & 0x8000) d->handleEscapeKey(); else d->titleMouseReleaseEvent(); } break; } return false; } #endif void FloatingDockContainer::closeEvent(QCloseEvent *event) { qCInfo(adsLog) << Q_FUNC_INFO << "closable" << isClosable(); d->setState(DraggingInactive); event->ignore(); if (!isClosable()) return; bool hasOpenDockWidgets = false; for (auto dockWidget : d->m_dockContainer->openedDockWidgets()) { if (dockWidget->features().testFlag(DockWidget::DockWidgetDeleteOnClose) || dockWidget->features().testFlag(DockWidget::CustomCloseHandling)) { bool closed = dockWidget->closeDockWidgetInternal(); if (!closed) hasOpenDockWidgets = true; } else { dockWidget->toggleView(false); } } if (hasOpenDockWidgets) return; // In Qt version after 5.9.2 there seems to be a bug that causes the QWidget::event() function // to not receive any NonClientArea mouse events anymore after a close/show cycle. The bug is // reported here: https://bugreports.qt.io/browse/QTBUG-73295 // The following code is a workaround for Qt versions > 5.9.2 that seems to work. Starting from // Qt version 5.12.2 this seems to work again. But now the QEvent::NonClientAreaMouseButtonPress // function returns always Qt::RightButton even if the left button was pressed. hide(); } void FloatingDockContainer::hideEvent(QHideEvent *event) { qCInfo(adsLog) << Q_FUNC_INFO; Super::hideEvent(event); if (event->spontaneous()) return; // Prevent toogleView() events during restore state if (d->m_dockManager->isRestoringState()) return; if (d->m_autoHideChildren) { d->m_hiding = true; for (auto dockArea : d->m_dockContainer->openedDockAreas()) { for (auto dockWidget : dockArea->openedDockWidgets()) dockWidget->toggleView(false); } d->m_hiding = false; } } void FloatingDockContainer::showEvent(QShowEvent *event) { qCInfo(adsLog) << Q_FUNC_INFO; Super::showEvent(event); #if defined(Q_OS_UNIX) && !defined(Q_OS_MACOS) if (DockManager::testConfigFlag(DockManager::FocusHighlighting)) window()->activateWindow(); #endif } void FloatingDockContainer::startFloating(const QPoint &dragStartMousePos, const QSize &size, eDragState dragState, QWidget *mouseEventHandler) { #if defined(Q_OS_UNIX) && !defined(Q_OS_MACOS) if (!isMaximized()) { resize(size); d->m_dragStartMousePosition = dragStartMousePos; } d->setState(dragState); if (dragState == DraggingFloatingWidget) { d->m_mouseEventHandler = mouseEventHandler; if (d->m_mouseEventHandler) d->m_mouseEventHandler->grabMouse(); } if (!isMaximized()) moveFloating(); show(); #else Q_UNUSED(mouseEventHandler) resize(size); d->m_dragStartMousePosition = dragStartMousePos; d->setState(dragState); moveFloating(); show(); #endif } void FloatingDockContainer::moveFloating() { const int borderSize = (frameSize().width() - size().width()) / 2; const QPoint moveToPos = QCursor::pos() - d->m_dragStartMousePosition - QPoint(borderSize, 0); move(moveToPos); switch (d->m_draggingState) { case DraggingMousePressed: d->setState(DraggingFloatingWidget); d->updateDropOverlays(QCursor::pos()); break; case DraggingFloatingWidget: d->updateDropOverlays(QCursor::pos()); // On macOS when hiding the DockAreaOverlay the application would set the main window as // the active window for some reason. This fixes that by resetting the active window to // the floating widget after updating the overlays. if (Utils::HostOsInfo::isMacHost()) activateWindow(); break; default: break; } } bool FloatingDockContainer::isClosable() const { return d->m_dockContainer->features().testFlag(DockWidget::DockWidgetClosable); } void FloatingDockContainer::onDockAreasAddedOrRemoved() { qCInfo(adsLog) << Q_FUNC_INFO; auto topLevelDockArea = d->m_dockContainer->topLevelDockArea(); if (topLevelDockArea) { d->m_singleDockArea = topLevelDockArea; DockWidget *currentWidget = d->m_singleDockArea->currentDockWidget(); d->reflectCurrentWidget(currentWidget); connect(d->m_singleDockArea, &DockAreaWidget::currentChanged, this, &FloatingDockContainer::onDockAreaCurrentChanged); } else { if (d->m_singleDockArea) { disconnect(d->m_singleDockArea, &DockAreaWidget::currentChanged, this, &FloatingDockContainer::onDockAreaCurrentChanged); d->m_singleDockArea = nullptr; } d->setWindowTitle(d->floatingContainersTitle()); setWindowIcon(QApplication::windowIcon()); } } void FloatingDockContainer::updateWindowTitle() { // If this floating container will be hidden, updating the window title is not required anymore. if (d->m_hiding) return; if (auto topLevelDockArea = d->m_dockContainer->topLevelDockArea()) { if (DockWidget *currentWidget = topLevelDockArea->currentDockWidget()) d->reflectCurrentWidget(currentWidget); } else { d->setWindowTitle(QApplication::applicationDisplayName()); setWindowIcon(QApplication::windowIcon()); } } void FloatingDockContainer::onDockAreaCurrentChanged(int index) { Q_UNUSED(index) DockWidget *currentWidget = d->m_singleDockArea->currentDockWidget(); d->reflectCurrentWidget(currentWidget); } bool FloatingDockContainer::restoreState(DockingStateReader &stream, bool testing) { if (!d->m_dockContainer->restoreState(stream, testing)) return false; onDockAreasAddedOrRemoved(); #if defined(Q_OS_UNIX) && !defined(Q_OS_MACOS) if (d->m_titleBar) d->m_titleBar->setMaximizedIcon(windowState() == Qt::WindowMaximized); #endif return true; } bool FloatingDockContainer::hasTopLevelDockWidget() const { return d->m_dockContainer->hasTopLevelDockWidget(); } DockWidget *FloatingDockContainer::topLevelDockWidget() const { return d->m_dockContainer->topLevelDockWidget(); } QList FloatingDockContainer::dockWidgets() const { return d->m_dockContainer->dockWidgets(); } void FloatingDockContainer::hideAndDeleteLater() { // Widget has been redocked, so it must be hidden right way (see // https://github.com/githubuser0xFFFF/Qt-Advanced-Docking-System/issues/351) // but AutoHideChildren must be set to false because "this" still contains // dock widgets that shall not be toggled hidden. d->m_autoHideChildren = false; hide(); deleteLater(); if (d->m_dockManager) { d->m_dockManager->removeFloatingWidget(this); d->m_dockManager->removeDockContainer(dockContainer()); } } void FloatingDockContainer::finishDragging() { qCInfo(adsLog) << Q_FUNC_INFO; #if defined(Q_OS_UNIX) && !defined(Q_OS_MACOS) setWindowOpacity(1); activateWindow(); if (d->m_mouseEventHandler) { d->m_mouseEventHandler->releaseMouse(); d->m_mouseEventHandler = nullptr; } #endif d->titleMouseReleaseEvent(); } #ifdef Q_OS_MACOS bool FloatingDockContainer::event(QEvent *event) { switch (d->m_draggingState) { case DraggingInactive: { // Normally we would check here, if the left mouse button is pressed. But from Qt version // 5.12.2 on the mouse events from QEvent::NonClientAreaMouseButtonPress return the wrong // mouse button. The event always returns Qt::RightButton even if the left button is // clicked. It is really great to work around the whole NonClientMouseArea bugs. if (event->type() == QEvent::NonClientAreaMouseButtonPress /*&& QGuiApplication::mouseButtons().testFlag(Qt::LeftButton)*/) { qCInfo(adsLog) << Q_FUNC_INFO << "QEvent::NonClientAreaMouseButtonPress" << event->type(); d->m_dragStartPos = pos(); d->setState(DraggingMousePressed); } } break; case DraggingMousePressed: switch (event->type()) { case QEvent::NonClientAreaMouseButtonDblClick: qCInfo(adsLog) << Q_FUNC_INFO << "QEvent::NonClientAreaMouseButtonDblClick"; d->setState(DraggingInactive); break; case QEvent::Resize: // If the first event after the mouse press is a resize event, then the user resizes // the window instead of dragging it around. But there is one exception. If the window // is maximized, then dragging the window via title bar will cause the widget to // leave the maximized state. This in turn will trigger a resize event. To know, if the // resize event was triggered by user via moving a corner of the window frame or if it // was caused by a windows state change, we check, if we are not in maximized state. if (!isMaximized()) d->setState(DraggingInactive); break; default: break; } break; case DraggingFloatingWidget: if (event->type() == QEvent::NonClientAreaMouseButtonRelease) { qCInfo(adsLog) << Q_FUNC_INFO << "QEvent::NonClientAreaMouseButtonRelease"; d->titleMouseReleaseEvent(); } break; default: break; } qCInfo(adsLog) << Q_FUNC_INFO << event->type(); return QWidget::event(event); } void FloatingDockContainer::moveEvent(QMoveEvent *event) { QWidget::moveEvent(event); switch (d->m_draggingState) { case DraggingMousePressed: d->setState(DraggingFloatingWidget); d->updateDropOverlays(QCursor::pos()); break; case DraggingFloatingWidget: d->updateDropOverlays(QCursor::pos()); // On macOS when hiding the DockAreaOverlay the application would set the main window as // the active window for some reason. This fixes that by resetting the active window to // the floating widget after updating the overlays. activateWindow(); break; default: break; } } #endif #if defined(Q_OS_UNIX) && !defined(Q_OS_MACOS) void FloatingDockContainer::onMaximizeRequest() { if (windowState() == Qt::WindowMaximized) showNormal(); else showMaximized(); } void FloatingDockContainer::showNormal(bool fixGeometry) { if (windowState() == Qt::WindowMaximized) { QRect oldNormal = normalGeometry(); Super::showNormal(); if (fixGeometry) setGeometry(oldNormal); } if (d->m_titleBar) d->m_titleBar->setMaximizedIcon(false); } void FloatingDockContainer::showMaximized() { Super::showMaximized(); if (d->m_titleBar) d->m_titleBar->setMaximizedIcon(true); } bool FloatingDockContainer::isMaximized() const { return windowState() == Qt::WindowMaximized; } void FloatingDockContainer::resizeEvent(QResizeEvent *event) { d->m_isResizing = true; Super::resizeEvent(event); } void FloatingDockContainer::moveEvent(QMoveEvent *event) { Super::moveEvent(event); if (!d->m_isResizing && event->spontaneous() && d->m_mousePressed) { d->setState(DraggingFloatingWidget); d->updateDropOverlays(QCursor::pos()); } d->m_isResizing = false; } bool FloatingDockContainer::event(QEvent *e) { bool result = Super::event(e); switch (e->type()) { case QEvent::WindowActivate: d->m_mousePressed = false; break; case QEvent::WindowDeactivate: d->m_mousePressed = true; break; default: break; } return result; } bool FloatingDockContainer::hasNativeTitleBar() { return d->m_titleBar == nullptr; } #endif } // namespace ADS