A Demo Project for the UnrealEngineSDK
Loading...
Searching...
No Matches
SDialogueTextPropertyEditableTextBox.cpp
Go to the documentation of this file.
1// Copyright Csaba Molnar, Daniel Butum. All Rights Reserved.
3#include "Internationalization/TextNamespaceUtil.h"
4#include "Widgets/SBoxPanel.h"
5#include "Widgets/Images/SImage.h"
6#include "Widgets/Text/STextBlock.h"
7#include "Widgets/Layout/SBox.h"
8#include "Widgets/Layout/SGridPanel.h"
9#include "Widgets/Layout/SUniformGridPanel.h"
10#include "Widgets/Input/SButton.h"
11#include "Widgets/Input/SMultiLineEditableTextBox.h"
12#include "Widgets/Input/SEditableTextBox.h"
13#include "Widgets/Input/SComboButton.h"
14#include "Widgets/Input/SComboBox.h"
15#include "Widgets/Input/SCheckBox.h"
16#include "EditorStyleSet.h"
17#include "AssetRegistryModule.h"
18#include "Internationalization/StringTable.h"
19#include "Internationalization/TextPackageNamespaceUtil.h"
20#include "Internationalization/StringTableCore.h"
21#include "Internationalization/StringTableRegistry.h"
22#include "Serialization/TextReferenceCollector.h"
24
25#define LOCTEXT_NAMESPACE "STextPropertyEditableTextBox"
26
27FText SDialogueTextPropertyEditableTextBox::MultipleValuesText(NSLOCTEXT("PropertyEditor", "MultipleValues", "Multiple Values"));
28
30 const FArguments& InArgs,
31 const TSharedRef<IEditableTextProperty>& InEditableTextProperty,
32 const TSharedRef<IPropertyHandle>& InPropertyHandle
33)
34{
35 EditableTextProperty = InEditableTextProperty;
36 PropertyHandle = InPropertyHandle;
37 bAddResetToDefaultWidget = InArgs._AddResetToDefaultWidget;
38
39 TSharedPtr<SHorizontalBox> HorizontalBox;
40
41 //const bool bIsPassword = EditableTextProperty->IsPassword();
42 //bIsMultiLine = EditableTextProperty->IsMultiLineText();
43 if (bIsMultiLine)
44 {
45 ChildSlot
46 [
47 SAssignNew(HorizontalBox, SHorizontalBox)
48 +SHorizontalBox::Slot()
49 .FillWidth(1.0f)
50 [
51 SNew(SBox)
52 .MinDesiredWidth(InArgs._MinDesiredWidth)
53 .MaxDesiredHeight(InArgs._MaxDesiredHeight)
54 [
55 SAssignNew(MultiLineWidget, SMultiLineEditableTextBox)
56 .Text(this, &Self::GetTextValue)
57 .ToolTipText(this, &Self::GetToolTipText)
58 .Style(InArgs._Style)
59 .Font(InArgs._Font)
60 .ForegroundColor(InArgs._ForegroundColor)
61 .ReadOnlyForegroundColor(InArgs._ReadOnlyForegroundColor)
62 .SelectAllTextWhenFocused(InArgs._SelectAllTextWhenFocused)
63 .ClearKeyboardFocusOnCommit(InArgs._ClearKeyboardFocusOnCommit)
64 .SelectAllTextOnCommit(InArgs._SelectAllTextOnCommit)
65 .OnTextChanged(this, &Self::OnTextChanged)
66 .OnTextCommitted(this, &Self::OnTextCommitted)
67 .IsReadOnly(this, &Self::IsSourceTextReadOnly)
68 .AutoWrapText(InArgs._AutoWrapText)
69 .WrapTextAt(InArgs._WrapTextAt)
70 .ModiferKeyForNewLine(InArgs._ModiferKeyForNewLine)
71 .AllowMultiLine(true)
72 //.IsPassword(bIsPassword)
73 ]
74 ]
75 ];
76
78 }
79 else
80 {
81 checkNoEntry();
82 }
83
84 HorizontalBox->AddSlot()
85 .AutoWidth()
86 [
87 SNew(SComboButton)
88 .HAlign(HAlign_Center)
89 .VAlign(VAlign_Center)
90 .ContentPadding(FMargin(4, 0))
91 .ButtonStyle(FEditorStyle::Get(), "HoverHintOnly")
92 .ForegroundColor(FSlateColor::UseForeground())
93 .ToolTipText(LOCTEXT("AdvancedTextSettingsComboToolTip", "Edit advanced text settings."))
94 .MenuContent()
95 [
96 SNew(SBox)
97 .WidthOverride(340)
98 .Padding(4)
99 [
100 SNew(SGridPanel)
101 .FillColumn(1, 1.0f)
102
103 // Inline Text
104 +SGridPanel::Slot(0, 0)
105 .ColumnSpan(2)
106 .Padding(2)
107 .HAlign(HAlign_Left)
108 .VAlign(VAlign_Center)
109 [
110 SNew(STextBlock)
111 .TextStyle(FEditorStyle::Get(), "LargeText")
112 .Text(LOCTEXT("TextInlineTextLabel", "Inline Text"))
113 ]
114
115 // Localizable?
116 +SGridPanel::Slot(0, 1)
117 .Padding(2)
118 .HAlign(HAlign_Right)
119 .VAlign(VAlign_Center)
120 [
121 SNew(STextBlock)
122 .Text(LOCTEXT("TextLocalizableLabel", "Localizable:"))
123 ]
124 +SGridPanel::Slot(1, 1)
125 .Padding(2)
126 [
127 SNew(SHorizontalBox)
128
129 +SHorizontalBox::Slot()
130 .AutoWidth()
131 .Padding(0)
132 [
133 SNew(SUniformGridPanel)
134 .SlotPadding(FMargin(0, 0, 4, 0))
135
136 +SUniformGridPanel::Slot(0, 0)
137 [
138 SNew(SCheckBox)
139 .Style(FEditorStyle::Get(), "ToggleButtonCheckbox")
140 .ToolTipText(LOCTEXT("TextLocalizableToggleYesToolTip", "Assign this text a key and allow it to be gathered for localization."))
141 .Padding(FMargin(4, 2))
142 .HAlign(HAlign_Center)
143 .IsEnabled(this, &Self::IsCultureInvariantFlagEnabled)
144 .IsChecked(this, &Self::GetLocalizableCheckState, true/*bActiveState*/)
145 .OnCheckStateChanged(this, &Self::HandleLocalizableCheckStateChanged, true/*bActiveState*/)
146 [
147 SNew(STextBlock)
148 .Text(LOCTEXT("TextLocalizableToggleYes", "Yes"))
149 ]
150 ]
151
152 +SUniformGridPanel::Slot(1, 0)
153 [
154 SNew(SCheckBox)
155 .Style(FEditorStyle::Get(), "ToggleButtonCheckbox")
156 .ToolTipText(LOCTEXT("TextLocalizableToggleNoToolTip", "Mark this text as 'culture invariant' to prevent it being gathered for localization."))
157 .Padding(FMargin(4, 2))
158 .HAlign(HAlign_Center)
159 .IsEnabled(this, &Self::IsCultureInvariantFlagEnabled)
160 .IsChecked(this, &Self::GetLocalizableCheckState, false/*bActiveState*/)
161 .OnCheckStateChanged(this, &Self::HandleLocalizableCheckStateChanged, false/*bActiveState*/)
162 [
163 SNew(STextBlock)
164 .Text(LOCTEXT("TextLocalizableToggleNo", "No"))
165 ]
166 ]
167 ]
168 ]
169
170#if USE_STABLE_LOCALIZATION_KEYS
171 // Package
172 +SGridPanel::Slot(0, 2)
173 .Padding(2)
174 .HAlign(HAlign_Right)
175 .VAlign(VAlign_Center)
176 [
177 SNew(STextBlock)
178 .Text(LOCTEXT("TextPackageLabel", "Package:"))
179 .Visibility(this, &Self::GetLocalizableVisibility)
180 ]
181 +SGridPanel::Slot(1, 2)
182 .Padding(2)
183 [
184 SNew(SEditableTextBox)
185 .Text(this, &Self::GetPackageValue)
186 .Visibility(this, &Self::GetLocalizableVisibility)
187 .IsReadOnly(true)
188 ]
189#endif // USE_STABLE_LOCALIZATION_KEYS
190
191 // Namespace
192 +SGridPanel::Slot(0, 3)
193 .Padding(2)
194 .HAlign(HAlign_Right)
195 .VAlign(VAlign_Center)
196 [
197 SNew(STextBlock)
198 .Text(LOCTEXT("TextNamespaceLabel", "Namespace:"))
199 .Visibility(this, &Self::GetLocalizableVisibility)
200 ]
201 +SGridPanel::Slot(1, 3)
202 .Padding(2)
203 [
204 SAssignNew(NamespaceEditableTextBox, SEditableTextBox)
205 .Text(this, &Self::GetNamespaceValue)
206 .SelectAllTextWhenFocused(true)
207 .ClearKeyboardFocusOnCommit(false)
208 .OnTextChanged(this, &Self::OnNamespaceChanged)
209 .OnTextCommitted(this, &Self::OnNamespaceCommitted)
210 .SelectAllTextOnCommit(true)
211 .Visibility(this, &Self::GetLocalizableVisibility)
212 .IsReadOnly(this, &Self::IsNamespaceReadOnly)
213 ]
214
215 // Warning, key will be overriden
216 +SGridPanel::Slot(2, 3)
217 .Padding(2)
218 [
219 SNew(SImage)
220 .Image(FCoreStyle::Get().GetBrush("Icons.Warning"))
222 .ToolTipText(LOCTEXT("NamespaceOverridenWarningToolTip", "The namespace will be overriden on Save.\nTo change this bevahiour go to Project Settings -> (Editors) Dialogue -> Localization"))
223 ]
224
225 // Key
226 +SGridPanel::Slot(0, 4)
227 .Padding(2)
228 .HAlign(HAlign_Right)
229 .VAlign(VAlign_Center)
230 [
231 SNew(STextBlock)
232 .Text(LOCTEXT("TextKeyLabel", "Key:"))
233 .Visibility(this, &Self::GetLocalizableVisibility)
234 ]
235 +SGridPanel::Slot(1, 4)
236 .Padding(2)
237 [
238 SAssignNew(KeyEditableTextBox, SEditableTextBox)
239 .Text(this, &Self::GetKeyValue)
240 .Visibility(this, &Self::GetLocalizableVisibility)
241#if USE_STABLE_LOCALIZATION_KEYS
242 .SelectAllTextWhenFocused(true)
243 .ClearKeyboardFocusOnCommit(false)
244 .OnTextChanged(this, &Self::OnKeyChanged)
245 .OnTextCommitted(this, &Self::OnKeyCommitted)
246 .SelectAllTextOnCommit(true)
247 .IsReadOnly(this, &Self::IsKeyReadOnly)
248#else // USE_STABLE_LOCALIZATION_KEYS
249 .IsReadOnly(true)
250#endif // USE_STABLE_LOCALIZATION_KEYS
251 ]
252
253 // Referenced Text
254 +SGridPanel::Slot(0, 5)
255 .ColumnSpan(2)
256 .Padding(2)
257 .HAlign(HAlign_Left)
258 .VAlign(VAlign_Center)
259 [
260 SNew(STextBlock)
261 .TextStyle(FEditorStyle::Get(), "LargeText")
262 .Text(LOCTEXT("TextReferencedTextLabel", "Referenced Text"))
263 .Visibility(this, &Self::GetLocalizableVisibility)
264 ]
265
266 // String Table
267 +SGridPanel::Slot(0, 6)
268 .Padding(2)
269 .HAlign(HAlign_Right)
270 .VAlign(VAlign_Center)
271 [
272 SNew(STextBlock)
273 .Text(LOCTEXT("TextStringTableLabel", "String Table:"))
274 .Visibility(this, &Self::GetLocalizableVisibility)
275 ]
276 +SGridPanel::Slot(1, 6)
277 .Padding(2)
278 [
279 SNew(STextPropertyEditableStringTableReference, InEditableTextProperty)
280 .AllowUnlink(true)
281 .IsEnabled(this, &Self::CanEdit)
282 .Visibility(this, &Self::GetLocalizableVisibility)
283 ]
284 ]
285 ]
286 ];
287
288 HorizontalBox->AddSlot()
289 .VAlign(VAlign_Center)
290 .HAlign(HAlign_Center)
291 .AutoWidth()
292 [
293 SNew(SImage)
294 .Image(FCoreStyle::Get().GetBrush("Icons.Warning"))
295 .Visibility(this, &Self::GetTextWarningImageVisibility)
296 .ToolTipText(LOCTEXT("TextNotLocalizedWarningToolTip", "This text is marked as 'culture invariant' and won't be gathered for localization.\nYou can change this by editing the advanced text settings."))
297 ];
298
299
300 // Add Reset to default
302 {
303 PropertyHandle->MarkResetToDefaultCustomized(true);
304 HorizontalBox->AddSlot()
305 .AutoWidth()
306 .VAlign(VAlign_Center)
307 .Padding(4.f, 2.f)
308 [
309 SNew(SButton)
310 .IsFocusable(false)
311 .ToolTipText(this, &Self::GetResetToolTip)
312 .ButtonStyle(FEditorStyle::Get(), "NoBorder")
313 .ContentPadding(0)
315 .OnClicked(this, &Self::OnResetClicked)
316 .Content()
317 [
318 SNew(SImage)
319 .Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault"))
320 ]
321 ];
322 }
323
324 SetEnabled(TAttribute<bool>(this, &Self::CanEdit));
325}
326
328{
329 FString Tooltip = NSLOCTEXT("PropertyEditor", "ResetToDefaultToolTip", "Reset to Default").ToString();
330 if (PropertyHandle.IsValid() && !PropertyHandle->IsEditConst() && PropertyHandle->DiffersFromDefault())
331 {
332 const FString DefaultLabel = PropertyHandle->GetResetToDefaultLabel().ToString();
333 if (DefaultLabel.Len() > 0)
334 {
335 Tooltip += "\n";
336 Tooltip += DefaultLabel;
337 }
338 }
339
340 return FText::FromString(Tooltip);
341}
342
344{
345 if (PropertyHandle.IsValid())
346 {
347 return PropertyHandle->DiffersFromDefault() ? EVisibility::Visible : EVisibility::Hidden;
348 }
349
350 return EVisibility::Visible;
351}
352
354{
355 if (EditableTextProperty.IsValid() && PropertyHandle.IsValid())
356 {
357 PropertyHandle->ResetToDefault();
359 }
360 return FReply::Handled();
361}
362
363
365{
366 const int32 NumTexts = EditableTextProperty->GetNumTexts();
367 if (NumTexts == 1)
368 {
369 const FText PropertyValue = EditableTextProperty->GetText(0);
370 return PropertyValue.IsCultureInvariant() ? EVisibility::Collapsed : EVisibility::Visible;
371 }
372 return EVisibility::Visible;
373}
374
375void SDialogueTextPropertyEditableTextBox::GetDesiredWidth(float& OutMinDesiredWidth, float& OutMaxDesiredWidth)
376{
377 if (bIsMultiLine)
378 {
379 OutMinDesiredWidth = 250.0f;
380 }
381 else
382 {
383 OutMinDesiredWidth = 125.0f;
384 }
385
386 OutMaxDesiredWidth = 600.0f;
387}
388
390{
391 return PrimaryWidget.IsValid() && PrimaryWidget->SupportsKeyboardFocus();
392}
393
394FReply SDialogueTextPropertyEditableTextBox::OnFocusReceived(const FGeometry& MyGeometry, const FFocusEvent& InFocusEvent)
395{
396 // Forward keyboard focus to our editable text widget
397 return FReply::Handled().SetUserFocus(PrimaryWidget.ToSharedRef(), InFocusEvent.GetCause());
398}
399
400void SDialogueTextPropertyEditableTextBox::Tick(const FGeometry& AllottedGeometry, double InCurrentTime, float InDeltaTime)
401{
402 const float CurrentHeight = AllottedGeometry.GetLocalSize().Y;
403 if (bIsMultiLine && PreviousHeight.IsSet() && PreviousHeight.GetValue() != CurrentHeight)
404 {
405 EditableTextProperty->RequestRefresh();
406 }
407 PreviousHeight = CurrentHeight;
408}
409
411{
412 const bool bIsReadOnly = FTextLocalizationManager::Get().IsLocalizationLocked() || EditableTextProperty->IsReadOnly();
413 return !bIsReadOnly;
414}
415
420
422{
423 if (!CanEdit())
424 {
425 return true;
426 }
427
428 // We can't edit the source string of string table references
429 const int32 NumTexts = EditableTextProperty->GetNumTexts();
430 if (NumTexts == 1)
431 {
432 const FText TextValue = EditableTextProperty->GetText(0);
433 if (TextValue.IsFromStringTable())
434 {
435 return true;
436 }
437 }
438
439 return false;
440}
441
446
451
453{
454 if (!CanEdit())
455 {
456 return true;
457 }
458
459 // We can't edit the identity of texts that don't gather for localization
460 const int32 NumTexts = EditableTextProperty->GetNumTexts();
461 if (NumTexts == 1)
462 {
463 const FText TextValue = EditableTextProperty->GetText(0);
464 if (!TextValue.ShouldGatherForLocalization())
465 {
466 return true;
467 }
468 }
469
470 return false;
471}
472
474{
475 FText LocalizedTextToolTip;
476 const int32 NumTexts = EditableTextProperty->GetNumTexts();
477 if (NumTexts == 1)
478 {
479 const FText TextValue = EditableTextProperty->GetText(0);
480
481 if (TextValue.IsFromStringTable())
482 {
483 FName TableId;
484 FString Key;
485 FStringTableRegistry::Get().FindTableIdAndKey(TextValue, TableId, Key);
486
487 LocalizedTextToolTip = FText::Format(
488 LOCTEXT("StringTableTextToolTipFmt", "--- String Table Reference ---\nTable ID: {0}\nKey: {1}"),
489 FText::FromName(TableId), FText::FromString(Key)
490 );
491 }
492 else
493 {
494 bool bIsLocalized = false;
495 FString Namespace;
496 FString Key;
497 const FString* SourceString = FTextInspector::GetSourceString(TextValue);
498
499 if (SourceString && TextValue.ShouldGatherForLocalization())
500 {
501 bIsLocalized = FTextLocalizationManager::Get().FindNamespaceAndKeyFromDisplayString(FTextInspector::GetSharedDisplayString(TextValue), Namespace, Key);
502 }
503
504 if (bIsLocalized)
505 {
506 const FString PackageNamespace = TextNamespaceUtil::ExtractPackageNamespace(Namespace);
507 const FString TextNamespace = TextNamespaceUtil::StripPackageNamespace(Namespace);
508
509 LocalizedTextToolTip = FText::Format(
510 LOCTEXT("LocalizedTextToolTipFmt", "--- Localized Text ---\nPackage: {0}\nNamespace: {1}\nKey: {2}\nSource: {3}"),
511 FText::FromString(PackageNamespace), FText::FromString(TextNamespace), FText::FromString(Key), FText::FromString(*SourceString)
512 );
513 }
514 }
515 }
516
517 FText BaseToolTipText = EditableTextProperty->GetToolTipText();
518 if (FTextLocalizationManager::Get().IsLocalizationLocked())
519 {
520 const FText LockdownToolTip = FTextLocalizationManager::Get().IsGameLocalizationPreviewEnabled()
521 ? LOCTEXT("LockdownToolTip_Preview", "Localization is locked down due to the active game localization preview")
522 : LOCTEXT("LockdownToolTip_Other", "Localization is locked down");
523 BaseToolTipText = BaseToolTipText.IsEmptyOrWhitespace() ? LockdownToolTip : FText::Format(LOCTEXT("ToolTipLockdownFmt", "!!! {0} !!!\n\n{1}"), LockdownToolTip, BaseToolTipText);
524 }
525
526 if (LocalizedTextToolTip.IsEmptyOrWhitespace())
527 {
528 return BaseToolTipText;
529 }
530 if (BaseToolTipText.IsEmptyOrWhitespace())
531 {
532 return LocalizedTextToolTip;
533 }
534
535 return FText::Format(LOCTEXT("ToolTipCompleteFmt", "{0}\n\n{1}"), BaseToolTipText, LocalizedTextToolTip);
536}
537
539{
540 FText TextValue;
541
542 const int32 NumTexts = EditableTextProperty->GetNumTexts();
543 if (NumTexts == 1)
544 {
545 TextValue = EditableTextProperty->GetText(0);
546 }
547 else if (NumTexts > 1)
548 {
549 TextValue = MultipleValuesText;
550 }
551
552 return TextValue;
553}
554
556{
557 if (MultiLineWidget.IsValid())
558 {
559 MultiLineWidget->SetText(NewValue);
560 }
561}
562
564{
565 const int32 NumTexts = EditableTextProperty->GetNumTexts();
566
567 FText TextErrorMsg;
568
569 // Don't validate the Multiple Values text if there are multiple properties being set
570 if (NumTexts > 0 && (NumTexts == 1 || NewText.ToString().Equals(MultipleValuesText.ToString(), ESearchCase::CaseSensitive)))
571 {
572 EditableTextProperty->IsValidText(NewText, TextErrorMsg);
573 }
574
575 // Update or clear the error message
576 SetTextError(TextErrorMsg);
577 TextChangedEvent.Broadcast(NewText);
578}
579
580void SDialogueTextPropertyEditableTextBox::OnTextCommitted(const FText& NewText, ETextCommit::Type CommitInfo)
581{
582 const int32 NumTexts = EditableTextProperty->GetNumTexts();
583
584 // Don't commit the Multiple Values text if there are multiple properties being set
585 if (NumTexts > 0 && (NumTexts == 1 || !NewText.ToString().Equals(MultipleValuesText.ToString(), ESearchCase::CaseSensitive)))
586 {
587 FText TextErrorMsg;
588 if (EditableTextProperty->IsValidText(NewText, TextErrorMsg))
589 {
590 // Valid text; clear any error
591 SetTextError(FText::GetEmpty());
592 }
593 else
594 {
595 // Invalid text; set the error and prevent the new text from being set
596 SetTextError(TextErrorMsg);
597 return;
598 }
599
600 const FString& SourceString = NewText.ToString();
601 for (int32 TextIndex = 0; TextIndex < NumTexts; ++TextIndex)
602 {
603 const FText PropertyValue = EditableTextProperty->GetText(TextIndex);
604
605 // Only apply the change if the new text is different
606 if (PropertyValue.ToString().Equals(NewText.ToString(), ESearchCase::CaseSensitive))
607 {
608 continue;
609 }
610
611 // Is the new text is empty, just use the empty instance
612 if (NewText.IsEmpty())
613 {
614 EditableTextProperty->SetText(TextIndex, FText::GetEmpty());
615 continue;
616 }
617
618 // Maintain culture invariance when editing the text
619 if (PropertyValue.IsCultureInvariant())
620 {
621 EditableTextProperty->SetText(TextIndex, FText::AsCultureInvariant(NewText.ToString()));
622 continue;
623 }
624
625 FString NewNamespace;
626 FString NewKey;
627#if USE_STABLE_LOCALIZATION_KEYS
628 {
629 // Get the stable namespace and key that we should use for this property
630 const FString* TextSource = FTextInspector::GetSourceString(PropertyValue);
631 EditableTextProperty->GetStableTextId(
632 TextIndex,
633 IEditableTextProperty::ETextPropertyEditAction::EditedSource,
634 TextSource ? *TextSource : FString(),
635 FTextInspector::GetNamespace(PropertyValue).Get(FString()),
636 FTextInspector::GetKey(PropertyValue).Get(FString()),
637 NewNamespace,
638 NewKey
639 );
640 }
641#else // USE_STABLE_LOCALIZATION_KEYS
642 {
643 // We want to preserve the namespace set on this property if it's *not* the default value
644 if (!EditableTextProperty->IsDefaultValue())
645 {
646 // Some properties report that they're not the default, but still haven't been set from a property, so we also check the property key to see if it's a valid GUID before allowing the namespace to persist
647 FGuid TmpGUID;
648 if (FGuid::Parse(FTextInspector::GetKey(PropertyValue).Get(FString()), TmpGUID))
649 {
650 NewNamespace = FTextInspector::GetNamespace(PropertyValue).Get(FString());
651 }
652 }
653
654 NewKey = FGuid::NewGuid().ToString();
655 }
656#endif // USE_STABLE_LOCALIZATION_KEYS
657
658 const FText FinalText = FText::ChangeKey(NewNamespace, NewKey, NewText);
659 EditableTextProperty->SetText(TextIndex, FinalText);
660 TextCommittedEvent.Broadcast(FinalText, CommitInfo);
661 }
662 }
663}
664
666{
667 if (MultiLineWidget.IsValid())
668 {
669 MultiLineWidget->SetError(InErrorMsg);
670 }
671
672 // if (SingleLineWidget.IsValid())
673 // {
674 // SingleLineWidget->SetError(InErrorMsg);
675 // }
676}
677
679{
680 FText NamespaceValue;
681
682 const int32 NumTexts = EditableTextProperty->GetNumTexts();
683 if (NumTexts == 1)
684 {
685 const FText PropertyValue = EditableTextProperty->GetText(0);
686 TOptional<FString> FoundNamespace = FTextInspector::GetNamespace(PropertyValue);
687 if (FoundNamespace.IsSet())
688 {
689 NamespaceValue = FText::FromString(TextNamespaceUtil::StripPackageNamespace(FoundNamespace.GetValue()));
690 }
691 }
692 else if (NumTexts > 1)
693 {
694 NamespaceValue = MultipleValuesText;
695 }
696
697 return NamespaceValue;
698}
699
701{
702 FText ErrorMessage;
703 const FText ErrorCtx = LOCTEXT("TextNamespaceErrorCtx", "Namespace");
704 IsValidIdentity(NewText, &ErrorMessage, &ErrorCtx);
705
706 NamespaceEditableTextBox->SetError(ErrorMessage);
707}
708
709void SDialogueTextPropertyEditableTextBox::OnNamespaceCommitted(const FText& NewText, ETextCommit::Type CommitInfo)
710{
711 if (!IsValidIdentity(NewText))
712 {
713 return;
714 }
715
716 const int32 NumTexts = EditableTextProperty->GetNumTexts();
717
718 // Don't commit the Multiple Values text if there are multiple properties being set
719 if (NumTexts > 0 && (NumTexts == 1 || NewText.ToString() != MultipleValuesText.ToString()))
720 {
721 const FString& TextNamespace = NewText.ToString();
722 for (int32 TextIndex = 0; TextIndex < NumTexts; ++TextIndex)
723 {
724 const FText PropertyValue = EditableTextProperty->GetText(TextIndex);
725
726 // Only apply the change if the new namespace is different - we want to keep the keys stable where possible
727 const FString CurrentTextNamespace = TextNamespaceUtil::StripPackageNamespace(FTextInspector::GetNamespace(PropertyValue).Get(FString()));
728 if (CurrentTextNamespace.Equals(TextNamespace, ESearchCase::CaseSensitive))
729 {
730 continue;
731 }
732
733 // Get the stable namespace and key that we should use for this property
734 FString NewNamespace;
735 FString NewKey;
736#if USE_STABLE_LOCALIZATION_KEYS
737 {
738 const FString* TextSource = FTextInspector::GetSourceString(PropertyValue);
739 EditableTextProperty->GetStableTextId(
740 TextIndex,
741 IEditableTextProperty::ETextPropertyEditAction::EditedNamespace,
742 TextSource ? *TextSource : FString(),
743 TextNamespace,
744 FTextInspector::GetKey(PropertyValue).Get(FString()),
745 NewNamespace,
746 NewKey
747 );
748 }
749#else // USE_STABLE_LOCALIZATION_KEYS
750 {
751 NewNamespace = TextNamespace;
752
753 // If the current key is a GUID, then we can preserve that when setting the new namespace
754 NewKey = FTextInspector::GetKey(PropertyValue).Get(FString());
755 {
756 FGuid TmpGuid;
757 if (!FGuid::Parse(NewKey, TmpGuid))
758 {
759 NewKey = FGuid::NewGuid().ToString();
760 }
761 }
762 }
763#endif // USE_STABLE_LOCALIZATION_KEYS
764
765 EditableTextProperty->SetText(TextIndex, FText::ChangeKey(NewNamespace, NewKey, PropertyValue));
766 }
767 }
768}
769
771{
772 FText KeyValue;
773
774 const int32 NumTexts = EditableTextProperty->GetNumTexts();
775 if (NumTexts == 1)
776 {
777 const FText PropertyValue = EditableTextProperty->GetText(0);
778 TOptional<FString> FoundKey = FTextInspector::GetKey(PropertyValue);
779 if (FoundKey.IsSet())
780 {
781 KeyValue = FText::FromString(FoundKey.GetValue());
782 }
783 }
784 else if (NumTexts > 1)
785 {
786 KeyValue = MultipleValuesText;
787 }
788
789 return KeyValue;
790}
791
792#if USE_STABLE_LOCALIZATION_KEYS
793
794void SDialogueTextPropertyEditableTextBox::OnKeyChanged(const FText& NewText)
795{
796 FText ErrorMessage;
797 const FText ErrorCtx = LOCTEXT("TextKeyErrorCtx", "Key");
798 const bool bIsValidName = IsValidIdentity(NewText, &ErrorMessage, &ErrorCtx);
799
800 if (NewText.IsEmptyOrWhitespace())
801 {
802 ErrorMessage = LOCTEXT("TextKeyEmptyErrorMsg", "Key cannot be empty so a new key will be assigned");
803 }
804 else if (bIsValidName)
805 {
806 // Valid name, so check it won't cause an identity conflict (only test if we have a single text selected to avoid confusion)
807 const int32 NumTexts = EditableTextProperty->GetNumTexts();
808 if (NumTexts == 1)
809 {
810 const FText PropertyValue = EditableTextProperty->GetText(0);
811
812 const FString TextNamespace = FTextInspector::GetNamespace(PropertyValue).Get(FString());
813 const FString TextKey = NewText.ToString();
814
815 // Get the stable namespace and key that we should use for this property
816 // If it comes back with the same namespace but a different key then it means there was an identity conflict
817 FString NewNamespace;
818 FString NewKey;
819 const FString* TextSource = FTextInspector::GetSourceString(PropertyValue);
820 EditableTextProperty->GetStableTextId(
821 0,
822 IEditableTextProperty::ETextPropertyEditAction::EditedKey,
823 TextSource ? *TextSource : FString(),
824 TextNamespace,
825 TextKey,
826 NewNamespace,
827 NewKey
828 );
829
830 if (TextNamespace.Equals(NewNamespace, ESearchCase::CaseSensitive) && !TextKey.Equals(NewKey, ESearchCase::CaseSensitive))
831 {
832 ErrorMessage = LOCTEXT("TextKeyConflictErrorMsg", "Identity (namespace & key) is being used by a different text within this package so a new key will be assigned");
833 }
834 }
835 }
836
837 KeyEditableTextBox->SetError(ErrorMessage);
838}
839
840void SDialogueTextPropertyEditableTextBox::OnKeyCommitted(const FText& NewText, ETextCommit::Type CommitInfo)
841{
842 if (!IsValidIdentity(NewText))
843 {
844 return;
845 }
846
847 const int32 NumTexts = EditableTextProperty->GetNumTexts();
848
849 // Don't commit the Multiple Values text if there are multiple properties being set
850 if (NumTexts > 0 && (NumTexts == 1 || NewText.ToString() != MultipleValuesText.ToString()))
851 {
852 const FString& TextKey = NewText.ToString();
853 for (int32 TextIndex = 0; TextIndex < NumTexts; ++TextIndex)
854 {
855 const FText PropertyValue = EditableTextProperty->GetText(TextIndex);
856
857 // Only apply the change if the new key is different - we want to keep the keys stable where possible
858 const FString CurrentTextKey = FTextInspector::GetKey(PropertyValue).Get(FString());
859 if (CurrentTextKey.Equals(TextKey, ESearchCase::CaseSensitive))
860 {
861 continue;
862 }
863
864 // Get the stable namespace and key that we should use for this property
865 FString NewNamespace;
866 FString NewKey;
867 const FString* TextSource = FTextInspector::GetSourceString(PropertyValue);
868 EditableTextProperty->GetStableTextId(
869 TextIndex,
870 IEditableTextProperty::ETextPropertyEditAction::EditedKey,
871 TextSource ? *TextSource : FString(),
872 FTextInspector::GetNamespace(PropertyValue).Get(FString()),
873 TextKey,
874 NewNamespace,
875 NewKey
876 );
877
878 EditableTextProperty->SetText(TextIndex, FText::ChangeKey(NewNamespace, NewKey, PropertyValue));
879 }
880 }
881}
882
883FText SDialogueTextPropertyEditableTextBox::GetPackageValue() const
884{
885 FText PackageValue;
886
887 const int32 NumTexts = EditableTextProperty->GetNumTexts();
888 if (NumTexts == 1)
889 {
890 const FText PropertyValue = EditableTextProperty->GetText(0);
891 TOptional<FString> FoundNamespace = FTextInspector::GetNamespace(PropertyValue);
892 if (FoundNamespace.IsSet())
893 {
894 PackageValue = FText::FromString(TextNamespaceUtil::ExtractPackageNamespace(FoundNamespace.GetValue()));
895 }
896 }
897 else if (NumTexts > 1)
898 {
899 PackageValue = MultipleValuesText;
900 }
901
902 return PackageValue;
903}
904
905#endif // USE_STABLE_LOCALIZATION_KEYS
906
908{
909 const int32 NumTexts = EditableTextProperty->GetNumTexts();
910 if (NumTexts == 1)
911 {
912 const FText PropertyValue = EditableTextProperty->GetText(0);
913
914 const bool bIsLocalized = !PropertyValue.IsCultureInvariant();
915 return bIsLocalized == bActiveState ? ECheckBoxState::Checked : ECheckBoxState::Unchecked;
916 }
917
918 return ECheckBoxState::Undetermined;
919}
920
921void SDialogueTextPropertyEditableTextBox::HandleLocalizableCheckStateChanged(ECheckBoxState InCheckboxState, bool bActiveState)
922{
923 const int32 NumTexts = EditableTextProperty->GetNumTexts();
924
925 if (bActiveState)
926 {
927 for (int32 TextIndex = 0; TextIndex < NumTexts; ++TextIndex)
928 {
929 const FText PropertyValue = EditableTextProperty->GetText(TextIndex);
930
931 // Assign a key to any currently culture invariant texts
932 if (PropertyValue.IsCultureInvariant())
933 {
934 // Get the stable namespace and key that we should use for this property
935 FString NewNamespace;
936 FString NewKey;
937 EditableTextProperty->GetStableTextId(
938 TextIndex,
939 IEditableTextProperty::ETextPropertyEditAction::EditedKey,
940 PropertyValue.ToString(),
941 FString(),
942 FString(),
943 NewNamespace,
944 NewKey
945 );
946
947 EditableTextProperty->SetText(TextIndex, FInternationalization::Get().ForUseOnlyByLocMacroAndGraphNodeTextLiterals_CreateText(*PropertyValue.ToString(), *NewNamespace, *NewKey));
948 }
949 }
950 }
951 else
952 {
953 for (int32 TextIndex = 0; TextIndex < NumTexts; ++TextIndex)
954 {
955 const FText PropertyValue = EditableTextProperty->GetText(TextIndex);
956
957 // Clear the identity from any non-culture invariant texts
958 if (!PropertyValue.IsCultureInvariant())
959 {
960 const FString* TextSource = FTextInspector::GetSourceString(PropertyValue);
961 EditableTextProperty->SetText(TextIndex, FText::AsCultureInvariant(PropertyValue.ToString()));
962 }
963 }
964 }
965}
966
968{
969 // Nothing to show
970 if (GetLocalizableVisibility() != EVisibility::Visible)
971 {
972 return EVisibility::Collapsed;
973 }
974
975 return WillNamespaceBeUpdated() ? EVisibility::Visible : EVisibility::Collapsed;
976}
977
979{
980 const int32 NumTexts = EditableTextProperty->GetNumTexts();
981
982 if (NumTexts == 1)
983 {
984 const FText PropertyValue = EditableTextProperty->GetText(0);
985 return PropertyValue.IsCultureInvariant() ? EVisibility::Visible : EVisibility::Collapsed;
986 }
987
988 return EVisibility::Collapsed;
989}
990
991bool SDialogueTextPropertyEditableTextBox::IsValidIdentity(const FText& InIdentity, FText* OutReason, const FText* InErrorCtx) const
992{
993 const FString InvalidIdentityChars = FString::Printf(TEXT("%s%c%c"), INVALID_NAME_CHARACTERS, TextNamespaceUtil::PackageNamespaceStartMarker, TextNamespaceUtil::PackageNamespaceEndMarker);
994 return FName::IsValidXName(InIdentity.ToString(), InvalidIdentityChars, OutReason, InErrorCtx);
995}
996
997#undef LOCTEXT_NAMESPACE
static bool WillTextNamespaceBeUpdated(const FText &Text)
TSharedPtr< SMultiLineEditableTextBox > MultiLineWidget
void Tick(const FGeometry &AllottedGeometry, double InCurrentTime, float InDeltaTime) override
void OnTextCommitted(const FText &NewText, ETextCommit::Type CommitInfo)
void OnNamespaceCommitted(const FText &NewText, ETextCommit::Type CommitInfo)
TSharedPtr< IEditableTextProperty > EditableTextProperty
ECheckBoxState GetLocalizableCheckState(bool bActiveState) const
void HandleLocalizableCheckStateChanged(ECheckBoxState InCheckboxState, bool bActiveState)
bool IsValidIdentity(const FText &InIdentity, FText *OutReason=nullptr, const FText *InErrorCtx=nullptr) const
void GetDesiredWidth(float &OutMinDesiredWidth, float &OutMaxDesiredWidth)
FReply OnFocusReceived(const FGeometry &MyGeometry, const FFocusEvent &InFocusEvent) override
void Construct(const FArguments &Arguments, const TSharedRef< IEditableTextProperty > &InEditableTextProperty, const TSharedRef< IPropertyHandle > &InPropertyHandle)