// Copyright Epic Games, Inc. All Rights Reserved. #include "PropertyEditorClipboard.h" #include "HAL/PlatformApplicationMisc.h" #include "PropertyNode.h" #include "Serialization/JsonReader.h" #include "Serialization/JsonSerializer.h" namespace UE::PropertyEditor::Internal { constexpr const TCHAR* ClipboardMapKeyName = TEXT("Tagged"); bool IsJsonString(const FString& InStr) { if (InStr.IsEmpty()) { return false; } // Str has contents but isn't necessarily json TSharedPtr UnusedJsonObject; const TSharedRef> JsonReader = TJsonReaderFactory<>::Create(*InStr); return FJsonSerializer::Deserialize(JsonReader, UnusedJsonObject); // This will return false if not parsed } bool TryWriteClipboard(const TMap& InTaggedClipboardPairs, FString& OutClipboardStr) { const TSharedPtr RootJsonObject = MakeShared(); TArray> TaggedValuesJson; TaggedValuesJson.Reserve(InTaggedClipboardPairs.Num()); for (const TPair& TaggedPair : InTaggedClipboardPairs) { TArray> TaggedPairJson; TaggedPairJson.Reserve(2); TaggedPairJson.Add(MakeShared(TaggedPair.Key.ToString())); TaggedPairJson.Add(MakeShared(TaggedPair.Value)); TaggedValuesJson.Add(MakeShared(MoveTemp(TaggedPairJson))); } RootJsonObject->SetArrayField(ClipboardMapKeyName, TaggedValuesJson); const TSharedRef> JsonWriter = TJsonWriterFactory<>::Create(&OutClipboardStr); return FJsonSerializer::Serialize(RootJsonObject.ToSharedRef(), JsonWriter); } bool TryParseClipboard(const FString& InClipboardStr, TMap& OutTaggedClipboard) { TSharedPtr RootJsonObject; const TSharedRef> JsonReader = TJsonReaderFactory<>::Create(InClipboardStr); if (FJsonSerializer::Deserialize(JsonReader, RootJsonObject)) { const TArray>* TaggedValueArray = nullptr; if (RootJsonObject->TryGetArrayField(ClipboardMapKeyName, TaggedValueArray)) { OutTaggedClipboard.Reserve(TaggedValueArray->Num()); for (const TSharedPtr& TaggedValueEntry : *TaggedValueArray) { const TArray>* TaggedValuePair = nullptr; if (TaggedValueEntry->TryGetArray(TaggedValuePair)) { // Expects at least key, value entries if (TaggedValuePair->Num() < 2) { continue; } FString TagName = (*TaggedValuePair)[0]->AsString(); FString TagValue = (*TaggedValuePair)[1]->AsString(); OutTaggedClipboard.Add(FName(TagName), TagValue); } } return true; } } return false; } } TSharedRef FPropertyEditorClipboard::Get() { static TSharedRef Instance = MakeShared(); return Instance; } void FPropertyEditorClipboard::ClipboardCopy(const TCHAR* Str) { FPropertyEditorClipboard::Get()->ClipboardContents.Reset(); FPlatformApplicationMisc::ClipboardCopy(Str); } static bool EncodeClipboardCopy(const TMap& InTaggedClipboard) { FString ClipboardStr; if (UE::PropertyEditor::Internal::TryWriteClipboard(InTaggedClipboard, ClipboardStr)) { // Tagged content written, now encode and write to clipboard FPlatformApplicationMisc::ClipboardCopy(*ClipboardStr); // Encoding (to json) successful return true; } // Encoding failed - silently fail and do regular copy UE_LOG(LogPropertyNode, Warning, TEXT("Failed to encode tagged clipboard as json.")) // Only if default exists (it always should, caller!) if (const FString* DefaultClipboardStr = InTaggedClipboard.Find(NAME_None)) { FPlatformApplicationMisc::ClipboardCopy(**DefaultClipboardStr); } return false; } void FPropertyEditorClipboard::ClipboardCopy( const TCHAR* Str, FName Tag) { if (Tag == NAME_None) { return ClipboardCopy(Str); } TMap& TaggedContents = FPropertyEditorClipboard::Get()->ClipboardContents; TaggedContents.Reset(); // Populate both default and tagged, makes it easy to check OnPaste if the contents is intended to be "specialized" TaggedContents.Add(NAME_None, Str); TaggedContents.Add(Tag, Str); EncodeClipboardCopy(TaggedContents); } void FPropertyEditorClipboard::ClipboardCopy( TUniqueFunction&)>&& TagMappingFunc) { TMap& TaggedContents = FPropertyEditorClipboard::Get()->ClipboardContents; TaggedContents.Reset(); ClipboardAppend(MoveTemp(TagMappingFunc)); } void FPropertyEditorClipboard::ClipboardAppend(TUniqueFunction&)>&& TagMappingFunc) { TMap& TaggedContents = FPropertyEditorClipboard::Get()->ClipboardContents; TagMappingFunc(TaggedContents); EncodeClipboardCopy(TaggedContents); } bool FPropertyEditorClipboard::ClipboardPaste(FString& Dest) { FPlatformApplicationMisc::ClipboardPaste(Dest); return true; } bool FPropertyEditorClipboard::ClipboardPaste(FString& Dest, FName Tag) { if (Tag != NAME_None) { // Check for tagged entry, return true if found if (FString* ClipboardText = FPropertyEditorClipboard::Get()->ClipboardContents.Find(Tag)) { Dest = *ClipboardText; return true; } } // Otherwise perform default operation return ClipboardPaste(Dest); }