You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
511 lines
20 KiB
C++
511 lines
20 KiB
C++
/*
|
|
* Copyright (c) Contributors to the Open 3D Engine Project.
|
|
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
|
*
|
|
*/
|
|
|
|
#include <AtomToolsFramework/Debug/TraceRecorder.h>
|
|
#include <AtomToolsFramework/Document/AtomToolsDocumentNotificationBus.h>
|
|
#include <AtomToolsFramework/Document/AtomToolsDocumentRequestBus.h>
|
|
#include <AtomToolsFramework/Document/AtomToolsDocumentSystem.h>
|
|
#include <AtomToolsFramework/Document/AtomToolsDocumentSystemRequestBus.h>
|
|
#include <AtomToolsFramework/Util/Util.h>
|
|
#include <AzCore/RTTI/BehaviorContext.h>
|
|
#include <AzCore/Serialization/EditContext.h>
|
|
#include <AzCore/Serialization/SerializeContext.h>
|
|
#include <AzFramework/Asset/AssetSystemBus.h>
|
|
#include <AzFramework/StringFunc/StringFunc.h>
|
|
#include <AzToolsFramework/API/EditorAssetSystemAPI.h>
|
|
|
|
AZ_PUSH_DISABLE_WARNING(4251 4800, "-Wunknown-warning-option") // disable warnings spawned by QT
|
|
#include <QApplication>
|
|
#include <QMessageBox>
|
|
#include <QString>
|
|
#include <QTimer>
|
|
AZ_POP_DISABLE_WARNING
|
|
|
|
namespace AtomToolsFramework
|
|
{
|
|
void AtomToolsDocumentSystem::Reflect(AZ::ReflectContext* context)
|
|
{
|
|
if (auto serialize = azrtti_cast<AZ::SerializeContext*>(context))
|
|
{
|
|
serialize->Class<AtomToolsDocumentSystem>()
|
|
->Version(0);
|
|
|
|
if (auto editContext = serialize->GetEditContext())
|
|
{
|
|
editContext->Class<AtomToolsDocumentSystem>("AtomToolsDocumentSystem", "")
|
|
->ClassElement(AZ::Edit::ClassElements::EditorData, "")
|
|
->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC_CE("System"))
|
|
->Attribute(AZ::Edit::Attributes::AutoExpand, true)
|
|
;
|
|
}
|
|
}
|
|
|
|
if (auto behaviorContext = azrtti_cast<AZ::BehaviorContext*>(context))
|
|
{
|
|
behaviorContext->EBus<AtomToolsDocumentSystemRequestBus>("AtomToolsDocumentSystemRequestBus")
|
|
->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Common)
|
|
->Attribute(AZ::Script::Attributes::Category, "Editor")
|
|
->Attribute(AZ::Script::Attributes::Module, "atomtools")
|
|
->Event("CreateDocument", &AtomToolsDocumentSystemRequestBus::Events::CreateDocument)
|
|
->Event("DestroyDocument", &AtomToolsDocumentSystemRequestBus::Events::DestroyDocument)
|
|
->Event("OpenDocument", &AtomToolsDocumentSystemRequestBus::Events::OpenDocument)
|
|
->Event("CreateDocumentFromFile", &AtomToolsDocumentSystemRequestBus::Events::CreateDocumentFromFile)
|
|
->Event("CloseDocument", &AtomToolsDocumentSystemRequestBus::Events::CloseDocument)
|
|
->Event("CloseAllDocuments", &AtomToolsDocumentSystemRequestBus::Events::CloseAllDocuments)
|
|
->Event("CloseAllDocumentsExcept", &AtomToolsDocumentSystemRequestBus::Events::CloseAllDocumentsExcept)
|
|
->Event("SaveDocument", &AtomToolsDocumentSystemRequestBus::Events::SaveDocument)
|
|
->Event("SaveDocumentAsCopy", &AtomToolsDocumentSystemRequestBus::Events::SaveDocumentAsCopy)
|
|
->Event("SaveDocumentAsChild", &AtomToolsDocumentSystemRequestBus::Events::SaveDocumentAsChild)
|
|
->Event("SaveAllDocuments", &AtomToolsDocumentSystemRequestBus::Events::SaveAllDocuments)
|
|
->Event("GetDocumentCount", &AtomToolsDocumentSystemRequestBus::Events::GetDocumentCount)
|
|
;
|
|
}
|
|
}
|
|
|
|
AtomToolsDocumentSystem::AtomToolsDocumentSystem(const AZ::Crc32& toolId)
|
|
: m_toolId(toolId)
|
|
{
|
|
AtomToolsDocumentSystemRequestBus::Handler::BusConnect(m_toolId);
|
|
AtomToolsDocumentNotificationBus::Handler::BusConnect(m_toolId);
|
|
}
|
|
|
|
AtomToolsDocumentSystem::~AtomToolsDocumentSystem()
|
|
{
|
|
m_documentMap.clear();
|
|
AtomToolsDocumentNotificationBus::Handler::BusDisconnect();
|
|
AtomToolsDocumentSystemRequestBus::Handler::BusDisconnect();
|
|
}
|
|
|
|
void AtomToolsDocumentSystem::RegisterDocumentType(const AtomToolsDocumentFactoryCallback& documentCreator)
|
|
{
|
|
m_documentCreator = documentCreator;
|
|
}
|
|
|
|
AZ::Uuid AtomToolsDocumentSystem::CreateDocument()
|
|
{
|
|
if (!m_documentCreator)
|
|
{
|
|
AZ_Error("AtomToolsDocument", false, "Failed to create new document");
|
|
return AZ::Uuid::CreateNull();
|
|
}
|
|
|
|
AZStd::unique_ptr<AtomToolsDocument> document(m_documentCreator(m_toolId));
|
|
if (!document)
|
|
{
|
|
AZ_Error("AtomToolsDocument", false, "Failed to create new document");
|
|
return AZ::Uuid::CreateNull();
|
|
}
|
|
|
|
AZ::Uuid documentId = document->GetId();
|
|
m_documentMap.emplace(documentId, document.release());
|
|
return documentId;
|
|
}
|
|
|
|
bool AtomToolsDocumentSystem::DestroyDocument(const AZ::Uuid& documentId)
|
|
{
|
|
return m_documentMap.erase(documentId) != 0;
|
|
}
|
|
|
|
AZ::Uuid AtomToolsDocumentSystem::OpenDocument(AZStd::string_view sourcePath)
|
|
{
|
|
return OpenDocumentImpl(sourcePath, true);
|
|
}
|
|
|
|
AZ::Uuid AtomToolsDocumentSystem::CreateDocumentFromFile(AZStd::string_view sourcePath, AZStd::string_view targetPath)
|
|
{
|
|
const AZ::Uuid documentId = OpenDocumentImpl(sourcePath, false);
|
|
if (documentId.IsNull())
|
|
{
|
|
return AZ::Uuid::CreateNull();
|
|
}
|
|
|
|
if (!SaveDocumentAsChild(documentId, targetPath))
|
|
{
|
|
CloseDocument(documentId);
|
|
return AZ::Uuid::CreateNull();
|
|
}
|
|
|
|
// Send document open notification after creating new one
|
|
AtomToolsDocumentNotificationBus::Event(m_toolId, &AtomToolsDocumentNotificationBus::Events::OnDocumentOpened, documentId);
|
|
return documentId;
|
|
}
|
|
|
|
bool AtomToolsDocumentSystem::CloseDocument(const AZ::Uuid& documentId)
|
|
{
|
|
bool isOpen = false;
|
|
AtomToolsDocumentRequestBus::EventResult(isOpen, documentId, &AtomToolsDocumentRequestBus::Events::IsOpen);
|
|
if (!isOpen)
|
|
{
|
|
// immediately destroy unopened documents
|
|
DestroyDocument(documentId);
|
|
return true;
|
|
}
|
|
|
|
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(QApplication::activeWindow(),
|
|
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", documentPath.c_str());
|
|
return false;
|
|
}
|
|
if (selection == QMessageBox::Yes)
|
|
{
|
|
if (!SaveDocument(documentId))
|
|
{
|
|
AZ_Error("AtomToolsDocument", false, "Close document failed because document was not saved: %s", documentPath.c_str());
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
TraceRecorder traceRecorder(m_maxMessageBoxLineCount);
|
|
|
|
bool closeResult = true;
|
|
AtomToolsDocumentRequestBus::EventResult(closeResult, documentId, &AtomToolsDocumentRequestBus::Events::Close);
|
|
if (!closeResult)
|
|
{
|
|
QMessageBox::critical(
|
|
QApplication::activeWindow(),
|
|
QObject::tr("Document could not be closed"),
|
|
QObject::tr("Failed to close: \n%1\n\n%2").arg(documentPath.c_str()).arg(traceRecorder.GetDump().c_str()));
|
|
return false;
|
|
}
|
|
|
|
DestroyDocument(documentId);
|
|
return true;
|
|
}
|
|
|
|
bool AtomToolsDocumentSystem::CloseAllDocuments()
|
|
{
|
|
bool result = true;
|
|
auto documentMap = m_documentMap;
|
|
for (const auto& documentPair : documentMap)
|
|
{
|
|
if (!CloseDocument(documentPair.first))
|
|
{
|
|
result = false;
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
bool AtomToolsDocumentSystem::CloseAllDocumentsExcept(const AZ::Uuid& documentId)
|
|
{
|
|
bool result = true;
|
|
auto documentMap = m_documentMap;
|
|
for (const auto& documentPair : documentMap)
|
|
{
|
|
if (documentPair.first != documentId)
|
|
{
|
|
if (!CloseDocument(documentPair.first))
|
|
{
|
|
result = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
bool AtomToolsDocumentSystem::SaveDocument(const AZ::Uuid& documentId)
|
|
{
|
|
AZStd::string saveDocumentPath;
|
|
AtomToolsDocumentRequestBus::EventResult(saveDocumentPath, documentId, &AtomToolsDocumentRequestBus::Events::GetAbsolutePath);
|
|
|
|
if (saveDocumentPath.empty() || !AzFramework::StringFunc::Path::Normalize(saveDocumentPath))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
const QFileInfo saveInfo(saveDocumentPath.c_str());
|
|
if (saveInfo.exists() && !saveInfo.isWritable())
|
|
{
|
|
QMessageBox::critical(
|
|
QApplication::activeWindow(),
|
|
QObject::tr("Document could not be saved"),
|
|
QObject::tr("Document could not be overwritten:\n%1").arg(saveDocumentPath.c_str()));
|
|
return false;
|
|
}
|
|
|
|
TraceRecorder traceRecorder(m_maxMessageBoxLineCount);
|
|
|
|
bool result = false;
|
|
AtomToolsDocumentRequestBus::EventResult(result, documentId, &AtomToolsDocumentRequestBus::Events::Save);
|
|
if (!result)
|
|
{
|
|
QMessageBox::critical(
|
|
QApplication::activeWindow(),
|
|
QObject::tr("Document could not be saved"),
|
|
QObject::tr("Failed to save: \n%1\n\n%2").arg(saveDocumentPath.c_str()).arg(traceRecorder.GetDump().c_str()));
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool AtomToolsDocumentSystem::SaveDocumentAsCopy(const AZ::Uuid& documentId, AZStd::string_view targetPath)
|
|
{
|
|
AZStd::string saveDocumentPath = targetPath;
|
|
if (saveDocumentPath.empty() || !AzFramework::StringFunc::Path::Normalize(saveDocumentPath))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
const QFileInfo saveInfo(saveDocumentPath.c_str());
|
|
if (saveInfo.exists() && !saveInfo.isWritable())
|
|
{
|
|
QMessageBox::critical(QApplication::activeWindow(),
|
|
QObject::tr("Document could not be saved"),
|
|
QObject::tr("Document could not be overwritten:\n%1").arg(saveDocumentPath.c_str()));
|
|
return false;
|
|
}
|
|
|
|
TraceRecorder traceRecorder(m_maxMessageBoxLineCount);
|
|
|
|
bool result = false;
|
|
AtomToolsDocumentRequestBus::EventResult(result, documentId, &AtomToolsDocumentRequestBus::Events::SaveAsCopy, saveDocumentPath);
|
|
if (!result)
|
|
{
|
|
QMessageBox::critical(
|
|
QApplication::activeWindow(),
|
|
QObject::tr("Document could not be saved"),
|
|
QObject::tr("Failed to save: \n%1\n\n%2").arg(saveDocumentPath.c_str()).arg(traceRecorder.GetDump().c_str()));
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool AtomToolsDocumentSystem::SaveDocumentAsChild(const AZ::Uuid& documentId, AZStd::string_view targetPath)
|
|
{
|
|
AZStd::string saveDocumentPath = targetPath;
|
|
if (saveDocumentPath.empty() || !AzFramework::StringFunc::Path::Normalize(saveDocumentPath))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
const QFileInfo saveInfo(saveDocumentPath.c_str());
|
|
if (saveInfo.exists() && !saveInfo.isWritable())
|
|
{
|
|
QMessageBox::critical(
|
|
QApplication::activeWindow(),
|
|
QObject::tr("Document could not be saved"),
|
|
QObject::tr("Document could not be overwritten:\n%1").arg(saveDocumentPath.c_str()));
|
|
return false;
|
|
}
|
|
|
|
TraceRecorder traceRecorder(m_maxMessageBoxLineCount);
|
|
|
|
bool result = false;
|
|
AtomToolsDocumentRequestBus::EventResult(result, documentId, &AtomToolsDocumentRequestBus::Events::SaveAsChild, saveDocumentPath);
|
|
if (!result)
|
|
{
|
|
QMessageBox::critical(
|
|
QApplication::activeWindow(),
|
|
QObject::tr("Document could not be saved"),
|
|
QObject::tr("Failed to save: \n%1\n\n%2").arg(saveDocumentPath.c_str()).arg(traceRecorder.GetDump().c_str()));
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool AtomToolsDocumentSystem::SaveAllDocuments()
|
|
{
|
|
bool result = true;
|
|
for (const auto& documentPair : m_documentMap)
|
|
{
|
|
if (!SaveDocument(documentPair.first))
|
|
{
|
|
result = false;
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
AZ::u32 AtomToolsDocumentSystem::GetDocumentCount() const
|
|
{
|
|
return aznumeric_cast<AZ::u32>(m_documentMap.size());
|
|
}
|
|
|
|
|
|
void AtomToolsDocumentSystem::OnDocumentExternallyModified(const AZ::Uuid& documentId)
|
|
{
|
|
m_documentIdsWithExternalChanges.insert(documentId);
|
|
QueueReopenDocuments();
|
|
}
|
|
|
|
void AtomToolsDocumentSystem::OnDocumentDependencyModified(const AZ::Uuid& documentId)
|
|
{
|
|
m_documentIdsWithDependencyChanges.insert(documentId);
|
|
QueueReopenDocuments();
|
|
}
|
|
|
|
void AtomToolsDocumentSystem::QueueReopenDocuments()
|
|
{
|
|
if (!m_queueReopenDocuments)
|
|
{
|
|
m_queueReopenDocuments = true;
|
|
QTimer::singleShot(0, [this] { ReopenDocuments(); });
|
|
}
|
|
}
|
|
|
|
void AtomToolsDocumentSystem::ReopenDocuments()
|
|
{
|
|
const bool enableHotReload = GetSettingsValue<bool>("/O3DE/AtomToolsFramework/AtomToolsDocumentSystem/EnableHotReload", true);
|
|
if (!enableHotReload)
|
|
{
|
|
m_documentIdsWithDependencyChanges.clear();
|
|
m_documentIdsWithExternalChanges.clear();
|
|
m_queueReopenDocuments = false;
|
|
}
|
|
|
|
const bool enableHotReloadPrompts =
|
|
GetSettingsValue<bool>("/O3DE/AtomToolsFramework/AtomToolsDocumentSystem/EnableHotReloadPrompts", true);
|
|
|
|
for (const AZ::Uuid& documentId : m_documentIdsWithExternalChanges)
|
|
{
|
|
m_documentIdsWithDependencyChanges.erase(documentId);
|
|
|
|
AZStd::string documentPath;
|
|
AtomToolsDocumentRequestBus::EventResult(documentPath, documentId, &AtomToolsDocumentRequestBus::Events::GetAbsolutePath);
|
|
|
|
if (enableHotReloadPrompts &&
|
|
(QMessageBox::question(QApplication::activeWindow(),
|
|
QObject::tr("Document was externally modified"),
|
|
QObject::tr("Would you like to reopen the document:\n%1?").arg(documentPath.c_str()),
|
|
QMessageBox::Yes | QMessageBox::No) != QMessageBox::Yes))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
TraceRecorder traceRecorder(m_maxMessageBoxLineCount);
|
|
|
|
bool openResult = false;
|
|
AtomToolsDocumentRequestBus::EventResult(openResult, documentId, &AtomToolsDocumentRequestBus::Events::Open, documentPath);
|
|
if (!openResult)
|
|
{
|
|
QMessageBox::critical(
|
|
QApplication::activeWindow(),
|
|
QObject::tr("Document could not be opened"),
|
|
QObject::tr("Failed to open: \n%1\n\n%2").arg(documentPath.c_str()).arg(traceRecorder.GetDump().c_str()));
|
|
CloseDocument(documentId);
|
|
}
|
|
}
|
|
|
|
for (const AZ::Uuid& documentId : m_documentIdsWithDependencyChanges)
|
|
{
|
|
AZStd::string documentPath;
|
|
AtomToolsDocumentRequestBus::EventResult(documentPath, documentId, &AtomToolsDocumentRequestBus::Events::GetAbsolutePath);
|
|
|
|
if (enableHotReloadPrompts &&
|
|
(QMessageBox::question(QApplication::activeWindow(),
|
|
QObject::tr("Document dependencies have changed"),
|
|
QObject::tr("Would you like to update the document with these changes:\n%1?").arg(documentPath.c_str()),
|
|
QMessageBox::Yes | QMessageBox::No) != QMessageBox::Yes))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
TraceRecorder traceRecorder(m_maxMessageBoxLineCount);
|
|
|
|
bool openResult = false;
|
|
AtomToolsDocumentRequestBus::EventResult(openResult, documentId, &AtomToolsDocumentRequestBus::Events::Reopen);
|
|
if (!openResult)
|
|
{
|
|
QMessageBox::critical(
|
|
QApplication::activeWindow(),
|
|
QObject::tr("Document could not be opened"),
|
|
QObject::tr("Failed to open: \n%1\n\n%2").arg(documentPath.c_str()).arg(traceRecorder.GetDump().c_str()));
|
|
CloseDocument(documentId);
|
|
}
|
|
}
|
|
|
|
m_documentIdsWithDependencyChanges.clear();
|
|
m_documentIdsWithExternalChanges.clear();
|
|
m_queueReopenDocuments = false;
|
|
}
|
|
|
|
AZ::Uuid AtomToolsDocumentSystem::OpenDocumentImpl(AZStd::string_view sourcePath, bool checkIfAlreadyOpen)
|
|
{
|
|
AZStd::string requestedPath = sourcePath;
|
|
if (requestedPath.empty())
|
|
{
|
|
return AZ::Uuid::CreateNull();
|
|
}
|
|
|
|
if (!AzFramework::StringFunc::Path::Normalize(requestedPath))
|
|
{
|
|
QMessageBox::critical(QApplication::activeWindow(),
|
|
QObject::tr("Document could not be opened"),
|
|
QObject::tr("Document path is invalid:\n%1").arg(requestedPath.c_str()));
|
|
return AZ::Uuid::CreateNull();
|
|
}
|
|
|
|
// Determine if the file is already open and select it
|
|
if (checkIfAlreadyOpen)
|
|
{
|
|
for (const auto& documentPair : m_documentMap)
|
|
{
|
|
AZStd::string openDocumentPath;
|
|
AtomToolsDocumentRequestBus::EventResult(openDocumentPath, documentPair.first, &AtomToolsDocumentRequestBus::Events::GetAbsolutePath);
|
|
if (openDocumentPath == requestedPath)
|
|
{
|
|
AtomToolsDocumentNotificationBus::Event(
|
|
m_toolId, &AtomToolsDocumentNotificationBus::Events::OnDocumentOpened, documentPair.first);
|
|
return documentPair.first;
|
|
}
|
|
}
|
|
}
|
|
|
|
TraceRecorder traceRecorder(m_maxMessageBoxLineCount);
|
|
|
|
AZ::Uuid documentId = CreateDocument();
|
|
if (documentId.IsNull())
|
|
{
|
|
QMessageBox::critical(
|
|
QApplication::activeWindow(),
|
|
QObject::tr("Document could not be opened"),
|
|
QObject::tr("Failed to create: \n%1\n\n%2").arg(requestedPath.c_str()).arg(traceRecorder.GetDump().c_str()));
|
|
return AZ::Uuid::CreateNull();
|
|
}
|
|
|
|
bool openResult = false;
|
|
AtomToolsDocumentRequestBus::EventResult(openResult, documentId, &AtomToolsDocumentRequestBus::Events::Open, requestedPath);
|
|
if (!openResult)
|
|
{
|
|
QMessageBox::critical(
|
|
QApplication::activeWindow(),
|
|
QObject::tr("Document could not be opened"),
|
|
QObject::tr("Failed to open: \n%1\n\n%2").arg(requestedPath.c_str()).arg(traceRecorder.GetDump().c_str()));
|
|
DestroyDocument(documentId);
|
|
return AZ::Uuid::CreateNull();
|
|
}
|
|
else if (traceRecorder.GetWarningCount(true) > 0)
|
|
{
|
|
QMessageBox::warning(
|
|
QApplication::activeWindow(),
|
|
QObject::tr("Document opened with warnings"),
|
|
QObject::tr("Warnings encountered: \n%1\n\n%2").arg(requestedPath.c_str()).arg(traceRecorder.GetDump().c_str()));
|
|
}
|
|
|
|
return documentId;
|
|
}
|
|
} // namespace AtomToolsFramework
|