123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857 |
- /*
- * 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 <Atom/RPI.Edit/Common/AssetUtils.h>
- #include <Atom/RPI.Reflect/Asset/AssetUtils.h>
- #include <AtomToolsFramework/Document/AtomToolsDocumentMainWindow.h>
- #include <AtomToolsFramework/Document/AtomToolsDocumentRequestBus.h>
- #include <AtomToolsFramework/Document/AtomToolsDocumentSystemRequestBus.h>
- #include <AtomToolsFramework/Document/CreateDocumentDialog.h>
- #include <AtomToolsFramework/SettingsDialog/SettingsDialog.h>
- #include <AtomToolsFramework/Util/Util.h>
- #include <AzCore/Utils/Utils.h>
- #include <AzFramework/StringFunc/StringFunc.h>
- AZ_PUSH_DISABLE_WARNING(4251 4800, "-Wunknown-warning-option") // disable warnings spawned by QT
- #include <QApplication>
- #include <QByteArray>
- #include <QCloseEvent>
- #include <QDesktopServices>
- #include <QDragEnterEvent>
- #include <QDragLeaveEvent>
- #include <QDropEvent>
- #include <QInputDialog>
- #include <QLayout>
- #include <QMenu>
- #include <QMenuBar>
- #include <QMessageBox>
- #include <QMimeData>
- #include <QTimer>
- #include <QWindow>
- AZ_POP_DISABLE_WARNING
- namespace AtomToolsFramework
- {
- AtomToolsDocumentMainWindow::AtomToolsDocumentMainWindow(const AZ::Crc32& toolId, const QString& objectName, QWidget* parent)
- : Base(toolId, objectName, parent)
- {
- AddDocumentTabBar();
- // Register a handler with the asset browser that attempts to open the first compatible document type for the selected path.
- m_assetBrowser->SetOpenHandler([this](const AZStd::string& absolutePath) {
- DocumentTypeInfoVector documentTypes;
- AtomToolsDocumentSystemRequestBus::EventResult(
- documentTypes, m_toolId, &AtomToolsDocumentSystemRequestBus::Events::GetRegisteredDocumentTypes);
- for (const auto& documentType : documentTypes)
- {
- if (documentType.IsSupportedExtensionToOpen(absolutePath))
- {
- AZ::SystemTickBus::QueueFunction([toolId = m_toolId, absolutePath]() {
- AtomToolsDocumentSystemRequestBus::Event(toolId, &AtomToolsDocumentSystemRequestBus::Events::OpenDocument, absolutePath);
- });
- return;
- }
- }
- // If there was no compatible document type I tend to open the file using standard OS file openers
- QDesktopServices::openUrl(QUrl::fromLocalFile(absolutePath.c_str()));
- });
- // Enable dragging and dropping of files onto this window.
- setAcceptDrops(true);
- AtomToolsDocumentNotificationBus::Handler::BusConnect(m_toolId);
- }
- AtomToolsDocumentMainWindow::~AtomToolsDocumentMainWindow()
- {
- AtomToolsDocumentNotificationBus::Handler::BusDisconnect();
- }
- void AtomToolsDocumentMainWindow::CreateMenus(QMenuBar* menuBar)
- {
- Base::CreateMenus(menuBar);
- // Generating the main menu manually because it's easier and we will have some dynamic or data driven entries
- QAction* insertPostion = !m_menuFile->actions().empty() ? m_menuFile->actions().front() : nullptr;
- BuildCreateMenu(insertPostion);
- BuildOpenMenu(insertPostion);
- m_menuOpenRecent = new QMenu("Open Recent", menuBar);
- connect(m_menuOpenRecent, &QMenu::aboutToShow, menuBar, [this]() {
- UpdateRecentFileMenu();
- });
- m_menuFile->insertMenu(insertPostion, m_menuOpenRecent);
- m_menuFile->insertSeparator(insertPostion);
- m_actionSave = CreateActionAtPosition(m_menuFile, insertPostion, "&Save", [this]() {
- SaveDocument(GetCurrentDocumentId());
- }, QKeySequence::Save);
- m_actionSaveAsCopy = CreateActionAtPosition(m_menuFile, insertPostion, "Save &As...", [this]() {
- const AZ::Uuid documentId = GetCurrentDocumentId();
- const QString documentPath = GetDocumentPath(documentId);
- if (const auto& savePath = GetSaveDocumentParams(documentPath.toUtf8().constData(), documentId); !savePath.empty())
- {
- bool result = false;
- AtomToolsDocumentSystemRequestBus::EventResult(
- result, m_toolId, &AtomToolsDocumentSystemRequestBus::Events::SaveDocumentAsCopy, documentId, savePath);
- if (!result)
- {
- SetStatusError(tr("Document save failed: %1").arg(documentPath).toUtf8().constData());
- }
- }
- }, QKeySequence::SaveAs);
- m_actionSaveAsChild = CreateActionAtPosition(m_menuFile, insertPostion, "Save As &Child...", [this]() {
- const AZ::Uuid documentId = GetCurrentDocumentId();
- const QString documentPath = GetDocumentPath(documentId);
- if (const auto& savePath = GetSaveDocumentParams(documentPath.toUtf8().constData(), documentId); !savePath.empty())
- {
- bool result = false;
- AtomToolsDocumentSystemRequestBus::EventResult(
- result, m_toolId, &AtomToolsDocumentSystemRequestBus::Events::SaveDocumentAsChild, documentId, savePath);
- if (!result)
- {
- SetStatusError(tr("Document save failed: %1").arg(documentPath).toUtf8().constData());
- }
- }
- });
- m_actionSaveAll = CreateActionAtPosition(m_menuFile, insertPostion, "Save A&ll", [this]() {
- for (const auto& documentId : GetOpenDocumentIds())
- {
- if (!SaveDocument(documentId))
- {
- // Stop if there is any save failed or cancel
- break;
- }
- }
- });
- m_menuFile->insertSeparator(insertPostion);
- m_actionClose = CreateActionAtPosition(m_menuFile, insertPostion, "&Close", [this]() {
- CloseDocuments({GetCurrentDocumentId()});
- }, QKeySequence::Close);
- m_actionCloseAll = CreateActionAtPosition(m_menuFile, insertPostion, "Close All", [this]() {
- CloseDocuments(GetOpenDocumentIds());
- });
- m_actionCloseOthers = CreateActionAtPosition(m_menuFile, insertPostion, "Close Others", [this]() {
- auto documentIds = GetOpenDocumentIds();
- AZStd::erase(documentIds, GetCurrentDocumentId());
- CloseDocuments(documentIds);
- });
- m_menuFile->insertSeparator(insertPostion);
- insertPostion = !m_menuEdit->actions().empty() ? m_menuEdit->actions().front() : nullptr;
- m_actionUndo = CreateActionAtPosition(m_menuEdit, insertPostion, "&Undo", [this]() {
- const AZ::Uuid documentId = GetCurrentDocumentId();
- bool result = false;
- AtomToolsDocumentRequestBus::EventResult(result, documentId, &AtomToolsDocumentRequestBus::Events::Undo);
- if (!result)
- {
- SetStatusError(tr("Document undo failed: %1").arg(GetDocumentPath(documentId)).toUtf8().constData());
- }
- }, QKeySequence::Undo);
- m_actionRedo = CreateActionAtPosition(m_menuEdit, insertPostion, "&Redo", [this]() {
- const AZ::Uuid documentId = GetCurrentDocumentId();
- bool result = false;
- AtomToolsDocumentRequestBus::EventResult(result, documentId, &AtomToolsDocumentRequestBus::Events::Redo);
- if (!result)
- {
- SetStatusError(tr("Document redo failed: %1").arg(GetDocumentPath(documentId)).toUtf8().constData());
- }
- }, QKeySequence::Redo);
- m_menuEdit->insertSeparator(insertPostion);
- insertPostion = !m_menuView->actions().empty() ? m_menuView->actions().front() : nullptr;
- m_actionPreviousTab = CreateActionAtPosition(m_menuView, insertPostion, "&Previous Tab", [this]() {
- SelectPrevDocumentTab();
- }, 0x0 | Qt::CTRL | Qt::SHIFT | Qt::Key_Tab); //QKeySequence::PreviousChild is mapped incorrectly in Qt
- m_actionNextTab = CreateActionAtPosition(m_menuView, insertPostion, "&Next Tab", [this]() {
- SelectNextDocumentTab();
- }, 0x0 | Qt::CTRL | Qt::Key_Tab); //QKeySequence::NextChild works as expected but mirroring Previous
- m_menuView->insertSeparator(insertPostion);
- }
- bool AtomToolsDocumentMainWindow::SaveDocument(const AZ::Uuid& documentId)
- {
- AZStd::string documentPath;
- AtomToolsDocumentRequestBus::EventResult(documentPath, documentId, &AtomToolsDocumentRequestBus::Events::GetAbsolutePath);
- DocumentTypeInfo documentInfo;
- AtomToolsDocumentRequestBus::EventResult(documentInfo, documentId, &AtomToolsDocumentRequestBus::Events::GetDocumentTypeInfo);
- // Attempt to save using the current path if it is not empty and has a supported save extension.
- if (documentInfo.IsSupportedExtensionToSave(documentPath))
- {
- bool result = false;
- AtomToolsDocumentSystemRequestBus::EventResult(
- result, m_toolId, &AtomToolsDocumentSystemRequestBus::Events::SaveDocument, documentId);
- if (!result)
- {
- SetStatusError(tr("Document save failed: %1").arg(documentPath.c_str()).toUtf8().constData());
- return false;
- }
- return true;
- }
- // If the path is empty or the extension is not valid for saving, do a save as operation, which prompts the user for a path.
- if (const auto& savePath = GetSaveDocumentParams(documentPath, documentId); !savePath.empty())
- {
- bool result = false;
- AtomToolsDocumentSystemRequestBus::EventResult(
- result, m_toolId, &AtomToolsDocumentSystemRequestBus::Events::SaveDocumentAsCopy, documentId, savePath);
- if (!result)
- {
- SetStatusError(tr("Document save failed: %1").arg(documentPath.c_str()).toUtf8().constData());
- return false;
- }
- return true;
- }
- // Cancel the save if no valid path was selected by this point.
- return false;
- }
- bool AtomToolsDocumentMainWindow::CloseDocumentCheck(const AZ::Uuid& documentId)
- {
- AZStd::string documentPath;
- AtomToolsDocumentRequestBus::EventResult(documentPath, documentId, &AtomToolsDocumentRequestBus::Events::GetAbsolutePath);
- bool isModified = false;
- AtomToolsDocumentRequestBus::EventResult(isModified, documentId, &AtomToolsDocumentRequestBus::Events::IsModified);
- if (isModified)
- {
- auto selection = QMessageBox::question(
- GetToolMainWindow(),
- QObject::tr("Document has unsaved changes"),
- QObject::tr("Do you want to save changes to\n%1?").arg(documentPath.c_str()),
- QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel);
- if (selection == QMessageBox::Cancel)
- {
- AZ_TracePrintf("AtomToolsDocument", "Close document canceled: %s\n", documentPath.c_str());
- return false;
- }
- if (selection == QMessageBox::Yes)
- {
- if (!SaveDocument(documentId))
- {
- const QString title = QObject::tr("Document could not be closed");
- const QString text = QObject::tr("Close document failed because document was not saved: \n%1").arg(documentPath.c_str());
- AZ_Error("AtomToolsDocumentMainWindow", false, "%s: %s", title.toUtf8().constData(), text.toUtf8().constData());
- QMessageBox::critical(
- GetToolMainWindow(), title, QObject::tr("%1").arg(text));
- return false;
- }
- }
- }
- return true;
- }
- bool AtomToolsDocumentMainWindow::CloseDocuments(const AZStd::vector<AZ::Uuid>& documentIds)
- {
- for (const auto& documentId : documentIds)
- {
- if (!CloseDocumentCheck(documentId))
- {
- return false;
- }
- bool result = false;
- AtomToolsDocumentSystemRequestBus::EventResult(result, m_toolId, &AtomToolsDocumentSystemRequestBus::Events::CloseDocument, documentId);
- if (!result)
- {
- return false;
- }
- }
- return true;
- }
- const AZStd::vector<AZ::Uuid> AtomToolsDocumentMainWindow::GetOpenDocumentIds() const
- {
- AZStd::vector<AZ::Uuid> documentIds;
- documentIds.reserve(m_tabWidget->count());
- for (int index = 0; index < m_tabWidget->count(); ++index)
- {
- documentIds.push_back(GetDocumentTabId(index));
- }
- return documentIds;
- }
- void AtomToolsDocumentMainWindow::UpdateMenus(QMenuBar* menuBar)
- {
- Base::UpdateMenus(menuBar);
- const AZ::Uuid documentId = GetCurrentDocumentId();
- bool isOpen = false;
- AtomToolsDocumentRequestBus::EventResult(isOpen, documentId, &AtomToolsDocumentRequestBus::Events::IsOpen);
- bool canSaveAsChild = false;
- AtomToolsDocumentRequestBus::EventResult(canSaveAsChild, documentId, &AtomToolsDocumentRequestBus::Events::CanSaveAsChild);
- bool canUndo = false;
- AtomToolsDocumentRequestBus::EventResult(canUndo, documentId, &AtomToolsDocumentRequestBus::Events::CanUndo);
- bool canRedo = false;
- AtomToolsDocumentRequestBus::EventResult(canRedo, documentId, &AtomToolsDocumentRequestBus::Events::CanRedo);
- const bool hasTabs = m_tabWidget->count() > 0;
- // Update menu options
- m_actionClose->setEnabled(hasTabs);
- m_actionCloseAll->setEnabled(hasTabs);
- m_actionCloseOthers->setEnabled(hasTabs);
- m_actionSave->setEnabled(isOpen);
- m_actionSaveAsCopy->setEnabled(isOpen);
- m_actionSaveAsChild->setEnabled(canSaveAsChild);
- m_actionSaveAsChild->setVisible(canSaveAsChild);
- m_actionSaveAll->setEnabled(hasTabs);
- m_actionUndo->setEnabled(canUndo);
- m_actionRedo->setEnabled(canRedo);
- m_actionPreviousTab->setEnabled(m_tabWidget->count() > 1);
- m_actionNextTab->setEnabled(m_tabWidget->count() > 1);
- }
- void AtomToolsDocumentMainWindow::PopulateSettingsInspector(InspectorWidget* inspector) const
- {
- Base::PopulateSettingsInspector(inspector);
- m_documentSystemSettingsGroup = CreateSettingsPropertyGroup(
- "Document System Settings",
- "Document System Settings",
- { CreateSettingsPropertyValue(
- "/O3DE/AtomToolsFramework/AtomToolsDocumentSystem/DisplayWarningMessageDialogs",
- "Display Warning Message Dialogs",
- "Display message boxes for warnings opening documents",
- true),
- CreateSettingsPropertyValue(
- "/O3DE/AtomToolsFramework/AtomToolsDocumentSystem/DisplayErrorMessageDialogs",
- "Display Error Message Dialogs",
- "Display message boxes for errors opening documents",
- true),
- CreateSettingsPropertyValue(
- "/O3DE/AtomToolsFramework/AtomToolsDocumentSystem/EnableAutomaticReload",
- "Enable Automatic Reload",
- "Automatically reload documents after external modifications",
- true),
- CreateSettingsPropertyValue(
- "/O3DE/AtomToolsFramework/AtomToolsDocumentSystem/EnableAutomaticReloadPrompts",
- "Enable Automatic Reload Prompts",
- "Confirm before automatically reloading modified documents",
- true),
- CreateSettingsPropertyValue(
- "/O3DE/AtomToolsFramework/AtomToolsDocumentSystem/AutoSaveEnabled",
- "Enable Auto Save",
- "Automatically save documents after they are modified",
- false),
- CreateSettingsPropertyValue(
- "/O3DE/AtomToolsFramework/AtomToolsDocumentSystem/AutoSaveInterval",
- "Auto Save Interval",
- "How often (in milliseconds) auto save occurs",
- aznumeric_cast<AZ::s64>(250),
- aznumeric_cast<AZ::s64>(0),
- aznumeric_cast<AZ::s64>(1000)) });
- inspector->AddGroup(
- m_documentSystemSettingsGroup->m_name,
- m_documentSystemSettingsGroup->m_displayName,
- m_documentSystemSettingsGroup->m_description,
- new InspectorPropertyGroupWidget(
- m_documentSystemSettingsGroup.get(), m_documentSystemSettingsGroup.get(), azrtti_typeid<DynamicPropertyGroup>()));
- }
- void AtomToolsDocumentMainWindow::BuildCreateMenu(QAction* insertPostion)
- {
- DocumentTypeInfoVector documentTypes;
- AtomToolsDocumentSystemRequestBus::EventResult(
- documentTypes, m_toolId, &AtomToolsDocumentSystemRequestBus::Events::GetRegisteredDocumentTypes);
- // If there is more than one document type then we create a sub menu to insert all of the actions
- auto parentMenu = m_menuFile;
- if (documentTypes.size() > 1)
- {
- parentMenu = new QMenu("&New", this);
- m_menuFile->insertMenu(insertPostion, parentMenu);
- }
- bool isFirstDocumentTypeAdded = true;
- for (const auto& documentType : documentTypes)
- {
- const QString name = tr("New %1 Document...").arg(documentType.m_documentTypeName.c_str());
- CreateActionAtPosition(parentMenu, insertPostion, name, [documentType, toolId = m_toolId, this]() {
- // Open the create document dialog with labels and filters configured from the document type info.
- CreateDocumentDialog dialog(
- documentType, AZStd::string::format("%s/Assets", AZ::Utils::GetProjectPath().c_str()).c_str(), this);
- dialog.adjustSize();
- if (dialog.exec() == QDialog::Accepted)
- {
- AtomToolsDocumentSystemRequestBus::Event(
- toolId,
- &AtomToolsDocumentSystemRequestBus::Events::CreateDocumentFromFilePath,
- dialog.m_sourcePath.toUtf8().constData(),
- dialog.m_targetPath.toUtf8().constData());
- }
- }, isFirstDocumentTypeAdded ? QKeySequence::New : QKeySequence());
- isFirstDocumentTypeAdded = false;
- }
- }
- void AtomToolsDocumentMainWindow::BuildOpenMenu(QAction* insertPostion)
- {
- DocumentTypeInfoVector documentTypes;
- AtomToolsDocumentSystemRequestBus::EventResult(
- documentTypes, m_toolId, &AtomToolsDocumentSystemRequestBus::Events::GetRegisteredDocumentTypes);
- // If there is more than one document type then we create a sub menu to insert all of the actions
- auto parentMenu = m_menuFile;
- if (documentTypes.size() > 1)
- {
- parentMenu = new QMenu("&Open", this);
- m_menuFile->insertMenu(insertPostion, parentMenu);
- }
- bool isFirstDocumentTypeAdded = true;
- for (const auto& documentType : documentTypes)
- {
- if (!documentType.m_supportedExtensionsToOpen.empty())
- {
- // Create a menu action for each document type instead of one action for all document types to reduce the number of
- // extensions displayed in the dialog
- const QString name = tr("Open %1 Document...").arg(documentType.m_documentTypeName.c_str());
- CreateActionAtPosition(parentMenu, insertPostion, name, [documentType, toolId = m_toolId]() {
- // Visual Studio 2022 flags toolId as unused even though it is passed to the QueueFunction lambda below.
- // Use AZ_UNUSED to prevent the compiler error.
- AZ_UNUSED(toolId);
- // Open all files selected in the dialog
- const auto& paths =
- GetOpenFilePathsFromDialog({}, documentType.m_supportedExtensionsToOpen, documentType.m_documentTypeName, true);
- AZ::SystemTickBus::QueueFunction([toolId, paths]() {
- for (const auto& path : paths)
- {
- AtomToolsDocumentSystemRequestBus::Event(toolId, &AtomToolsDocumentSystemRequestBus::Events::OpenDocument, path);
- }
- });
- }, isFirstDocumentTypeAdded ? QKeySequence::Open : QKeySequence());
- isFirstDocumentTypeAdded = false;
- }
- }
- }
- void AtomToolsDocumentMainWindow::AddDocumentTabBar()
- {
- m_tabWidget = new AzQtComponents::TabWidget(centralWidget());
- m_tabWidget->setObjectName("TabWidget");
- m_tabWidget->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Preferred);
- m_tabWidget->setContentsMargins(0, 0, 0, 0);
- // The tab bar should only be visible if it has active documents
- m_tabWidget->setVisible(false);
- m_tabWidget->setTabBarAutoHide(false);
- m_tabWidget->setMovable(true);
- m_tabWidget->setTabsClosable(true);
- m_tabWidget->setUsesScrollButtons(true);
- // Update document tab styling to fix the close button and be conformant with similar windows
- AzQtComponents::TabWidget::applySecondaryStyle(m_tabWidget);
- // This signal will be triggered whenever a tab is added, removed, selected, clicked, dragged
- // When the last tab is removed tabIndex will be -1 and the document ID will be null
- // This should automatically clear the active document
- connect(m_tabWidget, &QTabWidget::currentChanged, this, [this]() {
- const AZ::Uuid documentId = GetCurrentDocumentId();
- AtomToolsDocumentNotificationBus::Event(m_toolId, &AtomToolsDocumentNotificationBus::Events::OnDocumentOpened, documentId);
- if (auto viewWidget = m_tabWidget->currentWidget())
- {
- viewWidget->setFocus();
- }
- });
- connect(m_tabWidget, &QTabWidget::tabCloseRequested, this, [this](int index) {
- CloseDocuments({ GetDocumentTabId(index) });
- });
- // Add context menu for right-clicking on tabs
- m_tabWidget->setContextMenuPolicy(Qt::ContextMenuPolicy::CustomContextMenu);
- connect(m_tabWidget, &QWidget::customContextMenuRequested, this, [this]() {
- OpenDocumentTabContextMenu();
- });
- centralWidget()->layout()->addWidget(m_tabWidget);
- }
- void AtomToolsDocumentMainWindow::UpdateRecentFileMenu()
- {
- m_menuOpenRecent->clear();
- AZStd::vector<AZStd::string> absolutePaths;
- AtomToolsDocumentSystemRequestBus::EventResult(
- absolutePaths, m_toolId, &AtomToolsDocumentSystemRequestBus::Handler::GetRecentFilePaths);
- for (const AZStd::string& path : absolutePaths)
- {
- if (QFile::exists(path.c_str()))
- {
- m_menuOpenRecent->addAction(tr("&%1: %2").arg(m_menuOpenRecent->actions().size()).arg(path.c_str()), [this, path]() {
- // Deferring execution with timer to not corrupt menu after document is opened.
- AZ::SystemTickBus::QueueFunction([toolId = m_toolId, path]() {
- AtomToolsDocumentSystemRequestBus::Event(toolId, &AtomToolsDocumentSystemRequestBus::Events::OpenDocument, path);
- });
- });
- }
- }
- m_menuOpenRecent->addAction(tr("Clear Recent Files"), [this]() {
- AZ::SystemTickBus::QueueFunction([toolId = m_toolId]() {
- AtomToolsDocumentSystemRequestBus::Event(toolId, &AtomToolsDocumentSystemRequestBus::Handler::ClearRecentFilePaths);
- });
- });
- }
- QString AtomToolsDocumentMainWindow::GetDocumentPath(const AZ::Uuid& documentId) const
- {
- AZStd::string absolutePath;
- AtomToolsDocumentRequestBus::EventResult(absolutePath, documentId, &AtomToolsDocumentRequestBus::Handler::GetAbsolutePath);
- return absolutePath.c_str();
- }
- AZ::Uuid AtomToolsDocumentMainWindow::GetDocumentTabId(const int tabIndex) const
- {
- const QVariant tabData = m_tabWidget->tabBar()->tabData(tabIndex);
- if (!tabData.isNull())
- {
- // We need to be able to convert between a UUID and a string to store and retrieve a document ID from the tab bar
- const QString documentIdString = tabData.toString();
- const QByteArray documentIdBytes = documentIdString.toUtf8();
- const AZ::Uuid documentId(documentIdBytes.data(), documentIdBytes.size());
- return documentId;
- }
- return AZ::Uuid::CreateNull();
- }
- AZ::Uuid AtomToolsDocumentMainWindow::GetCurrentDocumentId() const
- {
- return GetDocumentTabId(m_tabWidget->currentIndex());
- }
- int AtomToolsDocumentMainWindow::GetDocumentTabIndex(const AZ::Uuid& documentId) const
- {
- for (int tabIndex = 0; tabIndex < m_tabWidget->count(); ++tabIndex)
- {
- if (documentId == GetDocumentTabId(tabIndex))
- {
- return tabIndex;
- }
- }
- return -1;
- }
- bool AtomToolsDocumentMainWindow::HasDocumentTab(const AZ::Uuid& documentId) const
- {
- return GetDocumentTabIndex(documentId) >= 0;
- }
- bool AtomToolsDocumentMainWindow::AddDocumentTab(const AZ::Uuid& documentId, QWidget* viewWidget)
- {
- if (!documentId.IsNull() && viewWidget)
- {
- // Blocking signals from the tab bar so the currentChanged signal is not sent while a document is already being opened.
- // This prevents the OnDocumentOpened notification from being sent recursively.
- const QSignalBlocker blocker(m_tabWidget);
- // If a tab for this document already exists then select it instead of creating a new one
- if (const int tabIndex = GetDocumentTabIndex(documentId); tabIndex >= 0)
- {
- m_tabWidget->setVisible(true);
- m_tabWidget->setCurrentIndex(tabIndex);
- UpdateDocumentTab(documentId);
- delete viewWidget;
- return false;
- }
- // The user can manually reorder tabs which will invalidate any association by index.
- // We need to store the document ID with the tab using the tab instead of a separate mapping.
- const int tabIndex = m_tabWidget->addTab(viewWidget, QString());
- m_tabWidget->tabBar()->setTabData(tabIndex, QVariant(QString::fromUtf8(documentId.ToFixedString().c_str())));
- m_tabWidget->setVisible(true);
- m_tabWidget->setCurrentIndex(tabIndex);
- UpdateDocumentTab(documentId);
- QueueUpdateMenus(true);
- return true;
- }
- delete viewWidget;
- return false;
- }
- void AtomToolsDocumentMainWindow::RemoveDocumentTab(const AZ::Uuid& documentId)
- {
- // We are not blocking signals here because we want closing tabs to close the document and automatically select the next document.
- if (const int tabIndex = GetDocumentTabIndex(documentId); tabIndex >= 0)
- {
- // removeTab does not destroy the widget contained in a tab. It must be manually deleted.
- auto viewWidget = m_tabWidget->widget(tabIndex);
- m_tabWidget->removeTab(tabIndex);
- m_tabWidget->setVisible(m_tabWidget->count() > 0);
- m_tabWidget->repaint();
- delete viewWidget;
- QueueUpdateMenus(true);
- }
- }
- void AtomToolsDocumentMainWindow::UpdateDocumentTab(const AZ::Uuid& documentId)
- {
- // Whenever a document is opened, saved, or modified we need to update the tab label
- if (const int tabIndex = GetDocumentTabIndex(documentId); tabIndex >= 0)
- {
- bool isModified = false;
- AtomToolsDocumentRequestBus::EventResult(isModified, documentId, &AtomToolsDocumentRequestBus::Events::IsModified);
- AZStd::string absolutePath;
- AtomToolsDocumentRequestBus::EventResult(absolutePath, documentId, &AtomToolsDocumentRequestBus::Events::GetAbsolutePath);
- AZStd::string filename;
- AzFramework::StringFunc::Path::GetFullFileName(absolutePath.c_str(), filename);
- if (filename.empty())
- {
- filename = "(untitled)";
- }
- // We use an asterisk prepended to the file name to denote modified document.
- // Appending is standard and preferred but the tabs elide from the end (instead of middle) and cut it off.
- const AZStd::string label = isModified ? "* " + filename : filename;
- m_tabWidget->setTabText(tabIndex, label.c_str());
- m_tabWidget->setTabToolTip(tabIndex, absolutePath.c_str());
- m_tabWidget->repaint();
- }
- }
- void AtomToolsDocumentMainWindow::SelectPrevDocumentTab()
- {
- if (m_tabWidget->count() > 1)
- {
- // Adding count to wrap around when index <= 0
- m_tabWidget->setCurrentIndex((m_tabWidget->currentIndex() + m_tabWidget->count() - 1) % m_tabWidget->count());
- }
- }
- void AtomToolsDocumentMainWindow::SelectNextDocumentTab()
- {
- if (m_tabWidget->count() > 1)
- {
- m_tabWidget->setCurrentIndex((m_tabWidget->currentIndex() + 1) % m_tabWidget->count());
- }
- }
- void AtomToolsDocumentMainWindow::OpenDocumentTabContextMenu()
- {
- const QTabBar* tabBar = m_tabWidget->tabBar();
- const QPoint position = tabBar->mapFromGlobal(QCursor::pos());
- const int clickedTabIndex = tabBar->tabAt(position);
- if (const AZ::Uuid documentId = GetDocumentTabId(clickedTabIndex); !documentId.IsNull())
- {
- QMenu menu;
- PopulateTabContextMenu(documentId, menu);
- menu.exec(QCursor::pos());
- }
- }
- void AtomToolsDocumentMainWindow::PopulateTabContextMenu(const AZ::Uuid& documentId, QMenu& menu)
- {
- menu.addAction("Select", [this, documentId]() {
- AtomToolsDocumentNotificationBus::Event(m_toolId, &AtomToolsDocumentNotificationBus::Events::OnDocumentOpened, documentId);
- });
- menu.addAction("Close", [this, documentId]() {
- CloseDocuments({documentId});
- });
- menu.addAction("Close Others", [this, documentId]() {
- auto documentIds = GetOpenDocumentIds();
- AZStd::erase(documentIds, documentId);
- CloseDocuments(documentIds);
- })->setEnabled(m_tabWidget->tabBar()->count() > 1);
- }
- AZStd::string AtomToolsDocumentMainWindow::GetSaveDocumentParams(const AZStd::string& initialPath, const AZ::Uuid& documentId) const
- {
- DocumentTypeInfo documentType;
- AtomToolsDocumentRequestBus::EventResult(
- documentType, documentId, &AtomToolsDocumentRequestBus::Events::GetDocumentTypeInfo);
- return GetSaveFilePathFromDialog(initialPath, documentType.m_supportedExtensionsToSave, documentType.m_documentTypeName);
- }
- void AtomToolsDocumentMainWindow::OnDocumentOpened(const AZ::Uuid& documentId)
- {
- AZStd::string absolutePath;
- AtomToolsDocumentRequestBus::EventResult(absolutePath, documentId, &AtomToolsDocumentRequestBus::Events::GetAbsolutePath);
- UpdateDocumentTab(documentId);
- ActivateWindow();
- QueueUpdateMenus(true);
- // Whenever a document is opened or selected select the corresponding tab
- m_tabWidget->setCurrentIndex(GetDocumentTabIndex(documentId));
- if (!absolutePath.empty())
- {
- // Find and select the file path in the asset browser
- m_assetBrowser->SelectEntries(absolutePath);
- SetStatusMessage(tr("Document opened: %1").arg(absolutePath.c_str()).toUtf8().constData());
- }
- }
- void AtomToolsDocumentMainWindow::OnDocumentClosed(const AZ::Uuid& documentId)
- {
- RemoveDocumentTab(documentId);
- SetStatusMessage(tr("Document closed: %1").arg(GetDocumentPath(documentId)).toUtf8().constData());
- }
- void AtomToolsDocumentMainWindow::OnDocumentCleared(const AZ::Uuid& documentId)
- {
- UpdateDocumentTab(documentId);
- QueueUpdateMenus(true);
- SetStatusMessage(tr("Document cleared: %1").arg(GetDocumentPath(documentId)).toUtf8().constData());
- }
- void AtomToolsDocumentMainWindow::OnDocumentError(const AZ::Uuid& documentId)
- {
- UpdateDocumentTab(documentId);
- QueueUpdateMenus(true);
- SetStatusError(tr("Document error: %1").arg(GetDocumentPath(documentId)).toUtf8().constData());
- }
- void AtomToolsDocumentMainWindow::OnDocumentDestroyed(const AZ::Uuid& documentId)
- {
- RemoveDocumentTab(documentId);
- }
- void AtomToolsDocumentMainWindow::OnDocumentModified(const AZ::Uuid& documentId)
- {
- UpdateDocumentTab(documentId);
- }
- void AtomToolsDocumentMainWindow::OnDocumentUndoStateChanged(const AZ::Uuid& documentId)
- {
- if (documentId == GetCurrentDocumentId())
- {
- QueueUpdateMenus(false);
- }
- }
- void AtomToolsDocumentMainWindow::OnDocumentSaved(const AZ::Uuid& documentId)
- {
- UpdateDocumentTab(documentId);
- SetStatusMessage(tr("Document saved: %1").arg(GetDocumentPath(documentId)).toUtf8().constData());
- }
- void AtomToolsDocumentMainWindow::closeEvent(QCloseEvent* closeEvent)
- {
- if (!CloseDocuments(GetOpenDocumentIds()))
- {
- closeEvent->ignore();
- return;
- }
- closeEvent->accept();
- Base::closeEvent(closeEvent);
- }
- void AtomToolsDocumentMainWindow::dragEnterEvent(QDragEnterEvent* event)
- {
- // Check for files matching supported document types being dragged into the main window
- for (const AZStd::string& path : GetPathsFromMimeData(event->mimeData()))
- {
- DocumentTypeInfoVector documentTypes;
- AtomToolsDocumentSystemRequestBus::EventResult(
- documentTypes, m_toolId, &AtomToolsDocumentSystemRequestBus::Events::GetRegisteredDocumentTypes);
- for (const auto& documentType : documentTypes)
- {
- if (documentType.IsSupportedExtensionToOpen(path))
- {
- event->setAccepted(true);
- event->acceptProposedAction();
- Base::dragEnterEvent(event);
- return;
- }
- }
- }
- event->setAccepted(false);
- Base::dragEnterEvent(event);
- }
- void AtomToolsDocumentMainWindow::dragMoveEvent(QDragMoveEvent* event)
- {
- // Files dragged into the main window must only be accepted if they are within the client area
- event->setAccepted(centralWidget() && centralWidget()->geometry().contains(event->pos()));
- Base::dragMoveEvent(event);
- }
- void AtomToolsDocumentMainWindow::dragLeaveEvent(QDragLeaveEvent* event)
- {
- Base::dragLeaveEvent(event);
- }
- void AtomToolsDocumentMainWindow::dropEvent(QDropEvent* event)
- {
- // If supported document files are dragged into the main window client area attempt to open them
- if (centralWidget() && centralWidget()->geometry().contains(event->pos()))
- {
- AZStd::vector<AZStd::string> acceptedPaths;
- for (const AZStd::string& path : GetPathsFromMimeData(event->mimeData()))
- {
- DocumentTypeInfoVector documentTypes;
- AtomToolsDocumentSystemRequestBus::EventResult(
- documentTypes, m_toolId, &AtomToolsDocumentSystemRequestBus::Events::GetRegisteredDocumentTypes);
- for (const auto& documentType : documentTypes)
- {
- if (documentType.IsSupportedExtensionToOpen(path))
- {
- acceptedPaths.push_back(path);
- }
- }
- }
- if (!acceptedPaths.empty())
- {
- AZ::SystemTickBus::QueueFunction([toolId = m_toolId, acceptedPaths]() {
- for (const AZStd::string& path : acceptedPaths)
- {
- AtomToolsDocumentSystemRequestBus::Event(toolId, &AtomToolsDocumentSystemRequestBus::Events::OpenDocument, path);
- }
- });
- event->acceptProposedAction();
- }
- }
- Base::dropEvent(event);
- }
- template<typename Functor>
- QAction* AtomToolsDocumentMainWindow::CreateActionAtPosition(
- QMenu* menu, QAction* position, const QString& name, Functor fn, const QKeySequence& shortcut)
- {
- QAction* action = new QAction(name, menu);
- action->setShortcut(shortcut);
- action->setShortcutContext(Qt::WindowShortcut);
- QObject::connect(action, &QAction::triggered, menu, fn);
- menu->insertAction(position, action);
- return action;
- }
- } // namespace AtomToolsFramework
|