/* * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or * its licensors. * * For complete copyright and license terms please see the LICENSE at the root of this * distribution (the "License"). All use of this software is governed by the License, * or, if provided, by the license below or the license accompanying this file. Do not * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // Save Format Conversion #include #include //// #include #include #include #include #include #include #include #include namespace ScriptCanvasEditor { using namespace AzToolsFramework; namespace { template class ScopedVariableSetter { public: ScopedVariableSetter(T& value) : m_oldValue(value) , m_value(value) { } ScopedVariableSetter(T& value, const T& newValue) : m_oldValue(value) , m_value(value) { m_value = newValue; } ~ScopedVariableSetter() { m_value = m_oldValue; } private: T m_oldValue; T& m_value; }; template AZ::EntityId CreateMimeDataDelegate(ComponentArgs... componentArgs) { AZ::Entity* mimeDelegateEntity = aznew AZ::Entity("MimeData Delegate"); mimeDelegateEntity->CreateComponent(AZStd::forward(componentArgs) ...); mimeDelegateEntity->Init(); mimeDelegateEntity->Activate(); return mimeDelegateEntity->GetId(); } void EnsureSaveDestinationDirectory(AZStd::string directoryLocation) { if (AzFramework::StringFunc::Path::HasExtension(directoryLocation.c_str())) { size_t offset = directoryLocation.find_last_of('/'); if (offset != AZStd::string::npos) { directoryLocation = directoryLocation.substr(0, offset); } else { AzFramework::StringFunc::Path::StripComponent(directoryLocation, true); } } // We just need the path to exist. QDir canvasDirectory = QDir(directoryLocation.c_str()); if (!canvasDirectory.exists()) { canvasDirectory.mkpath("."); } } } // anonymous namespace. void Workspace::Save() { auto workspace = AZ::UserSettings::CreateFind(AZ_CRC("ScriptCanvasEditorWindowState", 0x10c47d36), AZ::UserSettings::CT_LOCAL); if (workspace) { workspace->Init(m_mainWindow->saveState(), m_mainWindow->saveGeometry()); Widget::GraphTabBar* tabBar = m_mainWindow->m_tabBar; AZStd::vector activeAssets; AZ::Data::AssetId focusedAssetId = tabBar->FindAssetId(tabBar->currentIndex()); if (m_rememberOpenCanvases) { activeAssets.reserve(tabBar->count()); for (int i = 0; i < tabBar->count(); ++i) { AZ::Data::AssetId assetId = tabBar->FindAssetId(i); const Tracker::ScriptCanvasFileState& fileState = m_mainWindow->GetAssetFileState(assetId); if (fileState == Tracker::ScriptCanvasFileState::MODIFIED || fileState == Tracker::ScriptCanvasFileState::UNMODIFIED) { AZ::Data::AssetId sourceId = GetSourceAssetId(assetId); if (sourceId.IsValid()) { activeAssets.push_back(sourceId); } } else if (assetId == focusedAssetId) { focusedAssetId.SetInvalid(); } } // The assetId needs to be the file AssetId to restore the workspace if (focusedAssetId.IsValid()) { focusedAssetId = GetSourceAssetId(focusedAssetId); } // If our currently focused asset won't be restored, just show the first element. if (!focusedAssetId.IsValid()) { if (!activeAssets.empty()) { focusedAssetId = activeAssets.front(); } } } workspace->Clear(); if (!activeAssets.empty()) { workspace->ConfigureActiveAssets(focusedAssetId, activeAssets); } } } // Workspace void Workspace::Restore() { auto workspace = AZ::UserSettings::Find(AZ_CRC("ScriptCanvasEditorWindowState", 0x10c47d36), AZ::UserSettings::CT_LOCAL); if (workspace) { workspace->Restore(qobject_cast(m_mainWindow)); if (m_rememberOpenCanvases) { for (const auto& fileAssetId : workspace->GetActiveAssetIds()) { m_loadingAssets.push_back(fileAssetId); } if (m_loadingAssets.empty()) { m_mainWindow->OnWorkspaceRestoreEnd(AZ::Data::AssetId()); } else { m_mainWindow->OnWorkspaceRestoreStart(); } AZ::Data::AssetId focusedAsset = workspace->GetFocusedAssetId(); m_queuedAssetFocus = workspace->GetFocusedAssetId(); for (const auto& fileAssetId : workspace->GetActiveAssetIds()) { AssetTrackerNotificationBus::MultiHandler::BusConnect(fileAssetId); Callbacks::OnAssetReadyCallback onAssetReady = [this, focusedAsset, fileAssetId](ScriptCanvasMemoryAsset& asset) { // If we get an error callback. Just remove it from out active lists. if (asset.IsSourceInError()) { if (fileAssetId == m_queuedAssetFocus) { m_queuedAssetFocus = AZ::Data::AssetId(); } SignalAssetComplete(asset.GetFileAssetId()); } }; bool loadedFile = true; AssetTrackerRequestBus::BroadcastResult(loadedFile, &AssetTrackerRequests::Load, fileAssetId, /*assetInfo.m_assetType*/azrtti_typeid(), onAssetReady); if (!loadedFile) { if (fileAssetId == m_queuedAssetFocus) { m_queuedAssetFocus = AZ::Data::AssetId(); } SignalAssetComplete(fileAssetId); } } } else { m_mainWindow->OnWorkspaceRestoreEnd(AZ::Data::AssetId()); } } } void Workspace::OnAssetReady(const ScriptCanvasMemoryAsset::pointer memoryAsset) { const AZ::Data::AssetId& fileAssetId = memoryAsset->GetFileAssetId(); if (AssetTrackerNotificationBus::MultiHandler::BusIsConnectedId(fileAssetId)) { AssetTrackerNotificationBus::MultiHandler::BusDisconnect(fileAssetId); m_mainWindow->OpenScriptCanvasAsset(*memoryAsset); SignalAssetComplete(fileAssetId); } } void Workspace::SignalAssetComplete(const AZ::Data::AssetId& fileAssetId) { auto it = AZStd::find(m_loadingAssets.begin(), m_loadingAssets.end(), fileAssetId); if (it != m_loadingAssets.end()) { m_loadingAssets.erase(it); } //! When we are done loading all assets we can safely set the focus to the recorded asset if (m_loadingAssets.empty()) { m_mainWindow->OnWorkspaceRestoreEnd(m_queuedAssetFocus); m_queuedAssetFocus.SetInvalid(); } } AZ::Data::AssetId Workspace::GetSourceAssetId(const AZ::Data::AssetId& memoryAssetId) const { ScriptCanvasMemoryAsset::pointer memoryAsset; AssetTrackerRequestBus::BroadcastResult(memoryAsset, &AssetTrackerRequests::GetAsset, memoryAssetId); if (memoryAsset) { return memoryAsset->GetFileAssetId(); } return AZ::Data::AssetId(); } //////////////// // MainWindow //////////////// MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent, Qt::Widget | Qt::WindowMinMaxButtonsHint) , ui(new Ui::MainWindow) , m_loadingNewlySavedFile(false) , m_isClosingTabs(false) , m_enterState(false) , m_ignoreSelection(false) , m_isRestoringWorkspace(false) , m_preventUndoStateUpdateCount(0) , m_queueCloseRequest(false) , m_hasQueuedClose(false) , m_isInAutomation(false) , m_allowAutoSave(true) , m_systemTickActions(0) , m_closeCurrentGraphAfterSave(false) , m_batchTool(nullptr) , m_styleManager(ScriptCanvasEditor::AssetEditorId, "ScriptCanvas/StyleSheet/graphcanvas_style.json") { VariablePaletteRequestBus::Handler::BusConnect(); AZStd::array unresolvedPath; AZ::IO::FileIOBase::GetInstance()->ResolvePath("@assets@/editor/translation/scriptcanvas_en_us.qm", unresolvedPath.data(), unresolvedPath.size()); QString translationFilePath(unresolvedPath.data()); if ( m_translator.load(QLocale::Language::English, translationFilePath) ) { if ( !qApp->installTranslator(&m_translator) ) { AZ_Warning("ScriptCanvas", false, "Error installing translation %s!", unresolvedPath.data()); } } else { AZ_Warning("ScriptCanvas", false, "Error loading translation file %s", unresolvedPath.data()); } AzToolsFramework::AssetBrowser::AssetBrowserModel* assetBrowserModel = nullptr; AzToolsFramework::AssetBrowser::AssetBrowserComponentRequestBus::BroadcastResult(assetBrowserModel, &AzToolsFramework::AssetBrowser::AssetBrowserComponentRequests::GetAssetBrowserModel); { m_scriptEventsAssetModel = new AzToolsFramework::AssetBrowser::AssetBrowserFilterModel(this); AzToolsFramework::AssetBrowser::AssetGroupFilter* scriptEventAssetFilter = new AzToolsFramework::AssetBrowser::AssetGroupFilter(); scriptEventAssetFilter->SetAssetGroup(ScriptEvents::ScriptEventsAsset::GetGroup()); scriptEventAssetFilter->SetFilterPropagation(AzToolsFramework::AssetBrowser::AssetBrowserEntryFilter::PropagateDirection::Down); // Filter doesn't actually work for whatever reason //m_scriptEventsAssetModel->SetFilter(AzToolsFramework::AssetBrowser::FilterConstType(scriptEventAssetFilter)); m_scriptEventsAssetModel->setSourceModel(assetBrowserModel); } { m_scriptCanvasAssetModel = new AzToolsFramework::AssetBrowser::AssetBrowserFilterModel(this); AzToolsFramework::AssetBrowser::AssetGroupFilter* scriptCanvasAssetFilter = new AzToolsFramework::AssetBrowser::AssetGroupFilter(); scriptCanvasAssetFilter->SetAssetGroup(ScriptCanvasAsset::Description::GetGroup(azrtti_typeid())); scriptCanvasAssetFilter->SetFilterPropagation(AzToolsFramework::AssetBrowser::AssetBrowserEntryFilter::PropagateDirection::Down); // Filter doesn't actually work for whatever reason //m_scriptCanvasAssetModel->SetFilter(AzToolsFramework::AssetBrowser::FilterConstType(scriptCanvasAssetFilter)); m_scriptCanvasAssetModel->setSourceModel(assetBrowserModel); } m_nodePaletteModel.AssignAssetModel(m_scriptCanvasAssetModel); ui->setupUi(this); CreateMenus(); UpdateRecentMenu(); m_host = new QWidget(); m_layout = new QVBoxLayout(); m_emptyCanvas = aznew GraphCanvas::GraphCanvasEditorEmptyDockWidget(this); m_emptyCanvas->SetDragTargetText(tr("Use the File Menu or drag out a node from the Node Palette to create a new script.").toStdString().c_str()); m_emptyCanvas->SetEditorId(ScriptCanvasEditor::AssetEditorId); m_emptyCanvas->RegisterAcceptedMimeType(Widget::NodePaletteDockWidget::GetMimeType()); m_emptyCanvas->RegisterAcceptedMimeType(AzToolsFramework::EditorEntityIdContainer::GetMimeType()); m_editorToolbar = aznew GraphCanvas::AssetEditorToolbar(ScriptCanvasEditor::AssetEditorId); // Custom Actions { m_assignToSelectedEntity = new QToolButton(); m_assignToSelectedEntity->setIcon(QIcon(":/ScriptCanvasEditorResources/Resources/attach_to_entity.png")); m_assignToSelectedEntity->setToolTip("Assigns the currently active graph to all of the currently selected entities."); m_selectedEntityMenu = new QMenu(); m_assignToSelectedEntity->setPopupMode(QToolButton::ToolButtonPopupMode::MenuButtonPopup); m_assignToSelectedEntity->setMenu(m_selectedEntityMenu); m_assignToSelectedEntity->setEnabled(false); m_editorToolbar->AddCustomAction(m_assignToSelectedEntity); QObject::connect(m_selectedEntityMenu, &QMenu::aboutToShow, this, &MainWindow::OnSelectedEntitiesAboutToShow); QObject::connect(m_assignToSelectedEntity, &QToolButton::clicked, this, &MainWindow::OnAssignToSelectedEntities); } // Creation Actions { m_createScriptCanvas = new QToolButton(); m_createScriptCanvas->setIcon(QIcon(ScriptCanvas::AssetDescription::GetIconPath())); m_createScriptCanvas->setToolTip("Creates a new Script Canvas Graph"); QObject::connect(m_createScriptCanvas, &QToolButton::clicked, this, &MainWindow::OnFileNew); m_editorToolbar->AddCreationAction(m_createScriptCanvas); m_createFunction = new QToolButton(); m_createFunction->setIcon(QIcon(ScriptCanvas::AssetDescription::GetIconPath())); m_createFunction->setToolTip("Creates a new Script Canvas Function Graph"); QObject::connect(m_createFunction, &QToolButton::clicked, this, &MainWindow::OnFileNewFunction); m_editorToolbar->AddCreationAction(m_createFunction); } { m_createFunctionInput = new QToolButton(); m_createFunctionInput->setToolTip("Creates an Execution Nodeling on the leftmost side of the graph to be used as input for the graph."); m_createFunctionInput->setIcon(QIcon(":/ScriptCanvasEditorResources/Resources/create_function_input.png")); m_createFunctionInput->setEnabled(false); } m_editorToolbar->AddCustomAction(m_createFunctionInput); connect(m_createFunctionInput, &QToolButton::clicked, this, &MainWindow::CreateFunctionInput); { m_createFunctionOutput = new QToolButton(); m_createFunctionOutput->setToolTip("Creates an Execution Nodeling on the rightmost side of the graph to be used as output for the graph."); m_createFunctionOutput->setIcon(QIcon(":/ScriptCanvasEditorResources/Resources/create_function_output.png")); m_createFunctionOutput->setEnabled(false); } m_editorToolbar->AddCustomAction(m_createFunctionOutput); connect(m_createFunctionOutput, &QToolButton::clicked, this, &MainWindow::CreateFunctionOutput); { m_validateGraphToolButton = new QToolButton(); m_validateGraphToolButton->setToolTip("Will run a validation check on the current graph and report any warnings/errors discovered."); m_validateGraphToolButton->setIcon(QIcon(":/ScriptCanvasEditorResources/Resources/validate_icon.png")); m_validateGraphToolButton->setEnabled(false); } m_editorToolbar->AddCustomAction(m_validateGraphToolButton); connect(m_validateGraphToolButton, &QToolButton::clicked, this, &MainWindow::OnValidateCurrentGraph); m_layout->addWidget(m_editorToolbar); // Tab bar { m_tabWidget = new AzQtComponents::TabWidget(m_host); m_tabBar = new Widget::GraphTabBar(m_tabWidget); m_tabWidget->setCustomTabBar(m_tabBar); m_tabWidget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); connect(m_tabBar, &QTabBar::tabCloseRequested, this, &MainWindow::OnTabCloseButtonPressed); connect(m_tabBar, &Widget::GraphTabBar::TabCloseNoButton, this, &MainWindow::OnTabCloseRequest); connect(m_tabBar, &Widget::GraphTabBar::SaveTab, this, &MainWindow::SaveTab); connect(m_tabBar, &Widget::GraphTabBar::CloseAllTabsSignal, this, &MainWindow::CloseAllTabs); connect(m_tabBar, &Widget::GraphTabBar::CloseAllTabsButSignal, this, &MainWindow::CloseAllTabsBut); connect(m_tabBar, &Widget::GraphTabBar::CopyPathToClipboard, this, &MainWindow::CopyPathToClipboard); connect(m_tabBar, &Widget::GraphTabBar::OnActiveFileStateChanged, this, &MainWindow::OnActiveFileStateChanged); AzQtComponents::TabWidget::applySecondaryStyle(m_tabWidget, false); m_tabWidget->setObjectName("ScriptCanvasTabs"); m_layout->addWidget(m_tabWidget); } m_commandLine = new Widget::CommandLine(this); m_commandLine->setBaseSize(QSize(size().width(), m_commandLine->size().height())); m_commandLine->setObjectName("CommandLine"); m_commandLine->hide(); m_layout->addWidget(m_commandLine); m_layout->addWidget(m_emptyCanvas); // Minimap should be a child of the dock widget. But until performance concerns are resolved // we want to hide it(mostly to avoid re-setting up all of the structural code around it). // // If this is a child, it appears on the default context menu to show/hide. m_minimap = aznew GraphCanvas::MiniMapDockWidget(ScriptCanvasEditor::AssetEditorId); m_minimap->setObjectName("MiniMapDockWidget"); m_statusWidget = aznew MainWindowStatusWidget(this); statusBar()->addWidget(m_statusWidget,1); QObject::connect(m_statusWidget, &MainWindowStatusWidget::OnErrorButtonPressed, this, &MainWindow::OnShowValidationErrors); QObject::connect(m_statusWidget, &MainWindowStatusWidget::OnWarningButtonPressed, this, &MainWindow::OnShowValidationWarnings); m_nodePaletteModel.RepopulateModel(); // Order these are created denotes the order for an auto-generate Qt menu. Keeping this construction order // in sync with the order we display under tools for consistency. { const bool isInContextMenu = false; Widget::ScriptCanvasNodePaletteConfig nodePaletteConfig(m_nodePaletteModel, m_scriptEventsAssetModel, isInContextMenu); m_nodePalette = aznew Widget::NodePaletteDockWidget(tr("Node Palette"), this, nodePaletteConfig); m_nodePalette->setObjectName("NodePalette"); } m_propertyGrid = new Widget::PropertyGrid(this, "Node Inspector"); m_propertyGrid->setObjectName("NodeInspector"); m_bookmarkDockWidget = aznew GraphCanvas::BookmarkDockWidget(ScriptCanvasEditor::AssetEditorId, this); m_variableDockWidget = new VariableDockWidget(this); m_variableDockWidget->setObjectName("VariableManager"); QObject::connect(m_variableDockWidget, &VariableDockWidget::OnVariableSelectionChanged, this, &MainWindow::OnVariableSelectionChanged); // This needs to happen after the node palette is created, because we scrape for the variable data from inside // of there. m_variableDockWidget->PopulateVariablePalette(m_variablePaletteTypes); m_validationDockWidget = aznew GraphValidationDockWidget(this); m_validationDockWidget->setObjectName("ValidationDockWidget"); // End Construction list m_loggingWindow = aznew LoggingWindow(this); m_loggingWindow->setObjectName("LoggingWindow"); m_ebusHandlerActionMenu = aznew EBusHandlerActionMenu(); m_statisticsDialog = aznew StatisticsDialog(m_nodePaletteModel, m_scriptCanvasAssetModel, nullptr); m_statisticsDialog->hide(); m_presetEditor = aznew GraphCanvas::ConstructPresetDialog(nullptr); m_presetEditor->SetEditorId(ScriptCanvasEditor::AssetEditorId); m_presetWrapper = new AzQtComponents::WindowDecorationWrapper(AzQtComponents::WindowDecorationWrapper::OptionAutoTitleBarButtons); m_presetWrapper->setGuest(m_presetEditor); m_presetWrapper->hide(); m_host->setLayout(m_layout); setCentralWidget(m_host); m_workspace = new Workspace(this); QTimer::singleShot(0, [this]() { SetDefaultLayout(); if (m_activeAssetId.IsValid()) { m_queuedFocusOverride = m_activeAssetId; } m_workspace->Restore(); m_workspace->Save(); }); m_entityMimeDelegateId = CreateMimeDataDelegate(); ScriptCanvasEditor::GeneralRequestBus::Handler::BusConnect(); ScriptCanvasEditor::AutomationRequestBus::Handler::BusConnect(); UIRequestBus::Handler::BusConnect(); UndoNotificationBus::Handler::BusConnect(); GraphCanvas::AssetEditorRequestBus::Handler::BusConnect(ScriptCanvasEditor::AssetEditorId); GraphCanvas::AssetEditorSettingsRequestBus::Handler::BusConnect(ScriptCanvasEditor::AssetEditorId); ScriptCanvas::BatchOperationNotificationBus::Handler::BusConnect(); AssetGraphSceneBus::Handler::BusConnect(); AzToolsFramework::ToolsApplicationNotificationBus::Handler::BusConnect(); UINotificationBus::Broadcast(&UINotifications::MainWindowCreationEvent, this); Metrics::MetricsEventsBus::Broadcast(&Metrics::MetricsEventRequests::SendEditorMetric, ScriptCanvasEditor::Metrics::Events::Editor::Open, m_activeAssetId); m_userSettings = AZ::UserSettings::CreateFind(AZ_CRC("ScriptCanvasPreviewSettings", 0x1c5a2965), AZ::UserSettings::CT_LOCAL); if (m_userSettings) { m_allowAutoSave = m_userSettings->m_autoSaveConfig.m_enabled; m_autoSaveTimer.setInterval(m_userSettings->m_autoSaveConfig.m_timeSeconds * 1000); m_userSettings->m_constructPresets.SetEditorId(ScriptCanvasEditor::AssetEditorId); } // These should be created after we load up the user settings so we can // initialize the user presets m_sceneContextMenu = aznew SceneContextMenu(m_nodePaletteModel, m_scriptEventsAssetModel); m_connectionContextMenu = aznew ConnectionContextMenu(m_nodePaletteModel, m_scriptEventsAssetModel); connect(m_nodePalette, &QDockWidget::visibilityChanged, this, &MainWindow::OnViewVisibilityChanged); connect(m_minimap, &QDockWidget::visibilityChanged, this, &MainWindow::OnViewVisibilityChanged); connect(m_propertyGrid, &QDockWidget::visibilityChanged, this, &MainWindow::OnViewVisibilityChanged); connect(m_bookmarkDockWidget, &QDockWidget::visibilityChanged, this, &MainWindow::OnViewVisibilityChanged); connect(m_variableDockWidget, &QDockWidget::visibilityChanged, this, &MainWindow::OnViewVisibilityChanged); connect(m_loggingWindow, &QDockWidget::visibilityChanged, this, &MainWindow::OnViewVisibilityChanged); connect(m_validationDockWidget, &QDockWidget::visibilityChanged, this, &MainWindow::OnViewVisibilityChanged); m_autoSaveTimer.setSingleShot(true); connect(&m_autoSaveTimer, &QTimer::timeout, this, &MainWindow::OnAutoSave); UpdateMenuState(false); } MainWindow::~MainWindow() { m_workspace->Save(); ScriptCanvas::BatchOperationNotificationBus::Handler::BusDisconnect(); GraphCanvas::AssetEditorRequestBus::Handler::BusDisconnect(); UndoNotificationBus::Handler::BusDisconnect(); UIRequestBus::Handler::BusDisconnect(); ScriptCanvasEditor::GeneralRequestBus::Handler::BusDisconnect(); Clear(); Metrics::MetricsEventsBus::Broadcast(&Metrics::MetricsEventRequests::SendMetric, ScriptCanvasEditor::Metrics::Events::Editor::Close); delete m_batchTool; delete m_nodePalette; delete m_unitTestDockWidget; delete m_statisticsDialog; delete m_presetEditor; delete m_workspace; delete m_sceneContextMenu; delete m_connectionContextMenu; } void MainWindow::CreateMenus() { // File menu connect(ui->action_New_Script, &QAction::triggered, this, &MainWindow::OnFileNew); ui->action_New_Script->setShortcut(QKeySequence(QKeySequence::New)); connect(ui->actionFunction, &QAction::triggered, this, &MainWindow::OnFileNewFunction); //ui->action_New_Function->setShortcut(QKeySequence(QKeySequence::New)); connect(ui->actionEditor_Graph, &QAction::triggered, this, &MainWindow::OnFileNewFunction); connect(ui->action_Open, &QAction::triggered, this, &MainWindow::OnFileOpen); ui->action_Open->setShortcut(QKeySequence(QKeySequence::Open)); connect(ui->action_BatchConversion, &QAction::triggered, this, &MainWindow::RunBatchConversion); ui->action_BatchConversion->setVisible(true); // List of recent files. { QMenu* recentMenu = new QMenu("Open &Recent"); for (int i = 0; i < m_recentActions.size(); ++i) { QAction* action = new QAction(this); action->setVisible(false); m_recentActions[i] = AZStd::make_pair(action, QMetaObject::Connection()); recentMenu->addAction(action); } connect(recentMenu, &QMenu::aboutToShow, this, &MainWindow::UpdateRecentMenu); recentMenu->addSeparator(); // Clear Recent Files. { QAction* action = new QAction("&Clear Recent Files", this); QObject::connect(action, &QAction::triggered, [this](bool /*checked*/) { ClearRecentFile(); UpdateRecentMenu(); }); recentMenu->addAction(action); } ui->menuFile->insertMenu(ui->action_Save, recentMenu); ui->menuFile->insertSeparator(ui->action_Save); } connect(ui->action_Save, &QAction::triggered, this, &MainWindow::OnFileSaveCaller); ui->action_Save->setShortcut(QKeySequence(QKeySequence::Save)); connect(ui->action_Save_As, &QAction::triggered, this, &MainWindow::OnFileSaveAsCaller); ui->action_Save_As->setShortcut(QKeySequence(tr("Ctrl+Shift+S", "File|Save As..."))); QObject::connect(ui->action_Close, &QAction::triggered, [this](bool /*checked*/) { m_tabBar->tabCloseRequested(m_tabBar->currentIndex()); }); ui->action_Close->setShortcut(QKeySequence(QKeySequence::Close)); // Edit Menu SetupEditMenu(); // View menu connect(ui->action_ViewNodePalette, &QAction::triggered, this, &MainWindow::OnViewNodePalette); // Disabling the Minimap since it does not play nicely with the Qt caching solution // And causing some weird visual issues. connect(ui->action_ViewMiniMap, &QAction::triggered, this, &MainWindow::OnViewMiniMap); ui->action_ViewMiniMap->setVisible(false); connect(ui->action_ViewProperties, &QAction::triggered, this, &MainWindow::OnViewProperties); connect(ui->action_ViewBookmarks, &QAction::triggered, this, &MainWindow::OnBookmarks); connect(ui->action_ViewVariableManager, &QAction::triggered, this, &MainWindow::OnVariableManager); connect(m_variableDockWidget, &QDockWidget::visibilityChanged, this, &MainWindow::OnViewVisibilityChanged); connect(ui->action_ViewLogWindow, &QAction::triggered, this, &MainWindow::OnViewLogWindow); connect(m_loggingWindow, &QDockWidget::visibilityChanged, this, &MainWindow::OnViewVisibilityChanged); connect(ui->action_ViewDebugger, &QAction::triggered, this, &MainWindow::OnViewDebugger); connect(ui->action_ViewCommandLine, &QAction::triggered, this, &MainWindow::OnViewCommandLine); connect(ui->action_ViewLog, &QAction::triggered, this, &MainWindow::OnViewLog); connect(ui->action_GraphValidation, &QAction::triggered, this, &MainWindow::OnViewGraphValidation); connect(ui->action_Debugging, &QAction::triggered, this, &MainWindow::OnViewDebuggingWindow); connect(ui->action_ViewUnitTestManager, &QAction::triggered, this, &MainWindow::OnViewUnitTestManager); connect(ui->action_NodeStatistics, &QAction::triggered, this, &MainWindow::OnViewStatisticsPanel); connect(ui->action_PresetsEditor, &QAction::triggered, this, &MainWindow::OnViewPresetsEditor); connect(ui->action_ViewRestoreDefaultLayout, &QAction::triggered, this, &MainWindow::OnRestoreDefaultLayout); } void MainWindow::SignalActiveSceneChanged(AZ::Data::AssetId assetId) { ScriptCanvasMemoryAsset::pointer memoryAsset; AssetTrackerRequestBus::BroadcastResult(memoryAsset, &AssetTrackerRequests::GetAsset, assetId); AZ::EntityId graphId; if (memoryAsset) { graphId = memoryAsset->GetGraphId(); } m_autoSaveTimer.stop(); // The paste action refreshes based on the scene's mimetype RefreshPasteAction(); GraphCanvas::AssetEditorNotificationBus::Event(ScriptCanvasEditor::AssetEditorId, &GraphCanvas::AssetEditorNotifications::PreOnActiveGraphChanged); GraphCanvas::AssetEditorNotificationBus::Event(ScriptCanvasEditor::AssetEditorId, &GraphCanvas::AssetEditorNotifications::OnActiveGraphChanged, graphId); GraphCanvas::AssetEditorNotificationBus::Event(ScriptCanvasEditor::AssetEditorId, &GraphCanvas::AssetEditorNotifications::PostOnActiveGraphChanged); bool enabled = false; if (graphId.IsValid()) { GraphCanvas::ViewId viewId; GraphCanvas::SceneRequestBus::EventResult(viewId, graphId, &GraphCanvas::SceneRequests::GetViewId); AZ_Assert(viewId.IsValid(), "SceneRequest must return a valid ViewId"); if (viewId.IsValid()) { GraphCanvas::ViewNotificationBus::Handler::BusDisconnect(); GraphCanvas::ViewNotificationBus::Handler::BusConnect(viewId); enabled = memoryAsset->GetScriptCanvasId().IsValid(); } } UpdateMenuState(enabled); } void MainWindow::UpdateRecentMenu() { QStringList recentFiles = ReadRecentFiles(); int recentCount = 0; for (auto filename : recentFiles) { if (!QFile::exists(filename)) { continue; } auto& recent = m_recentActions[recentCount++]; recent.first->setText(QString("&%1 %2").arg(QString::number(recentCount), filename)); recent.first->setData(filename); recent.first->setVisible(true); QObject::disconnect(recent.second); recent.second = QObject::connect(recent.first, &QAction::triggered, [this, filename](bool /*checked*/) { OpenFile(filename.toUtf8().data()); }); } for (int i = recentCount; i < m_recentActions.size(); ++i) { auto& recent = m_recentActions[recentCount++]; recent.first->setVisible(false); } } void MainWindow::OnViewVisibilityChanged(bool visibility) { UpdateViewMenu(); } void MainWindow::closeEvent(QCloseEvent* event) { // If we are in the middle of saving a graph. We don't want to close ourselves down and potentially retrigger the saving logic. if (m_queueCloseRequest) { m_hasQueuedClose = true; event->ignore(); return; } AssetTrackerRequests::AssetList unsavedAssets; AssetTrackerRequestBus::BroadcastResult(unsavedAssets, &AssetTrackerRequests::GetUnsavedAssets); for (int tabCounter = 0; tabCounter < m_tabBar->count(); ++tabCounter) { AZ::Data::AssetId assetId = m_tabBar->FindAssetId(tabCounter); auto resultIterator = m_processedClosedAssetIds.insert(assetId); if (!resultIterator.second) { continue; } const Tracker::ScriptCanvasFileState& fileState = GetAssetFileState(assetId); if (fileState == Tracker::ScriptCanvasFileState::UNMODIFIED) { continue; } // Query the user. SetActiveAsset(assetId); QString tabName = m_tabBar->tabText(tabCounter); UnsavedChangesOptions shouldSaveResults = ShowSaveDialog(tabName); if (shouldSaveResults == UnsavedChangesOptions::SAVE) { Callbacks::OnSave saveCB = [this, assetId](bool isSuccessful, AZ::Data::Asset, AZ::Data::AssetId) { if (isSuccessful) { // Continue closing. qobject_cast(parent())->close(); } else { // Abort closing. QMessageBox::critical(this, QString(), QObject::tr("Failed to save.")); m_processedClosedAssetIds.clear(); } }; ActivateAndSaveAsset(assetId, saveCB); event->ignore(); return; } else if (shouldSaveResults == UnsavedChangesOptions::CANCEL_WITHOUT_SAVING) { m_processedClosedAssetIds.clear(); event->ignore(); return; } else if (shouldSaveResults == UnsavedChangesOptions::CONTINUE_WITHOUT_SAVING && (fileState == Tracker::ScriptCanvasFileState::NEW || fileState == Tracker::ScriptCanvasFileState::SOURCE_REMOVED)) { CloseScriptCanvasAsset(assetId); --tabCounter; } } m_workspace->Save(); // Close all files. AssetTrackerRequests::AssetList allAssets; AssetTrackerRequestBus::BroadcastResult(allAssets, &AssetTrackerRequests::GetAssets); for (auto trackedAsset : allAssets) { const AZ::Data::AssetId& assetId = trackedAsset->GetAsset().GetId(); CloseScriptCanvasAsset(assetId); } m_processedClosedAssetIds.clear(); event->accept(); } UnsavedChangesOptions MainWindow::ShowSaveDialog(const QString& filename) { bool wasActive = m_autoSaveTimer.isActive(); if (wasActive) { m_autoSaveTimer.stop(); } UnsavedChangesOptions shouldSaveResults = UnsavedChangesOptions::INVALID; UnsavedChangesDialog dialog(filename, this); dialog.exec(); shouldSaveResults = dialog.GetResult(); // If the auto save timer was active, and we cancelled our save dialog, we want // to resume the auto save timer. if (shouldSaveResults == UnsavedChangesOptions::CANCEL_WITHOUT_SAVING || shouldSaveResults == UnsavedChangesOptions::INVALID) { RestartAutoTimerSave(wasActive); } return shouldSaveResults; } void MainWindow::TriggerUndo() { GeneralEditorNotificationBus::Event(GetActiveScriptCanvasId(), &GeneralEditorNotifications::OnUndoRedoBegin); DequeuePropertyGridUpdate(); UndoRequestBus::Event(GetActiveScriptCanvasId(), &UndoRequests::Undo); SignalSceneDirty(m_activeAssetId); m_propertyGrid->ClearSelection(); GeneralEditorNotificationBus::Event(GetActiveScriptCanvasId(), &GeneralEditorNotifications::OnUndoRedoEnd); } void MainWindow::TriggerRedo() { GeneralEditorNotificationBus::Event(GetActiveScriptCanvasId(), &GeneralEditorNotifications::OnUndoRedoBegin); DequeuePropertyGridUpdate(); UndoRequestBus::Event(GetActiveScriptCanvasId(), &UndoRequests::Redo); SignalSceneDirty(m_activeAssetId); m_propertyGrid->ClearSelection(); GeneralEditorNotificationBus::Event(GetActiveScriptCanvasId(), &GeneralEditorNotifications::OnUndoRedoEnd); } void MainWindow::RegisterVariableType(const ScriptCanvas::Data::Type& variableType) { m_variablePaletteTypes.insert(ScriptCanvas::Data::ToAZType(variableType)); } bool MainWindow::IsValidVariableType(const ScriptCanvas::Data::Type& dataType) const { return m_variableDockWidget->IsValidVariableType(dataType); } void MainWindow::OpenValidationPanel() { if (!m_validationDockWidget->isVisible()) { OnViewGraphValidation(); } } void MainWindow::PostUndoPoint(ScriptCanvas::ScriptCanvasId scriptCanvasId) { bool isIdle = true; UndoRequestBus::EventResult(isIdle, scriptCanvasId, &UndoRequests::IsIdle); if (m_preventUndoStateUpdateCount == 0 && isIdle) { ScopedUndoBatch scopedUndoBatch("Modify Graph Canvas Scene"); UndoRequestBus::Event(scriptCanvasId, &UndoRequests::AddGraphItemChangeUndo, "Graph Change"); MarkAssetModified(m_activeAssetId); } const bool forceTimer = true; RestartAutoTimerSave(forceTimer); } void MainWindow::SignalSceneDirty(AZ::Data::AssetId assetId) { MarkAssetModified(assetId); } void MainWindow::PushPreventUndoStateUpdate() { ++m_preventUndoStateUpdateCount; } void MainWindow::PopPreventUndoStateUpdate() { if (m_preventUndoStateUpdateCount > 0) { --m_preventUndoStateUpdateCount; } } void MainWindow::ClearPreventUndoStateUpdate() { m_preventUndoStateUpdateCount = 0; } void MainWindow::MarkAssetModified(const AZ::Data::AssetId& assetId) { if (!assetId.IsValid()) { return; } ScriptCanvasMemoryAsset::pointer memoryAsset; AssetTrackerRequestBus::BroadcastResult(memoryAsset, &AssetTrackerRequests::GetAsset, assetId); if (memoryAsset) { const auto& memoryAssetId = memoryAsset->GetId(); const Tracker::ScriptCanvasFileState& fileState = GetAssetFileState(memoryAssetId); if (fileState != Tracker::ScriptCanvasFileState::NEW) { AssetTrackerRequestBus::Broadcast(&AssetTrackerRequests::UpdateFileState, memoryAssetId, Tracker::ScriptCanvasFileState::MODIFIED); if (memoryAsset->GetView()) { bool isSaving = false; AssetTrackerRequestBus::BroadcastResult(isSaving, &AssetTrackerRequests::IsSaving, assetId); if (isSaving) { DisableAssetView(memoryAsset); } else { EnableAssetView(memoryAsset); } } } } } void MainWindow::RefreshScriptCanvasAsset(const AZ::Data::Asset& asset) { ScriptCanvasMemoryAsset::pointer memoryAsset; AssetTrackerRequestBus::BroadcastResult(memoryAsset, &AssetTrackerRequests::GetAsset, asset.GetId()); if (memoryAsset && asset.IsReady()) { AZ::EntityId scGraphId = memoryAsset->GetScriptCanvasId(); GraphCanvas::SceneNotificationBus::MultiHandler::BusDisconnect(scGraphId); AZ::EntityId graphCanvasId = GetGraphCanvasGraphId(scGraphId); GraphCanvas::AssetEditorNotificationBus::Event(ScriptCanvasEditor::AssetEditorId, &GraphCanvas::AssetEditorNotifications::OnGraphRefreshed, graphCanvasId, graphCanvasId); int tabIndex = -1; if (IsTabOpen(asset.GetId(), tabIndex)) { const AZStd::string& assetPath = memoryAsset->GetAbsolutePath(); m_tabBar->setTabToolTip(tabIndex, assetPath.c_str()); m_tabBar->SetTabText(tabIndex, memoryAsset->GetTabName().c_str(), memoryAsset->GetFileState()); } if (graphCanvasId.IsValid()) { GraphCanvas::SceneNotificationBus::MultiHandler::BusConnect(graphCanvasId); GraphCanvas::SceneMimeDelegateRequestBus::Event(graphCanvasId, &GraphCanvas::SceneMimeDelegateRequests::AddDelegate, m_entityMimeDelegateId); GraphCanvas::SceneRequestBus::Event(graphCanvasId, &GraphCanvas::SceneRequests::SetMimeType, Widget::NodePaletteDockWidget::GetMimeType()); GraphCanvas::SceneMemberNotificationBus::Event(graphCanvasId, &GraphCanvas::SceneMemberNotifications::OnSceneReady); } } } AZ::Outcome MainWindow::OpenScriptCanvasAssetId(const AZ::Data::AssetId& fileAssetId) { if (!fileAssetId.IsValid()) { return AZ::Failure(AZStd::string("Unable to open asset with invalid asset id")); } int outTabIndex = m_tabBar->FindTab(fileAssetId); if (outTabIndex >= 0) { m_tabBar->SelectTab(fileAssetId); return AZ::Success(outTabIndex); } AZ::Data::AssetInfo assetInfo; AZ::Data::AssetCatalogRequestBus::BroadcastResult(assetInfo, &AZ::Data::AssetCatalogRequests::GetAssetInfoById, fileAssetId); if (assetInfo.m_relativePath.empty()) { return AZ::Failure(AZStd::string("Unknown AssetId")); } if (assetInfo.m_assetType != azrtti_typeid() && assetInfo.m_assetType != azrtti_typeid()) { return AZ::Failure(AZStd::string("Invalid AssetId provided, it's not a Script Canvas supported type")); } AssetTrackerRequests::OnAssetReadyCallback onAssetReady = [this, fileAssetId, &outTabIndex](ScriptCanvasMemoryAsset& asset) { if (!asset.IsSourceInError()) { outTabIndex = CreateAssetTab(asset.GetFileAssetId()); if (!m_isRestoringWorkspace) { SetActiveAsset(fileAssetId); } UpdateWorkspaceStatus(asset); } else { outTabIndex = -1; m_loadingAssets.erase(fileAssetId); } }; m_loadingAssets.insert(fileAssetId); AssetTrackerRequestBus::Broadcast(&AssetTrackerRequests::Load, fileAssetId, assetInfo.m_assetType, onAssetReady); if (outTabIndex >= 0) { return AZ::Success(outTabIndex); } else { return AZ::Failure(AZStd::string("Specified asset is in an error state and cannot be properly displayed.")); } } AZ::Outcome MainWindow::OpenScriptCanvasAsset(const ScriptCanvasMemoryAsset& scriptCanvasAsset, int tabIndex /*= -1*/) { const AZ::Data::AssetId& fileAssetId = scriptCanvasAsset.GetFileAssetId(); if (!fileAssetId.IsValid()) { return AZ::Failure(AZStd::string("Unable to open asset with invalid asset id")); } if (scriptCanvasAsset.IsSourceInError()) { if (!m_isRestoringWorkspace) { AZStd::string errorPath = scriptCanvasAsset.GetAbsolutePath(); if (errorPath.empty()) { errorPath = m_errorFilePath; } if (m_queuedFocusOverride == fileAssetId) { m_queuedFocusOverride.SetInvalid(); } QMessageBox::warning(this, "Unable to open source file", QString("Source File(%1) is in error and cannot be opened").arg(errorPath.c_str()), QMessageBox::StandardButton::Ok); } return AZ::Failure(AZStd::string("Source File is in error")); } int outTabIndex = m_tabBar->FindTab(fileAssetId); if (outTabIndex >= 0) { if (!m_isRestoringWorkspace) { m_tabBar->SelectTab(fileAssetId); } return AZ::Success(outTabIndex); } outTabIndex = CreateAssetTab(fileAssetId, tabIndex); if (outTabIndex == -1) { return AZ::Failure(AZStd::string::format("Unable to open existing Script Canvas Asset with id %s in the Script Canvas Editor", AssetHelpers::AssetIdToString(fileAssetId).c_str())); } AZStd::string assetPath = scriptCanvasAsset.GetAbsolutePath(); if (!assetPath.empty() && !m_loadingNewlySavedFile) { int eraseCount = m_loadingWorkspaceAssets.erase(fileAssetId); if (eraseCount == 0) { AddRecentFile(assetPath.c_str()); } } if (!m_isRestoringWorkspace) { SetActiveAsset(fileAssetId); } GraphCanvas::GraphId graphCanvasGraphId = GetGraphCanvasGraphId(scriptCanvasAsset.GetScriptCanvasId()); GraphCanvas::AssetEditorNotificationBus::Event(ScriptCanvasEditor::AssetEditorId, &GraphCanvas::AssetEditorNotifications::OnGraphLoaded, graphCanvasGraphId); GeneralAssetNotificationBus::Event(fileAssetId, &GeneralAssetNotifications::OnAssetVisualized); AssetTrackerNotificationBus::MultiHandler::BusConnect(fileAssetId); Metrics::MetricsEventsBus::Broadcast(&Metrics::MetricsEventRequests::SendGraphMetric, ScriptCanvasEditor::Metrics::Events::Canvas::OpenGraph, fileAssetId); return AZ::Success(outTabIndex); } AZ::Outcome MainWindow::OpenScriptCanvasAsset(AZ::Data::AssetId scriptCanvasAssetId, int tabIndex /*= -1*/) { ScriptCanvasMemoryAsset::pointer memoryAsset; AssetTrackerRequestBus::BroadcastResult(memoryAsset, &AssetTrackerRequests::GetAsset, scriptCanvasAssetId); // If the asset is already tracked we can go directly to opening it. if (memoryAsset) { return OpenScriptCanvasAsset(*memoryAsset, tabIndex); } else { return OpenScriptCanvasAssetId(scriptCanvasAssetId); } } int MainWindow::CreateAssetTab(const AZ::Data::AssetId& assetId, int tabIndex) { return m_tabBar->InsertGraphTab(tabIndex, assetId); } AZ::Outcome MainWindow::UpdateScriptCanvasAsset(const AZ::Data::Asset& scriptCanvasAsset) { int outTabIndex = -1; PushPreventUndoStateUpdate(); RefreshScriptCanvasAsset(scriptCanvasAsset); if (IsTabOpen(scriptCanvasAsset.GetId(), outTabIndex)) { RefreshActiveAsset(); } PopPreventUndoStateUpdate(); if (outTabIndex == -1) { return AZ::Failure(AZStd::string::format("Script Canvas Asset %s is not open in a tab", scriptCanvasAsset.ToString().c_str())); } return AZ::Success(outTabIndex); } void MainWindow::RemoveScriptCanvasAsset(const AZ::Data::AssetId& assetId) { AssetHelpers::PrintInfo("RemoveScriptCanvasAsset : %s", AssetHelpers::AssetIdToString(assetId).c_str()); m_assetCreationRequests.erase(assetId); GeneralAssetNotificationBus::Event(assetId, &GeneralAssetNotifications::OnAssetUnloaded); AssetTrackerNotificationBus::MultiHandler::BusDisconnect(assetId); ScriptCanvasMemoryAsset::pointer memoryAsset; AssetTrackerRequestBus::BroadcastResult(memoryAsset, &AssetTrackerRequests::GetAsset, assetId); if (memoryAsset) { // Disconnect scene and asset editor buses GraphCanvas::SceneNotificationBus::MultiHandler::BusDisconnect(memoryAsset->GetScriptCanvasId()); GraphCanvas::AssetEditorNotificationBus::Event(ScriptCanvasEditor::AssetEditorId, &GraphCanvas::AssetEditorNotifications::OnGraphUnloaded, memoryAsset->GetGraphId()); } AssetTrackerRequestBus::Broadcast(&AssetTrackerRequests::Close, assetId); int tabIndex = m_tabBar->FindTab(assetId); QVariant data = m_tabBar->tabData(tabIndex); if (data.isValid()) { auto tabAssetId = data.value(); SetActiveAsset(tabAssetId); } } int MainWindow::CloseScriptCanvasAsset(const AZ::Data::AssetId& assetId) { int tabIndex = -1; if (IsTabOpen(assetId, tabIndex)) { OnTabCloseRequest(tabIndex); Metrics::MetricsEventsBus::Broadcast(&Metrics::MetricsEventRequests::SendGraphMetric, ScriptCanvasEditor::Metrics::Events::Canvas::CloseGraph, assetId); } return tabIndex; } bool MainWindow::CreateScriptCanvasAssetFor(const TypeDefs::EntityComponentId& requestingEntityId) { for (auto createdAssetPair : m_assetCreationRequests) { if (createdAssetPair.second == requestingEntityId) { return OpenScriptCanvasAssetId(createdAssetPair.first).IsSuccess(); } } AZ::Data::AssetId previousAssetId = m_activeAssetId; OnFileNew(); bool createdNewAsset = m_activeAssetId != previousAssetId; if (createdNewAsset) { m_assetCreationRequests[m_activeAssetId] = requestingEntityId; } if (m_isRestoringWorkspace) { m_queuedFocusOverride = m_activeAssetId; } return createdNewAsset; } bool MainWindow::IsScriptCanvasAssetOpen(const AZ::Data::AssetId& assetId) const { ScriptCanvasMemoryAsset::pointer memoryAsset; AssetTrackerRequestBus::BroadcastResult(memoryAsset, &AssetTrackerRequests::GetAsset, assetId); return memoryAsset != nullptr; } const CategoryInformation* MainWindow::FindNodePaletteCategoryInformation(AZStd::string_view categoryPath) const { return m_nodePaletteModel.FindBestCategoryInformation(categoryPath); } const NodePaletteModelInformation* MainWindow::FindNodePaletteModelInformation(const ScriptCanvas::NodeTypeIdentifier& nodeType) const { return m_nodePaletteModel.FindNodePaletteInformation(nodeType); } void MainWindow::GetSuggestedFullFilenameToSaveAs(const AZ::Data::AssetId& assetId, AZStd::string& filePath, AZStd::string& fileFilter) { ScriptCanvasMemoryAsset::pointer memoryAsset; AssetTrackerRequestBus::BroadcastResult(memoryAsset, &AssetTrackerRequests::GetAsset, assetId); AZStd::string assetPath; if (memoryAsset) { assetPath = memoryAsset->GetAbsolutePath(); AZ::Data::AssetType assetType = memoryAsset->GetAsset().GetType(); ScriptCanvasAssetHandler* assetHandler; AssetTrackerRequestBus::BroadcastResult(assetHandler, &AssetTrackerRequests::GetAssetHandlerForType, assetType); AZ_Assert(assetHandler, "Asset type must have a valid asset handler"); AZ::EBusAggregateResults results; AssetRegistryRequestBus::BroadcastResult(results, &AssetRegistryRequests::GetAssetDescription, assetType); ScriptCanvas::AssetDescription* description = nullptr; for (auto item : results.values) { if (item->GetAssetType() == assetType) { description = item; break; } } AZ_Assert(description, "Asset type must have a valid description"); fileFilter = description->GetFileFilterImpl(); AZStd::string tabName; AssetTrackerRequestBus::BroadcastResult(tabName, &AssetTrackerRequests::GetTabName, assetId); assetPath = AZStd::string::format("%s/%s%s", description->GetSuggestedSavePathImpl(), tabName.c_str(), description->GetExtensionImpl()); } AZStd::array resolvedPath; AZ::IO::FileIOBase::GetInstance()->ResolvePath(assetPath.data(), resolvedPath.data(), resolvedPath.size()); filePath = resolvedPath.data(); } void MainWindow::OpenFile(const char* fullPath) { m_errorFilePath = fullPath; // Let's find the source file on disk AZStd::string watchFolder; AZ::Data::AssetInfo assetInfo; bool sourceInfoFound{}; AzToolsFramework::AssetSystemRequestBus::BroadcastResult(sourceInfoFound, &AzToolsFramework::AssetSystemRequestBus::Events::GetSourceInfoBySourcePath, fullPath, assetInfo, watchFolder); if (sourceInfoFound) { const Tracker::ScriptCanvasFileState& fileState = GetAssetFileState(assetInfo.m_assetId); if (fileState != Tracker::ScriptCanvasFileState::NEW && fileState != Tracker::ScriptCanvasFileState::INVALID) { ScriptCanvasMemoryAsset::pointer memoryAsset; AssetTrackerRequestBus::BroadcastResult(memoryAsset, &AssetTrackerRequests::GetAsset, assetInfo.m_assetId); if (m_tabBar->FindTab(assetInfo.m_assetId) < 0) { CreateAssetTab(assetInfo.m_assetId); } SetActiveAsset(memoryAsset->GetFileAssetId()); OpenNextFile(); return; } Callbacks::OnAssetReadyCallback onAssetReady = [this, fullPath, assetInfo](ScriptCanvasMemoryAsset& asset) { ScriptCanvasMemoryAsset::pointer memoryAsset; AssetTrackerRequestBus::BroadcastResult(memoryAsset, &AssetTrackerRequests::GetAsset, assetInfo.m_assetId); auto openOutcome = OpenScriptCanvasAsset(*memoryAsset); if (openOutcome) { SetRecentAssetId(assetInfo.m_assetId); } else { AZ_Warning("Script Canvas", openOutcome, "%s", openOutcome.GetError().data()); } OpenNextFile(); }; // TODO-LS the assetInfo.m_assetType is always null for some reason, I know in this case we want default assets so it's ok to hardcode it AssetTrackerRequestBus::Broadcast(&AssetTrackerRequests::Load, assetInfo.m_assetId, /*assetInfo.m_assetType*/azrtti_typeid(), onAssetReady); } else { QMessageBox::warning(this, "Invalid Source Asset", QString("'%1' is not a valid asset path.").arg(fullPath), QMessageBox::Ok); } } GraphCanvas::Endpoint MainWindow::HandleProposedConnection(const GraphCanvas::GraphId& graphId, const GraphCanvas::ConnectionId& connectionId, const GraphCanvas::Endpoint& endpoint, const GraphCanvas::NodeId& nodeId, const QPoint& screenPoint) { GraphCanvas::Endpoint retVal; GraphCanvas::ConnectionType connectionType = GraphCanvas::ConnectionType::CT_Invalid; GraphCanvas::SlotRequestBus::EventResult(connectionType, endpoint.GetSlotId(), &GraphCanvas::SlotRequests::GetConnectionType); GraphCanvas::NodeId currentTarget = nodeId; while (!retVal.IsValid() && currentTarget.IsValid()) { AZStd::vector targetSlotIds; GraphCanvas::NodeRequestBus::EventResult(targetSlotIds, currentTarget, &GraphCanvas::NodeRequests::GetSlotIds); AZStd::list< GraphCanvas::Endpoint > endpoints; for (const auto& targetSlotId : targetSlotIds) { GraphCanvas::Endpoint proposedEndpoint(currentTarget, targetSlotId); bool canCreate = false; GraphCanvas::SlotRequestBus::EventResult(canCreate, endpoint.GetSlotId(), &GraphCanvas::SlotRequests::CanCreateConnectionTo, proposedEndpoint); if (canCreate) { GraphCanvas::SlotGroup slotGroup = GraphCanvas::SlotGroups::Invalid; GraphCanvas::SlotRequestBus::EventResult(slotGroup, targetSlotId, &GraphCanvas::SlotRequests::GetSlotGroup); bool isVisible = slotGroup != GraphCanvas::SlotGroups::Invalid; GraphCanvas::SlotLayoutRequestBus::EventResult(isVisible, currentTarget, &GraphCanvas::SlotLayoutRequests::IsSlotGroupVisible, slotGroup); if (isVisible) { endpoints.push_back(proposedEndpoint); } } } if (!endpoints.empty()) { if (endpoints.size() == 1) { retVal = endpoints.front(); } else { QMenu menu; for (GraphCanvas::Endpoint proposedEndpoint : endpoints) { QAction* action = aznew EndpointSelectionAction(proposedEndpoint); menu.addAction(action); } QAction* result = menu.exec(screenPoint); if (result != nullptr) { EndpointSelectionAction* selectedEnpointAction = static_cast(result); retVal = selectedEnpointAction->GetEndpoint(); } else { retVal.Clear(); } } if (retVal.IsValid()) { // Double safety check. This should be gauranteed by the previous checks. But just extra safety. bool canCreateConnection = false; GraphCanvas::SlotRequestBus::EventResult(canCreateConnection, endpoint.GetSlotId(), &GraphCanvas::SlotRequests::CanCreateConnectionTo, retVal); if (!canCreateConnection) { retVal.Clear(); } } } else { retVal.Clear(); } if (!retVal.IsValid()) { bool isWrapped = false; GraphCanvas::NodeRequestBus::EventResult(isWrapped, currentTarget, &GraphCanvas::NodeRequests::IsWrapped); if (isWrapped) { GraphCanvas::NodeRequestBus::EventResult(currentTarget, currentTarget, &GraphCanvas::NodeRequests::GetWrappingNode); } else { currentTarget.SetInvalid(); } } } return retVal; } void MainWindow::OnFileNew() { MakeNewFile(); } void MainWindow::OnFileNewFunction() { MakeNewFile(); } int MainWindow::InsertTabForAsset(AZStd::string_view assetPath, AZ::Data::AssetId assetId, int tabIndex) { int outTabIndex = -1; { // Insert tab block AZStd::string tabName; AzFramework::StringFunc::Path::GetFileName(assetPath.data(), tabName); m_tabBar->InsertGraphTab(tabIndex, assetId); if (!IsTabOpen(assetId, outTabIndex)) { AZ_Assert(false, AZStd::string::format("Unable to open new Script Canvas Asset with id %s in the Script Canvas Editor", AssetHelpers::AssetIdToString(assetId).c_str()).c_str()); return -1; } m_tabBar->setTabToolTip(outTabIndex, assetPath.data()); } return outTabIndex; } void MainWindow::UpdateUndoCache(AZ::Data::AssetId assetId) { UndoCache* undoCache = nullptr; UndoRequestBus::EventResult(undoCache, GetActiveScriptCanvasId(), &UndoRequests::GetSceneUndoCache); if (undoCache) { undoCache->UpdateCache(GetActiveScriptCanvasId()); } } AZ::Outcome MainWindow::CreateScriptCanvasAsset(AZStd::string_view assetPath, AZ::Data::AssetType assetType, int tabIndex) { int outTabIndex = -1; AZ::Data::AssetId newAssetId; auto onAssetCreated = [this, assetPath, tabIndex, &outTabIndex](ScriptCanvasMemoryAsset& asset) { const AZ::Data::AssetId& assetId = asset.GetId(); outTabIndex = InsertTabForAsset(assetPath, assetId, tabIndex); SetActiveAsset(assetId); UpdateScriptCanvasAsset(asset.GetAsset()); AZ::EntityId scriptCanvasEntityId; AssetTrackerRequestBus::BroadcastResult(scriptCanvasEntityId, &AssetTrackerRequests::GetScriptCanvasId, assetId); GraphCanvas::GraphId graphCanvasGraphId = GetGraphCanvasGraphId(scriptCanvasEntityId); GraphCanvas::AssetEditorNotificationBus::Event(ScriptCanvasEditor::AssetEditorId, &GraphCanvas::AssetEditorNotifications::OnGraphLoaded, graphCanvasGraphId); }; AssetTrackerRequestBus::BroadcastResult(newAssetId, &AssetTrackerRequests::Create, assetPath, assetType, onAssetCreated); return AZ::Success(outTabIndex); } bool MainWindow::OnFileSave(const Callbacks::OnSave& saveCB) { return SaveAssetImpl(m_activeAssetId, saveCB); } bool MainWindow::OnFileSaveAs(const Callbacks::OnSave& saveCB) { return SaveAssetAsImpl(m_activeAssetId, saveCB); } bool MainWindow::SaveAssetImpl(const AZ::Data::AssetId& assetId, const Callbacks::OnSave& saveCB) { if (!assetId.IsValid()) { return false; } // TODO: Set graph read-only to prevent edits during save bool saveSuccessful = false; Tracker::ScriptCanvasFileState fileState = GetAssetFileState(assetId); if (fileState == Tracker::ScriptCanvasFileState::NEW) { saveSuccessful = SaveAssetAsImpl(assetId, saveCB); } else if (fileState == Tracker::ScriptCanvasFileState::MODIFIED || fileState == Tracker::ScriptCanvasFileState::SOURCE_REMOVED) { SaveAsset(assetId, saveCB); saveSuccessful = true; } return saveSuccessful; } bool MainWindow::SaveAssetAsImpl(const AZ::Data::AssetId& inMemoryAssetId, const Callbacks::OnSave& saveCB) { if (!inMemoryAssetId.IsValid()) { return false; } if (m_activeAssetId != inMemoryAssetId) { OnChangeActiveGraphTab(inMemoryAssetId); } PrepareAssetForSave(inMemoryAssetId); AZStd::string suggestedFilename; AZStd::string suggestedFileFilter; GetSuggestedFullFilenameToSaveAs(inMemoryAssetId, suggestedFilename, suggestedFileFilter); EnsureSaveDestinationDirectory(suggestedFilename); QString filter = suggestedFileFilter.c_str(); QString selectedFile; bool isValidFileName = false; while (!isValidFileName) { selectedFile = QFileDialog::getSaveFileName(this, tr("Save As..."), suggestedFilename.data(), filter); // If the selected file is empty that means we just cancelled. // So we want to break out. if (!selectedFile.isEmpty()) { AZStd::string filePath = selectedFile.toUtf8().data(); AZStd::string fileName; if (AzFramework::StringFunc::Path::GetFileName(filePath.c_str(), fileName)) { isValidFileName = !(fileName.empty()); } else { QMessageBox::information(this, "Unable to Save", "File name cannot be empty"); } } else { break; } } if (isValidFileName) { AZStd::string internalStringFile = selectedFile.toUtf8().data(); if (!AssetHelpers::IsValidSourceFile(internalStringFile, GetActiveScriptCanvasId())) { QMessageBox::warning(this, "Unable to Save", QString("File\n'%1'\n\nDoes not match the asset type of the current Graph.").arg(selectedFile)); return false; } SaveNewAsset(internalStringFile, inMemoryAssetId, saveCB); m_newlySavedFile = internalStringFile; // Forcing the file add here, since we are creating a new file AddRecentFile(m_newlySavedFile.c_str()); return true; } return false; } void MainWindow::OnSaveCallback(bool saveSuccess, AZ::Data::Asset fileAsset, AZ::Data::AssetId previousFileAssetId) { ScriptCanvasMemoryAsset::pointer memoryAsset; AZStd::string tabName = m_tabBar->tabText(m_tabBar->currentIndex()).toUtf8().data(); int saveTabIndex = m_tabBar->currentIndex(); if (saveSuccess) { AssetTrackerRequestBus::BroadcastResult(memoryAsset, &AssetTrackerRequests::GetAsset, fileAsset.GetId()); AZ_Assert(memoryAsset, "At this point we must have a MemoryAsset"); // Update the editor with the new information about this asset. const AZ::Data::AssetId& fileAssetId = memoryAsset->GetFileAssetId(); saveTabIndex = m_tabBar->FindTab(fileAssetId); // We've saved as over a new graph, so we need to close the old one. if (saveTabIndex != m_tabBar->currentIndex()) { // Invalidate the file asset id so we don't delete trigger the asset flow. m_tabBar->setTabData(saveTabIndex, QVariant::fromValue(AZ::Data::AssetId())); m_tabBar->CloseTab(saveTabIndex); saveTabIndex = -1; } if (saveTabIndex < 0) { // This asset had not been saved yet, we will need to use the in memory asset Id to get the index. saveTabIndex = m_tabBar->FindTab(memoryAsset->GetId()); if (saveTabIndex < 0) { // Finally, we may have Saved-As and we need the previous file asset Id to find the tab saveTabIndex = m_tabBar->FindTab(previousFileAssetId); } } AzFramework::StringFunc::Path::GetFileName(memoryAsset->GetAbsolutePath().c_str(), tabName); // Update the tab's assetId to the file asset Id (necessary when saving a new asset) m_tabBar->ConfigureTab(saveTabIndex, fileAssetId, tabName); GeneralAssetNotificationBus::Event(memoryAsset->GetId(), &GeneralAssetNotifications::OnAssetVisualized); auto requestorIter = m_assetCreationRequests.find(fileAsset.GetId()); if (requestorIter != m_assetCreationRequests.end()) { auto editorComponents = AZ::EntityUtils::FindDerivedComponents(requestorIter->second.first); if (editorComponents.empty()) { auto firstRequestBus = EditorScriptCanvasComponentRequestBus::FindFirstHandler(requestorIter->second.first); if (firstRequestBus) { firstRequestBus->SetAssetId(fileAsset.GetId()); } } else { for (auto editorComponent : editorComponents) { if (editorComponent->GetId() == requestorIter->second.second) { editorComponent->SetAssetId(fileAsset.GetId()); break; } } } m_assetCreationRequests.erase(requestorIter); } // Soft switch the asset id here. We'll do a double scene switch down below to actually switch the active assetid m_activeAssetId = fileAssetId; } else { // Use the previous memory asset to find what we had setup as our display AssetTrackerRequestBus::BroadcastResult(memoryAsset, &AssetTrackerRequests::GetAsset, m_activeAssetId); // Drop off our file modifier status for our display name when we fail to save. if (tabName.at(tabName.size() -1) == '*') { tabName = tabName.substr(0, tabName.size() - 2); } } if (m_tabBar->currentIndex() != saveTabIndex) { m_tabBar->setCurrentIndex(saveTabIndex); } else { // Something weird happens with our saving. Where we are relying on these scene changes being called. AZ::Data::AssetId previousAssetId = m_activeAssetId; OnChangeActiveGraphTab(AZ::Data::AssetId()); OnChangeActiveGraphTab(previousAssetId); } UpdateAssignToSelectionState(); OnSaveToast toast(tabName, GetActiveGraphCanvasGraphId(), saveSuccess); const bool displayAsNotification = true; RunGraphValidation(displayAsNotification); // This is called during saving, so the is scaving flag is always true Need to update the state after this callback is complete. So schedule for next system tick. AddSystemTickAction(SystemTickActionFlag::UpdateSaveMenuState); if (m_closeCurrentGraphAfterSave) { AddSystemTickAction(SystemTickActionFlag::CloseCurrentGraph); } m_closeCurrentGraphAfterSave = false; EnableAssetView(memoryAsset); UnblockCloseRequests(); } bool MainWindow::ActivateAndSaveAsset(const AZ::Data::AssetId& unsavedAssetId, const Callbacks::OnSave& saveCB) { SetActiveAsset(unsavedAssetId); return OnFileSave(saveCB); } void MainWindow::SaveAsset(AZ::Data::AssetId assetId, const Callbacks::OnSave& onSave) { PrepareAssetForSave(assetId); auto onSaveCallback = [this, onSave](bool saveSuccess, AZ::Data::Asset asset, AZ::Data::AssetId previousAssetId) { OnSaveCallback(saveSuccess, asset, previousAssetId); if (onSave) { AZStd::invoke(onSave, saveSuccess, asset, previousAssetId); } }; AssetTrackerRequestBus::Broadcast(&AssetTrackerRequests::Save, assetId, onSaveCallback); UpdateSaveState(); ScriptCanvasMemoryAsset::pointer memoryAsset; AssetTrackerRequestBus::BroadcastResult(memoryAsset, &AssetTrackerRequests::GetAsset, m_activeAssetId); // Disable the current view if we are saving. if (memoryAsset) { DisableAssetView(memoryAsset); } BlockCloseRequests(); } void MainWindow::SaveNewAsset(AZStd::string_view path, AZ::Data::AssetId inMemoryAssetId, const Callbacks::OnSave& onSave) { PrepareAssetForSave(inMemoryAssetId); auto onSaveCallback = [this, onSave](bool saveSuccess, AZ::Data::Asset asset, AZ::Data::AssetId previousAssetId) { OnSaveCallback(saveSuccess, asset, previousAssetId); if (onSave) { AZStd::invoke(onSave, saveSuccess, asset, previousAssetId); } }; AssetTrackerRequestBus::Broadcast(&AssetTrackerRequests::SaveAs, inMemoryAssetId, path, onSaveCallback); UpdateSaveState(); ScriptCanvasMemoryAsset::pointer memoryAsset; AssetTrackerRequestBus::BroadcastResult(memoryAsset, &AssetTrackerRequests::GetAsset, inMemoryAssetId); // Disable the current view if we are saving. if (memoryAsset) { DisableAssetView(memoryAsset); } BlockCloseRequests(); } void MainWindow::OnFileOpen() { AZ::SerializeContext* serializeContext = nullptr; EBUS_EVENT_RESULT(serializeContext, AZ::ComponentApplicationBus, GetSerializeContext); AZ_Assert(serializeContext, "Failed to acquire application serialize context."); AZ::Data::AssetId openId = ReadRecentAssetId(); AZStd::string assetRoot; { AZStd::array assetRootChar; AZ::IO::FileIOBase::GetInstance()->ResolvePath("@devassets@", assetRootChar.data(), assetRootChar.size()); assetRoot = assetRootChar.data(); } AZStd::string assetPath; AZ::Data::AssetCatalogRequestBus::BroadcastResult(assetPath, &AZ::Data::AssetCatalogRequests::GetAssetPathById, openId); if (!assetPath.empty()) { assetPath = AZStd::string::format("%s/%s", assetRoot.c_str(), assetPath.c_str()); } if (!openId.IsValid() || !QFile::exists(assetPath.c_str())) { assetPath = AZStd::string::format("%s/scriptcanvas", assetRoot.c_str()); } assetPath = AZStd::string::format("%s/scriptcanvas", assetRoot.c_str()); AZ::EBusAggregateResults> fileFilters; AssetRegistryRequestBus::BroadcastResult(fileFilters, &AssetRegistryRequests::GetAssetHandlerFileFilters); QString filter; AZStd::set filterSet; auto aggregateFilters = fileFilters.values; for (auto aggregateFilters : fileFilters.values) { for (const AZStd::string& fileFilter : aggregateFilters) { filterSet.insert(fileFilter); } } QStringList nameFilters; QString globalFilter; for (auto fileFilter : filterSet) { nameFilters.push_back(fileFilter.c_str()); AZStd::size_t filterStart = fileFilter.find_last_of("("); AZStd::size_t filterEnd = fileFilter.find_last_of(")"); if (filterStart != AZStd::string::npos && filterEnd != AZStd::string::npos) { AZStd::string substring = fileFilter.substr(filterStart + 1, (filterEnd - filterStart) - 1); if (!globalFilter.isEmpty()) { globalFilter.append(" "); } globalFilter.append(substring.c_str()); } } globalFilter = QString("All ScriptCanvas Files (%1)").arg(globalFilter); nameFilters.push_front(globalFilter); QFileDialog dialog(nullptr, tr("Open..."), assetPath.c_str()); dialog.setFileMode(QFileDialog::ExistingFiles); dialog.setNameFilters(nameFilters); if (dialog.exec() == QDialog::Accepted) { m_filesToOpen = dialog.selectedFiles(); OpenNextFile(); } } void MainWindow::SetupEditMenu() { ui->action_Undo->setShortcut(QKeySequence::Undo); ui->action_Cut->setShortcut(QKeySequence(QKeySequence::Cut)); ui->action_Copy->setShortcut(QKeySequence(QKeySequence::Copy)); ui->action_Paste->setShortcut(QKeySequence(QKeySequence::Paste)); ui->action_Delete->setShortcut(QKeySequence(QKeySequence::Delete)); connect(ui->menuEdit, &QMenu::aboutToShow, this, &MainWindow::OnEditMenuShow); // Edit Menu connect(ui->action_Undo, &QAction::triggered, this, &MainWindow::TriggerUndo); connect(ui->action_Redo, &QAction::triggered, this, &MainWindow::TriggerRedo); connect(ui->action_Cut, &QAction::triggered, this, &MainWindow::OnEditCut); connect(ui->action_Copy, &QAction::triggered, this, &MainWindow::OnEditCopy); connect(ui->action_Paste, &QAction::triggered, this, &MainWindow::OnEditPaste); connect(ui->action_Duplicate, &QAction::triggered, this, &MainWindow::OnEditDuplicate); connect(ui->action_Delete, &QAction::triggered, this, &MainWindow::OnEditDelete); connect(QApplication::clipboard(), &QClipboard::dataChanged, this, &MainWindow::RefreshPasteAction); connect(ui->action_RemoveUnusedNodes, &QAction::triggered, this, &MainWindow::OnRemoveUnusedNodes); connect(ui->action_RemoveUnusedVariables, &QAction::triggered, this, &MainWindow::OnRemoveUnusedVariables); connect(ui->action_RemoveUnusedElements, &QAction::triggered, this, &MainWindow::OnRemoveUnusedElements); connect(ui->action_Screenshot, &QAction::triggered, this, &MainWindow::OnScreenshot); connect(ui->action_SelectAll, &QAction::triggered, this, &MainWindow::OnSelectAll); connect(ui->action_SelectInputs, &QAction::triggered, this, &MainWindow::OnSelectInputs); connect(ui->action_SelectOutputs, &QAction::triggered, this, &MainWindow::OnSelectOutputs); connect(ui->action_SelectConnected, &QAction::triggered, this, &MainWindow::OnSelectConnected); connect(ui->action_ClearSelection, &QAction::triggered, this, &MainWindow::OnClearSelection); connect(ui->action_EnableSelection, &QAction::triggered, this, &MainWindow::OnEnableSelection); connect(ui->action_DisableSelection, &QAction::triggered, this, &MainWindow::OnDisableSelection); connect(ui->action_AlignTop, &QAction::triggered, this, &MainWindow::OnAlignTop); connect(ui->action_AlignBottom, &QAction::triggered, this, &MainWindow::OnAlignBottom); connect(ui->action_AlignLeft, &QAction::triggered, this, &MainWindow::OnAlignLeft); connect(ui->action_AlignRight, &QAction::triggered, this, &MainWindow::OnAlignRight); ui->action_ZoomIn->setShortcuts({ QKeySequence(Qt::CTRL + Qt::Key_Plus), QKeySequence(Qt::CTRL + Qt::Key_Equal) }); // View Menu connect(ui->action_ShowEntireGraph, &QAction::triggered, this, &MainWindow::OnShowEntireGraph); connect(ui->action_ZoomIn, &QAction::triggered, this, &MainWindow::OnZoomIn); connect(ui->action_ZoomOut, &QAction::triggered, this, &MainWindow::OnZoomOut); connect(ui->action_ZoomSelection, &QAction::triggered, this, &MainWindow::OnZoomToSelection); connect(ui->action_GotoStartOfChain, &QAction::triggered, this, &MainWindow::OnGotoStartOfChain); connect(ui->action_GotoEndOfChain, &QAction::triggered, this, &MainWindow::OnGotoEndOfChain); connect(ui->action_GlobalPreferences, &QAction::triggered, [this]() { ScriptCanvasEditor::SettingsDialog(ui->action_GlobalPreferences->text(), ScriptCanvas::ScriptCanvasId(), this).exec(); if (m_userSettings) { if (m_userSettings->m_autoSaveConfig.m_enabled) { m_allowAutoSave = true; m_autoSaveTimer.setInterval(m_userSettings->m_autoSaveConfig.m_timeSeconds * 1000); } else { m_allowAutoSave = false; } } }); connect(ui->action_GraphPreferences, &QAction::triggered, [this]() { ScriptCanvas::ScriptCanvasId scriptCanvasId = GetActiveScriptCanvasId(); if (!scriptCanvasId.IsValid()) { return; } m_autoSaveTimer.stop(); ScriptCanvasEditor::SettingsDialog(ui->action_GraphPreferences->text(), scriptCanvasId, this).exec(); }); } void MainWindow::OnEditMenuShow() { RefreshGraphPreferencesAction(); ui->action_Screenshot->setEnabled(GetActiveGraphCanvasGraphId().IsValid()); ui->menuSelect->setEnabled(GetActiveGraphCanvasGraphId().IsValid()); ui->action_ClearSelection->setEnabled(GetActiveGraphCanvasGraphId().IsValid()); ui->menuAlign->setEnabled(GetActiveGraphCanvasGraphId().IsValid()); } void MainWindow::RefreshPasteAction() { AZStd::string copyMimeType; GraphCanvas::SceneRequestBus::EventResult(copyMimeType, GetActiveGraphCanvasGraphId(), &GraphCanvas::SceneRequests::GetCopyMimeType); const bool pasteableClipboard = (!copyMimeType.empty() && QApplication::clipboard()->mimeData()->hasFormat(copyMimeType.c_str())) || GraphVariablesTableView::HasCopyVariableData(); ui->action_Paste->setEnabled(pasteableClipboard); } void MainWindow::RefreshGraphPreferencesAction() { ui->action_GraphPreferences->setEnabled(GetActiveGraphCanvasGraphId().IsValid()); } void MainWindow::OnEditCut() { AZ::EntityId graphCanvasGraphId = GetActiveGraphCanvasGraphId(); GraphCanvas::SceneRequestBus::Event(graphCanvasGraphId, &GraphCanvas::SceneRequests::CutSelection); } void MainWindow::OnEditCopy() { AZ::EntityId graphCanvasGraphId = GetActiveGraphCanvasGraphId(); GraphCanvas::SceneRequestBus::Event(graphCanvasGraphId, &GraphCanvas::SceneRequests::CopySelection); } void MainWindow::OnEditPaste() { AZ::EntityId graphCanvasGraphId = GetActiveGraphCanvasGraphId(); GraphCanvas::SceneRequestBus::Event(graphCanvasGraphId, &GraphCanvas::SceneRequests::Paste); } void MainWindow::OnEditDuplicate() { AZ::EntityId graphCanvasGraphId = GetActiveGraphCanvasGraphId(); GraphCanvas::SceneRequestBus::Event(graphCanvasGraphId, &GraphCanvas::SceneRequests::DuplicateSelection); } void MainWindow::OnEditDelete() { AZ::EntityId graphCanvasGraphId = GetActiveGraphCanvasGraphId(); GraphCanvas::SceneRequestBus::Event(graphCanvasGraphId, &GraphCanvas::SceneRequests::DeleteSelection); } void MainWindow::OnRemoveUnusedVariables() { AZ::EntityId scriptCanvasGraphId = GetActiveScriptCanvasId(); EditorGraphRequestBus::Event(scriptCanvasGraphId, &EditorGraphRequests::RemoveUnusedVariables); } void MainWindow::OnRemoveUnusedNodes() { AZ::EntityId graphCanvasGraphId = GetActiveGraphCanvasGraphId(); GraphCanvas::SceneRequestBus::Event(graphCanvasGraphId, &GraphCanvas::SceneRequests::RemoveUnusedNodes); } void MainWindow::OnRemoveUnusedElements() { AZ::EntityId graphCanvasGraphId = GetActiveGraphCanvasGraphId(); GraphCanvas::SceneRequestBus::Event(graphCanvasGraphId, &GraphCanvas::SceneRequests::RemoveUnusedElements); } void MainWindow::OnScreenshot() { AZ::EntityId graphCanvasGraphId = GetActiveGraphCanvasGraphId(); GraphCanvas::ViewId viewId; GraphCanvas::SceneRequestBus::EventResult(viewId, graphCanvasGraphId, &GraphCanvas::SceneRequests::GetViewId); GraphCanvas::ViewRequestBus::Event(viewId, &GraphCanvas::ViewRequests::ScreenshotSelection); } void MainWindow::OnSelectAll() { AZ::EntityId graphCanvasGraphId = GetActiveGraphCanvasGraphId(); GraphCanvas::SceneRequestBus::Event(graphCanvasGraphId, &GraphCanvas::SceneRequests::SelectAll); } void MainWindow::OnSelectInputs() { AZ::EntityId graphCanvasGraphId = GetActiveGraphCanvasGraphId(); GraphCanvas::SceneRequestBus::Event(graphCanvasGraphId, &GraphCanvas::SceneRequests::SelectAllRelative, GraphCanvas::ConnectionType::CT_Input); } void MainWindow::OnSelectOutputs() { AZ::EntityId graphCanvasGraphId = GetActiveGraphCanvasGraphId(); GraphCanvas::SceneRequestBus::Event(graphCanvasGraphId, &GraphCanvas::SceneRequests::SelectAllRelative, GraphCanvas::ConnectionType::CT_Output); GraphCanvas::ViewId viewId; GraphCanvas::SceneRequestBus::EventResult(viewId, graphCanvasGraphId, &GraphCanvas::SceneRequests::GetViewId); } void MainWindow::OnSelectConnected() { AZ::EntityId graphCanvasGraphId = GetActiveGraphCanvasGraphId(); GraphCanvas::SceneRequestBus::Event(graphCanvasGraphId, &GraphCanvas::SceneRequests::SelectConnectedNodes); } void MainWindow::OnClearSelection() { AZ::EntityId graphCanvasGraphId = GetActiveGraphCanvasGraphId(); GraphCanvas::SceneRequestBus::Event(graphCanvasGraphId, &GraphCanvas::SceneRequests::ClearSelection); } void MainWindow::OnEnableSelection() { AZ::EntityId graphCanvasGraphId = GetActiveGraphCanvasGraphId(); GraphCanvas::SceneRequestBus::Event(graphCanvasGraphId, &GraphCanvas::SceneRequests::EnableSelection); } void MainWindow::OnDisableSelection() { AZ::EntityId graphCanvasGraphId = GetActiveGraphCanvasGraphId(); GraphCanvas::SceneRequestBus::Event(graphCanvasGraphId, &GraphCanvas::SceneRequests::DisableSelection); } void MainWindow::OnAlignTop() { GraphCanvas::AlignConfig alignConfig; alignConfig.m_horAlign = GraphCanvas::GraphUtils::HorizontalAlignment::None; alignConfig.m_verAlign = GraphCanvas::GraphUtils::VerticalAlignment::Top; alignConfig.m_alignTime = GetAlignmentTime(); AlignSelected(alignConfig); } void MainWindow::OnAlignBottom() { GraphCanvas::AlignConfig alignConfig; alignConfig.m_horAlign = GraphCanvas::GraphUtils::HorizontalAlignment::None; alignConfig.m_verAlign = GraphCanvas::GraphUtils::VerticalAlignment::Bottom; alignConfig.m_alignTime = GetAlignmentTime(); AlignSelected(alignConfig); } void MainWindow::OnAlignLeft() { GraphCanvas::AlignConfig alignConfig; alignConfig.m_horAlign = GraphCanvas::GraphUtils::HorizontalAlignment::Left; alignConfig.m_verAlign = GraphCanvas::GraphUtils::VerticalAlignment::None; alignConfig.m_alignTime = GetAlignmentTime(); AlignSelected(alignConfig); } void MainWindow::OnAlignRight() { GraphCanvas::AlignConfig alignConfig; alignConfig.m_horAlign = GraphCanvas::GraphUtils::HorizontalAlignment::Right; alignConfig.m_verAlign = GraphCanvas::GraphUtils::VerticalAlignment::None; alignConfig.m_alignTime = GetAlignmentTime(); AlignSelected(alignConfig); } void MainWindow::AlignSelected(const GraphCanvas::AlignConfig& alignConfig) { AZ::EntityId graphCanvasGraphId = GetActiveGraphCanvasGraphId(); AZStd::vector< GraphCanvas::NodeId > selectedNodes; GraphCanvas::SceneRequestBus::EventResult(selectedNodes, graphCanvasGraphId, &GraphCanvas::SceneRequests::GetSelectedNodes); GraphCanvas::GraphUtils::AlignNodes(selectedNodes, alignConfig); } void MainWindow::OnShowEntireGraph() { AZ::EntityId graphCanvasGraphId = GetActiveGraphCanvasGraphId(); GraphCanvas::ViewId viewId; GraphCanvas::SceneRequestBus::EventResult(viewId, graphCanvasGraphId, &GraphCanvas::SceneRequests::GetViewId); GraphCanvas::ViewRequestBus::Event(viewId, &GraphCanvas::ViewRequests::ShowEntireGraph); } void MainWindow::OnZoomIn() { AZ::EntityId graphCanvasGraphId = GetActiveGraphCanvasGraphId(); GraphCanvas::ViewId viewId; GraphCanvas::SceneRequestBus::EventResult(viewId, graphCanvasGraphId, &GraphCanvas::SceneRequests::GetViewId); GraphCanvas::ViewRequestBus::Event(viewId, &GraphCanvas::ViewRequests::ZoomIn); } void MainWindow::OnZoomOut() { AZ::EntityId graphCanvasGraphId = GetActiveGraphCanvasGraphId(); GraphCanvas::ViewId viewId; GraphCanvas::SceneRequestBus::EventResult(viewId, graphCanvasGraphId, &GraphCanvas::SceneRequests::GetViewId); GraphCanvas::ViewRequestBus::Event(viewId, &GraphCanvas::ViewRequests::ZoomOut); } void MainWindow::OnZoomToSelection() { AZ::EntityId graphCanvasGraphId = GetActiveGraphCanvasGraphId(); GraphCanvas::ViewId viewId; GraphCanvas::SceneRequestBus::EventResult(viewId, graphCanvasGraphId, &GraphCanvas::SceneRequests::GetViewId); GraphCanvas::ViewRequestBus::Event(viewId, &GraphCanvas::ViewRequests::CenterOnSelection); } void MainWindow::OnGotoStartOfChain() { AZ::EntityId graphCanvasGraphId = GetActiveGraphCanvasGraphId(); GraphCanvas::ViewId viewId; GraphCanvas::SceneRequestBus::EventResult(viewId, graphCanvasGraphId, &GraphCanvas::SceneRequests::GetViewId); GraphCanvas::ViewRequestBus::Event(viewId, &GraphCanvas::ViewRequests::CenterOnStartOfChain); } void MainWindow::OnGotoEndOfChain() { AZ::EntityId graphCanvasGraphId = GetActiveGraphCanvasGraphId(); GraphCanvas::ViewId viewId; GraphCanvas::SceneRequestBus::EventResult(viewId, graphCanvasGraphId, &GraphCanvas::SceneRequests::GetViewId); GraphCanvas::ViewRequestBus::Event(viewId, &GraphCanvas::ViewRequests::CenterOnEndOfChain); } void MainWindow::UpdateWorkspaceStatus(const ScriptCanvasMemoryAsset& memoryAsset) { AZ::Data::AssetId fileAssetId = memoryAsset.GetFileAssetId(); AZ::Data::AssetId memoryAssetId = memoryAsset.GetId(); int eraseCount = m_loadingAssets.erase(fileAssetId); if (eraseCount > 0) { AZStd::string rootFilePath; AZ::Data::AssetInfo assetInfo = AssetHelpers::GetAssetInfo(fileAssetId, rootFilePath); // Don't want to use the join since I don't want the normalized path if (!rootFilePath.empty() && !assetInfo.m_relativePath.empty()) { int eraseCount = m_loadingWorkspaceAssets.erase(fileAssetId); if (eraseCount == 0) { AZStd::string fullPath = AZStd::string::format("%s/%s", rootFilePath.c_str(), assetInfo.m_relativePath.c_str()); AddRecentFile(fullPath.c_str()); } } } } void MainWindow::OnCanUndoChanged(bool canUndo) { ui->action_Undo->setEnabled(canUndo); } void MainWindow::OnCanRedoChanged(bool canRedo) { ui->action_Redo->setEnabled(canRedo); } GraphCanvas::ContextMenuAction::SceneReaction MainWindow::HandleContextMenu(GraphCanvas::EditorContextMenu& editorContextMenu, const AZ::EntityId& memberId, const QPoint& screenPoint, const QPointF& scenePoint) const { AZ::Vector2 sceneVector(aznumeric_cast(scenePoint.x()), aznumeric_cast(scenePoint.y())); GraphCanvas::GraphId graphCanvasGraphId = GetActiveGraphCanvasGraphId(); editorContextMenu.RefreshActions(graphCanvasGraphId, memberId); QAction* result = editorContextMenu.exec(screenPoint); GraphCanvas::ContextMenuAction* contextMenuAction = qobject_cast(result); if (contextMenuAction) { return contextMenuAction->TriggerAction(graphCanvasGraphId, sceneVector); } else { return GraphCanvas::ContextMenuAction::SceneReaction::Nothing; } } void MainWindow::OnAutoSave() { if (m_allowAutoSave) { const Tracker::ScriptCanvasFileState& fileState = GetAssetFileState(m_activeAssetId); if (fileState != Tracker::ScriptCanvasFileState::INVALID && fileState != Tracker::ScriptCanvasFileState::NEW) { OnFileSaveCaller(); } } } //! GeneralRequestBus void MainWindow::OnChangeActiveGraphTab(AZ::Data::AssetId assetId) { SetActiveAsset(assetId); } AZ::EntityId MainWindow::GetActiveGraphCanvasGraphId() const { AZ::EntityId graphId; AssetTrackerRequestBus::BroadcastResult(graphId, &AssetTrackerRequests::GetGraphId, m_activeAssetId); return graphId; } ScriptCanvas::ScriptCanvasId MainWindow::GetActiveScriptCanvasId() const { ScriptCanvas::ScriptCanvasId sceneId; AssetTrackerRequestBus::BroadcastResult(sceneId, &AssetTrackerRequests::GetScriptCanvasId, m_activeAssetId); return sceneId; } GraphCanvas::GraphId MainWindow::GetGraphCanvasGraphId(const ScriptCanvas::ScriptCanvasId& scriptCanvasId) const { AZ::EntityId graphCanvasId; AssetTrackerRequestBus::BroadcastResult(graphCanvasId, &AssetTrackerRequests::GetGraphCanvasId, scriptCanvasId); return graphCanvasId; } GraphCanvas::GraphId MainWindow::FindGraphCanvasGraphIdByAssetId(const AZ::Data::AssetId& assetId) const { AZ::EntityId graphId; AssetTrackerRequestBus::BroadcastResult(graphId, &AssetTrackerRequests::GetGraphId, assetId); return graphId; } ScriptCanvas::ScriptCanvasId MainWindow::FindScriptCanvasIdByAssetId(const AZ::Data::AssetId& assetId) const { ScriptCanvas::ScriptCanvasId scriptCanvasId; AssetTrackerRequestBus::BroadcastResult(scriptCanvasId, &AssetTrackerRequests::GetScriptCanvasId, assetId); return scriptCanvasId; } ScriptCanvas::ScriptCanvasId MainWindow::GetScriptCanvasId(const GraphCanvas::GraphId& graphCanvasGraphId) const { ScriptCanvas::ScriptCanvasId scriptCanvasId; AssetTrackerRequestBus::BroadcastResult(scriptCanvasId, &AssetTrackerRequests::GetScriptCanvasIdFromGraphId, graphCanvasGraphId); return scriptCanvasId; } bool MainWindow::IsInUndoRedo(const AZ::EntityId& graphCanvasGraphId) const { bool isActive = false; UndoRequestBus::EventResult(isActive, GetScriptCanvasId(graphCanvasGraphId), &UndoRequests::IsActive); return isActive; } bool MainWindow::IsScriptCanvasInUndoRedo(const ScriptCanvas::ScriptCanvasId& scriptCanvasId) const { if (GetActiveScriptCanvasId() == scriptCanvasId) { bool isInUndoRedo = false; UndoRequestBus::BroadcastResult(isInUndoRedo, &UndoRequests::IsActive); return isInUndoRedo; } return false; } bool MainWindow::IsActiveGraphInUndoRedo() const { bool isActive = false; UndoRequestBus::EventResult(isActive, GetActiveScriptCanvasId(), &UndoRequests::IsActive); return isActive; } QVariant MainWindow::GetTabData(const AZ::Data::AssetId& assetId) { for (int tabIndex = 0; tabIndex < m_tabBar->count(); ++tabIndex) { QVariant data = m_tabBar->tabData(tabIndex); if (data.isValid()) { auto tabAssetId = data.value(); if (tabAssetId == assetId) { return data; } } } return QVariant(); } bool MainWindow::IsTabOpen(const AZ::Data::AssetId& fileAssetId, int& outTabIndex) const { int tabIndex = m_tabBar->FindTab(fileAssetId); if (-1 != tabIndex) { outTabIndex = tabIndex; return true; } return false; } void MainWindow::ReconnectSceneBuses(AZ::Data::AssetId previousAssetId, AZ::Data::AssetId nextAssetId) { ScriptCanvasMemoryAsset::pointer previousAsset; AssetTrackerRequestBus::BroadcastResult(previousAsset, &AssetTrackerRequests::GetAsset, previousAssetId); ScriptCanvasMemoryAsset::pointer nextAsset; AssetTrackerRequestBus::BroadcastResult(nextAsset, &AssetTrackerRequests::GetAsset, nextAssetId); // Disconnect previous asset AZ::EntityId previousScriptCanvasSceneId; if (previousAsset) { previousScriptCanvasSceneId = previousAsset->GetScriptCanvasId(); GraphCanvas::SceneNotificationBus::MultiHandler::BusDisconnect(previousScriptCanvasSceneId); } AZ::EntityId nextAssetGraphCanvasId; if (nextAsset) { // Connect the next asset nextAssetGraphCanvasId = nextAsset->GetGraphId(); if (nextAssetGraphCanvasId.IsValid()) { GraphCanvas::SceneNotificationBus::MultiHandler::BusConnect(nextAssetGraphCanvasId); GraphCanvas::SceneMimeDelegateRequestBus::Event(nextAssetGraphCanvasId, &GraphCanvas::SceneMimeDelegateRequests::AddDelegate, m_entityMimeDelegateId); GraphCanvas::SceneRequestBus::Event(nextAssetGraphCanvasId, &GraphCanvas::SceneRequests::SetMimeType, Widget::NodePaletteDockWidget::GetMimeType()); GraphCanvas::SceneMemberNotificationBus::Event(nextAssetGraphCanvasId, &GraphCanvas::SceneMemberNotifications::OnSceneReady); } } // Notify about the graph refresh GraphCanvas::AssetEditorNotificationBus::Event(ScriptCanvasEditor::AssetEditorId, &GraphCanvas::AssetEditorNotifications::OnGraphRefreshed, previousScriptCanvasSceneId, nextAssetGraphCanvasId); } void MainWindow::SetActiveAsset(const AZ::Data::AssetId& fileAssetId) { if (m_activeAssetId == fileAssetId) { return; } AssetHelpers::PrintInfo("SetActiveAsset : from: %s to %s", AssetHelpers::AssetIdToString(m_activeAssetId).c_str(), AssetHelpers::AssetIdToString(fileAssetId).c_str()); if (fileAssetId.IsValid()) { if (m_tabBar->FindTab(fileAssetId) >= 0) { QSignalBlocker signalBlocker(m_tabBar); m_tabBar->SelectTab(fileAssetId); } else { AZ_Assert(false, "A graph was opened, but a tab was not created for it."); } } if (m_activeAssetId.IsValid()) { ScriptCanvasMemoryAsset::pointer memoryAsset; AssetTrackerRequestBus::BroadcastResult(memoryAsset, &AssetTrackerRequests::GetAsset, m_activeAssetId); // If we are saving the asset, the Id may have changed from the in-memory to the file asset Id, in that case, // there's no need to hide the view or remove the widget if (memoryAsset && memoryAsset->GetView()) { memoryAsset->GetView()->hide(); m_layout->removeWidget(memoryAsset->GetView()); } } if (fileAssetId.IsValid()) { AZ::Data::AssetId previousAssetId = m_activeAssetId; m_activeAssetId = fileAssetId; RefreshActiveAsset(); ReconnectSceneBuses(previousAssetId, m_activeAssetId); } else { AZ::Data::AssetId previousAssetId = m_activeAssetId; m_activeAssetId.SetInvalid(); m_emptyCanvas->show(); ReconnectSceneBuses(previousAssetId, m_activeAssetId); SignalActiveSceneChanged(AZ::Data::AssetId()); } UpdateUndoCache(fileAssetId); RefreshSelection(); } void MainWindow::RefreshActiveAsset() { if (m_activeAssetId.IsValid()) { AssetHelpers::PrintInfo("RefreshActiveAsset : m_activeAssetId (%s)", AssetHelpers::AssetIdToString(m_activeAssetId).c_str()); ScriptCanvasMemoryAsset::pointer memoryAsset; AssetTrackerRequestBus::BroadcastResult(memoryAsset, &AssetTrackerRequests::GetAsset, m_activeAssetId); if (memoryAsset) { AZ::EntityId sceneEntityId = memoryAsset->GetScriptCanvasId(); const auto& scriptCanvasAsset = memoryAsset->GetAsset(); if (scriptCanvasAsset.IsReady() && scriptCanvasAsset.Get()->GetScriptCanvasEntity()->GetState() == AZ::Entity::ES_ACTIVE) { if (!memoryAsset->GetView()) { memoryAsset->CreateView(m_tabBar); } auto view = memoryAsset->GetView(); AZ_Assert(view, "Asset should have a view"); if (view) { AssetHelpers::PrintInfo("RefreshActiveAsset : m_activeAssetId (%s)", AssetHelpers::AssetIdToString(m_activeAssetId).c_str()); view->ShowScene(sceneEntityId); m_layout->addWidget(view); view->show(); m_emptyCanvas->hide(); } SignalActiveSceneChanged(m_activeAssetId); } } else { // If we couldn't load a memory asset for our active asset. Just set ourselves to invalid. SetActiveAsset({}); } } } void MainWindow::Clear() { m_tabBar->CloseAllTabs(); AssetTrackerRequests::AssetList assets; AssetTrackerRequestBus::BroadcastResult(assets, &AssetTrackerRequests::GetAssets); for (auto asset : assets) { RemoveScriptCanvasAsset(asset->GetAsset().GetId()); } SetActiveAsset({}); } void MainWindow::OnTabCloseButtonPressed(int index) { QVariant data = m_tabBar->tabData(index); if (data.isValid()) { auto fileAssetId = data.value(); Tracker::ScriptCanvasFileState fileState; AssetTrackerRequestBus::BroadcastResult(fileState, &AssetTrackerRequests::GetFileState, fileAssetId); bool isSaving = false; AssetTrackerRequestBus::BroadcastResult(isSaving, &AssetTrackerRequests::IsSaving, fileAssetId); if (isSaving) { m_closeCurrentGraphAfterSave = true; return; } UnsavedChangesOptions saveDialogResults = UnsavedChangesOptions::CONTINUE_WITHOUT_SAVING; if (!isSaving && (fileState == Tracker::ScriptCanvasFileState::NEW || fileState == Tracker::ScriptCanvasFileState::MODIFIED || fileState == Tracker::ScriptCanvasFileState::SOURCE_REMOVED)) { SetActiveAsset(fileAssetId); AZStd::string tabName; AssetTrackerRequestBus::BroadcastResult(tabName, &AssetTrackerRequests::GetTabName, fileAssetId); saveDialogResults = ShowSaveDialog(tabName.c_str()); } if (saveDialogResults == UnsavedChangesOptions::SAVE) { auto saveCB = [this](bool isSuccessful, AZ::Data::Asset asset, AZ::Data::AssetId previousAssetId) { if (isSuccessful) { ScriptCanvasMemoryAsset::pointer memoryAsset; AssetTrackerRequestBus::BroadcastResult(memoryAsset, &AssetTrackerRequests::GetAsset, asset.GetId()); AZ_Assert(memoryAsset, "At this point we must have a MemoryAsset"); int tabIndex = -1; if (IsTabOpen(memoryAsset->GetFileAssetId(), tabIndex)) { OnTabCloseRequest(tabIndex); } } else { QMessageBox::critical(this, QString(), QObject::tr("Failed to save.")); } }; if (fileState == Tracker::ScriptCanvasFileState::NEW) { SaveAssetAsImpl(fileAssetId, saveCB); } else { SaveAsset(fileAssetId, saveCB); } } else if (saveDialogResults == UnsavedChangesOptions::CONTINUE_WITHOUT_SAVING) { OnTabCloseRequest(index); } } } void MainWindow::SaveTab(int index) { QVariant data = m_tabBar->tabData(index); if (data.isValid()) { auto assetId = data.value(); SaveAssetImpl(assetId, nullptr); } } void MainWindow::CloseAllTabs() { m_isClosingTabs = true; m_skipTabOnClose.SetInvalid(); CloseNextTab(); } void MainWindow::CloseAllTabsBut(int index) { QVariant data = m_tabBar->tabData(index); if (data.isValid()) { auto assetId = data.value(); m_isClosingTabs = true; m_skipTabOnClose = assetId; CloseNextTab(); } } void MainWindow::CopyPathToClipboard(int index) { QVariant data = m_tabBar->tabData(index); if (data.isValid()) { QClipboard* clipBoard = QGuiApplication::clipboard(); auto assetId = data.value(); ScriptCanvasMemoryAsset::pointer memoryAsset; AssetTrackerRequestBus::BroadcastResult(memoryAsset, &AssetTrackerRequests::GetAsset, assetId); if (memoryAsset) { clipBoard->setText(memoryAsset->GetAbsolutePath().c_str()); } else { clipBoard->setText(m_tabBar->tabText(index)); } } } void MainWindow::OnActiveFileStateChanged() { UpdateSaveState(); UpdateAssignToSelectionState(); } void MainWindow::CloseNextTab() { if (m_isClosingTabs) { if (m_tabBar->count() == 0 || (m_tabBar->count() == 1 && m_skipTabOnClose.IsValid())) { m_isClosingTabs = false; m_skipTabOnClose.SetInvalid(); return; } int tab = 0; while (tab < m_tabBar->count()) { QVariant data = m_tabBar->tabData(tab); if (data.isValid()) { auto assetId = data.value(); if (assetId != m_skipTabOnClose) { break; } } tab++; } m_tabBar->tabCloseRequested(tab); } } void MainWindow::OnTabCloseRequest(int index) { QVariant data = m_tabBar->tabData(index); if (data.isValid()) { auto tabAssetId = data.value(); if (tabAssetId == m_activeAssetId) { SetActiveAsset({}); } ScriptCanvasMemoryAsset::pointer memoryAsset; AssetTrackerRequestBus::BroadcastResult(memoryAsset, &AssetTrackerRequests::GetAsset, tabAssetId); if (memoryAsset && memoryAsset->GetView()) { memoryAsset->GetView()->hide(); } m_tabBar->CloseTab(index); m_tabBar->update(); RemoveScriptCanvasAsset(tabAssetId); if (m_tabBar->count() == 0) { // The last tab has been removed. SetActiveAsset({}); } // Handling various close all events because the save is async need to deal with this in a bunch of different ways // Always want to trigger this, even if we don't have any active tabs to avoid doubling the clean-up // information AddSystemTickAction(SystemTickActionFlag::CloseNextTabAction); } } void MainWindow::OnSelectionChanged() { QueuePropertyGridUpdate(); } void MainWindow::OnVariableSelectionChanged(const AZStd::vector& variablePropertyIds) { m_selectedVariableIds = variablePropertyIds; QueuePropertyGridUpdate(); } void MainWindow::QueuePropertyGridUpdate() { // Selection will be ignored when a delete operation is are taking place to prevent slowdown from processing // too many events at once. if (!m_ignoreSelection && !m_isInAutomation) { AddSystemTickAction(SystemTickActionFlag::RefreshPropertyGrid); } } void MainWindow::DequeuePropertyGridUpdate() { RemoveSystemTickAction(SystemTickActionFlag::RefreshPropertyGrid); } void MainWindow::SetDefaultLayout() { // Disable updates while we restore the layout to avoid temporary glitches // as the panes are moved around setUpdatesEnabled(false); if (m_commandLine) { m_commandLine->hide(); } if (m_validationDockWidget) { addDockWidget(Qt::BottomDockWidgetArea, m_validationDockWidget); m_validationDockWidget->setFloating(false); m_validationDockWidget->hide(); } if (m_logPanel) { addDockWidget(Qt::BottomDockWidgetArea, m_logPanel); m_logPanel->setFloating(false); m_logPanel->hide(); } /* Disable Mini-map until we fix rendering performance if (m_minimap) { addDockWidget(Qt::LeftDockWidgetArea, m_minimap); m_minimap->setFloating(false); m_minimap->hide(); } */ if (m_nodePalette) { addDockWidget(Qt::LeftDockWidgetArea, m_nodePalette); m_nodePalette->setFloating(false); m_nodePalette->show(); } if (m_variableDockWidget) { addDockWidget(Qt::RightDockWidgetArea, m_variableDockWidget); m_variableDockWidget->setFloating(false); m_variableDockWidget->show(); } if (m_unitTestDockWidget) { addDockWidget(Qt::LeftDockWidgetArea, m_unitTestDockWidget); m_unitTestDockWidget->setFloating(false); m_unitTestDockWidget->hide(); } if (m_loggingWindow) { addDockWidget(Qt::BottomDockWidgetArea, m_loggingWindow); m_loggingWindow->setFloating(false); m_loggingWindow->hide(); } if (m_propertyGrid) { addDockWidget(Qt::RightDockWidgetArea, m_propertyGrid); m_propertyGrid->setFloating(false); m_propertyGrid->show(); } if (m_bookmarkDockWidget) { addDockWidget(Qt::RightDockWidgetArea, m_bookmarkDockWidget); m_bookmarkDockWidget->setFloating(false); m_bookmarkDockWidget->hide(); } /* Disable mini-map until we fix rendering performance if (m_minimap) { addDockWidget(Qt::RightDockWidgetArea, m_minimap); m_minimap->setFloating(false); m_minimap->hide(); } */ resizeDocks( { m_nodePalette, m_propertyGrid }, { static_cast(size().width() * 0.15f), static_cast(size().width() * 0.2f) }, Qt::Horizontal); resizeDocks({ m_nodePalette, m_minimap }, { static_cast(size().height() * 0.70f), static_cast(size().height() * 0.30f) }, Qt::Vertical); resizeDocks({ m_propertyGrid, m_variableDockWidget }, { static_cast(size().height() * 0.70f), static_cast(size().height() * 0.30f) }, Qt::Vertical); resizeDocks({ m_validationDockWidget }, { static_cast(size().height() * 0.01) }, Qt::Vertical); // Disabled until debugger is implemented //resizeDocks({ m_logPanel }, { static_cast(size().height() * 0.1f) }, Qt::Vertical); // Re-enable updates now that we've finished adjusting the layout setUpdatesEnabled(true); m_defaultLayout = saveState(); UpdateViewMenu(); } void MainWindow::RefreshSelection() { ScriptCanvas::ScriptCanvasId scriptCanvasId = GetActiveScriptCanvasId(); AZ::EntityId graphCanvasGraphId; EditorGraphRequestBus::EventResult(graphCanvasGraphId, scriptCanvasId, &EditorGraphRequests::GetGraphCanvasGraphId); bool hasCopiableSelection = false; bool hasSelection = false; if (m_activeAssetId.IsValid()) { if (graphCanvasGraphId.IsValid()) { // Get the selected nodes. GraphCanvas::SceneRequestBus::EventResult(hasCopiableSelection, graphCanvasGraphId, &GraphCanvas::SceneRequests::HasCopiableSelection); } AZStd::vector< AZ::EntityId > selection; GraphCanvas::SceneRequestBus::EventResult(selection, graphCanvasGraphId, &GraphCanvas::SceneRequests::GetSelectedItems); selection.reserve(selection.size() + m_selectedVariableIds.size()); selection.insert(selection.end(), m_selectedVariableIds.begin(), m_selectedVariableIds.end()); if (!selection.empty()) { hasSelection = true; m_propertyGrid->SetSelection(selection); } else { m_propertyGrid->ClearSelection(); } } else { m_propertyGrid->ClearSelection(); } // cut, copy and duplicate only works for specified items ui->action_Cut->setEnabled(hasCopiableSelection); ui->action_Copy->setEnabled(hasCopiableSelection); ui->action_Duplicate->setEnabled(hasCopiableSelection); // Delete will work for anything that is selectable ui->action_Delete->setEnabled(hasSelection); } void MainWindow::OnViewNodePalette() { if (m_nodePalette) { m_nodePalette->toggleViewAction()->trigger(); } } void MainWindow::OnViewMiniMap() { if (m_minimap) { m_minimap->toggleViewAction()->trigger(); } } void MainWindow::OnViewLogWindow() { if (m_loggingWindow) { m_loggingWindow->toggleViewAction()->trigger(); } } void MainWindow::OnViewGraphValidation() { if (m_validationDockWidget) { m_validationDockWidget->toggleViewAction()->trigger(); } } void MainWindow::OnViewDebuggingWindow() { if (m_loggingWindow) { m_loggingWindow->toggleViewAction()->trigger(); } } void MainWindow::OnViewUnitTestManager() { if (m_unitTestDockWidget == nullptr) { CreateUnitTestWidget(); } if (m_unitTestDockWidget) { m_unitTestDockWidget->show(); m_unitTestDockWidget->raise(); m_unitTestDockWidget->activateWindow(); } } void MainWindow::OnViewStatisticsPanel() { if (m_statisticsDialog) { m_statisticsDialog->InitStatisticsWindow(); m_statisticsDialog->show(); m_statisticsDialog->raise(); m_statisticsDialog->activateWindow(); } } void MainWindow::OnViewPresetsEditor() { if (m_presetEditor && m_presetWrapper) { QSize boundingBox = size(); QPointF newPosition = mapToGlobal(QPoint(aznumeric_cast(boundingBox.width() * 0.5f), aznumeric_cast(boundingBox.height() * 0.5f))); m_presetEditor->show(); m_presetWrapper->show(); m_presetWrapper->raise(); m_presetWrapper->activateWindow(); QRect geometry = m_presetWrapper->geometry(); QSize originalSize = geometry.size(); newPosition.setX(newPosition.x() - geometry.width() * 0.5f); newPosition.setY(newPosition.y() - geometry.height() * 0.5f); geometry.setTopLeft(newPosition.toPoint()); geometry.setWidth(originalSize.width()); geometry.setHeight(originalSize.height()); m_presetWrapper->setGeometry(geometry); } } void MainWindow::OnViewProperties() { if (m_propertyGrid) { m_propertyGrid->toggleViewAction()->trigger(); } } void MainWindow::OnViewDebugger() { } void MainWindow::OnViewCommandLine() { if (m_commandLine->isVisible()) { m_commandLine->hide(); } else { m_commandLine->show(); } } void MainWindow::OnViewLog() { if (m_logPanel) { m_logPanel->toggleViewAction()->trigger(); } } void MainWindow::OnBookmarks() { if (m_bookmarkDockWidget) { m_bookmarkDockWidget->toggleViewAction()->trigger(); } } void MainWindow::OnVariableManager() { if (m_variableDockWidget) { m_variableDockWidget->toggleViewAction()->trigger(); } } void MainWindow::OnRestoreDefaultLayout() { if (!m_defaultLayout.isEmpty()) { restoreState(m_defaultLayout); UpdateViewMenu(); } } void MainWindow::UpdateViewMenu() { if (ui->action_ViewBookmarks->isChecked() != m_bookmarkDockWidget->isVisible()) { QSignalBlocker signalBlocker(ui->action_ViewBookmarks); ui->action_ViewBookmarks->setChecked(m_bookmarkDockWidget->isVisible()); } if (ui->action_ViewMiniMap->isChecked() != m_minimap->isVisible()) { QSignalBlocker signalBlocker(ui->action_ViewMiniMap); ui->action_ViewMiniMap->setChecked(m_minimap->isVisible()); } if (ui->action_ViewNodePalette->isChecked() != m_nodePalette->isVisible()) { QSignalBlocker signalBlocker(ui->action_ViewNodePalette); ui->action_ViewNodePalette->setChecked(m_nodePalette->isVisible()); } if (ui->action_ViewProperties->isChecked() != m_propertyGrid->isVisible()) { QSignalBlocker signalBlocker(ui->action_ViewProperties); ui->action_ViewProperties->setChecked(m_propertyGrid->isVisible()); } if (ui->action_ViewVariableManager->isChecked() != m_variableDockWidget->isVisible()) { QSignalBlocker signalBlocker(ui->action_ViewVariableManager); ui->action_ViewVariableManager->setChecked(m_variableDockWidget->isVisible()); } if (ui->action_ViewLogWindow->isChecked() != m_loggingWindow->isVisible()) { QSignalBlocker signalBlocker(ui->action_ViewLogWindow); ui->action_ViewLogWindow->setChecked(m_loggingWindow->isVisible()); } if (ui->action_GraphValidation->isChecked() != m_validationDockWidget->isVisible()) { QSignalBlocker signalBlocker(ui->action_GraphValidation); ui->action_GraphValidation->setChecked(m_validationDockWidget->isVisible()); } if (ui->action_Debugging->isChecked() != m_loggingWindow->isVisible()) { ui->action_Debugging->setChecked(m_loggingWindow->isVisible()); } // Want these two elements to be mutually exclusive. if (m_statusWidget->isVisible() == m_validationDockWidget->isVisible()) { statusBar()->setVisible(!m_validationDockWidget->isVisible()); m_statusWidget->setVisible(!m_validationDockWidget->isVisible()); } } void MainWindow::DeleteNodes(const AZ::EntityId& graphCanvasGraphId, const AZStd::vector& nodes) { // clear the selection then delete the nodes that were selected GraphCanvas::SceneRequestBus::Event(graphCanvasGraphId, &GraphCanvas::SceneRequests::ClearSelection); GraphCanvas::SceneRequestBus::Event(graphCanvasGraphId, &GraphCanvas::SceneRequests::Delete, AZStd::unordered_set{ nodes.begin(), nodes.end() }); } void MainWindow::DeleteConnections(const AZ::EntityId& graphCanvasGraphId, const AZStd::vector& connections) { ScopedVariableSetter scopedIgnoreSelection(m_ignoreSelection, true); GraphCanvas::SceneRequestBus::Event(graphCanvasGraphId, &GraphCanvas::SceneRequests::Delete, AZStd::unordered_set{ connections.begin(), connections.end() }); } void MainWindow::DisconnectEndpoints(const AZ::EntityId& graphCanvasGraphId, const AZStd::vector& endpoints) { AZStd::unordered_set connections; for (const auto& endpoint : endpoints) { AZStd::vector endpointConnections; GraphCanvas::SceneRequestBus::EventResult(endpointConnections, graphCanvasGraphId, &GraphCanvas::SceneRequests::GetConnectionsForEndpoint, endpoint); connections.insert(endpointConnections.begin(), endpointConnections.end()); } DeleteConnections(graphCanvasGraphId, { connections.begin(), connections.end() }); } void MainWindow::RunBatchConversion() { if (m_batchTool == nullptr) { QFileDialog directoryDialog(this); directoryDialog.setFileMode(QFileDialog::FileMode::Directory); if (directoryDialog.exec()) { QStringList directories = directoryDialog.selectedFiles(); m_batchTool = aznew ScriptCanvasBatchConverter(this, directories); } } } void MainWindow::OnShowValidationErrors() { m_userSettings->m_showValidationErrors = true; if (!m_validationDockWidget->isVisible()) { OnViewGraphValidation(); // If the window wasn't visible, it doesn't seem to get the signals. // So need to manually prompt it to get the desired result m_validationDockWidget->OnShowErrors(); } } void MainWindow::OnShowValidationWarnings() { m_userSettings->m_showValidationWarnings = true; if (!m_validationDockWidget->isVisible()) { OnViewGraphValidation(); // If the window wasn't visible, it doesn't seem to get the signals. // So need to manually prompt it to get the desired result m_validationDockWidget->OnShowWarnings(); } } void MainWindow::OnValidateCurrentGraph() { const bool displayToastNotification = false; RunGraphValidation(displayToastNotification); } void MainWindow::RunGraphValidation(bool displayToastNotification) { m_validationDockWidget->OnRunValidator(displayToastNotification); if (m_validationDockWidget->HasValidationIssues()) { OpenValidationPanel(); } } void MainWindow::OnViewParamsChanged(const GraphCanvas::ViewParams& viewParams) { AZ_UNUSED(viewParams); RestartAutoTimerSave(); } void MainWindow::OnZoomChanged(qreal zoomLevel) { RestartAutoTimerSave(); } void MainWindow::AfterEntitySelectionChanged(const AzToolsFramework::EntityIdList&, const AzToolsFramework::EntityIdList&) { UpdateAssignToSelectionState(); } void MainWindow::UpdateMenuState(bool enabled) { m_validateGraphToolButton->setEnabled(enabled); ui->menuRemove_Unused->setEnabled(enabled); ui->action_RemoveUnusedNodes->setEnabled(enabled); ui->action_RemoveUnusedVariables->setEnabled(enabled); ui->action_RemoveUnusedElements->setEnabled(enabled); ui->action_ZoomIn->setEnabled(enabled); ui->action_ZoomOut->setEnabled(enabled); ui->action_ZoomSelection->setEnabled(enabled); ui->action_ShowEntireGraph->setEnabled(enabled); ui->menuGo_To->setEnabled(enabled); ui->action_GotoStartOfChain->setEnabled(enabled); ui->action_GotoEndOfChain->setEnabled(enabled); ui->actionZoom_To->setEnabled(enabled); ui->action_EnableSelection->setEnabled(enabled); ui->action_DisableSelection->setEnabled(enabled); // File Menu ui->action_Close->setEnabled(enabled); bool isFunctionGraph = false; EditorGraphRequestBus::EventResult(isFunctionGraph, GetActiveScriptCanvasId(), &EditorGraphRequests::IsFunctionGraph); m_createFunctionInput->setEnabled(isFunctionGraph && enabled); m_createFunctionOutput->setEnabled(isFunctionGraph && enabled); RefreshGraphPreferencesAction(); UpdateAssignToSelectionState(); UpdateUndoRedoState(); UpdateSaveState(); } void MainWindow::OnWorkspaceRestoreStart() { m_isRestoringWorkspace = true; } void MainWindow::OnWorkspaceRestoreEnd(AZ::Data::AssetId lastFocusAsset) { if (m_isRestoringWorkspace) { m_isRestoringWorkspace = false; if (m_queuedFocusOverride.IsValid()) { SetActiveAsset(m_queuedFocusOverride); m_queuedFocusOverride.SetInvalid(); } else if (lastFocusAsset.IsValid()) { SetActiveAsset(lastFocusAsset); } if (!m_activeAssetId.IsValid()) { if (m_tabBar->count() > 0) { if (m_tabBar->currentIndex() != 0) { m_tabBar->setCurrentIndex(0); } else { SetActiveAsset(m_tabBar->FindAssetId(0)); } } else { SetActiveAsset({}); } } } } void MainWindow::UpdateAssignToSelectionState() { bool buttonEnabled = m_activeAssetId.IsValid(); if (buttonEnabled) { const Tracker::ScriptCanvasFileState& fileState = GetAssetFileState(m_activeAssetId); if (fileState == Tracker::ScriptCanvasFileState::INVALID || fileState == Tracker::ScriptCanvasFileState::NEW || fileState == Tracker::ScriptCanvasFileState::SOURCE_REMOVED) { buttonEnabled = false; } else { EditorGraphRequestBus::EventResult(buttonEnabled, GetActiveScriptCanvasId(), &EditorGraphRequests::IsRuntimeGraph); } m_assignToSelectedEntity->setEnabled(buttonEnabled); } else { m_assignToSelectedEntity->setEnabled(false); } } void MainWindow::UpdateUndoRedoState() { bool isEnabled = false; UndoRequestBus::EventResult(isEnabled, GetActiveScriptCanvasId(), &UndoRequests::CanUndo); ui->action_Undo->setEnabled(isEnabled); isEnabled = false; UndoRequestBus::EventResult(isEnabled, GetActiveScriptCanvasId(), &UndoRequests::CanRedo); ui->action_Redo->setEnabled(isEnabled); } void MainWindow::UpdateSaveState() { bool enabled = m_activeAssetId.IsValid(); bool isSaving = false; bool hasModifications = false; if (enabled) { Tracker::ScriptCanvasFileState fileState = GetAssetFileState(m_activeAssetId); hasModifications = ( fileState == Tracker::ScriptCanvasFileState::MODIFIED || fileState == Tracker::ScriptCanvasFileState::NEW || fileState == Tracker::ScriptCanvasFileState::SOURCE_REMOVED); AssetTrackerRequestBus::BroadcastResult(isSaving, &AssetTrackerRequests::IsSaving, m_activeAssetId); } ui->action_Save->setEnabled(enabled && !isSaving && hasModifications); ui->action_Save_As->setEnabled(enabled && !isSaving); } void MainWindow::CreateFunctionInput() { PushPreventUndoStateUpdate(); CreateExecutionNodeling(-1); PopPreventUndoStateUpdate(); PostUndoPoint(GetActiveScriptCanvasId()); } void MainWindow::CreateFunctionOutput() { PushPreventUndoStateUpdate(); CreateExecutionNodeling(1); PopPreventUndoStateUpdate(); PostUndoPoint(GetActiveScriptCanvasId()); } void MainWindow::CreateExecutionNodeling(int positionOffset) { ScriptCanvas::ScriptCanvasId scriptCanvasId = GetActiveScriptCanvasId(); GraphCanvas::GraphId graphCanvasGraphId = GetActiveGraphCanvasGraphId(); GraphCanvas::ViewId viewId; GraphCanvas::SceneRequestBus::EventResult(viewId, graphCanvasGraphId, &GraphCanvas::SceneRequests::GetViewId); QRectF viewBounds; GraphCanvas::ViewRequestBus::EventResult(viewBounds, viewId, &GraphCanvas::ViewRequests::GetCompleteArea); AZStd::string rootName = "New Output"; if (positionOffset < 0) { rootName = "New Input"; } NodeIdPair nodeIdPair = Nodes::CreateExecutionNodeling(scriptCanvasId, rootName); GraphCanvas::SceneRequests* sceneRequests = GraphCanvas::SceneRequestBus::FindFirstHandler(graphCanvasGraphId); if (sceneRequests == nullptr) { return; } QPointF pasteOffset = sceneRequests->SignalGenericAddPositionUseBegin(); sceneRequests->AddNode(nodeIdPair.m_graphCanvasId, GraphCanvas::ConversionUtils::QPointToVector(pasteOffset)); sceneRequests->SignalGenericAddPositionUseEnd(); if (!viewBounds.isEmpty()) { QPointF topLeftPoint = viewBounds.center(); int widthOffset = aznumeric_cast((viewBounds.width() * 0.5f) * positionOffset); topLeftPoint.setX(topLeftPoint.x() + widthOffset); QGraphicsItem* graphicsItem = nullptr; GraphCanvas::SceneMemberUIRequestBus::EventResult(graphicsItem, nodeIdPair.m_graphCanvasId, &GraphCanvas::SceneMemberUIRequests::GetRootGraphicsItem); GraphCanvas::NodeUIRequestBus::Event(nodeIdPair.m_graphCanvasId, &GraphCanvas::NodeUIRequests::AdjustSize); qreal width = graphicsItem->sceneBoundingRect().width(); // If we are going negative we need to move over the width of the node. if (positionOffset < 0) { topLeftPoint.setX(topLeftPoint.x() - width); } // Center the node. qreal height = graphicsItem->sceneBoundingRect().height(); topLeftPoint.setY(topLeftPoint.y() - height * 0.5); // Offset by the width step. AZ::Vector2 minorStep = AZ::Vector2::CreateZero(); AZ::EntityId gridId; GraphCanvas::SceneRequestBus::EventResult(gridId, graphCanvasGraphId, &GraphCanvas::SceneRequests::GetGrid); GraphCanvas::GridRequestBus::EventResult(minorStep, gridId, &GraphCanvas::GridRequests::GetMinorPitch); QRectF sceneBoundaries = sceneRequests->AsQGraphicsScene()->sceneRect(); sceneBoundaries.adjust(minorStep.GetX(), minorStep.GetY(), -minorStep.GetX(), -minorStep.GetY()); topLeftPoint.setX(topLeftPoint.x() + minorStep.GetX() * positionOffset); // Sanitizes the position of the node to ensure it's always 'visible' while (topLeftPoint.x() + width <= sceneBoundaries.left()) { topLeftPoint.setX(topLeftPoint.x() + width); } while (topLeftPoint.x() >= sceneBoundaries.right()) { topLeftPoint.setX(topLeftPoint.x() - width); } while (topLeftPoint.y() + height <= sceneBoundaries.top()) { topLeftPoint.setY(topLeftPoint.y() + height); } while (topLeftPoint.y() >= sceneBoundaries.bottom()) { topLeftPoint.setY(topLeftPoint.y() - height); } //// GraphCanvas::GeometryRequestBus::Event(nodeIdPair.m_graphCanvasId, &GraphCanvas::GeometryRequests::SetPosition, GraphCanvas::ConversionUtils::QPointToVector(topLeftPoint)); GraphCanvas::ViewRequestBus::Event(viewId, &GraphCanvas::ViewRequests::CenterOnArea, graphicsItem->sceneBoundingRect()); } } NodeIdPair MainWindow::ProcessCreateNodeMimeEvent(GraphCanvas::GraphCanvasMimeEvent* mimeEvent, const AZ::EntityId& graphCanvasGraphId, AZ::Vector2 nodeCreationPos) { if (!m_isInAutomation) { GraphCanvas::SceneRequestBus::Event(graphCanvasGraphId, &GraphCanvas::SceneRequests::ClearSelection); } NodeIdPair retVal; if (azrtti_istypeof(mimeEvent)) { CreateNodeMimeEvent* createEvent = static_cast(mimeEvent); if (createEvent->ExecuteEvent(nodeCreationPos, nodeCreationPos, graphCanvasGraphId)) { retVal = createEvent->GetCreatedPair(); } } else if (azrtti_istypeof(mimeEvent)) { SpecializedCreateNodeMimeEvent* specializedCreationEvent = static_cast(mimeEvent); retVal = specializedCreationEvent->ConstructNode(graphCanvasGraphId, nodeCreationPos); } return retVal; } const GraphCanvas::GraphCanvasTreeItem* MainWindow::GetNodePaletteRoot() const { return m_nodePalette->GetTreeRoot(); } void MainWindow::SignalAutomationBegin() { m_isInAutomation = true; } void MainWindow::SignalAutomationEnd() { m_isInAutomation = false; } AZ::EntityId MainWindow::FindEditorNodeIdByAssetNodeId(const AZ::Data::AssetId& assetId, AZ::EntityId assetNodeId) const { AZ::EntityId editorEntityId; AssetTrackerRequestBus::BroadcastResult(editorEntityId, &AssetTrackerRequests::GetEditorEntityIdFromSceneEntityId, assetId, assetNodeId); return editorEntityId; } AZ::EntityId MainWindow::FindAssetNodeIdByEditorNodeId(const AZ::Data::AssetId& assetId, AZ::EntityId editorNodeId) const { AZ::EntityId sceneEntityId; AssetTrackerRequestBus::BroadcastResult(sceneEntityId, &AssetTrackerRequests::GetSceneEntityIdFromEditorEntityId, assetId, editorNodeId); return sceneEntityId; } GraphCanvas::Endpoint MainWindow::CreateNodeForProposalWithGroup(const AZ::EntityId& connectionId, const GraphCanvas::Endpoint& endpoint, const QPointF& scenePoint, const QPoint& screenPoint, AZ::EntityId groupTarget) { PushPreventUndoStateUpdate(); GraphCanvas::Endpoint retVal; AZ::EntityId graphCanvasGraphId = GetActiveGraphCanvasGraphId(); m_sceneContextMenu->FilterForSourceSlot(graphCanvasGraphId, endpoint.GetSlotId()); m_sceneContextMenu->RefreshActions(graphCanvasGraphId, connectionId); m_sceneContextMenu->SetupDisplayForProposal(); QAction* action = m_sceneContextMenu->exec(screenPoint); // If the action returns null. We need to check if it was our widget, or just a close command. if (action == nullptr) { GraphCanvas::GraphCanvasMimeEvent* mimeEvent = m_sceneContextMenu->GetNodePalette()->GetContextMenuEvent(); if (mimeEvent) { bool isValid = false; NodeIdPair finalNode = ProcessCreateNodeMimeEvent(mimeEvent, graphCanvasGraphId, AZ::Vector2(aznumeric_cast(scenePoint.x()), aznumeric_cast(scenePoint.y()))); if (finalNode.m_graphCanvasId.IsValid()) { GraphCanvas::VisualRequestBus::Event(finalNode.m_graphCanvasId, &GraphCanvas::VisualRequests::SetVisible, false); retVal = HandleProposedConnection(graphCanvasGraphId, connectionId, endpoint, finalNode.m_graphCanvasId, screenPoint); } if (retVal.IsValid()) { AZStd::unordered_set createdConnections = GraphCanvas::GraphUtils::CreateOpportunisticConnectionsBetween(endpoint, retVal); GraphCanvas::VisualRequestBus::Event(finalNode.m_graphCanvasId, &GraphCanvas::VisualRequests::SetVisible, true); AZ::Vector2 position; GraphCanvas::GeometryRequestBus::EventResult(position, retVal.GetNodeId(), &GraphCanvas::GeometryRequests::GetPosition); QPointF connectionPoint; GraphCanvas::SlotUIRequestBus::EventResult(connectionPoint, retVal.GetSlotId(), &GraphCanvas::SlotUIRequests::GetConnectionPoint); qreal verticalOffset = connectionPoint.y() - position.GetY(); position.SetY(aznumeric_cast(scenePoint.y() - verticalOffset)); qreal horizontalOffset = connectionPoint.x() - position.GetX(); position.SetX(aznumeric_cast(scenePoint.x() - horizontalOffset)); GraphCanvas::GeometryRequestBus::Event(retVal.GetNodeId(), &GraphCanvas::GeometryRequests::SetPosition, position); GraphCanvas::GraphUtils::AddElementToGroup(finalNode.m_graphCanvasId, groupTarget); GraphCanvas::SceneNotificationBus::Event(graphCanvasGraphId, &GraphCanvas::SceneNotifications::PostCreationEvent); } else { GraphCanvas::GraphUtils::DeleteOutermostNode(graphCanvasGraphId, finalNode.m_graphCanvasId); } } } PopPreventUndoStateUpdate(); return retVal; } void MainWindow::OnWrapperNodeActionWidgetClicked(const AZ::EntityId& wrapperNode, const QRect& actionWidgetBoundingRect, const QPointF& scenePoint, const QPoint& screenPoint) { if (EBusHandlerNodeDescriptorRequestBus::FindFirstHandler(wrapperNode) != nullptr) { m_ebusHandlerActionMenu->SetEbusHandlerNode(wrapperNode); // We don't care about the result, since the actions are done on demand with the menu m_ebusHandlerActionMenu->exec(screenPoint); } else if (ScriptCanvasWrapperNodeDescriptorRequestBus::FindFirstHandler(wrapperNode) != nullptr) { ScriptCanvasWrapperNodeDescriptorRequestBus::Event(wrapperNode, &ScriptCanvasWrapperNodeDescriptorRequests::OnWrapperAction, actionWidgetBoundingRect, scenePoint, screenPoint); } } void MainWindow::OnSelectionManipulationBegin() { m_ignoreSelection = true; } void MainWindow::OnSelectionManipulationEnd() { m_ignoreSelection = false; OnSelectionChanged(); } AZ::EntityId MainWindow::CreateNewGraph() { AZ::EntityId graphId; OnFileNew(); if (m_activeAssetId.IsValid()) { graphId = GetActiveGraphCanvasGraphId(); } return graphId; } bool MainWindow::ContainsGraph(const GraphCanvas::GraphId& graphId) const { return false; } bool MainWindow::CloseGraph(const GraphCanvas::GraphId& graphId) { return false; } void MainWindow::CustomizeConnectionEntity(AZ::Entity* connectionEntity) { connectionEntity->CreateComponent(); } void MainWindow::ShowAssetPresetsMenu(GraphCanvas::ConstructType constructType) { OnViewPresetsEditor(); if (m_presetEditor) { m_presetEditor->SetActiveConstructType(constructType); } } //! Hook for receiving context menu events for each QGraphicsScene GraphCanvas::ContextMenuAction::SceneReaction MainWindow::ShowSceneContextMenuWithGroup(const QPoint& screenPoint, const QPointF& scenePoint, AZ::EntityId groupTarget) { bool tryDaisyChain = (QApplication::keyboardModifiers() & Qt::KeyboardModifier::ShiftModifier) != 0; GraphCanvas::GraphId graphCanvasGraphId = GetActiveGraphCanvasGraphId(); ScriptCanvas::ScriptCanvasId scriptCanvasGraphId = GetActiveScriptCanvasId(); if (!graphCanvasGraphId.IsValid() || !scriptCanvasGraphId.IsValid()) { // Nothing to do. return GraphCanvas::ContextMenuAction::SceneReaction::Nothing; } m_sceneContextMenu->ResetSourceSlotFilter(); m_sceneContextMenu->RefreshActions(graphCanvasGraphId, AZ::EntityId()); QAction* action = m_sceneContextMenu->exec(screenPoint); GraphCanvas::ContextMenuAction::SceneReaction reaction = GraphCanvas::ContextMenuAction::SceneReaction::Nothing; if (action == nullptr) { GraphCanvas::GraphCanvasMimeEvent* mimeEvent = m_sceneContextMenu->GetNodePalette()->GetContextMenuEvent(); NodeIdPair finalNode = ProcessCreateNodeMimeEvent(mimeEvent, graphCanvasGraphId, AZ::Vector2(aznumeric_cast(scenePoint.x()), aznumeric_cast(scenePoint.y()))); GraphCanvas::SceneRequestBus::Event(graphCanvasGraphId, &GraphCanvas::SceneRequests::ClearSelection); if (finalNode.m_graphCanvasId.IsValid()) { GraphCanvas::VisualRequestBus::Event(finalNode.m_graphCanvasId, &GraphCanvas::VisualRequests::SetVisible, true); AZ::Vector2 position; GraphCanvas::GeometryRequestBus::EventResult(position, finalNode.m_graphCanvasId, &GraphCanvas::GeometryRequests::GetPosition); GraphCanvas::GeometryRequestBus::Event(finalNode.m_graphCanvasId, &GraphCanvas::GeometryRequests::SetPosition, position); // If we have a valid group target. We're going to want to add the element to the group. GraphCanvas::GraphUtils::AddElementToGroup(finalNode.m_graphCanvasId, groupTarget); GraphCanvas::SceneNotificationBus::Event(graphCanvasGraphId, &GraphCanvas::SceneNotifications::PostCreationEvent); if (tryDaisyChain) { QTimer::singleShot(50, [graphCanvasGraphId, finalNode, screenPoint, scenePoint, groupTarget]() { GraphCanvas::SceneRequestBus::Event(graphCanvasGraphId, &GraphCanvas::SceneRequests::HandleProposalDaisyChainWithGroup, finalNode.m_graphCanvasId, GraphCanvas::SlotTypes::ExecutionSlot, GraphCanvas::CT_Output, screenPoint, scenePoint, groupTarget); }); } } } else { GraphCanvas::ContextMenuAction* contextMenuAction = qobject_cast(action); if (contextMenuAction) { PushPreventUndoStateUpdate(); AZ::Vector2 mousePoint(aznumeric_cast(scenePoint.x()), aznumeric_cast(scenePoint.y())); reaction = contextMenuAction->TriggerAction(graphCanvasGraphId, mousePoint); PopPreventUndoStateUpdate(); } } return reaction; } //! Hook for receiving context menu events for each QGraphicsScene GraphCanvas::ContextMenuAction::SceneReaction MainWindow::ShowNodeContextMenu(const AZ::EntityId& nodeId, const QPoint& screenPoint, const QPointF& scenePoint) { GraphCanvas::NodeContextMenu contextMenu(ScriptCanvasEditor::AssetEditorId); NodeDescriptorType descriptorType = NodeDescriptorType::Unknown; NodeDescriptorRequestBus::EventResult(descriptorType, nodeId, &NodeDescriptorRequests::GetType); if (descriptorType == NodeDescriptorType::GetVariable || descriptorType == NodeDescriptorType::SetVariable) { contextMenu.AddMenuAction(aznew ConvertVariableNodeToReferenceAction(&contextMenu)); } return HandleContextMenu(contextMenu, nodeId, screenPoint, scenePoint); } GraphCanvas::ContextMenuAction::SceneReaction MainWindow::ShowCommentContextMenu(const AZ::EntityId& nodeId, const QPoint& screenPoint, const QPointF& scenePoint) { GraphCanvas::CommentContextMenu contextMenu(ScriptCanvasEditor::AssetEditorId); return HandleContextMenu(contextMenu, nodeId, screenPoint, scenePoint); } GraphCanvas::ContextMenuAction::SceneReaction MainWindow::ShowNodeGroupContextMenu(const AZ::EntityId& groupId, const QPoint& screenPoint, const QPointF& scenePoint) { GraphCanvas::NodeGroupContextMenu contextMenu(ScriptCanvasEditor::AssetEditorId); return HandleContextMenu(contextMenu, groupId, screenPoint, scenePoint); } GraphCanvas::ContextMenuAction::SceneReaction MainWindow::ShowCollapsedNodeGroupContextMenu(const AZ::EntityId& nodeId, const QPoint& screenPoint, const QPointF& scenePoint) { GraphCanvas::CollapsedNodeGroupContextMenu contextMenu(ScriptCanvasEditor::AssetEditorId); return HandleContextMenu(contextMenu, nodeId, screenPoint, scenePoint); } GraphCanvas::ContextMenuAction::SceneReaction MainWindow::ShowBookmarkContextMenu(const AZ::EntityId& bookmarkId, const QPoint& screenPoint, const QPointF& scenePoint) { GraphCanvas::BookmarkContextMenu contextMenu(ScriptCanvasEditor::AssetEditorId); return HandleContextMenu(contextMenu, bookmarkId, screenPoint, scenePoint); } GraphCanvas::ContextMenuAction::SceneReaction MainWindow::ShowConnectionContextMenuWithGroup(const AZ::EntityId& connectionId, const QPoint& screenPoint, const QPointF& scenePoint, AZ::EntityId groupTarget) { PushPreventUndoStateUpdate(); GraphCanvas::ContextMenuAction::SceneReaction reaction = GraphCanvas::ContextMenuAction::SceneReaction::Nothing; AZ::Vector2 sceneVector(aznumeric_cast(scenePoint.x()), aznumeric_cast(scenePoint.y())); GraphCanvas::GraphId graphCanvasGraphId = GetActiveGraphCanvasGraphId(); m_connectionContextMenu->RefreshActions(graphCanvasGraphId, connectionId); QAction* result = m_connectionContextMenu->exec(screenPoint); GraphCanvas::ContextMenuAction* contextMenuAction = qobject_cast(result); // If the action returns null. We need to check if it was our widget, or just a close command. if (contextMenuAction) { reaction = contextMenuAction->TriggerAction(graphCanvasGraphId, sceneVector); } else { GraphCanvas::GraphCanvasMimeEvent* mimeEvent = m_connectionContextMenu->GetNodePalette()->GetContextMenuEvent(); if (mimeEvent) { bool isValid = false; NodeIdPair finalNode = ProcessCreateNodeMimeEvent(mimeEvent, graphCanvasGraphId, AZ::Vector2(aznumeric_cast(scenePoint.x()), aznumeric_cast(scenePoint.y()))); GraphCanvas::Endpoint sourceEndpoint; GraphCanvas::ConnectionRequestBus::EventResult(sourceEndpoint, connectionId, &GraphCanvas::ConnectionRequests::GetSourceEndpoint); GraphCanvas::Endpoint targetEndpoint; GraphCanvas::ConnectionRequestBus::EventResult(targetEndpoint, connectionId, &GraphCanvas::ConnectionRequests::GetTargetEndpoint); if (finalNode.m_graphCanvasId.IsValid()) { GraphCanvas::ConnectionSpliceConfig spliceConfig; spliceConfig.m_allowOpportunisticConnections = true; if (!GraphCanvas::GraphUtils::SpliceNodeOntoConnection(finalNode.m_graphCanvasId, connectionId, spliceConfig)) { GraphCanvas::GraphUtils::DeleteOutermostNode(graphCanvasGraphId, finalNode.m_graphCanvasId); } else { reaction = GraphCanvas::ContextMenuAction::SceneReaction::PostUndo; // Now we can deal with the alignment of the node. GraphCanvas::VisualRequestBus::Event(finalNode.m_graphCanvasId, &GraphCanvas::VisualRequests::SetVisible, true); AZ::Vector2 position(0,0); GraphCanvas::GeometryRequestBus::EventResult(position, finalNode.m_graphCanvasId, &GraphCanvas::GeometryRequests::GetPosition); QPointF sourceConnectionPoint(0,0); GraphCanvas::SlotUIRequestBus::EventResult(sourceConnectionPoint, spliceConfig.m_splicedSourceEndpoint.GetSlotId(), &GraphCanvas::SlotUIRequests::GetConnectionPoint); QPointF targetConnectionPoint(0,0); GraphCanvas::SlotUIRequestBus::EventResult(targetConnectionPoint, spliceConfig.m_splicedTargetEndpoint.GetSlotId(), &GraphCanvas::SlotUIRequests::GetConnectionPoint); // Average our two points so we splice roughly in the center of our node. QPointF connectionPoint = (sourceConnectionPoint + targetConnectionPoint) * 0.5f; qreal verticalOffset = connectionPoint.y() - position.GetY(); position.SetY(aznumeric_cast(scenePoint.y() - verticalOffset)); qreal horizontalOffset = connectionPoint.x() - position.GetX(); position.SetX(aznumeric_cast(scenePoint.x() - horizontalOffset)); GraphCanvas::GeometryRequestBus::Event(finalNode.m_graphCanvasId, &GraphCanvas::GeometryRequests::SetPosition, position); if (IsNodeNudgingEnabled()) { GraphCanvas::NodeNudgingController nudgingController(graphCanvasGraphId, { finalNode.m_graphCanvasId }); nudgingController.FinalizeNudging(); } GraphCanvas::GraphUtils::AddElementToGroup(finalNode.m_graphCanvasId, groupTarget); GraphCanvas::SceneNotificationBus::Event(graphCanvasGraphId, &GraphCanvas::SceneNotifications::PostCreationEvent); } } } } PopPreventUndoStateUpdate(); return reaction; } GraphCanvas::ContextMenuAction::SceneReaction MainWindow::ShowSlotContextMenu(const AZ::EntityId& slotId, const QPoint& screenPoint, const QPointF& scenePoint) { GraphCanvas::SlotContextMenu contextMenu(ScriptCanvasEditor::AssetEditorId); contextMenu.AddMenuAction(aznew ConvertReferenceToVariableNodeAction(&contextMenu)); contextMenu.AddMenuAction(aznew ExposeSlotMenuAction(&contextMenu)); return HandleContextMenu(contextMenu, slotId, screenPoint, scenePoint); } void MainWindow::OnSystemTick() { if (HasSystemTickAction(SystemTickActionFlag::RefreshPropertyGrid)) { RemoveSystemTickAction(SystemTickActionFlag::RefreshPropertyGrid); RefreshSelection(); } if (HasSystemTickAction(SystemTickActionFlag::CloseWindow)) { RemoveSystemTickAction(SystemTickActionFlag::CloseWindow); qobject_cast(parent())->close(); } if (HasSystemTickAction(SystemTickActionFlag::UpdateSaveMenuState)) { RemoveSystemTickAction(SystemTickActionFlag::UpdateSaveMenuState); UpdateSaveState(); } if (HasSystemTickAction(SystemTickActionFlag::CloseCurrentGraph)) { RemoveSystemTickAction(SystemTickActionFlag::CloseCurrentGraph); if (m_tabBar) { m_tabBar->tabCloseRequested(m_tabBar->currentIndex()); } } if (HasSystemTickAction(SystemTickActionFlag::CloseNextTabAction)) { RemoveSystemTickAction(SystemTickActionFlag::CloseNextTabAction); CloseNextTab(); } if (m_systemTickActions == 0) { AZ::SystemTickBus::Handler::BusDisconnect(); } } void MainWindow::OnCommandStarted(AZ::Crc32) { PushPreventUndoStateUpdate(); } void MainWindow::OnCommandFinished(AZ::Crc32) { PopPreventUndoStateUpdate(); } void MainWindow::SignalBatchOperationComplete(BatchOperatorTool* batchTool) { if (m_batchTool == batchTool) { delete m_batchTool; m_batchTool = nullptr; } } void MainWindow::PrepareActiveAssetForSave() { PrepareAssetForSave(m_activeAssetId); } void MainWindow::PrepareAssetForSave(const AZ::Data::AssetId& assetId) { ScriptCanvasMemoryAsset::pointer memoryAsset; AssetTrackerRequestBus::BroadcastResult(memoryAsset, &AssetTrackerRequests::GetAsset, assetId); if (memoryAsset) { AZ::EntityId graphId = memoryAsset->GetGraphId(); AZ::EntityId scriptCanvasId = memoryAsset->GetScriptCanvasId(); AZ::Entity* entity = nullptr; GraphRequestBus::EventResult(entity, scriptCanvasId, &GraphRequests::GetGraphEntity); if (entity) { GraphCanvas::GraphModelRequestBus::Event(graphId, &GraphCanvas::GraphModelRequests::OnSaveDataDirtied, entity->GetId()); } GraphCanvas::GraphModelRequestBus::Event(graphId, &GraphCanvas::GraphModelRequests::OnSaveDataDirtied, graphId); if (memoryAsset->GetAssetType() == azrtti_typeid()) { // Conversion code from WIP assets AZ::Entity* entity = memoryAsset->GetAsset()->GetScriptCanvasEntity(); ScriptCanvasEditor::Graph* graph = AZ::EntityUtils::FindFirstDerivedComponent(entity); if (graph) { graph->SetAssetType(azrtti_typeid()); } //// RuntimeRequests* graphRequests = RuntimeRequestBus::FindFirstHandler(scriptCanvasId); ScriptCanvasFunctionAsset* functionAsset = memoryAsset->GetAsset().GetAs(); ScriptCanvasFunctionDataComponent* functionDataComponent = functionAsset->GetFunctionData(); functionDataComponent->m_version += 1; AZStd::unordered_map graphCanvasToScriptCanvasMapping; GraphCanvas::OrderedEndpointSet orderedEndpointSet; NodelingRequestBus::EnumerateHandlers([this, graphRequests, &orderedEndpointSet, &graphCanvasToScriptCanvasMapping](NodelingRequests* requests) { ScriptCanvas::Nodes::Core::Internal::Nodeling* nodeling = static_cast(graphRequests->FindNode(requests->GetNodeId())); if (auto executionNodeling = azrtti_cast(nodeling)) { AZStd::vector entrySlots = executionNodeling->GetEntrySlots(); for (auto entrySlot : entrySlots) { GraphCanvas::Endpoint endpoint; SceneMemberMappingRequestBus::EventResult(endpoint.m_nodeId, nodeling->GetNodeId(), &SceneMemberMappingRequests::GetGraphCanvasEntityId); SlotMappingRequestBus::EventResult(endpoint.m_slotId, endpoint.m_nodeId, &SlotMappingRequests::MapToGraphCanvasId, entrySlot->GetId()); GraphCanvas::EndpointOrderingStruct orderingStruct = GraphCanvas::EndpointOrderingStruct::ConstructOrderingInformation(endpoint); orderedEndpointSet.insert(orderingStruct); graphCanvasToScriptCanvasMapping[endpoint.m_nodeId] = nodeling->GetNodeId(); } } return true; }); functionDataComponent->m_executionNodeOrder.clear(); for (GraphCanvas::EndpointOrderingStruct endpoint : orderedEndpointSet) { AZ::EntityId scriptCanvasNodeId = graphCanvasToScriptCanvasMapping[endpoint.m_endpoint.m_nodeId]; if (functionDataComponent->m_executionNodeOrder.empty() || functionDataComponent->m_executionNodeOrder.back() != scriptCanvasNodeId) { functionDataComponent->m_executionNodeOrder.emplace_back(scriptCanvasNodeId); } } functionDataComponent->m_variableOrder.clear(); AZStd::set graphVariables; const ScriptCanvas::VariableData* variableData = graphRequests->GetVariableDataConst(); for (const auto& variablePair : variableData->GetVariables()) { if ( variablePair.second.IsInScope(ScriptCanvas::VariableFlags::Scope::Input) || variablePair.second.IsInScope(ScriptCanvas::VariableFlags::Scope::Output)) { graphVariables.insert(&variablePair.second); } } functionDataComponent->m_variableOrder.reserve(graphVariables.size()); for (const GraphVariable* variable : graphVariables) { functionDataComponent->m_variableOrder.emplace_back(variable->GetVariableId()); } } } } void MainWindow::RestartAutoTimerSave(bool forceTimer) { if (m_autoSaveTimer.isActive() || forceTimer) { m_autoSaveTimer.stop(); m_autoSaveTimer.start(); } } void MainWindow::OnSelectedEntitiesAboutToShow() { AzToolsFramework::EntityIdList selectedEntityIds; AzToolsFramework::ToolsApplicationRequestBus::BroadcastResult(selectedEntityIds, &AzToolsFramework::ToolsApplicationRequests::GetSelectedEntities); m_selectedEntityMenu->clear(); for (const AZ::EntityId& entityId : selectedEntityIds) { AZ::NamedEntityId namedEntityId(entityId); QAction* actionElement = new QAction(namedEntityId.GetName().data(), m_selectedEntityMenu); QObject::connect(actionElement, &QAction::triggered, [this, entityId]() { OnAssignToEntity(entityId); }); m_selectedEntityMenu->addAction(actionElement); } } void MainWindow::OnAssignToSelectedEntities() { Tracker::ScriptCanvasFileState fileState; AssetTrackerRequestBus::BroadcastResult(fileState, &AssetTrackerRequests::GetFileState, m_activeAssetId); bool isDocumentOpen = false; AzToolsFramework::EditorRequests::Bus::BroadcastResult(isDocumentOpen, &AzToolsFramework::EditorRequests::IsLevelDocumentOpen); if (fileState == Tracker::ScriptCanvasFileState::NEW || fileState == Tracker::ScriptCanvasFileState::SOURCE_REMOVED || !isDocumentOpen) { return; } AzToolsFramework::EntityIdList selectedEntityIds; AzToolsFramework::ToolsApplicationRequestBus::BroadcastResult(selectedEntityIds, &AzToolsFramework::ToolsApplicationRequests::GetSelectedEntities); if (selectedEntityIds.empty()) { AZ::EntityId createdId; AzToolsFramework::EditorRequests::Bus::BroadcastResult(createdId, &AzToolsFramework::EditorRequests::CreateNewEntity, AZ::EntityId()); selectedEntityIds.emplace_back(createdId); } for (const AZ::EntityId& entityId : selectedEntityIds) { AssignGraphToEntityImpl(entityId); } } void MainWindow::OnAssignToEntity(const AZ::EntityId& entityId) { Tracker::ScriptCanvasFileState fileState = GetAssetFileState(m_activeAssetId); if (fileState == Tracker::ScriptCanvasFileState::MODIFIED || fileState == Tracker::ScriptCanvasFileState::UNMODIFIED) { AssignGraphToEntityImpl(entityId); } } ScriptCanvasEditor::Tracker::ScriptCanvasFileState MainWindow::GetAssetFileState(AZ::Data::AssetId assetId) const { Tracker::ScriptCanvasFileState fileState = Tracker::ScriptCanvasFileState::INVALID; AssetTrackerRequestBus::BroadcastResult(fileState, &AssetTrackerRequests::GetFileState, assetId); return fileState; } void MainWindow::AssignGraphToEntityImpl(const AZ::EntityId& entityId) { EditorScriptCanvasComponentRequests* firstRequestBus = nullptr; EditorScriptCanvasComponentRequests* firstEmptyRequestBus = nullptr; EditorScriptCanvasComponentRequestBus::EnumerateHandlersId(entityId, [&firstRequestBus, &firstEmptyRequestBus](EditorScriptCanvasComponentRequests* scriptCanvasRequests) { if (firstRequestBus == nullptr) { firstRequestBus = scriptCanvasRequests; } if (!scriptCanvasRequests->HasAssetId()) { firstEmptyRequestBus = scriptCanvasRequests; } return firstRequestBus == nullptr || firstEmptyRequestBus == nullptr; }); auto usableRequestBus = firstEmptyRequestBus; if (usableRequestBus == nullptr) { usableRequestBus = firstRequestBus; } if (usableRequestBus == nullptr) { AzToolsFramework::EntityCompositionRequestBus::Broadcast(&EntityCompositionRequests::AddComponentsToEntities, AzToolsFramework::EntityIdList{ entityId } , AZ::ComponentTypeList{ azrtti_typeid() }); usableRequestBus = EditorScriptCanvasComponentRequestBus::FindFirstHandler(entityId); } if (usableRequestBus) { ScriptCanvasMemoryAsset::pointer memoryAsset; AssetTrackerRequestBus::BroadcastResult(memoryAsset, &AssetTrackerRequests::GetAsset, m_activeAssetId); if (memoryAsset) { // We need to assign the AssetId for the file asset, not the in-memory asset usableRequestBus->SetAssetId(memoryAsset->GetFileAssetId()); } } } bool MainWindow::HasSystemTickAction(SystemTickActionFlag action) { return (m_systemTickActions & action) != 0; } void MainWindow::RemoveSystemTickAction(SystemTickActionFlag action) { m_systemTickActions = m_systemTickActions & (~action); } void MainWindow::AddSystemTickAction(SystemTickActionFlag action) { if (!AZ::SystemTickBus::Handler::BusIsConnected()) { AZ::SystemTickBus::Handler::BusConnect(); } m_systemTickActions |= action; } void MainWindow::BlockCloseRequests() { m_queueCloseRequest = true; } void MainWindow::UnblockCloseRequests() { if (m_queueCloseRequest) { m_queueCloseRequest = false; if (m_hasQueuedClose) { qobject_cast(parent())->close(); } } } void MainWindow::OpenNextFile() { if (!m_filesToOpen.empty()) { QString nextFile = m_filesToOpen.front(); m_filesToOpen.pop_front(); OpenFile(nextFile.toUtf8().data()); } else { m_errorFilePath.clear(); } } double MainWindow::GetSnapDistance() const { if (m_userSettings) { return m_userSettings->m_snapDistance; } return 10.0; } bool MainWindow::IsGroupDoubleClickCollapseEnabled() const { if (m_userSettings) { return m_userSettings->m_enableGroupDoubleClickCollapse; } return true; } bool MainWindow::IsBookmarkViewportControlEnabled() const { if (m_userSettings) { return m_userSettings->m_allowBookmarkViewpointControl; } return false; } bool MainWindow::IsDragNodeCouplingEnabled() const { if (m_userSettings) { return m_userSettings->m_dragNodeCouplingConfig.m_enabled; } return false; } AZStd::chrono::milliseconds MainWindow::GetDragCouplingTime() const { if (m_userSettings) { return AZStd::chrono::milliseconds(m_userSettings->m_dragNodeCouplingConfig.m_timeMS); } return AZStd::chrono::milliseconds(500); } bool MainWindow::IsDragConnectionSpliceEnabled() const { if (m_userSettings) { return m_userSettings->m_dragNodeSplicingConfig.m_enabled; } return false; } AZStd::chrono::milliseconds MainWindow::GetDragConnectionSpliceTime() const { if (m_userSettings) { return AZStd::chrono::milliseconds(m_userSettings->m_dragNodeSplicingConfig.m_timeMS); } return AZStd::chrono::milliseconds(500); } bool MainWindow::IsDropConnectionSpliceEnabled() const { if (m_userSettings) { return m_userSettings->m_dropNodeSplicingConfig.m_enabled; } return false; } AZStd::chrono::milliseconds MainWindow::GetDropConnectionSpliceTime() const { if (m_userSettings) { return AZStd::chrono::milliseconds(m_userSettings->m_dropNodeSplicingConfig.m_timeMS); } return AZStd::chrono::milliseconds(500); } bool MainWindow::IsNodeNudgingEnabled() const { if (m_userSettings) { return m_userSettings->m_allowNodeNudging; } return false; } bool MainWindow::IsShakeToDespliceEnabled() const { if (m_userSettings) { return m_userSettings->m_shakeDespliceConfig.m_enabled; } return false; } int MainWindow::GetShakesToDesplice() const { if (m_userSettings) { return m_userSettings->m_shakeDespliceConfig.m_shakeCount; } return 3; } float MainWindow::GetMinimumShakePercent() const { if (m_userSettings) { return m_userSettings->m_shakeDespliceConfig.GetMinimumShakeLengthPercent(); } return 0.03f; } float MainWindow::GetShakeDeadZonePercent() const { if (m_userSettings) { return m_userSettings->m_shakeDespliceConfig.GetDeadZonePercent(); } return 0.01f; } float MainWindow::GetShakeStraightnessPercent() const { if (m_userSettings) { return m_userSettings->m_shakeDespliceConfig.GetStraightnessPercent(); } return 0.75f; } AZStd::chrono::milliseconds MainWindow::GetMaximumShakeDuration() const { if (m_userSettings) { return AZStd::chrono::milliseconds(m_userSettings->m_shakeDespliceConfig.m_maximumShakeTimeMS); } return AZStd::chrono::milliseconds(500); } AZStd::chrono::milliseconds MainWindow::GetAlignmentTime() const { if (m_userSettings) { return AZStd::chrono::milliseconds(m_userSettings->m_alignmentTimeMS); } return AZStd::chrono::milliseconds(250); } float MainWindow::GetMaxZoom() const { if (m_userSettings) { return m_userSettings->m_zoomSettings.GetMaxZoom(); } return 2.0f; } float MainWindow::GetEdgePanningPercentage() const { if (m_userSettings) { return m_userSettings->m_edgePanningSettings.GetEdgeScrollPercent(); } return 0.1f; } float MainWindow::GetEdgePanningScrollSpeed() const { if (m_userSettings) { return m_userSettings->m_edgePanningSettings.GetEdgeScrollSpeed(); } return 100.0f; } GraphCanvas::EditorConstructPresets* MainWindow::GetConstructPresets() const { if (m_userSettings) { return &m_userSettings->m_constructPresets; } return nullptr; } const GraphCanvas::ConstructTypePresetBucket* MainWindow::GetConstructTypePresetBucket(GraphCanvas::ConstructType constructType) const { GraphCanvas::EditorConstructPresets* presets = GetConstructPresets(); if (presets) { return presets->FindPresetBucket(constructType); } return nullptr; } GraphCanvas::Styling::ConnectionCurveType MainWindow::GetConnectionCurveType() const { if (m_userSettings) { return m_userSettings->m_stylingSettings.GetConnectionCurveType(); } return GraphCanvas::Styling::ConnectionCurveType::Straight; } GraphCanvas::Styling::ConnectionCurveType MainWindow::GetDataConnectionCurveType() const { if (m_userSettings) { return m_userSettings->m_stylingSettings.GetDataConnectionCurveType(); } return GraphCanvas::Styling::ConnectionCurveType::Straight; } bool MainWindow::AllowNodeDisabling() const { return true; } bool MainWindow::AllowDataReferenceSlots() const { return true; } void MainWindow::CreateUnitTestWidget() { // Dock Widget will be unable to dock with this as it doesn't have a parent. // Going to orphan this as a floating window to more mimic its behavior as a pop-up window rather then a dock widget. m_unitTestDockWidget = aznew UnitTestDockWidget(this); m_unitTestDockWidget->setObjectName("TestManager"); m_unitTestDockWidget->setAllowedAreas(Qt::NoDockWidgetArea); m_unitTestDockWidget->setFloating(true); m_unitTestDockWidget->hide(); // Restore this if we want the dock widget to again be a toggleable thing. //connect(m_unitTestDockWidget, &QDockWidget::visibilityChanged, this, &MainWindow::OnViewVisibilityChanged); } void MainWindow::DisableAssetView(ScriptCanvasMemoryAsset::pointer memoryAsset) { if (memoryAsset->GetView()) { memoryAsset->GetView()->DisableView(); } m_tabBar->setEnabled(false); m_bookmarkDockWidget->setEnabled(false); m_variableDockWidget->setEnabled(false); m_propertyGrid->setEnabled(false); m_editorToolbar->OnViewDisabled(); m_createFunction->setEnabled(false); m_createScriptCanvas->setEnabled(false); UpdateMenuState(false); ui->action_New_Function->setEnabled(false); ui->action_New_Script->setEnabled(false); m_autoSaveTimer.stop(); } void MainWindow::EnableAssetView(ScriptCanvasMemoryAsset::pointer memoryAsset) { if (memoryAsset->GetView()) { memoryAsset->GetView()->EnableView(); } m_tabBar->setEnabled(true); m_bookmarkDockWidget->setEnabled(true); m_variableDockWidget->setEnabled(true); m_propertyGrid->setEnabled(true); m_editorToolbar->OnViewEnabled(); m_createFunction->setEnabled(true); m_createScriptCanvas->setEnabled(true); ui->action_New_Function->setEnabled(true); ui->action_New_Script->setEnabled(true); UpdateMenuState(true); UpdateUndoRedoState(); } #include }