text: /*
   * Copyright (c) Contributors to the Open 3D Engine Project.
   * For complete copyright and license terms please see the LICENSE at the root of this distribution.
   *
   * SPDX-License-Identifier: Apache-2.0 OR MIT
   *
   */
  
  #include <ISystem.h>
  #include <IConsole.h>
  
  #include <Editor/View/Windows/MainWindow.h>
  #include <Editor/GraphCanvas/AutomationIds.h>
  #include <Editor/GraphCanvas/GraphCanvasEditorNotificationBusId.h>
  
  #include <QSplitter>
  #include <QListView>
  #include <QShortcut>
  #include <QKeySequence>
  #include <QKeyEvent>
  #include <QApplication>
  #include <QClipboard>
  #include <QHBoxLayout>
  #include <QVBoxLayout>
  #include <QGraphicsScene>
  #include <QGraphicsView>
  #include <QGraphicsSceneEvent>
  #include <QMimeData>
  #include <QCoreApplication>
  #include <QMessageBox>
  #include <QDir>
  #include <QDirIterator>
  #include <QProgressDialog>
  #include <QToolButton>
  
  #include <ScriptEvents/ScriptEventsAsset.h>
  
  #include <Editor/GraphCanvas/Components/MappingComponent.h>
  #include <Editor/View/Dialogs/UnsavedChangesDialog.h>
  #include <Editor/View/Dialogs/SettingsDialog.h>
  #include <Editor/View/Widgets/ScriptCanvasNodePaletteDockWidget.h>
  #include <Editor/View/Widgets/PropertyGrid.h>
  #include <Editor/View/Widgets/CommandLine.h>
  #include <Editor/View/Widgets/GraphTabBar.h>
  #include <Editor/View/Widgets/CanvasWidget.h>
  #include <Editor/View/Widgets/LogPanel.h>
  #include <Editor/View/Widgets/LoggingPanel/LoggingWindow.h>
  #include <Editor/View/Widgets/MainWindowStatusWidget.h>
  #include <Editor/View/Widgets/NodePalette/NodePaletteModel.h>
  #include <Editor/View/Widgets/StatisticsDialog/ScriptCanvasStatisticsDialog.h>
  #include <Editor/View/Widgets/VariablePanel/VariableDockWidget.h>
  #include <Editor/View/Widgets/UnitTestPanel/UnitTestDockWidget.h>
  #include <Editor/View/Widgets/ValidationPanel/GraphValidationDockWidget.h>
  
  #include <Editor/View/Windows/ui_mainwindow.h>
  
  #include <Editor/Model/EntityMimeDataHandler.h>
  
  #include <Editor/Utilities/RecentAssetPath.h>
  
  #include <Editor/Settings.h>
  #include <Editor/Nodes/NodeCreateUtils.h>
  
  #include <AzCore/Component/ComponentApplicationBus.h>
  #include <AzCore/Component/TransformBus.h>
  #include <AzCore/Serialization/Utils.h>
  #include <AzCore/Asset/AssetManagerBus.h>
  #include <AzCore/Asset/AssetManager.h>
  #include <AzCore/IO/FileIO.h>
  #include <AzCore/std/containers/array.h>
  #include <AzCore/std/containers/set.h>
  #include <AzCore/std/smart_ptr/make_shared.h>
  #include <AzCore/Component/EntityUtils.h>
  #include <AzCore/Serialization/IdUtils.h>
  #include <AzCore/Settings/SettingsRegistryMergeUtils.h>
  #include <AzCore/Math/Color.h>
  #include <AzCore/Math/Vector2.h>
  #include <AzCore/Math/Vector3.h>
  #include <AzCore/Math/Vector4.h>
  
  #include <AzFramework/Asset/AssetCatalog.h>
  #include <AzFramework/StringFunc/StringFunc.h>
  
  #include <AzToolsFramework/AssetBrowser/AssetBrowserBus.h>
  #include <AzToolsFramework/AssetBrowser/AssetBrowserModel.h>
  #include <AzToolsFramework/API/EditorAssetSystemAPI.h>
  #include <AzToolsFramework/API/EntityCompositionRequestBus.h>
  #include <AzToolsFramework/API/ToolsApplicationAPI.h>
  #include <AzToolsFramework/ToolsComponents/EditorEntityIdContainer.h>
  #include <AzToolsFramework/ToolsComponents/GenericComponentWrapper.h>
  #include <AzToolsFramework/ToolsComponents/ToolsAssetCatalogBus.h>
  #include <AzToolsFramework/UI/UICore/WidgetHelpers.h>
  
  #include <AzQtComponents/Components/Widgets/FileDialog.h>
  #include <AzQtComponents/Components/Widgets/TabWidget.h>
  
  #include <ScriptCanvas/Core/ScriptCanvasBus.h>
  #include <ScriptCanvas/Core/Graph.h>
  #include <ScriptCanvas/Assets/ScriptCanvasAsset.h>
  #include <ScriptCanvas/Assets/ScriptCanvasAssetHandler.h>
  #include <ScriptCanvas/Libraries/Core/FunctionDefinitionNode.h>
  
  #include <GraphCanvas/GraphCanvasBus.h>
  #include <GraphCanvas/Components/Nodes/NodeBus.h>
  #include <GraphCanvas/Components/GeometryBus.h>
  #include <GraphCanvas/Components/GridBus.h>
  #include <GraphCanvas/Components/ViewBus.h>
  #include <GraphCanvas/Components/VisualBus.h>
  #include <GraphCanvas/Components/MimeDataHandlerBus.h>
  #include <GraphCanvas/Components/Connections/ConnectionBus.h>
  
  #include <GraphCanvas/Styling/Parser.h>
  #include <GraphCanvas/Styling/Style.h>
  #include <GraphCanvas/Widgets/AssetEditorToolbar/AssetEditorToolbar.h>
  #include <GraphCanvas/Widgets/Bookmarks/BookmarkDockWidget.h>
  #include <GraphCanvas/Widgets/GraphCanvasMimeContainer.h>
  #include <GraphCanvas/Widgets/MiniMapGraphicsView/MiniMapGraphicsView.h>
  #include <GraphCanvas/Widgets/GraphCanvasEditor/GraphCanvasEditorCentralWidget.h>
  #include <GraphCanvas/Widgets/GraphCanvasGraphicsView/GraphCanvasGraphicsView.h>
  #include <GraphCanvas/Widgets/EditorContextMenu/EditorContextMenu.h>
  #include <GraphCanvas/Widgets/EditorContextMenu/ContextMenus/BookmarkContextMenu.h>
  #include <GraphCanvas/Widgets/EditorContextMenu/ContextMenus/CollapsedNodeGroupContextMenu.h>
  #include <GraphCanvas/Widgets/EditorContextMenu/ContextMenus/ConnectionContextMenu.h>
  #include <GraphCanvas/Widgets/EditorContextMenu/ContextMenus/NodeGroupContextMenu.h>
  #include <GraphCanvas/Widgets/EditorContextMenu/ContextMenus/NodeContextMenu.h>
  #include <GraphCanvas/Widgets/EditorContextMenu/ContextMenus/CommentContextMenu.h>
  #include <GraphCanvas/Widgets/EditorContextMenu/ContextMenus/SceneContextMenu.h>
  #include <GraphCanvas/Widgets/EditorContextMenu/ContextMenus/SlotContextMenu.h>
  #include <GraphCanvas/Utils/ConversionUtils.h>
  #include <GraphCanvas/Utils/NodeNudgingController.h>
  #include <GraphCanvas/Types/ConstructPresets.h>
  
  #include <Editor/View/Windows/ScriptCanvasContextMenus.h>
  #include <Editor/View/Windows/EBusHandlerActionMenu.h>
  #include <Editor/View/Widgets/NodePalette/CreateNodeMimeEvent.h>
  #include <Editor/View/Widgets/NodePalette/EBusNodePaletteTreeItemTypes.h>
  #include <Editor/View/Windows/Tools/UpgradeTool/UpgradeHelper.h>
  #include <ScriptCanvas/Assets/ScriptCanvasFileHandling.h>
  
  #include <Editor/View/Widgets/VariablePanel/SlotTypeSelectorWidget.h>
  
  // Save Format Conversion
  #include <AzCore/Component/EntityUtils.h>
  #include <Editor/Include/ScriptCanvas/Components/EditorGraph.h>
  ////
  
  #include <Editor/Assets/ScriptCanvasAssetHelpers.h>
  #include <Editor/Assets/ScriptCanvasAssetTracker.h>
  #include <Editor/Assets/ScriptCanvasAssetTrackerDefinitions.h>
  
  #include <ScriptCanvas/Asset/AssetDescription.h>
  #include <ScriptCanvas/Components/EditorScriptCanvasComponent.h>
  #include <ScriptCanvas/Assets/ScriptCanvasAsset.h>
  
  #include <Editor/QtMetaTypes.h>
  #include <GraphCanvas/Components/SceneBus.h>
  
  #include <Editor/LyViewPaneNames.h>
  
  namespace ScriptCanvasEditor
  {
      using namespace AzToolsFramework;
  
      namespace
      {
          template <typename T>
          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<typename MimeDataDelegateHandler, typename ... ComponentArgs>
          AZ::EntityId CreateMimeDataDelegate(ComponentArgs... componentArgs)
          {
              AZ::Entity* mimeDelegateEntity = aznew AZ::Entity("MimeData Delegate");
  
              mimeDelegateEntity->CreateComponent<MimeDataDelegateHandler>(AZStd::forward<ComponentArgs>(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<EditorSettings::EditorWorkspace>(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<EditorSettings::EditorWorkspace::WorkspaceAssetSaveData> activeAssets;
              ScriptCanvasEditor::SourceHandle focusedAssetId = tabBar->FindAssetId(tabBar->currentIndex());
  
              if (m_rememberOpenCanvases)
              {
                  activeAssets.reserve(tabBar->count());
  
                  for (int i = 0; i < tabBar->count(); ++i)
                  {
                      ScriptCanvasEditor::SourceHandle assetId = tabBar->FindAssetId(i);
  
                      const Tracker::ScriptCanvasFileState& fileState = m_mainWindow->GetAssetFileState(assetId);
  
                      if (fileState == Tracker::ScriptCanvasFileState::MODIFIED || fileState == Tracker::ScriptCanvasFileState::UNMODIFIED)
                      {
                          ScriptCanvasEditor::SourceHandle sourceId = GetSourceAssetId(assetId);
                          if (sourceId.IsGraphValid())
                          {
                              EditorSettings::EditorWorkspace::WorkspaceAssetSaveData assetSaveData;
                              assetSaveData.m_assetId = sourceId;
  
                              activeAssets.push_back(assetSaveData);
                          }
                      }
                      else if (assetId.AnyEquals(focusedAssetId))
                      {
                          focusedAssetId.Clear();
                      }
                  }
  
                  // The assetId needs to be the file AssetId to restore the workspace
                  if (focusedAssetId.IsGraphValid())
                  {
                      focusedAssetId = GetSourceAssetId(focusedAssetId);
                  }
  
                  // If our currently focused asset won't be restored, just show the first element.
                  if (!focusedAssetId.IsGraphValid())
                  {
                      if (!activeAssets.empty())
                      {
                          focusedAssetId = activeAssets.front().m_assetId;
                      }
                  }
              }
  
              workspace->Clear();
              if (!activeAssets.empty())
              {
                  workspace->ConfigureActiveAssets(focusedAssetId, activeAssets);
              }
          }
      }
  
      // Workspace
  
      void Workspace::Restore()
      {
  
          auto workspace = AZ::UserSettings::Find<EditorSettings::EditorWorkspace>(AZ_CRC("ScriptCanvasEditorWindowState", 0x10c47d36), AZ::UserSettings::CT_LOCAL);
          if (workspace)
          {
              workspace->Restore(qobject_cast<QMainWindow*>(m_mainWindow));
  
              if (m_rememberOpenCanvases)
              {
                  for (const auto& assetSaveData : workspace->GetActiveAssetData())
                  {
                      m_loadingAssets.push_back(assetSaveData.m_assetId);
                  }
  
                  if (m_loadingAssets.empty())
                  {
                      m_mainWindow->OnWorkspaceRestoreEnd(ScriptCanvasEditor::SourceHandle());
                  }
                  else
                  {
                      m_mainWindow->OnWorkspaceRestoreStart();
                  }
  
                  m_queuedAssetFocus = workspace->GetFocusedAssetId();
  
                  // #sc-asset-editor
                  //for (const auto& assetSaveData : workspace->GetActiveAssetData())
                  {
                      // load all the files
  //                     AssetTrackerNotificationBus::MultiHandler::BusConnect(assetSaveData.m_assetId);
  // 
  //                     Callbacks::OnAssetReadyCallback onAssetReady = [this, assetSaveData](ScriptCanvasMemoryAsset& asset)
  //                     {
  //                         // If we get an error callback. Just remove it from out active lists.
  //                         if (asset.IsSourceInError())
  //                         {
  //                             if (assetSaveData.m_assetId == m_queuedAssetFocus)
  //                             {
  //                                 m_queuedAssetFocus = ScriptCanvasEditor::SourceHandle();
  //                             }
  // 
  //                             SignalAssetComplete(asset.GetFileAssetId());
  //                         }
  //                     };
  // 
  //                     bool loadedFile = true;
  //                     AssetTrackerRequestBus::BroadcastResult(loadedFile, &AssetTrackerRequests::Load, assetSaveData.m_assetId, assetSaveData.m_assetType, onAssetReady);
  // 
  //                     if (!loadedFile)
  //                     {
  //                         if (assetSaveData.m_assetId == m_queuedAssetFocus)
  //                         {
  //                             m_queuedAssetFocus = ScriptCanvasEditor::SourceHandle();
  //                         }
  // 
  //                         SignalAssetComplete(assetSaveData.m_assetId);
  //                     }
                  }
              }
              else
              {
                  m_mainWindow->OnWorkspaceRestoreEnd(ScriptCanvasEditor::SourceHandle());
              }
          }
      }
  
      void Workspace::OnAssetReady(const ScriptCanvasMemoryAsset::pointer memoryAsset)
      {
          // open the file in the main window
  //         const ScriptCanvasEditor::SourceHandle& fileAssetId = memoryAsset->GetFileAssetId();
  // 
  //         if (AssetTrackerNotificationBus::MultiHandler::BusIsConnectedId(fileAssetId))
  //         {
  //             AssetTrackerNotificationBus::MultiHandler::BusDisconnect(fileAssetId);
  // 
  //             m_mainWindow->OpenScriptCanvasAsset(*memoryAsset);
  // 
  //             SignalAssetComplete(fileAssetId);
  //         }
      }
  
      void Workspace::SignalAssetComplete(const ScriptCanvasEditor::SourceHandle& /*fileAssetId*/)
      {
          // When we are done loading all assets we can safely set the focus to the recorded asset
  //         auto it = AZStd::find(m_loadingAssets.begin(), m_loadingAssets.end(), fileAssetId);
  //         if (it != m_loadingAssets.end())
  //         {
  //             m_loadingAssets.erase(it);
  //         }
  // 
  //         if (m_loadingAssets.empty())
  //         {
  //             m_mainWindow->OnWorkspaceRestoreEnd(m_queuedAssetFocus);
  //             m_queuedAssetFocus.SetInvalid();
  //         }
      }
  
      ScriptCanvasEditor::SourceHandle Workspace::GetSourceAssetId(const ScriptCanvasEditor::SourceHandle& memoryAssetId) const
      {
          return memoryAssetId;
      }
  
      ////////////////
      // 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_styleManager(ScriptCanvasEditor::AssetEditorId, "ScriptCanvas/StyleSheet/graphcanvas_style.json")
      {
          AZ_PROFILE_FUNCTION(ScriptCanvas);
  
          VariablePaletteRequestBus::Handler::BusConnect();
          GraphCanvas::AssetEditorAutomationRequestBus::Handler::BusConnect(ScriptCanvasEditor::AssetEditorId);
  
          AZStd::array<char, AZ::IO::MaxPathLength> unresolvedPath;
          AZ::IO::FileIOBase::GetInstance()->ResolvePath("@products@/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 ScriptCanvasAssetBrowserModel(this);
  
              AzToolsFramework::AssetBrowser::AssetGroupFilter* scriptEventAssetFilter = new AzToolsFramework::AssetBrowser::AssetGroupFilter();
              scriptEventAssetFilter->SetAssetGroup(ScriptEvents::ScriptEventsAsset::GetGroup());
              scriptEventAssetFilter->SetFilterPropagation(AzToolsFramework::AssetBrowser::AssetBrowserEntryFilter::PropagateDirection::Down);
  
              m_scriptEventsAssetModel->setSourceModel(assetBrowserModel);
          }
  
          {
              m_scriptCanvasAssetModel = new ScriptCanvasAssetBrowserModel(this);
  
              AzToolsFramework::AssetBrowser::AssetGroupFilter* scriptCanvasAssetFilter = new AzToolsFramework::AssetBrowser::AssetGroupFilter();
              scriptCanvasAssetFilter->SetAssetGroup(ScriptCanvasAsset::Description::GetGroup(azrtti_typeid<ScriptCanvasAsset>()));
              scriptCanvasAssetFilter->SetFilterPropagation(AzToolsFramework::AssetBrowser::AssetBrowserEntryFilter::PropagateDirection::Down);
  
              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(":/ScriptCanvasEditorResources/Resources/create_graph.png"));
              m_createScriptCanvas->setToolTip("Creates a new Script Canvas Graph");
  
              QObject::connect(m_createScriptCanvas, &QToolButton::clicked, this, &MainWindow::OnFileNew);
  
              m_editorToolbar->AddCreationAction(m_createScriptCanvas);
              RegisterObject(AutomationIds::CreateScriptCanvasButton, m_createScriptCanvas);
  
          }
  
          {
              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_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");
  
              RegisterObject(AutomationIds::NodePaletteDockWidget, m_nodePalette);
              RegisterObject(AutomationIds::NodePaletteWidget, m_nodePalette->GetNodePaletteWidget());
          }
  
          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_activeGraph.IsGraphValid())
              {
                  m_queuedFocusOverride = m_activeGraph;
              }
  
              m_workspace->Restore();
              m_workspace->Save();
          });
  
          m_entityMimeDelegateId = CreateMimeDataDelegate<ScriptCanvasEditor::EntityMimeDataHandler>();
  
          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();
          AzToolsFramework::AssetSystemBus::Handler::BusConnect();
          ScriptCanvas::ScriptCanvasSettingsRequestBus::Handler::BusConnect();
          AZ::SystemTickBus::Handler::BusConnect();
  
          UINotificationBus::Broadcast(&UINotifications::MainWindowCreationEvent, this);
  
          m_userSettings = AZ::UserSettings::CreateFind<EditorSettings::ScriptCanvasEditorSettings>(AZ_CRC("ScriptCanvasPreviewSettings", 0x1c5a2965), AZ::UserSettings::CT_LOCAL);
          if (m_userSettings)
          {
              m_allowAutoSave = m_userSettings->m_autoSaveConfig.m_enabled;
              m_showUpgradeTool = m_userSettings->m_showUpgradeDialog;
              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();
          GraphCanvas::AssetEditorAutomationRequestBus::Handler::BusDisconnect();
          ScriptCanvas::ScriptCanvasSettingsRequestBus::Handler::BusDisconnect();
          AzToolsFramework::AssetSystemBus::Handler::BusDisconnect();
  
          Clear();
  
          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->action_Open, &QAction::triggered, this, &MainWindow::OnFileOpen);
          ui->action_Open->setShortcut(QKeySequence(QKeySequence::Open));
  
          connect(ui->action_UpgradeTool, &QAction::triggered, this, &MainWindow::RunUpgradeTool);
          ui->action_UpgradeTool->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(ScriptCanvasEditor::SourceHandle assetId)
      {
          AZ::EntityId graphId;
          if (assetId.IsGraphValid())
          {
              EditorGraphRequestBus::EventResult(graphId, assetId.Get()->GetScriptCanvasId(), &EditorGraphRequests::GetGraphCanvasGraphId);
          }
  
          m_autoSaveTimer.stop();
  
          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);
  
          // The paste action refreshes based on the scene's mimetype
          RefreshPasteAction();
  
          bool enabled = false;
  
          if (graphId.IsValid())
          {
              GraphCanvas::ViewId viewId;
              GraphCanvas::SceneRequestBus::EventResult(viewId, graphId, &GraphCanvas::SceneRequests::GetViewId);
  
              if (viewId.IsValid())
              {
                  GraphCanvas::ViewNotificationBus::Handler::BusDisconnect();
                  GraphCanvas::ViewNotificationBus::Handler::BusConnect(viewId);
  
                  enabled = true;
              }
              else
              {
                  AZ_Error("ScriptCanvasEditor", viewId.IsValid(), "SceneRequest must return a valid ViewId");
              }
          }
  
          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)
      {
          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;
          }
  
          for (int tabCounter = 0; tabCounter < m_tabBar->count(); ++tabCounter)
          {
              ScriptCanvasEditor::SourceHandle assetId = m_tabBar->FindAssetId(tabCounter);
  
              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)
              {
                  SaveAssetImpl(assetId, Save::InPlace);
                  event->ignore();
                  return;
              }
              else if (shouldSaveResults == UnsavedChangesOptions::CANCEL_WITHOUT_SAVING)
              {
                  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();
          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_activeGraph);
  
          m_propertyGrid->ClearSelection();
          GeneralEditorNotificationBus::Event(GetActiveScriptCanvasId(), &GeneralEditorNotifications::OnUndoRedoEnd);
      }
  
      void MainWindow::TriggerRedo()
      {
          GeneralEditorNotificationBus::Event(GetActiveScriptCanvasId(), &GeneralEditorNotifications::OnUndoRedoBegin);
          DequeuePropertyGridUpdate();
  
          UndoRequestBus::Event(GetActiveScriptCanvasId(), &UndoRequests::Redo);
          SignalSceneDirty(m_activeGraph);
  
          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);
      }
  
      bool MainWindow::ShowSlotTypeSelector(ScriptCanvas::Slot* slot, const QPoint& scenePosition, VariablePaletteRequests::SlotSetup& outSetup)
      {
          AZ_Assert(slot, "A valid slot must be provided");
          if (slot)
          {
              m_slotTypeSelector = new SlotTypeSelectorWidget(GetActiveScriptCanvasId(), this); // Recreate the widget every time because of https://bugreports.qt.io/browse/QTBUG-76509
              m_slotTypeSelector->PopulateVariablePalette(m_variablePaletteTypes);
  
              // Only set the slot name if the user has already configured this slot, so if they are creating
              // for the first time they will see the placeholder text instead
              bool isValidVariableType = false;
              VariablePaletteRequestBus::BroadcastResult(isValidVariableType, &VariablePaletteRequests::IsValidVariableType, slot->GetDataType());
              if (isValidVariableType)
              {
                  m_slotTypeSelector->SetSlotName(slot->GetName());
              }
  
              m_slotTypeSelector->move(scenePosition);
              m_slotTypeSelector->setEnabled(true);
              m_slotTypeSelector->update();
  
              if (m_slotTypeSelector->exec() != QDialog::Rejected)
              {
                  outSetup.m_name = m_slotTypeSelector->GetSlotName();
                  outSetup.m_type = m_slotTypeSelector->GetSelectedType();
              }
              else
              {
                  delete m_slotTypeSelector;
  
                  return false;
              }
  
              delete m_slotTypeSelector;
          }
  
          return true;
      }
  
  
      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");
              UpdateFileState(m_activeGraph, Tracker::ScriptCanvasFileState::MODIFIED);
          }
  
          const bool forceTimer = true;
          RestartAutoTimerSave(forceTimer);
      }
  
      void MainWindow::SourceFileChanged
          ( [[maybe_unused]] AZStd::string relativePath
          , AZStd::string scanFolder
          , [[maybe_unused]] AZ::Uuid fileAssetId)
      {
          auto handle = CompleteDescription(SourceHandle(nullptr, fileAssetId, {}));
          if (handle)
          {
              if (!IsRecentSave(*handle))
              {
                  UpdateFileState(*handle, Tracker::ScriptCanvasFileState::MODIFIED);
              }
              else
              {
                  AZ_TracePrintf
                      ( "ScriptCanvas"
                      , "Ignoring source file modification notification (possibly external), as a it was recently saved by the editor: %s"
                      , relativePath.c_str());
              }
          }
      }
  
      void MainWindow::SourceFileRemoved
          ( AZStd::string relativePath
          , [[maybe_unused]] AZStd::string scanFolder
          , AZ::Uuid fileAssetId)
      {
          SourceHandle handle(nullptr, fileAssetId, relativePath);
          {
              if (!IsRecentSave(handle))
              {
                  UpdateFileState(handle, Tracker::ScriptCanvasFileState::SOURCE_REMOVED);
              }
              else
              {
                  AZ_TracePrintf
                      ( "ScriptCanvas"
                      , "Ignoring source file removed notification (possibly external), as a it was recently saved by the editor: %s"
                      , relativePath.c_str());
              }
          }
      }
  
      void MainWindow::SignalSceneDirty(ScriptCanvasEditor::SourceHandle assetId)
      {
          UpdateFileState(assetId, Tracker::ScriptCanvasFileState::MODIFIED);
      }
  
      void MainWindow::PushPreventUndoStateUpdate()
      {
          ++m_preventUndoStateUpdateCount;
      }
  
      void MainWindow::PopPreventUndoStateUpdate()
      {
          if (m_preventUndoStateUpdateCount > 0)
          {
              --m_preventUndoStateUpdateCount;
          }
      }
  
      void MainWindow::ClearPreventUndoStateUpdate()
      {
          m_preventUndoStateUpdateCount = 0;
      }
  
      void MainWindow::UpdateFileState(const ScriptCanvasEditor::SourceHandle& assetId, Tracker::ScriptCanvasFileState fileState)
      {
          m_tabBar->UpdateFileState(assetId, fileState);
      }
  
      AZ::Outcome<int, AZStd::string> MainWindow::OpenScriptCanvasAssetId(const ScriptCanvasEditor::SourceHandle& fileAssetId, Tracker::ScriptCanvasFileState fileState)
      {
          if (fileAssetId.Id().IsNull())
          {
              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);
          }
  
          auto loadedGraph = LoadFromFile(fileAssetId.Path().c_str());
          if (!loadedGraph.IsSuccess())
          {
              return AZ::Failure(AZStd::string("Failed to load graph at %s", fileAssetId.Path().c_str()));
          }
  
          outTabIndex = CreateAssetTab(loadedGraph.GetValue(), fileState);
  
          if (!m_isRestoringWorkspace)
          {
              SetActiveAsset(loadedGraph.GetValue());
          }
  
          if (outTabIndex >= 0)
          {
              AddRecentFile(loadedGraph.GetValue().Path().c_str());
              OpenScriptCanvasAssetImplementation(loadedGraph.GetValue(), fileState);
              return AZ::Success(outTabIndex);
          }
          else
          {
              return AZ::Failure(AZStd::string("Specified asset is in an error state and cannot be properly displayed."));
          }
      }
  
      AZ::Outcome<int, AZStd::string> MainWindow::OpenScriptCanvasAssetImplementation(const SourceHandle& scriptCanvasAsset, Tracker::ScriptCanvasFileState fileState, int tabIndex)
      {
          const ScriptCanvasEditor::SourceHandle& fileAssetId = scriptCanvasAsset;
          if (!fileAssetId.IsDescriptionValid())
          {
              return AZ::Failure(AZStd::string("Unable to open asset with invalid asset id"));
          }
  
          if (!scriptCanvasAsset.IsDescriptionValid())
          {
              if (!m_isRestoringWorkspace)
              {
                  AZStd::string errorPath = scriptCanvasAsset.Path().c_str();
  
                  if (errorPath.empty())
                  {
                      errorPath = m_errorFilePath;
                  }
  
                  if (m_queuedFocusOverride.AnyEquals(fileAssetId))
                  {
                      m_queuedFocusOverride = fileAssetId;
                  }
  
                  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, fileState, 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"
                  , fileAssetId.ToString().c_str()));
          }
  
          AZStd::string assetPath = scriptCanvasAsset.Path().c_str();
          if (!assetPath.empty() && !m_loadingNewlySavedFile)
          {
              AddRecentFile(assetPath.c_str());
          }
  
          GraphCanvas::GraphId graphCanvasGraphId = GetGraphCanvasGraphId(scriptCanvasAsset.Get()->GetScriptCanvasId());
          GraphCanvas::AssetEditorNotificationBus::Event(ScriptCanvasEditor::AssetEditorId, &GraphCanvas::AssetEditorNotifications::OnGraphLoaded, graphCanvasGraphId);
          GeneralAssetNotificationBus::Event(fileAssetId, &GeneralAssetNotifications::OnAssetVisualized);
          return AZ::Success(outTabIndex);
      }
  
      AZ::Outcome<int, AZStd::string> MainWindow::OpenScriptCanvasAsset(ScriptCanvasEditor::SourceHandle scriptCanvasAssetId, Tracker::ScriptCanvasFileState fileState, int tabIndex)
      {
          if (scriptCanvasAssetId.IsGraphValid())
          {
              return OpenScriptCanvasAssetImplementation(scriptCanvasAssetId, fileState, tabIndex);
          }
          else
          {
              return OpenScriptCanvasAssetId(scriptCanvasAssetId, fileState);
          }
      }
  
      int MainWindow::CreateAssetTab(const ScriptCanvasEditor::SourceHandle& assetId, Tracker::ScriptCanvasFileState fileState, int tabIndex)
      {
          return m_tabBar->InsertGraphTab(tabIndex, assetId, fileState);
      }
  
      void MainWindow::RemoveScriptCanvasAsset(const ScriptCanvasEditor::SourceHandle& assetId)
      {
          AssetHelpers::PrintInfo("RemoveScriptCanvasAsset : %s", assetId.ToString().c_str());
          m_assetCreationRequests.erase(assetId);
          GeneralAssetNotificationBus::Event(assetId, &GeneralAssetNotifications::OnAssetUnloaded);
  
          if (assetId.IsGraphValid())
          {
              // Disconnect scene and asset editor buses
              GraphCanvas::SceneNotificationBus::MultiHandler::BusDisconnect(assetId.Get()->GetScriptCanvasId());
              GraphCanvas::AssetEditorNotificationBus::Event(ScriptCanvasEditor::AssetEditorId
                  , &GraphCanvas::AssetEditorNotifications::OnGraphUnloaded, assetId.Get()->GetGraphCanvasGraphId());
          }
  
          int tabIndex = m_tabBar->FindTab(assetId);
          QVariant tabdata = m_tabBar->tabData(tabIndex);
          if (tabdata.isValid())
          {
              auto tabAssetId = tabdata.value<Widget::GraphTabMetadata>();
              SetActiveAsset(tabAssetId.m_assetId);
          }
      }
  
      int MainWindow::CloseScriptCanvasAsset(const ScriptCanvasEditor::SourceHandle& assetId)
      {
          int tabIndex = -1;
          if (IsTabOpen(assetId, tabIndex))
          {
              OnTabCloseRequest(tabIndex);
          }
  
          return tabIndex;
      }
  
      bool MainWindow::CreateScriptCanvasAssetFor(const TypeDefs::EntityComponentId& requestingEntityId)
      {
          for (auto createdAssetPair : m_assetCreationRequests)
          {
              if (createdAssetPair.second == requestingEntityId)
              {
                  return OpenScriptCanvasAssetId(createdAssetPair.first, Tracker::ScriptCanvasFileState::NEW).IsSuccess();
              }
          }
  
          ScriptCanvasEditor::SourceHandle previousAssetId = m_activeGraph;
  
          OnFileNew();
  
          bool createdNewAsset = !(m_activeGraph.AnyEquals(previousAssetId));
  
          if (createdNewAsset)
          {
              m_assetCreationRequests[m_activeGraph] = requestingEntityId;
          }
  
          if (m_isRestoringWorkspace)
          {
              m_queuedFocusOverride = m_activeGraph;
          }
  
          return createdNewAsset;
      }
  
      bool MainWindow::IsScriptCanvasAssetOpen(const ScriptCanvasEditor::SourceHandle& assetId) const
      {
          return m_tabBar->FindTab(assetId) >= 0;
      }
  
      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::OpenFile(const char* fullPath)
      {
          auto tabIndex = m_tabBar->FindTabByPath(fullPath);
          if (tabIndex.IsGraphValid())
          {
              SetActiveAsset(tabIndex);
              return;
          }
  
          AZStd::string watchFolder;
          AZ::Data::AssetInfo assetInfo;
          bool sourceInfoFound{};
          AzToolsFramework::AssetSystemRequestBus::BroadcastResult
              ( sourceInfoFound
              , &AzToolsFramework::AssetSystemRequestBus::Events::GetSourceInfoBySourcePath, fullPath, assetInfo, watchFolder);
  
          if (!sourceInfoFound)
          {
              QMessageBox::warning(this, "Invalid Source Asset", QString("'%1' is not a valid asset path.").arg(fullPath), QMessageBox::Ok);
              m_errorFilePath = fullPath;
              AZ_Warning("ScriptCanvas", false, "Unable to open file as a ScriptCanvas graph: %s", fullPath);
              return;
          }
  
          AZ::Outcome<ScriptCanvasEditor::SourceHandle, AZStd::string> outcome = LoadFromFile(fullPath);
          if (!outcome.IsSuccess())
          {
              QMessageBox::warning(this, "Invalid Source File", QString("'%1' failed to load properly.").arg(fullPath), QMessageBox::Ok);
              m_errorFilePath = fullPath;
              AZ_Warning("ScriptCanvas", false, "Unable to open file as a ScriptCanvas graph: %s", fullPath);
              return;
          }
  
          m_errorFilePath.clear();
          auto activeGraph = ScriptCanvasEditor::SourceHandle(outcome.TakeValue(), assetInfo.m_assetId.m_guid, fullPath);
          
          auto openOutcome = OpenScriptCanvasAsset(activeGraph, Tracker::ScriptCanvasFileState::UNMODIFIED);
          if (openOutcome)
          {
              RunGraphValidation(false);
              SetActiveAsset(activeGraph);
              SetRecentAssetId(activeGraph);
          }
          else
          {
              AZ_Warning("Script Canvas", openOutcome, "%s", openOutcome.GetError().data());
          }
      }
  
      GraphCanvas::Endpoint MainWindow::HandleProposedConnection(const GraphCanvas::GraphId&, const GraphCanvas::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<AZ::EntityId> 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<EndpointSelectionAction*>(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()
      {
          static int scriptCanvasEditorDefaultNewNameCount = 0;
  
          AZStd::string assetPath;
  
          for (;;)
          {
              ScriptCanvasAssetDescription description;
              AZStd::string newAssetName = AZStd::string::format(description.GetAssetNamePatternImpl(), ++scriptCanvasEditorDefaultNewNameCount);
              
              AZStd::array<char, AZ::IO::MaxPathLength> assetRootArray;
              if (!AZ::IO::FileIOBase::GetInstance()->ResolvePath(description.GetSuggestedSavePathImpl()
                  , assetRootArray.data(), assetRootArray.size()))
              {
                  AZ_ErrorOnce("Script Canvas", false, "Unable to resolve @projectroot@ path");
              }
  
              AzFramework::StringFunc::Path::Join(assetRootArray.data(), (newAssetName + description.GetExtensionImpl()).data(), assetPath);
              AZ::Data::AssetInfo assetInfo;
  
              if (!AssetHelpers::GetAssetInfo(assetPath, assetInfo))
              {
                  break;
              }
          }
  
          auto createOutcome = CreateScriptCanvasAsset(assetPath);
          if (!createOutcome.IsSuccess())
          {
              AZ_Warning("Script Canvas", createOutcome, "%s", createOutcome.GetError().data());
          }
      }
  
      int MainWindow::InsertTabForAsset(AZStd::string_view assetPath, ScriptCanvasEditor::SourceHandle assetId, int tabIndex)
      {
          int outTabIndex = -1;
  
          {
              // Insert tab block
              AZStd::string tabName;
              AzFramework::StringFunc::Path::GetFileName(assetPath.data(), tabName);
              m_tabBar->InsertGraphTab(tabIndex, assetId, Tracker::ScriptCanvasFileState::NEW);
  
              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", assetId.ToString().c_str()).c_str());
                  return -1;
              }
  
              m_tabBar->setTabToolTip(outTabIndex, assetPath.data());
          }
  
          return outTabIndex;
      }
  
      void MainWindow::UpdateUndoCache(ScriptCanvasEditor::SourceHandle)
      {
          UndoCache* undoCache = nullptr;
          UndoRequestBus::EventResult(undoCache, GetActiveScriptCanvasId(), &UndoRequests::GetSceneUndoCache);
          if (undoCache)
          {
              undoCache->UpdateCache(GetActiveScriptCanvasId());
          }
      }
  
      AZ::Outcome<int, AZStd::string> MainWindow::CreateScriptCanvasAsset(AZStd::string_view assetPath, int tabIndex)
      {
          int outTabIndex = -1;
  
          ScriptCanvas::DataPtr graph = Graph::Create();
          AZ::Uuid assetId = AZ::Uuid::CreateRandom();
          ScriptCanvasEditor::SourceHandle handle = ScriptCanvasEditor::SourceHandle(graph, assetId, assetPath);
  
          outTabIndex = InsertTabForAsset(assetPath, handle, tabIndex);
          
          if (outTabIndex == -1)
          {
              return AZ::Failure(AZStd::string::format("Script Canvas Asset %.*s is not open in a tab"
                  , static_cast<int>(assetPath.size()), assetPath.data()));
          }
  
          SetActiveAsset(handle);
          PushPreventUndoStateUpdate();
  
          AZ::EntityId scriptCanvasEntityId = graph->GetGraph()->GetScriptCanvasId();
          GraphCanvas::SceneNotificationBus::MultiHandler::BusDisconnect(scriptCanvasEntityId);
          AZ::EntityId graphCanvasGraphId = GetGraphCanvasGraphId(scriptCanvasEntityId);
  
          GraphCanvas::AssetEditorNotificationBus::Event(ScriptCanvasEditor::AssetEditorId
              , &GraphCanvas::AssetEditorNotifications::OnGraphRefreshed, graphCanvasGraphId, graphCanvasGraphId);
  
          if (IsTabOpen(handle, tabIndex))
          {
              AZStd::string tabName;
              AzFramework::StringFunc::Path::GetFileName(assetPath.data(), tabName);
              m_tabBar->setTabToolTip(tabIndex, assetPath.data());
              m_tabBar->SetTabText(tabIndex, tabName.c_str(), Tracker::ScriptCanvasFileState::NEW);
          }
  
          if (graphCanvasGraphId.IsValid())
          {
              GraphCanvas::SceneNotificationBus::MultiHandler::BusConnect(graphCanvasGraphId);
              GraphCanvas::SceneMimeDelegateRequestBus::Event(graphCanvasGraphId, &GraphCanvas::SceneMimeDelegateRequests::AddDelegate, m_entityMimeDelegateId);
  
              GraphCanvas::SceneRequestBus::Event(graphCanvasGraphId, &GraphCanvas::SceneRequests::SetMimeType, Widget::NodePaletteDockWidget::GetMimeType());
              GraphCanvas::SceneMemberNotificationBus::Event(graphCanvasGraphId, &GraphCanvas::SceneMemberNotifications::OnSceneReady);
          }
  
          if (IsTabOpen(handle, outTabIndex))
          {
              RefreshActiveAsset();
          }
  
          PopPreventUndoStateUpdate();
  
  
          GraphCanvas::AssetEditorNotificationBus::Event(ScriptCanvasEditor::AssetEditorId
              , &GraphCanvas::AssetEditorNotifications::OnGraphLoaded, graphCanvasGraphId);
  
          return AZ::Success(outTabIndex);
      }
  
      bool MainWindow::OnFileSave()
      {
          return SaveAssetImpl(m_activeGraph, Save::InPlace);
      }
  
      bool MainWindow::OnFileSaveAs()
      {
          return SaveAssetImpl(m_activeGraph, Save::As);
      }
  
      bool MainWindow::SaveAssetImpl(const ScriptCanvasEditor::SourceHandle& inMemoryAssetId, Save save)
      {
          if (!inMemoryAssetId.IsGraphValid())
          {
              return false;
          }
  
          if (!m_activeGraph.AnyEquals(inMemoryAssetId))
          {
              OnChangeActiveGraphTab(inMemoryAssetId);
          }
  
          PrepareAssetForSave(inMemoryAssetId);
          ScriptCanvasAssetDescription assetDescription;
  
          AZStd::string suggestedFilename;
          AZStd::string suggestedFileFilter;
          bool isValidFileName = false;
              
          if (save == Save::InPlace)
          {
              isValidFileName = true;
              suggestedFileFilter = ScriptCanvasAssetDescription().GetExtensionImpl();
              suggestedFilename = inMemoryAssetId.Path().c_str();
          }
          else
          {
              suggestedFileFilter = ScriptCanvasAssetDescription().GetExtensionImpl();
  
              if (inMemoryAssetId.Path().empty())
              {
                  suggestedFilename = ScriptCanvasAssetDescription().GetSuggestedSavePathImpl();
              }
              else
              {
                  suggestedFilename = inMemoryAssetId.Path().c_str();
              }
          }
          
          EnsureSaveDestinationDirectory(suggestedFilename);
          QString filter = suggestedFileFilter.c_str();
          QString selectedFile = suggestedFilename.c_str();
  
          while (!isValidFileName)
          {
              selectedFile = AzQtComponents::FileDialog::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();
  
                  if (!AZ::StringFunc::EndsWith(filePath, assetDescription.GetExtensionImpl(), false))
                  {
                      filePath += assetDescription.GetExtensionImpl();
                  }
  
                  AZStd::string fileName;
  
                  // Verify that the path is within the project
  
                  AZStd::string assetRoot;
                  AZStd::array<char, AZ::IO::MaxPathLength> assetRootChar;
                  AZ::IO::FileIOBase::GetInstance()->ResolvePath("@engroot@", assetRootChar.data(), assetRootChar.size());
                  assetRoot = assetRootChar.data();
  
                  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 (!AZ::StringFunc::EndsWith(internalStringFile, assetDescription.GetExtensionImpl(), false))
              {
                  internalStringFile += assetDescription.GetExtensionImpl();
              }
  
              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;
              }
  
              SaveAs(internalStringFile, inMemoryAssetId);
              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(const VersionExplorer::FileSaveResult& result)
      {
          const bool saveSuccess = result.fileSaveError.empty();
          auto completeDescription = CompleteDescription(m_fileSaver->GetSource());
          auto memoryAsset = completeDescription ? *completeDescription : m_fileSaver->GetSource();
          int saveTabIndex = m_tabBar->FindTab(memoryAsset);
          AZStd::string tabName = saveTabIndex >= 0 ? m_tabBar->tabText(saveTabIndex).toUtf8().data() : "";
  
          if (saveSuccess)
          {
              ScriptCanvasEditor::SourceHandle& fileAssetId = memoryAsset;
              int currentTabIndex = m_tabBar->currentIndex();
  
              AZ::Data::AssetId oldId = fileAssetId.Id();
              AZ::Data::AssetInfo assetInfo;
              assetInfo.m_assetId = fileAssetId.Id();
              AZ_VerifyWarning("ScriptCanvas", AssetHelpers::GetAssetInfo(fileAssetId.Path().c_str(), assetInfo)
                  , "Failed to find asset info for source file just saved: %s", fileAssetId.Path().c_str());
  
              const bool assetIdHasChanged = assetInfo.m_assetId.m_guid != fileAssetId.Id();
              fileAssetId = SourceHandle(fileAssetId, assetInfo.m_assetId.m_guid, fileAssetId.Path());
  
              // check for saving a graph over another graph with an open tab
              for (;;)
              {
                  auto graph = fileAssetId.Get();
                  int tabIndexByGraph = m_tabBar->FindTab(graph);
                  if (tabIndexByGraph == -1)
                  {
                      AZ_Warning("ScriptCanvas", false, "unable to find graph just saved");
                      break;
                  }
  
                  int saveOverMatch = m_tabBar->FindSaveOverMatch(fileAssetId);
                  if (saveOverMatch < 0)
                  {
                      saveTabIndex = tabIndexByGraph;
                      currentTabIndex = m_tabBar->currentIndex();
                      break;
                  }
  
                  m_tabBar->CloseTab(saveOverMatch);
              }
  
              // this path is questionable, this is a save request that is not the current graph
              // We've saved as over a new graph, so we need to close the old one.
              if (saveTabIndex != currentTabIndex)
              {
                  // Invalidate the file asset id so we don't delete trigger the asset flow.
                  m_tabBar->setTabData(saveTabIndex, QVariant::fromValue(Widget::GraphTabMetadata()));
                  m_tabBar->CloseTab(saveTabIndex);
                  saveTabIndex = -1;
              }
  
              AzFramework::StringFunc::Path::GetFileName(memoryAsset.Path().c_str(), tabName);
  
              if (assetIdHasChanged)
              {
                  auto entity = memoryAsset.Get()->GetEntity();
  
                  auto editorComponents = AZ::EntityUtils::FindDerivedComponents<EditorScriptCanvasComponent>(entity);
  
                  if (editorComponents.empty())
                  {
                      if (auto firstRequestBus = EditorScriptCanvasComponentRequestBus::FindFirstHandler(entity->GetId()))
                      {
                          firstRequestBus->SetAssetId(memoryAsset.Describe());
                      }
                  }
                  else
                  {
                      for (auto editorComponent : editorComponents)
                      {
                          if (editorComponent->GetAssetId() == oldId)
                          {
                              editorComponent->SetAssetId(memoryAsset.Describe());
                              break;
                          }
                      }
                  }
              }             
  
              // Soft switch the asset id here. We'll do a double scene switch down below to actually switch the active assetid
              m_activeGraph = fileAssetId;
  
              if (tabName.at(tabName.size() - 1) == '*' || tabName.at(tabName.size() - 1) == '^')
              {
                  tabName = tabName.substr(0, tabName.size() - 2);
              }
  
              auto tabData = m_tabBar->GetTabData(saveTabIndex);
              tabData->m_fileState = Tracker::ScriptCanvasFileState::UNMODIFIED;
              tabData->m_assetId = fileAssetId;
              m_tabBar->SetTabData(*tabData, saveTabIndex);
              m_tabBar->SetTabText(saveTabIndex, tabName.c_str());
          }
          else
          {
              const auto failureMessage = AZStd::string::format("Failed to save %s: %s", tabName.c_str(), result.fileSaveError.c_str());
              QMessageBox::critical(this, QString(), QObject::tr(failureMessage.data()));
          }
  
          if (m_tabBar->currentIndex() != saveTabIndex)
          {
              m_tabBar->setCurrentIndex(saveTabIndex);
          }
  
          UpdateAssignToSelectionState();
  
          OnSaveToast toast(tabName, GetActiveGraphCanvasGraphId(), saveSuccess);
  
          const bool displayAsNotification = true;
          RunGraphValidation(displayAsNotification);
  
          m_closeCurrentGraphAfterSave = false;
  
          EnableAssetView(memoryAsset);
  
          UpdateSaveState(true);
          UnblockCloseRequests();
          m_fileSaver.reset();
      }
  
      bool MainWindow::ActivateAndSaveAsset(const ScriptCanvasEditor::SourceHandle& unsavedAssetId)
      {
          SetActiveAsset(unsavedAssetId);
          return OnFileSave();
      }
  
      void MainWindow::SaveAs(AZStd::string_view path, ScriptCanvasEditor::SourceHandle inMemoryAssetId)
      {
          DisableAssetView(inMemoryAssetId);
          UpdateSaveState(false);
          m_fileSaver = AZStd::make_unique<VersionExplorer::FileSaver>
                  ( nullptr
                  , [this](const VersionExplorer::FileSaveResult& fileSaveResult) { OnSaveCallBack(fileSaveResult); });
  
          ScriptCanvasEditor::SourceHandle newLocation(inMemoryAssetId, AZ::Uuid::CreateNull(), path);
          MarkRecentSave(newLocation);
          m_fileSaver->Save(newLocation);
  
          BlockCloseRequests();        
      }
  
      void MainWindow::OnFileOpen()
      {
          AZStd::string assetRoot;
          {
              AZStd::array<char, AZ::IO::MaxPathLength> assetRootChar;
              AZ::IO::FileIOBase::GetInstance()->ResolvePath("@projectroot@", assetRootChar.data(), assetRootChar.size());
              assetRoot = assetRootChar.data();
          }
  
          AZStd::string assetPath = AZStd::string::format("%s/scriptcanvas", assetRoot.c_str());
          QString filter;
  
          AZStd::set<AZStd::string> filterSet { ".scriptcanvas" };
  
          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::OnCanUndoChanged(bool canUndo)
      {
          ui->action_Undo->setEnabled(canUndo);
      }
  
      void MainWindow::OnCanRedoChanged(bool canRedo)
      {
          ui->action_Redo->setEnabled(canRedo);
      }
  
      bool MainWindow::CanShowNetworkSettings()
      {
          return m_userSettings->m_experimentalSettings.GetShowNetworkProperties();
      }
  
      GraphCanvas::ContextMenuAction::SceneReaction MainWindow::HandleContextMenu(GraphCanvas::EditorContextMenu& editorContextMenu, const AZ::EntityId& memberId, const QPoint& screenPoint, const QPointF& scenePoint) const
      {
          AZ::Vector2 sceneVector(aznumeric_cast<float>(scenePoint.x()), aznumeric_cast<float>(scenePoint.y()));
          GraphCanvas::GraphId graphCanvasGraphId = GetActiveGraphCanvasGraphId();
  
          editorContextMenu.RefreshActions(graphCanvasGraphId, memberId);
  
          QAction* result = editorContextMenu.exec(screenPoint);
  
          GraphCanvas::ContextMenuAction* contextMenuAction = qobject_cast<GraphCanvas::ContextMenuAction*>(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_activeGraph);
              if (fileState != Tracker::ScriptCanvasFileState::INVALID && fileState != Tracker::ScriptCanvasFileState::NEW)
              {
                  OnFileSaveCaller();
              }
          }
      }
  
      //! GeneralRequestBus
      void MainWindow::OnChangeActiveGraphTab(ScriptCanvasEditor::SourceHandle assetId)
      {
          SetActiveAsset(assetId);
      }
  
      AZ::EntityId MainWindow::GetActiveGraphCanvasGraphId() const
      {
          AZ::EntityId graphId{};
  
          if (m_activeGraph.IsGraphValid())
          {
              EditorGraphRequestBus::EventResult
                  ( graphId, m_activeGraph.Get()->GetScriptCanvasId(), &EditorGraphRequests::GetGraphCanvasGraphId);
          }
  
          return graphId;
      }
  
      ScriptCanvas::ScriptCanvasId MainWindow::GetActiveScriptCanvasId() const
      {
          return FindScriptCanvasIdByAssetId(m_activeGraph);
      }
  
      GraphCanvas::GraphId MainWindow::GetGraphCanvasGraphId(const ScriptCanvas::ScriptCanvasId& scriptCanvasId) const
      {
          AZ::EntityId graphId{};
          EditorGraphRequestBus::EventResult(graphId, scriptCanvasId, &EditorGraphRequests::GetGraphCanvasGraphId);
          return graphId;
      }
  
      GraphCanvas::GraphId MainWindow::FindGraphCanvasGraphIdByAssetId(const ScriptCanvasEditor::SourceHandle& assetId) const
      {
          AZ::EntityId graphId{};
  
          if (assetId.IsGraphValid())
          {
              EditorGraphRequestBus::EventResult
                  ( graphId, assetId.Get()->GetScriptCanvasId(), &EditorGraphRequests::GetGraphCanvasGraphId);
          }
  
          return graphId;
      }
  
      ScriptCanvas::ScriptCanvasId MainWindow::FindScriptCanvasIdByAssetId(const ScriptCanvasEditor::SourceHandle& assetId) const
      {
          return assetId.IsGraphValid() ? assetId.Get()->GetScriptCanvasId() : ScriptCanvas::ScriptCanvasId{};
      }
  
      ScriptCanvas::ScriptCanvasId MainWindow::GetScriptCanvasId(const GraphCanvas::GraphId& graphCanvasGraphId) const
      {
          return m_tabBar->FindScriptCanvasIdFromGraphCanvasId(graphCanvasGraphId);
      }
  
      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 ScriptCanvasEditor::SourceHandle& assetId)
      {
          for (int tabIndex = 0; tabIndex < m_tabBar->count(); ++tabIndex)
          {
              QVariant tabdata = m_tabBar->tabData(tabIndex);
              if (tabdata.isValid())
              {
                  auto tabAssetId = tabdata.value<Widget::GraphTabMetadata>();
                  if (tabAssetId.m_assetId.AnyEquals(assetId))
                  {
                      return tabdata;
                  }
              }
          }
          return QVariant();
      }
  
      bool MainWindow::IsTabOpen(const ScriptCanvasEditor::SourceHandle& fileAssetId, int& outTabIndex) const
      {
          int tabIndex = m_tabBar->FindTab(fileAssetId);
          if (-1 != tabIndex)
          {
              outTabIndex = tabIndex;
              return true;
          }
          return false;
      }
  
      void MainWindow::ReconnectSceneBuses(ScriptCanvasEditor::SourceHandle previousAsset, ScriptCanvasEditor::SourceHandle nextAsset)
      {
          // Disconnect previous asset
          AZ::EntityId previousScriptCanvasSceneId;
          if (previousAsset.IsGraphValid())
          {
              previousScriptCanvasSceneId = previousAsset.Get()->GetScriptCanvasId();
              GraphCanvas::SceneNotificationBus::MultiHandler::BusDisconnect(previousScriptCanvasSceneId);
          }
  
          AZ::EntityId nextAssetGraphCanvasId;
          if (nextAsset.IsGraphValid())
          {
              // Connect the next asset
              EditorGraphRequestBus::EventResult(nextAssetGraphCanvasId, nextAsset.Get()->GetScriptCanvasId(), &EditorGraphRequests::GetGraphCanvasGraphId);
  
              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 ScriptCanvasEditor::SourceHandle& fileAssetId)
      {
          if (m_activeGraph.AnyEquals(fileAssetId))
          {
              return;
          }
  
          AssetHelpers::PrintInfo("SetActiveAsset : from: %s to %s", m_activeGraph.ToString().c_str(), fileAssetId.ToString().c_str());
  
          if (fileAssetId.IsGraphValid())
          {
              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_activeGraph.IsGraphValid())
          {
              // 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
              auto oldTab = m_tabBar->FindTab(m_activeGraph);
              if (auto view = m_tabBar->ModTabView(oldTab))
              {
                  view->hide();
                  m_layout->removeWidget(view);
                  m_tabBar->ClearTabView(oldTab);
              }
          }
  
          if (fileAssetId.IsGraphValid())
          {
              ScriptCanvasEditor::SourceHandle previousAssetId = m_activeGraph;
              m_activeGraph = fileAssetId;
              RefreshActiveAsset();
              ReconnectSceneBuses(previousAssetId, m_activeGraph);
          }
          else
          {
              ScriptCanvasEditor::SourceHandle previousAssetId = m_activeGraph;
              m_activeGraph.Clear();
              m_emptyCanvas->show();
              ReconnectSceneBuses(previousAssetId, m_activeGraph);
              SignalActiveSceneChanged(ScriptCanvasEditor::SourceHandle());
          }
  
          UpdateUndoCache(fileAssetId);
          RefreshSelection();        
      }
  
      void MainWindow::RefreshActiveAsset()
      {
          if (m_activeGraph.IsGraphValid())
          {
              AssetHelpers::PrintInfo("RefreshActiveAsset : m_activeGraph (%s)", m_activeGraph.ToString().c_str());
              if (auto view = m_tabBar->ModOrCreateTabView(m_tabBar->FindTab(m_activeGraph)))
              {
                  view->ShowScene(m_activeGraph.Get()->GetScriptCanvasId());
                  m_layout->addWidget(view);
                  view->show();
                  m_emptyCanvas->hide();
                  SignalActiveSceneChanged(m_activeGraph);
              }
              else
              {
                  SetActiveAsset({});
              }
          }
      }
  
      void MainWindow::Clear()
      {
          m_tabBar->CloseAllTabs();
          SetActiveAsset({});
      }
  
      void MainWindow::OnTabCloseButtonPressed(int index)
      {
          QVariant tabdata = m_tabBar->tabData(index);
          if (tabdata.isValid())
          {
              Widget::GraphTabMetadata tabMetadata = tabdata.value<Widget::GraphTabMetadata>();
              Tracker::ScriptCanvasFileState fileState = tabMetadata.m_fileState;
              UnsavedChangesOptions saveDialogResults = UnsavedChangesOptions::CONTINUE_WITHOUT_SAVING;
  
              if (fileState == Tracker::ScriptCanvasFileState::NEW
              || fileState == Tracker::ScriptCanvasFileState::MODIFIED
              || fileState == Tracker::ScriptCanvasFileState::SOURCE_REMOVED)
              {
                  SetActiveAsset(tabMetadata.m_assetId);
                  saveDialogResults = ShowSaveDialog(m_tabBar->tabText(index).toUtf8().constData());
              }
  
              if (saveDialogResults == UnsavedChangesOptions::SAVE)
              {
                  m_closeCurrentGraphAfterSave = true;
                  SaveAssetImpl(tabMetadata.m_assetId, fileState == Tracker::ScriptCanvasFileState::NEW ? Save::As : Save::InPlace);
              }
              else if (saveDialogResults == UnsavedChangesOptions::CONTINUE_WITHOUT_SAVING)
              {
                  OnTabCloseRequest(index);
              }
          }
      }
  
      void MainWindow::SaveTab(int index)
      {
          QVariant tabdata = m_tabBar->tabData(index);
          if (tabdata.isValid())
          {
              auto assetId = tabdata.value<Widget::GraphTabMetadata>();
              SaveAssetImpl(assetId.m_assetId, Save::InPlace);
          }
      }
  
      void MainWindow::CloseAllTabs()
      {
          m_isClosingTabs = true;
          m_skipTabOnClose.Clear();
  
          CloseNextTab();
      }
  
      void MainWindow::CloseAllTabsBut(int index)
      {
          QVariant tabdata = m_tabBar->tabData(index);
          if (tabdata.isValid())
          {
              auto assetId = tabdata.value<Widget::GraphTabMetadata>().m_assetId;
  
              m_isClosingTabs = true;
              m_skipTabOnClose = assetId;
              CloseNextTab();
          }
      }
  
      void MainWindow::CopyPathToClipboard(int index)
      {
          QVariant tabdata = m_tabBar->tabData(index);
  
          if (tabdata.isValid())
          {
              QClipboard* clipBoard = QGuiApplication::clipboard();
  
              auto assetId = tabdata.value<Widget::GraphTabMetadata>();
              if (!assetId.m_assetId.Path().empty())
              {
                  clipBoard->setText(assetId.m_assetId.Path().c_str());
              }
              else
              {
                  clipBoard->setText(m_tabBar->tabText(index));
              }
          }
      }
  
      void MainWindow::OnActiveFileStateChanged()
      {
          UpdateAssignToSelectionState();
      }
  
      void MainWindow::CloseNextTab()
      {
          if (m_isClosingTabs)
          {
              if (m_tabBar->count() == 0
                  || (m_tabBar->count() == 1 && m_skipTabOnClose.IsGraphValid()))
              {
                  m_isClosingTabs = false;
                  m_skipTabOnClose.Clear();
                  return;
              }
  
              int tab = 0;
  
              while (tab < m_tabBar->count())
              {
                  QVariant tabdata = m_tabBar->tabData(tab);
                  if (tabdata.isValid())
                  {
                      auto assetId = tabdata.value<Widget::GraphTabMetadata>();
  
                      if (!assetId.m_assetId.AnyEquals(m_skipTabOnClose))
                      {
                          break;
                      }
                  }
  
                  tab++;
              }
  
              m_tabBar->tabCloseRequested(tab);
          }
      }
  
      void MainWindow::OnTabCloseRequest(int index)
      {
          QVariant tabdata = m_tabBar->tabData(index);
          if (tabdata.isValid())
          {
              auto tabAssetId = tabdata.value<Widget::GraphTabMetadata>();
  
  
              if (tabAssetId.m_canvasWidget)
              {
                  tabAssetId.m_canvasWidget->hide();
              }
  
              bool activeSet = false;
  
              if (tabAssetId.m_assetId.AnyEquals(m_activeGraph))
              {
                  SetActiveAsset({});
                  activeSet = true;
              }
  
              m_tabBar->CloseTab(index);
              m_tabBar->update();
              RemoveScriptCanvasAsset(tabAssetId.m_assetId);
  
              if (!activeSet && 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::OnNodeAdded(const AZ::EntityId& nodeId, bool isPaste)
      {
          // Handle special-case where if a method node is created that has an AZ::Event output slot,
          // we will automatically create the AZ::Event Handler node for the user
          GraphCanvas::GraphId graphCanvasGraphId = GetActiveGraphCanvasGraphId();
          AZStd::vector<GraphCanvas::SlotId> outputDataSlotIds;
          GraphCanvas::NodeRequestBus::EventResult(outputDataSlotIds, nodeId, &GraphCanvas::NodeRequests::FindVisibleSlotIdsByType, GraphCanvas::CT_Output, GraphCanvas::SlotTypes::DataSlot);
  
          for (const auto& slotId : outputDataSlotIds)
          {
  
              if (!IsInUndoRedo(graphCanvasGraphId) && !isPaste && CreateAzEventHandlerSlotMenuAction::FindBehaviorMethodWithAzEventReturn(graphCanvasGraphId, slotId))
              {
                  CreateAzEventHandlerSlotMenuAction eventHandlerAction(this);
                  eventHandlerAction.RefreshAction(graphCanvasGraphId, slotId);
  
                  AZ::Vector2 position;
                  GraphCanvas::GeometryRequestBus::EventResult(position, nodeId, &GraphCanvas::GeometryRequests::GetPosition);
  
                  eventHandlerAction.TriggerAction(graphCanvasGraphId, position);
                  break;
              }
          }
      }
  
      void MainWindow::OnSelectionChanged()
      {
          QueuePropertyGridUpdate();
      }
  
      void MainWindow::OnVariableSelectionChanged(const AZStd::vector<AZ::EntityId>& 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<int>(size().width() * 0.15f), static_cast<int>(size().width() * 0.2f) },
              Qt::Horizontal);
  
          resizeDocks({ m_nodePalette, m_minimap },
          { static_cast<int>(size().height() * 0.70f), static_cast<int>(size().height() * 0.30f) },
              Qt::Vertical);
  
  
          resizeDocks({ m_propertyGrid, m_variableDockWidget },
          { static_cast<int>(size().height() * 0.70f), static_cast<int>(size().height() * 0.30f) },
              Qt::Vertical);
  
          resizeDocks({ m_validationDockWidget }, { static_cast<int>(size().height() * 0.01) }, Qt::Vertical);
  
          // Disabled until debugger is implemented
          //resizeDocks({ m_logPanel }, { static_cast<int>(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_activeGraph.IsGraphValid())
          {
              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<int>(boundingBox.width() * 0.5f), aznumeric_cast<int>(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<AZ::EntityId>& 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<AZ::EntityId>{ nodes.begin(), nodes.end() });
      }
  
      void MainWindow::DeleteConnections(const AZ::EntityId& graphCanvasGraphId, const AZStd::vector<AZ::EntityId>& connections)
      {
          ScopedVariableSetter<bool> scopedIgnoreSelection(m_ignoreSelection, true);
          GraphCanvas::SceneRequestBus::Event(graphCanvasGraphId, &GraphCanvas::SceneRequests::Delete, AZStd::unordered_set<AZ::EntityId>{ connections.begin(), connections.end() });
      }
  
      void MainWindow::DisconnectEndpoints(const AZ::EntityId& graphCanvasGraphId, const AZStd::vector<GraphCanvas::Endpoint>& endpoints)
      {
          AZStd::unordered_set<AZ::EntityId> connections;
          for (const auto& endpoint : endpoints)
          {
              AZStd::vector<AZ::EntityId> endpointConnections;
              GraphCanvas::SceneRequestBus::EventResult(endpointConnections, graphCanvasGraphId, &GraphCanvas::SceneRequests::GetConnectionsForEndpoint, endpoint);
              connections.insert(endpointConnections.begin(), endpointConnections.end());
          }
          DeleteConnections(graphCanvasGraphId, { connections.begin(), connections.end() });
      }
  
      void MainWindow::RunUpgradeTool()
      {
          using namespace VersionExplorer;
          auto versionExplorer = aznew VersionExplorer::Controller(this);
          versionExplorer->exec();
  
          const ModificationResults* result = nullptr;
          ModelRequestsBus::BroadcastResult(result, &ModelRequestsTraits::GetResults);
          if (result && !result->m_failures.empty())
          {
              // If there are graphs that need manual correction, show the helper
              UpgradeHelper* upgradeHelper = new UpgradeHelper(this);
              upgradeHelper->show();
          }
  
          delete versionExplorer;
      }
  
      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)
      {
          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);
  
          m_createFunctionOutput->setEnabled(enabled);
          m_createFunctionInput->setEnabled(enabled);
  
          // File Menu
          ui->action_Close->setEnabled(enabled);
  
          RefreshGraphPreferencesAction();
  
          UpdateAssignToSelectionState();
          UpdateUndoRedoState();
      }
  
      void MainWindow::OnWorkspaceRestoreStart()
      {
          m_isRestoringWorkspace = true;
      }
  
      void MainWindow::OnWorkspaceRestoreEnd(ScriptCanvasEditor::SourceHandle lastFocusAsset)
      {
          if (m_isRestoringWorkspace)
          {
              m_isRestoringWorkspace = false;
  
              if (m_queuedFocusOverride.IsGraphValid())
              {
                  SetActiveAsset(m_queuedFocusOverride);
                  m_queuedFocusOverride.Clear();
              }
              else if (lastFocusAsset.IsGraphValid())
              {
                  SetActiveAsset(lastFocusAsset);
              }
  
              if (!m_activeGraph.IsGraphValid())
              {
                  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_activeGraph.IsGraphValid();
  
          if (buttonEnabled)
          {
              const Tracker::ScriptCanvasFileState& fileState = GetAssetFileState(m_activeGraph);
              if (fileState == Tracker::ScriptCanvasFileState::INVALID || fileState == Tracker::ScriptCanvasFileState::NEW || fileState == Tracker::ScriptCanvasFileState::SOURCE_REMOVED)
              {
                  buttonEnabled = false;
              }
  
              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)
      {
           ui->action_Save->setEnabled(enabled);
           ui->action_Save_As->setEnabled(enabled);
      }
  
      void MainWindow::CreateFunctionInput()
      {
          PushPreventUndoStateUpdate();
          CreateFunctionDefinitionNode(-1);
          PopPreventUndoStateUpdate();
  
          PostUndoPoint(GetActiveScriptCanvasId());
      }
  
      void MainWindow::CreateFunctionOutput()
      {
          PushPreventUndoStateUpdate();
          CreateFunctionDefinitionNode(1);
          PopPreventUndoStateUpdate();
  
          PostUndoPoint(GetActiveScriptCanvasId());
      }
  
      void MainWindow::CreateFunctionDefinitionNode(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);
  
          const bool isInput = positionOffset < 0;
          const AZStd::string rootName = isInput ? "New Input" : "New Output";
          NodeIdPair nodeIdPair = Nodes::CreateFunctionDefinitionNode(scriptCanvasId, isInput, 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), false);
          sceneRequests->SignalGenericAddPositionUseEnd();
  
          if (!viewBounds.isEmpty())
          {
              QPointF topLeftPoint = viewBounds.center();
  
              int widthOffset = aznumeric_cast<int>((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<CreateNodeMimeEvent>(mimeEvent))
          {
              CreateNodeMimeEvent* createEvent = static_cast<CreateNodeMimeEvent*>(mimeEvent);
  
              if (createEvent->ExecuteEvent(nodeCreationPos, nodeCreationPos, graphCanvasGraphId))
              {
                  retVal = createEvent->GetCreatedPair();
              }
          }
          else if (azrtti_istypeof<SpecializedCreateNodeMimeEvent>(mimeEvent))
          {
              SpecializedCreateNodeMimeEvent* specializedCreationEvent = static_cast<SpecializedCreateNodeMimeEvent*>(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;
      }
  
      void MainWindow::ForceCloseActiveAsset()
      {
          OnTabCloseRequest(m_tabBar->currentIndex());
      }
  
      bool MainWindow::RegisterObject(AZ::Crc32 elementId, QObject* object)
      {
          auto lookupIter = m_automationLookUpMap.find(elementId);
  
          if (lookupIter != m_automationLookUpMap.end())
          {
              AZ_Error("ScriptCanvas", false, "Attempting to register two elements with the id %llu", (unsigned int)elementId);
              return false;
          }
  
          m_automationLookUpMap[elementId] = object;
          return true;
      }
  
      bool MainWindow::UnregisterObject(AZ::Crc32 elementId)
      {
          auto eraseCount = m_automationLookUpMap.erase(elementId);
          return eraseCount > 0;
      }
  
      QObject* MainWindow::FindObject(AZ::Crc32 elementId)
      {
          auto lookupIter = m_automationLookUpMap.find(elementId);
  
          if (lookupIter != m_automationLookUpMap.end())
          {
              return lookupIter->second;
          }
  
          return nullptr;
      }
  
      QObject* MainWindow::FindElementByName(QString elementName)
      {
          return findChild<QObject*>(elementName);
      }
  
      AZ::EntityId MainWindow::FindEditorNodeIdByAssetNodeId(const ScriptCanvasEditor::SourceHandle& assetId, AZ::EntityId assetNodeId) const
      {
          AZ::EntityId editorEntityId;
          AssetTrackerRequestBus::BroadcastResult
              ( editorEntityId, &AssetTrackerRequests::GetEditorEntityIdFromSceneEntityId, assetId.Id(), assetNodeId);
          return editorEntityId;
      }
  
      AZ::EntityId MainWindow::FindAssetNodeIdByEditorNodeId(const ScriptCanvasEditor::SourceHandle& assetId, AZ::EntityId editorNodeId) const
      {
          AZ::EntityId sceneEntityId;
          AssetTrackerRequestBus::BroadcastResult
              ( sceneEntityId, &AssetTrackerRequests::GetSceneEntityIdFromEditorEntityId, assetId.Id(), 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();
  
          // Handle the special-case if we are creating a node proposal for an AZ::Event, then we show
          // a small menu with only that applicable action
          if (CreateAzEventHandlerSlotMenuAction::FindBehaviorMethodWithAzEventReturn(graphCanvasGraphId, endpoint.GetSlotId()))
          {
              GraphCanvas::EditorContextMenu menu(ScriptCanvasEditor::AssetEditorId);
  
              menu.AddMenuAction(aznew CreateAzEventHandlerSlotMenuAction(&menu));
  
              HandleContextMenu(menu, endpoint.GetSlotId(), screenPoint, scenePoint);
          }
          // For everything else, show the full scene context menu
          else
          {
              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)
                  {
                      NodeIdPair finalNode = ProcessCreateNodeMimeEvent(mimeEvent, graphCanvasGraphId, AZ::Vector2(aznumeric_cast<float>(scenePoint.x()), aznumeric_cast<float>(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<GraphCanvas::ConnectionId> 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<float>(scenePoint.y() - verticalOffset));
  
                          qreal horizontalOffset = connectionPoint.x() - position.GetX();
                          position.SetX(aznumeric_cast<float>(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_activeGraph.IsGraphValid())
          {
              graphId = GetActiveGraphCanvasGraphId();
          }
  
          return graphId;
      }
  
      bool MainWindow::ContainsGraph(const GraphCanvas::GraphId&) const
      {
          return false;
      }
  
      bool MainWindow::CloseGraph(const GraphCanvas::GraphId&)
      {
          return false;
      }
  
      void MainWindow::CustomizeConnectionEntity(AZ::Entity* connectionEntity)
      {
          connectionEntity->CreateComponent<SceneMemberMappingComponent>();
      }
  
      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<float>(scenePoint.x()), aznumeric_cast<float>(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<GraphCanvas::ContextMenuAction*>(action);
  
              if (contextMenuAction)
              {
                  PushPreventUndoStateUpdate();
                  AZ::Vector2 mousePoint(aznumeric_cast<float>(scenePoint.x()), aznumeric_cast<float>(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));
          }
  
          if (descriptorType == NodeDescriptorType::FunctionDefinitionNode)
          {
              NodeDescriptorComponent* descriptor = nullptr;
              NodeDescriptorRequestBus::EventResult(descriptor, nodeId, &NodeDescriptorRequests::GetDescriptorComponent);
              contextMenu.AddMenuAction(aznew RenameFunctionDefinitionNodeAction(descriptor, &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<float>(scenePoint.x()), aznumeric_cast<float>(scenePoint.y()));
          GraphCanvas::GraphId graphCanvasGraphId = GetActiveGraphCanvasGraphId();
  
          m_connectionContextMenu->RefreshActions(graphCanvasGraphId, connectionId);
  
          QAction* result = m_connectionContextMenu->exec(screenPoint);
  
          GraphCanvas::ContextMenuAction* contextMenuAction = qobject_cast<GraphCanvas::ContextMenuAction*>(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)
              {
                  NodeIdPair finalNode = ProcessCreateNodeMimeEvent(mimeEvent, graphCanvasGraphId, AZ::Vector2(aznumeric_cast<float>(scenePoint.x()), aznumeric_cast<float>(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<float>(scenePoint.y() - verticalOffset));
  
                          qreal horizontalOffset = connectionPoint.x() - position.GetX();
                          position.SetX(aznumeric_cast<float>(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));
          contextMenu.AddMenuAction(aznew CreateAzEventHandlerSlotMenuAction(&contextMenu));
          contextMenu.AddMenuAction(aznew SetDataSlotTypeMenuAction(&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<QWidget*>(parent())->close();
          }
  
          if (HasSystemTickAction(SystemTickActionFlag::CloseCurrentGraph))
          {
              RemoveSystemTickAction(SystemTickActionFlag::CloseCurrentGraph);
  
              if (m_tabBar)
              {
                  m_tabBar->tabCloseRequested(m_tabBar->currentIndex());
              }
          }
  
          if (HasSystemTickAction(SystemTickActionFlag::CloseNextTabAction))
          {
              RemoveSystemTickAction(SystemTickActionFlag::CloseNextTabAction);
              CloseNextTab();
          }
  
          ClearStaleSaves();
      }
  
      void MainWindow::OnCommandStarted(AZ::Crc32)
      {
          PushPreventUndoStateUpdate();
      }
  
      void MainWindow::OnCommandFinished(AZ::Crc32)
      {
          PopPreventUndoStateUpdate();
      }
  
      void MainWindow::PrepareActiveAssetForSave()
      {
          PrepareAssetForSave(m_activeGraph);
      }
  
      void MainWindow::PrepareAssetForSave(const ScriptCanvasEditor::SourceHandle& /*assetId*/)
      {
      }
  
      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)
          {
              bool isLayerEntity = false;
              AzToolsFramework::Layers::EditorLayerComponentRequestBus::EventResult(isLayerEntity, entityId, &AzToolsFramework::Layers::EditorLayerComponentRequestBus::Events::HasLayer);
  
              if (isLayerEntity)
              {
                  continue;
              }
  
              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 = GetAssetFileState(m_activeGraph);;
          
          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);
   
          auto selectedEntityIdIter = selectedEntityIds.begin();
   
          bool isLayerAmbiguous = false;
          AZ::EntityId targetLayer;
   
          while (selectedEntityIdIter != selectedEntityIds.end())
          {
              bool isLayerEntity = false;
              AzToolsFramework::Layers::EditorLayerComponentRequestBus::EventResult(isLayerEntity, (*selectedEntityIdIter), &AzToolsFramework::Layers::EditorLayerComponentRequestBus::Events::HasLayer);
   
              if (isLayerEntity)
              {
                  if (targetLayer.IsValid())
                  {
                      isLayerAmbiguous = true;
                  }
   
                  targetLayer = (*selectedEntityIdIter);
   
                  selectedEntityIdIter = selectedEntityIds.erase(selectedEntityIdIter);
              }
              else
              {
                  ++selectedEntityIdIter;
              }
          }
   
          if (selectedEntityIds.empty())
          {
              AZ::EntityId createdId;
              AzToolsFramework::EditorRequests::Bus::BroadcastResult(createdId, &AzToolsFramework::EditorRequests::CreateNewEntity, AZ::EntityId());
   
              selectedEntityIds.emplace_back(createdId);
   
              if (targetLayer.IsValid() && !isLayerAmbiguous)
              {
                  AZ::TransformBus::Event(createdId, &AZ::TransformBus::Events::SetParent, targetLayer);
              }
          }
   
          for (const AZ::EntityId& entityId : selectedEntityIds)
          {
              AssignGraphToEntityImpl(entityId);
          }
      }
  
      void MainWindow::OnAssignToEntity(const AZ::EntityId& entityId)
      {
          Tracker::ScriptCanvasFileState fileState = GetAssetFileState(m_activeGraph);
  
          if (fileState == Tracker::ScriptCanvasFileState::MODIFIED
              || fileState == Tracker::ScriptCanvasFileState::UNMODIFIED)
          {
              AssignGraphToEntityImpl(entityId);
          }
      }
  
      ScriptCanvasEditor::Tracker::ScriptCanvasFileState MainWindow::GetAssetFileState(ScriptCanvasEditor::SourceHandle assetId) const
      {
          auto dataOptional = m_tabBar->GetTabData(assetId);
          return dataOptional ? dataOptional->m_fileState : Tracker::ScriptCanvasFileState::INVALID;
      }
  
      void MainWindow::AssignGraphToEntityImpl(const AZ::EntityId& entityId)
      {
          bool isLayerEntity = false;
          AzToolsFramework::Layers::EditorLayerComponentRequestBus::EventResult(isLayerEntity, entityId, &AzToolsFramework::Layers::EditorLayerComponentRequestBus::Events::HasLayer);
  
          if (isLayerEntity)
          {
              return;
          }
  
          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<EditorScriptCanvasComponent>() });
  
              usableRequestBus = EditorScriptCanvasComponentRequestBus::FindFirstHandler(entityId);
          }
  
          if (usableRequestBus)
          {
              usableRequestBus->SetAssetId(m_activeGraph.Describe());
          }
      }
  
      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)
      {
          m_systemTickActions |= action;
      }
  
      void MainWindow::BlockCloseRequests()
      {
          m_queueCloseRequest = true;
      }
  
      void MainWindow::UnblockCloseRequests()
      {
          if (m_queueCloseRequest)
          {
              m_queueCloseRequest = false;
  
              if (m_hasQueuedClose)
              {
                  qobject_cast<QWidget*>(parent())->close();
              }
          }
      }
  
      void MainWindow::OpenNextFile()
      {
          if (!m_filesToOpen.empty())
          {
              QString nextFile = m_filesToOpen.front();
              m_filesToOpen.pop_front();
  
              OpenFile(nextFile.toUtf8().data());
              OpenNextFile();
          }
          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(const ScriptCanvasEditor::SourceHandle& memoryAssetId)
      {
          if (auto view = m_tabBar->ModTabView(m_tabBar->FindTab(memoryAssetId)))
          {
              view->DisableView();
          }
  
          m_tabBar->setEnabled(false);
          m_bookmarkDockWidget->setEnabled(false);
          m_variableDockWidget->setEnabled(false);
          m_propertyGrid->DisableGrid();
          m_editorToolbar->OnViewDisabled();
  
          m_createFunctionInput->setEnabled(false);
          m_createFunctionOutput->setEnabled(false);
          m_createScriptCanvas->setEnabled(false);
  
          UpdateMenuState(false);
  
          ui->action_New_Script->setEnabled(false);
  
          m_autoSaveTimer.stop();
      }
  
      void MainWindow::EnableAssetView(const ScriptCanvasEditor::SourceHandle& memoryAssetId)
      {
          if (auto view = m_tabBar->ModTabView(m_tabBar->FindTab(memoryAssetId)))
          {
              view->EnableView();
          }
  
          m_tabBar->setEnabled(true);
          m_bookmarkDockWidget->setEnabled(true);
          m_variableDockWidget->setEnabled(true);
          m_propertyGrid->EnableGrid();
          m_editorToolbar->OnViewEnabled();
  
          m_createScriptCanvas->setEnabled(true);
  
          ui->action_New_Script->setEnabled(true);
  
          UpdateMenuState(true);
  
          UpdateUndoRedoState();
      }
  
  
      void MainWindow::ClearStaleSaves()
      {
          AZStd::lock_guard<AZStd::recursive_mutex> lock(m_mutex);
          auto timeNow = AZStd::chrono::system_clock::now();
          AZStd::erase_if(m_saves, [&timeNow](const auto& item)
          {
              AZStd::sys_time_t delta = AZStd::chrono::seconds(timeNow - item.second).count();
              return delta > 2.0f;
          });
      }
  
      bool MainWindow::IsRecentSave(const SourceHandle& handle) const
      {
          AZStd::lock_guard<AZStd::recursive_mutex> lock(const_cast<MainWindow*>(this)->m_mutex);
          AZStd::string key = handle.Path().Native();
          AZStd::to_lower(key.begin(), key.end());
          auto iter = m_saves.find(key);
          return iter != m_saves.end();
      }
  
      void MainWindow::MarkRecentSave(const SourceHandle& handle)
      {
          AZStd::lock_guard<AZStd::recursive_mutex> lock(m_mutex);
          AZStd::string key = handle.Path().Native();
          AZStd::to_lower(key.begin(), key.end());
          m_saves[key] = AZStd::chrono::system_clock::now();
      }
  
  #include <Editor/View/Windows/moc_MainWindow.cpp>
  }
  