AtomToolsDocument.cpp 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389
  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 <Atom/RPI.Edit/Common/AssetUtils.h>
  9. #include <AtomToolsFramework/Document/AtomToolsDocument.h>
  10. #include <AtomToolsFramework/Document/AtomToolsDocumentNotificationBus.h>
  11. #include <AtomToolsFramework/Util/Util.h>
  12. #include <AzCore/RTTI/BehaviorContext.h>
  13. #include <AzCore/Serialization/EditContext.h>
  14. #include <AzCore/Serialization/SerializeContext.h>
  15. #include <AzToolsFramework/SourceControl/SourceControlAPI.h>
  16. namespace AtomToolsFramework
  17. {
  18. void AtomToolsDocument::Reflect(AZ::ReflectContext* context)
  19. {
  20. if (auto serialize = azrtti_cast<AZ::SerializeContext*>(context))
  21. {
  22. serialize->Class<AtomToolsDocument>()
  23. ->Version(0);
  24. }
  25. if (auto behaviorContext = azrtti_cast<AZ::BehaviorContext*>(context))
  26. {
  27. behaviorContext->EBus<AtomToolsDocumentRequestBus>("AtomToolsDocumentRequestBus")
  28. ->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Common)
  29. ->Attribute(AZ::Script::Attributes::Category, "Editor")
  30. ->Attribute(AZ::Script::Attributes::Module, "atomtools")
  31. ->Event("GetAbsolutePath", &AtomToolsDocumentRequestBus::Events::GetAbsolutePath)
  32. ->Event("Open", &AtomToolsDocumentRequestBus::Events::Open)
  33. ->Event("Reopen", &AtomToolsDocumentRequestBus::Events::Reopen)
  34. ->Event("Close", &AtomToolsDocumentRequestBus::Events::Close)
  35. ->Event("Save", &AtomToolsDocumentRequestBus::Events::Save)
  36. ->Event("SaveAsChild", &AtomToolsDocumentRequestBus::Events::SaveAsChild)
  37. ->Event("SaveAsCopy", &AtomToolsDocumentRequestBus::Events::SaveAsCopy)
  38. ->Event("IsOpen", &AtomToolsDocumentRequestBus::Events::IsOpen)
  39. ->Event("IsModified", &AtomToolsDocumentRequestBus::Events::IsModified)
  40. ->Event("CanSaveAsChild", &AtomToolsDocumentRequestBus::Events::CanSaveAsChild)
  41. ->Event("CanUndo", &AtomToolsDocumentRequestBus::Events::CanUndo)
  42. ->Event("CanRedo", &AtomToolsDocumentRequestBus::Events::CanRedo)
  43. ->Event("Undo", &AtomToolsDocumentRequestBus::Events::Undo)
  44. ->Event("Redo", &AtomToolsDocumentRequestBus::Events::Redo)
  45. ->Event("BeginEdit", &AtomToolsDocumentRequestBus::Events::BeginEdit)
  46. ->Event("EndEdit", &AtomToolsDocumentRequestBus::Events::EndEdit)
  47. ;
  48. }
  49. }
  50. AtomToolsDocument::AtomToolsDocument(const AZ::Crc32& toolId, const DocumentTypeInfo& documentTypeInfo)
  51. : m_toolId(toolId)
  52. , m_documentTypeInfo(documentTypeInfo)
  53. {
  54. AtomToolsDocumentRequestBus::Handler::BusConnect(m_id);
  55. AtomToolsDocumentNotificationBus::Event(m_toolId, &AtomToolsDocumentNotificationBus::Events::OnDocumentCreated, m_id);
  56. }
  57. AtomToolsDocument::~AtomToolsDocument()
  58. {
  59. AtomToolsDocumentNotificationBus::Event(m_toolId, &AtomToolsDocumentNotificationBus::Events::OnDocumentDestroyed, m_id);
  60. AtomToolsDocumentRequestBus::Handler::BusDisconnect();
  61. AzToolsFramework::AssetSystemBus::Handler::BusDisconnect();
  62. }
  63. const DocumentTypeInfo& AtomToolsDocument::GetDocumentTypeInfo() const
  64. {
  65. return m_documentTypeInfo;
  66. }
  67. DocumentObjectInfoVector AtomToolsDocument::GetObjectInfo() const
  68. {
  69. return DocumentObjectInfoVector();
  70. }
  71. const AZ::Uuid& AtomToolsDocument::GetId() const
  72. {
  73. return m_id;
  74. }
  75. const AZStd::string& AtomToolsDocument::GetAbsolutePath() const
  76. {
  77. return m_absolutePath;
  78. }
  79. bool AtomToolsDocument::Open(const AZStd::string& loadPath)
  80. {
  81. Clear();
  82. m_absolutePath = loadPath;
  83. if (!ValidateDocumentPath(m_absolutePath))
  84. {
  85. AZ_Error("AtomToolsDocument", false, "Document path is invalid, not in a supported project or gem folder, or marked as non-editable: '%s'.", m_absolutePath.c_str());
  86. return OpenFailed();
  87. }
  88. if (!GetDocumentTypeInfo().IsSupportedExtensionToOpen(m_absolutePath) &&
  89. !GetDocumentTypeInfo().IsSupportedExtensionToCreate(m_absolutePath))
  90. {
  91. AZ_Error("AtomToolsDocument", false, "Document path extension is not supported: '%s'.", m_absolutePath.c_str());
  92. return OpenFailed();
  93. }
  94. return true;
  95. }
  96. bool AtomToolsDocument::Reopen()
  97. {
  98. if (!ReopenRecordState())
  99. {
  100. return false;
  101. }
  102. const auto loadPath = m_absolutePath;
  103. if (!Open(loadPath))
  104. {
  105. return false;
  106. }
  107. if (!ReopenRestoreState())
  108. {
  109. return false;
  110. }
  111. AtomToolsDocumentNotificationBus::Event(m_toolId, &AtomToolsDocumentNotificationBus::Events::OnDocumentModified, m_id);
  112. AtomToolsDocumentNotificationBus::Event(m_toolId, &AtomToolsDocumentNotificationBus::Events::OnDocumentUndoStateChanged, m_id);
  113. return true;
  114. }
  115. bool AtomToolsDocument::Save()
  116. {
  117. if (!GetDocumentTypeInfo().IsSupportedExtensionToSave(m_absolutePath))
  118. {
  119. AZ_Error("AtomToolsDocument", false, "Document type can not be saved: '%s'.", m_absolutePath.c_str());
  120. return SaveFailed();
  121. }
  122. m_savePathNormalized = m_absolutePath;
  123. if (!ValidateDocumentPath(m_savePathNormalized))
  124. {
  125. AZ_Error("AtomToolsDocument", false, "Document path is invalid, not in a supported project or gem folder, or marked as non-editable: '%s'.", m_savePathNormalized.c_str());
  126. return SaveFailed();
  127. }
  128. if (!GetDocumentTypeInfo().IsSupportedExtensionToSave(m_savePathNormalized))
  129. {
  130. AZ_Error("AtomToolsDocument", false, "Document save path extension is not supported: '%s'.", m_savePathNormalized.c_str());
  131. return SaveFailed();
  132. }
  133. return true;
  134. }
  135. bool AtomToolsDocument::SaveAsCopy(const AZStd::string& savePath)
  136. {
  137. if (!GetDocumentTypeInfo().IsSupportedExtensionToSave(savePath))
  138. {
  139. AZ_Error("AtomToolsDocument", false, "Document type can not be saved: '%s'.", m_absolutePath.c_str());
  140. return SaveFailed();
  141. }
  142. m_savePathNormalized = savePath;
  143. if (!ValidateDocumentPath(m_savePathNormalized))
  144. {
  145. AZ_Error("AtomToolsDocument", false, "Document path is invalid, not in a supported project or gem folder, or marked as non-editable: '%s'.", m_savePathNormalized.c_str());
  146. return SaveFailed();
  147. }
  148. if (!GetDocumentTypeInfo().IsSupportedExtensionToSave(m_savePathNormalized))
  149. {
  150. AZ_Error("AtomToolsDocument", false, "Document save path extension is not supported: '%s'.", m_savePathNormalized.c_str());
  151. return SaveFailed();
  152. }
  153. return true;
  154. }
  155. bool AtomToolsDocument::SaveAsChild(const AZStd::string& savePath)
  156. {
  157. if (!GetDocumentTypeInfo().IsSupportedExtensionToSave(savePath))
  158. {
  159. AZ_Error("AtomToolsDocument", false, "Document type can not be saved: '%s'.", m_absolutePath.c_str());
  160. return SaveFailed();
  161. }
  162. m_savePathNormalized = savePath;
  163. if (!ValidateDocumentPath(m_savePathNormalized))
  164. {
  165. AZ_Error("AtomToolsDocument", false, "Document path is invalid, not in a supported project or gem folder, or marked as non-editable: '%s'.", m_savePathNormalized.c_str());
  166. return SaveFailed();
  167. }
  168. if (!GetDocumentTypeInfo().IsSupportedExtensionToSave(m_savePathNormalized))
  169. {
  170. AZ_Error("AtomToolsDocument", false, "Document save path extension is not supported: '%s'.", m_savePathNormalized.c_str());
  171. return SaveFailed();
  172. }
  173. if (m_absolutePath == m_savePathNormalized || m_sourceDependencies.find(m_savePathNormalized) != m_sourceDependencies.end())
  174. {
  175. AZ_Error("AtomToolsDocument", false, "Document can not be saved over a dependancy: '%s'.", m_savePathNormalized.c_str());
  176. return SaveFailed();
  177. }
  178. return true;
  179. }
  180. bool AtomToolsDocument::Close()
  181. {
  182. AZ_TracePrintf("AtomToolsDocument", "Document closed: '%s'.\n", m_absolutePath.c_str());
  183. AtomToolsDocumentNotificationBus::Event(m_toolId, &AtomToolsDocumentNotificationBus::Events::OnDocumentClosed, m_id);
  184. // Clearing after notification so paths are still available
  185. Clear();
  186. return true;
  187. }
  188. void AtomToolsDocument::Clear()
  189. {
  190. AzToolsFramework::AssetSystemBus::Handler::BusDisconnect();
  191. m_absolutePath.clear();
  192. m_sourceDependencies.clear();
  193. m_ignoreSourceFileChangeToSelf = {};
  194. m_undoHistory.clear();
  195. m_undoHistoryIndex = {};
  196. AtomToolsDocumentNotificationBus::Event(m_toolId, &AtomToolsDocumentNotificationBus::Events::OnDocumentCleared, m_id);
  197. }
  198. bool AtomToolsDocument::IsOpen() const
  199. {
  200. return !m_id.IsNull();
  201. }
  202. bool AtomToolsDocument::IsModified() const
  203. {
  204. return false;
  205. }
  206. bool AtomToolsDocument::CanSaveAsChild() const
  207. {
  208. return false;
  209. }
  210. bool AtomToolsDocument::CanUndo() const
  211. {
  212. // Undo will only be allowed if something has been recorded and we're not at the beginning of history
  213. return !m_undoHistory.empty() && m_undoHistoryIndex > 0;
  214. }
  215. bool AtomToolsDocument::CanRedo() const
  216. {
  217. // Redo will only be allowed if something has been recorded and we're not at the end of history
  218. return !m_undoHistory.empty() && m_undoHistoryIndex < m_undoHistory.size();
  219. }
  220. bool AtomToolsDocument::Undo()
  221. {
  222. if (CanUndo())
  223. {
  224. // The history index is one beyond the last executed command. Decrement the index then execute undo.
  225. m_undoHistory[--m_undoHistoryIndex].first();
  226. AZ_TracePrintf("AtomToolsDocument", "Document undo: '%s'.\n", m_absolutePath.c_str());
  227. AtomToolsDocumentNotificationBus::Event(m_toolId, &AtomToolsDocumentNotificationBus::Events::OnDocumentUndoStateChanged, m_id);
  228. return true;
  229. }
  230. return false;
  231. }
  232. bool AtomToolsDocument::Redo()
  233. {
  234. if (CanRedo())
  235. {
  236. // Execute the current redo command then move the history index to the next position.
  237. m_undoHistory[m_undoHistoryIndex++].second();
  238. AZ_TracePrintf("AtomToolsDocument", "Document redo: '%s'.\n", m_absolutePath.c_str());
  239. AtomToolsDocumentNotificationBus::Event(m_toolId, &AtomToolsDocumentNotificationBus::Events::OnDocumentUndoStateChanged, m_id);
  240. return true;
  241. }
  242. return false;
  243. }
  244. bool AtomToolsDocument::BeginEdit()
  245. {
  246. AZ_Warning("AtomToolsDocument", false, "%s not implemented.", __FUNCTION__);
  247. return false;
  248. }
  249. bool AtomToolsDocument::EndEdit()
  250. {
  251. AZ_Warning("AtomToolsDocument", false, "%s not implemented.", __FUNCTION__);
  252. return false;
  253. }
  254. bool AtomToolsDocument::OpenSucceeded()
  255. {
  256. AZ_TracePrintf("AtomToolsDocument", "Document opened: '%s'.\n", m_absolutePath.c_str());
  257. AzToolsFramework::AssetSystemBus::Handler::BusConnect();
  258. AtomToolsDocumentNotificationBus::Event(m_toolId, &AtomToolsDocumentNotificationBus::Events::OnDocumentOpened, m_id);
  259. return true;
  260. }
  261. bool AtomToolsDocument::OpenFailed()
  262. {
  263. AZ_TracePrintf("AtomToolsDocument", "Document could not opened: '%s'.\n", m_absolutePath.c_str());
  264. AtomToolsDocumentNotificationBus::Event(m_toolId, &AtomToolsDocumentNotificationBus::Events::OnDocumentError, m_id);
  265. Clear();
  266. return false;
  267. }
  268. bool AtomToolsDocument::SaveSucceeded()
  269. {
  270. m_ignoreSourceFileChangeToSelf = true;
  271. AZ_TracePrintf("AtomToolsDocument", "Document saved: '%s'.\n", m_savePathNormalized.c_str());
  272. // Auto add or checkout saved file
  273. AzToolsFramework::SourceControlCommandBus::Broadcast(
  274. &AzToolsFramework::SourceControlCommandBus::Events::RequestEdit, m_savePathNormalized.c_str(), true,
  275. [](bool, const AzToolsFramework::SourceControlFileInfo&) {});
  276. AtomToolsDocumentNotificationBus::Event(m_toolId, &AtomToolsDocumentNotificationBus::Events::OnDocumentSaved, m_id);
  277. return true;
  278. }
  279. bool AtomToolsDocument::SaveFailed()
  280. {
  281. AZ_TracePrintf("AtomToolsDocument", "Document not saved: '%s'.\n", m_savePathNormalized.c_str());
  282. AtomToolsDocumentNotificationBus::Event(m_toolId, &AtomToolsDocumentNotificationBus::Events::OnDocumentError, m_id);
  283. return false;
  284. }
  285. bool AtomToolsDocument::ReopenRecordState()
  286. {
  287. m_undoHistoryBeforeReopen = m_undoHistory;
  288. m_undoHistoryIndexBeforeReopen = m_undoHistoryIndex;
  289. return true;
  290. }
  291. bool AtomToolsDocument::ReopenRestoreState()
  292. {
  293. m_undoHistory = m_undoHistoryBeforeReopen;
  294. m_undoHistoryIndex = m_undoHistoryIndexBeforeReopen;
  295. m_undoHistoryBeforeReopen = {};
  296. m_undoHistoryIndexBeforeReopen = {};
  297. return true;
  298. }
  299. void AtomToolsDocument::AddUndoRedoHistory(const UndoRedoFunction& undoCommand, const UndoRedoFunction& redoCommand)
  300. {
  301. // Wipe any state beyond the current history index
  302. m_undoHistory.erase(m_undoHistory.begin() + m_undoHistoryIndex, m_undoHistory.end());
  303. // Add undo and redo operations using functions that capture state and restore it when executed
  304. m_undoHistory.emplace_back(undoCommand, redoCommand);
  305. // Assign the index to the end of history
  306. m_undoHistoryIndex = aznumeric_cast<int>(m_undoHistory.size());
  307. AtomToolsDocumentNotificationBus::Event(m_toolId, &AtomToolsDocumentNotificationBus::Events::OnDocumentUndoStateChanged, m_id);
  308. }
  309. void AtomToolsDocument::SourceFileChanged(AZStd::string relativePath, AZStd::string scanFolder, [[maybe_unused]] AZ::Uuid sourceUUID)
  310. {
  311. const auto sourcePath = AZ::RPI::AssetUtils::ResolvePathReference(scanFolder, relativePath);
  312. if (m_absolutePath == sourcePath)
  313. {
  314. // ignore notifications caused by saving the open document
  315. if (!m_ignoreSourceFileChangeToSelf)
  316. {
  317. AZ_TracePrintf("AtomToolsDocument", "Document changed externally: '%s'.\n", m_absolutePath.c_str());
  318. AtomToolsDocumentNotificationBus::Event(
  319. m_toolId, &AtomToolsDocumentNotificationBus::Events::OnDocumentExternallyModified, m_id);
  320. }
  321. m_ignoreSourceFileChangeToSelf = false;
  322. }
  323. else if (m_sourceDependencies.find(sourcePath) != m_sourceDependencies.end())
  324. {
  325. AZ_TracePrintf("AtomToolsDocument", "Document dependency changed: '%s'.\n", m_absolutePath.c_str());
  326. AtomToolsDocumentNotificationBus::Event(
  327. m_toolId, &AtomToolsDocumentNotificationBus::Events::OnDocumentDependencyModified, m_id);
  328. }
  329. }
  330. } // namespace AtomToolsFramework