3
0

AtomToolsDocumentSystem.cpp 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686
  1. /*
  2. * Copyright (c) Contributors to the Open 3D Engine Project.
  3. * For complete copyright and license terms please see the LICENSE at the root of this distribution.
  4. *
  5. * SPDX-License-Identifier: Apache-2.0 OR MIT
  6. *
  7. */
  8. #include <AtomToolsFramework/Debug/TraceRecorder.h>
  9. #include <AtomToolsFramework/Document/AtomToolsDocumentNotificationBus.h>
  10. #include <AtomToolsFramework/Document/AtomToolsDocumentRequestBus.h>
  11. #include <AtomToolsFramework/Document/AtomToolsDocumentSystem.h>
  12. #include <AtomToolsFramework/Document/AtomToolsDocumentSystemRequestBus.h>
  13. #include <AtomToolsFramework/Util/Util.h>
  14. #include <AzCore/RTTI/BehaviorContext.h>
  15. #include <AzCore/Serialization/EditContext.h>
  16. #include <AzCore/Serialization/SerializeContext.h>
  17. #include <AzFramework/Asset/AssetSystemBus.h>
  18. #include <AzFramework/StringFunc/StringFunc.h>
  19. #include <AzToolsFramework/API/EditorAssetSystemAPI.h>
  20. AZ_PUSH_DISABLE_WARNING(4251 4800, "-Wunknown-warning-option") // disable warnings spawned by QT
  21. #include <QApplication>
  22. #include <QMessageBox>
  23. #include <QString>
  24. #include <QTimer>
  25. AZ_POP_DISABLE_WARNING
  26. namespace AtomToolsFramework
  27. {
  28. void DisplayErrorMessage(QWidget* parent, const QString& title, const QString& text)
  29. {
  30. AZ_Error("AtomToolsDocumentSystem", false, "%s: %s", title.toUtf8().constData(), text.toUtf8().constData());
  31. if (GetSettingsValue<bool>("/O3DE/AtomToolsFramework/AtomToolsDocumentSystem/DisplayErrorMessageDialogs", true))
  32. {
  33. QMessageBox::critical(parent, title, QObject::tr("%1\nThese messages can be disabled from settings.").arg(text));
  34. }
  35. }
  36. void DisplayWarningMessage(QWidget* parent, const QString& title, const QString& text)
  37. {
  38. AZ_Warning("AtomToolsDocumentSystem", false, "%s: %s", title.toUtf8().constData(), text.toUtf8().constData());
  39. if (GetSettingsValue<bool>("/O3DE/AtomToolsFramework/AtomToolsDocumentSystem/DisplayWarningMessageDialogs", true))
  40. {
  41. QMessageBox::warning(parent, title, QObject::tr("%1\nThese messages can be disabled from settings.").arg(text));
  42. }
  43. }
  44. void AtomToolsDocumentSystem::Reflect(AZ::ReflectContext* context)
  45. {
  46. if (auto serialize = azrtti_cast<AZ::SerializeContext*>(context))
  47. {
  48. serialize->Class<AtomToolsDocumentSystem>()
  49. ->Version(0);
  50. if (auto editContext = serialize->GetEditContext())
  51. {
  52. editContext->Class<AtomToolsDocumentSystem>("AtomToolsDocumentSystem", "")
  53. ->ClassElement(AZ::Edit::ClassElements::EditorData, "")
  54. ->Attribute(AZ::Edit::Attributes::AutoExpand, true)
  55. ;
  56. }
  57. }
  58. if (auto behaviorContext = azrtti_cast<AZ::BehaviorContext*>(context))
  59. {
  60. behaviorContext->EBus<AtomToolsDocumentSystemRequestBus>("AtomToolsDocumentSystemRequestBus")
  61. ->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Common)
  62. ->Attribute(AZ::Script::Attributes::Category, "Editor")
  63. ->Attribute(AZ::Script::Attributes::Module, "atomtools")
  64. ->Event("CreateDocumentFromTypeName", &AtomToolsDocumentSystemRequestBus::Events::CreateDocumentFromTypeName)
  65. ->Event("CreateDocumentFromFileType", &AtomToolsDocumentSystemRequestBus::Events::CreateDocumentFromFileType)
  66. ->Event("CreateDocumentFromFilePath", &AtomToolsDocumentSystemRequestBus::Events::CreateDocumentFromFilePath)
  67. ->Event("DestroyDocument", &AtomToolsDocumentSystemRequestBus::Events::DestroyDocument)
  68. ->Event("OpenDocument", &AtomToolsDocumentSystemRequestBus::Events::OpenDocument)
  69. ->Event("CloseDocument", &AtomToolsDocumentSystemRequestBus::Events::CloseDocument)
  70. ->Event("CloseAllDocuments", &AtomToolsDocumentSystemRequestBus::Events::CloseAllDocuments)
  71. ->Event("CloseAllDocumentsExcept", &AtomToolsDocumentSystemRequestBus::Events::CloseAllDocumentsExcept)
  72. ->Event("SaveDocument", &AtomToolsDocumentSystemRequestBus::Events::SaveDocument)
  73. ->Event("SaveDocumentAsCopy", &AtomToolsDocumentSystemRequestBus::Events::SaveDocumentAsCopy)
  74. ->Event("SaveDocumentAsChild", &AtomToolsDocumentSystemRequestBus::Events::SaveDocumentAsChild)
  75. ->Event("SaveAllDocuments", &AtomToolsDocumentSystemRequestBus::Events::SaveAllDocuments)
  76. ->Event("SaveAllModifiedDocuments", &AtomToolsDocumentSystemRequestBus::Events::SaveAllModifiedDocuments)
  77. ->Event("QueueReopenModifiedDocuments", &AtomToolsDocumentSystemRequestBus::Events::QueueReopenModifiedDocuments)
  78. ->Event("ReopenModifiedDocuments", &AtomToolsDocumentSystemRequestBus::Events::ReopenModifiedDocuments)
  79. ->Event("GetDocumentCount", &AtomToolsDocumentSystemRequestBus::Events::GetDocumentCount)
  80. ->Event("IsDocumentOpen", &AtomToolsDocumentSystemRequestBus::Events::IsDocumentOpen)
  81. ->Event("AddRecentFilePath", &AtomToolsDocumentSystemRequestBus::Events::AddRecentFilePath)
  82. ->Event("ClearRecentFilePaths", &AtomToolsDocumentSystemRequestBus::Events::ClearRecentFilePaths)
  83. ->Event("SetRecentFilePaths", &AtomToolsDocumentSystemRequestBus::Events::SetRecentFilePaths)
  84. ->Event("GetRecentFilePaths", &AtomToolsDocumentSystemRequestBus::Events::GetRecentFilePaths)
  85. ;
  86. }
  87. }
  88. AtomToolsDocumentSystem::AtomToolsDocumentSystem(const AZ::Crc32& toolId)
  89. : m_toolId(toolId)
  90. {
  91. AtomToolsDocumentSystemRequestBus::Handler::BusConnect(m_toolId);
  92. AtomToolsDocumentNotificationBus::Handler::BusConnect(m_toolId);
  93. }
  94. AtomToolsDocumentSystem::~AtomToolsDocumentSystem()
  95. {
  96. m_documentMap.clear();
  97. AtomToolsDocumentNotificationBus::Handler::BusDisconnect();
  98. AtomToolsDocumentSystemRequestBus::Handler::BusDisconnect();
  99. }
  100. void AtomToolsDocumentSystem::RegisterDocumentType(const DocumentTypeInfo& documentType)
  101. {
  102. m_documentTypes.push_back(documentType);
  103. }
  104. const DocumentTypeInfoVector& AtomToolsDocumentSystem::GetRegisteredDocumentTypes() const
  105. {
  106. return m_documentTypes;
  107. }
  108. AZ::Uuid AtomToolsDocumentSystem::CreateDocumentFromType(const DocumentTypeInfo& documentType)
  109. {
  110. AZStd::unique_ptr<AtomToolsDocumentRequests> document(documentType.CreateDocument(m_toolId));
  111. if (!document)
  112. {
  113. DisplayErrorMessage(
  114. GetToolMainWindow(),
  115. QObject::tr("Document could not be created"),
  116. QObject::tr("Could not create document using type: %1").arg(documentType.m_documentTypeName.c_str()));
  117. return AZ::Uuid::CreateNull();
  118. }
  119. const AZ::Uuid documentId = document->GetId();
  120. m_documentMap.emplace(documentId, document.release());
  121. documentType.CreateDocumentView(m_toolId, documentId);
  122. return documentId;
  123. }
  124. AZ::Uuid AtomToolsDocumentSystem::CreateDocumentFromTypeName(const AZStd::string& documentTypeName)
  125. {
  126. for (const auto& documentType : m_documentTypes)
  127. {
  128. if (AZ::StringFunc::Equal(documentType.m_documentTypeName, documentTypeName))
  129. {
  130. return CreateDocumentFromType(documentType);
  131. }
  132. }
  133. return AZ::Uuid();
  134. }
  135. AZ::Uuid AtomToolsDocumentSystem::CreateDocumentFromFileType(const AZStd::string& path)
  136. {
  137. for (const auto& documentType : m_documentTypes)
  138. {
  139. if (documentType.IsSupportedExtensionToCreate(path) || documentType.IsSupportedExtensionToOpen(path))
  140. {
  141. return CreateDocumentFromType(documentType);
  142. }
  143. }
  144. return AZ::Uuid();
  145. }
  146. AZ::Uuid AtomToolsDocumentSystem::CreateDocumentFromFilePath(const AZStd::string& sourcePath, const AZStd::string& targetPath)
  147. {
  148. // This function attempts to create a new document in a couple of different ways.
  149. // If a source path is specified then it will attempt to create a document using the source path extension and automatically open
  150. // the source file. This mechanism supports creating new documents from pre-existing templates or using the source document as a
  151. // parent. If no source file is specified, an attempt will be made to create the document using the target path extension to
  152. // determine the document type. If a target path is specified then the new document will also automatically be saved to that
  153. // location.
  154. TraceRecorder traceRecorder(m_maxMessageBoxLineCount);
  155. AZStd::string openPath = sourcePath;
  156. if (!openPath.empty() && !ValidateDocumentPath(openPath))
  157. {
  158. DisplayErrorMessage(
  159. GetToolMainWindow(),
  160. QObject::tr("Document could not be created"),
  161. QObject::tr("Document path is invalid, not in a supported project or gem folder, or marked as non-editable:\n%1").arg(openPath.c_str()));
  162. return AZ::Uuid::CreateNull();
  163. }
  164. AZStd::string savePath = targetPath;
  165. if (!savePath.empty() && !ValidateDocumentPath(savePath))
  166. {
  167. DisplayErrorMessage(
  168. GetToolMainWindow(),
  169. QObject::tr("Document could not be created"),
  170. QObject::tr("Document path is invalid, not in a supported project or gem folder, or marked as non-editable:\n%1").arg(savePath.c_str()));
  171. return AZ::Uuid::CreateNull();
  172. }
  173. const AZStd::string& createPath = !openPath.empty() ? openPath : savePath;
  174. const AZ::Uuid documentId = CreateDocumentFromFileType(createPath);
  175. if (documentId.IsNull())
  176. {
  177. DisplayErrorMessage(
  178. GetToolMainWindow(),
  179. QObject::tr("Document could not be created"),
  180. QObject::tr("Failed to create document from file type: \n%1\n\n%2").arg(createPath.c_str()).arg(traceRecorder.GetDump().c_str()));
  181. return AZ::Uuid::CreateNull();
  182. }
  183. if (!openPath.empty())
  184. {
  185. bool openResult = false;
  186. AtomToolsDocumentRequestBus::EventResult(openResult, documentId, &AtomToolsDocumentRequestBus::Events::Open, openPath);
  187. if (!openResult)
  188. {
  189. DisplayErrorMessage(
  190. GetToolMainWindow(),
  191. QObject::tr("Document could not be opened"),
  192. QObject::tr("Failed to open: \n%1\n\n%2").arg(openPath.c_str()).arg(traceRecorder.GetDump().c_str()));
  193. DestroyDocument(documentId);
  194. return AZ::Uuid::CreateNull();
  195. }
  196. }
  197. if (!savePath.empty())
  198. {
  199. if (!SaveDocumentAsChild(documentId, savePath))
  200. {
  201. CloseDocument(documentId);
  202. return AZ::Uuid::CreateNull();
  203. }
  204. AddRecentFilePath(savePath);
  205. }
  206. else
  207. {
  208. AddRecentFilePath(openPath);
  209. }
  210. // Send document open notification after creating new one
  211. AtomToolsDocumentNotificationBus::Event(m_toolId, &AtomToolsDocumentNotificationBus::Events::OnDocumentOpened, documentId);
  212. if (traceRecorder.GetWarningCount(true) > 0)
  213. {
  214. DisplayWarningMessage(
  215. GetToolMainWindow(),
  216. QObject::tr("Document opened with warnings"),
  217. QObject::tr("Warnings encountered: \n%1\n\n%2").arg(openPath.c_str()).arg(traceRecorder.GetDump().c_str()));
  218. }
  219. return documentId;
  220. }
  221. bool AtomToolsDocumentSystem::DestroyDocument(const AZ::Uuid& documentId)
  222. {
  223. return m_documentMap.erase(documentId) != 0;
  224. }
  225. AZ::Uuid AtomToolsDocumentSystem::OpenDocument(const AZStd::string& sourcePath)
  226. {
  227. AZStd::string openPath = sourcePath;
  228. if (!ValidateDocumentPath(openPath))
  229. {
  230. DisplayErrorMessage(
  231. GetToolMainWindow(),
  232. QObject::tr("Document could not be opened"),
  233. QObject::tr("Document path is invalid, not in a supported project or gem folder, or marked as non-editable:\n%1").arg(openPath.c_str()));
  234. return AZ::Uuid::CreateNull();
  235. }
  236. // Determine if the file is already open and select it
  237. for (const auto& documentPair : m_documentMap)
  238. {
  239. const auto& documentId = documentPair.first;
  240. AZStd::string openDocumentPath;
  241. AtomToolsDocumentRequestBus::EventResult(openDocumentPath, documentId, &AtomToolsDocumentRequestBus::Events::GetAbsolutePath);
  242. if (AZ::StringFunc::Equal(openDocumentPath, openPath))
  243. {
  244. AddRecentFilePath(openPath);
  245. AtomToolsDocumentNotificationBus::Event(m_toolId, &AtomToolsDocumentNotificationBus::Events::OnDocumentOpened, documentId);
  246. return documentId;
  247. }
  248. }
  249. return CreateDocumentFromFilePath(openPath, {});
  250. }
  251. bool AtomToolsDocumentSystem::CloseDocument(const AZ::Uuid& documentId)
  252. {
  253. AZStd::string documentPath;
  254. AtomToolsDocumentRequestBus::EventResult(documentPath, documentId, &AtomToolsDocumentRequestBus::Events::GetAbsolutePath);
  255. TraceRecorder traceRecorder(m_maxMessageBoxLineCount);
  256. bool closeResult = true;
  257. AtomToolsDocumentRequestBus::EventResult(closeResult, documentId, &AtomToolsDocumentRequestBus::Events::Close);
  258. if (!closeResult)
  259. {
  260. DisplayErrorMessage(
  261. GetToolMainWindow(),
  262. QObject::tr("Document could not be closed"),
  263. QObject::tr("Failed to close: \n%1\n\n%2").arg(documentPath.c_str()).arg(traceRecorder.GetDump().c_str()));
  264. return false;
  265. }
  266. DestroyDocument(documentId);
  267. return true;
  268. }
  269. bool AtomToolsDocumentSystem::CloseAllDocuments()
  270. {
  271. bool result = true;
  272. auto documentMap = m_documentMap;
  273. for (const auto& documentPair : documentMap)
  274. {
  275. if (!CloseDocument(documentPair.first))
  276. {
  277. result = false;
  278. }
  279. }
  280. return result;
  281. }
  282. bool AtomToolsDocumentSystem::CloseAllDocumentsExcept(const AZ::Uuid& documentId)
  283. {
  284. bool result = true;
  285. auto documentMap = m_documentMap;
  286. for (const auto& documentPair : documentMap)
  287. {
  288. if (documentPair.first != documentId)
  289. {
  290. if (!CloseDocument(documentPair.first))
  291. {
  292. result = false;
  293. }
  294. }
  295. }
  296. return result;
  297. }
  298. bool AtomToolsDocumentSystem::SaveDocument(const AZ::Uuid& documentId)
  299. {
  300. AZStd::string savePath;
  301. AtomToolsDocumentRequestBus::EventResult(savePath, documentId, &AtomToolsDocumentRequestBus::Events::GetAbsolutePath);
  302. if (!ValidateDocumentPath(savePath))
  303. {
  304. DisplayErrorMessage(
  305. GetToolMainWindow(),
  306. QObject::tr("Document could not be saved"),
  307. QObject::tr("Document path is invalid, not in a supported project or gem folder, or marked as non-editable:\n%1").arg(savePath.c_str()));
  308. return false;
  309. }
  310. const QFileInfo saveInfo(savePath.c_str());
  311. if (saveInfo.exists() && !saveInfo.isWritable())
  312. {
  313. DisplayErrorMessage(
  314. GetToolMainWindow(),
  315. QObject::tr("Document could not be saved"),
  316. QObject::tr("Document could not be overwritten:\n%1").arg(savePath.c_str()));
  317. return false;
  318. }
  319. TraceRecorder traceRecorder(m_maxMessageBoxLineCount);
  320. bool result = false;
  321. AtomToolsDocumentRequestBus::EventResult(result, documentId, &AtomToolsDocumentRequestBus::Events::Save);
  322. if (!result)
  323. {
  324. DisplayErrorMessage(
  325. GetToolMainWindow(),
  326. QObject::tr("Document could not be saved"),
  327. QObject::tr("Failed to save: \n%1\n\n%2").arg(savePath.c_str()).arg(traceRecorder.GetDump().c_str()));
  328. return false;
  329. }
  330. return true;
  331. }
  332. bool AtomToolsDocumentSystem::SaveDocumentAsCopy(const AZ::Uuid& documentId, const AZStd::string& targetPath)
  333. {
  334. AZStd::string savePath = targetPath;
  335. if (!ValidateDocumentPath(savePath))
  336. {
  337. DisplayErrorMessage(
  338. GetToolMainWindow(),
  339. QObject::tr("Document could not be saved"),
  340. QObject::tr("Document path is invalid, not in a supported project or gem folder, or marked as non-editable:\n%1").arg(savePath.c_str()));
  341. return false;
  342. }
  343. const QFileInfo saveInfo(savePath.c_str());
  344. if (saveInfo.exists() && !saveInfo.isWritable())
  345. {
  346. DisplayErrorMessage(
  347. GetToolMainWindow(),
  348. QObject::tr("Document could not be saved"),
  349. QObject::tr("Document could not be overwritten:\n%1").arg(savePath.c_str()));
  350. return false;
  351. }
  352. TraceRecorder traceRecorder(m_maxMessageBoxLineCount);
  353. bool result = false;
  354. AtomToolsDocumentRequestBus::EventResult(result, documentId, &AtomToolsDocumentRequestBus::Events::SaveAsCopy, savePath);
  355. if (!result)
  356. {
  357. DisplayErrorMessage(
  358. GetToolMainWindow(),
  359. QObject::tr("Document could not be saved"),
  360. QObject::tr("Failed to save: \n%1\n\n%2").arg(savePath.c_str()).arg(traceRecorder.GetDump().c_str()));
  361. return false;
  362. }
  363. AddRecentFilePath(savePath);
  364. return true;
  365. }
  366. bool AtomToolsDocumentSystem::SaveDocumentAsChild(const AZ::Uuid& documentId, const AZStd::string& targetPath)
  367. {
  368. AZStd::string savePath = targetPath;
  369. if (!ValidateDocumentPath(savePath))
  370. {
  371. DisplayErrorMessage(
  372. GetToolMainWindow(),
  373. QObject::tr("Document could not be saved"),
  374. QObject::tr("Document path is invalid, not in a supported project or gem folder, or marked as non-editable:\n%1").arg(savePath.c_str()));
  375. return false;
  376. }
  377. const QFileInfo saveInfo(savePath.c_str());
  378. if (saveInfo.exists() && !saveInfo.isWritable())
  379. {
  380. DisplayErrorMessage(
  381. GetToolMainWindow(),
  382. QObject::tr("Document could not be saved"),
  383. QObject::tr("Document could not be overwritten:\n%1").arg(savePath.c_str()));
  384. return false;
  385. }
  386. TraceRecorder traceRecorder(m_maxMessageBoxLineCount);
  387. bool result = false;
  388. AtomToolsDocumentRequestBus::EventResult(result, documentId, &AtomToolsDocumentRequestBus::Events::SaveAsChild, savePath);
  389. if (!result)
  390. {
  391. DisplayErrorMessage(
  392. GetToolMainWindow(),
  393. QObject::tr("Document could not be saved"),
  394. QObject::tr("Failed to save: \n%1\n\n%2").arg(savePath.c_str()).arg(traceRecorder.GetDump().c_str()));
  395. return false;
  396. }
  397. AddRecentFilePath(savePath);
  398. return true;
  399. }
  400. bool AtomToolsDocumentSystem::SaveAllDocuments()
  401. {
  402. bool result = true;
  403. for (const auto& documentPair : m_documentMap)
  404. {
  405. const auto& documentId = documentPair.first;
  406. AZStd::string documentPath;
  407. AtomToolsDocumentRequestBus::EventResult(documentPath, documentId, &AtomToolsDocumentRequestBus::Events::GetAbsolutePath);
  408. DocumentTypeInfo documentInfo;
  409. AtomToolsDocumentRequestBus::EventResult(documentInfo, documentId, &AtomToolsDocumentRequestBus::Events::GetDocumentTypeInfo);
  410. if (documentInfo.IsSupportedExtensionToSave(documentPath))
  411. {
  412. if (!SaveDocument(documentId))
  413. {
  414. result = false;
  415. }
  416. }
  417. }
  418. return result;
  419. }
  420. bool AtomToolsDocumentSystem::SaveAllModifiedDocuments()
  421. {
  422. bool result = true;
  423. for (const auto& documentPair : m_documentMap)
  424. {
  425. const auto& documentId = documentPair.first;
  426. AZStd::string documentPath;
  427. AtomToolsDocumentRequestBus::EventResult(documentPath, documentId, &AtomToolsDocumentRequestBus::Events::GetAbsolutePath);
  428. DocumentTypeInfo documentInfo;
  429. AtomToolsDocumentRequestBus::EventResult(documentInfo, documentId, &AtomToolsDocumentRequestBus::Events::GetDocumentTypeInfo);
  430. if (documentInfo.IsSupportedExtensionToSave(documentPath))
  431. {
  432. bool isModified = false;
  433. AtomToolsDocumentRequestBus::EventResult(isModified, documentId, &AtomToolsDocumentRequestBus::Events::IsModified);
  434. if (isModified)
  435. {
  436. if (!SaveDocument(documentId))
  437. {
  438. result = false;
  439. }
  440. }
  441. }
  442. }
  443. m_queueSaveAllModifiedDocuments = false;
  444. return result;
  445. }
  446. bool AtomToolsDocumentSystem::QueueReopenModifiedDocuments()
  447. {
  448. if (!m_queueReopenModifiedDocuments)
  449. {
  450. m_queueReopenModifiedDocuments = true;
  451. const int interval =
  452. static_cast<int>(GetSettingsValue<AZ::s64>("/O3DE/AtomToolsFramework/AtomToolsDocumentSystem/ReopenInterval", 500));
  453. QTimer::singleShot(interval, [toolId = m_toolId]() {
  454. AtomToolsDocumentSystemRequestBus::Event(toolId, &AtomToolsDocumentSystemRequestBus::Events::ReopenModifiedDocuments);
  455. });
  456. return true;
  457. }
  458. return false;
  459. }
  460. bool AtomToolsDocumentSystem::ReopenModifiedDocuments()
  461. {
  462. const bool enableHotReload = GetSettingsValue<bool>("/O3DE/AtomToolsFramework/AtomToolsDocumentSystem/EnableAutomaticReload", true);
  463. if (!enableHotReload)
  464. {
  465. m_documentIdsWithDependencyChanges.clear();
  466. m_documentIdsWithExternalChanges.clear();
  467. m_queueReopenModifiedDocuments = false;
  468. return false;
  469. }
  470. // Postpone document reload if a modal dialog is active or the application is out of focus
  471. if (QApplication::activeModalWidget() || !(QApplication::applicationState() & Qt::ApplicationActive))
  472. {
  473. QueueReopenModifiedDocuments();
  474. return false;
  475. }
  476. const bool enableHotReloadPrompts =
  477. GetSettingsValue<bool>("/O3DE/AtomToolsFramework/AtomToolsDocumentSystem/EnableAutomaticReloadPrompts", true);
  478. for (const AZ::Uuid& documentId : m_documentIdsWithExternalChanges)
  479. {
  480. m_documentIdsWithDependencyChanges.erase(documentId);
  481. AZStd::string documentPath;
  482. AtomToolsDocumentRequestBus::EventResult(documentPath, documentId, &AtomToolsDocumentRequestBus::Events::GetAbsolutePath);
  483. if (enableHotReloadPrompts &&
  484. (QMessageBox::question(GetToolMainWindow(),
  485. QObject::tr("Document was externally modified"),
  486. QObject::tr("Would you like to reopen the document:\n%1?").arg(documentPath.c_str()),
  487. QMessageBox::Yes | QMessageBox::No) != QMessageBox::Yes))
  488. {
  489. continue;
  490. }
  491. TraceRecorder traceRecorder(m_maxMessageBoxLineCount);
  492. bool openResult = false;
  493. AtomToolsDocumentRequestBus::EventResult(openResult, documentId, &AtomToolsDocumentRequestBus::Events::Open, documentPath);
  494. if (!openResult)
  495. {
  496. DisplayErrorMessage(
  497. GetToolMainWindow(),
  498. QObject::tr("Document could not be opened"),
  499. QObject::tr("Failed to open: \n%1\n\n%2").arg(documentPath.c_str()).arg(traceRecorder.GetDump().c_str()));
  500. CloseDocument(documentId);
  501. }
  502. }
  503. for (const AZ::Uuid& documentId : m_documentIdsWithDependencyChanges)
  504. {
  505. AZStd::string documentPath;
  506. AtomToolsDocumentRequestBus::EventResult(documentPath, documentId, &AtomToolsDocumentRequestBus::Events::GetAbsolutePath);
  507. if (enableHotReloadPrompts &&
  508. (QMessageBox::question(GetToolMainWindow(),
  509. QObject::tr("Document dependencies have changed"),
  510. QObject::tr("Would you like to update the document with these changes:\n%1?").arg(documentPath.c_str()),
  511. QMessageBox::Yes | QMessageBox::No) != QMessageBox::Yes))
  512. {
  513. continue;
  514. }
  515. TraceRecorder traceRecorder(m_maxMessageBoxLineCount);
  516. bool openResult = false;
  517. AtomToolsDocumentRequestBus::EventResult(openResult, documentId, &AtomToolsDocumentRequestBus::Events::Reopen);
  518. if (!openResult)
  519. {
  520. DisplayErrorMessage(
  521. GetToolMainWindow(),
  522. QObject::tr("Document could not be opened"),
  523. QObject::tr("Failed to open: \n%1\n\n%2").arg(documentPath.c_str()).arg(traceRecorder.GetDump().c_str()));
  524. CloseDocument(documentId);
  525. }
  526. }
  527. m_documentIdsWithDependencyChanges.clear();
  528. m_documentIdsWithExternalChanges.clear();
  529. m_queueReopenModifiedDocuments = false;
  530. return true;
  531. }
  532. AZ::u32 AtomToolsDocumentSystem::GetDocumentCount() const
  533. {
  534. return aznumeric_cast<AZ::u32>(m_documentMap.size());
  535. }
  536. bool AtomToolsDocumentSystem::IsDocumentOpen(const AZ::Uuid& documentId) const
  537. {
  538. bool result = false;
  539. AtomToolsDocumentRequestBus::EventResult(result, documentId, &AtomToolsDocumentRequestBus::Events::IsOpen);
  540. return result;
  541. }
  542. void AtomToolsDocumentSystem::AddRecentFilePath(const AZStd::string& absolutePath)
  543. {
  544. if (!absolutePath.empty())
  545. {
  546. // Get the list of previously stored recent file paths from the settings registry
  547. AZStd::vector<AZStd::string> paths = GetRecentFilePaths();
  548. // If the new path is already in the list then remove it Because it will be moved to the front of the list
  549. AZStd::erase_if(paths, [&absolutePath](const AZStd::string& currentPath) {
  550. return AZ::StringFunc::Equal(currentPath, absolutePath);
  551. });
  552. paths.insert(paths.begin(), absolutePath);
  553. constexpr const size_t recentFilePathsMax = 10;
  554. if (paths.size() > recentFilePathsMax)
  555. {
  556. paths.resize(recentFilePathsMax);
  557. }
  558. SetRecentFilePaths(paths);
  559. }
  560. }
  561. void AtomToolsDocumentSystem::ClearRecentFilePaths()
  562. {
  563. SetRecentFilePaths(AZStd::vector<AZStd::string>());
  564. }
  565. void AtomToolsDocumentSystem::SetRecentFilePaths(const AZStd::vector<AZStd::string>& absolutePaths)
  566. {
  567. SetSettingsObject("/O3DE/AtomToolsFramework/AtomToolsDocumentSystem/RecentFilePaths", absolutePaths);
  568. }
  569. const AZStd::vector<AZStd::string> AtomToolsDocumentSystem::GetRecentFilePaths() const
  570. {
  571. return GetSettingsObject("/O3DE/AtomToolsFramework/AtomToolsDocumentSystem/RecentFilePaths", AZStd::vector<AZStd::string>());
  572. }
  573. void AtomToolsDocumentSystem::OnDocumentModified([[maybe_unused]] const AZ::Uuid& documentId)
  574. {
  575. if (GetSettingsValue<bool>("/O3DE/AtomToolsFramework/AtomToolsDocumentSystem/AutoSaveEnabled", false))
  576. {
  577. if (!m_queueSaveAllModifiedDocuments)
  578. {
  579. m_queueSaveAllModifiedDocuments = true;
  580. const int interval =
  581. static_cast<int>(GetSettingsValue<AZ::s64>("/O3DE/AtomToolsFramework/AtomToolsDocumentSystem/AutoSaveInterval", 250));
  582. QTimer::singleShot(interval, [toolId = m_toolId]() {
  583. AtomToolsDocumentSystemRequestBus::Event(toolId, &AtomToolsDocumentSystemRequestBus::Events::SaveAllModifiedDocuments);
  584. });
  585. }
  586. }
  587. }
  588. void AtomToolsDocumentSystem::OnDocumentExternallyModified(const AZ::Uuid& documentId)
  589. {
  590. m_documentIdsWithExternalChanges.insert(documentId);
  591. QueueReopenModifiedDocuments();
  592. }
  593. void AtomToolsDocumentSystem::OnDocumentDependencyModified(const AZ::Uuid& documentId)
  594. {
  595. m_documentIdsWithDependencyChanges.insert(documentId);
  596. QueueReopenModifiedDocuments();
  597. }
  598. } // namespace AtomToolsFramework