/* * Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution. * * SPDX-License-Identifier: Apache-2.0 OR MIT * */ #include "EditorDefs.h" // Editor #include "Resource.h" #include "LevelEditorMenuHandler.h" #include "CryEdit.h" #include "MainWindow.h" #include "QtViewPaneManager.h" #include "ToolBox.h" #include "Include/IObjectManager.h" #include "Objects/SelectionGroup.h" #include "ViewManager.h" #include // Qt #include #include #include #include #include // AzFramework #include // AzToolsFramework #include // AzQtComponents #include using namespace AZ; using namespace AzToolsFramework; static const char* const s_LUAEditorName = "Lua Editor"; static const char* const s_shortTimeInterval = "debug"; static const char* const s_assetImporterMetricsIdentifier = "AssetImporter"; // top level menu ids static const char* const s_fileMenuId = "FileMenu"; static const char* const s_editMenuId = "EditMenu"; static const char* const s_gameMenuId = "GameMenu"; static const char* const s_toolMenuId = "ToolMenu"; static const char* const s_viewMenuId = "ViewMenu"; static const char* const s_awsMenuId = "AwsMenu"; static const char* const s_helpMenuId = "HelpMenu"; static bool CompareLayoutNames(const QString& name1, const QString& name2) { return name1.compare(name2, Qt::CaseInsensitive) < 0; } static void AddOpenViewPaneAction(ActionManager::MenuWrapper& menu, const char* viewPaneName, const char* menuActionText = nullptr) { QAction* action = menu->addAction(QObject::tr(menuActionText ? menuActionText : viewPaneName)); QObject::connect(action, &QAction::triggered, action, [viewPaneName]() { QtViewPaneManager::instance()->OpenPane(viewPaneName); }); } namespace { // This helper allows us to watch editor notifications to control action enable states class EditorListener : public QObject , public IEditorNotifyListener { public: explicit EditorListener(QObject* parent, AZStd::function trigger) : QObject(parent) , m_trigger(trigger) {} virtual ~EditorListener() { GetIEditor()->UnregisterNotifyListener(this); } void OnEditorNotifyEvent(EEditorNotifyEvent event) { m_trigger(event); } private: AZStd::function m_trigger; }; void DisableActionWhileLevelChanges(QAction* action, EEditorNotifyEvent e) { if (e == eNotify_OnBeginNewScene || e == eNotify_OnBeginLoad) { action->setDisabled(true); } else if (e == eNotify_OnEndNewScene || e == eNotify_OnEndLoad) { action->setDisabled(false); } } void HideActionWhileEntitiesDeselected(QAction* action, EEditorNotifyEvent editorNotifyEvent) { if (action == nullptr) { return; } switch (editorNotifyEvent) { case eNotify_OnEntitiesDeselected: { action->setVisible(false); break; } case eNotify_OnEntitiesSelected: { action->setVisible(true); break; } default: break; } } void DisableActionWhileInSimMode(QAction* action, EEditorNotifyEvent editorNotifyEvent) { if (action == nullptr) { return; } switch (editorNotifyEvent) { case eNotify_OnBeginSimulationMode: { action->setVisible(false); action->setDisabled(true); break; } case eNotify_OnEndSimulationMode: { action->setVisible(true); action->setDisabled(false); break; } default: break; } } } LevelEditorMenuHandler::LevelEditorMenuHandler( MainWindow* mainWindow, QtViewPaneManager* const viewPaneManager, QSettings& settings) : QObject(mainWindow) , m_mainWindow(mainWindow) , m_viewPaneManager(viewPaneManager) , m_actionManager(mainWindow->GetActionManager()) , m_settings(settings) { #if defined(AZ_PLATFORM_MAC) // Hide the non-native toolbar, then setNativeMenuBar to ensure it is always visible on macOS. m_mainWindow->menuBar()->hide(); m_mainWindow->menuBar()->setNativeMenuBar(true); #endif ComponentModeFramework::EditorComponentModeNotificationBus::Handler::BusConnect( AzToolsFramework::GetEntityContextId()); EditorMenuRequestBus::Handler::BusConnect(); } LevelEditorMenuHandler::~LevelEditorMenuHandler() { EditorMenuRequestBus::Handler::BusDisconnect(); ComponentModeFramework::EditorComponentModeNotificationBus::Handler::BusDisconnect(); } void LevelEditorMenuHandler::Initialize() { // make sure we can fix the view menus connect( m_viewPaneManager, &QtViewPaneManager::registeredPanesChanged, this, &LevelEditorMenuHandler::ResetToolsMenus); m_levelExtension = EditorUtils::LevelFile::GetDefaultFileExtension(); m_topLevelMenus << CreateFileMenu(); auto editMenu = CreateEditMenu(); auto editMenuWrapper = ActionManager::MenuWrapper(editMenu, m_actionManager); PopulateEditMenu(editMenuWrapper); m_editmenu = editMenu; m_topLevelMenus << editMenu; m_topLevelMenus << CreateGameMenu(); m_topLevelMenus << CreateToolsMenu(); m_topLevelMenus << CreateViewMenu(); m_topLevelMenus << CreateHelpMenu(); // have to do this after creating the AWS Menu for the first time ResetToolsMenus(); // Add our menus to the main window menu bar QMenuBar* menuBar = m_mainWindow->menuBar(); menuBar->clear(); for (QMenu* menu : m_topLevelMenus) { menuBar->addMenu(menu); } } bool LevelEditorMenuHandler::MRUEntryIsValid(const QString& entry, const QString& gameFolderPath) { if (entry.isEmpty()) { return false; } QFileInfo info(entry); if (!info.exists()) { return false; } if (!entry.endsWith(m_levelExtension)) { return false; } const QDir gameDir(gameFolderPath); QDir dir(entry); // actually pointing at file, first cdUp() gets us the parent dir while (dir.cdUp()) { if (dir == gameDir) { return true; } } return false; } void LevelEditorMenuHandler::IncrementViewPaneVersion() { m_viewPaneVersion++; } int LevelEditorMenuHandler::GetViewPaneVersion() const { return m_viewPaneVersion; } void LevelEditorMenuHandler::UpdateViewLayoutsMenu(ActionManager::MenuWrapper& layoutsMenu) { if (layoutsMenu.isNull()) { return; } QStringList layoutNames = m_viewPaneManager->LayoutNames(); std::sort(layoutNames.begin(), layoutNames.end(), CompareLayoutNames); layoutsMenu->clear(); const int MAX_LAYOUTS = ID_VIEW_LAYOUT_LAST - ID_VIEW_LAYOUT_FIRST + 1; // Component entity layout is the default QString componentLayoutLabel = tr("Component Entity Layout"); QAction* componentLayoutAction = layoutsMenu->addAction(componentLayoutLabel); connect(componentLayoutAction, &QAction::triggered, this, &LevelEditorMenuHandler::LoadComponentLayout); layoutsMenu.AddSeparator(); for (int i = 0; i < layoutNames.size() && i <= MAX_LAYOUTS; i++) { const QString layoutName = layoutNames[i]; QAction* action = layoutsMenu->addAction(layoutName); QMenu* subSubMenu = new QMenu(); QAction* subSubAction = subSubMenu->addAction(tr("Load")); connect(subSubAction, &QAction::triggered, this, [layoutName, this]() { m_mainWindow->ViewLoadPaneLayout(layoutName); }); subSubAction = subSubMenu->addAction(tr("Save")); connect(subSubAction, &QAction::triggered, this, [layoutName, this]() { m_mainWindow->ViewSavePaneLayout(layoutName); }); subSubAction = subSubMenu->addAction(tr("Rename...")); connect(subSubAction, &QAction::triggered, this, [layoutName, this]() { m_mainWindow->ViewRenamePaneLayout(layoutName); }); subSubAction = subSubMenu->addAction(tr("Delete")); connect(subSubAction, &QAction::triggered, this, [layoutName, this]() { m_mainWindow->ViewDeletePaneLayout(layoutName); }); action->setMenu(subSubMenu); } layoutsMenu.AddAction(ID_VIEW_SAVELAYOUT); layoutsMenu.AddAction(ID_VIEW_LAYOUT_LOAD_DEFAULT); } void LevelEditorMenuHandler::ResetToolsMenus() { if (!m_toolsMenu->isEmpty()) { // Clear everything from the Tools menu m_toolsMenu->clear(); AzToolsFramework::EditorMenuNotificationBus::Broadcast(&AzToolsFramework::EditorMenuNotificationBus::Handler::OnResetToolMenuItems); } QtViewPanes allRegisteredViewPanes = QtViewPaneManager::instance()->GetRegisteredPanes(); QMap > menuMap; CreateMenuMap(menuMap, allRegisteredViewPanes); CreateMenuOptions(&menuMap, m_toolsMenu, LyViewPane::CategoryTools); AzToolsFramework::EditorMenuNotificationBus::Broadcast(&AzToolsFramework::EditorMenuNotificationBus::Handler::OnPopulateToolMenuItems); m_toolsMenu.AddSeparator(); // Other auto otherSubMenu = m_toolsMenu.AddMenu(QObject::tr("Other")); CreateMenuOptions(&menuMap, otherSubMenu, LyViewPane::CategoryOther); m_toolsMenu.AddSeparator(); // Optional Sub Menus if (!menuMap.isEmpty()) { QMap >::iterator it = menuMap.begin(); while (it != menuMap.end()) { auto currentSubMenu = m_toolsMenu.AddMenu(it.key()); CreateMenuOptions(&menuMap, currentSubMenu, it.key().toStdString().c_str()); it = menuMap.begin(); } } } QMenu* LevelEditorMenuHandler::CreateFileMenu() { auto fileMenu = m_actionManager->AddMenu(tr("&File"), s_fileMenuId); connect(fileMenu, &QMenu::aboutToShow, this, &LevelEditorMenuHandler::OnUpdateOpenRecent); // New level auto fileNew = fileMenu.AddAction(ID_FILE_NEW); GetIEditor()->RegisterNotifyListener(new EditorListener(fileNew, [fileNew](EEditorNotifyEvent e) { DisableActionWhileLevelChanges(fileNew, e); })); // Open level... auto fileOpenLevel = fileMenu.AddAction(ID_FILE_OPEN_LEVEL); GetIEditor()->RegisterNotifyListener(new EditorListener(fileOpenLevel, [fileOpenLevel](EEditorNotifyEvent e) { DisableActionWhileLevelChanges(fileOpenLevel, e); })); #ifdef ENABLE_SLICE_EDITOR // New slice auto fileNewSlice = fileMenu.AddAction(ID_FILE_NEW_SLICE); GetIEditor()->RegisterNotifyListener(new EditorListener(fileNewSlice, [fileNewSlice](EEditorNotifyEvent e) { DisableActionWhileLevelChanges(fileNewSlice, e); })); // Open slice... auto fileOpenSlice = fileMenu.AddAction(ID_FILE_OPEN_SLICE); GetIEditor()->RegisterNotifyListener(new EditorListener(fileOpenSlice, [fileOpenSlice](EEditorNotifyEvent e) { DisableActionWhileLevelChanges(fileOpenSlice, e); })); #endif // Save Selected Slice auto saveSelectedSlice = fileMenu.AddAction(ID_FILE_SAVE_SELECTED_SLICE); saveSelectedSlice->setVisible(false); GetIEditor()->RegisterNotifyListener(new EditorListener(saveSelectedSlice, [saveSelectedSlice](EEditorNotifyEvent e) { HideActionWhileEntitiesDeselected(saveSelectedSlice, e); })); // Save Slice to Root auto saveSliceToRoot = fileMenu.AddAction(ID_FILE_SAVE_SLICE_TO_ROOT); saveSliceToRoot->setVisible(false); GetIEditor()->RegisterNotifyListener(new EditorListener(saveSliceToRoot, [saveSliceToRoot](EEditorNotifyEvent e) { HideActionWhileEntitiesDeselected(saveSliceToRoot, e); })); // Open Recent m_mostRecentLevelsMenu = fileMenu.AddMenu(tr("Open Recent")); connect(m_mostRecentLevelsMenu, &QMenu::aboutToShow, this, &LevelEditorMenuHandler::UpdateMRUFiles); OnUpdateOpenRecent(); // Import... auto assetImporterMenu = fileMenu.Get()->addAction(tr("Import...")); connect(assetImporterMenu, &QAction::triggered, this, &LevelEditorMenuHandler::ActivateAssetImporter); fileMenu.AddSeparator(); // Save fileMenu.AddAction(ID_FILE_SAVE_LEVEL); // Save As... fileMenu.AddAction(ID_FILE_SAVE_AS); // Save Level Statistics fileMenu.AddAction(ID_TOOLS_LOGMEMORYUSAGE); fileMenu.AddSeparator(); // Project Settings fileMenu.AddAction(ID_FILE_PROJECT_MANAGER_SETTINGS); // Platform Settings - Project Settings Tool // Shortcut must be set while adding the action otherwise it doesn't work fileMenu.Get()->addAction( tr(LyViewPane::ProjectSettingsTool), []() { QtViewPaneManager::instance()->OpenPane(LyViewPane::ProjectSettingsTool); }, tr("Ctrl+Shift+P")); fileMenu.AddSeparator(); fileMenu.AddAction(ID_FILE_PROJECT_MANAGER_NEW); fileMenu.AddAction(ID_FILE_PROJECT_MANAGER_OPEN); fileMenu.AddSeparator(); // NEWMENUS: NEEDS IMPLEMENTATION // should have the equivalent of a Close here; it should be close the current slice, but the editor isn't slice based right now // so that won't work. // instead, it should be Close of the level, but we don't have that either. I'm leaving it here so that it's obvious where UX intended it // to go //fileMenu.AddAction(ID_FILE_CLOSE); // Show Log File fileMenu.AddAction(ID_FILE_EDITLOGFILE); fileMenu.AddSeparator(); fileMenu.AddAction(ID_FILE_RESAVESLICES); fileMenu.AddSeparator(); fileMenu.AddAction(ID_APP_EXIT); return fileMenu; } void LevelEditorMenuHandler::PopulateEditMenu(ActionManager::MenuWrapper& editMenu) { // Undo editMenu.AddAction(ID_UNDO); // Redo editMenu.AddAction(ID_REDO); editMenu.AddSeparator(); // NEWMENUS: NEEDS IMPLEMENTATION // Not quite ready for these yet. Have to register them with the ActionManager in MainWindow.cpp when we're ready // editMenu->addAction(ID_EDIT_CUT); // editMenu->addAction(ID_EDIT_COPY); // editMenu->addAction(ID_EDIT_PASTE); // editMenu.AddSeparator(); // Duplicate editMenu.AddAction(AzToolsFramework::DuplicateSelect); // Delete editMenu.AddAction(AzToolsFramework::DeleteSelect); editMenu.AddSeparator(); // Select All editMenu.AddAction(AzToolsFramework::SelectAll); // Invert Selection editMenu.AddAction(AzToolsFramework::InvertSelect); editMenu.AddSeparator(); // New Viewport Interaction Model actions/shortcuts editMenu.AddAction(AzToolsFramework::EditPivot); editMenu.AddAction(AzToolsFramework::EditReset); editMenu.AddAction(AzToolsFramework::EditResetManipulator); editMenu.AddAction(AzToolsFramework::EditResetLocal); editMenu.AddAction(AzToolsFramework::EditResetWorld); // Hide Selection editMenu.AddAction(AzToolsFramework::HideSelection); // Show All editMenu.AddAction(AzToolsFramework::ShowAll); // Lock Selection editMenu.AddAction(AzToolsFramework::LockSelection); // UnLock All editMenu.AddAction(AzToolsFramework::UnlockAll); /* * The following block of code is part of the feature "Isolation Mode" and is temporarily * disabled for 1.10 release. * Jira: LY-49532 // Isolate Selected QAction* isolateSelectedAction = editMenu->addAction(tr("Isolate Selected")); connect(isolateSelectedAction, &QAction::triggered, this, []() { AzToolsFramework::ToolsApplicationRequestBus::Broadcast(&AzToolsFramework::ToolsApplicationRequestBus::Events::EnterEditorIsolationMode); }); // Exit Isolation QAction* exitIsolationAction = editMenu->addAction(tr("Exit Isolation")); connect(exitIsolationAction, &QAction::triggered, this, []() { AzToolsFramework::ToolsApplicationRequestBus::Broadcast(&AzToolsFramework::ToolsApplicationRequestBus::Events::ExitEditorIsolationMode); }); connect(editMenu, &QMenu::aboutToShow, this, [isolateSelectedAction, exitIsolationAction]() { bool isInIsolationMode = false; AzToolsFramework::ToolsApplicationRequestBus::BroadcastResult(isInIsolationMode, &AzToolsFramework::ToolsApplicationRequestBus::Events::IsEditorInIsolationMode); if (isInIsolationMode) { isolateSelectedAction->setDisabled(true); exitIsolationAction->setDisabled(false); } else { isolateSelectedAction->setDisabled(false); exitIsolationAction->setDisabled(true); } }); */ editMenu.AddSeparator(); // Modify Menu auto modifyMenu = editMenu.AddMenu(tr("&Modify")); auto snapMenu = modifyMenu.AddMenu(tr("Snap")); snapMenu.AddAction(ID_SNAPANGLE); auto transformModeMenu = modifyMenu.AddMenu(tr("Transform Mode")); transformModeMenu.AddAction(ID_EDITMODE_MOVE); transformModeMenu.AddAction(ID_EDITMODE_ROTATE); transformModeMenu.AddAction(ID_EDITMODE_SCALE); editMenu.AddSeparator(); // Editor Settings auto editorSettingsMenu = editMenu.AddMenu(tr("Editor Settings")); // Global Preferences... editorSettingsMenu.AddAction(ID_TOOLS_PREFERENCES); // Graphics Settings... editorSettingsMenu.AddAction(ID_GRAPHICS_SETTINGS); // Editor Settings Manager AddOpenViewPaneAction(editorSettingsMenu, LyViewPane::EditorSettingsManager); // Graphics Performance auto graphicPerformanceSubMenu = editorSettingsMenu.AddMenu(QObject::tr("Graphics Performance")); auto pcMenu = graphicPerformanceSubMenu.AddMenu(tr("PC")); pcMenu.AddAction(ID_GAME_PC_ENABLEVERYHIGHSPEC); pcMenu.AddAction(ID_GAME_PC_ENABLEHIGHSPEC); pcMenu.AddAction(ID_GAME_PC_ENABLEMEDIUMSPEC); pcMenu.AddAction(ID_GAME_PC_ENABLELOWSPEC); auto osxmetalMenu = graphicPerformanceSubMenu.AddMenu(tr("OSX Metal")); osxmetalMenu.AddAction(ID_GAME_OSXMETAL_ENABLEVERYHIGHSPEC); osxmetalMenu.AddAction(ID_GAME_OSXMETAL_ENABLEHIGHSPEC); osxmetalMenu.AddAction(ID_GAME_OSXMETAL_ENABLEMEDIUMSPEC); osxmetalMenu.AddAction(ID_GAME_OSXMETAL_ENABLELOWSPEC); auto androidMenu = graphicPerformanceSubMenu.AddMenu(tr("Android")); androidMenu.AddAction(ID_GAME_ANDROID_ENABLEVERYHIGHSPEC); androidMenu.AddAction(ID_GAME_ANDROID_ENABLEHIGHSPEC); androidMenu.AddAction(ID_GAME_ANDROID_ENABLEMEDIUMSPEC); androidMenu.AddAction(ID_GAME_ANDROID_ENABLELOWSPEC); auto iosMenu = graphicPerformanceSubMenu.AddMenu(tr("iOS")); iosMenu.AddAction(ID_GAME_IOS_ENABLEVERYHIGHSPEC); iosMenu.AddAction(ID_GAME_IOS_ENABLEHIGHSPEC); iosMenu.AddAction(ID_GAME_IOS_ENABLEMEDIUMSPEC); iosMenu.AddAction(ID_GAME_IOS_ENABLELOWSPEC); #if defined(AZ_TOOLS_EXPAND_FOR_RESTRICTED_PLATFORMS) #define AZ_RESTRICTED_PLATFORM_EXPANSION(CodeName, CODENAME, codename, PrivateName, PRIVATENAME, privatename, PublicName, PUBLICNAME, publicname, PublicAuxName1, PublicAuxName2, PublicAuxName3)\ auto publicname##Menu = graphicPerformanceSubMenu.AddMenu(tr(PublicAuxName2));\ publicname##Menu.AddAction(ID_GAME_##CODENAME##_ENABLEHIGHSPEC);\ publicname##Menu.AddAction(ID_GAME_##CODENAME##_ENABLEMEDIUMSPEC);\ publicname##Menu.AddAction(ID_GAME_##CODENAME##_ENABLELOWSPEC); AZ_TOOLS_EXPAND_FOR_RESTRICTED_PLATFORMS #undef AZ_RESTRICTED_PLATFORM_EXPANSION #endif // Keyboard Customization auto keyboardCustomizationMenu = editorSettingsMenu.AddMenu(tr("Keyboard Customization")); keyboardCustomizationMenu.AddAction(ID_TOOLS_CUSTOMIZEKEYBOARD); keyboardCustomizationMenu.AddAction(ID_TOOLS_EXPORT_SHORTCUTS); keyboardCustomizationMenu.AddAction(ID_TOOLS_IMPORT_SHORTCUTS); } QMenu* LevelEditorMenuHandler::CreateEditMenu() { return m_actionManager->AddMenu(tr("&Edit"), s_editMenuId); } QMenu* LevelEditorMenuHandler::CreateGameMenu() { auto gameMenu = m_actionManager->AddMenu(tr("&Game"), s_gameMenuId); // Play Game gameMenu.AddAction(ID_VIEW_SWITCHTOGAME); // Enable Physics/AI gameMenu.AddAction(ID_SWITCH_PHYSICS); gameMenu.AddSeparator(); bool usePrefabSystemForLevels = false; AzFramework::ApplicationRequests::Bus::BroadcastResult( usePrefabSystemForLevels, &AzFramework::ApplicationRequests::IsPrefabSystemForLevelsEnabled); if (!usePrefabSystemForLevels) { // Export to Engine gameMenu.AddAction(ID_FILE_EXPORTTOGAMENOSURFACETEXTURE); } // Export Selected Objects gameMenu.AddAction(ID_FILE_EXPORT_SELECTEDOBJECTS); // Export Occlusion Mesh gameMenu.AddAction(ID_FILE_EXPORTOCCLUSIONMESH); gameMenu.AddSeparator(); // Synchronize Player with Camera gameMenu.AddAction(ID_GAME_SYNCPLAYER); gameMenu.AddSeparator(); // Audio auto audioMenu = gameMenu.AddMenu(tr("Audio")); // Stop All Sounds audioMenu.AddAction(ID_SOUND_STOPALLSOUNDS); // Refresh Audio audioMenu.AddAction(ID_AUDIO_REFRESH_AUDIO_SYSTEM); gameMenu.AddSeparator(); CreateDebuggingSubMenu(gameMenu); return gameMenu; } QMenu* LevelEditorMenuHandler::CreateToolsMenu() { // Tools m_toolsMenu = m_actionManager->AddMenu(tr("&Tools"), s_toolMenuId); return m_toolsMenu; } QMenu* LevelEditorMenuHandler::CreateViewMenu() { auto viewMenu = m_actionManager->AddMenu(tr("&View"), s_viewMenuId); // NEWMENUS: NEEDS IMPLEMENTATION // minimize window - Ctrl+M // Zoom - Ctrl+Plus(+) -> Need the inverse too? #ifdef FEATURE_ORTHOGRAPHIC_VIEW // Cycle Viewports viewMenu.AddAction(ID_VIEW_CYCLE2DVIEWPORT); #endif // Center on Selection viewMenu.AddAction(ID_MODIFY_GOTO_SELECTION); // Show Quick Access Bar viewMenu.AddAction(ID_OPEN_QUICK_ACCESS_BAR); // Layouts if (CViewManager::IsMultiViewportEnabled()) // Only supports 1 viewport for now. { // Disable Layouts menu m_layoutsMenu = viewMenu.AddMenu(tr("Layouts")); connect(m_viewPaneManager, &QtViewPaneManager::savedLayoutsChanged, this, [this]() { UpdateViewLayoutsMenu(m_layoutsMenu); }); UpdateViewLayoutsMenu(m_layoutsMenu); } // Viewport auto viewportViewsMenuWrapper = viewMenu.AddMenu(tr("Viewport")); #ifdef FEATURE_ORTHOGRAPHIC_VIEW auto viewportTypesMenuWrapper = viewportViewsMenuWrapper.AddMenu(tr("Viewport Type")); m_viewportViewsMenu = viewportViewsMenuWrapper; connect(viewportTypesMenuWrapper, &QMenu::aboutToShow, this, &LevelEditorMenuHandler::UpdateOpenViewPaneMenu); InitializeViewPaneMenu(m_actionManager, viewportTypesMenuWrapper, [](const QtViewPane& view) { return view.IsViewportPane(); }); viewportViewsMenuWrapper.AddSeparator(); #endif if (CViewManager::IsMultiViewportEnabled()) { viewportViewsMenuWrapper.AddAction(ID_VIEW_CONFIGURELAYOUT); } viewportViewsMenuWrapper.AddSeparator(); viewportViewsMenuWrapper.AddAction(ID_DISPLAY_GOTOPOSITION); viewportViewsMenuWrapper.AddAction(ID_MODIFY_GOTO_SELECTION); auto gotoLocationMenu = viewportViewsMenuWrapper.AddMenu(tr("Go to Location")); gotoLocationMenu.AddAction(ID_GOTO_LOC1); gotoLocationMenu.AddAction(ID_GOTO_LOC2); gotoLocationMenu.AddAction(ID_GOTO_LOC3); gotoLocationMenu.AddAction(ID_GOTO_LOC4); gotoLocationMenu.AddAction(ID_GOTO_LOC5); gotoLocationMenu.AddAction(ID_GOTO_LOC6); gotoLocationMenu.AddAction(ID_GOTO_LOC7); gotoLocationMenu.AddAction(ID_GOTO_LOC8); gotoLocationMenu.AddAction(ID_GOTO_LOC9); gotoLocationMenu.AddAction(ID_GOTO_LOC10); gotoLocationMenu.AddAction(ID_GOTO_LOC11); gotoLocationMenu.AddAction(ID_GOTO_LOC12); auto rememberLocationMenu = viewportViewsMenuWrapper.AddMenu(tr("Remember Location")); rememberLocationMenu.AddAction(ID_TAG_LOC1); rememberLocationMenu.AddAction(ID_TAG_LOC2); rememberLocationMenu.AddAction(ID_TAG_LOC3); rememberLocationMenu.AddAction(ID_TAG_LOC4); rememberLocationMenu.AddAction(ID_TAG_LOC5); rememberLocationMenu.AddAction(ID_TAG_LOC6); rememberLocationMenu.AddAction(ID_TAG_LOC7); rememberLocationMenu.AddAction(ID_TAG_LOC8); rememberLocationMenu.AddAction(ID_TAG_LOC9); rememberLocationMenu.AddAction(ID_TAG_LOC10); rememberLocationMenu.AddAction(ID_TAG_LOC11); rememberLocationMenu.AddAction(ID_TAG_LOC12); viewportViewsMenuWrapper.AddSeparator(); auto switchCameraMenu = viewportViewsMenuWrapper.AddMenu(tr("Switch Camera")); switchCameraMenu.AddAction(ID_SWITCHCAMERA_DEFAULTCAMERA); switchCameraMenu.AddAction(ID_SWITCHCAMERA_SEQUENCECAMERA); switchCameraMenu.AddAction(ID_SWITCHCAMERA_SELECTEDCAMERA); switchCameraMenu.AddAction(ID_SWITCHCAMERA_NEXT); // NEWMENUS: // MISSING AVIRECORDER viewportViewsMenuWrapper.AddSeparator(); viewportViewsMenuWrapper.AddAction(ID_DISPLAY_SHOWHELPERS); // Refresh Style viewMenu.AddAction(ID_SKINS_REFRESH); return viewMenu; } QMenu* LevelEditorMenuHandler::CreateHelpMenu() { // Help auto helpMenu = m_actionManager->AddMenu(tr("&Help"), s_helpMenuId); auto lineEditSearchAction = new QWidgetAction(m_mainWindow); auto containerWidget = new QWidget(m_mainWindow); auto lineEdit = new AzQtComponents::SearchLineEdit(m_mainWindow); QHBoxLayout *layout = new QHBoxLayout; layout->setContentsMargins(0, 0, 0, 0); layout->addWidget(lineEdit); containerWidget->setLayout(layout); containerWidget->setContentsMargins(2, 0, 2, 0); lineEdit->setPlaceholderText(tr("Search documentation")); lineEditSearchAction->setDefaultWidget(containerWidget); auto searchAction = [lineEdit]() { auto text = lineEdit->text(); if (text.isEmpty()) { QDesktopServices::openUrl(QUrl("https://o3de.org/docs/")); } else { static const int versionStringSize = 128; char productVersionString[versionStringSize]; const SFileVersion& productVersion = gEnv->pSystem->GetProductVersion(); productVersion.ToString(productVersionString, versionStringSize); QUrl docSearchUrl("https://o3de.org/docs/"); QUrlQuery docSearchQuery; docSearchQuery.addQueryItem("query", text); docSearchUrl.setQuery(docSearchQuery); QDesktopServices::openUrl(docSearchUrl); } lineEdit->clear(); }; connect(lineEdit, &QLineEdit::returnPressed, this, searchAction); connect(helpMenu.Get(), &QMenu::aboutToHide, lineEdit, &QLineEdit::clear); connect(helpMenu.Get(), &QMenu::aboutToShow, lineEdit, &QLineEdit::clearFocus); helpMenu->addAction(lineEditSearchAction); // Tutorials helpMenu.AddAction(ID_DOCUMENTATION_TUTORIALS); // Documentation auto documentationMenu = helpMenu.AddMenu(tr("Documentation")); // Open 3D Engine Documentation documentationMenu.AddAction(ID_DOCUMENTATION_O3DE); // GameLift Documentation documentationMenu.AddAction(ID_DOCUMENTATION_GAMELIFT); // Release Notes documentationMenu.AddAction(ID_DOCUMENTATION_RELEASENOTES); // GameDev Resources auto gameDevResourceMenu = helpMenu.AddMenu(tr("GameDev Resources")); // Game Dev Blog gameDevResourceMenu.AddAction(ID_DOCUMENTATION_GAMEDEVBLOG); // Forums gameDevResourceMenu.AddAction(ID_DOCUMENTATION_FORUMS); // AWS Support gameDevResourceMenu.AddAction(ID_DOCUMENTATION_AWSSUPPORT); helpMenu.AddSeparator(); // About Open 3D Engine helpMenu.AddAction(ID_APP_ABOUT); // Welcome dialog auto helpWelcome = helpMenu.AddAction(ID_APP_SHOW_WELCOME); GetIEditor()->RegisterNotifyListener(new EditorListener(helpWelcome, [helpWelcome](EEditorNotifyEvent e) { DisableActionWhileLevelChanges(helpWelcome, e); })); return helpMenu; } QAction* LevelEditorMenuHandler::CreateViewPaneAction(const QtViewPane* view) { QAction* action = m_actionManager->HasAction(view->m_id) ? m_actionManager->GetAction(view->m_id) : nullptr; if (!action) { const QString menuText = view->m_options.optionalMenuText.length() > 0 ? view->m_options.optionalMenuText : view->m_name; action = new QAction(menuText, this); action->setObjectName(view->m_name); action->setCheckable(true); if (view->m_options.showOnToolsToolbar) { action->setIcon(QIcon(view->m_options.toolbarIcon)); } m_actionManager->AddAction(view->m_id, action); if (!view->m_options.shortcut.isEmpty()) { action->setShortcut(view->m_options.shortcut); } QObject::connect( action, &QAction::triggered, this, &LevelEditorMenuHandler::checkOrOpenView, Qt::UniqueConnection); } return action; } // Function used to show menu options without its icon and be able to toggle shortcut visibility in the new menu layout // This is a work around for the fact that setting the shortcut on the original action isn't working for some reasons // and we need to investigate it further in the future. QAction* LevelEditorMenuHandler::CreateViewPaneMenuItem( [[maybe_unused]] ActionManager* actionManager, ActionManager::MenuWrapper& menu, const QtViewPane* view) { QAction* action = CreateViewPaneAction(view); if (action && view->m_options.isDisabledInSimMode) { AddDisableActionInSimModeListener(action); } menu->addAction(action); if (view->m_options.showOnToolsToolbar) { m_mainWindow->GetToolbarManager()->AddButtonToEditToolbar(action); } return action; } void LevelEditorMenuHandler::InitializeViewPaneMenu( ActionManager* actionManager, ActionManager::MenuWrapper& menu, AZStd::function functor) { QtViewPanes views = QtViewPaneManager::instance()->GetRegisteredPanes(); for (auto it = views.cbegin(), end = views.cend(); it != end; ++it) { const QtViewPane& view = *it; if (!functor(view)) { continue; } CreateViewPaneMenuItem(actionManager, menu, it); } } void LevelEditorMenuHandler::LoadComponentLayout() { m_viewPaneManager->RestoreDefaultLayout(); } QMap> LevelEditorMenuHandler::CreateMenuMap( QMap >& menuMap, QtViewPanes& allRegisteredViewPanes) { // set up view panes to each category for (QtViewPane& viewpane : allRegisteredViewPanes) { // only store the view panes that should be shown in the menu if (!viewpane.IsViewportPane()) { menuMap[viewpane.m_category].push_back(&viewpane); } } return menuMap; } // sort the menu option name in alphabetically order struct CaseInsensitiveStringCompare { bool operator() (const QString& left, const QString& right) const { return left.toLower() < right.toLower(); } }; static void LaunchLuaEditor() { AzToolsFramework::EditorRequestBus::Broadcast( &AzToolsFramework::EditorRequests::LaunchLuaEditor, nullptr); } void LevelEditorMenuHandler::CreateMenuOptions( QMap >* menuMap, ActionManager::MenuWrapper& menu, const char* category) { // list in the menu and remove this menu category from the menuMap QList menuList = menuMap->take(category); std::map, CaseInsensitiveStringCompare> sortMenuMap; // store menu options into the map // name as a key, functionality as a value for (QtViewPane* viewpane : menuList) { if (viewpane->m_options.builtInActionId != LyViewPane::NO_BUILTIN_ACTION) { sortMenuMap[viewpane->m_name] = [this, &menu, viewpane]() { // Handle shortcuts for actions with a built-in ID since they // bypass our CreateViewPaneMenuItem method QAction* action = menu.AddAction(viewpane->m_options.builtInActionId); if (action) { if (viewpane->m_options.isDisabledInSimMode) { AddDisableActionInSimModeListener(action); } if (!viewpane->m_options.shortcut.isEmpty()) { action->setShortcut(viewpane->m_options.shortcut); } } }; } else { const QString menuText = viewpane->m_options.optionalMenuText.length() > 0 ? viewpane->m_options.optionalMenuText : viewpane->m_name; sortMenuMap[menuText] = [this, viewpane, &menu]() { CreateViewPaneMenuItem(m_actionManager, menu, viewpane); }; } } if (category == LyViewPane::CategoryTools) { // Add LUA Editor into the Tools map sortMenuMap[s_LUAEditorName] = [this, &menu]() { auto luaEditormenu = menu.AddAction(ID_TOOLS_LUA_EDITOR); connect(luaEditormenu, &QAction::triggered, this, &LaunchLuaEditor, Qt::UniqueConnection); }; } // add each menu option into the menu std::map, CaseInsensitiveStringCompare>::iterator iter; for (iter = sortMenuMap.begin(); iter != sortMenuMap.end(); ++iter) { iter->second(); } } void LevelEditorMenuHandler::CreateDebuggingSubMenu(ActionManager::MenuWrapper gameMenu) { // DebuggingSubMenu auto debuggingSubMenu = gameMenu.AddMenu(QObject::tr("Debugging")); // Error Report AddOpenViewPaneAction(debuggingSubMenu, LyViewPane::ErrorReport); debuggingSubMenu.AddSeparator(); // Configure Toolbox Macros debuggingSubMenu.AddAction(ID_TOOLS_CONFIGURETOOLS); // Toolbox Macros m_macrosMenu = debuggingSubMenu.AddMenu(tr("ToolBox Macros")); connect(m_macrosMenu, &QMenu::aboutToShow, this, &LevelEditorMenuHandler::UpdateMacrosMenu, Qt::UniqueConnection); UpdateMacrosMenu(); } void LevelEditorMenuHandler::UpdateMRUFiles() { auto cryEdit = CCryEditApp::instance(); RecentFileList* mruList = cryEdit->GetRecentFileList(); const int numMru = mruList->GetSize(); if (!m_mostRecentLevelsMenu) { return; } // Remove most recent items m_mostRecentLevelsMenu->clear(); // Insert mrus QString sCurDir = QString(Path::GetEditingGameDataFolder().c_str()) + QDir::separator(); QFileInfo gameDir(sCurDir); // Pass it through QFileInfo so it comes out normalized const QString gameDirPath = gameDir.absolutePath(); for (int i = 0; i < numMru; ++i) { if (!MRUEntryIsValid((*mruList)[i], gameDirPath)) { continue; } QString displayName; mruList->GetDisplayName(displayName, i, sCurDir); QString entry = QString("%1 %2").arg(i + 1).arg(displayName); QAction* action = m_actionManager->GetAction(ID_FILE_MRU_FILE1 + i); action->setText(entry); m_actionManager->RegisterActionHandler(ID_FILE_MRU_FILE1 + i, [i]() { auto cryEdit = CCryEditApp::instance(); RecentFileList* mruList = cryEdit->GetRecentFileList(); // Check file is still available if (mruList->GetSize() > i) { cryEdit->OpenDocumentFile((*mruList)[i].toUtf8().data()); } }); m_actionManager->RegisterUpdateCallback(ID_FILE_MRU_FILE1 + i, cryEdit, &CCryEditApp::OnUpdateFileOpen); GetIEditor()->RegisterNotifyListener(new EditorListener(action, [action](EEditorNotifyEvent e) { DisableActionWhileLevelChanges(action, e); })); m_mostRecentLevelsMenu->addAction(action); } // Used when disabling the "Open Recent" menu options OnUpdateOpenRecent(); m_mostRecentLevelsMenu->addSeparator(); // Clear All auto clearAllMenu = m_mostRecentLevelsMenu->addAction(tr("Clear All")); connect(clearAllMenu, &QAction::triggered, this, &LevelEditorMenuHandler::ClearAll); } void LevelEditorMenuHandler::ClearAll() { RecentFileList* mruList = CCryEditApp::instance()->GetRecentFileList(); // remove everything from the mru list for (int i = mruList->GetSize(); i > 0; i--) { mruList->Remove(i - 1); } // save the settings immediately to the registry mruList->WriteList(); // re-update the menus UpdateMRUFiles(); } // Used for disabling "Open Recent" menu option void LevelEditorMenuHandler::OnUpdateOpenRecent() { RecentFileList* mruList = CCryEditApp::instance()->GetRecentFileList(); const int numMru = mruList->GetSize(); QString currentMru = numMru > 0 ? (*mruList)[0] : QString(); if (!currentMru.isEmpty()) { m_mostRecentLevelsMenu->setEnabled(true); } else { m_mostRecentLevelsMenu->setEnabled(false); } } void LevelEditorMenuHandler::OnUpdateMacrosMenu() { auto tools = GetIEditor()->GetToolBoxManager(); const int macroCount = tools->GetMacroCount(true); if (macroCount <= 0) { m_macrosMenu->setEnabled(false); } else { m_macrosMenu->setEnabled(true); } } void LevelEditorMenuHandler::UpdateMacrosMenu() { m_macrosMenu->clear(); auto tools = GetIEditor()->GetToolBoxManager(); const int macroCount = tools->GetMacroCount(true); for (int i = 0; i < macroCount; ++i) { auto macro = tools->GetMacro(i, true); const int toolbarId = macro->GetToolbarId(); if (toolbarId == -1 || toolbarId == ID_TOOLS_TOOL1) { m_macrosMenu->addAction(macro->action()); } } } void LevelEditorMenuHandler::UpdateOpenViewPaneMenu() { // This function goes through all the viewport menu actions (top, left, perspective...) // and adds a check mark on the viewport that has focus QtViewport* viewport = m_mainWindow->GetActiveViewport(); QString activeViewportName = viewport ? viewport->GetName() : QString(); for (QAction* action : m_viewportViewsMenu->actions()) { action->setChecked(action->objectName() == activeViewportName); } } void LevelEditorMenuHandler::checkOrOpenView() { QAction* action = qobject_cast(sender()); const QString viewPaneName = action->objectName(); // If this action is checkable and was just unchecked, then we // should close the view pane if (action->isCheckable() && !action->isChecked()) { QtViewPaneManager::instance()->ClosePane(viewPaneName); } // Otherwise, this action should open the view pane else { const QtViewPane* pane = QtViewPaneManager::instance()->OpenPane(viewPaneName); QObject::connect(pane->Widget(), &QWidget::destroyed, action, [action] { action->setChecked(false); }); } } void LevelEditorMenuHandler::AddDisableActionInSimModeListener(QAction* action) { GetIEditor()->RegisterNotifyListener(new EditorListener(action, [action](EEditorNotifyEvent e) { DisableActionWhileInSimMode(action, e); })); } void LevelEditorMenuHandler::EnteredComponentMode(const AZStd::vector& /*componentModeTypes*/) { auto menuWrapper = m_actionManager->FindMenu(s_editMenuId); if (!menuWrapper.isNull()) { // copy of menu actions auto actions = menuWrapper.Get()->actions(); // remove all non-reserved edit menu options actions.erase( std::remove_if(actions.begin(), actions.end(), [](QAction* action) { return !action->property("Reserved").toBool(); }), actions.end()); // clear and update the menu with new actions menuWrapper.Get()->clear(); menuWrapper.Get()->addActions(actions); } } void LevelEditorMenuHandler::LeftComponentMode(const AZStd::vector& /*componentModeTypes*/) { RestoreEditMenuToDefault(); } void LevelEditorMenuHandler::AddEditMenuAction(QAction* action) { auto menuWrapper = m_actionManager->FindMenu(s_editMenuId); if (!menuWrapper.isNull()) { menuWrapper.Get()->addAction(action); } } void LevelEditorMenuHandler::AddMenuAction(AZStd::string_view categoryId, QAction* action, bool addToToolsToolbar) { auto menuWrapper = m_actionManager->FindMenu(categoryId.data()); if (menuWrapper.isNull()) { AZ_Assert(false, "No %s category exists in Editor menu."); return; } menuWrapper.Get()->addAction(action); if (addToToolsToolbar) { m_mainWindow->GetToolbarManager()->AddButtonToEditToolbar(action); } } void LevelEditorMenuHandler::RestoreEditMenuToDefault() { auto menuWrapper = m_actionManager->FindMenu(s_editMenuId); if (!menuWrapper.isNull()) { menuWrapper.Get()->clear(); PopulateEditMenu(menuWrapper); } } #include