QtViewPaneManager.cpp 58 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716
  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 "EditorDefs.h"
  9. #include "QtViewPaneManager.h"
  10. #include "Controls/ConsoleSCB.h"
  11. #include <AzQtComponents/Components/FancyDocking.h>
  12. #include <AzQtComponents/Components/Titlebar.h>
  13. #include <AzQtComponents/Components/Widgets/Card.h>
  14. #include <QDockWidget>
  15. #include <QMainWindow>
  16. #include <QDataStream>
  17. #include <QDebug>
  18. #include <QCloseEvent>
  19. #include <QLayout>
  20. #include <QApplication>
  21. #include <QRect>
  22. #include <QDesktopWidget>
  23. #include <QMessageBox>
  24. #include <QRubberBand>
  25. #include <QCursor>
  26. #include <QTimer>
  27. #include <QGraphicsOpacityEffect>
  28. #include "MainWindow.h"
  29. #include <algorithm>
  30. #include <QScopedValueRollback>
  31. #include <AzFramework/API/ApplicationAPI.h>
  32. #include <AzAssetBrowser/AzAssetBrowserWindow.h>
  33. #include <AzToolsFramework/UI/UICore/WidgetHelpers.h>
  34. #include <AzQtComponents/Utilities/AutoSettingsGroup.h>
  35. #include <AzToolsFramework/API/ViewportEditorModeTrackerNotificationBus.h>
  36. #include <AzToolsFramework/UI/Docking/DockWidgetUtils.h>
  37. #include <AzToolsFramework/UI/PropertyEditor/ComponentEditor.hxx>
  38. #include <AzToolsFramework/UI/PropertyEditor/EntityPropertyEditor.hxx>
  39. #include <AzToolsFramework/Viewport/ViewportMessages.h>
  40. #include <AzQtComponents/Buses/ShortcutDispatch.h>
  41. #include <AzQtComponents/Utilities/QtViewPaneEffects.h>
  42. #include <AzQtComponents/Components/StyleManager.h>
  43. #include <AzCore/UserSettings/UserSettingsComponent.h>
  44. // Helper for EditorComponentModeNotifications to be used
  45. // as a member instead of inheriting from EBus directly.
  46. class ViewportEditorModeNotificationsBusImpl
  47. : public AzToolsFramework::ViewportEditorModeNotificationsBus::Handler
  48. {
  49. public:
  50. // Set the function to be called when entering ComponentMode.
  51. void SetEnteredComponentModeFunc(
  52. const AZStd::function<void(const AzToolsFramework::ViewportEditorModesInterface&)>& enteredComponentModeFunc)
  53. {
  54. m_enteredComponentModeFunc = enteredComponentModeFunc;
  55. }
  56. // Set the function to be called when leaving ComponentMode.
  57. void SetLeftComponentModeFunc(
  58. const AZStd::function<void(const AzToolsFramework::ViewportEditorModesInterface&)>& leftComponentModeFunc)
  59. {
  60. m_leftComponentModeFunc = leftComponentModeFunc;
  61. }
  62. private:
  63. // ViewportEditorModeNotificationsBus overrides ...
  64. void OnEditorModeActivated(
  65. const AzToolsFramework::ViewportEditorModesInterface& editorModeState, AzToolsFramework::ViewportEditorMode mode) override
  66. {
  67. if (mode == AzToolsFramework::ViewportEditorMode::Component)
  68. {
  69. m_enteredComponentModeFunc(editorModeState);
  70. }
  71. }
  72. void OnEditorModeDeactivated(
  73. const AzToolsFramework::ViewportEditorModesInterface& editorModeState, AzToolsFramework::ViewportEditorMode mode) override
  74. {
  75. if (mode == AzToolsFramework::ViewportEditorMode::Component)
  76. {
  77. m_leftComponentModeFunc(editorModeState);
  78. }
  79. }
  80. AZStd::function<void(const AzToolsFramework::ViewportEditorModesInterface&)> m_enteredComponentModeFunc; ///< Function to call when entering ComponentMode.
  81. AZStd::function<void(const AzToolsFramework::ViewportEditorModesInterface&)> m_leftComponentModeFunc; ///< Function to call when leaving ComponentMode.
  82. };
  83. struct ViewLayoutState
  84. {
  85. QVector<QString> viewPanes;
  86. QByteArray mainWindowState;
  87. QMap<QString, QRect> fakeDockWidgetGeometries;
  88. };
  89. Q_DECLARE_METATYPE(ViewLayoutState)
  90. static QDataStream &operator<<(QDataStream & out, const ViewLayoutState&myObj)
  91. {
  92. int placeHolderVersion = 1;
  93. out << myObj.viewPanes << myObj.mainWindowState << placeHolderVersion << myObj.fakeDockWidgetGeometries;
  94. return out;
  95. }
  96. static QDataStream& operator>>(QDataStream& in, ViewLayoutState& myObj)
  97. {
  98. in >> myObj.viewPanes;
  99. in >> myObj.mainWindowState;
  100. int version = 0;
  101. if (!in.atEnd())
  102. {
  103. in >> version;
  104. in >> myObj.fakeDockWidgetGeometries;
  105. }
  106. return in;
  107. }
  108. // All settings keys for stored layouts are in the form "layouts/<name>"
  109. // When starting up, "layouts/last" is loaded
  110. static QLatin1String s_lastLayoutName = QLatin1String("last");
  111. static QString GetFancyViewPaneStateGroupName()
  112. {
  113. return QString("%1/%2").arg("Editor").arg("fancyWindowLayouts");
  114. }
  115. #if AZ_TRAIT_OS_PLATFORM_APPLE
  116. // this event filter class eats mouse events
  117. // it is used in the non dockable fake dock widget
  118. // to make sure its inner title bar cannot be dragged
  119. class MouseEatingEventFilter : public QObject
  120. {
  121. public:
  122. MouseEatingEventFilter(QObject* parent)
  123. : QObject(parent)
  124. {
  125. }
  126. protected:
  127. bool eventFilter(QObject*, QEvent* event) override
  128. {
  129. switch (event->type())
  130. {
  131. case QEvent::MouseButtonPress:
  132. case QEvent::MouseButtonRelease:
  133. case QEvent::MouseButtonDblClick:
  134. case QEvent::MouseMove:
  135. return true;
  136. default:
  137. return false;
  138. }
  139. }
  140. };
  141. #endif
  142. Q_GLOBAL_STATIC(QtViewPaneManager, s_viewPaneManagerInstance)
  143. QWidget* QtViewPane::CreateWidget()
  144. {
  145. QWidget* w = nullptr;
  146. if (m_factoryFunc)
  147. {
  148. // Although all the factory lambdas do have a default nullptr argument, this information
  149. // doesn't get retained when they are converted to std::function<QWidget*(QWidget*)>,
  150. // thus we need to set the parent explicitly.
  151. // At the same time, adding a default argument to the lambdas will allow for them to be
  152. // called exactly as before in all other places where they are not converted, so we get
  153. // to explicitly pass an argument only if strictly necessary.
  154. w = m_factoryFunc(nullptr);
  155. }
  156. else
  157. {
  158. // If a view pane was registered using RegisterCustomViewPane, then instead of a factory function, we rely
  159. // on ViewPaneCallbackBus::CreateViewPaneWidget to create the widget for us and then pass back the Qt windowId
  160. // so that we can retrieve it.
  161. AZ::u64 createdWidgetWinId;
  162. AzToolsFramework::ViewPaneCallbackBus::EventResult(createdWidgetWinId, m_name.toUtf8().constData(), &AzToolsFramework::ViewPaneCallbacks::CreateViewPaneWidget);
  163. w = QWidget::find(createdWidgetWinId);
  164. }
  165. return w;
  166. }
  167. bool QtViewPane::Close(QtViewPane::CloseModes closeModes)
  168. {
  169. if (!IsConstructed())
  170. {
  171. return true;
  172. }
  173. return CloseInstance(m_dockWidget, closeModes);
  174. }
  175. bool QtViewPane::CloseInstance(QDockWidget* dockWidget, CloseModes closeModes)
  176. {
  177. if (!dockWidget)
  178. {
  179. return false;
  180. }
  181. bool canClose = true;
  182. bool destroy = closeModes & CloseMode::Destroy;
  183. // Console is not deletable, so always hide it instead of destroying
  184. if (!m_options.isDeletable)
  185. {
  186. destroy = false;
  187. }
  188. if (!(closeModes & CloseMode::Force))
  189. {
  190. QCloseEvent closeEvent;
  191. // Prevent closing view pane if it has modal dialog open, as modal dialogs
  192. // are often constructed on stack and will not finish properly when the view
  193. // pane is destroyed.
  194. QWidgetList topLevelWidgets = QApplication::topLevelWidgets();
  195. const int numTopLevel = topLevelWidgets.size();
  196. for (size_t i = 0; i < numTopLevel; ++i)
  197. {
  198. QWidget* widget = topLevelWidgets[static_cast<int>(i)];
  199. if (widget->isModal() && widget->isVisible())
  200. {
  201. widget->activateWindow();
  202. return false;
  203. }
  204. }
  205. // Check if embedded QWidget allows view pane to be closed.
  206. QCoreApplication::sendEvent(dockWidget->widget(), &closeEvent);
  207. // If widget accepted the close event, we delete the dockwidget, which will also delete the child widget in case it doesn't have Qt::WA_DeleteOnClose
  208. if (!closeEvent.isAccepted())
  209. {
  210. // Widget doesn't want to close
  211. canClose = false;
  212. }
  213. }
  214. if (canClose)
  215. {
  216. if (destroy)
  217. {
  218. //important to set parent to null otherwise docking code will still find it while restoring since that happens before the delete.
  219. dockWidget->setParent(nullptr);
  220. dockWidget->deleteLater();
  221. if (dockWidget == m_dockWidget)
  222. {
  223. //clear dockwidget pointer otherwise if we open this pane before the delete happens we'll think it's already there, then it gets deleted on us.
  224. m_dockWidget.clear();
  225. }
  226. }
  227. else
  228. {
  229. // If the dock widget is tabbed, then just remove it from the tab widget
  230. AzQtComponents::DockTabWidget* tabWidget = AzQtComponents::DockTabWidget::ParentTabWidget(dockWidget);
  231. if (tabWidget)
  232. {
  233. tabWidget->removeTab(dockWidget);
  234. }
  235. // Otherwise just hide the widget
  236. else
  237. {
  238. dockWidget->hide();
  239. }
  240. }
  241. AzToolsFramework::EditorEventsBus::Broadcast(&AzToolsFramework::EditorEventsBus::Handler::OnViewPaneClosed, m_name.toUtf8().data());
  242. }
  243. return canClose;
  244. }
  245. static bool SkipTitleBarOverdraw(QtViewPane* pane)
  246. {
  247. return !pane->m_options.isDockable;
  248. }
  249. DockWidget::DockWidget(QWidget* widget, QtViewPane* pane, [[maybe_unused]] QSettings* settings, QMainWindow* parent, AzQtComponents::FancyDocking* advancedDockManager)
  250. : AzQtComponents::StyledDockWidget(pane->m_name, SkipTitleBarOverdraw(pane),
  251. #if AZ_TRAIT_OS_PLATFORM_APPLE
  252. pane->m_options.detachedWindow ? nullptr : parent)
  253. #else
  254. parent)
  255. #endif
  256. , m_mainWindow(parent)
  257. , m_pane(pane)
  258. , m_advancedDockManager(advancedDockManager)
  259. {
  260. // keyboard shortcuts from any other context shouldn't trigger actions under this dock widget
  261. AzQtComponents::MarkAsShortcutSearchBreak(this);
  262. if (pane->m_options.isDeletable)
  263. {
  264. setAttribute(Qt::WA_DeleteOnClose);
  265. }
  266. QString objectNameForSave = pane->m_options.saveKeyName.length() > 0 ? pane->m_options.saveKeyName : pane->m_name;
  267. setObjectName(objectNameForSave);
  268. setWidget(widget);
  269. setFocusPolicy(Qt::StrongFocus);
  270. setAttribute(Qt::WA_Hover, true);
  271. setMouseTracking(true);
  272. }
  273. bool DockWidget::event(QEvent* qtEvent)
  274. {
  275. // this accounts for a difference in behavior where we want all floating windows to be always parented to the main window instead of to each other, so that
  276. // they don't overlap in odd ways - for example, if you tear off a floating window from another floating window, under Qt's system its technically still a child of that window
  277. // so that window can't ever be placed on top of it. This is not what we want. We want you to be able to then take that window and drag it into this new one.
  278. // (Qt's original behavior is like that so if you double click on a floating widget it docks back into the parent which it came from - we don't use this functionality)
  279. if (qtEvent->type() == QEvent::WindowActivate
  280. #if AZ_TRAIT_OS_PLATFORM_APPLE
  281. && !m_pane->m_options.detachedWindow
  282. #endif
  283. )
  284. {
  285. reparentToMainWindowFix();
  286. }
  287. if (qtEvent->type() == QEvent::Close)
  288. {
  289. // Wait one frame so that the pane's state is propagated correctly.
  290. QTimer::singleShot(0, this, [pane = m_pane]()
  291. {
  292. AzToolsFramework::EditorEventsBus::Broadcast(&AzToolsFramework::EditorEventsBus::Handler::OnViewPaneClosed, pane->m_name.toUtf8().data());
  293. }
  294. );
  295. }
  296. return AzQtComponents::StyledDockWidget::event(qtEvent);
  297. }
  298. void DockWidget::reparentToMainWindowFix()
  299. {
  300. if (!isFloating() || !AzToolsFramework::DockWidgetUtils::isDockWidgetWindowGroup(parentWidget()))
  301. {
  302. return;
  303. }
  304. if (qApp->mouseButtons() & Qt::LeftButton)
  305. {
  306. // We're still dragging, lets try later
  307. QTimer::singleShot(200, this, &DockWidget::reparentToMainWindowFix);
  308. return;
  309. }
  310. // bump it up and to the left by the size of its frame, to account for the reparenting operation;
  311. QPoint framePos = pos();
  312. QPoint contentPos = mapToGlobal(QPoint(0, 0));
  313. move(framePos.x() - (contentPos.x() - framePos.x()), framePos.y() - (contentPos.y() - framePos.y()));
  314. // we have to dock this to the mainwindow, even if we're floating, so that the mainwindow knows about it.
  315. // if the preferred area is valid, use that. Otherwise, arbitrarily toss it in the left.
  316. // This is relevant because it will determine where the widget goes if the title bar is double clicked
  317. // after it's been detached from a QDockWidgetGroupWindow
  318. auto dockArea = (m_pane->m_options.preferedDockingArea != Qt::DockWidgetArea::NoDockWidgetArea) ? m_pane->m_options.preferedDockingArea : Qt::LeftDockWidgetArea;
  319. setParent(m_mainWindow);
  320. m_mainWindow->addDockWidget(dockArea, this);
  321. setFloating(true);
  322. }
  323. QString DockWidget::PaneName() const
  324. {
  325. return m_pane->m_name;
  326. }
  327. void DockWidget::RestoreState(bool forceDefault)
  328. {
  329. #if AZ_TRAIT_OS_PLATFORM_APPLE
  330. if (m_pane->m_options.detachedWindow)
  331. {
  332. if (forceDefault)
  333. {
  334. window()->setGeometry(m_pane->m_options.paneRect);
  335. }
  336. else
  337. {
  338. QRect geometry = QtViewPaneManager::instance()->GetLayout().fakeDockWidgetGeometries[objectName()];
  339. if (!geometry.isValid())
  340. {
  341. geometry = m_pane->m_options.paneRect;
  342. }
  343. window()->setGeometry(geometry);
  344. }
  345. return;
  346. }
  347. #endif
  348. // check if we can get the main window to do all the work for us first
  349. // (which is also the proper way to do this)
  350. if (!forceDefault)
  351. {
  352. // If the advanced docking is enabled, let it try to restore the dock widget
  353. bool restored = false;
  354. if (m_advancedDockManager)
  355. {
  356. restored = m_advancedDockManager->restoreDockWidget(this);
  357. }
  358. // Otherwise, let our main window do it directly
  359. else
  360. {
  361. restored = m_mainWindow->restoreDockWidget(this);
  362. }
  363. if (restored)
  364. {
  365. AzToolsFramework::DockWidgetUtils::correctVisibility(this);
  366. return;
  367. }
  368. }
  369. // can't rely on the main window; fall back to our preferences
  370. auto dockingArea = m_pane->m_options.preferedDockingArea;
  371. auto paneRect = m_pane->m_options.paneRect;
  372. // If we are floating and have multiple instances, try to calculate an appropriate rect from the most recently created, non-docked instance
  373. // make sure the new location would be reasonable on screen - otherwise use default paneRect for new widget positioning
  374. if (dockingArea == Qt::NoDockWidgetArea && m_pane->m_dockWidgetInstances.size() > 1)
  375. {
  376. static const int horizontalCascadeAmount = 20;
  377. static const int verticalCascadeAmount = 20;
  378. static const int lowerScreenEdgeBuffer = 50;
  379. QRect screenRect = QApplication::primaryScreen()->geometry();
  380. int screenHeight = screenRect.height();
  381. int screenWidth = screenRect.width();
  382. for (QList<DockWidget*>::reverse_iterator it = m_pane->m_dockWidgetInstances.rbegin(); it != m_pane->m_dockWidgetInstances.rend(); ++it)
  383. {
  384. DockWidget* dock = *it;
  385. if (dock != this)
  386. {
  387. QMainWindow* mainWindow = qobject_cast<QMainWindow*>(dock->parentWidget());
  388. if (mainWindow && mainWindow->parentWidget())
  389. {
  390. QPoint windowLocation = mainWindow->parentWidget()->mapToGlobal(QPoint(0, 0));
  391. // Only nudge it to the right if we have room to do so
  392. if (windowLocation.x() + horizontalCascadeAmount < screenWidth - paneRect.width())
  393. {
  394. paneRect.moveLeft(windowLocation.x() + horizontalCascadeAmount);
  395. // Keep it from getting too low on the screen
  396. if (windowLocation.y() + verticalCascadeAmount < screenHeight - lowerScreenEdgeBuffer)
  397. {
  398. paneRect.moveTop(windowLocation.y() + verticalCascadeAmount);
  399. }
  400. }
  401. // We found an undocked window, just go ahead and break, if we couldn't adjust because of positioning,
  402. // it will be placed at the default location
  403. break;
  404. }
  405. }
  406. }
  407. }
  408. // make sure we're sized properly before we dock
  409. if (paneRect.isValid())
  410. {
  411. resize(paneRect.size());
  412. }
  413. // check if we should force floating
  414. bool floatWidget = (dockingArea == Qt::NoDockWidgetArea);
  415. // if we're floating, we need to move and resize again, because the act of docking may have moved us
  416. if (floatWidget)
  417. {
  418. // in order for saving and restoring state to work properly in Qt,
  419. // along with docking widgets within other floating widgets, the widget
  420. // must be added at least once to the main window, with a VALID area,
  421. // before we set it to floating.
  422. auto arbitraryDockingArea = Qt::LeftDockWidgetArea;
  423. m_mainWindow->addDockWidget(arbitraryDockingArea, this);
  424. // If we are using the fancy docking, let it handle making the dock
  425. // widget floating, or else the titlebar will be missing, since
  426. // floating widgets are actually contained in a floating main
  427. // window container
  428. if (m_advancedDockManager)
  429. {
  430. m_advancedDockManager->makeDockWidgetFloating(this, paneRect);
  431. }
  432. // Otherwise, we can make the dock widget floating directly and move it
  433. else
  434. {
  435. setFloating(true);
  436. // Not using setGeometry() since it excludes the frame when positioning
  437. if (paneRect.isValid())
  438. {
  439. resize(paneRect.size());
  440. move(paneRect.topLeft());
  441. }
  442. }
  443. }
  444. else
  445. {
  446. m_mainWindow->addDockWidget(dockingArea, this);
  447. }
  448. }
  449. QRect DockWidget::ProperGeometry() const
  450. {
  451. QRect myGeom(pos(), size());
  452. // we need this state in global coordinates, but if we're parented to one of those group dock windows, there is a problem, it will be local coords.
  453. if (!isFloating())
  454. {
  455. if (parentWidget() && (strcmp(parentWidget()->metaObject()->className(), "QDockWidgetGroupWindow") == 0))
  456. {
  457. myGeom = QRect(parentWidget()->pos(), parentWidget()->size());
  458. }
  459. }
  460. return myGeom;
  461. }
  462. QString DockWidget::settingsKey() const
  463. {
  464. return settingsKey(m_pane->m_name);
  465. }
  466. QString DockWidget::settingsKey(const QString& paneName)
  467. {
  468. return QStringLiteral("ViewPane-") + paneName;
  469. }
  470. void EnableAllWidgetInstances(QList<DockWidget*>& widgetInstances, bool enable)
  471. {
  472. for (auto& dockWidget : widgetInstances)
  473. {
  474. AzQtComponents::SetWidgetInteractEnabled(dockWidget->widget(), enable);
  475. }
  476. }
  477. QtViewPaneManager::QtViewPaneManager(QObject* parent)
  478. : QObject(parent)
  479. , m_mainWindow(nullptr)
  480. , m_settings(nullptr)
  481. , m_restoreInProgress(false)
  482. , m_advancedDockManager(nullptr)
  483. , m_componentModeNotifications(AZStd::make_unique<ViewportEditorModeNotificationsBusImpl>())
  484. {
  485. qRegisterMetaTypeStreamOperators<ViewLayoutState>("ViewLayoutState");
  486. qRegisterMetaTypeStreamOperators<QVector<QString> >("QVector<QString>");
  487. // view pane manager is interested when we enter/exit ComponentMode
  488. m_componentModeNotifications->BusConnect(AzToolsFramework::GetEntityContextId());
  489. m_windowRequest.BusConnect();
  490. m_componentModeNotifications->SetEnteredComponentModeFunc(
  491. [this]([[maybe_unused]] const AzToolsFramework::ViewportEditorModesInterface& editorModes)
  492. {
  493. for (QtViewPane& p : m_registeredPanes)
  494. {
  495. if (p.m_options.isDisabledInComponentMode)
  496. {
  497. // By default, disable all widgets when entering Component Mode
  498. EnableAllWidgetInstances(p.m_dockWidgetInstances, false);
  499. }
  500. }
  501. });
  502. m_componentModeNotifications->SetLeftComponentModeFunc(
  503. [this]([[maybe_unused]] const AzToolsFramework::ViewportEditorModesInterface& editorModes)
  504. {
  505. for (QtViewPane& p : m_registeredPanes)
  506. {
  507. if (p.m_options.isDisabledInComponentMode)
  508. {
  509. // By default, enable all widgets again when leaving Component Mode
  510. EnableAllWidgetInstances(p.m_dockWidgetInstances, true);
  511. }
  512. }
  513. });
  514. m_windowRequest.SetEnableEditorUiFunc(
  515. [this](bool enable)
  516. {
  517. for (QtViewPane& p : m_registeredPanes)
  518. {
  519. if (p.m_options.isDisabledInImGuiMode)
  520. {
  521. // By default, disable/enable all widgets when entering/exiting IMGUI
  522. EnableAllWidgetInstances(p.m_dockWidgetInstances, enable);
  523. }
  524. }
  525. });
  526. }
  527. QtViewPaneManager::~QtViewPaneManager()
  528. {
  529. m_windowRequest.BusDisconnect();
  530. m_componentModeNotifications->BusDisconnect();
  531. }
  532. static bool lessThan(const QtViewPane& v1, const QtViewPane& v2)
  533. {
  534. if (v1.IsViewportPane() && v2.IsViewportPane())
  535. {
  536. // Registration order (Top, Front, Left ...)
  537. return v1.m_id < v2.m_id;
  538. }
  539. else if (!v1.IsViewportPane() && !v2.IsViewportPane())
  540. {
  541. // Sort by name
  542. return v1.m_name.compare(v2.m_name, Qt::CaseInsensitive) < 0;
  543. }
  544. else
  545. {
  546. // viewports on top of non-viewports
  547. return v1.IsViewportPane();
  548. }
  549. }
  550. void QtViewPaneManager::RegisterPane(const QString& name, const QString& category, ViewPaneFactory factory, const AzToolsFramework::ViewPaneOptions& options)
  551. {
  552. if (IsPaneRegistered(name))
  553. {
  554. return;
  555. }
  556. QtViewPane view = { NextAvailableId(), name, category, factory, nullptr, options };
  557. // Sorted insert
  558. auto it = std::upper_bound(m_registeredPanes.begin(), m_registeredPanes.end(), view, lessThan);
  559. m_registeredPanes.insert(it, view);
  560. emit registeredPanesChanged();
  561. }
  562. void QtViewPaneManager::UnregisterPane(const QString& name)
  563. {
  564. auto it = std::find_if(m_registeredPanes.begin(), m_registeredPanes.end(),
  565. [name](const QtViewPane& pane) { return name == pane.m_name; });
  566. if (it != m_registeredPanes.end())
  567. {
  568. QtViewPane& pane = *it;
  569. ClosePane(&pane);
  570. m_knownIdsSet.removeOne(pane.m_id);
  571. m_registeredPanes.erase(it);
  572. emit registeredPanesChanged();
  573. }
  574. }
  575. QtViewPaneManager* QtViewPaneManager::instance()
  576. {
  577. return s_viewPaneManagerInstance();
  578. }
  579. bool QtViewPaneManager::exists()
  580. {
  581. return s_viewPaneManagerInstance.exists();
  582. }
  583. void QtViewPaneManager::SetMainWindow(AzQtComponents::DockMainWindow* mainWindow, QSettings* settings, const QByteArray& lastMainWindowState)
  584. {
  585. Q_ASSERT(mainWindow && !m_mainWindow && settings && !m_settings);
  586. m_mainWindow = mainWindow;
  587. m_settings = settings;
  588. m_advancedDockManager = new AzQtComponents::FancyDocking(mainWindow);
  589. m_defaultMainWindowState = mainWindow->saveState();
  590. m_loadedMainWindowState = lastMainWindowState;
  591. }
  592. const QtViewPane* QtViewPaneManager::OpenPane(const QString& name, QtViewPane::OpenModes modes)
  593. {
  594. QtViewPane* pane = GetPane(name);
  595. if (!pane || !pane->IsValid())
  596. {
  597. qWarning() << Q_FUNC_INFO << "Could not find pane with name" << name;
  598. return nullptr;
  599. }
  600. // this multi-pane code is a bit of an hack to support more than one view of the same class
  601. // All views are single pane, except for one in Maglev Control plugin
  602. // Save/Restore support of the duplicates will only be implemented if required.
  603. const bool isMultiPane = modes & QtViewPane::OpenMode::MultiplePanes;
  604. DockWidget* newDockWidget = pane->m_dockWidget;
  605. if (!pane->IsVisible() || isMultiPane)
  606. {
  607. if (!pane->IsConstructed() || isMultiPane)
  608. {
  609. QWidget* w = pane->CreateWidget();
  610. if (!w)
  611. {
  612. qWarning() << Q_FUNC_INFO << "Unable to create widget for pane with name" << name;
  613. return nullptr;
  614. }
  615. w->setProperty("restored", (modes & QtViewPane::OpenMode::RestoreLayout) != 0);
  616. newDockWidget = new DockWidget(w, pane, m_settings, m_mainWindow, m_advancedDockManager);
  617. AzQtComponents::StyleManager::repolishStyleSheet(newDockWidget);
  618. // track every new dock widget instance that we created
  619. pane->m_dockWidgetInstances.push_back(newDockWidget);
  620. connect(newDockWidget, &QObject::destroyed, this, [this, name, newDockWidget]() {
  621. QtViewPane* pane = GetPane(name);
  622. if (pane && pane->IsValid())
  623. {
  624. pane->m_dockWidgetInstances.removeAll(newDockWidget);
  625. }
  626. AzToolsFramework::EditorEventsBus::Broadcast(
  627. &AzToolsFramework::EditorEventsBus::Handler::OnViewPaneClosed, pane->m_name.toUtf8().data());
  628. });
  629. // only set the single instance of the dock widget on the pane if this
  630. // isn't a special multi-pane instance
  631. if (!isMultiPane)
  632. {
  633. pane->m_dockWidget = newDockWidget;
  634. }
  635. else
  636. {
  637. m_advancedDockManager->disableAutoSaveLayout(newDockWidget);
  638. }
  639. newDockWidget->setVisible(true);
  640. // If this pane isn't dockable, set the allowed areas to none on the
  641. // dock widget so the fancy docking knows to prevent it from docking
  642. if (!pane->m_options.isDockable)
  643. {
  644. newDockWidget->setAllowedAreas(Qt::NoDockWidgetArea);
  645. }
  646. // only emit this signal if we're not creating a non-saving instance of the view pane
  647. if (!isMultiPane)
  648. {
  649. emit viewPaneCreated(pane);
  650. }
  651. #if AZ_TRAIT_OS_PLATFORM_APPLE
  652. // handle showing fake dock widgets
  653. if (pane->m_options.detachedWindow)
  654. {
  655. ShowFakeNonDockableDockWidget(newDockWidget, pane);
  656. }
  657. #endif
  658. }
  659. else if (!AzQtComponents::DockTabWidget::IsTabbed(newDockWidget))
  660. {
  661. newDockWidget->setVisible(true);
  662. #if AZ_TRAIT_OS_PLATFORM_APPLE
  663. if (pane->m_options.detachedWindow)
  664. {
  665. newDockWidget->window()->show();
  666. }
  667. #endif
  668. }
  669. if ((modes & QtViewPane::OpenMode::UseDefaultState) || isMultiPane)
  670. {
  671. const bool forceToDefault = true;
  672. newDockWidget->RestoreState(forceToDefault);
  673. }
  674. else if (!AzQtComponents::DockTabWidget::IsTabbed(newDockWidget) && !(modes & QtViewPane::OpenMode::OnlyOpen))
  675. {
  676. newDockWidget->RestoreState();
  677. }
  678. }
  679. // If the dock widget is off screen (e.g. second monitor was disconnected),
  680. // restore its default state
  681. if (QApplication::desktop()->screenNumber(newDockWidget) == -1)
  682. {
  683. const bool forceToDefault = true;
  684. newDockWidget->RestoreState(forceToDefault);
  685. }
  686. // If the widget's window is minimized, show it.
  687. QWidget* window = newDockWidget->window();
  688. if (window->isMinimized())
  689. {
  690. window->setWindowState(window->windowState() & ~Qt::WindowMinimized | Qt::WindowActive);
  691. }
  692. if (pane->IsVisible())
  693. {
  694. if (!modes.testFlag(QtViewPane::OpenMode::RestoreLayout))
  695. {
  696. newDockWidget->setFocus();
  697. }
  698. }
  699. else
  700. {
  701. // If the dock widget is tabbed, then set it as the active tab
  702. AzQtComponents::DockTabWidget* tabWidget = AzQtComponents::DockTabWidget::ParentTabWidget(newDockWidget);
  703. if (tabWidget)
  704. {
  705. int index = tabWidget->indexOf(newDockWidget);
  706. tabWidget->setCurrentIndex(index);
  707. }
  708. // Otherwise just show the widget
  709. else
  710. {
  711. newDockWidget->show();
  712. }
  713. }
  714. // When a user opens a pane, if it is docked in a floating window, make sure
  715. // it isn't hidden behind other floating windows or the Editor main window
  716. if (modes.testFlag(QtViewPane::OpenMode::None))
  717. {
  718. QMainWindow* mainWindow = qobject_cast<QMainWindow*>(newDockWidget->parentWidget());
  719. if (!mainWindow)
  720. {
  721. // If the parent of our dock widgets isn't a QMainWindow, then it
  722. // might be tabbed, so try to find the tab container dock widget
  723. // and then get the QMainWindow from that.
  724. AzQtComponents::DockTabWidget* tabWidget = AzQtComponents::DockTabWidget::ParentTabWidget(newDockWidget);
  725. if (tabWidget)
  726. {
  727. QDockWidget* tabDockContainer = qobject_cast<QDockWidget*>(tabWidget->parentWidget());
  728. if (tabDockContainer)
  729. {
  730. mainWindow = qobject_cast<QMainWindow*>(tabDockContainer->parentWidget());
  731. }
  732. }
  733. }
  734. if (mainWindow)
  735. {
  736. // If our pane is part of a floating window, then the parent of its
  737. // QMainWindow will be another dock widget container that is floating.
  738. // If this is the case, then raise it to the front so it won't be
  739. // hidden behind other floating windows (or the Editor main window)
  740. QDockWidget* parentDockWidget = qobject_cast<QDockWidget*>(mainWindow->parentWidget());
  741. if (parentDockWidget && parentDockWidget->isFloating())
  742. {
  743. parentDockWidget->raise();
  744. }
  745. }
  746. }
  747. AzToolsFramework::EditorEventsBus::Broadcast(&AzToolsFramework::EditorEventsBus::Handler::OnViewPaneOpened, pane->m_name.toUtf8().data());
  748. return pane;
  749. }
  750. QDockWidget* QtViewPaneManager::InstancePane(const QString& name)
  751. {
  752. const QtViewPane* pane = OpenPane(name, QtViewPane::OpenMode::UseDefaultState | QtViewPane::OpenMode::MultiplePanes);
  753. QDockWidget* newPaneWidget = nullptr;
  754. if (pane != nullptr)
  755. {
  756. newPaneWidget = pane->m_dockWidgetInstances.last();
  757. }
  758. return newPaneWidget;
  759. }
  760. bool QtViewPaneManager::ClosePane(const QString& name, QtViewPane::CloseModes closeModes)
  761. {
  762. if (QtViewPane* p = GetPane(name))
  763. {
  764. return ClosePane(p, closeModes);
  765. }
  766. return false;
  767. }
  768. bool QtViewPaneManager::ClosePaneInstance(const QString& name, QDockWidget* dockWidget, QtViewPane::CloseModes closeModes)
  769. {
  770. if (QtViewPane* p = GetPane(name))
  771. {
  772. return p->CloseInstance(dockWidget, closeModes);
  773. }
  774. return false;
  775. }
  776. bool QtViewPaneManager::ClosePane(QtViewPane* pane, QtViewPane::CloseModes closeModes)
  777. {
  778. if (pane)
  779. {
  780. // Don't allow a dock widget to be closed if it is being dragged for docking
  781. if (m_advancedDockManager && m_advancedDockManager->IsDockWidgetBeingDragged(pane->m_dockWidget))
  782. {
  783. return false;
  784. }
  785. return pane->Close(closeModes | QtViewPane::CloseMode::Force);
  786. }
  787. return false;
  788. }
  789. bool QtViewPaneManager::CloseAllPanes()
  790. {
  791. for (QtViewPane& p : m_registeredPanes)
  792. {
  793. if (!p.Close())
  794. {
  795. return false; // Abort closing
  796. }
  797. }
  798. return true;
  799. }
  800. void QtViewPaneManager::CloseAllNonStandardPanes()
  801. {
  802. for (QtViewPane& p : m_registeredPanes)
  803. {
  804. if (!p.m_options.isStandard)
  805. {
  806. p.Close(QtViewPane::CloseMode::Force);
  807. }
  808. }
  809. }
  810. void QtViewPaneManager::TogglePane(const QString& name)
  811. {
  812. // Find the first pane with this name, or an enumerated instance.
  813. QtViewPane* pane = GetFirstVisiblePaneMatching(name);
  814. if (pane)
  815. {
  816. ClosePane(pane->m_name);
  817. return;
  818. }
  819. pane = GetPane(name);
  820. if (!pane)
  821. {
  822. Q_ASSERT_X(false, "QtViewPaneManager", ("Failed to open pane " + name).toUtf8().data());
  823. return;
  824. }
  825. OpenPane(name);
  826. }
  827. QWidget* QtViewPaneManager::CreateWidget(const QString& paneName)
  828. {
  829. QtViewPane* pane = GetPane(paneName);
  830. if (!pane)
  831. {
  832. qWarning() << Q_FUNC_INFO << "Couldn't find pane" << paneName << "; paneCount=" << m_registeredPanes.size();
  833. return nullptr;
  834. }
  835. QWidget* w = pane->CreateWidget();
  836. if (w)
  837. {
  838. w->setWindowTitle(paneName);
  839. return w;
  840. }
  841. return nullptr;
  842. }
  843. void QtViewPaneManager::SaveLayout()
  844. {
  845. SaveLayout(s_lastLayoutName);
  846. }
  847. void QtViewPaneManager::RestoreLayout(bool restoreDefaults)
  848. {
  849. if (!restoreDefaults)
  850. {
  851. restoreDefaults = !RestoreLayout(s_lastLayoutName);
  852. }
  853. if (restoreDefaults)
  854. {
  855. // Nothing is saved in settings, restore default layout
  856. RestoreDefaultLayout();
  857. }
  858. }
  859. bool QtViewPaneManager::ClosePanesWithRollback(const QVector<QString>& panesToKeepOpen)
  860. {
  861. QVector<QString> closedPanes;
  862. // try to close all panes that aren't remaining open after relayout
  863. bool rollback = false;
  864. for (QtViewPane& p : m_registeredPanes)
  865. {
  866. // Only close the panes that aren't remaining open and are currently
  867. // visible (which has to include a check if the pane is tabbed since
  868. // it could be hidden if its not the active tab)
  869. if (panesToKeepOpen.contains(p.m_name) || (!p.IsVisible() && !AzQtComponents::DockTabWidget::IsTabbed(p.m_dockWidget)))
  870. {
  871. continue;
  872. }
  873. // attempt to close this pane; if Close returns false, then the close event
  874. // was intercepted and the pane doesn't want to close, so we should cancel the whole thing
  875. // and rollback
  876. if (!p.Close())
  877. {
  878. rollback = true;
  879. break;
  880. }
  881. // keep track of the panes that we closed, so we can rollback later and reopen them
  882. closedPanes.push_back(p.m_name);
  883. }
  884. // check if we cancelled and need to roll everything back
  885. if (rollback)
  886. {
  887. for (const QString& paneName : closedPanes)
  888. {
  889. // append this to the end of the event loop with a zero length timer, so that
  890. // all of the close/hide events above are entirely processed
  891. QTimer::singleShot(0, this, [paneName, this]()
  892. {
  893. OpenPane(paneName, QtViewPane::OpenMode::RestoreLayout);
  894. });
  895. }
  896. return false;
  897. }
  898. return true;
  899. }
  900. /**
  901. * Restore the default layout (also known as component entity layout)
  902. */
  903. void QtViewPaneManager::RestoreDefaultLayout(bool resetSettings)
  904. {
  905. // Get whether the prefab system is enabled
  906. bool isPrefabSystemEnabled = false;
  907. AzFramework::ApplicationRequests::Bus::BroadcastResult(
  908. isPrefabSystemEnabled, &AzFramework::ApplicationRequests::IsPrefabSystemEnabled);
  909. if (resetSettings)
  910. {
  911. // We're going to do something destructive (removing all of the viewpane settings). Better confirm with the user
  912. auto buttonPressed = QMessageBox::warning(m_mainWindow, tr("Restore Default Layout"), tr("Are you sure you'd like to restore to the default layout? This will reset all of your view related settings."), QMessageBox::Cancel | QMessageBox::RestoreDefaults, QMessageBox::RestoreDefaults);
  913. if (buttonPressed != QMessageBox::RestoreDefaults)
  914. {
  915. return;
  916. }
  917. }
  918. // First, close all the open panes
  919. if (!ClosePanesWithRollback(QVector<QString>()))
  920. {
  921. return;
  922. }
  923. // Disable updates while we restore the layout to avoid temporary glitches
  924. // as the panes are moved around
  925. m_mainWindow->setUpdatesEnabled(false);
  926. AzToolsFramework::EntityIdList selectedEntityIds;
  927. // Reset all of the settings, or windows opened outside of RestoreDefaultLayout won't be reset at all.
  928. // Also ensure that this is done after CloseAllPanes, because settings will be saved in CloseAllPanes
  929. if (resetSettings)
  930. {
  931. // Store off currently selected entities
  932. AzToolsFramework::ToolsApplicationRequestBus::BroadcastResult(selectedEntityIds, &AzToolsFramework::ToolsApplicationRequests::GetSelectedEntities);
  933. // Clear any selection
  934. AzToolsFramework::EntityIdList noEntities;
  935. AzToolsFramework::ToolsApplicationRequests::Bus::Broadcast(&AzToolsFramework::ToolsApplicationRequests::SetSelectedEntities, noEntities);
  936. ViewLayoutState state;
  937. state.viewPanes.push_back(LyViewPane::EntityOutliner);
  938. state.viewPanes.push_back(LyViewPane::Inspector);
  939. state.viewPanes.push_back(LyViewPane::AssetBrowser);
  940. state.viewPanes.push_back(LyViewPane::Console);
  941. if (!isPrefabSystemEnabled)
  942. {
  943. state.viewPanes.push_back(LyViewPane::LevelInspector);
  944. }
  945. state.mainWindowState = m_defaultMainWindowState;
  946. {
  947. AzQtComponents::AutoSettingsGroup settingsGroupGuard(m_settings, GetFancyViewPaneStateGroupName());
  948. m_settings->setValue(s_lastLayoutName, QVariant::fromValue<ViewLayoutState>(state));
  949. }
  950. m_settings->sync();
  951. // Let anything listening know to reset as well (*cough*CLayoutWnd*cough*)
  952. emit layoutReset();
  953. // Ensure that the main window knows it's new state
  954. // otherwise when we load view panes that haven't been loaded,
  955. // the main window will attempt to position them where they were last, not in their default spot
  956. m_mainWindow->restoreState(m_defaultMainWindowState);
  957. }
  958. // Reset the default view panes to be opened. Used for restoring default layout and component entity layout.
  959. const QtViewPane* entityOutlinerViewPane = OpenPane(LyViewPane::EntityOutliner, QtViewPane::OpenMode::UseDefaultState);
  960. const QtViewPane* assetBrowserViewPane = OpenPane(LyViewPane::AssetBrowser, QtViewPane::OpenMode::UseDefaultState);
  961. const QtViewPane* InspectorViewPane = OpenPane(LyViewPane::Inspector, QtViewPane::OpenMode::UseDefaultState);
  962. const QtViewPane* consoleViewPane = OpenPane(LyViewPane::Console, QtViewPane::OpenMode::UseDefaultState);
  963. const QtViewPane* levelInspectorPane = nullptr;
  964. if (!isPrefabSystemEnabled)
  965. {
  966. levelInspectorPane = OpenPane(LyViewPane::LevelInspector, QtViewPane::OpenMode::UseDefaultState);
  967. }
  968. // This class does all kinds of behind the scenes magic to make docking / restore work, especially with groups
  969. // so instead of doing our special default layout attach / docking right now, we want to make it happen
  970. // after all of the other events have been processed.
  971. QTimer::singleShot(0, [=]
  972. {
  973. // If we are using the new docking, set the right dock area to be absolute
  974. // so that the inspector will be to the right of the viewport and console
  975. m_advancedDockManager->setAbsoluteCornersForDockArea(m_mainWindow, Qt::RightDockWidgetArea);
  976. // Retrieve the width and height of the screen that our main window is on so we can
  977. // use it later for resizing our panes. The main window ends up being maximized
  978. // when we restore the default layout, but even if we maximize the main window
  979. // before doing anything else, its height and width won't update until after this has all
  980. // been processed, so we need to resize the panes based on what the main window
  981. // height and width WILL be after maximized
  982. int screenWidth = QApplication::desktop()->screenGeometry(m_mainWindow).width();
  983. int screenHeight = QApplication::desktop()->screenGeometry(m_mainWindow).height();
  984. // Add the console view pane first
  985. m_mainWindow->addDockWidget(Qt::BottomDockWidgetArea, consoleViewPane->m_dockWidget);
  986. consoleViewPane->m_dockWidget->setFloating(false);
  987. if (assetBrowserViewPane)
  988. {
  989. m_mainWindow->addDockWidget(Qt::BottomDockWidgetArea, assetBrowserViewPane->m_dockWidget);
  990. assetBrowserViewPane->m_dockWidget->setFloating(false);
  991. static const float bottomTabWidgetPercentage = 0.25f;
  992. int newHeight = static_cast<int>((float)screenHeight * bottomTabWidgetPercentage);
  993. AzQtComponents::DockTabWidget* bottomTabWidget = m_advancedDockManager->tabifyDockWidget(assetBrowserViewPane->m_dockWidget, consoleViewPane->m_dockWidget, m_mainWindow);
  994. if (bottomTabWidget)
  995. {
  996. bottomTabWidget->setCurrentWidget(assetBrowserViewPane->m_dockWidget);
  997. QDockWidget* bottomTabWidgetParent = qobject_cast<QDockWidget*>(bottomTabWidget->parentWidget());
  998. m_mainWindow->resizeDocks({ bottomTabWidgetParent }, { newHeight }, Qt::Vertical);
  999. }
  1000. else
  1001. {
  1002. m_mainWindow->resizeDocks({ assetBrowserViewPane->m_dockWidget }, { newHeight }, Qt::Vertical);
  1003. }
  1004. }
  1005. if (InspectorViewPane)
  1006. {
  1007. m_mainWindow->addDockWidget(Qt::RightDockWidgetArea, InspectorViewPane->m_dockWidget);
  1008. InspectorViewPane->m_dockWidget->setFloating(false);
  1009. static const float tabWidgetWidthPercentage = 0.2f;
  1010. int newWidth = static_cast<int>((float)screenWidth * tabWidgetWidthPercentage);
  1011. if (levelInspectorPane)
  1012. {
  1013. // Tab the entity inspector with the level Inspector so that when they are
  1014. // tabbed they will be given the default width, and move the entity inspector
  1015. // to be the first tab on the left and active
  1016. AzQtComponents::DockTabWidget* tabWidget = m_advancedDockManager->tabifyDockWidget(levelInspectorPane->m_dockWidget, InspectorViewPane->m_dockWidget, m_mainWindow);
  1017. if (tabWidget)
  1018. {
  1019. tabWidget->moveTab(1, 0);
  1020. tabWidget->setCurrentWidget(InspectorViewPane->m_dockWidget);
  1021. QDockWidget* tabWidgetParent = qobject_cast<QDockWidget*>(tabWidget->parentWidget());
  1022. m_mainWindow->resizeDocks({ tabWidgetParent }, { newWidth }, Qt::Horizontal);
  1023. }
  1024. }
  1025. else
  1026. {
  1027. m_mainWindow->resizeDocks({ InspectorViewPane->m_dockWidget }, { newWidth }, Qt::Horizontal);
  1028. }
  1029. }
  1030. if (entityOutlinerViewPane)
  1031. {
  1032. m_mainWindow->addDockWidget(Qt::LeftDockWidgetArea, entityOutlinerViewPane->m_dockWidget);
  1033. entityOutlinerViewPane->m_dockWidget->setFloating(false);
  1034. // Resize our entity outliner so that it gets an appropriate default width
  1035. // since the minimum sizes been removed from this widget
  1036. static const float entityOutlinerWidthPercentage = 0.15f;
  1037. int newWidth = static_cast<int>((float)screenWidth * entityOutlinerWidthPercentage);
  1038. m_mainWindow->resizeDocks({ entityOutlinerViewPane->m_dockWidget }, { newWidth }, Qt::Horizontal);
  1039. }
  1040. // Re-enable updates now that we've finished restoring the layout
  1041. m_mainWindow->setUpdatesEnabled(true);
  1042. // Default layout should always be maximized
  1043. // (use window() because the MainWindow may be wrapped in another window
  1044. // like a WindowDecoratorWrapper or another QMainWindow for various layout reasons)
  1045. m_mainWindow->window()->showMaximized();
  1046. if (resetSettings)
  1047. {
  1048. // Restore selection
  1049. AzToolsFramework::ToolsApplicationRequests::Bus::Broadcast(&AzToolsFramework::ToolsApplicationRequests::SetSelectedEntities, selectedEntityIds);
  1050. }
  1051. });
  1052. }
  1053. void QtViewPaneManager::SaveLayout(QString layoutName)
  1054. {
  1055. if (!m_mainWindow || m_restoreInProgress)
  1056. {
  1057. return;
  1058. }
  1059. layoutName = layoutName.trimmed();
  1060. ViewLayoutState state;
  1061. foreach(const QtViewPane &pane, m_registeredPanes)
  1062. {
  1063. // Include all visible and tabbed panes in our layout, since tabbed panes
  1064. // won't be visible if they aren't the active tab, but still need to be
  1065. // retained in the layout
  1066. if (pane.IsVisible() || AzQtComponents::DockTabWidget::IsTabbed(pane.m_dockWidget))
  1067. {
  1068. state.viewPanes.push_back(pane.m_dockWidget->PaneName());
  1069. }
  1070. }
  1071. state.mainWindowState = m_advancedDockManager->saveState();
  1072. state.fakeDockWidgetGeometries = m_fakeDockWidgetGeometries;
  1073. SaveStateToLayout(state, layoutName);
  1074. AZ::UserSettingsComponentRequestBus::Broadcast(&AZ::UserSettingsComponentRequestBus::Events::Save);
  1075. }
  1076. void QtViewPaneManager::SaveStateToLayout(const ViewLayoutState& state, const QString& layoutName)
  1077. {
  1078. const bool isNew = !HasLayout(layoutName);
  1079. {
  1080. AzQtComponents::AutoSettingsGroup settingsGroupGuard(m_settings, GetFancyViewPaneStateGroupName());
  1081. m_settings->setValue(layoutName, QVariant::fromValue<ViewLayoutState>(state));
  1082. }
  1083. m_settings->sync();
  1084. if (isNew)
  1085. {
  1086. emit savedLayoutsChanged();
  1087. }
  1088. }
  1089. #if AZ_TRAIT_OS_PLATFORM_APPLE
  1090. /*
  1091. * This methods creates a fake wrapper dock widget around the passed dock widget. The returned dock widget has
  1092. * no parent and can therefore be used for a contained QOpenGLWidget on macOS, since this doesn't work as expected
  1093. * when the QOpenGLWidget has the application's main window as (grand)parent.
  1094. * The return dock widget looks like a normal dock widget. It cannot be docked and no other dock widget can be
  1095. * docked into it.
  1096. */
  1097. QDockWidget* QtViewPaneManager::ShowFakeNonDockableDockWidget(AzQtComponents::StyledDockWidget* dockWidget, QtViewPane* pane)
  1098. {
  1099. dockWidget->customTitleBar()->setButtons({});
  1100. dockWidget->customTitleBar()->setContextMenuPolicy(Qt::NoContextMenu);
  1101. dockWidget->customTitleBar()->installEventFilter(new MouseEatingEventFilter(dockWidget));
  1102. auto fakeDockWidget = new AzQtComponents::StyledDockWidget(QString(), false, nullptr);
  1103. connect(dockWidget, &QObject::destroyed, fakeDockWidget, &QObject::deleteLater);
  1104. fakeDockWidget->setAllowedAreas(Qt::NoDockWidgetArea);
  1105. auto titleBar = fakeDockWidget->customTitleBar();
  1106. titleBar->setDragEnabled(true);
  1107. titleBar->setDrawSimple(true);
  1108. titleBar->setButtons({AzQtComponents::DockBarButton::MaximizeButton, AzQtComponents::DockBarButton::CloseButton});
  1109. fakeDockWidget->setWidget(dockWidget);
  1110. fakeDockWidget->show();
  1111. fakeDockWidget->setObjectName(dockWidget->objectName());
  1112. connect(fakeDockWidget, &AzQtComponents::StyledDockWidget::aboutToClose, this, [this, fakeDockWidget]() {
  1113. m_fakeDockWidgetGeometries[fakeDockWidget->objectName()] = fakeDockWidget->geometry();
  1114. });
  1115. if (pane->m_options.isDeletable)
  1116. {
  1117. fakeDockWidget->setAttribute(Qt::WA_DeleteOnClose);
  1118. }
  1119. return fakeDockWidget;
  1120. }
  1121. #endif
  1122. void QtViewPaneManager::SerializeLayout(XmlNodeRef& parentNode) const
  1123. {
  1124. ViewLayoutState state = GetLayout();
  1125. XmlNodeRef paneListNode = XmlHelpers::CreateXmlNode("ViewPanes");
  1126. parentNode->addChild(paneListNode);
  1127. for (const QString& paneName : state.viewPanes)
  1128. {
  1129. XmlNodeRef paneNode = XmlHelpers::CreateXmlNode("ViewPane");
  1130. paneNode->setContent(paneName.toUtf8().data());
  1131. paneListNode->addChild(paneNode);
  1132. }
  1133. XmlNodeRef windowStateNode = XmlHelpers::CreateXmlNode("WindowState");
  1134. windowStateNode->setContent(state.mainWindowState.toHex().data());
  1135. parentNode->addChild(windowStateNode);
  1136. }
  1137. bool QtViewPaneManager::DeserializeLayout(const XmlNodeRef& parentNode)
  1138. {
  1139. ViewLayoutState state;
  1140. XmlNodeRef paneListNode = parentNode->findChild("ViewPanes");
  1141. if (!paneListNode)
  1142. return false;
  1143. for (int i = 0; i < paneListNode->getChildCount(); ++i)
  1144. {
  1145. XmlNodeRef paneNode = paneListNode->getChild(i);
  1146. state.viewPanes.push_back(QString(paneNode->getContent()));
  1147. }
  1148. XmlNodeRef windowStateNode = parentNode->findChild("WindowState");
  1149. if (!windowStateNode)
  1150. return false;
  1151. state.mainWindowState = QByteArray::fromHex(windowStateNode->getContent());
  1152. return RestoreLayout(state);
  1153. }
  1154. ViewLayoutState QtViewPaneManager::GetLayout() const
  1155. {
  1156. ViewLayoutState state;
  1157. foreach(const QtViewPane &pane, m_registeredPanes)
  1158. {
  1159. // Include all visible and tabbed panes in our layout, since tabbed panes
  1160. // won't be visible if they aren't the active tab, but still need to be
  1161. // retained in the layout
  1162. if (pane.IsVisible() || AzQtComponents::DockTabWidget::IsTabbed(pane.m_dockWidget))
  1163. {
  1164. state.viewPanes.push_back(pane.m_dockWidget->PaneName());
  1165. }
  1166. }
  1167. state.mainWindowState = m_advancedDockManager->saveState();
  1168. state.fakeDockWidgetGeometries = m_fakeDockWidgetGeometries;
  1169. return state;
  1170. }
  1171. bool QtViewPaneManager::RestoreLayout(QString layoutName)
  1172. {
  1173. if (m_restoreInProgress) // Against re-entrancy
  1174. {
  1175. return true;
  1176. }
  1177. QScopedValueRollback<bool> recursionGuard(m_restoreInProgress);
  1178. m_restoreInProgress = true;
  1179. layoutName = layoutName.trimmed();
  1180. if (layoutName.isEmpty())
  1181. {
  1182. return false;
  1183. }
  1184. ViewLayoutState state;
  1185. {
  1186. AzQtComponents::AutoSettingsGroup settingsGroupGuard(m_settings, GetFancyViewPaneStateGroupName());
  1187. if (!m_settings->contains(layoutName))
  1188. {
  1189. return false;
  1190. }
  1191. state = m_settings->value(layoutName).value<ViewLayoutState>();
  1192. }
  1193. // If we have the legacy UI disabled and are restoring the last user layout,
  1194. // if it doesn't contain the Entity Inspector and Outliner then we need to
  1195. // save their previous layout for them and switch them to the new default
  1196. // layout because they won't be able to do much without them
  1197. static const QString userLegacyLayout = "User Legacy Layout";
  1198. if (layoutName == s_lastLayoutName && !HasLayout(userLegacyLayout))
  1199. {
  1200. bool layoutHasInspector = false;
  1201. bool layoutHasEntityOutliner = false;
  1202. for (const QString& paneName : state.viewPanes)
  1203. {
  1204. if (paneName == LyViewPane::Inspector)
  1205. {
  1206. layoutHasInspector = true;
  1207. }
  1208. else if (paneName == LyViewPane::EntityOutliner)
  1209. {
  1210. layoutHasEntityOutliner = true;
  1211. }
  1212. }
  1213. if (!layoutHasInspector || !layoutHasEntityOutliner)
  1214. {
  1215. SaveStateToLayout(state, userLegacyLayout);
  1216. QMessageBox box(AzToolsFramework::GetActiveWindow());
  1217. box.addButton(QMessageBox::Ok);
  1218. box.setWindowTitle(tr("Layout Saved"));
  1219. box.setText(tr("Your layout has been automatically updated for the new Component-Entity workflows. Your old layout has been saved as \"%1\" and can be restored from the View -> Layouts menu.").arg(userLegacyLayout));
  1220. box.exec();
  1221. return false;
  1222. }
  1223. }
  1224. if (!ClosePanesWithRollback(state.viewPanes))
  1225. {
  1226. return false;
  1227. }
  1228. // Store off currently selected entities
  1229. AzToolsFramework::EntityIdList selectedEntityIds;
  1230. AzToolsFramework::ToolsApplicationRequestBus::BroadcastResult(selectedEntityIds, &AzToolsFramework::ToolsApplicationRequests::GetSelectedEntities);
  1231. // Clear any selection
  1232. AzToolsFramework::EntityIdList noEntities;
  1233. AzToolsFramework::ToolsApplicationRequests::Bus::Broadcast(&AzToolsFramework::ToolsApplicationRequests::SetSelectedEntities, noEntities);
  1234. m_fakeDockWidgetGeometries = state.fakeDockWidgetGeometries;
  1235. for (const QString& paneName : state.viewPanes)
  1236. {
  1237. OpenPane(paneName, QtViewPane::OpenMode::OnlyOpen);
  1238. }
  1239. // must do this after opening all of the panes!
  1240. m_advancedDockManager->restoreState(state.mainWindowState);
  1241. AzToolsFramework::ToolsApplicationRequests::Bus::Broadcast(&AzToolsFramework::ToolsApplicationRequests::SetSelectedEntities, selectedEntityIds);
  1242. // In case of a crash it might happen that the QMainWindow state gets out of sync with the
  1243. // QtViewPaneManager state, which would result in we opening dock widgets that QMainWindow
  1244. // didn't know how to restore.
  1245. // Check if that happened and return false indicating the restore failed and giving caller
  1246. // a chance to restore the default layout.
  1247. if (AzToolsFramework::DockWidgetUtils::hasInvalidDockWidgets(m_mainWindow))
  1248. {
  1249. return false;
  1250. }
  1251. return true;
  1252. }
  1253. bool QtViewPaneManager::RestoreLayout(const ViewLayoutState& state)
  1254. {
  1255. m_fakeDockWidgetGeometries = state.fakeDockWidgetGeometries;
  1256. if (!ClosePanesWithRollback(state.viewPanes))
  1257. {
  1258. return false;
  1259. }
  1260. for (const QString& paneName : state.viewPanes)
  1261. {
  1262. OpenPane(paneName, QtViewPane::OpenMode::OnlyOpen);
  1263. }
  1264. // must do this after opening all of the panes!
  1265. m_advancedDockManager->restoreState(state.mainWindowState);
  1266. return true;
  1267. }
  1268. void QtViewPaneManager::RenameLayout(QString name, QString newName)
  1269. {
  1270. name = name.trimmed();
  1271. newName = newName.trimmed();
  1272. if (name == newName || newName.isEmpty() || name.isEmpty())
  1273. {
  1274. return;
  1275. }
  1276. {
  1277. AzQtComponents::AutoSettingsGroup settingsGroupGuard(m_settings, GetFancyViewPaneStateGroupName());
  1278. m_settings->setValue(newName, m_settings->value(name));
  1279. m_settings->remove(name);
  1280. }
  1281. m_settings->sync();
  1282. emit savedLayoutsChanged();
  1283. }
  1284. void QtViewPaneManager::RemoveLayout(QString layoutName)
  1285. {
  1286. layoutName = layoutName.trimmed();
  1287. if (layoutName.isEmpty())
  1288. {
  1289. return;
  1290. }
  1291. {
  1292. AzQtComponents::AutoSettingsGroup settingsGroupGuard(m_settings, GetFancyViewPaneStateGroupName());
  1293. m_settings->remove(layoutName.trimmed());
  1294. }
  1295. m_settings->sync();
  1296. emit savedLayoutsChanged();
  1297. }
  1298. bool QtViewPaneManager::HasLayout(const QString& name) const
  1299. {
  1300. return LayoutNames().contains(name.trimmed(), Qt::CaseInsensitive);
  1301. }
  1302. QStringList QtViewPaneManager::LayoutNames(bool userLayoutsOnly) const
  1303. {
  1304. QStringList layouts;
  1305. AzQtComponents::AutoSettingsGroup settingsGroupGuard(m_settings, GetFancyViewPaneStateGroupName());
  1306. layouts = m_settings->childKeys();
  1307. if (userLayoutsOnly)
  1308. {
  1309. layouts.removeOne(s_lastLayoutName); // "last" is internal
  1310. }
  1311. return layouts;
  1312. }
  1313. QtViewPanes QtViewPaneManager::GetRegisteredPanes(bool viewPaneMenuOnly) const
  1314. {
  1315. if (!viewPaneMenuOnly)
  1316. {
  1317. return m_registeredPanes;
  1318. }
  1319. QtViewPanes panes;
  1320. panes.reserve(30); // approximate
  1321. std::copy_if(m_registeredPanes.cbegin(), m_registeredPanes.cend(), std::back_inserter(panes), [](QtViewPane pane)
  1322. {
  1323. return pane.m_options.showInMenu;
  1324. });
  1325. return panes;
  1326. }
  1327. QtViewPanes QtViewPaneManager::GetRegisteredMultiInstancePanes(bool viewPaneMenuOnly) const
  1328. {
  1329. QtViewPanes panes;
  1330. panes.reserve(30); // approximate
  1331. if (viewPaneMenuOnly)
  1332. {
  1333. std::copy_if(m_registeredPanes.cbegin(), m_registeredPanes.cend(), std::back_inserter(panes), [](QtViewPane pane)
  1334. {
  1335. return pane.m_options.showInMenu && pane.m_options.canHaveMultipleInstances;
  1336. });
  1337. }
  1338. else
  1339. {
  1340. std::copy_if(m_registeredPanes.cbegin(), m_registeredPanes.cend(), std::back_inserter(panes), [](QtViewPane pane)
  1341. {
  1342. return pane.m_options.canHaveMultipleInstances;
  1343. });
  1344. }
  1345. return panes;
  1346. }
  1347. QtViewPanes QtViewPaneManager::GetRegisteredViewportPanes() const
  1348. {
  1349. QtViewPanes viewportPanes;
  1350. viewportPanes.reserve(5); // approximate
  1351. std::copy_if(m_registeredPanes.cbegin(), m_registeredPanes.cend(), std::back_inserter(viewportPanes), [](QtViewPane pane)
  1352. {
  1353. return pane.IsViewportPane();
  1354. });
  1355. return viewportPanes;
  1356. }
  1357. int QtViewPaneManager::NextAvailableId()
  1358. {
  1359. for (int candidate = ID_VIEW_OPENPANE_FIRST; candidate <= ID_VIEW_OPENPANE_LAST; ++candidate)
  1360. {
  1361. if (!m_knownIdsSet.contains(candidate))
  1362. {
  1363. m_knownIdsSet.push_back(candidate);
  1364. return candidate;
  1365. }
  1366. }
  1367. return -1;
  1368. }
  1369. QtViewPane* QtViewPaneManager::GetPane(int id)
  1370. {
  1371. auto it = std::find_if(m_registeredPanes.begin(), m_registeredPanes.end(),
  1372. [id](const QtViewPane& pane) { return id == pane.m_id; });
  1373. return it == m_registeredPanes.end() ? nullptr : it;
  1374. }
  1375. QtViewPane* QtViewPaneManager::GetPane(const QString& name)
  1376. {
  1377. auto it = std::find_if(m_registeredPanes.begin(), m_registeredPanes.end(),
  1378. [name](const QtViewPane& pane) { return name == pane.m_name; });
  1379. QtViewPane* foundPane = ((it == m_registeredPanes.end()) ? nullptr : it);
  1380. if (foundPane == nullptr)
  1381. {
  1382. // if we couldn't find the pane based on the name (which will be the title), look it up by saveKeyName next
  1383. it = std::find_if(m_registeredPanes.begin(), m_registeredPanes.end(),
  1384. [name](const QtViewPane& pane) { return name == pane.m_options.saveKeyName; });
  1385. foundPane = ((it == m_registeredPanes.end()) ? nullptr : it);
  1386. }
  1387. return foundPane;
  1388. }
  1389. QtViewPane* QtViewPaneManager::GetFirstVisiblePaneMatching(const QString& name)
  1390. {
  1391. QString baseName = name;
  1392. // Strip away any enumeration.
  1393. baseName = baseName.remove(QRegExp("\\([0-9]+\\)$"));
  1394. // Build a regexp which will match just the name, or the name followed by a number in parentheses.
  1395. QRegExp pattern(name + "([ ]*\\([0-9]+\\))*");
  1396. auto it = std::find_if(m_registeredPanes.begin(), m_registeredPanes.end(),
  1397. [pattern](const QtViewPane& pane)
  1398. {
  1399. return pattern.exactMatch(pane.m_name) && pane.IsVisible();
  1400. });
  1401. QtViewPane* foundPane = ((it == m_registeredPanes.end()) ? nullptr : it);
  1402. if (foundPane == nullptr)
  1403. {
  1404. // if we couldn't find the pane based on the name (which will be the title), look it up by saveKeyName next
  1405. auto optionsIt = std::find_if(m_registeredPanes.begin(), m_registeredPanes.end(),
  1406. [pattern](const QtViewPane& pane)
  1407. {
  1408. return pattern.exactMatch(pane.m_options.saveKeyName) && pane.IsVisible();
  1409. });
  1410. foundPane = ((optionsIt == m_registeredPanes.end()) ? nullptr : optionsIt);
  1411. }
  1412. return foundPane;
  1413. }
  1414. QtViewPane* QtViewPaneManager::GetViewportPane(int viewportType)
  1415. {
  1416. auto it = std::find_if(m_registeredPanes.begin(), m_registeredPanes.end(),
  1417. [viewportType](const QtViewPane& pane) { return viewportType == pane.m_options.viewportType; });
  1418. return it == m_registeredPanes.end() ? nullptr : it;
  1419. }
  1420. QDockWidget* QtViewPaneManager::GetView(const QString& name)
  1421. {
  1422. QtViewPane* pane = GetPane(name);
  1423. return pane ? pane->m_dockWidget : nullptr;
  1424. }
  1425. bool QtViewPaneManager::IsVisible(const QString& name)
  1426. {
  1427. QtViewPane* view = GetPane(name);
  1428. return view && view->IsVisible();
  1429. }
  1430. bool QtViewPaneManager::IsEnumeratedInstanceVisible(const QString& name)
  1431. {
  1432. // Returns true is panel "name" is visible or "name (1)" etc.
  1433. return GetFirstVisiblePaneMatching(name);
  1434. }
  1435. bool QtViewPaneManager::IsPaneRegistered(const QString& name) const
  1436. {
  1437. auto it = std::find_if(m_registeredPanes.begin(), m_registeredPanes.end(),
  1438. [name](const QtViewPane& pane) { return name == pane.m_name; });
  1439. return it != m_registeredPanes.end();
  1440. }
  1441. #include <moc_QtViewPaneManager.cpp>