WindowDecorationWrapper.cpp 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839
  1. /*
  2. * Copyright (c) Contributors to the Open 3D Engine Project.
  3. * For complete copyright and license terms please see the LICENSE at the root of this distribution.
  4. *
  5. * SPDX-License-Identifier: Apache-2.0 OR MIT
  6. *
  7. */
  8. #include <AzCore/PlatformIncl.h>
  9. #include <qglobal.h> // For Q_OS_WIN
  10. #include <AzQtComponents/Components/WindowDecorationWrapper.h>
  11. #include <AzQtComponents/Components/DockBarButton.h>
  12. #include <AzQtComponents/Components/StyledDockWidget.h>
  13. #include <AzQtComponents/Components/Titlebar.h>
  14. #include <AzQtComponents/Components/TitleBarOverdrawHandler.h>
  15. #include <AzQtComponents/Utilities/QtWindowUtilities.h>
  16. #include <QTimer>
  17. #include <QPainter>
  18. #include <QDebug>
  19. #include <QApplication>
  20. #include <QSettings>
  21. #include <QWindow>
  22. #include <QScreen>
  23. #include <QCloseEvent>
  24. #include <QDesktopWidget>
  25. #include <QDialog>
  26. #include <QStyleOption>
  27. #include <QScopedValueRollback>
  28. #include <QStyle>
  29. #include <QLayout>
  30. #include <QtGui/private/qhighdpiscaling_p.h>
  31. #ifdef Q_OS_WIN
  32. #endif
  33. namespace AzQtComponents
  34. {
  35. enum
  36. {
  37. FrameWidth = 1, // 1px black line
  38. };
  39. namespace
  40. {
  41. // Restores the window state from QWidget::saveGeometry
  42. // Includes a workaround for restoring the maximization state correctly on Windows
  43. bool RestoreWindowState(QWidget* window, QByteArray geometry)
  44. {
  45. if (geometry.size() < 4 || !window->restoreGeometry(geometry))
  46. {
  47. return false;
  48. }
  49. // Read the geometry based on the Qt format, bailing out if it's invalid
  50. QDataStream stream(geometry);
  51. stream.setVersion(QDataStream::Qt_4_0);
  52. const quint32 magicNumber = 0x1D9D0CB;
  53. quint32 storedMagicNumber;
  54. stream >> storedMagicNumber;
  55. if (storedMagicNumber != magicNumber)
  56. {
  57. return false;
  58. }
  59. const quint16 currentMajorVersion = 2;
  60. quint16 majorVersion = 0;
  61. quint16 minorVersion = 0;
  62. stream >> majorVersion >> minorVersion;
  63. QRect restoredFrameGeometry;
  64. QRect restoredNormalGeometry;
  65. qint32 restoredScreenNumber;
  66. quint8 maximized;
  67. quint8 fullScreen;
  68. qint32 restoredScreenWidth;
  69. stream >> restoredFrameGeometry
  70. >> restoredNormalGeometry
  71. >> restoredScreenNumber
  72. >> maximized
  73. >> fullScreen
  74. >> restoredScreenWidth;
  75. if (!window->isVisible())
  76. {
  77. if (maximized || fullScreen)
  78. {
  79. window->showMaximized();
  80. }
  81. else
  82. {
  83. window->show();
  84. }
  85. }
  86. if (maximized || fullScreen)
  87. {
  88. // Need to separately resize based on the available geometry for
  89. // the screen because since floating windows are frameless, on
  90. // Windows 10 they end up taking up the entire screen when maximized
  91. // instead of respecting the available space (e.g. taskbar)
  92. window->setGeometry(QApplication::desktop()->availableGeometry(window));
  93. }
  94. return true;
  95. }
  96. }
  97. WindowDecorationWrapper::WindowDecorationWrapper(Options options, QWidget* parent)
  98. : QFrame(parent, options & OptionDisabled ? Qt::Window : WindowDecorationWrapper::specialFlagsForOS() | Qt::Window)
  99. , m_options(options)
  100. , m_titleBar(options & OptionDisabled ? nullptr : new TitleBar(this))
  101. {
  102. if (m_titleBar)
  103. {
  104. m_titleBar->setDragEnabled(true);
  105. m_titleBar->setButtons({ DockBarButton::DividerButton, DockBarButton::MinimizeButton,
  106. DockBarButton::DividerButton, DockBarButton::MaximizeButton,
  107. DockBarButton::DividerButton, DockBarButton::CloseButton});
  108. }
  109. adjustTitleBarGeometry();
  110. m_initialized = true;
  111. // Create a QWindow -- windowHandle()
  112. setAttribute(Qt::WA_NativeWindow, true);
  113. TitleBarOverdrawHandler::getInstance()->addTitleBarOverdrawWidget(this);
  114. }
  115. WindowDecorationWrapper::~WindowDecorationWrapper()
  116. {
  117. if (saveRestoreGeometryEnabled())
  118. {
  119. saveGeometryToSettings();
  120. }
  121. }
  122. static bool shouldCenterInParent(const QWidget* w)
  123. {
  124. return (w->windowFlags() & Qt::Dialog) == Qt::Dialog;
  125. }
  126. void WindowDecorationWrapper::setGuest(QWidget* guest)
  127. {
  128. if (m_guestWidget || m_guestWidget == guest || !guest)
  129. {
  130. qDebug() << "WindowDecorationWrapper::setGuest bailing out" << m_guestWidget << guest << this;
  131. return;
  132. }
  133. m_guestWidget = guest;
  134. if (guest->isWindow())
  135. {
  136. if (auto layout = guest->layout())
  137. {
  138. // On windows, by default the layout will set the widget's minimum size to be the layout's
  139. // minimum size. Since guest won't be a window any more, change the resize mode to SetMinimumSize
  140. // so that minimum size of the contents is honored.
  141. layout->setSizeConstraint(QLayout::SetMinimumSize);
  142. }
  143. }
  144. m_shouldCenterInParent = shouldCenterInParent(guest); // Don't move this variable after applyFlagsAndAttributes()
  145. guest->setParent(this);
  146. connect(guest, &QWidget::windowTitleChanged,
  147. this, &WindowDecorationWrapper::onWindowTitleChanged);
  148. // The wrapper is deleted when widget is destroyed
  149. // Connect even if OptionDisable is used otherwise the WindowDecorationWrapper is still
  150. // visible after the guest widget is closed.
  151. connect(guest, &QWidget::destroyed, this, [this]
  152. {
  153. m_guestWidget = nullptr;
  154. // the Open 3D Engine Editor has code that checks for Modal widgets, and blocks on doing other things
  155. // if there are still active Modal dialogs.
  156. // So we need to ensure that this WindowDecorationWrapper doesn't report itself as being modal
  157. // after the guest widget has been deleted.
  158. if (isModal())
  159. {
  160. setWindowModality(Qt::NonModal);
  161. }
  162. deleteLater();
  163. });
  164. if (m_options & OptionDisabled)
  165. {
  166. return;
  167. }
  168. applyFlagsAndAttributes();
  169. guest->installEventFilter(this);
  170. m_titleBar->setWindowTitleOverride(guest->windowTitle());
  171. updateConstraints();
  172. }
  173. QWidget* WindowDecorationWrapper::topLevelParent()
  174. {
  175. // Returns the parent window
  176. QWidget* pw = parentWidget();
  177. if (!pw)
  178. {
  179. return nullptr;
  180. }
  181. if (pw->window() == window())
  182. {
  183. // Just a sanity check that probably doesn't happen.
  184. // We're only interested in the case where there's an actual parent window
  185. return nullptr;
  186. }
  187. return pw->window();
  188. }
  189. static QPoint screenCenter(QWidget* w)
  190. {
  191. return w->screen()->availableGeometry().center();
  192. }
  193. void WindowDecorationWrapper::centerInParent()
  194. {
  195. if (QWidget* topLevelWidget = topLevelParent())
  196. {
  197. QPoint parentWindowCenter = topLevelWidget->mapToGlobal(topLevelWidget->rect().center());
  198. if (topLevelWidget->isMaximized())
  199. {
  200. // During startup if the parent window is maximized it can happen that it still
  201. // doesn't have the correct geometry, because it's being restored. This only affects
  202. // maximized windows and is easy to fix:
  203. parentWindowCenter = screenCenter(topLevelWidget);
  204. }
  205. // Center within the parent
  206. QRect geo = geometry();
  207. geo.moveCenter(parentWindowCenter);
  208. QWindow *w = topLevelWidget->windowHandle();
  209. if (!w)
  210. {
  211. return;
  212. }
  213. QScreen *screen = w->screen();
  214. if (!screen)
  215. {
  216. // defensive, shouldn't happen
  217. return;
  218. }
  219. const QRect screenGeometry = screen->availableGeometry();
  220. const bool crossesScreenBoundaries = geo.intersected(screenGeometry) != geo;
  221. if (crossesScreenBoundaries)
  222. {
  223. // We don't want the window half-hidden, or even worse like having it's title-bar hidden.
  224. // Just center in the middle of the screen
  225. geo.moveCenter(screenCenter(topLevelWidget));
  226. }
  227. setGeometry(geo);
  228. }
  229. }
  230. QWidget* WindowDecorationWrapper::guest() const
  231. {
  232. return m_guestWidget;
  233. }
  234. bool WindowDecorationWrapper::isAttached() const
  235. {
  236. return m_guestWidget;
  237. }
  238. TitleBar* WindowDecorationWrapper::titleBar() const
  239. {
  240. return m_titleBar;
  241. }
  242. void WindowDecorationWrapper::enableSaveRestoreGeometry(const QString& organization, const QString& app, const QString& key, bool autoRestoreOnShow)
  243. {
  244. enableSaveRestoreGeometry(new QSettings(organization, app, this), key, autoRestoreOnShow);
  245. }
  246. void WindowDecorationWrapper::enableSaveRestoreGeometry(const QString& key, bool autoRestoreOnShow)
  247. {
  248. enableSaveRestoreGeometry(new QSettings(this), key, autoRestoreOnShow);
  249. }
  250. void WindowDecorationWrapper::enableSaveRestoreGeometry(QSettings* settings, const QString& key, bool autoRestoreOnShow)
  251. {
  252. if (m_settings)
  253. {
  254. qWarning() << Q_FUNC_INFO << "Save/restore already enabled";
  255. return;
  256. }
  257. if (key.isEmpty())
  258. {
  259. qWarning() << Q_FUNC_INFO << "Invalid parameters";
  260. return;
  261. }
  262. m_settings = settings;
  263. m_settingsKey = key;
  264. m_autoRestoreOnShow = autoRestoreOnShow;
  265. m_blockForRestoreOnShow = autoRestoreOnShow;
  266. connect(qApp, &QCoreApplication::aboutToQuit, this, &WindowDecorationWrapper::saveGeometryToSettings);
  267. connect(qApp, &QGuiApplication::applicationStateChanged, this, &WindowDecorationWrapper::saveGeometryToSettings);
  268. }
  269. bool WindowDecorationWrapper::saveRestoreGeometryEnabled() const
  270. {
  271. return m_settings != nullptr;
  272. }
  273. bool WindowDecorationWrapper::eventFilter(QObject* watched, QEvent* ev)
  274. {
  275. if (watched != m_guestWidget)
  276. {
  277. return QFrame::eventFilter(watched, ev);
  278. }
  279. if (ev->type() == QEvent::HideToParent)
  280. {
  281. hide();
  282. }
  283. else if (ev->type() == QEvent::Resize && !m_restoringGeometry)
  284. {
  285. // if WA_Resized is set it means we already explicitly resized the wrapper, so that should
  286. // take precedence
  287. updateConstraints(); // Better way to detect size constraints changed ?
  288. adjustWrapperGeometry();
  289. adjustTitleBarGeometry();
  290. }
  291. else if (ev->type() == QEvent::ShowToParent || ev->type() == QEvent::Show)
  292. {
  293. applyFlagsAndAttributes();
  294. if (m_shouldCenterInParent)
  295. {
  296. centerInParent();
  297. }
  298. if (!m_restoringGeometry)
  299. {
  300. const QSize guestMinSize = m_guestWidget->minimumSize();
  301. show();
  302. if (!guestMinSize.isNull())
  303. {
  304. // If the widget is not a window then it's layout will remove the size constraints
  305. // So save and restore constraints after showing
  306. m_guestWidget->setMinimumSize(guestMinSize);
  307. }
  308. }
  309. // Titlebar may have a native window that could have received hide() (due to e.g. Alt+F4).
  310. // Because of that it won't be shown automatically when its parent, wrapper, is shown.
  311. // Make sure, that it's indeed shown.
  312. if (m_titleBar)
  313. {
  314. m_titleBar->show();
  315. }
  316. }
  317. else if (ev->type() == QEvent::LayoutRequest)
  318. {
  319. updateConstraints();
  320. }
  321. return QFrame::eventFilter(watched, ev);
  322. }
  323. void WindowDecorationWrapper::resizeEvent(QResizeEvent* ev)
  324. {
  325. Q_UNUSED(ev);
  326. // qDebug() << "WindowDecorationWrapper::resizeEvent old=" << ev->oldSize() << ";new=" << ev->size() << this;
  327. adjustTitleBarGeometry();
  328. if (m_guestWidget && !m_guestWidget->testAttribute(Qt::WA_PendingResizeEvent))
  329. {
  330. adjustWidgetGeometry();
  331. }
  332. }
  333. void WindowDecorationWrapper::childEvent(QChildEvent* ev)
  334. {
  335. if (!m_initialized || ev->type() != QEvent::ChildAdded || !autoAttachEnabled() || isAttached())
  336. {
  337. return;
  338. }
  339. if (!ev->child()->isWidgetType())
  340. {
  341. return;
  342. }
  343. QWidget* w = static_cast<QWidget*>(ev->child());
  344. if (w == m_titleBar)
  345. {
  346. return;
  347. }
  348. #if AZ_TRAIT_OS_PLATFORM_APPLE
  349. // On macOS, tool windows correspond to the Floating class of windows. This means that the
  350. // window lives on a level above normal windows making it impossible to put a normal window
  351. // on top of it. Therefore we need to add Qt::Tool to QDialogs to ensure they are not hidden
  352. // under a Floating window.
  353. // qobject_cast in QObject::childEvent is not ideal because the child object may not have
  354. // been constructed yet. To be on the safe side, check the windowFlags too.
  355. if ((qobject_cast<QDialog*>(w) != nullptr) || (w->windowFlags() & Qt::Dialog))
  356. {
  357. setWindowFlags(windowFlags() | Qt::Tool);
  358. }
  359. #endif
  360. setGuest(w);
  361. }
  362. void WindowDecorationWrapper::closeEvent(QCloseEvent* ev)
  363. {
  364. saveGeometryToSettings();
  365. if (m_guestWidget)
  366. {
  367. QApplication::sendEvent(m_guestWidget, ev);
  368. }
  369. }
  370. void WindowDecorationWrapper::hideEvent(QHideEvent* ev)
  371. {
  372. saveGeometryToSettings();
  373. QFrame::hideEvent(ev);
  374. }
  375. static void centerOnScreen(WindowDecorationWrapper* window)
  376. {
  377. const QDesktopWidget* desktop = QApplication::desktop();
  378. QRect availableGeometry = desktop->availableGeometry(window);
  379. QRect alignedRect = QStyle::alignedRect(Qt::LeftToRight, Qt::AlignCenter, window->size(), availableGeometry);
  380. window->setGeometry(alignedRect);
  381. }
  382. void WindowDecorationWrapper::showEvent(QShowEvent* ev)
  383. {
  384. if (m_autoRestoreOnShow && (m_settings != nullptr))
  385. {
  386. // use a timer to trigger this as soon as possible, but not now.
  387. // We're in the middle of the show right now and restoreGeometryFromSettings
  388. // can call show. No recursion shenanigans.
  389. QTimer::singleShot(0, this, [this] {
  390. if (!restoreGeometryFromSettings())
  391. {
  392. // default to centering it on screen if the restore failed
  393. centerOnScreen(this);
  394. }
  395. m_blockForRestoreOnShow = false;
  396. });
  397. // reset this so that we don't do this again
  398. m_autoRestoreOnShow = false;
  399. }
  400. QFrame::showEvent(ev);
  401. }
  402. bool WindowDecorationWrapper::nativeEvent(const QByteArray& eventType, void* message, long* result)
  403. {
  404. return handleNativeEvent(eventType, message, result, this);
  405. }
  406. void WindowDecorationWrapper::changeEvent(QEvent* ev)
  407. {
  408. if (ev->type() == QEvent::WindowStateChange)
  409. {
  410. // only way to know when the window has minimized/maximized or full screen has changed
  411. saveGeometryToSettings();
  412. }
  413. QFrame::changeEvent(ev);
  414. }
  415. /* static */
  416. bool WindowDecorationWrapper::handleNativeEvent(const QByteArray& eventType, void* message, long* result, const QWidget* widget)
  417. {
  418. #ifdef Q_OS_WIN
  419. MSG* msg = static_cast<MSG*>(message);
  420. if (strcmp(eventType.constData(), "windows_generic_MSG") != 0)
  421. {
  422. return false;
  423. }
  424. if (isWin10())
  425. {
  426. if (widget->window() && msg->message == WM_NCHITTEST && GetAsyncKeyState(VK_RBUTTON) >= 0) // We're not interested in right click
  427. {
  428. /**
  429. * This code block enables Windows native dragging, which enables the "Aero Snap" feature,
  430. * where we can snap our windows to the sides of the screen.
  431. */
  432. HWND handle = (HWND)widget->window()->winId();
  433. const LRESULT defWinProcResult = DefWindowProc(handle, msg->message, msg->wParam, msg->lParam);
  434. if (defWinProcResult == 1)
  435. {
  436. if (auto wrapper = qobject_cast<const WindowDecorationWrapper *>(widget))
  437. {
  438. /**
  439. * We only care about the title bars belonging to WindowDecorationWrapper.
  440. * The ones from StyledDockWidget::titleBar() must use our custom dragging, so the docking system works,
  441. * we can't use the native dragging and we can't have "Aero Snap" for dock widgets.
  442. */
  443. TitleBar* titleBar = wrapper->titleBar();
  444. const short global_x = static_cast<short>(LOWORD(msg->lParam));
  445. const short global_y = static_cast<short>(HIWORD(msg->lParam));
  446. const QPoint globalPos = QHighDpi::fromNativePixels(QPoint(global_x, global_y), widget->window()->windowHandle());
  447. const QPoint local = titleBar->mapFromGlobal(globalPos);
  448. if (titleBar->draggableRect().contains(local))
  449. {
  450. if (titleBar->isTopResizeArea(globalPos))
  451. {
  452. *result = HTTOP;
  453. }
  454. else
  455. {
  456. *result = HTCAPTION;
  457. }
  458. return true;
  459. }
  460. }
  461. }
  462. }
  463. }
  464. else
  465. {
  466. // No other event to process for win10()
  467. // for Win10 we have the native title-bar, so maximized geometry is calculated correctly out of the box
  468. return false;
  469. }
  470. if (msg->message == WM_GETMINMAXINFO && widget->isMaximized())
  471. {
  472. // When Windows maximizes a window without native titlebar it will cover the taskbar.
  473. // Qt is patched to catch this case, but only for Qt::FramelessWindowHint cases, which
  474. // we're not using, so calculate the size ourselves.
  475. QWindow* w = widget->windowHandle();
  476. if (!w || !w->screen() || w->screen() != QApplication::primaryScreen())
  477. {
  478. // WM_GETMINMAXINFO only works for the primary screen
  479. return false;
  480. }
  481. // Get the sizes Windows would have chosen
  482. DefWindowProc(msg->hwnd, msg->message, msg->wParam, msg->lParam);
  483. const QScreen *screen = w->screen();
  484. const QRect availableGeometry = QHighDpi::toNativePixels(screen->availableGeometry(), screen);
  485. auto mmi = reinterpret_cast<MINMAXINFO*>(msg->lParam);
  486. mmi->ptMaxSize.y = availableGeometry.height();
  487. mmi->ptMaxSize.x = availableGeometry.width();
  488. mmi->ptMaxPosition.y = availableGeometry.y();
  489. mmi->ptMaxPosition.x = availableGeometry.x();
  490. *result = 0;
  491. return true;
  492. }
  493. #else
  494. Q_UNUSED(eventType)
  495. Q_UNUSED(message)
  496. Q_UNUSED(result)
  497. Q_UNUSED(widget)
  498. #endif
  499. return false;
  500. }
  501. QMargins WindowDecorationWrapper::margins() const
  502. {
  503. if (m_titleBar)
  504. return QMargins(FrameWidth, m_titleBar->height(), FrameWidth, FrameWidth);
  505. else
  506. return QMargins(0, 0, 0, 0);
  507. }
  508. bool WindowDecorationWrapper::autoAttachEnabled() const
  509. {
  510. return m_options & OptionAutoAttach;
  511. }
  512. bool WindowDecorationWrapper::autoTitleBarButtonsEnabled() const
  513. {
  514. return m_options & OptionAutoTitleBarButtons;
  515. }
  516. QSize WindowDecorationWrapper::nonContentsSize() const
  517. {
  518. // The size of the wrapper is always m_guestWidget->size() + nonContentsSize()
  519. // This returns the non-widget size, left and right frame, bottom frame, and titlebar height
  520. const QMargins m = margins();
  521. return QSize(m.left() + m.right(), m.top() + m.bottom());
  522. }
  523. void WindowDecorationWrapper::saveGeometryToSettings()
  524. {
  525. if (!m_settings)
  526. {
  527. return;
  528. }
  529. if (m_blockForRestoreOnShow)
  530. {
  531. // if m_blockForRestoreOnShow is set, it means that we haven't loaded
  532. // settings from show yet. If we save the geometry settings now, it will
  533. // overwrite the settings that were previously saved and haven't been restored yet.
  534. return;
  535. }
  536. QByteArray geo = saveGeometry();
  537. m_settings->setValue(m_settingsKey, geo);
  538. }
  539. bool WindowDecorationWrapper::restoreGeometryFromSettings()
  540. {
  541. if (!m_settings || m_restoringGeometry)
  542. {
  543. return false;
  544. }
  545. const QByteArray savedGeometry = m_settings->value(m_settingsKey).toByteArray();
  546. QScopedValueRollback<bool> rollback(m_restoringGeometry);
  547. m_restoringGeometry = true;
  548. if (!RestoreWindowState(this, savedGeometry))
  549. {
  550. return false;
  551. }
  552. // We don't currently support fullscreen mode, so remove that window state when we are restoring
  553. // in case it was set inadvertently
  554. setWindowState(windowState() & ~Qt::WindowFullScreen);
  555. adjustWidgetGeometry();
  556. m_restoringGeometry = false;
  557. return true;
  558. }
  559. void WindowDecorationWrapper::showFromSettings()
  560. {
  561. if (!restoreGeometryFromSettings())
  562. {
  563. show();
  564. // If we failed to restore from settings (the first time this window is loaded),
  565. // then center it on the screen by default
  566. centerOnScreen(this);
  567. }
  568. }
  569. void WindowDecorationWrapper::applyFlagsAndAttributes()
  570. {
  571. if (m_titleBar == nullptr)
  572. return;
  573. // Remove Qt::Window, if it's present.
  574. m_guestWidget->setWindowFlags(m_guestWidget->windowFlags() & ~Qt::Window);
  575. // Copy other relevant flags
  576. const QList<Qt::WindowFlags> flags = { Qt::WindowStaysOnTopHint };
  577. for (auto flag : flags)
  578. {
  579. if (m_guestWidget->windowFlags() & flag)
  580. {
  581. setWindowFlags(windowFlags() | flag);
  582. }
  583. }
  584. // Copy relevant attributes from widget
  585. const QList<Qt::WidgetAttribute> attrs = {
  586. Qt::WA_DeleteOnClose,
  587. Qt::WA_QuitOnClose,
  588. Qt::WA_ShowModal,
  589. Qt::WA_Hover
  590. };
  591. for (auto attr : attrs)
  592. {
  593. setAttribute(attr, m_guestWidget->testAttribute(attr));
  594. }
  595. updateTitleBarButtons();
  596. }
  597. bool WindowDecorationWrapper::canResize() const
  598. {
  599. return (canResizeHeight() || canResizeWidth()) && !isMaximized();
  600. }
  601. bool WindowDecorationWrapper::canResizeHeight() const
  602. {
  603. return m_guestWidget && m_guestWidget->minimumHeight() < m_guestWidget->maximumHeight();
  604. }
  605. bool WindowDecorationWrapper::canResizeWidth() const
  606. {
  607. return m_guestWidget && m_guestWidget->minimumWidth() < m_guestWidget->maximumWidth();
  608. }
  609. void WindowDecorationWrapper::onWindowTitleChanged(const QString& title)
  610. {
  611. if (m_titleBar)
  612. m_titleBar->setWindowTitleOverride(title);
  613. else
  614. setWindowTitle(title);
  615. }
  616. void WindowDecorationWrapper::adjustTitleBarGeometry()
  617. {
  618. if (m_titleBar)
  619. m_titleBar->resize(width(), m_titleBar->height());
  620. }
  621. void WindowDecorationWrapper::adjustWrapperGeometry()
  622. {
  623. if (m_guestWidget && m_titleBar)
  624. {
  625. const QMargins m = margins();
  626. const QSize nonContentsSz = nonContentsSize();
  627. const QSize contentsSz = m_guestWidget->size();
  628. resize(nonContentsSz + contentsSz);
  629. m_guestWidget->move(m.left(), m.top());
  630. }
  631. }
  632. void WindowDecorationWrapper::adjustWidgetGeometry()
  633. { // Called when resizing the wrapper manually
  634. if (m_guestWidget)
  635. {
  636. const QMargins m = margins();
  637. const QSize newSize = size() - nonContentsSize();
  638. const QPoint topLeft = QPoint(m.left(), m.top());
  639. m_guestWidget->setGeometry(QRect(topLeft, newSize));
  640. }
  641. }
  642. void WindowDecorationWrapper::updateConstraints()
  643. {
  644. // Copy size constraints from the widget
  645. if (!m_guestWidget)
  646. {
  647. return;
  648. }
  649. const QSize nonContentsSize = this->nonContentsSize();
  650. const int nonContentsWidth = nonContentsSize.width();
  651. const int nonContentsHeight = nonContentsSize.height();
  652. QSize guestMinSize = m_guestWidget->minimumSize();
  653. guestMinSize.setWidth(qMax(guestMinSize.width(), 5));
  654. guestMinSize.setHeight(qMax(guestMinSize.height(), 5));
  655. setMinimumSize(guestMinSize + nonContentsSize);
  656. setMaximumHeight(qMin(m_guestWidget->maximumHeight() + nonContentsHeight, QWIDGETSIZE_MAX));
  657. setMaximumWidth(qMin(m_guestWidget->maximumWidth() + nonContentsWidth, QWIDGETSIZE_MAX));
  658. updateTitleBarButtons();
  659. }
  660. void WindowDecorationWrapper::updateTitleBarButtons()
  661. {
  662. if (!autoTitleBarButtonsEnabled() || !isAttached() || m_titleBar == nullptr)
  663. {
  664. return;
  665. }
  666. TitleBar::WindowDecorationButtons buttons;
  667. const auto flags = m_guestWidget->windowFlags();
  668. if (flags & Qt::WindowMinimizeButtonHint)
  669. {
  670. buttons.append(DockBarButton::MinimizeButton);
  671. }
  672. if ((canResize() || isMaximized()) && (flags & Qt::WindowMaximizeButtonHint))
  673. {
  674. buttons.append(DockBarButton::MaximizeButton);
  675. }
  676. // We could also honor WindowCloseButtonHint, but there's no good reason, for now.
  677. buttons.append(DockBarButton::CloseButton);
  678. m_titleBar->setButtons(buttons);
  679. }
  680. /** static */
  681. QMargins WindowDecorationWrapper::win10TitlebarHeight(QWindow* w)
  682. {
  683. qDebug() << w->geometry() << w->frameGeometry() << w->frameMargins();
  684. return QMargins(0, -w->frameMargins().top(), 0, 0);
  685. }
  686. Qt::WindowFlags WindowDecorationWrapper::specialFlagsForOS()
  687. {
  688. // Qt::CustomizeWindowHint means native frame but no native titlebar
  689. // For Win 10 we have the native titlebar but we draw on top of it, otherwise QTBUG-47543
  690. return isWin10() ? Qt::WindowFlags() : Qt::CustomizeWindowHint;
  691. }
  692. void WindowDecorationWrapper::drawFrame(const QStyleOption *option, QPainter *painter, const QWidget *widget)
  693. {
  694. Q_UNUSED(widget);
  695. painter->save();
  696. painter->setPen(QColor(33, 34, 35));
  697. painter->drawRect(option->rect.adjusted(0, 0, -1, -1));
  698. painter->restore();
  699. }
  700. bool WindowDecorationWrapper::event(QEvent* ev)
  701. {
  702. // Overridden for debugging purposes
  703. return QFrame::event(ev);
  704. }
  705. } // namespace AzQtComponents
  706. #include "Components/moc_WindowDecorationWrapper.cpp"