b815c203da
Engine improvements/fixes Fixed behavior that made the editor automated test to be sometimes stuck if lost the focus is lost. Added support for specifying multiple tests to in batch to the editor, this is achieved by passing --runpythontest with the tests separated by ';' Added new cmdline argument --project-user-path for overriding the user path. This allows to have multiple editors running writing logs and crash logs in different locations. Moved responsability of exiting after a test finishes/passes out of ExecuteByFilenameAsTest, callers will use the bool return to know if the test passed. Editor test batch and parallelization implementation: Now the external python portion of the editor tests will be specified via test specs which will generate the test. Requiring no code. This is almost a data-driven approach. Tests can be specified as single tests, parallel, batchable or batchable+parallel Command line arguments for pytest to override the maximum number of editors, disable parallelization or batching. Automated tests for testing this new editor testing utility Signed-off-by: Garcia Ruiz <aljanru@amazon.co.uk> Co-authored-by: Garcia Ruiz <aljanru@amazon.co.uk>
1960 lines
67 KiB
C++
1960 lines
67 KiB
C++
/*
|
|
* 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"
|
|
|
|
#include "MainWindow.h"
|
|
|
|
#include <algorithm>
|
|
|
|
// AWs Native SDK
|
|
AZ_PUSH_DISABLE_WARNING(4251 4355 4996, "-Wunknown-warning-option")
|
|
#include <aws/core/auth/AWSCredentialsProvider.h>
|
|
AZ_POP_DISABLE_WARNING
|
|
|
|
// Qt
|
|
#include <QMenuBar>
|
|
#include <QDebug>
|
|
#include <QMessageBox>
|
|
#include <QInputDialog>
|
|
#include <QHBoxLayout>
|
|
#ifdef Q_OS_WIN
|
|
#include <QAbstractEventDispatcher>
|
|
#endif
|
|
|
|
// AzCore
|
|
#include <AzCore/std/smart_ptr/make_shared.h>
|
|
#include <AzCore/Component/ComponentApplication.h>
|
|
#include <AzCore/Settings/SettingsRegistryMergeUtils.h>
|
|
#include <AzCore/Interface/Interface.h>
|
|
#include <AzCore/Utils/Utils.h>
|
|
|
|
// AzFramework
|
|
#include <AzFramework/API/ApplicationAPI.h>
|
|
#include <AzFramework/Asset/AssetSystemBus.h>
|
|
#include <AzFramework/Input/Devices/Mouse/InputDeviceMouse.h>
|
|
#include <AzFramework/Network/SocketConnection.h>
|
|
#include <AzFramework/Asset/AssetSystemComponent.h>
|
|
|
|
// AzToolsFramework
|
|
#include <AzToolsFramework/Application/Ticker.h>
|
|
#include <AzToolsFramework/API/EditorWindowRequestBus.h>
|
|
#include <AzToolsFramework/API/EditorAnimationSystemRequestBus.h>
|
|
#include <AzToolsFramework/SourceControl/QtSourceControlNotificationHandler.h>
|
|
#include <AzToolsFramework/PythonTerminal/ScriptTermDialog.h>
|
|
|
|
// AzQtComponents
|
|
#include <AzQtComponents/Buses/ShortcutDispatch.h>
|
|
#include <AzQtComponents/Components/DockMainWindow.h>
|
|
#include <AzQtComponents/Components/Widgets/SpinBox.h>
|
|
#include <AzQtComponents/Components/Style.h>
|
|
#include <AzQtComponents/Components/WindowDecorationWrapper.h>
|
|
#include <AzQtComponents/DragAndDrop/MainWindowDragAndDrop.h>
|
|
|
|
// Editor
|
|
#include "Resource.h"
|
|
#include "Core/LevelEditorMenuHandler.h"
|
|
#include "ShortcutDispatcher.h"
|
|
#include "LayoutWnd.h"
|
|
#include "AssetImporter/AssetImporterManager/AssetImporterManager.h"
|
|
#include "AssetImporter/AssetImporterManager/AssetImporterDragAndDropHandler.h"
|
|
#include "CryEdit.h"
|
|
#include "Controls/ConsoleSCB.h"
|
|
#include "ViewManager.h"
|
|
#include "CryEditDoc.h"
|
|
#include "ToolBox.h"
|
|
#include "LevelIndependentFileMan.h"
|
|
#include "GameEngine.h"
|
|
#include "MainStatusBar.h"
|
|
#include "ToolbarCustomizationDialog.h"
|
|
#include "ToolbarManager.h"
|
|
#include "Core/QtEditorApplication.h"
|
|
#include "UndoDropDown.h"
|
|
#include "CVarMenu.h"
|
|
#include "EditorViewportSettings.h"
|
|
|
|
#include "KeyboardCustomizationSettings.h"
|
|
#include "CustomizeKeyboardDialog.h"
|
|
#include "QtViewPaneManager.h"
|
|
#include "ViewPane.h"
|
|
#include "Include/IObjectManager.h"
|
|
#include "Include/Command.h"
|
|
#include "Commands/CommandManager.h"
|
|
#include "SettingsManagerDialog.h"
|
|
|
|
#include "TrackView/TrackViewDialog.h"
|
|
#include "ErrorReportDialog.h"
|
|
|
|
#include "Dialogs/PythonScriptsDialog.h"
|
|
|
|
#include "AzAssetBrowser/AzAssetBrowserWindow.h"
|
|
#include "AssetEditor/AssetEditorWindow.h"
|
|
#include "ActionManager.h"
|
|
|
|
#include <ImGuiBus.h>
|
|
|
|
using namespace AZ;
|
|
using namespace AzQtComponents;
|
|
using namespace AzToolsFramework;
|
|
|
|
#define LAYOUTS_PATH "Editor\\Layouts\\"
|
|
#define LAYOUTS_EXTENSION ".layout"
|
|
#define LAYOUTS_WILDCARD "*.layout"
|
|
#define DUMMY_LAYOUT_NAME "Dummy_Layout"
|
|
|
|
static const char* g_openViewPaneEventName = "OpenViewPaneEvent"; //Sent when users open view panes;
|
|
static const char* g_viewPaneAttributeName = "ViewPaneName"; //Name of the current view pane
|
|
static const char* g_openLocationAttributeName = "OpenLocation"; //Indicates where the current view pane is opened from
|
|
|
|
static const char* g_assetImporterName = "AssetImporter";
|
|
|
|
class CEditorOpenViewCommand
|
|
: public _i_reference_target_t
|
|
{
|
|
QString m_className;
|
|
public:
|
|
CEditorOpenViewCommand(IEditor* pEditor, const QString& className)
|
|
: m_pEditor(pEditor)
|
|
, m_className(className)
|
|
{
|
|
assert(m_pEditor);
|
|
}
|
|
void Execute()
|
|
{
|
|
// Create browse mode for this category.
|
|
m_pEditor->OpenView(m_className);
|
|
}
|
|
|
|
private:
|
|
IEditor* m_pEditor;
|
|
};
|
|
|
|
namespace
|
|
{
|
|
// The purpose of this vector is just holding shared pointers, so CEditorOpenViewCommand dtors are called at exit
|
|
std::vector<_smart_ptr<CEditorOpenViewCommand> > s_openViewCmds;
|
|
}
|
|
|
|
class EngineConnectionListener
|
|
: public AzFramework::EngineConnectionEvents::Bus::Handler
|
|
, public AzFramework::AssetSystemInfoBus::Handler
|
|
{
|
|
public:
|
|
using EConnectionState = AzFramework::SocketConnection::EConnectionState;
|
|
|
|
public:
|
|
EngineConnectionListener()
|
|
: m_state(EConnectionState::Disconnected)
|
|
{
|
|
AzFramework::EngineConnectionEvents::Bus::Handler::BusConnect();
|
|
AzFramework::AssetSystemInfoBus::Handler::BusConnect();
|
|
|
|
AzFramework::SocketConnection* engineConnection = AzFramework::SocketConnection::GetInstance();
|
|
if (engineConnection)
|
|
{
|
|
m_state = engineConnection->GetConnectionState();
|
|
}
|
|
}
|
|
|
|
~EngineConnectionListener()
|
|
{
|
|
AzFramework::AssetSystemInfoBus::Handler::BusDisconnect();
|
|
AzFramework::EngineConnectionEvents::Bus::Handler::BusDisconnect();
|
|
}
|
|
|
|
public:
|
|
virtual void Connected([[maybe_unused]] AzFramework::SocketConnection* connection)
|
|
{
|
|
m_state = EConnectionState::Connected;
|
|
}
|
|
virtual void Connecting([[maybe_unused]] AzFramework::SocketConnection* connection)
|
|
{
|
|
m_state = EConnectionState::Connecting;
|
|
}
|
|
virtual void Listening([[maybe_unused]] AzFramework::SocketConnection* connection)
|
|
{
|
|
m_state = EConnectionState::Listening;
|
|
}
|
|
virtual void Disconnecting([[maybe_unused]] AzFramework::SocketConnection* connection)
|
|
{
|
|
m_state = EConnectionState::Disconnecting;
|
|
}
|
|
virtual void Disconnected([[maybe_unused]] AzFramework::SocketConnection* connection)
|
|
{
|
|
m_state = EConnectionState::Disconnected;
|
|
}
|
|
|
|
virtual void AssetCompilationSuccess(const AZStd::string& assetPath) override
|
|
{
|
|
m_lastAssetProcessorTask = assetPath;
|
|
}
|
|
|
|
virtual void AssetCompilationFailed(const AZStd::string& assetPath) override
|
|
{
|
|
m_failedJobs.insert(assetPath);
|
|
}
|
|
|
|
virtual void CountOfAssetsInQueue(const int& count) override
|
|
{
|
|
m_pendingJobsCount = count;
|
|
}
|
|
|
|
int GetJobsCount() const
|
|
{
|
|
return m_pendingJobsCount;
|
|
}
|
|
|
|
AZStd::set<AZStd::string> FailedJobsList() const
|
|
{
|
|
return m_failedJobs;
|
|
}
|
|
|
|
AZStd::string LastAssetProcessorTask() const
|
|
{
|
|
return m_lastAssetProcessorTask;
|
|
}
|
|
|
|
public:
|
|
EConnectionState GetState() const
|
|
{
|
|
return m_state;
|
|
}
|
|
|
|
private:
|
|
EConnectionState m_state;
|
|
int m_pendingJobsCount = 0;
|
|
AZStd::set<AZStd::string> m_failedJobs;
|
|
AZStd::string m_lastAssetProcessorTask;
|
|
};
|
|
|
|
namespace
|
|
{
|
|
void PyOpenViewPane(const char* viewClassName)
|
|
{
|
|
QtViewPaneManager::instance()->OpenPane(viewClassName);
|
|
}
|
|
|
|
void PyCloseViewPane(const char* viewClassName)
|
|
{
|
|
QtViewPaneManager::instance()->ClosePane(viewClassName);
|
|
}
|
|
|
|
bool PyIsViewPaneVisible(const char* viewClassName)
|
|
{
|
|
return QtViewPaneManager::instance()->IsVisible(viewClassName);
|
|
}
|
|
|
|
AZStd::vector<AZStd::string> PyGetViewPaneNames()
|
|
{
|
|
const QtViewPanes panes = QtViewPaneManager::instance()->GetRegisteredPanes();
|
|
|
|
AZStd::vector<AZStd::string> names;
|
|
names.reserve(panes.size());
|
|
|
|
AZStd::transform(panes.begin(), panes.end(), AZStd::back_inserter(names), [](const QtViewPane& pane)
|
|
{
|
|
return AZStd::string(pane.m_name.toUtf8().constData());
|
|
});
|
|
|
|
return names;
|
|
}
|
|
|
|
void PyExit()
|
|
{
|
|
// Adding a single-shot QTimer to PyExit delays the QApplication::closeAllWindows call until
|
|
// all the events in the event queue have been processed. Calling QApplication::closeAllWindows instead
|
|
// of MainWindow::close ensures the Metal render window is cleaned up on macOS.
|
|
QTimer::singleShot(0, qApp, &QApplication::closeAllWindows);
|
|
}
|
|
|
|
void PyExitNoPrompt()
|
|
{
|
|
// Set the level to "unmodified" so that it doesn't prompt to save on exit.
|
|
GetIEditor()->GetDocument()->SetModifiedFlag(false);
|
|
PyExit();
|
|
}
|
|
|
|
void PyTestOutput(const AZStd::string& output)
|
|
{
|
|
CCryEditApp::instance()->PrintAlways(output);
|
|
}
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
// MainWindow
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
MainWindow* MainWindow::m_instance = nullptr;
|
|
|
|
MainWindow::MainWindow(QWidget* parent)
|
|
: QMainWindow(parent)
|
|
, m_oldMainFrame(nullptr)
|
|
, m_viewPaneManager(QtViewPaneManager::instance())
|
|
, m_shortcutDispatcher(new ShortcutDispatcher(this))
|
|
, m_actionManager(new ActionManager(this, QtViewPaneManager::instance(), m_shortcutDispatcher))
|
|
, m_undoStateAdapter(new UndoStackStateAdapter(this))
|
|
, m_keyboardCustomization(nullptr)
|
|
, m_activeView(nullptr)
|
|
, m_settings("O3DE", "O3DE")
|
|
, m_toolbarManager(new ToolbarManager(m_actionManager, this))
|
|
, m_assetImporterManager(new AssetImporterManager(this))
|
|
, m_levelEditorMenuHandler(new LevelEditorMenuHandler(this, m_viewPaneManager, m_settings))
|
|
, m_sourceControlNotifHandler(new AzToolsFramework::QtSourceControlNotificationHandler(this))
|
|
, m_viewPaneHost(nullptr)
|
|
, m_autoSaveTimer(nullptr)
|
|
, m_autoRemindTimer(nullptr)
|
|
, m_backgroundUpdateTimer(nullptr)
|
|
, m_connectionLostTimer(new QTimer(this))
|
|
{
|
|
setObjectName("MainWindow"); // For IEditor::GetEditorMainWindow to work in plugins, where we can't link against MainWindow::instance()
|
|
m_instance = this;
|
|
|
|
//for new docking, create a DockMainWindow to host dock widgets so we can call QMainWindow::restoreState to restore docks without affecting our main toolbars.
|
|
m_viewPaneHost = new AzQtComponents::DockMainWindow();
|
|
|
|
m_viewPaneHost->setDockOptions(QMainWindow::GroupedDragging | QMainWindow::AllowNestedDocks | QMainWindow::AllowTabbedDocks);
|
|
|
|
m_connectionListener = AZStd::make_shared<EngineConnectionListener>();
|
|
QObject::connect(m_connectionLostTimer, &QTimer::timeout, this, &MainWindow::ShowConnectionDisconnectedDialog);
|
|
|
|
setStatusBar(new MainStatusBar(this));
|
|
|
|
setAttribute(Qt::WA_DeleteOnClose, true);
|
|
|
|
connect(m_viewPaneManager, &QtViewPaneManager::viewPaneCreated, this, &MainWindow::OnViewPaneCreated);
|
|
GetIEditor()->RegisterNotifyListener(this);
|
|
|
|
AssetImporterDragAndDropHandler* assetImporterDragAndDropHandler = new AssetImporterDragAndDropHandler(this, m_assetImporterManager);
|
|
connect(assetImporterDragAndDropHandler, &AssetImporterDragAndDropHandler::OpenAssetImporterManager, this, &MainWindow::OnOpenAssetImporterManager);
|
|
|
|
connect(m_levelEditorMenuHandler, &LevelEditorMenuHandler::ActivateAssetImporter, this, [this]() {
|
|
m_assetImporterManager->Exec();
|
|
});
|
|
|
|
setFocusPolicy(Qt::StrongFocus);
|
|
|
|
setAcceptDrops(true);
|
|
|
|
// special handling for escape key (outside ActionManager)
|
|
auto* escapeAction = new QAction(this);
|
|
escapeAction->setShortcut(QKeySequence(Qt::Key_Escape));
|
|
addAction(escapeAction);
|
|
connect(escapeAction, &QAction::triggered, this, &MainWindow::OnEscapeAction);
|
|
|
|
const QSize minSize(800, 600);
|
|
if (size().height() < minSize.height() || size().width() < minSize.width())
|
|
{
|
|
resize(size().expandedTo(minSize));
|
|
}
|
|
}
|
|
|
|
void MainWindow::SystemTick()
|
|
{
|
|
AZ::ComponentApplication* componentApplication = nullptr;
|
|
EBUS_EVENT_RESULT(componentApplication, AZ::ComponentApplicationBus, GetApplication);
|
|
if (componentApplication)
|
|
{
|
|
componentApplication->TickSystem();
|
|
}
|
|
}
|
|
|
|
#ifdef Q_OS_WIN
|
|
HWND MainWindow::GetNativeHandle()
|
|
{
|
|
// if the parent widget is set, it's a window decoration wrapper
|
|
// we use that instead, to ensure we're in lock step the code in CryEdit.cpp when it calls
|
|
// InitGameSystem
|
|
if (parentWidget() != nullptr)
|
|
{
|
|
assert(qobject_cast<AzQtComponents::WindowDecorationWrapper*>(parentWidget()));
|
|
return QtUtil::getNativeHandle(parentWidget());
|
|
}
|
|
|
|
return QtUtil::getNativeHandle(this);
|
|
}
|
|
#endif // #ifdef Q_OS_WIN
|
|
|
|
void MainWindow::OnOpenAssetImporterManager(const QStringList& dragAndDropFileList)
|
|
{
|
|
m_assetImporterManager->Exec(dragAndDropFileList);
|
|
}
|
|
|
|
CLayoutWnd* MainWindow::GetLayout() const
|
|
{
|
|
return m_pLayoutWnd;
|
|
}
|
|
|
|
CLayoutViewPane* MainWindow::GetActiveView() const
|
|
{
|
|
return m_activeView;
|
|
}
|
|
|
|
QtViewport* MainWindow::GetActiveViewport() const
|
|
{
|
|
return m_activeView ? qobject_cast<QtViewport*>(m_activeView->GetViewport()) : nullptr;
|
|
}
|
|
|
|
void MainWindow::SetActiveView(CLayoutViewPane* v)
|
|
{
|
|
m_activeView = v;
|
|
}
|
|
|
|
MainWindow::~MainWindow()
|
|
{
|
|
AzToolsFramework::SourceControlNotificationBus::Handler::BusDisconnect();
|
|
|
|
delete m_toolbarManager;
|
|
m_connectionListener.reset();
|
|
GetIEditor()->UnregisterNotifyListener(this);
|
|
|
|
// tear down the ActionOverride (clear the overrideWidget's parent)
|
|
ActionOverrideRequestBus::Event(
|
|
GetEntityContextId(), &ActionOverrideRequests::TeardownActionOverrideHandler);
|
|
|
|
m_instance = nullptr;
|
|
}
|
|
|
|
void MainWindow::InitCentralWidget()
|
|
{
|
|
m_pLayoutWnd = new CLayoutWnd(&m_settings);
|
|
|
|
// Set the central widgets before calling CreateLayout to avoid reparenting everything later
|
|
setCentralWidget(m_viewPaneHost);
|
|
m_viewPaneHost->setCentralWidget(m_pLayoutWnd);
|
|
|
|
if (MainWindow::instance()->IsPreview())
|
|
{
|
|
m_pLayoutWnd->CreateLayout(ET_Layout0, true, ET_ViewportModel);
|
|
}
|
|
else
|
|
{
|
|
if (!m_pLayoutWnd->LoadConfig())
|
|
{
|
|
m_pLayoutWnd->CreateLayout(ET_Layout0);
|
|
}
|
|
}
|
|
|
|
// make sure the layout wnd knows to reset it's layout and settings
|
|
connect(m_viewPaneManager, &QtViewPaneManager::layoutReset, m_pLayoutWnd, &CLayoutWnd::ResetLayout);
|
|
|
|
EBUS_EVENT(AzToolsFramework::EditorEvents::Bus, NotifyCentralWidgetInitialized);
|
|
}
|
|
|
|
void MainWindow::Initialize()
|
|
{
|
|
m_viewPaneManager->SetMainWindow(m_viewPaneHost, &m_settings, /*unused*/ QByteArray());
|
|
|
|
RegisterStdViewClasses();
|
|
InitCentralWidget();
|
|
|
|
InitActions();
|
|
|
|
// load toolbars ("shelves") and macros
|
|
GetIEditor()->GetToolBoxManager()->Load(m_actionManager);
|
|
|
|
InitToolActionHandlers();
|
|
|
|
// Initialize toolbars before we setup the menu so that any tools can be added to the toolbar as needed
|
|
InitToolBars();
|
|
|
|
m_levelEditorMenuHandler->Initialize();
|
|
|
|
InitStatusBar();
|
|
|
|
AzToolsFramework::SourceControlNotificationBus::Handler::BusConnect();
|
|
m_sourceControlNotifHandler->Init();
|
|
|
|
m_keyboardCustomization = new KeyboardCustomizationSettings(QStringLiteral("Main Window"), this);
|
|
|
|
if (!IsPreview())
|
|
{
|
|
RegisterOpenWndCommands();
|
|
}
|
|
|
|
ResetBackgroundUpdateTimer();
|
|
|
|
ICVar* pBackgroundUpdatePeriod = gEnv->pConsole->GetCVar("ed_backgroundUpdatePeriod");
|
|
if (pBackgroundUpdatePeriod)
|
|
{
|
|
pBackgroundUpdatePeriod->SetOnChangeCallback([](ICVar*) {
|
|
MainWindow::instance()->ResetBackgroundUpdateTimer();
|
|
});
|
|
}
|
|
|
|
// setup the ActionOverride (set overrideWidgets parent to be the MainWindow)
|
|
ActionOverrideRequestBus::Event(
|
|
GetEntityContextId(), &ActionOverrideRequests::SetupActionOverrideHandler, this);
|
|
|
|
if (auto imGuiManager = AZ::Interface<ImGui::IImGuiManager>::Get())
|
|
{
|
|
auto handleImGuiStateChangeFn = [](bool enabled)
|
|
{
|
|
EditorWindowUIRequestBus::Broadcast(&EditorWindowUIRequests::SetEditorUiEnabled, enabled);
|
|
};
|
|
m_handleImGuiStateChangeHandler = ImGui::IImGuiManager::ImGuiSetEnabledEvent::Handler(handleImGuiStateChangeFn);
|
|
imGuiManager->ConnectImGuiSetEnabledChangedHandler(m_handleImGuiStateChangeHandler);
|
|
}
|
|
|
|
AzToolsFramework::EditorEventsBus::Broadcast(&AzToolsFramework::EditorEvents::NotifyMainWindowInitialized, this);
|
|
}
|
|
|
|
void MainWindow::InitStatusBar()
|
|
{
|
|
StatusBar()->Init();
|
|
connect(qobject_cast<StatusBarItem*>(StatusBar()->GetItem("connection")), &StatusBarItem::clicked, this, &MainWindow::OnConnectionStatusClicked);
|
|
connect(StatusBar(), &MainStatusBar::requestStatusUpdate, this, &MainWindow::OnUpdateConnectionStatus);
|
|
}
|
|
|
|
|
|
CMainFrame* MainWindow::GetOldMainFrame() const
|
|
{
|
|
return m_oldMainFrame;
|
|
}
|
|
|
|
MainWindow* MainWindow::instance()
|
|
{
|
|
return m_instance;
|
|
}
|
|
|
|
void MainWindow::closeEvent(QCloseEvent* event)
|
|
{
|
|
gSettings.Save();
|
|
|
|
AzFramework::SystemCursorState currentCursorState;
|
|
bool isInGameMode = false;
|
|
if (GetIEditor()->IsInGameMode())
|
|
{
|
|
isInGameMode = true;
|
|
// Storecurrent state in case we need to restore Game Mode.
|
|
AzFramework::InputSystemCursorRequestBus::EventResult(currentCursorState, AzFramework::InputDeviceMouse::Id,
|
|
&AzFramework::InputSystemCursorRequests::GetSystemCursorState);
|
|
// make sure the mouse is turned on before popping up any dialog boxes.
|
|
AzFramework::InputSystemCursorRequestBus::Event(AzFramework::InputDeviceMouse::Id,
|
|
&AzFramework::InputSystemCursorRequests::SetSystemCursorState,
|
|
AzFramework::SystemCursorState::UnconstrainedAndVisible);
|
|
}
|
|
if (GetIEditor()->GetDocument() && !GetIEditor()->GetDocument()->CanCloseFrame())
|
|
{
|
|
if (isInGameMode)
|
|
{
|
|
// make sure the mouse is turned back off if returning to the game.
|
|
AzFramework::InputSystemCursorRequestBus::Event(AzFramework::InputDeviceMouse::Id,
|
|
&AzFramework::InputSystemCursorRequests::SetSystemCursorState,
|
|
currentCursorState);
|
|
}
|
|
event->ignore();
|
|
return;
|
|
}
|
|
|
|
KeyboardCustomizationSettings::EnableShortcutsGlobally(true);
|
|
SaveConfig();
|
|
|
|
// Some of the panes may ask for confirmation to save changes before closing.
|
|
if (!QtViewPaneManager::instance()->ClosePanesWithRollback(QVector<QString>()) ||
|
|
!GetIEditor() ||
|
|
!GetIEditor()->GetLevelIndependentFileMan()->PromptChangedFiles())
|
|
{
|
|
if (isInGameMode)
|
|
{
|
|
// make sure the mouse is turned back off if returning to the game.
|
|
AzFramework::InputSystemCursorRequestBus::Event(AzFramework::InputDeviceMouse::Id,
|
|
&AzFramework::InputSystemCursorRequests::SetSystemCursorState,
|
|
currentCursorState);
|
|
|
|
}
|
|
event->ignore();
|
|
return;
|
|
}
|
|
|
|
Editor::EditorQtApplication::instance()->EnableOnIdle(false);
|
|
|
|
if (GetIEditor()->GetDocument())
|
|
{
|
|
GetIEditor()->GetDocument()->SetModifiedFlag(FALSE);
|
|
GetIEditor()->GetDocument()->SetModifiedModules(eModifiedNothing);
|
|
}
|
|
// Close all edit panels.
|
|
GetIEditor()->ClearSelection();
|
|
GetIEditor()->GetObjectManager()->EndEditParams();
|
|
|
|
// force clean up of all deferred deletes, so that we don't have any issues with windows from plugins not being deleted yet
|
|
qApp->sendPostedEvents(0, QEvent::DeferredDelete);
|
|
|
|
QMainWindow::closeEvent(event);
|
|
}
|
|
|
|
void MainWindow::SaveConfig()
|
|
{
|
|
m_settings.setValue("mainWindowState", saveState());
|
|
QtViewPaneManager::instance()->SaveLayout();
|
|
if (m_pLayoutWnd)
|
|
{
|
|
m_pLayoutWnd->SaveConfig();
|
|
}
|
|
GetIEditor()->GetToolBoxManager()->Save();
|
|
}
|
|
|
|
void MainWindow::ShowKeyboardCustomization()
|
|
{
|
|
CustomizeKeyboardDialog dialog(*m_keyboardCustomization, this);
|
|
dialog.exec();
|
|
}
|
|
|
|
void MainWindow::ExportKeyboardShortcuts()
|
|
{
|
|
KeyboardCustomizationSettings::ExportToFile(this);
|
|
}
|
|
|
|
void MainWindow::ImportKeyboardShortcuts()
|
|
{
|
|
KeyboardCustomizationSettings::ImportFromFile(this);
|
|
KeyboardCustomizationSettings::SaveGlobally();
|
|
}
|
|
|
|
void MainWindow::InitActions()
|
|
{
|
|
auto am = m_actionManager;
|
|
auto cryEdit = CCryEditApp::instance();
|
|
cryEdit->RegisterActionHandlers();
|
|
|
|
am->AddAction(ID_TOOLBAR_SEPARATOR, QString());
|
|
|
|
am->AddAction(ID_TOOLBAR_WIDGET_UNDO, QString());
|
|
am->AddAction(ID_TOOLBAR_WIDGET_REDO, QString());
|
|
am->AddAction(ID_TOOLBAR_WIDGET_SNAP_ANGLE, QString());
|
|
am->AddAction(ID_TOOLBAR_WIDGET_SNAP_GRID, QString());
|
|
am->AddAction(ID_TOOLBAR_WIDGET_SPACER_RIGHT, QString());
|
|
|
|
// File actions
|
|
am->AddAction(ID_FILE_NEW, tr("New Level"))
|
|
.SetShortcut(tr("Ctrl+N"))
|
|
.Connect(&QAction::triggered, [cryEdit]()
|
|
{
|
|
cryEdit->OnCreateLevel();
|
|
})
|
|
.RegisterUpdateCallback(cryEdit, &CCryEditApp::OnUpdateNewLevel);
|
|
am->AddAction(ID_FILE_OPEN_LEVEL, tr("Open Level..."))
|
|
.SetShortcut(tr("Ctrl+O"))
|
|
.SetStatusTip(tr("Open an existing level"))
|
|
.RegisterUpdateCallback(cryEdit, &CCryEditApp::OnUpdateFileOpen);
|
|
#ifdef ENABLE_SLICE_EDITOR
|
|
am->AddAction(ID_FILE_NEW_SLICE, tr("New Slice"))
|
|
.SetStatusTip(tr("Create a new slice"));
|
|
am->AddAction(ID_FILE_OPEN_SLICE, tr("Open Slice..."))
|
|
.SetStatusTip(tr("Open an existing slice"));
|
|
#endif
|
|
am->AddAction(ID_FILE_SAVE_SELECTED_SLICE, tr("Save selected slice")).SetShortcut(tr("Alt+S"))
|
|
.SetStatusTip(tr("Save the selected slice to the first level root"));
|
|
am->AddAction(ID_FILE_SAVE_SLICE_TO_ROOT, tr("Save Slice to root")).SetShortcut(tr("Ctrl+Alt+S"))
|
|
.SetStatusTip(tr("Save the selected slice to the top level root"));
|
|
am->AddAction(ID_FILE_SAVE_LEVEL, tr("&Save"))
|
|
.SetShortcut(tr("Ctrl+S"))
|
|
.SetReserved()
|
|
.SetStatusTip(tr("Save the current level"))
|
|
.RegisterUpdateCallback(cryEdit, &CCryEditApp::OnUpdateDocumentReady);
|
|
am->AddAction(ID_FILE_SAVE_AS, tr("Save &As..."))
|
|
.SetShortcut(tr("Ctrl+Shift+S"))
|
|
.SetReserved()
|
|
.SetStatusTip(tr("Save the active document with a new name"))
|
|
.RegisterUpdateCallback(cryEdit, &CCryEditApp::OnUpdateDocumentReady);
|
|
am->AddAction(ID_FILE_SAVELEVELRESOURCES, tr("Save Level Resources..."))
|
|
.SetStatusTip(tr("Save Resources"))
|
|
.RegisterUpdateCallback(cryEdit, &CCryEditApp::OnUpdateDocumentReady);
|
|
am->AddAction(ID_IMPORT_ASSET, tr("Import &FBX..."));
|
|
|
|
bool usePrefabSystemForLevels = false;
|
|
AzFramework::ApplicationRequests::Bus::BroadcastResult(
|
|
usePrefabSystemForLevels, &AzFramework::ApplicationRequests::IsPrefabSystemForLevelsEnabled);
|
|
if (!usePrefabSystemForLevels)
|
|
{
|
|
am->AddAction(ID_FILE_EXPORTTOGAMENOSURFACETEXTURE, tr("&Export to Engine"))
|
|
.SetShortcut(tr("Ctrl+E"))
|
|
.RegisterUpdateCallback(cryEdit, &CCryEditApp::OnUpdateDocumentReady);
|
|
}
|
|
|
|
am->AddAction(ID_FILE_EXPORT_SELECTEDOBJECTS, tr("Export Selected &Objects"))
|
|
.RegisterUpdateCallback(cryEdit, &CCryEditApp::OnUpdateSelected);
|
|
am->AddAction(ID_FILE_EXPORTOCCLUSIONMESH, tr("Export Occlusion Mesh"));
|
|
am->AddAction(ID_FILE_EDITLOGFILE, tr("Show Log File"));
|
|
am->AddAction(ID_FILE_RESAVESLICES, tr("Resave All Slices"));
|
|
am->AddAction(ID_FILE_PROJECT_MANAGER_SETTINGS, tr("Edit Project Settings..."));
|
|
am->AddAction(ID_FILE_PROJECT_MANAGER_NEW, tr("New Project..."));
|
|
am->AddAction(ID_FILE_PROJECT_MANAGER_OPEN, tr("Open Project..."));
|
|
am->AddAction(ID_TOOLS_CUSTOMIZEKEYBOARD, tr("Customize &Keyboard..."))
|
|
.Connect(&QAction::triggered, this, &MainWindow::ShowKeyboardCustomization);
|
|
am->AddAction(ID_TOOLS_EXPORT_SHORTCUTS, tr("&Export Keyboard Settings..."))
|
|
.Connect(&QAction::triggered, this, &MainWindow::ExportKeyboardShortcuts);
|
|
am->AddAction(ID_TOOLS_IMPORT_SHORTCUTS, tr("&Import Keyboard Settings..."))
|
|
.Connect(&QAction::triggered, this, &MainWindow::ImportKeyboardShortcuts);
|
|
am->AddAction(ID_TOOLS_PREFERENCES, tr("Global Preferences..."));
|
|
|
|
for (int i = ID_FILE_MRU_FIRST; i <= ID_FILE_MRU_LAST; ++i)
|
|
{
|
|
am->AddAction(i, QString());
|
|
}
|
|
|
|
#if AZ_TRAIT_OS_PLATFORM_APPLE
|
|
const QString appExitText = tr("&Quit");
|
|
#else
|
|
const QString appExitText = tr("E&xit");
|
|
#endif
|
|
|
|
am->AddAction(ID_APP_EXIT, appExitText)
|
|
.SetReserved();
|
|
|
|
// Edit actions
|
|
am->AddAction(ID_UNDO, tr("&Undo"))
|
|
.SetShortcut(QKeySequence::Undo)
|
|
.SetReserved()
|
|
.SetStatusTip(tr("Undo last operation"))
|
|
//.SetMenu(new QMenu("FIXME"))
|
|
.SetApplyHoverEffect()
|
|
.RegisterUpdateCallback(cryEdit, &CCryEditApp::OnUpdateUndo);
|
|
am->AddAction(ID_REDO, tr("&Redo"))
|
|
.SetShortcut(AzQtComponents::RedoKeySequence)
|
|
.SetReserved()
|
|
//.SetMenu(new QMenu("FIXME"))
|
|
.SetApplyHoverEffect()
|
|
.SetStatusTip(tr("Redo last undo operation"))
|
|
.RegisterUpdateCallback(cryEdit, &CCryEditApp::OnUpdateRedo);
|
|
|
|
am->AddAction(ID_EDIT_HOLD, tr("&Hold"))
|
|
.SetShortcut(tr("Ctrl+Alt+H"))
|
|
.SetToolTip(tr("&Hold (Ctrl+Alt+H)"))
|
|
.SetStatusTip(tr("Save the current state(Hold)"));
|
|
am->AddAction(ID_EDIT_FETCH, tr("&Fetch"))
|
|
.SetShortcut(tr("Ctrl+Alt+F"))
|
|
.SetToolTip(tr("&Fetch (Ctrl+Alt+F)"))
|
|
.SetStatusTip(tr("Restore saved state (Fetch)"));
|
|
|
|
// Modify actions
|
|
am->AddAction(ID_EDITMODE_MOVE, tr("Move"))
|
|
.SetIcon(Style::icon("Move"))
|
|
.SetApplyHoverEffect()
|
|
.SetShortcut(tr("1"))
|
|
.SetToolTip(tr("Move (1)"))
|
|
.SetCheckable(true)
|
|
.SetStatusTip(tr("Select and move selected object(s)"))
|
|
.RegisterUpdateCallback(cryEdit, &CCryEditApp::OnUpdateEditmodeMove);
|
|
am->AddAction(ID_EDITMODE_ROTATE, tr("Rotate"))
|
|
.SetIcon(Style::icon("Translate"))
|
|
.SetApplyHoverEffect()
|
|
.SetShortcut(tr("2"))
|
|
.SetToolTip(tr("Rotate (2)"))
|
|
.SetCheckable(true)
|
|
.SetStatusTip(tr("Select and rotate selected object(s)"))
|
|
.RegisterUpdateCallback(cryEdit, &CCryEditApp::OnUpdateEditmodeRotate);
|
|
am->AddAction(ID_EDITMODE_SCALE, tr("Scale"))
|
|
.SetIcon(Style::icon("Scale"))
|
|
.SetApplyHoverEffect()
|
|
.SetShortcut(tr("3"))
|
|
.SetToolTip(tr("Scale (3)"))
|
|
.SetCheckable(true)
|
|
.SetStatusTip(tr("Select and scale selected object(s)"))
|
|
.RegisterUpdateCallback(cryEdit, &CCryEditApp::OnUpdateEditmodeScale);
|
|
|
|
am->AddAction(ID_SNAP_TO_GRID, tr("Snap to grid"))
|
|
.SetIcon(Style::icon("Grid"))
|
|
.SetApplyHoverEffect()
|
|
.SetShortcut(tr("G"))
|
|
.SetToolTip(tr("Snap to grid (G)"))
|
|
.SetStatusTip(tr("Toggles snap to grid"))
|
|
.SetCheckable(true)
|
|
.RegisterUpdateCallback([](QAction* action) {
|
|
Q_ASSERT(action->isCheckable());
|
|
action->setChecked(SandboxEditor::GridSnappingEnabled());
|
|
})
|
|
.Connect(&QAction::triggered, []() { SandboxEditor::SetGridSnapping(!SandboxEditor::GridSnappingEnabled()); });
|
|
|
|
am->AddAction(ID_SNAPANGLE, tr("Snap angle"))
|
|
.SetIcon(Style::icon("Angle"))
|
|
.SetApplyHoverEffect()
|
|
.SetStatusTip(tr("Snap angle"))
|
|
.SetCheckable(true)
|
|
.RegisterUpdateCallback([](QAction* action) {
|
|
Q_ASSERT(action->isCheckable());
|
|
action->setChecked(SandboxEditor::AngleSnappingEnabled());
|
|
})
|
|
.Connect(&QAction::triggered, []() { SandboxEditor::SetAngleSnapping(!SandboxEditor::AngleSnappingEnabled()); });
|
|
|
|
// Display actions
|
|
am->AddAction(ID_SWITCHCAMERA_DEFAULTCAMERA, tr("Default Camera")).SetCheckable(true)
|
|
.RegisterUpdateCallback(cryEdit, &CCryEditApp::OnUpdateSwitchToDefaultCamera);
|
|
am->AddAction(ID_SWITCHCAMERA_SEQUENCECAMERA, tr("Sequence Camera")).SetCheckable(true)
|
|
.RegisterUpdateCallback(cryEdit, &CCryEditApp::OnUpdateSwitchToSequenceCamera);
|
|
am->AddAction(ID_SWITCHCAMERA_SELECTEDCAMERA, tr("Selected Camera Object")).SetCheckable(true)
|
|
.RegisterUpdateCallback(cryEdit, &CCryEditApp::OnUpdateSwitchToSelectedCamera);
|
|
am->AddAction(ID_SWITCHCAMERA_NEXT, tr("Cycle Camera"))
|
|
.SetShortcut(tr("Ctrl+`"))
|
|
.SetToolTip(tr("Cycle Camera (Ctrl+`)"));
|
|
am->AddAction(ID_DISPLAY_GOTOPOSITION, tr("Go to Position..."));
|
|
am->AddAction(ID_MODIFY_GOTO_SELECTION, tr("Center on Selection"))
|
|
.SetShortcut(tr("Z"))
|
|
.SetToolTip(tr("Center on Selection (Z)"))
|
|
.Connect(&QAction::triggered, this, &MainWindow::OnGotoSelected);
|
|
am->AddAction(ID_GOTO_LOC1, tr("Location 1"))
|
|
.SetShortcut(tr("Shift+F1"))
|
|
.SetToolTip(tr("Location 1 (Shift+F1)"));
|
|
am->AddAction(ID_GOTO_LOC2, tr("Location 2"))
|
|
.SetShortcut(tr("Shift+F2"))
|
|
.SetToolTip(tr("Location 2 (Shift+F2)"));
|
|
am->AddAction(ID_GOTO_LOC3, tr("Location 3"))
|
|
.SetShortcut(tr("Shift+F3"))
|
|
.SetToolTip(tr("Location 3 (Shift+F3)"));
|
|
am->AddAction(ID_GOTO_LOC4, tr("Location 4"))
|
|
.SetShortcut(tr("Shift+F4"))
|
|
.SetToolTip(tr("Location 4 (Shift+F4)"));
|
|
am->AddAction(ID_GOTO_LOC5, tr("Location 5"))
|
|
.SetShortcut(tr("Shift+F5"))
|
|
.SetToolTip(tr("Location 5 (Shift+F5)"));
|
|
am->AddAction(ID_GOTO_LOC6, tr("Location 6"))
|
|
.SetShortcut(tr("Shift+F6"))
|
|
.SetToolTip(tr("Location 6 (Shift+F6)"));
|
|
am->AddAction(ID_GOTO_LOC7, tr("Location 7"))
|
|
.SetShortcut(tr("Shift+F7"))
|
|
.SetToolTip(tr("Location 7 (Shift+F7)"));
|
|
am->AddAction(ID_GOTO_LOC8, tr("Location 8"))
|
|
.SetShortcut(tr("Shift+F8"))
|
|
.SetToolTip(tr("Location 8 (Shift+F8)"));
|
|
am->AddAction(ID_GOTO_LOC9, tr("Location 9"))
|
|
.SetShortcut(tr("Shift+F9"))
|
|
.SetToolTip(tr("Location 9 (Shift+F9)"));
|
|
am->AddAction(ID_GOTO_LOC10, tr("Location 10"))
|
|
.SetShortcut(tr("Shift+F10"))
|
|
.SetToolTip(tr("Location 10 (Shift+F10)"));
|
|
am->AddAction(ID_GOTO_LOC11, tr("Location 11"))
|
|
.SetShortcut(tr("Shift+F11"))
|
|
.SetToolTip(tr("Location 11 (Shift+F11)"));
|
|
am->AddAction(ID_GOTO_LOC12, tr("Location 12"))
|
|
.SetShortcut(tr("Shift+F12"))
|
|
.SetToolTip(tr("Location 12 (Shift+F12)"));
|
|
am->AddAction(ID_TAG_LOC1, tr("Location 1"))
|
|
.SetShortcut(tr("Ctrl+F1"))
|
|
.SetToolTip(tr("Location 1 (Ctrl+F1)"));
|
|
am->AddAction(ID_TAG_LOC2, tr("Location 2"))
|
|
.SetShortcut(tr("Ctrl+F2"))
|
|
.SetToolTip(tr("Location 2 (Ctrl+F2)"));
|
|
am->AddAction(ID_TAG_LOC3, tr("Location 3"))
|
|
.SetShortcut(tr("Ctrl+F3"))
|
|
.SetToolTip(tr("Location 3 (Ctrl+F3)"));
|
|
am->AddAction(ID_TAG_LOC4, tr("Location 4"))
|
|
.SetShortcut(tr("Ctrl+F4"))
|
|
.SetToolTip(tr("Location 4 (Ctrl+F4)"));
|
|
am->AddAction(ID_TAG_LOC5, tr("Location 5"))
|
|
.SetShortcut(tr("Ctrl+F5"))
|
|
.SetToolTip(tr("Location 5 (Ctrl+F5)"));
|
|
am->AddAction(ID_TAG_LOC6, tr("Location 6"))
|
|
.SetShortcut(tr("Ctrl+F6"))
|
|
.SetToolTip(tr("Location 6 (Ctrl+F6)"));
|
|
am->AddAction(ID_TAG_LOC7, tr("Location 7"))
|
|
.SetShortcut(tr("Ctrl+F7"))
|
|
.SetToolTip(tr("Location 7 (Ctrl+F7)"));
|
|
am->AddAction(ID_TAG_LOC8, tr("Location 8"))
|
|
.SetShortcut(tr("Ctrl+F8"))
|
|
.SetToolTip(tr("Location 8 (Ctrl+F8)"));
|
|
am->AddAction(ID_TAG_LOC9, tr("Location 9"))
|
|
.SetShortcut(tr("Ctrl+F9"))
|
|
.SetToolTip(tr("Location 9 (Ctrl+F9)"));
|
|
am->AddAction(ID_TAG_LOC10, tr("Location 10"))
|
|
.SetShortcut(tr("Ctrl+F10"))
|
|
.SetToolTip(tr("Location 10 (Ctrl+F10)"));
|
|
am->AddAction(ID_TAG_LOC11, tr("Location 11"))
|
|
.SetShortcut(tr("Ctrl+F11"))
|
|
.SetToolTip(tr("Location 11 (Ctrl+F11)"));
|
|
am->AddAction(ID_TAG_LOC12, tr("Location 12"))
|
|
.SetShortcut(tr("Ctrl+F12"))
|
|
.SetToolTip(tr("Location 12 (Ctrl+F12)"));
|
|
|
|
if (CViewManager::IsMultiViewportEnabled())
|
|
{
|
|
am->AddAction(ID_VIEW_CONFIGURELAYOUT, tr("Configure Layout..."));
|
|
}
|
|
#ifdef FEATURE_ORTHOGRAPHIC_VIEW
|
|
am->AddAction(ID_VIEW_CYCLE2DVIEWPORT, tr("Cycle Viewports"))
|
|
.SetShortcut(tr("Ctrl+Tab"))
|
|
.SetStatusTip(tr("Cycle 2D Viewport"))
|
|
.RegisterUpdateCallback(cryEdit, &CCryEditApp::OnUpdateNonGameMode);
|
|
#endif
|
|
am->AddAction(ID_DISPLAY_SHOWHELPERS, tr("Show/Hide Helpers"))
|
|
.SetShortcut(tr("Shift+Space"))
|
|
.SetToolTip(tr("Show/Hide Helpers (Shift+Space)"));
|
|
|
|
// Audio actions
|
|
am->AddAction(ID_SOUND_STOPALLSOUNDS, tr("Stop All Sounds"))
|
|
.Connect(&QAction::triggered, this, &MainWindow::OnStopAllSounds);
|
|
am->AddAction(ID_AUDIO_REFRESH_AUDIO_SYSTEM, tr("Refresh Audio"))
|
|
.Connect(&QAction::triggered, this, &MainWindow::OnRefreshAudioSystem);
|
|
|
|
// Game actions
|
|
am->AddAction(ID_VIEW_SWITCHTOGAME, tr("Play &Game"))
|
|
.SetIcon(QIcon(":/stylesheet/img/UI20/toolbar/Play.svg"))
|
|
.SetShortcut(tr("Ctrl+G"))
|
|
.SetToolTip(tr("Play Game (Ctrl+G)"))
|
|
.SetStatusTip(tr("Activate the game input mode"))
|
|
.SetApplyHoverEffect()
|
|
.SetCheckable(true)
|
|
.RegisterUpdateCallback(cryEdit, &CCryEditApp::OnUpdatePlayGame);
|
|
am->AddAction(ID_VIEW_SWITCHTOGAME_FULLSCREEN, tr("Play &Game (Maximized)"))
|
|
.SetShortcut(tr("Ctrl+Shift+G"))
|
|
.SetStatusTip(tr("Activate the game input mode (maximized)"))
|
|
.SetIcon(Style::icon("Play"))
|
|
.SetApplyHoverEffect()
|
|
.SetCheckable(true);
|
|
am->AddAction(ID_TOOLBAR_WIDGET_PLAYCONSOLE_LABEL, tr("Play Controls"))
|
|
.SetText(tr("Play Controls"));
|
|
am->AddAction(ID_SWITCH_PHYSICS, tr("Simulate"))
|
|
.SetIcon(QIcon(":/stylesheet/img/UI20/toolbar/Simulate_Physics.svg"))
|
|
.SetShortcut(tr("Ctrl+P"))
|
|
.SetToolTip(tr("Simulate (Ctrl+P)"))
|
|
.SetCheckable(true)
|
|
.SetStatusTip(tr("Enable processing of Physics and AI."))
|
|
.SetApplyHoverEffect()
|
|
.SetCheckable(true)
|
|
.RegisterUpdateCallback(cryEdit, &CCryEditApp::OnSwitchPhysicsUpdate);
|
|
am->AddAction(ID_GAME_SYNCPLAYER, tr("Move Player and Camera Separately")).SetCheckable(true)
|
|
.SetStatusTip(tr("Move Player and Camera Separately"))
|
|
.RegisterUpdateCallback(cryEdit, &CCryEditApp::OnSyncPlayerUpdate);
|
|
|
|
// Physics actions
|
|
am->AddAction(ID_PHYSICS_GETPHYSICSSTATE, tr("Get Physics State"))
|
|
.RegisterUpdateCallback(cryEdit, &CCryEditApp::OnUpdateSelected);
|
|
am->AddAction(ID_PHYSICS_RESETPHYSICSSTATE, tr("Reset Physics State"))
|
|
.RegisterUpdateCallback(cryEdit, &CCryEditApp::OnUpdateSelected);
|
|
am->AddAction(ID_PHYSICS_SIMULATEOBJECTS, tr("Simulate Objects"))
|
|
.RegisterUpdateCallback(cryEdit, &CCryEditApp::OnUpdateSelected);
|
|
|
|
// Tools actions
|
|
am->AddAction(ID_TOOLS_ENABLEFILECHANGEMONITORING, tr("Enable File Change Monitoring"));
|
|
am->AddAction(ID_CLEAR_REGISTRY, tr("Clear Registry Data"))
|
|
.SetStatusTip(tr("Clear Registry Data"));
|
|
am->AddAction(ID_VALIDATELEVEL, tr("&Check Level for Errors"))
|
|
.SetStatusTip(tr("Validate Level"));
|
|
am->AddAction(ID_TOOLS_VALIDATEOBJECTPOSITIONS, tr("Check Object Positions"));
|
|
QAction* saveLevelStatsAction =
|
|
am->AddAction(ID_TOOLS_LOGMEMORYUSAGE, tr("Save Level Statistics"))
|
|
.SetStatusTip(tr("Logs Editor memory usage."));
|
|
if( saveLevelStatsAction )
|
|
{
|
|
saveLevelStatsAction->setEnabled(false);
|
|
}
|
|
am->AddAction(ID_RESOURCES_REDUCEWORKINGSET, tr("Reduce Working Set"))
|
|
.SetStatusTip(tr("Reduce Physical RAM Working Set."));
|
|
am->AddAction(ID_TOOLS_UPDATEPROCEDURALVEGETATION, tr("Update Procedural Vegetation"));
|
|
am->AddAction(ID_TOOLS_CONFIGURETOOLS, tr("Configure ToolBox Macros..."));
|
|
am->AddAction(ID_TOOLS_SCRIPTHELP, tr("Script Help"));
|
|
am->AddAction(ID_TOOLS_LUA_EDITOR, tr("Lua Editor"));
|
|
|
|
// View actions
|
|
am->AddAction(ID_VIEW_OPENVIEWPANE, tr("Open View Pane"));
|
|
am->AddAction(ID_VIEW_CONSOLEWINDOW, tr(LyViewPane::ConsoleMenuName))
|
|
.SetShortcut(tr("^"))
|
|
.SetReserved()
|
|
.SetStatusTip(tr("Show or hide the console window"))
|
|
.SetCheckable(true)
|
|
.Connect(&QAction::triggered, this, &MainWindow::ToggleConsole);
|
|
am->AddAction(ID_OPEN_QUICK_ACCESS_BAR, tr("Show &Quick Access Bar"))
|
|
.SetShortcut(tr("Ctrl+Alt+Space"))
|
|
.SetToolTip(tr("Show &Quick Access Bar (Ctrl+Alt+Space)"));
|
|
|
|
// Disable layouts menu
|
|
if (CViewManager::IsMultiViewportEnabled())
|
|
{
|
|
am->AddAction(ID_VIEW_LAYOUTS, tr("Layouts"));
|
|
|
|
am->AddAction(ID_VIEW_SAVELAYOUT, tr("Save Layout..."))
|
|
.Connect(&QAction::triggered, this, &MainWindow::SaveLayout);
|
|
am->AddAction(ID_VIEW_LAYOUT_LOAD_DEFAULT, tr("Restore Default Layout"))
|
|
.Connect(&QAction::triggered, [this]() { m_viewPaneManager->RestoreDefaultLayout(true); });
|
|
}
|
|
|
|
am->AddAction(ID_SKINS_REFRESH, tr("Refresh Style"))
|
|
.SetToolTip(tr("Refreshes the editor stylesheet"))
|
|
.Connect(&QAction::triggered, this, &MainWindow::RefreshStyle);
|
|
|
|
// Help actions
|
|
am->AddAction(ID_DOCUMENTATION_TUTORIALS, tr("Tutorials"))
|
|
.SetReserved();
|
|
|
|
am->AddAction(ID_DOCUMENTATION_O3DE, tr("Open 3D Engine Documentation"))
|
|
.SetReserved();
|
|
am->AddAction(ID_DOCUMENTATION_GAMELIFT, tr("GameLift Documentation"))
|
|
.SetReserved();
|
|
am->AddAction(ID_DOCUMENTATION_RELEASENOTES, tr("Release Notes"))
|
|
.SetReserved();
|
|
|
|
am->AddAction(ID_DOCUMENTATION_GAMEDEVBLOG, tr("GameDev Blog"))
|
|
.SetReserved();
|
|
am->AddAction(ID_DOCUMENTATION_FORUMS, tr("Forums"))
|
|
.SetReserved();
|
|
am->AddAction(ID_DOCUMENTATION_AWSSUPPORT, tr("AWS Support"))
|
|
.SetReserved();
|
|
|
|
am->AddAction(ID_APP_ABOUT, tr("&About O3DE"))
|
|
.SetStatusTip(tr("Display program information, version number and copyright"))
|
|
.SetReserved();
|
|
am->AddAction(ID_APP_SHOW_WELCOME, tr("&Welcome"))
|
|
.SetStatusTip(tr("Show the Welcome to O3DE dialog box"))
|
|
.RegisterUpdateCallback(cryEdit, &CCryEditApp::OnUpdateShowWelcomeScreen);
|
|
|
|
// Editors Toolbar actions
|
|
am->AddAction(ID_OPEN_ASSET_BROWSER, tr("Asset browser"))
|
|
.SetToolTip(tr("Open Asset Browser"))
|
|
.SetApplyHoverEffect();
|
|
|
|
AZ::EBusReduceResult<bool, AZStd::logical_or<bool>> emfxEnabled(false);
|
|
using AnimationRequestBus = AzToolsFramework::EditorAnimationSystemRequestsBus;
|
|
using AnimationSystemType = AzToolsFramework::EditorAnimationSystemRequests::AnimationSystem;
|
|
AnimationRequestBus::BroadcastResult(emfxEnabled, &AnimationRequestBus::Events::IsSystemActive, AnimationSystemType::EMotionFX);
|
|
if (emfxEnabled.value)
|
|
{
|
|
QAction* action = am->AddAction(ID_OPEN_EMOTIONFX_EDITOR, tr("Animation Editor"))
|
|
.SetToolTip(tr("Open Animation Editor (PREVIEW)"))
|
|
.SetIcon(QIcon(":/EMotionFX/EMFX_icon_32x32.png"))
|
|
.SetApplyHoverEffect();
|
|
QObject::connect(action, &QAction::triggered, this, []() {
|
|
QtViewPaneManager::instance()->OpenPane(LyViewPane::AnimationEditor);
|
|
});
|
|
}
|
|
|
|
am->AddAction(ID_OPEN_AUDIO_CONTROLS_BROWSER, tr("Audio Controls Editor"))
|
|
.SetToolTip(tr("Open Audio Controls Editor"))
|
|
.SetIcon(Style::icon("Audio"))
|
|
.SetApplyHoverEffect();
|
|
|
|
am->AddAction(ID_OPEN_UICANVASEDITOR, tr(LyViewPane::UiEditor))
|
|
.SetToolTip(tr("Open UI Editor"))
|
|
.SetApplyHoverEffect();
|
|
|
|
// Edit Mode Toolbar Actions
|
|
am->AddAction(IDC_SELECTION_MASK, tr("Selected Object Types"));
|
|
am->AddAction(ID_REF_COORDS_SYS, tr("Reference coordinate system"))
|
|
.SetShortcut(tr("Ctrl+W"))
|
|
.SetToolTip(tr("Reference coordinate system (Ctrl+W)"))
|
|
.Connect(&QAction::triggered, this, &MainWindow::ToggleRefCoordSys);
|
|
am->AddAction(IDC_SELECTION, tr("Named Selections"));
|
|
|
|
// Object Toolbar Actions
|
|
am->AddAction(ID_GOTO_SELECTED, tr("Go to selected object"))
|
|
.SetIcon(Style::icon("select_object"))
|
|
.SetApplyHoverEffect()
|
|
.Connect(&QAction::triggered, this, &MainWindow::OnGotoSelected);
|
|
|
|
// Misc Toolbar Actions
|
|
am->AddAction(ID_OPEN_SUBSTANCE_EDITOR, tr("Open Substance Editor"))
|
|
.SetApplyHoverEffect();
|
|
}
|
|
|
|
void MainWindow::InitToolActionHandlers()
|
|
{
|
|
ActionManager* am = GetActionManager();
|
|
CToolBoxManager* tbm = GetIEditor()->GetToolBoxManager();
|
|
am->RegisterActionHandler(ID_APP_EXIT, [=]() { window()->close(); });
|
|
|
|
for (int id = ID_TOOL_FIRST; id <= ID_TOOL_LAST; ++id)
|
|
{
|
|
am->RegisterActionHandler(id, [tbm, id] {
|
|
tbm->ExecuteMacro(id - ID_TOOL_FIRST, true);
|
|
});
|
|
}
|
|
|
|
for (int id = ID_TOOL_SHELVE_FIRST; id <= ID_TOOL_SHELVE_LAST; ++id)
|
|
{
|
|
am->RegisterActionHandler(id, [tbm, id] {
|
|
tbm->ExecuteMacro(id - ID_TOOL_SHELVE_FIRST, false);
|
|
});
|
|
}
|
|
|
|
for (int id = CEditorCommandManager::CUSTOM_COMMAND_ID_FIRST; id <= CEditorCommandManager::CUSTOM_COMMAND_ID_LAST; ++id)
|
|
{
|
|
am->RegisterActionHandler(id, [id] {
|
|
GetIEditor()->GetCommandManager()->Execute(id);
|
|
});
|
|
}
|
|
}
|
|
|
|
void MainWindow::OnEscapeAction()
|
|
{
|
|
if (!CCryEditApp::instance()->IsInAutotestMode())
|
|
{
|
|
if (GetIEditor()->IsInGameMode())
|
|
{
|
|
GetIEditor()->SetInGameMode(false);
|
|
}
|
|
else
|
|
{
|
|
AzToolsFramework::EditorEvents::Bus::Broadcast(
|
|
&AzToolsFramework::EditorEvents::OnEscape);
|
|
}
|
|
}
|
|
}
|
|
|
|
void MainWindow::InitToolBars()
|
|
{
|
|
m_toolbarManager->LoadToolbars();
|
|
AdjustToolBarIconSize(static_cast<AzQtComponents::ToolBar::ToolBarIconSize>(gSettings.gui.nToolbarIconSize));
|
|
}
|
|
|
|
QToolButton* MainWindow::CreateUndoRedoButton(int command)
|
|
{
|
|
// We do either undo or redo below, sort that out here
|
|
UndoRedoDirection direction = UndoRedoDirection::Undo;
|
|
auto stateSignal = &UndoStackStateAdapter::UndoAvailable;
|
|
if (ID_REDO == command)
|
|
{
|
|
direction = UndoRedoDirection::Redo;
|
|
stateSignal = &UndoStackStateAdapter::RedoAvailable;
|
|
}
|
|
|
|
auto button = new UndoRedoToolButton(this);
|
|
button->setAutoRaise(true);
|
|
button->setPopupMode(QToolButton::MenuButtonPopup);
|
|
button->setDefaultAction(m_actionManager->GetAction(command));
|
|
|
|
QMenu* menu = new QMenu(button);
|
|
auto action = new QWidgetAction(button);
|
|
auto undoRedo = new CUndoDropDown(direction, button);
|
|
action->setDefaultWidget(undoRedo);
|
|
menu->addAction(action);
|
|
button->setMenu(menu);
|
|
|
|
connect(menu, &QMenu::aboutToShow, undoRedo, &CUndoDropDown::Prepare);
|
|
connect(undoRedo, &CUndoDropDown::accepted, menu, &QMenu::hide);
|
|
connect(m_undoStateAdapter, stateSignal, button, &UndoRedoToolButton::Update);
|
|
|
|
button->setEnabled(false);
|
|
|
|
return button;
|
|
}
|
|
|
|
QWidget* MainWindow::CreateSpacerRightWidget()
|
|
{
|
|
QWidget* spacer = new QWidget(this);
|
|
spacer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
|
|
spacer->setVisible(true);
|
|
return spacer;
|
|
}
|
|
|
|
UndoRedoToolButton::UndoRedoToolButton(QWidget* parent)
|
|
: QToolButton(parent)
|
|
{
|
|
}
|
|
|
|
void UndoRedoToolButton::Update(int count)
|
|
{
|
|
setEnabled(count > 0);
|
|
}
|
|
|
|
bool MainWindow::IsPreview() const
|
|
{
|
|
return GetIEditor()->IsInPreviewMode();
|
|
}
|
|
|
|
MainStatusBar* MainWindow::StatusBar() const
|
|
{
|
|
assert(statusBar()->inherits("MainStatusBar"));
|
|
return static_cast<MainStatusBar*>(statusBar());
|
|
}
|
|
|
|
KeyboardCustomizationSettings* MainWindow::GetShortcutManager() const
|
|
{
|
|
return m_keyboardCustomization;
|
|
}
|
|
|
|
ActionManager* MainWindow::GetActionManager() const
|
|
{
|
|
return m_actionManager;
|
|
}
|
|
|
|
void MainWindow::OpenViewPane(int paneId)
|
|
{
|
|
OpenViewPane(QtViewPaneManager::instance()->GetPane(paneId));
|
|
}
|
|
|
|
void MainWindow::OpenViewPane(QtViewPane* pane)
|
|
{
|
|
if (pane && pane->IsValid())
|
|
{
|
|
QtViewPaneManager::instance()->OpenPane(pane->m_name);
|
|
}
|
|
else
|
|
{
|
|
if (pane)
|
|
{
|
|
qWarning() << Q_FUNC_INFO << "Invalid pane" << pane->m_id << pane->m_category << pane->m_name;
|
|
}
|
|
else
|
|
{
|
|
qWarning() << Q_FUNC_INFO << "Invalid pane";
|
|
}
|
|
}
|
|
}
|
|
|
|
void MainWindow::AdjustToolBarIconSize(AzQtComponents::ToolBar::ToolBarIconSize size)
|
|
{
|
|
const QList<QToolBar*> toolbars = findChildren<QToolBar*>();
|
|
|
|
// make sure to set this back, so that the general settings page matches up with what the size is too
|
|
if (gSettings.gui.nToolbarIconSize != static_cast<int>(size))
|
|
{
|
|
gSettings.gui.nToolbarIconSize = static_cast<int>(size);
|
|
}
|
|
|
|
for (auto toolbar : toolbars)
|
|
{
|
|
AzQtComponents::ToolBar::setToolBarIconSize(toolbar, size);
|
|
}
|
|
}
|
|
|
|
void MainWindow::OnGameModeChanged(bool inGameMode)
|
|
{
|
|
menuBar()->setDisabled(inGameMode);
|
|
m_toolbarManager->SetEnabled(!inGameMode);
|
|
|
|
// block signals on the switch to game actions before setting the checked state, as
|
|
// setting the checked state triggers the action, which will re-enter this function
|
|
// and result in an infinite loop
|
|
AZStd::vector<QAction*> actions = { m_actionManager->GetAction(ID_VIEW_SWITCHTOGAME), m_actionManager->GetAction(ID_VIEW_SWITCHTOGAME_FULLSCREEN) };
|
|
for (auto action : actions)
|
|
{
|
|
action->blockSignals(true);
|
|
}
|
|
|
|
for (auto action : actions)
|
|
{
|
|
action->setChecked(inGameMode);
|
|
}
|
|
|
|
for (auto action : actions)
|
|
{
|
|
action->blockSignals(false);
|
|
}
|
|
}
|
|
|
|
void MainWindow::OnEditorNotifyEvent(EEditorNotifyEvent ev)
|
|
{
|
|
switch (ev)
|
|
{
|
|
case eNotify_OnEndSceneOpen:
|
|
case eNotify_OnEndSceneSave:
|
|
{
|
|
auto cryEdit = CCryEditApp::instance();
|
|
if (cryEdit)
|
|
{
|
|
cryEdit->SetEditorWindowTitle(0, AZ::Utils::GetProjectName().c_str(), GetIEditor()->GetGameEngine()->GetLevelName());
|
|
}
|
|
}
|
|
break;
|
|
case eNotify_OnCloseScene:
|
|
{
|
|
auto cryEdit = CCryEditApp::instance();
|
|
if (cryEdit)
|
|
{
|
|
cryEdit->SetEditorWindowTitle(0, AZ::Utils::GetProjectName().c_str(), 0);
|
|
}
|
|
}
|
|
break;
|
|
case eNotify_OnRefCoordSysChange:
|
|
emit UpdateRefCoordSys();
|
|
break;
|
|
case eNotify_OnInvalidateControls:
|
|
InvalidateControls();
|
|
break;
|
|
case eNotify_OnBeginGameMode:
|
|
OnGameModeChanged(true);
|
|
break;
|
|
case eNotify_OnEndGameMode:
|
|
OnGameModeChanged(false);
|
|
break;
|
|
// Remove track view option to avoid starting in bad state
|
|
case eNotify_OnBeginSimulationMode:
|
|
if (m_actionManager->HasAction(ID_OPEN_TRACKVIEW))
|
|
{
|
|
QAction* tvAction = m_actionManager->GetAction(ID_OPEN_TRACKVIEW);
|
|
if (tvAction)
|
|
{
|
|
tvAction->setVisible(false);
|
|
}
|
|
}
|
|
break;
|
|
case eNotify_OnEndSimulationMode:
|
|
if (m_actionManager->HasAction(ID_OPEN_TRACKVIEW))
|
|
{
|
|
QAction* tvAction = m_actionManager->GetAction(ID_OPEN_TRACKVIEW);
|
|
if (tvAction)
|
|
{
|
|
tvAction->setVisible(true);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
switch (ev)
|
|
{
|
|
case eNotify_OnBeginSceneOpen:
|
|
case eNotify_OnBeginNewScene:
|
|
case eNotify_OnCloseScene:
|
|
ResetAutoSaveTimers();
|
|
break;
|
|
case eNotify_OnEndSceneOpen:
|
|
case eNotify_OnEndNewScene:
|
|
ResetAutoSaveTimers(true);
|
|
break;
|
|
}
|
|
}
|
|
|
|
void MainWindow::InvalidateControls()
|
|
{
|
|
emit UpdateRefCoordSys();
|
|
}
|
|
|
|
void MainWindow::RegisterStdViewClasses()
|
|
{
|
|
AzAssetBrowserWindow::createListenerForShowAssetEditorEvent(this);
|
|
|
|
CTrackViewDialog::RegisterViewClass();
|
|
CErrorReportDialog::RegisterViewClass();
|
|
CPythonScriptsDialog::RegisterViewClass();
|
|
|
|
AzToolsFramework::CScriptTermDialog::RegisterViewClass();
|
|
CConsoleSCB::RegisterViewClass();
|
|
ConsoleVariableEditor::RegisterViewClass();
|
|
CSettingsManagerDialog::RegisterViewClass();
|
|
AzAssetBrowserWindow::RegisterViewClass();
|
|
AssetEditorWindow::RegisterViewClass();
|
|
|
|
// Notify that views can now be registered
|
|
AzToolsFramework::EditorEvents::Bus::Broadcast(
|
|
&AzToolsFramework::EditorEvents::Bus::Events::NotifyRegisterViews);
|
|
}
|
|
|
|
void MainWindow::OnCustomizeToolbar()
|
|
{
|
|
/* TODO_KDAB, rest of CMainFrm::OnCustomize() goes here*/
|
|
SaveConfig();
|
|
}
|
|
|
|
void MainWindow::RefreshStyle()
|
|
{
|
|
GetIEditor()->Notify(eNotify_OnStyleChanged);
|
|
}
|
|
|
|
void MainWindow::ResetAutoSaveTimers(bool bForceInit)
|
|
{
|
|
if (m_autoSaveTimer)
|
|
{
|
|
delete m_autoSaveTimer;
|
|
}
|
|
if (m_autoRemindTimer)
|
|
{
|
|
delete m_autoRemindTimer;
|
|
}
|
|
m_autoSaveTimer = 0;
|
|
m_autoRemindTimer = 0;
|
|
|
|
if (bForceInit)
|
|
{
|
|
if (gSettings.autoBackupTime > 0 && gSettings.autoBackupEnabled)
|
|
{
|
|
m_autoSaveTimer = new QTimer(this);
|
|
m_autoSaveTimer->start(gSettings.autoBackupTime * 1000 * 60);
|
|
connect(m_autoSaveTimer, &QTimer::timeout, this, [&]() {
|
|
if (gSettings.autoBackupEnabled)
|
|
{
|
|
// Call autosave function of CryEditApp
|
|
GetIEditor()->GetDocument()->SaveAutoBackup();
|
|
}
|
|
});
|
|
}
|
|
if (gSettings.autoRemindTime > 0)
|
|
{
|
|
m_autoRemindTimer = new QTimer(this);
|
|
m_autoRemindTimer->start(gSettings.autoRemindTime * 1000 * 60);
|
|
connect(m_autoRemindTimer, &QTimer::timeout, this, [&]() {
|
|
if (gSettings.autoRemindTime > 0)
|
|
{
|
|
// Remind to save.
|
|
CCryEditApp::instance()->SaveAutoRemind();
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
void MainWindow::ResetBackgroundUpdateTimer()
|
|
{
|
|
if (m_backgroundUpdateTimer)
|
|
{
|
|
delete m_backgroundUpdateTimer;
|
|
m_backgroundUpdateTimer = 0;
|
|
}
|
|
|
|
ICVar* pBackgroundUpdatePeriod = gEnv->pConsole->GetCVar("ed_backgroundUpdatePeriod");
|
|
if (pBackgroundUpdatePeriod && pBackgroundUpdatePeriod->GetIVal() > 0)
|
|
{
|
|
m_backgroundUpdateTimer = new QTimer(this);
|
|
m_backgroundUpdateTimer->start(pBackgroundUpdatePeriod->GetIVal());
|
|
connect(m_backgroundUpdateTimer, &QTimer::timeout, this, [&]() {
|
|
// Make sure that visible editor window get low-fps updates while in the background
|
|
|
|
CCryEditApp* pApp = CCryEditApp::instance();
|
|
if (!isMinimized() && !pApp->IsWindowInForeground())
|
|
{
|
|
pApp->IdleProcessing(true);
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
void MainWindow::UpdateToolsMenu()
|
|
{
|
|
m_levelEditorMenuHandler->UpdateMacrosMenu();
|
|
}
|
|
|
|
int MainWindow::ViewPaneVersion() const
|
|
{
|
|
return m_levelEditorMenuHandler->GetViewPaneVersion();
|
|
}
|
|
|
|
void MainWindow::OnStopAllSounds()
|
|
{
|
|
Audio::SAudioRequest oStopAllSoundsRequest;
|
|
Audio::SAudioManagerRequestData<Audio::eAMRT_STOP_ALL_SOUNDS> oStopAllSoundsRequestData;
|
|
oStopAllSoundsRequest.pData = &oStopAllSoundsRequestData;
|
|
|
|
CryLogAlways("<Audio> Executed \"Stop All Sounds\" command.");
|
|
Audio::AudioSystemRequestBus::Broadcast(&Audio::AudioSystemRequestBus::Events::PushRequest, oStopAllSoundsRequest);
|
|
}
|
|
|
|
void MainWindow::OnRefreshAudioSystem()
|
|
{
|
|
QString sLevelName = GetIEditor()->GetGameEngine()->GetLevelName();
|
|
|
|
if (QString::compare(sLevelName, "Untitled", Qt::CaseInsensitive) == 0)
|
|
{
|
|
// Rather pass NULL to indicate that no level is loaded!
|
|
sLevelName = QString();
|
|
}
|
|
|
|
Audio::AudioSystemRequestBus::Broadcast(&Audio::AudioSystemRequestBus::Events::RefreshAudioSystem, sLevelName.toUtf8().data());
|
|
}
|
|
|
|
void MainWindow::SaveLayout()
|
|
{
|
|
const int MAX_LAYOUTS = ID_VIEW_LAYOUT_LAST - ID_VIEW_LAYOUT_FIRST + 1;
|
|
|
|
if (m_viewPaneManager->LayoutNames(true).count() >= MAX_LAYOUTS)
|
|
{
|
|
QMessageBox::critical(this, tr("Maximum number of layouts reached"), tr("Please delete a saved layout before creating another."));
|
|
return;
|
|
}
|
|
|
|
QString layoutName = QInputDialog::getText(this, tr("Layout Name"), QString()).toLower();
|
|
if (layoutName.isEmpty())
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (m_viewPaneManager->HasLayout(layoutName))
|
|
{
|
|
QMessageBox box(this); // Not static so we can remove help button
|
|
box.setStandardButtons(QMessageBox::Yes | QMessageBox::No);
|
|
box.setText(tr("Overwrite Layout?"));
|
|
box.setIcon(QMessageBox::Warning);
|
|
box.setWindowFlags(box.windowFlags() & ~Qt::WindowContextHelpButtonHint);
|
|
box.setInformativeText(tr("The chosen layout name already exists. Do you want to overwrite it?"));
|
|
if (box.exec() != QMessageBox::Yes)
|
|
{
|
|
SaveLayout();
|
|
return;
|
|
}
|
|
}
|
|
|
|
m_viewPaneManager->SaveLayout(layoutName);
|
|
}
|
|
|
|
void MainWindow::ViewDeletePaneLayout(const QString& layoutName)
|
|
{
|
|
if (layoutName.isEmpty())
|
|
{
|
|
return;
|
|
}
|
|
|
|
QMessageBox box(this); // Not static so we can remove help button
|
|
box.setText(tr("Delete Layout?"));
|
|
box.setStandardButtons(QMessageBox::Yes | QMessageBox::No);
|
|
box.setIcon(QMessageBox::Warning);
|
|
box.setWindowFlags(box.windowFlags() & ~Qt::WindowContextHelpButtonHint);
|
|
box.setInformativeText(tr("Are you sure you want to delete the layout '%1'?").arg(layoutName));
|
|
if (box.exec() == QMessageBox::Yes)
|
|
{
|
|
m_viewPaneManager->RemoveLayout(layoutName);
|
|
}
|
|
}
|
|
|
|
void MainWindow::ViewRenamePaneLayout(const QString& layoutName)
|
|
{
|
|
if (layoutName.isEmpty())
|
|
{
|
|
return;
|
|
}
|
|
|
|
QString newLayoutName;
|
|
bool validName = false;
|
|
while (!validName)
|
|
{
|
|
newLayoutName = QInputDialog::getText(this, tr("Rename layout '%1'").arg(layoutName), QString());
|
|
if (newLayoutName.isEmpty())
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (m_viewPaneManager->HasLayout(newLayoutName))
|
|
{
|
|
QMessageBox box(this); // Not static so we can remove help button
|
|
box.setText(tr("Layout name already exists"));
|
|
box.setStandardButtons(QMessageBox::Yes | QMessageBox::No);
|
|
box.setIcon(QMessageBox::Warning);
|
|
box.setWindowFlags(box.windowFlags() & ~Qt::WindowContextHelpButtonHint);
|
|
box.setInformativeText(tr("The layout name '%1' already exists, please choose a different name").arg(newLayoutName));
|
|
if (box.exec() == QMessageBox::No)
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
validName = true;
|
|
}
|
|
}
|
|
|
|
m_viewPaneManager->RenameLayout(layoutName, newLayoutName);
|
|
}
|
|
|
|
void MainWindow::ViewLoadPaneLayout(const QString& layoutName)
|
|
{
|
|
if (!layoutName.isEmpty())
|
|
{
|
|
m_viewPaneManager->RestoreLayout(layoutName);
|
|
}
|
|
}
|
|
|
|
void MainWindow::ViewSavePaneLayout(const QString& layoutName)
|
|
{
|
|
if (layoutName.isEmpty())
|
|
{
|
|
return;
|
|
}
|
|
|
|
QMessageBox box(this); // Not static so we can remove help button
|
|
box.setText(tr("Overwrite Layout?"));
|
|
box.setStandardButtons(QMessageBox::Yes | QMessageBox::No);
|
|
box.setIcon(QMessageBox::Warning);
|
|
box.setWindowFlags(box.windowFlags() & ~Qt::WindowContextHelpButtonHint);
|
|
box.setInformativeText(tr("Do you want to overwrite the layout '%1' with the current one?").arg(layoutName));
|
|
if (box.exec() == QMessageBox::Yes)
|
|
{
|
|
m_viewPaneManager->SaveLayout(layoutName);
|
|
}
|
|
}
|
|
|
|
void MainWindow::OnUpdateConnectionStatus()
|
|
{
|
|
auto* statusBar = StatusBar();
|
|
|
|
if (!m_connectionListener)
|
|
{
|
|
statusBar->SetItem("connection", tr("Disconnected"), tr("Disconnected"), IDI_BALL_DISABLED);
|
|
//TODO: disable clicking
|
|
}
|
|
else
|
|
{
|
|
using EConnectionState = EngineConnectionListener::EConnectionState;
|
|
int icon = IDI_BALL_OFFLINE;
|
|
QString tooltip, status;
|
|
switch (m_connectionListener->GetState())
|
|
{
|
|
case EConnectionState::Connecting:
|
|
// Checking whether we are not connected here instead of disconnect state because this function is called on a timer
|
|
// and therefore we may not receive the disconnect state.
|
|
if (m_connectedToAssetProcessor)
|
|
{
|
|
m_connectedToAssetProcessor = false;
|
|
m_showAPDisconnectDialog = true;
|
|
}
|
|
tooltip = tr("Connecting to Asset Processor");
|
|
icon = IDI_BALL_PENDING;
|
|
break;
|
|
case EConnectionState::Disconnecting:
|
|
tooltip = tr("Disconnecting from Asset Processor");
|
|
icon = IDI_BALL_PENDING;
|
|
break;
|
|
case EConnectionState::Listening:
|
|
if (m_connectedToAssetProcessor)
|
|
{
|
|
m_connectedToAssetProcessor = false;
|
|
m_showAPDisconnectDialog = true;
|
|
}
|
|
tooltip = tr("Listening for incoming connections");
|
|
icon = IDI_BALL_PENDING;
|
|
break;
|
|
case EConnectionState::Connected:
|
|
m_connectedToAssetProcessor = true;
|
|
tooltip = tr("Connected to Asset Processor");
|
|
icon = IDI_BALL_ONLINE;
|
|
break;
|
|
case EConnectionState::Disconnected:
|
|
icon = IDI_BALL_OFFLINE;
|
|
tooltip = tr("Disconnected from Asset Processor");
|
|
break;
|
|
}
|
|
|
|
if (m_connectedToAssetProcessor)
|
|
{
|
|
m_connectionLostTimer->stop();
|
|
}
|
|
|
|
tooltip += "\n Last Asset Processor Task: ";
|
|
tooltip += m_connectionListener->LastAssetProcessorTask().c_str();
|
|
tooltip += "\n";
|
|
AZStd::set<AZStd::string> failedJobs = m_connectionListener->FailedJobsList();
|
|
int failureCount = failedJobs.size();
|
|
if (failureCount)
|
|
{
|
|
tooltip += "\n Failed Jobs\n";
|
|
for (auto failedJob : failedJobs)
|
|
{
|
|
tooltip += failedJob.c_str();
|
|
tooltip += "\n";
|
|
}
|
|
}
|
|
|
|
status = tr("Pending Jobs : %1 Failed Jobs : %2").arg(m_connectionListener->GetJobsCount()).arg(failureCount);
|
|
|
|
statusBar->SetItem(QtUtil::ToQString("connection"), status, tooltip, icon);
|
|
|
|
if (m_showAPDisconnectDialog && m_connectionListener->GetState() != EConnectionState::Connected)
|
|
{
|
|
m_showAPDisconnectDialog = false;// Just show the dialog only once if connection is lost
|
|
m_connectionLostTimer->setSingleShot(true);
|
|
m_connectionLostTimer->start(15000);
|
|
}
|
|
}
|
|
}
|
|
|
|
void MainWindow::ShowConnectionDisconnectedDialog()
|
|
{
|
|
// when REMOTE_ASSET_PROCESSOR is undef'd it means behave as if there is no such thing as the remote asset processor.
|
|
#ifdef REMOTE_ASSET_PROCESSOR
|
|
if (gEnv && gEnv->pSystem)
|
|
{
|
|
QMessageBox messageBox(this);
|
|
messageBox.setWindowTitle(tr("Asset Processor has disconnected."));
|
|
messageBox.setText(
|
|
tr("Asset Processor is not connected. Please try (re)starting the Asset Processor or restarting the Editor.<br><br>"
|
|
"Data may be lost while the Asset Processor is not running!<br>"
|
|
"The status of the Asset Processor can be monitored from the editor in the bottom-right corner of the status bar.<br><br>"
|
|
"Would you like to start the asset processor?<br>"));
|
|
messageBox.setStandardButtons(QMessageBox::Yes | QMessageBox::Ignore);
|
|
messageBox.setDefaultButton(QMessageBox::Yes);
|
|
messageBox.setIcon(QMessageBox::Critical);
|
|
if (messageBox.exec() == QMessageBox::Yes)
|
|
{
|
|
AzFramework::AssetSystem::LaunchAssetProcessor();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
QMessageBox::critical(this, tr("Asset Processor has disconnected."),
|
|
tr("Asset Processor is not connected. Please try (re)starting the asset processor or restarting the Editor.<br><br>"
|
|
"Data may be lost while the asset processor is not running!<br>"
|
|
"The status of the asset processor can be monitored from the editor in the bottom-right corner of the status bar."));
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void MainWindow::OnConnectionStatusClicked()
|
|
{
|
|
AzFramework::AssetSystemRequestBus::Broadcast(&AzFramework::AssetSystemRequestBus::Events::ShowAssetProcessor);
|
|
}
|
|
|
|
static bool paneLessThan(const QtViewPane& v1, const QtViewPane& v2)
|
|
{
|
|
return v1.m_name.compare(v2.m_name, Qt::CaseInsensitive) < 0;
|
|
}
|
|
|
|
void MainWindow::RegisterOpenWndCommands()
|
|
{
|
|
s_openViewCmds.clear();
|
|
|
|
auto panes = m_viewPaneManager->GetRegisteredPanes(/* viewPaneMenuOnly=*/ false);
|
|
std::sort(panes.begin(), panes.end(), paneLessThan);
|
|
|
|
for (auto viewPane : panes)
|
|
{
|
|
if (viewPane.m_category.isEmpty())
|
|
{
|
|
continue;
|
|
}
|
|
|
|
const QString className = viewPane.m_name;
|
|
|
|
// Make a open-view command for the class.
|
|
QString classNameLowered = viewPane.m_name.toLower();
|
|
classNameLowered.replace(' ', '_');
|
|
QString openCommandName = "open_";
|
|
openCommandName += classNameLowered;
|
|
|
|
CEditorOpenViewCommand* pCmd = new CEditorOpenViewCommand(GetIEditor(), viewPane.m_name);
|
|
s_openViewCmds.push_back(pCmd);
|
|
|
|
CCommand0::SUIInfo cmdUI;
|
|
cmdUI.caption = className.toUtf8().data();
|
|
cmdUI.tooltip = (QString("Open ") + className).toUtf8().data();
|
|
cmdUI.iconFilename = className.toUtf8().data();
|
|
GetIEditor()->GetCommandManager()->RegisterUICommand("editor", openCommandName.toUtf8().data(),
|
|
"", "", AZStd::bind(&CEditorOpenViewCommand::Execute, pCmd), cmdUI);
|
|
GetIEditor()->GetCommandManager()->GetUIInfo("editor", openCommandName.toUtf8().data(), cmdUI);
|
|
}
|
|
}
|
|
|
|
bool MainWindow::event(QEvent* event)
|
|
{
|
|
#ifdef Q_OS_MAC
|
|
if (event->type() == QEvent::HoverMove)
|
|
{
|
|
// this fixes a problem on macOS where the mouse cursor was not
|
|
// set when hovering over the splitter handles between dock widgets
|
|
// it might be fixed in future Qt versions
|
|
auto mouse = static_cast<QHoverEvent*>(event);
|
|
bool result = QMainWindow::event(event);
|
|
void setCocoaMouseCursor(QWidget*);
|
|
setCocoaMouseCursor(childAt(mouse->pos()));
|
|
return result;
|
|
}
|
|
#endif
|
|
return QMainWindow::event(event);
|
|
}
|
|
|
|
void MainWindow::ToggleConsole()
|
|
{
|
|
m_viewPaneManager->TogglePane(LyViewPane::Console);
|
|
|
|
QtViewPane* pane = m_viewPaneManager->GetPane(LyViewPane::Console);
|
|
if (!pane)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// If we toggled the console on, we want to focus its input text field
|
|
if (pane->IsVisible())
|
|
{
|
|
CConsoleSCB* console = qobject_cast<CConsoleSCB*>(pane->Widget());
|
|
if (!console)
|
|
{
|
|
return;
|
|
}
|
|
|
|
console->SetInputFocus();
|
|
}
|
|
}
|
|
|
|
void MainWindow::OnViewPaneCreated(const QtViewPane* pane)
|
|
{
|
|
QAction* action = nullptr;
|
|
int id = pane->m_id;
|
|
|
|
// Use built-in action id if available
|
|
if (pane->m_options.builtInActionId != -1)
|
|
{
|
|
id = pane->m_options.builtInActionId;
|
|
}
|
|
|
|
if (m_actionManager->HasAction(id))
|
|
{
|
|
action = m_actionManager->GetAction(id);
|
|
action->setChecked(true);
|
|
|
|
connect(pane->m_dockWidget->toggleViewAction(), &QAction::toggled,
|
|
action, &QAction::setChecked, Qt::UniqueConnection);
|
|
}
|
|
}
|
|
|
|
void MainWindow::ConnectivityStateChanged(const AzToolsFramework::SourceControlState state)
|
|
{
|
|
bool connected = false;
|
|
ISourceControl* pSourceControl = GetIEditor() ? GetIEditor()->GetSourceControl() : nullptr;
|
|
if (pSourceControl)
|
|
{
|
|
pSourceControl->SetSourceControlState(state);
|
|
if (state == AzToolsFramework::SourceControlState::Active || state == AzToolsFramework::SourceControlState::ConfigurationInvalid)
|
|
{
|
|
connected = true;
|
|
}
|
|
}
|
|
|
|
gSettings.enableSourceControl = connected;
|
|
gSettings.SaveEnableSourceControlFlag(false);
|
|
}
|
|
|
|
void MainWindow::OnGotoSelected()
|
|
{
|
|
AzToolsFramework::EditorRequestBus::Broadcast(&AzToolsFramework::EditorRequestBus::Events::GoToSelectedEntitiesInViewports);
|
|
}
|
|
|
|
void MainWindow::OnGotoSliceRoot()
|
|
{
|
|
int numViews = GetIEditor()->GetViewManager()->GetViewCount();
|
|
for (int i = 0; i < numViews; ++i)
|
|
{
|
|
CViewport* viewport = GetIEditor()->GetViewManager()->GetView(i);
|
|
if (viewport)
|
|
{
|
|
viewport->CenterOnSliceInstance();
|
|
}
|
|
}
|
|
}
|
|
|
|
void MainWindow::ShowCustomizeToolbarDialog()
|
|
{
|
|
if (m_toolbarCustomizationDialog)
|
|
{
|
|
return;
|
|
}
|
|
|
|
m_toolbarCustomizationDialog = new ToolbarCustomizationDialog(this);
|
|
m_toolbarCustomizationDialog->show();
|
|
}
|
|
|
|
QMenu* MainWindow::createPopupMenu()
|
|
{
|
|
QMenu* menu = QMainWindow::createPopupMenu();
|
|
menu->addSeparator();
|
|
QAction* action = menu->addAction(QStringLiteral("Customize..."));
|
|
connect(action, &QAction::triggered, this, &MainWindow::ShowCustomizeToolbarDialog);
|
|
return menu;
|
|
}
|
|
|
|
ToolbarManager* MainWindow::GetToolbarManager() const
|
|
{
|
|
return m_toolbarManager;
|
|
}
|
|
|
|
bool MainWindow::IsCustomizingToolbars() const
|
|
{
|
|
return m_toolbarCustomizationDialog != nullptr;
|
|
}
|
|
|
|
QWidget* MainWindow::CreateToolbarWidget(int actionId)
|
|
{
|
|
QWidgetAction* action = qobject_cast<QWidgetAction*>(m_actionManager->GetAction(actionId));
|
|
if (!action)
|
|
{
|
|
qWarning() << Q_FUNC_INFO << "No QWidgetAction for actionId = " << actionId;
|
|
return nullptr;
|
|
}
|
|
|
|
QWidget* w = nullptr;
|
|
switch (actionId)
|
|
{
|
|
case ID_TOOLBAR_WIDGET_UNDO:
|
|
w = CreateUndoRedoButton(ID_UNDO);
|
|
break;
|
|
case ID_TOOLBAR_WIDGET_REDO:
|
|
w = CreateUndoRedoButton(ID_REDO);
|
|
break;
|
|
case ID_TOOLBAR_WIDGET_SPACER_RIGHT:
|
|
w = CreateSpacerRightWidget();
|
|
break;
|
|
default:
|
|
qWarning() << Q_FUNC_INFO << "Unknown id " << actionId;
|
|
return nullptr;
|
|
}
|
|
|
|
return w;
|
|
}
|
|
|
|
|
|
// don't want to eat escape as if it were a shortcut, as it would eat it for other windows that also care about escape
|
|
// and are reading it as an event rather.
|
|
void MainWindow::keyPressEvent(QKeyEvent* e)
|
|
{
|
|
// We shouldn't need to do this, as there's already an escape key shortcut set on an action
|
|
// attached to the MainWindow. We need to explicitly trap the escape key here because when in
|
|
// Game Mode, all of the actions attached to the MainWindow are disabled.
|
|
if (e->key() == Qt::Key_Escape)
|
|
{
|
|
MainWindow::OnEscapeAction();
|
|
return;
|
|
}
|
|
return QMainWindow::keyPressEvent(e);
|
|
}
|
|
|
|
void MainWindow::dragEnterEvent(QDragEnterEvent *event)
|
|
{
|
|
using namespace AzQtComponents;
|
|
DragAndDropContextBase context;
|
|
DragAndDropEventsBus::Event(DragAndDropContexts::EditorMainWindow, &DragAndDropEvents::DragEnter, event, context);
|
|
}
|
|
|
|
void MainWindow::dragMoveEvent(QDragMoveEvent* event)
|
|
{
|
|
using namespace AzQtComponents;
|
|
DragAndDropContextBase context;
|
|
DragAndDropEventsBus::Event(DragAndDropContexts::EditorMainWindow, &DragAndDropEvents::DragMove, event, context);
|
|
}
|
|
|
|
void MainWindow::dragLeaveEvent(QDragLeaveEvent* event)
|
|
{
|
|
using namespace AzQtComponents;
|
|
DragAndDropEventsBus::Event(DragAndDropContexts::EditorMainWindow, &DragAndDropEvents::DragLeave, event);
|
|
}
|
|
|
|
void MainWindow::dropEvent(QDropEvent *event)
|
|
{
|
|
using namespace AzQtComponents;
|
|
DragAndDropContextBase context;
|
|
DragAndDropEventsBus::Event(DragAndDropContexts::EditorMainWindow, &DragAndDropEvents::Drop, event, context);
|
|
}
|
|
|
|
bool MainWindow::focusNextPrevChild(bool next)
|
|
{
|
|
// Don't change the focus when we're in game mode or else the viewport could
|
|
// stop receiving input events
|
|
if (GetIEditor()->IsInGameMode())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return QMainWindow::focusNextPrevChild(next);
|
|
}
|
|
|
|
namespace AzToolsFramework
|
|
{
|
|
void MainWindowEditorFuncsHandler::Reflect(AZ::ReflectContext* context)
|
|
{
|
|
if (auto behaviorContext = azrtti_cast<AZ::BehaviorContext*>(context))
|
|
{
|
|
// this will put these methods into the 'azlmbr.legacy.general' module
|
|
auto addLegacyGeneral = [](AZ::BehaviorContext::GlobalMethodBuilder methodBuilder)
|
|
{
|
|
methodBuilder->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Automation)
|
|
->Attribute(AZ::Script::Attributes::Category, "Legacy/Editor")
|
|
->Attribute(AZ::Script::Attributes::Module, "legacy.general");
|
|
};
|
|
addLegacyGeneral(behaviorContext->Method("open_pane", PyOpenViewPane, nullptr, "Opens a view pane specified by the pane class name."));
|
|
addLegacyGeneral(behaviorContext->Method("close_pane", PyCloseViewPane, nullptr, "Closes a view pane specified by the pane class name."));
|
|
addLegacyGeneral(behaviorContext->Method("is_pane_visible", PyIsViewPaneVisible, nullptr, "Returns true if pane specified by the pane class name is visible."));
|
|
addLegacyGeneral(behaviorContext->Method("get_pane_class_names", PyGetViewPaneNames, nullptr, "Get all available class names for use with open_pane & close_pane."));
|
|
addLegacyGeneral(behaviorContext->Method("exit", PyExit, nullptr, "Exits the editor."));
|
|
addLegacyGeneral(behaviorContext->Method("exit_no_prompt", PyExitNoPrompt, nullptr, "Exits the editor without prompting to save first."));
|
|
addLegacyGeneral(behaviorContext->Method("test_output", PyTestOutput, nullptr, "Report test information."));
|
|
}
|
|
}
|
|
}
|
|
|
|
#include <moc_MainWindow.cpp>
|