251 lines
7.1 KiB
C++
251 lines
7.1 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "DateTimeStructCustomization.h"
|
|
|
|
#include "Containers/Array.h"
|
|
#include "DetailWidgetRow.h"
|
|
#include "Fonts/SlateFontInfo.h"
|
|
#include "HAL/PlatformCrt.h"
|
|
#include "Internationalization/Internationalization.h"
|
|
#include "InternationalizationSettingsModel.h"
|
|
#include "Math/UnrealMathSSE.h"
|
|
#include "Misc/Attribute.h"
|
|
#include "Misc/CString.h"
|
|
#include "Misc/Timespan.h"
|
|
#include "PropertyHandle.h"
|
|
#include "Styling/AppStyle.h"
|
|
#include "Styling/ISlateStyle.h"
|
|
#include "UObject/NameTypes.h"
|
|
#include "UObject/UObjectGlobals.h"
|
|
#include "UObject/UnrealType.h"
|
|
#include "Widgets/DeclarativeSyntaxSupport.h"
|
|
#include "Widgets/Input/SEditableTextBox.h"
|
|
|
|
|
|
#define LOCTEXT_NAMESPACE "DateTimeStructCustomization"
|
|
|
|
|
|
/* IDetailCustomization interface
|
|
*****************************************************************************/
|
|
|
|
void FDateTimeStructCustomization::CustomizeChildren(TSharedRef<IPropertyHandle> StructPropertyHandle, class IDetailChildrenBuilder& StructBuilder, IPropertyTypeCustomizationUtils& StructCustomizationUtils)
|
|
{
|
|
/* do nothing */
|
|
}
|
|
|
|
|
|
void FDateTimeStructCustomization::CustomizeHeader(TSharedRef<IPropertyHandle> StructPropertyHandle, class FDetailWidgetRow& HeaderRow, IPropertyTypeCustomizationUtils& StructCustomizationUtils)
|
|
{
|
|
PropertyHandle = StructPropertyHandle;
|
|
|
|
HeaderRow
|
|
.NameContent()
|
|
[
|
|
StructPropertyHandle->CreatePropertyNameWidget()
|
|
]
|
|
.ValueContent()
|
|
.MaxDesiredWidth(0.0f)
|
|
.MinDesiredWidth(125.0f)
|
|
[
|
|
SNew(SEditableTextBox)
|
|
.ClearKeyboardFocusOnCommit(false)
|
|
.IsEnabled(StructPropertyHandle, &IPropertyHandle::IsEditable)
|
|
.ForegroundColor(this, &FDateTimeStructCustomization::HandleTextBoxForegroundColor)
|
|
.OnTextChanged(this, &FDateTimeStructCustomization::HandleTextBoxTextChanged)
|
|
.OnTextCommitted(this, &FDateTimeStructCustomization::HandleTextBoxTextCommited)
|
|
.SelectAllTextOnCommit(true)
|
|
.Font(IPropertyTypeCustomizationUtils::GetRegularFont())
|
|
.Text(this, &FDateTimeStructCustomization::HandleTextBoxText)
|
|
];
|
|
}
|
|
|
|
FDateTimeStructCustomization::FDateTimeStructCustomization()
|
|
: InputValid(true)
|
|
{
|
|
}
|
|
|
|
|
|
|
|
/* FDateTimeStructCustomization callbacks
|
|
*****************************************************************************/
|
|
|
|
FSlateColor FDateTimeStructCustomization::HandleTextBoxForegroundColor() const
|
|
{
|
|
if (InputValid)
|
|
{
|
|
static const FName DefaultForeground("Colors.Foreground");
|
|
return FAppStyle::Get().GetSlateColor(DefaultForeground);
|
|
}
|
|
|
|
static const FName Red("Colors.AccentRed");
|
|
|
|
return FAppStyle::Get().GetSlateColor(Red);
|
|
}
|
|
|
|
|
|
FText FDateTimeStructCustomization::HandleTextBoxText() const
|
|
{
|
|
TArray<void*> RawData;
|
|
PropertyHandle->AccessRawData(RawData);
|
|
|
|
if (RawData.Num() != 1)
|
|
{
|
|
return LOCTEXT("MultipleValues", "Multiple Values");
|
|
}
|
|
|
|
auto DateTimePtr = static_cast<FDateTime*>(RawData[0]);
|
|
if (!DateTimePtr)
|
|
{
|
|
return FText::GetEmpty();
|
|
}
|
|
|
|
return FText::FromString(ToDateTimeZoneString(*DateTimePtr));
|
|
}
|
|
|
|
|
|
void FDateTimeStructCustomization::HandleTextBoxTextChanged(const FText& NewText)
|
|
{
|
|
FDateTime DateTime;
|
|
InputValid = ParseDateTimeZone(NewText.ToString(), DateTime);
|
|
}
|
|
|
|
|
|
void FDateTimeStructCustomization::HandleTextBoxTextCommited(const FText& NewText, ETextCommit::Type CommitInfo)
|
|
{
|
|
FDateTime ParsedDateTime;
|
|
|
|
InputValid = ParseDateTimeZone(NewText.ToString(), ParsedDateTime);
|
|
if (InputValid && PropertyHandle.IsValid())
|
|
{
|
|
TArray<void*> RawData;
|
|
PropertyHandle->AccessRawData(RawData);
|
|
|
|
PropertyHandle->NotifyPreChange();
|
|
for (auto RawDataInstance : RawData)
|
|
{
|
|
*(FDateTime*)RawDataInstance = ParsedDateTime;
|
|
}
|
|
PropertyHandle->NotifyPostChange(EPropertyChangeType::ValueSet);
|
|
PropertyHandle->NotifyFinishedChangingProperties();
|
|
}
|
|
}
|
|
|
|
|
|
int32 FDateTimeStructCustomization::GetLocalTimezone()
|
|
{
|
|
const UInternationalizationSettingsModel* const InternationalizationSettingsModel = GetDefault<UInternationalizationSettingsModel>();
|
|
if (!InternationalizationSettingsModel)
|
|
{
|
|
return TIMEZONE_UTC;
|
|
}
|
|
|
|
return InternationalizationSettingsModel->GetTimezoneValue();
|
|
}
|
|
|
|
|
|
bool FDateTimeStructCustomization::ParseDateTimeZone(const FString& DateTimeZoneString, FDateTime& OutDateTime)
|
|
{
|
|
static FString Delimiter = FString(TEXT(" "));
|
|
|
|
// Split our DatetimeZone string into a date and a timezone marker
|
|
FString DateString;
|
|
FString TimezoneString;
|
|
if (!DateTimeZoneString.Split(Delimiter, &DateString, &TimezoneString, ESearchCase::CaseSensitive, ESearchDir::FromEnd))
|
|
{
|
|
DateString = DateTimeZoneString;
|
|
}
|
|
|
|
// Trim surrounding whitespace
|
|
DateString.TrimStartAndEndInline();
|
|
TimezoneString.TrimStartAndEndInline();
|
|
|
|
// Validate date
|
|
FDateTime LocalizedDate;
|
|
if (DateString.IsEmpty() || !FDateTime::Parse(DateString, LocalizedDate))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Validate timezone marker
|
|
if (TimezoneString.IsEmpty())
|
|
{
|
|
// If no timezone is present, we assume the user's preferred timezone
|
|
OutDateTime = ConvertTime(LocalizedDate, GetLocalTimezone(), TIMEZONE_UTC);
|
|
return true;
|
|
}
|
|
|
|
// Fail if timezone string isn't numeric
|
|
if (!TimezoneString.IsNumeric())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Convert timezone into int
|
|
int32 Timezone = FCString::Atoi(*TimezoneString);
|
|
Timezone = ConvertShortTimezone(Timezone);
|
|
|
|
// Check for timezones in the full-format HHMM, ex: -0500, +1345, etc
|
|
const int32 TimezoneHour = Timezone / 100;
|
|
const bool bHasValidMinuteOffset = ((FMath::Abs(Timezone) % 100) % 15 == 0);
|
|
const bool bIsTimezoneHourValid = (TimezoneHour >= -12 && TimezoneHour <= 14);
|
|
if (bHasValidMinuteOffset && bIsTimezoneHourValid)
|
|
{
|
|
OutDateTime = ConvertTime(LocalizedDate, Timezone, TIMEZONE_UTC);
|
|
return true;
|
|
}
|
|
|
|
// Not a valid time
|
|
return false;
|
|
}
|
|
|
|
|
|
FDateTime FDateTimeStructCustomization::ConvertTime(const FDateTime& InDate, int32 InTimezone, int32 OutTimezone)
|
|
{
|
|
if (InTimezone == OutTimezone)
|
|
{
|
|
return InDate;
|
|
}
|
|
|
|
// Timezone Hour ranges go from -12 to +14 from UTC
|
|
// Convert from whole-hour to the full-format HHMM (-5 -> -0500, 0 -> +0000, etc)
|
|
InTimezone = ConvertShortTimezone(InTimezone);
|
|
OutTimezone = ConvertShortTimezone(OutTimezone);
|
|
|
|
// Extract timezone minutes
|
|
const int32 InTimezoneMinutes = (FMath::Abs(InTimezone) % 100);
|
|
const int32 OutTimezoneMinutes = (FMath::Abs(OutTimezone) % 100);
|
|
|
|
// Calculate our Minutes difference
|
|
const int32 MinutesDifference = OutTimezoneMinutes - InTimezoneMinutes;
|
|
|
|
// Calculate our Hours difference
|
|
const int32 HoursDifference = (OutTimezone / 100) - (InTimezone / 100);
|
|
|
|
return FDateTime(InDate + FTimespan(HoursDifference, MinutesDifference, 0));
|
|
}
|
|
|
|
|
|
FString FDateTimeStructCustomization::ToDateTimeZoneString(const FDateTime& UTCDate)
|
|
{
|
|
const int32 DisplayTimezone = GetLocalTimezone();
|
|
const FDateTime LocalTime = ConvertTime(UTCDate, TIMEZONE_UTC, DisplayTimezone);
|
|
return FString::Printf(TEXT("%s %s%0.4d"), *LocalTime.ToString(), (DisplayTimezone >= 0 ? TEXT("+") : TEXT("")), DisplayTimezone);
|
|
|
|
}
|
|
|
|
|
|
int32 FDateTimeStructCustomization::ConvertShortTimezone(int32 ShortTimezone)
|
|
{
|
|
// Convert timezones from short-format into long format, -5 -> -0500
|
|
// Timezone Hour ranges go from -12 to +14 from UTC
|
|
if (ShortTimezone >= -12 && ShortTimezone <= 14)
|
|
{
|
|
return ShortTimezone * 100;
|
|
}
|
|
|
|
// Not a short-form timezone
|
|
return ShortTimezone;
|
|
}
|
|
|
|
#undef LOCTEXT_NAMESPACE
|