HierarchyClipboard.cpp 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262
  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 "EditorCommon.h"
  9. #include <QMimeData>
  10. #include <QApplication>
  11. #include <QClipboard>
  12. SerializeHelpers::SerializedEntryList& HierarchyClipboard::Serialize(HierarchyWidget* widget,
  13. const QTreeWidgetItemRawPtrQList& selectedItems,
  14. HierarchyItemRawPtrList* optionalItemsToSerialize,
  15. SerializeHelpers::SerializedEntryList& entryList,
  16. bool isUndo)
  17. {
  18. HierarchyItemRawPtrList itemsToSerialize;
  19. if (optionalItemsToSerialize)
  20. {
  21. // copy the list so we can sort it
  22. itemsToSerialize = *optionalItemsToSerialize;
  23. }
  24. else
  25. {
  26. SelectionHelpers::GetListOfTopLevelSelectedItems(widget,
  27. selectedItems,
  28. widget->invisibleRootItem(),
  29. itemsToSerialize);
  30. }
  31. // Sort the itemsToSerialize by order in the hierarchy, this is important for reliably restoring
  32. // them given that we maintain the order by remembering which item to insert before
  33. HierarchyHelpers::SortByHierarchyOrder(itemsToSerialize);
  34. // HierarchyItemRawPtrList -> SerializeHelpers::SerializedEntryList
  35. for (auto i : itemsToSerialize)
  36. {
  37. AZ::Entity* e = i->GetElement();
  38. AZ_Assert(e, "No entity found for item");
  39. // serialize this entity (and its descendants) to XML and get the set of prefab references
  40. // used by the serialized entities
  41. AZStd::unordered_set<AZ::Data::AssetId> referencedSliceAssets;
  42. AZStd::string xml = GetXml(widget, LyShine::EntityArray(1, e), false, referencedSliceAssets);
  43. AZ_Assert(!xml.empty(), "Failed to serialize");
  44. if (isUndo)
  45. {
  46. AZ::EntityId parentId;
  47. {
  48. HierarchyItem* parent = i->Parent();
  49. parentId = (parent ? parent->GetEntityId() : AZ::EntityId());
  50. }
  51. AZ::EntityId insertAboveThisId;
  52. {
  53. QTreeWidgetItem* parentItem = (i->QTreeWidgetItem::parent() ? i->QTreeWidgetItem::parent() : widget->invisibleRootItem());
  54. // +1 : Because the insertion point is the next sibling.
  55. QTreeWidgetItem* insertAboveThisBaseItem = parentItem->child(parentItem->indexOfChild(i) + 1);
  56. HierarchyItem* insertAboveThisItem = HierarchyItem::RttiCast(insertAboveThisBaseItem);
  57. insertAboveThisId = (insertAboveThisItem ? insertAboveThisItem->GetEntityId() : AZ::EntityId());
  58. }
  59. entryList.push_back(SerializeHelpers::SerializedEntry
  60. {
  61. i->GetEntityId(),
  62. parentId,
  63. insertAboveThisId,
  64. xml,
  65. "",
  66. referencedSliceAssets
  67. }
  68. );
  69. }
  70. else // isRedo.
  71. {
  72. AZ::EntityId id = i->GetEntityId();
  73. auto iter = std::find_if(entryList.begin(),
  74. entryList.end(),
  75. [ id ](const SerializeHelpers::SerializedEntry& entry)
  76. {
  77. return (entry.m_id == id);
  78. });
  79. // This function should ALWAYS be called
  80. // with ( isUndo == true ) first.
  81. AZ_Assert(entryList.size() > 0, "Empty entry list");
  82. AZ_Assert(iter != entryList.end(), "Entity ID not found in entryList");
  83. iter->m_redoXml = xml;
  84. }
  85. }
  86. return entryList;
  87. }
  88. bool HierarchyClipboard::Unserialize(HierarchyWidget* widget,
  89. SerializeHelpers::SerializedEntryList& entryList,
  90. bool isUndo)
  91. {
  92. if (!HierarchyHelpers::AllItemExists(widget, entryList))
  93. {
  94. // At least one item is missing.
  95. // Nothing to do.
  96. return false;
  97. }
  98. // Runtime-side: Replace element.
  99. for (auto && e : entryList)
  100. {
  101. HierarchyItem* item = HierarchyItem::RttiCast(HierarchyHelpers::ElementToItem(widget, e.m_id, false));
  102. item->ReplaceElement(isUndo ? e.m_undoXml : e.m_redoXml, e.m_referencedSliceAssets);
  103. }
  104. // Editor-side: Highlight.
  105. widget->clearSelection();
  106. HierarchyHelpers::SetSelectedItems(widget, &entryList);
  107. return true;
  108. }
  109. void HierarchyClipboard::CopySelectedItemsToClipboard(HierarchyWidget* widget,
  110. const QTreeWidgetItemRawPtrQList& selectedItems)
  111. {
  112. // selectedItems -> EntityArray.
  113. LyShine::EntityArray elements;
  114. {
  115. HierarchyItemRawPtrList itemsToSerialize;
  116. SelectionHelpers::GetListOfTopLevelSelectedItems(widget,
  117. selectedItems,
  118. widget->invisibleRootItem(),
  119. itemsToSerialize);
  120. for (auto i : itemsToSerialize)
  121. {
  122. elements.push_back(i->GetElement());
  123. }
  124. }
  125. // EntityArray -> XML.
  126. AZStd::unordered_set<AZ::Data::AssetId> referencedSliceAssets; // returned from GetXML but not used in this case
  127. AZStd::string xml = GetXml(widget, elements, true, referencedSliceAssets);
  128. // XML -> Clipboard.
  129. if (!xml.empty())
  130. {
  131. IEditor* pEditor = GetIEditor();
  132. AZ_Assert(pEditor, "Failed to get IEditor");
  133. QMimeData* mimeData = pEditor->CreateQMimeData();
  134. {
  135. // Concatenate all the data we need into a single QByteArray.
  136. QByteArray data(xml.c_str(), static_cast<int>(xml.size()));
  137. mimeData->setData(UICANVASEDITOR_MIMETYPE, data);
  138. }
  139. QApplication::clipboard()->setMimeData(mimeData);
  140. }
  141. }
  142. void HierarchyClipboard::CreateElementsFromClipboard(HierarchyWidget* widget,
  143. const QTreeWidgetItemRawPtrQList& selectedItems,
  144. bool createAsChildOfSelection)
  145. {
  146. if (!ClipboardContainsOurDataType())
  147. {
  148. // Nothing to do.
  149. return;
  150. }
  151. const QMimeData* mimeData = QApplication::clipboard()->mimeData();
  152. QByteArray data = mimeData->data(UICANVASEDITOR_MIMETYPE);
  153. char* rawData = data.data();
  154. // Extract all the data we need from the source QByteArray.
  155. char* xml = static_cast<char*>(rawData);
  156. CommandHierarchyItemCreateFromData::Push(widget->GetEditorWindow()->GetActiveStack(),
  157. widget,
  158. selectedItems,
  159. createAsChildOfSelection,
  160. [ widget, xml ](HierarchyItem* parent,
  161. LyShine::EntityArray& listOfNewlyCreatedTopLevelElements)
  162. {
  163. SerializeHelpers::RestoreSerializedElements(widget->GetEditorWindow()->GetCanvas(),
  164. (parent ? parent->GetElement() : nullptr),
  165. nullptr,
  166. widget->GetEditorWindow()->GetEntityContext(),
  167. xml,
  168. true,
  169. &listOfNewlyCreatedTopLevelElements);
  170. },
  171. "Paste");
  172. }
  173. AZStd::string HierarchyClipboard::GetXml(HierarchyWidget* widget,
  174. const LyShine::EntityArray& elements,
  175. bool isCopyOperation,
  176. AZStd::unordered_set<AZ::Data::AssetId>& referencedSliceAssets)
  177. {
  178. if (elements.empty())
  179. {
  180. // Nothing to do.
  181. return "";
  182. }
  183. AZ::SliceComponent* rootSlice = widget->GetEditorWindow()->GetSliceManager()->GetRootSlice();
  184. AZStd::string result = SerializeHelpers::SaveElementsToXmlString(elements, rootSlice, isCopyOperation, referencedSliceAssets);
  185. return result;
  186. }
  187. AZStd::string HierarchyClipboard::GetXmlForDiff(AZ::EntityId canvasEntityId)
  188. {
  189. AZStd::string xmlString;
  190. UiCanvasBus::EventResult(xmlString, canvasEntityId, &UiCanvasBus::Events::SaveToXmlString);
  191. return xmlString;
  192. }
  193. void HierarchyClipboard::BeginUndoableEntitiesChange(EditorWindow* editorWindow, SerializeHelpers::SerializedEntryList& preChangeState)
  194. {
  195. preChangeState.clear(); // Serialize just adds to the list, so we want to clear it first
  196. // This is used to save the "before" undo data.
  197. HierarchyClipboard::Serialize(editorWindow->GetHierarchy(),
  198. editorWindow->GetHierarchy()->selectedItems(),
  199. nullptr,
  200. preChangeState,
  201. true);
  202. }
  203. void HierarchyClipboard::EndUndoableEntitiesChange(EditorWindow* editorWindow, const char* commandName, SerializeHelpers::SerializedEntryList& preChangeState)
  204. {
  205. // Before saving the current entity state, make sure that all marked layouts are recomputed.
  206. // Otherwise they will be recomputed on the next update which will be after the entity state is saved.
  207. // An example where this is needed is when changing the properties of a layout fitter component
  208. UiCanvasBus::Event(editorWindow->GetActiveCanvasEntityId(), &UiCanvasBus::Events::RecomputeChangedLayouts);
  209. // This is used to save the "after" undo data. It puts a command on the undo stack with the given name.
  210. CommandPropertiesChange::Push(editorWindow->GetActiveStack(),
  211. editorWindow->GetHierarchy(),
  212. preChangeState,
  213. commandName);
  214. // Notify other systems (e.g. Animation) for each UI entity that changed
  215. LyShine::EntityArray selectedElements = SelectionHelpers::GetTopLevelSelectedElements(
  216. editorWindow->GetHierarchy(), editorWindow->GetHierarchy()->selectedItems());
  217. for (auto element : selectedElements)
  218. {
  219. UiElementChangeNotificationBus::Event(element->GetId(), &UiElementChangeNotificationBus::Events::UiElementPropertyChanged);
  220. }
  221. }