You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
=================== MAJOR FEATURES + CHANGES =================== Change 3123735 on 2016/09/13 by Josh.Markiewicz #UE4 - added FNames for some common online features to be refactored later #codereview david.nikdel Change 3123608 on 2016/09/13 by Josh.Markiewicz #OSS - refactor FUniqueNetIdRepl to derive from FUniqueNetIdWrapper and clean up redundant code #codereview david.nikdel Change 3120074 on 2016/09/09 by David.Nikdel #Analytics: Move log message to more correct location Change 3120073 on 2016/09/09 by David.Nikdel #Analytics: Adjustments to ET.DroppedSubmission per Wes's feedback - Don't try to restore events at all if caching is disabled (but do log the error) - Stop accumulating events (even the ET.DroppedSubmission event) at 1024 cached to defend against run away accumulation #CodeReview: Wes.Hunt Change 3119959 on 2016/09/09 by Peter.Sauerbrei update to engine to provide the generic chunk install when one is not implemented or requested #rb none Change3119378on 2016/09/09 by David.Nikdel #Analytics #ET: Added a config option to not drop events in the event of a failure to flush. - Events are re-added to the queue (may result in some reordering but timestamps will still be there) in the event of a failure - Also appending an ET.DroppedSubmission event in this case so we can track how often clients fail. Has HTTP_STATUS and URL attributes. - Events accumulate within a given application run but no attempt is made to persist across crashes etc. #CodeReview: Wes.Hunt Change 3118773 on 2016/09/08 by Peter.Sauerbrei bring over fix for missing debug information in IOS executables #rb none Change 3118574 on 2016/09/08 by Peter.Sauerbrei pulled over architecture fix for IOS from Main #rb none Change 3117672 on 2016/09/08 by Steve.Allison Adding 3544_iPadMini3_ChAIRQA Change 3116529 on 2016/09/07 by Josh.Markiewicz #UE4 - reverted started IOS purchase/store work from default engine OSS plugins #codereview david.nikdel Change 3116010 on 2016/09/07 by Josh.Markiewicz #WEX - Copying //WEX/Dev-Mobile to Dev-Main (//WEX/Dev-Main) to get OnlineSubsystemiOS changes Change 3114411 on 2016/09/06 by Peter.Sauerbrei fix for deploying movies to the correct directory #rb none Change3113944on 2016/09/06 by Peter.Sauerbrei addition of resave packages command to UAT #rb none Change 3112948 on 2016/09/02 by Nathan.Green #WEX - Fixing file length on Android so that we can write out stats files (change is being submitted into Dev-Mobile by Chris Babcock) #CodeReview: Chance.Lyon, David.Nikdel Change 3112567 on 2016/09/02 by Josh.Markiewicz #UE4 - fixed possible exception when checking directories that don't exist (copy of CL#3099217) #rb ben.marsh Change 3112055 on 2016/09/02 by Chance.Lyon #WEX - Merging mobile branch changes into main Change 3108827 on 2016/08/31 by Peter.Sauerbrei fix for warning #rb none Change 3105012 on 2016/08/29 by Peter.Sauerbrei enable ICMP for Android #rb none Change 3103322 on 2016/08/26 by David.Nikdel #WEX: Fix for clang warnings in Font Outline code #CodeReview: Matt.Kuhlenschmidt Change 3102935 on 2016/08/26 by David.Nikdel Merging CL 3102878 from //UE4/Dev-Editor/Engine/... to //WEX/Main/Engine/... ------ Added support for outline fonts - An outline size (in slate units), optional material and optional fill color can be specified with each font info. - Outlines do not contribute to measurement directly so the text measuring and shaping methods have been modified to account for outlines - Fixed a bug where font materials do not work properly if part of the font's rendered glyphs were in a different atlas #CodeReview: Matt.Kuhlenschmidt, Matt.Hancy, Peter.Sauerbrei Change 3102541 on 2016/08/26 by Peter.Sauerbrei fix for build warning on Mac and IOS #rb none Change 3101820 on 2016/08/25 by Peter.Sauerbrei Moved the online changes to the plugins directory #rb none Change 3101808 on 2016/08/25 by Peter.Sauerbrei Merging using WEX_Main_to_UE4_WEX_Staging bringing in UE4 engine 4.14 #rb none Change 3097879 on 2016/08/23 by David.Nikdel #Analytics #OSS: Adjusted cohort selection algorithm and test cases Change 3096606 on 2016/08/22 by Nathan.Green #WEX - Adding bAllowWindowResize, bAllowClose, bAllowMaximize, and bAllowMinimize boolean to GeneralProjectSettings - Preventing us from resizing the window for the time being (in the future we may only allow along aspect ratio resizing) #CodeReview: Chance.Lyon, David.Nikdel Change 3094946 on 2016/08/19 by David.Nikdel #OSS - Added FUserOnlineAccountMcp::SelectCohort Change 3094942 on 2016/08/19 by David.Nikdel #UE4 - Made FMD5 const-correct Change 3092494 on 2016/08/17 by Nathan.Green #WEX - Making sure we never build zip64 instead of zip #CodeReview: Chance.Lyon, David.Nikdel, Chris.Babcock Change 3090760 on 2016/08/16 by Michael.Trepka Copy of CL 3089133 Fix for task bar displayed over the fullscreen window on Windows 10 with Anniversary Update Change 3090759 on 2016/08/16 by Michael.Trepka Copy of CL 3078927 Updated WindowTitleBarArea widget to not override window zone in fullscreen mode, to prevent window from being moved. That required adding separate handling for double click in fullscreen, as it's no longer handled by window action. Change 3087872 on 2016/08/12 by Josh.Markiewicz #UE4 - cleaned up IOS store/purchase interface (first pass, minus IAP restore) #codereview david.nikdel, josh.adams Change 3084182 on 2016/08/10 by Peter.Sauerbrei revert out the OpenGL shader compression code #rb none Change 3082565 on 2016/08/09 by Ben.Marsh Fix building with VS2015 update 3. Change 3082557 on 2016/08/09 by Ben.Marsh Fix UBT makefile being invalidated to update adaptive unity build settings, even if that module happens to not include that file in a unity file. Keep a list of all files included by unity files as well as files in the working set. #codereview Mike.Fricker, Michael.Noland Change 3082456 on 2016/08/09 by Josh.Markiewicz #UE4 - fixed typo Change 3082439 on 2016/08/09 by Josh.Markiewicz #UE4 - added CanMakePurchase call to IOS Change 3081905 on 2016/08/09 by Michael.Noland Editor: Made the text colors and font size in the output log configurable in the editor appearance settings (no changes to default values ... yet) #codereview matt.kuhlenschmidt Change 3080932 on 2016/08/08 by Josh.Markiewicz #UE4 - New IOS purchasing/store interface v2 - added interfaces to main IOS subsystem - added proper destruction of interfaces to Shutdown of IOS - moved FStoreKitHelper to its own file -- extended it for new v2 (improvements forthcoming) - MCPCatalogHelper returns bogus user id for IOS app store #codereview josh.adams, david.nikdel #tests very basic IAP stuff so far Change 3080217 on 2016/08/07 by Michael.Noland Engine: Prevented a startup warning when SpectatorClass is nullptr, as not all games require a spectator pawn - Also reduced the number of GetWorld() calls in APlayerController::SpawnSpectatorPawn() Change 3080046 on 2016/08/06 by Michael.Noland Engine: Moved where scissor rect reset happens for custom slate drawables to avoid a conflict with an existing fix in another branch #codereview matt.kuhlenschmidt Change 3080032 on 2016/08/06 by Michael.Noland UMG: Fixed a bug where screen-mode UWidgetComponent widgets were drawn incorrectly offset in splitscreen or with aspect-ratio constrained cameras #codereview nick.darnell, marc.audy Change 3080031 on 2016/08/06 by Michael.Noland Engine: Add the option to return player viewport-relative positions to ProjectWorldLocationToScreenWithDistance, ProjectWorldLocationToScreen, and ProjectWorldToScreen, which is useful if the position is going to be used for widgets in splitscreen or with aspect-ratio constrained cameras #codereview nick.darnell, marc.audy Change 3080029 on 2016/08/06 by Michael.Noland Engine: Fixed a bug where the debug console and other debug rendering would be an incorrect size (based on the last player viewport) and also be partially clipped (depending on what in Slate rendered previously) - This fixes issues with the console being offset and clipped when using aspect ratio constrained cameras or split screen #codereview matt.kuhlenschmidt Change 3079656 on 2016/08/05 by Josh.Markiewicz #WEX - basic IOS changes to project - added OnlineSubsystemIOS - added some default settings - removed GoogleVR from project #codereview david.nikdel Change 3078971 on 2016/08/05 by Steve.Allison Updating to match check-in for UE4 Main @ CL 3078968 Change 3078025 on 2016/08/04 by Michael.Trepka Copy of CLs 3073978 and 3075931 - More reliable way of checking if the cursor should be changed to resize cursor in bordeless window mode - On Windows, lock the cursor to the center of the rect if the cursor is hidden to avoid problems with borderless window's round corners not treated as part of the window. Change 3075415 on 2016/08/03 by Peter.Sauerbrei reduce the metal command buffers Change 3071457 on 2016/07/31 by David.Hunt #WEX Blueprint indexing @Pete: This change and anything in Engine/Content can be stomped by any engine integration. This is just to help with not having to resave all of these to udpate a few of our own content blueprints for search indexing. #CodeReview Peter.Sauerbrei, Steve.Allison, David.Nikdel Change 3068661 on 2016/07/28 by Josh.Markiewicz #WEX - changed the max number of possible UObjects allowed by a factor of 10 to reduce the memory footprint #codereview david.nikdel, peter.sauerbrei Change 3068500 on 2016/07/28 by David.Nikdel #OSSMCP: Use correct HttpRequest creation method to respect game service config Change 3066945 on 2016/07/27 by David.Nikdel Reproduced CL 3063869 from Michael.Noland >> Engine: Added a cvar (t.FPSChart.OpenFolderOnDump) to control whether or not FPS charts automatically open the profiling folder when stopfpschart is executed, which can be useful to avoid a bunch of open >> windows while doing automated testing #CodeReview: Michael.Noland #JIRA: WEX-2342 Change 3063495 on 2016/07/25 by Michael.Trepka Copy of CL 3063426 Borderless window support improvements: - the cursor changes to resize when hovering over the window edge - added a way for widgets to register a delegate that's called when window actions occur (maximize, restore, etc.) - used window action notification for WindowTitleBarArea to improve how toggling fullscreen on double click is handled Change 3063431 on 2016/07/25 by Michael.Trepka Copy of CL 3063057 - Use round corners for windows with no system title bar and border only in windowed mode. Change 3062654 on 2016/07/23 by Michael.Trepka Copy of CL 3046975 and 3056204 - Support for making the game window borderless (no system border or title bar). Disabled by default. Enabling requires adding bUseBorderlessWindow=True to [/Script/EngineSettings.GeneralProjectSettings] in DefaultGame.ini. The game using this is responsible for adding WindowTitleBarArea widget to its UI, as well as window minimize/maximize/close buttons. Change 3062647 on 2016/07/23 by Michael.Trepka Copy of CL 3029211 - Added a setting (on by default) to make the game window preserve its content's aspect ratio while being resized by user Change 3062646 on 2016/07/23 by Michael.Trepka Copy of CLs 3039855, 3042644 and 3042911 - Added an option to toggle fullscreen with F11 key in addition to Alt+Enter Change 3062638 on 2016/07/23 by Michael.Trepka Copy of CL3038201and CL 3046803 -Added WindowTitleBarArea widget Change 3062056 on 2016/07/22 by Peter.Sauerbrei addition of optimization for ios compile times Change 3054586 on 2016/07/18 by Nathan.Green #WEX - Adding tags around my engine level change #CodeReview: Chance.Lyon, David.Nikdel Change 3054581 on 2016/07/18 by Nathan.Green #WEX - Removing previous change, making all buttons ignore space bar and enter as we don't really care about that functionality #JIRA: WEX-2256 #CodeReview: Chance.Lyon, Colin.Pyle, David.Nikdel Change 3048243 on 2016/07/13 by Steve.Allison This one actually has the changes from rev4 Change 3046649 on 2016/07/12 by Steve.Allison Updating to match provision in UE4 Main @ CL 3046262 Change 3046127 on 2016/07/12 by Ian.Fox #UE4, #OnlineSubSystem - Hotfix in the ExpirationDate field early so we can update the OGF plugin #codereview David.Nikdel Change 3034707 on 2016/06/30 by Peter.Sauerbrei update provision to go with latest certificate #rb none Change 3031429 on 2016/06/28 by David.Nikdel #WEX: porting an engine change we depend on in latest OGF (pre integrate) Change 3030084 on 2016/06/27 by David.Nikdel #GameCatalog: Add code to export attributes in itemGrants #CodeReview: Scott.Bowen Change 3030073 on 2016/06/27 by David.Nikdel #Json: Make JsonObjectWrapper play nice with serialization and Import/Export Text #CodeReview: Scott.Bowen Change 3030029 on 2016/06/27 by David.Nikdel #WEX: Fix for FJsonObjectWrapper::ImportTextItem (use FParse::QuotedString to read from Buffer) #CodeReview: Scott.Bowen Change 3029740 on 2016/06/27 by David.Nikdel #OGF #JsonObjectWrapper - Add attributes to catalog grants @ScottB - I didn't get a chance to test this today. Things are crazy for PS+. All the code should already be there on the backend though. Here's a shelf in case you need it asap #CodeReview: Scott.Bowen Change 3028704 on 2016/06/27 by Ian.Fox Duplicating 3027482 from //Orion/Main Read TaggedPropertyRedirects from all config files to allow plugins to register property redirectors You'll need this before you grab the latest OGF or your catalog prices will go away, so here it is now #ue4 #rb David.Nikdel #tests none Change 3021448 on 2016/06/21 by Peter.Sauerbrei potential fix for android apk size issue Change 3020999 on 2016/06/21 by David.Nikdel #WEX: Likely fix for WEX-1610 #CodeReview: Chance.Lyon Change 3008450 on 2016/06/09 by Colin.Pyle #PF - WEX-1737, WEX-1744 - Adding the ability to set new layers in widget components - WidgetComponents are now blueprintable - New blueprint for the level marker menu widget components - Map marker menus are now in a layer above other widget components Change 3007804 on 2016/06/09 by Peter.Sauerbrei fix for build set up failure Change 3007292 on 2016/06/09 by Peter.Sauerbrei add the WEX e-mail stuff back in, seems to have gotten lost in the transition Change 3004478 on 2016/06/07 by Peter.Sauerbrei for now have the cooker respect the bCookAll flag in the project settings. Change 3000256 on 2016/06/03 by Peter.Sauerbrei fix for iOS compile warning Change 2998304 on 2016/06/02 by Nathan.Green #PF - Fixing Windowed Mode #CodeReview: Chance.Lyon, Colin.Pyle, Peter.Sauerbrei Change 2994269 on 2016/05/31 by Peter.Sauerbrei Merging //depot/UE4-WEX/... to //WEX/Main/... Change 2987181 on 2016/05/23 by Peter.Sauerbrei Merging //UE4/WEX-Staging/.p4ignore.txt //UE4/WEX-Staging/Engine/... //UE4/WEX-Staging/GenerateProjectFiles.bat //UE4/WEX-Staging/GenerateProjectFiles.command //UE4/WEX-Staging/GenerateProjectFiles.sh //UE4/WEX-Staging/UE4Games.uprojectdirs //UE4/WEX-Staging/WEX/... to //WEX/Main/... Change 2984959 on 2016/05/20 by Peter.Sauerbrei re-applying HSL engine change #PF PF-292 - Make sure to regenerate the list when you open the recipe view - Fixes cases where you buy an item in the store then return to evolve Change 2984957 on 2016/05/20 by Peter.Sauerbrei re-apply engine change from HSL #PF PF-33 - Check if we are in BeginDestroyed on Animation updates, possibly fixes a crash on level transition - Make interactive items play their mouseover animations - Heroes make the screen shake if they are on cooldown, we should figure out how to remove that on PC Change 2984956 on 2016/05/20 by Peter.Sauerbrei re-apply HSL change #PF PF-11 - Remove simulated touch with the mouse - Add Right-click support to the game - Right click now does the special attack Change 2984345 on 2016/05/19 by Peter.Sauerbrei Copying //UE4/WEX-Staging/... to //WEX/Main/... This should reset the merge history Change 2981872 on 2016/05/18 by Peter.Sauerbrei fixes for IOS build of WEX Change 2980734 on 2016/05/17 by Peter.Sauerbrei Copying //depot/UE4-WEX/... to //WEX/Main/... Populating WEX stream from old depot at CL2979954 #lockdown nick.penwarden [CL 3129012 by Peter Sauerbrei in Main branch]
1500 lines
54 KiB
C++
1500 lines
54 KiB
C++
// Copyright 1998-2016 Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "IOSPlatformEditorPrivatePCH.h"
|
|
#include "SWidgetSwitcher.h"
|
|
#include "IDetailPropertyRow.h"
|
|
#include "IOSTargetSettingsCustomization.h"
|
|
#include "DetailLayoutBuilder.h"
|
|
#include "DetailCategoryBuilder.h"
|
|
#include "PropertyEditing.h"
|
|
#include "DesktopPlatformModule.h"
|
|
#include "MainFrame.h"
|
|
|
|
#include "ScopedTransaction.h"
|
|
#include "SExternalImageReference.h"
|
|
#include "SHyperlinkLaunchURL.h"
|
|
#include "SPlatformSetupMessage.h"
|
|
#include "PlatformIconInfo.h"
|
|
#include "SourceControlHelpers.h"
|
|
#include "ManifestUpdateHelper.h"
|
|
#include "SNotificationList.h"
|
|
#include "NotificationManager.h"
|
|
#include "TargetPlatform.h"
|
|
#include "GameProjectGenerationModule.h"
|
|
#include "SHyperlink.h"
|
|
#include "SProvisionListRow.h"
|
|
#include "SCertificateListRow.h"
|
|
#include "EngineBuildSettings.h"
|
|
|
|
#define LOCTEXT_NAMESPACE "IOSTargetSettings"
|
|
DEFINE_LOG_CATEGORY_STATIC(LogIOSTargetSettings, Log, All);
|
|
|
|
bool SProvisionListRow::bInitialized = false;
|
|
FCheckBoxStyle SProvisionListRow::ProvisionCheckBoxStyle;
|
|
|
|
const FString gProjectNameText("[PROJECT_NAME]");
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// FIOSTargetSettingsCustomization
|
|
namespace FIOSTargetSettingsCustomizationConstants
|
|
{
|
|
const FText DisabledTip = LOCTEXT("GitHubSourceRequiredToolTip", "This requires GitHub source.");
|
|
}
|
|
|
|
|
|
TSharedRef<IDetailCustomization> FIOSTargetSettingsCustomization::MakeInstance()
|
|
{
|
|
return MakeShareable(new FIOSTargetSettingsCustomization);
|
|
}
|
|
|
|
FIOSTargetSettingsCustomization::FIOSTargetSettingsCustomization()
|
|
: EngineInfoPath(FString::Printf(TEXT("%sBuild/IOS/UE4Game-Info.plist"), *FPaths::EngineDir()))
|
|
, GameInfoPath(FString::Printf(TEXT("%sBuild/IOS/Info.plist"), *FPaths::GameDir()))
|
|
, EngineGraphicsPath(FString::Printf(TEXT("%sBuild/IOS/Resources/Graphics"), *FPaths::EngineDir()))
|
|
, GameGraphicsPath(FString::Printf(TEXT("%sBuild/IOS/Resources/Graphics"), *FPaths::GameDir()))
|
|
{
|
|
new (IconNames) FPlatformIconInfo(TEXT("Icon29.png"), LOCTEXT("SettingsIcon_iPhone", "iPhone Settings Icon"), FText::GetEmpty(), 29, 29, FPlatformIconInfo::Optional);// also iOS6 spotlight search
|
|
new (IconNames) FPlatformIconInfo(TEXT("Icon29@2x.png"), LOCTEXT("SettingsIcon_iPhoneRetina", "iPhone Retina Settings Icon"), FText::GetEmpty(), 58, 58, FPlatformIconInfo::Optional); // also iOS6 spotlight search
|
|
new (IconNames) FPlatformIconInfo(TEXT("Icon40.png"), LOCTEXT("SpotlightIcon_iOS7", "iOS7 Spotlight Icon"), FText::GetEmpty(), 40, 40, FPlatformIconInfo::Optional);
|
|
new (IconNames) FPlatformIconInfo(TEXT("Icon40@2x.png"), LOCTEXT("SpotlightIcon_Retina_iOS7", "Retina iOS7 Spotlight Icon"), FText::GetEmpty(), 80, 80, FPlatformIconInfo::Optional);
|
|
new (IconNames) FPlatformIconInfo(TEXT("Icon50.png"), LOCTEXT("SpotlightIcon_iPad_iOS6", "iPad iOS6 Spotlight Icon"), FText::GetEmpty(), 50, 50, FPlatformIconInfo::Optional);
|
|
new (IconNames) FPlatformIconInfo(TEXT("Icon50@2x.png"), LOCTEXT("SpotlightIcon_iPadRetina_iOS6", "iPad Retina iOS6 Spotlight Icon"), FText::GetEmpty(), 100, 100, FPlatformIconInfo::Optional);
|
|
new (IconNames) FPlatformIconInfo(TEXT("Icon57.png"), LOCTEXT("AppIcon_iPhone_iOS6", "iPhone iOS6 App Icon"), FText::GetEmpty(), 57, 57, FPlatformIconInfo::Required);
|
|
new (IconNames) FPlatformIconInfo(TEXT("Icon57@2x.png"), LOCTEXT("AppIcon_iPhoneRetina_iOS6", "iPhone Retina iOS6 App Icon"), FText::GetEmpty(), 114, 114, FPlatformIconInfo::Required);
|
|
new (IconNames) FPlatformIconInfo(TEXT("Icon60@2x.png"), LOCTEXT("AppIcon_iPhoneRetina_iOS7", "iPhone Retina iOS7 App Icon"), FText::GetEmpty(), 120, 120, FPlatformIconInfo::Required);
|
|
new (IconNames)FPlatformIconInfo(TEXT("Icon60@3x.png"), LOCTEXT("AppIcon_iPhoneRetina_iOS8", "iPhone Plus Retina iOS8 App Icon"), FText::GetEmpty(), 180, 180, FPlatformIconInfo::Required);
|
|
new (IconNames)FPlatformIconInfo(TEXT("Icon72.png"), LOCTEXT("AppIcon_iPad_iOS6", "iPad iOS6 App Icon"), FText::GetEmpty(), 72, 72, FPlatformIconInfo::Required);
|
|
new (IconNames) FPlatformIconInfo(TEXT("Icon72@2x.png"), LOCTEXT("AppIcon_iPadRetina_iOS6", "iPad Retina iOS6 App Icon"), FText::GetEmpty(), 144, 144, FPlatformIconInfo::Required);
|
|
new (IconNames) FPlatformIconInfo(TEXT("Icon76.png"), LOCTEXT("AppIcon_iPad_iOS7", "iPad iOS7 App Icon"), FText::GetEmpty(), 76, 76, FPlatformIconInfo::Required);
|
|
new (IconNames) FPlatformIconInfo(TEXT("Icon76@2x.png"), LOCTEXT("AppIcon_iPadRetina_iOS7", "iPad Retina iOS7 App Icon"), FText::GetEmpty(), 152, 152, FPlatformIconInfo::Required);
|
|
new (IconNames)FPlatformIconInfo(TEXT("Icon83.5@2x.png"), LOCTEXT("AppIcon_iPadProRetina_iOS9", "iPad Pro Retina iOS9 App Icon"), FText::GetEmpty(), 167, 167, FPlatformIconInfo::Required);
|
|
|
|
new (LaunchImageNames) FPlatformIconInfo(TEXT("Default.png"), LOCTEXT("LaunchImage_iPhone", "Launch iPhone 4/4S"), FText::GetEmpty(), 320, 480, FPlatformIconInfo::Required);
|
|
new (LaunchImageNames) FPlatformIconInfo(TEXT("Default@2x.png"), LOCTEXT("LaunchImage_iPhoneRetina", "Launch iPhone 4/4S Retina"), FText::GetEmpty(), 640, 960, FPlatformIconInfo::Required);
|
|
new (LaunchImageNames) FPlatformIconInfo(TEXT("Default-568h@2x.png"), LOCTEXT("LaunchImage_iPhone5", "Launch iPhone 5/5S Retina"), FText::GetEmpty(), 640, 1136, FPlatformIconInfo::Required);
|
|
new (LaunchImageNames) FPlatformIconInfo(TEXT("Default-Landscape.png"), LOCTEXT("LaunchImage_iPad_Landscape", "Launch iPad in Landscape"), FText::GetEmpty(), 1024, 768, FPlatformIconInfo::Required);
|
|
new (LaunchImageNames) FPlatformIconInfo(TEXT("Default-Landscape@2x.png"), LOCTEXT("LaunchImage_iPadRetina_Landscape", "Launch iPad Retina in Landscape"), FText::GetEmpty(), 2048, 1536, FPlatformIconInfo::Required);
|
|
new (LaunchImageNames) FPlatformIconInfo(TEXT("Default-Portrait.png"), LOCTEXT("LaunchImage_iPad_Portrait", "Launch iPad in Portrait"), FText::GetEmpty(), 768, 1024, FPlatformIconInfo::Required);
|
|
new (LaunchImageNames) FPlatformIconInfo(TEXT("Default-Portrait@2x.png"), LOCTEXT("LaunchImage_iPadRetina_Portrait", "Launch iPad Retina in Portrait"), FText::GetEmpty(), 1536, 2048, FPlatformIconInfo::Required);
|
|
new (LaunchImageNames) FPlatformIconInfo(TEXT("Default-IPhone6.png"), LOCTEXT("LaunchImage_iPhone6", "Launch iPhone 6 in Portrait"), FText::GetEmpty(), 750, 1334, FPlatformIconInfo::Required);
|
|
new (LaunchImageNames)FPlatformIconInfo(TEXT("Default-IPhone6-Landscape.png"), LOCTEXT("LaunchImage_iPhone6_Landscape", "Launch iPhone 6 in Landscape"), FText::GetEmpty(), 1334, 750, FPlatformIconInfo::Required);
|
|
new (LaunchImageNames)FPlatformIconInfo(TEXT("Default-IPhone6Plus-Landscape.png"), LOCTEXT("LaunchImage_iPhone6Plus_Landscape", "Launch iPhone 6 Plus in Landscape"), FText::GetEmpty(), 2208, 1242, FPlatformIconInfo::Required);
|
|
new (LaunchImageNames) FPlatformIconInfo(TEXT("Default-IPhone6Plus-Portrait.png"), LOCTEXT("LaunchImage_iPhone6Plus_Portrait", "Launch iPhone 6 Plus in Portrait"), FText::GetEmpty(), 1242, 2208, FPlatformIconInfo::Required);
|
|
new (LaunchImageNames) FPlatformIconInfo(TEXT("Default-Landscape-1336.png"), LOCTEXT("LaunchImage_iPadPro_Landscape", "Launch iPad Pro in Landscape"), FText::GetEmpty(), 1336, 1024, FPlatformIconInfo::Required);
|
|
new (LaunchImageNames)FPlatformIconInfo(TEXT("Default-Portrait-1336.png"), LOCTEXT("LaunchImage_iPadPro_Portrait", "Launch iPad Pro in Portrait"), FText::GetEmpty(), 1024, 1336, FPlatformIconInfo::Required);
|
|
new (LaunchImageNames)FPlatformIconInfo(TEXT("Default-Landscape-1336@2x.png"), LOCTEXT("LaunchImage_iPadProRetina_Landscape", "Launch iPad Pro Retina in Landscape"), FText::GetEmpty(), 2732, 2048, FPlatformIconInfo::Required);
|
|
new (LaunchImageNames)FPlatformIconInfo(TEXT("Default-Portrait-1336@2x.png"), LOCTEXT("LaunchImage_iPadProRetina_Portrait", "Launch iPad Pro Retina in Portrait"), FText::GetEmpty(), 2048, 2732, FPlatformIconInfo::Required);
|
|
|
|
bShowAllProvisions = false;
|
|
bShowAllCertificates = false;
|
|
ProvisionList = MakeShareable(new TArray<ProvisionPtr>());
|
|
CertificateList = MakeShareable(new TArray<CertificatePtr>());
|
|
}
|
|
|
|
FIOSTargetSettingsCustomization::~FIOSTargetSettingsCustomization()
|
|
{
|
|
if (IPPProcess.IsValid())
|
|
{
|
|
IPPProcess = NULL;
|
|
FTicker::GetCoreTicker().RemoveTicker(TickerHandle);
|
|
}
|
|
}
|
|
|
|
void FIOSTargetSettingsCustomization::CustomizeDetails(IDetailLayoutBuilder& DetailLayout)
|
|
{
|
|
SavedLayoutBuilder = &DetailLayout;
|
|
|
|
BuildPListSection(DetailLayout);
|
|
|
|
BuildIconSection(DetailLayout);
|
|
|
|
BuildRemoteBuildingSection(DetailLayout);
|
|
|
|
FindRequiredFiles();
|
|
}
|
|
|
|
static FString OutputMessage;
|
|
static void OnOutput(FString Message)
|
|
{
|
|
OutputMessage += Message;
|
|
OutputMessage += "\n";
|
|
UE_LOG(LogTemp, Display, TEXT("%s\n"), *Message);
|
|
}
|
|
|
|
void FIOSTargetSettingsCustomization::UpdateStatus()
|
|
{
|
|
if (OutputMessage.Len() > 0)
|
|
{
|
|
CertificateList->Reset();
|
|
ProvisionList->Reset();
|
|
|
|
// Now split up the log into multiple lines
|
|
TArray<FString> LogLines;
|
|
OutputMessage.ParseIntoArray(LogLines, TEXT("\n"), true);
|
|
|
|
// format of the line being read here!!
|
|
bool bCerts = false;
|
|
bManuallySelected = false;
|
|
for (int Index = 0; Index < LogLines.Num(); Index++)
|
|
{
|
|
FString& Line = LogLines[Index];
|
|
TArray<FString> Fields;
|
|
Line.ParseIntoArray(Fields, TEXT(","), true);
|
|
if (Line.Contains(TEXT("CERTIFICATE-"), ESearchCase::CaseSensitive))
|
|
{
|
|
CertificatePtr Cert = MakeShareable<FCertificate>(new FCertificate());
|
|
for (int FieldIndex = 0; FieldIndex < Fields.Num(); ++FieldIndex)
|
|
{
|
|
FString Key, Value;
|
|
Fields[FieldIndex].Split(TEXT(":"), &Key, &Value);
|
|
if (Key.Contains("Name"))
|
|
{
|
|
Cert->Name = Value;
|
|
}
|
|
else if (Key.Contains(TEXT("Validity")))
|
|
{
|
|
Cert->Status = Value;
|
|
}
|
|
else if (Key.Contains(TEXT("EndDate")))
|
|
{
|
|
FString Date, Time;
|
|
Value.Split(TEXT("T"), &Date, &Time);
|
|
Cert->Expires = Date;
|
|
}
|
|
}
|
|
CertificatePtr PrevCert = NULL;
|
|
for (int CIndex = 0; CIndex < CertificateList->Num() && !PrevCert.IsValid(); ++CIndex)
|
|
{
|
|
if ((*CertificateList)[CIndex]->Name == Cert->Name)
|
|
{
|
|
PrevCert = (*CertificateList)[CIndex];
|
|
break;
|
|
}
|
|
}
|
|
|
|
// check to see if this the one selected in the ini file
|
|
FString OutString;
|
|
SignCertificateProperty->GetValueAsFormattedString(OutString);
|
|
Cert->bManuallySelected = (OutString == Cert->Name);
|
|
bManuallySelected |= Cert->bManuallySelected;
|
|
if (!PrevCert.IsValid())
|
|
{
|
|
CertificateList->Add(Cert);
|
|
}
|
|
else
|
|
{
|
|
FDateTime time1, time2;
|
|
FDateTime::ParseIso8601(*(PrevCert->Expires), time1);
|
|
FDateTime::ParseIso8601(*(Cert->Expires), time2);
|
|
if (time2 > time1)
|
|
{
|
|
PrevCert->Expires = Cert->Expires;
|
|
PrevCert->Status = Cert->Status;
|
|
}
|
|
Cert = NULL;
|
|
}
|
|
}
|
|
else if (Line.Contains(TEXT("PROVISION-"), ESearchCase::CaseSensitive))
|
|
{
|
|
ProvisionPtr Prov = MakeShareable<FProvision>(new FProvision());
|
|
for (int FieldIndex = 0; FieldIndex < Fields.Num(); ++FieldIndex)
|
|
{
|
|
FString Key, Value;
|
|
Fields[FieldIndex].Split(TEXT(":"), &Key, &Value);
|
|
if (Key.Contains("File"))
|
|
{
|
|
Prov->FileName = Value;
|
|
}
|
|
else if (Key.Contains("Name"))
|
|
{
|
|
Prov->Name = Value;
|
|
}
|
|
else if (Key.Contains(TEXT("Validity")))
|
|
{
|
|
Prov->Status = Value;
|
|
}
|
|
else if (Key.Contains(TEXT("Type")))
|
|
{
|
|
Prov->bDistribution = Value.Contains(TEXT("DISTRIBUTION"));
|
|
}
|
|
}
|
|
|
|
// check to see if this the one selected in the ini file
|
|
FString OutString;
|
|
MobileProvisionProperty->GetValueAsFormattedString(OutString);
|
|
Prov->bManuallySelected = (OutString == Prov->FileName);
|
|
bManuallySelected |= Prov->bManuallySelected;
|
|
ProvisionList->Add(Prov);
|
|
}
|
|
else if (Line.Contains(TEXT("MATCHED-"), ESearchCase::CaseSensitive))
|
|
{
|
|
for (int FieldIndex = 0; FieldIndex < Fields.Num(); ++FieldIndex)
|
|
{
|
|
FString Key, Value;
|
|
Fields[FieldIndex].Split(TEXT(":"), &Key, &Value);
|
|
if (Key.Contains("File"))
|
|
{
|
|
SelectedFile = Value;
|
|
}
|
|
else if (Key.Contains("Provision"))
|
|
{
|
|
SelectedProvision = Value;
|
|
}
|
|
else if (Key.Contains(TEXT("Cert")))
|
|
{
|
|
SelectedCert = Value;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
FilterLists();
|
|
}
|
|
}
|
|
|
|
void FIOSTargetSettingsCustomization::UpdateSSHStatus()
|
|
{
|
|
// updated SSH key
|
|
const UIOSRuntimeSettings* Settings = GetDefault<UIOSRuntimeSettings>();
|
|
const_cast<UIOSRuntimeSettings*>(Settings)->PostInitProperties();
|
|
}
|
|
|
|
void FIOSTargetSettingsCustomization::BuildPListSection(IDetailLayoutBuilder& DetailLayout)
|
|
{
|
|
// Info.plist category
|
|
IDetailCategoryBuilder& ProvisionCategory = DetailLayout.EditCategory(TEXT("Mobile Provision"));
|
|
IDetailCategoryBuilder& AppManifestCategory = DetailLayout.EditCategory(TEXT("Info.plist"));
|
|
IDetailCategoryBuilder& BundleCategory = DetailLayout.EditCategory(TEXT("BundleInformation"));
|
|
IDetailCategoryBuilder& OrientationCategory = DetailLayout.EditCategory(TEXT("Orientation"));
|
|
IDetailCategoryBuilder& RenderCategory = DetailLayout.EditCategory(TEXT("Rendering"));
|
|
IDetailCategoryBuilder& OSInfoCategory = DetailLayout.EditCategory(TEXT("OS Info"));
|
|
IDetailCategoryBuilder& DeviceCategory = DetailLayout.EditCategory(TEXT("Devices"));
|
|
IDetailCategoryBuilder& BuildCategory = DetailLayout.EditCategory(TEXT("Build"));
|
|
IDetailCategoryBuilder& ExtraCategory = DetailLayout.EditCategory(TEXT("Extra PList Data"));
|
|
MobileProvisionProperty = DetailLayout.GetProperty(GET_MEMBER_NAME_CHECKED(UIOSRuntimeSettings, MobileProvision));
|
|
BuildCategory.AddProperty(MobileProvisionProperty)
|
|
.Visibility(EVisibility::Hidden);
|
|
SignCertificateProperty = DetailLayout.GetProperty(GET_MEMBER_NAME_CHECKED(UIOSRuntimeSettings, SigningCertificate));
|
|
BuildCategory.AddProperty(SignCertificateProperty)
|
|
.Visibility(EVisibility::Hidden);
|
|
|
|
/* ProvisionCategory.AddCustomRow(TEXT("Certificate Request"), false)
|
|
.NameContent()
|
|
[
|
|
SNew(SHorizontalBox)
|
|
+ SHorizontalBox::Slot()
|
|
.Padding(FMargin(0, 1, 0, 1))
|
|
.FillWidth(1.0f)
|
|
[
|
|
SNew(STextBlock)
|
|
.Text(LOCTEXT("RequestLabel", "Certificate Request"))
|
|
.Font(DetailLayout.GetDetailFont())
|
|
]
|
|
]
|
|
.ValueContent()
|
|
[
|
|
SNew(SBox)
|
|
.HAlign(HAlign_Center)
|
|
[
|
|
SNew(SButton)
|
|
.HAlign(HAlign_Center)
|
|
.OnClicked(this, &FIOSTargetSettingsCustomization::OnCertificateRequestClicked)
|
|
[
|
|
SNew(STextBlock)
|
|
.Text(FText::FromString("Create Certificate Request and a Key Pair"))
|
|
]
|
|
]
|
|
];*/
|
|
|
|
ProvisionCategory.AddCustomRow(LOCTEXT("ProvisionLabel", "Provision"), false)
|
|
.WholeRowWidget
|
|
.MinDesiredWidth(0.f)
|
|
.MaxDesiredWidth(0.f)
|
|
.HAlign(HAlign_Fill)
|
|
[
|
|
SNew(SVerticalBox)
|
|
+ SVerticalBox::Slot()
|
|
.AutoHeight()
|
|
[
|
|
SAssignNew(ProvisionInfoSwitcher, SWidgetSwitcher)
|
|
.WidgetIndex(0)
|
|
// searching for provisions
|
|
+SWidgetSwitcher::Slot()
|
|
.HAlign(HAlign_Fill)
|
|
.VAlign(VAlign_Center)
|
|
[
|
|
SNew(SBorder)
|
|
.Padding(4)
|
|
[
|
|
SNew( STextBlock )
|
|
.Text( LOCTEXT( "ProvisionViewerFindingProvisions", "Please wait while we gather information." ) )
|
|
.AutoWrapText( true )
|
|
]
|
|
]
|
|
// importing a provision
|
|
+SWidgetSwitcher::Slot()
|
|
.HAlign(HAlign_Fill)
|
|
.VAlign(VAlign_Center)
|
|
[
|
|
SNew(SBorder)
|
|
.Padding(4)
|
|
[
|
|
SNew( STextBlock )
|
|
.Text( LOCTEXT( "ProvisionViewerImportingProvisions", "Importing Provision. Please wait..." ) )
|
|
.AutoWrapText( true )
|
|
]
|
|
]
|
|
// no provisions found or no valid provisions
|
|
+SWidgetSwitcher::Slot()
|
|
.HAlign(HAlign_Fill)
|
|
.VAlign(VAlign_Center)
|
|
[
|
|
SNew(SBorder)
|
|
.Padding(4)
|
|
[
|
|
SNew( STextBlock )
|
|
.Text( LOCTEXT( "ProvisionViewerNoValidProvisions", "No Provisions Found. Please Import a Provision." ) )
|
|
.AutoWrapText( true )
|
|
]
|
|
]
|
|
+SWidgetSwitcher::Slot()
|
|
.HAlign(HAlign_Fill)
|
|
.VAlign(VAlign_Center)
|
|
[
|
|
SNew(SVerticalBox)
|
|
+ SVerticalBox::Slot()
|
|
.Padding(FMargin(10, 10, 10, 10))
|
|
.AutoHeight()
|
|
[
|
|
SAssignNew(ProvisionListView, SListView<ProvisionPtr>)
|
|
.ItemHeight(20.0f)
|
|
.ListItemsSource(&FilteredProvisionList)
|
|
.OnGenerateRow(this, &FIOSTargetSettingsCustomization::HandleProvisionListGenerateRow)
|
|
.SelectionMode(ESelectionMode::None)
|
|
.HeaderRow
|
|
(
|
|
SNew(SHeaderRow)
|
|
+ SHeaderRow::Column("Selected")
|
|
.DefaultLabel(LOCTEXT("ProvisionListSelectColumnHeader", ""))
|
|
.FixedWidth(30.0f)
|
|
+ SHeaderRow::Column("Name")
|
|
.DefaultLabel(LOCTEXT("ProvisionListNameColumnHeader", "Provision"))
|
|
.FillWidth(1.0f)
|
|
+ SHeaderRow::Column("File")
|
|
.DefaultLabel(LOCTEXT("ProvisionListFileColumnHeader", "File"))
|
|
+ SHeaderRow::Column("Status")
|
|
.DefaultLabel(LOCTEXT("ProvisionListStatusColumnHeader", "Status"))
|
|
+ SHeaderRow::Column("Distribution")
|
|
.DefaultLabel(LOCTEXT("ProvisionListDistributionColumnHeader", "Distribution"))
|
|
.FixedWidth(75.0f)
|
|
)
|
|
]
|
|
+ SVerticalBox::Slot()
|
|
.AutoHeight()
|
|
.Padding(0.0f, 6.0f, 0.0f, 4.0f)
|
|
[
|
|
SNew(SSeparator)
|
|
.Orientation(Orient_Horizontal)
|
|
]
|
|
+ SVerticalBox::Slot()
|
|
.AutoHeight()
|
|
[
|
|
SNew(SHorizontalBox)
|
|
|
|
+ SHorizontalBox::Slot()
|
|
.AutoWidth()
|
|
[
|
|
SNew(SRichTextBlock)
|
|
.Text(LOCTEXT("ProvisionMessage", "<RichTextBlock.TextHighlight>Note</>: If no provision is selected the one in green will be used to provision the IPA."))
|
|
.TextStyle(FEditorStyle::Get(), "MessageLog")
|
|
.DecoratorStyleSet(&FEditorStyle::Get())
|
|
.AutoWrapText(true)
|
|
]
|
|
|
|
+ SHorizontalBox::Slot()
|
|
.FillWidth(1.0f)
|
|
.HAlign(HAlign_Right)
|
|
[
|
|
SNew(STextBlock)
|
|
.Text(LOCTEXT("ViewLabel", "View:"))
|
|
]
|
|
|
|
+ SHorizontalBox::Slot()
|
|
.AutoWidth()
|
|
.Padding(8.0f, 0.0f)
|
|
[
|
|
// all provisions hyper link
|
|
SNew(SHyperlink)
|
|
.OnNavigate(this, &FIOSTargetSettingsCustomization::HandleAllProvisionsHyperlinkNavigate, true)
|
|
.Text(LOCTEXT("AllProvisionsHyperLinkLabel", "All"))
|
|
.ToolTipText(LOCTEXT("AllProvisionsButtonTooltip", "View all provisions."))
|
|
]
|
|
|
|
+ SHorizontalBox::Slot()
|
|
.AutoWidth()
|
|
[
|
|
// valid provisions hyper link
|
|
SNew(SHyperlink)
|
|
.OnNavigate(this, &FIOSTargetSettingsCustomization::HandleAllProvisionsHyperlinkNavigate, false)
|
|
.Text(LOCTEXT("ValidProvisionsHyperlinkLabel", "Valid Only"))
|
|
.ToolTipText(LOCTEXT("ValidProvisionsHyperlinkTooltip", "View Valid provisions."))
|
|
]
|
|
]
|
|
]
|
|
]
|
|
+ SVerticalBox::Slot()
|
|
.AutoHeight()
|
|
[
|
|
SNew(SHorizontalBox)
|
|
+ SHorizontalBox::Slot()
|
|
.Padding(FMargin(0, 5, 0, 10))
|
|
.AutoWidth()
|
|
[
|
|
SNew(SButton)
|
|
.HAlign(HAlign_Center)
|
|
.VAlign(VAlign_Center)
|
|
.OnClicked(this, &FIOSTargetSettingsCustomization::OnInstallProvisionClicked)
|
|
.IsEnabled(this, &FIOSTargetSettingsCustomization::IsImportEnabled)
|
|
[
|
|
SNew(STextBlock)
|
|
.Text(LOCTEXT("ImportProvision", "Import Provision"))
|
|
]
|
|
]
|
|
]
|
|
];
|
|
|
|
ProvisionCategory.AddCustomRow(LOCTEXT("CertificateLabel", "Certificate"), false)
|
|
.WholeRowWidget
|
|
.MinDesiredWidth(0.f)
|
|
.MaxDesiredWidth(0.f)
|
|
.HAlign(HAlign_Fill)
|
|
[
|
|
SNew(SVerticalBox)
|
|
+ SVerticalBox::Slot()
|
|
.AutoHeight()
|
|
[
|
|
SAssignNew(CertificateInfoSwitcher, SWidgetSwitcher)
|
|
.WidgetIndex(0)
|
|
// searching for provisions
|
|
+SWidgetSwitcher::Slot()
|
|
.HAlign(HAlign_Fill)
|
|
.VAlign(VAlign_Center)
|
|
[
|
|
SNew(SBorder)
|
|
.Padding(4)
|
|
[
|
|
SNew( STextBlock )
|
|
.Text( LOCTEXT( "CertificateViewerFindingProvisions", "Please wait while we gather information." ) )
|
|
.AutoWrapText( true )
|
|
]
|
|
]
|
|
// importing certificate
|
|
+SWidgetSwitcher::Slot()
|
|
.HAlign(HAlign_Fill)
|
|
.VAlign(VAlign_Center)
|
|
[
|
|
SNew(SBorder)
|
|
.Padding(4)
|
|
[
|
|
SNew( STextBlock )
|
|
.Text( LOCTEXT( "CertificateViewerImportingCertificate", "Importing Certificate. Please wait..." ) )
|
|
.AutoWrapText( true )
|
|
]
|
|
]
|
|
// no provisions found or no valid provisions
|
|
+SWidgetSwitcher::Slot()
|
|
.HAlign(HAlign_Fill)
|
|
.VAlign(VAlign_Center)
|
|
[
|
|
SNew(SBorder)
|
|
.Padding(4)
|
|
[
|
|
SNew( STextBlock )
|
|
.Text( LOCTEXT( "CertificateViewerNoValidProvisions", "No Certificates Found. Please Import a Certificate." ) )
|
|
.AutoWrapText( true )
|
|
]
|
|
]
|
|
+SWidgetSwitcher::Slot()
|
|
.HAlign(HAlign_Fill)
|
|
.VAlign(VAlign_Center)
|
|
[
|
|
SNew(SVerticalBox)
|
|
+ SVerticalBox::Slot()
|
|
.AutoHeight()
|
|
[
|
|
SNew(SHorizontalBox)
|
|
+ SHorizontalBox::Slot()
|
|
.Padding(FMargin(10, 10, 10, 10))
|
|
.FillWidth(1.0f)
|
|
[
|
|
SAssignNew(CertificateListView, SListView<CertificatePtr>)
|
|
.ItemHeight(20.0f)
|
|
.ListItemsSource(&FilteredCertificateList)
|
|
.OnGenerateRow(this, &FIOSTargetSettingsCustomization::HandleCertificateListGenerateRow)
|
|
.SelectionMode(ESelectionMode::None)
|
|
.HeaderRow
|
|
(
|
|
SNew(SHeaderRow)
|
|
+ SHeaderRow::Column("Selected")
|
|
.DefaultLabel(LOCTEXT("CertificateListSelectColumnHeader", ""))
|
|
.FixedWidth(30.0f)
|
|
+ SHeaderRow::Column("Name")
|
|
.DefaultLabel(LOCTEXT("CertificateListNameColumnHeader", "Certificate"))
|
|
+ SHeaderRow::Column("Status")
|
|
.DefaultLabel(LOCTEXT("CertificateListStatusColumnHeader", "Status"))
|
|
.FixedWidth(75.0f)
|
|
+ SHeaderRow::Column("Expires")
|
|
.DefaultLabel(LOCTEXT("CertificateListExpiresColumnHeader", "Expires"))
|
|
.FixedWidth(75.0f)
|
|
)
|
|
]
|
|
]
|
|
+ SVerticalBox::Slot()
|
|
.AutoHeight()
|
|
.Padding(0.0f, 6.0f, 0.0f, 4.0f)
|
|
[
|
|
SNew(SSeparator)
|
|
.Orientation(Orient_Horizontal)
|
|
]
|
|
+ SVerticalBox::Slot()
|
|
.AutoHeight()
|
|
[
|
|
SNew(SHorizontalBox)
|
|
+ SHorizontalBox::Slot()
|
|
.AutoWidth()
|
|
[
|
|
SNew(SRichTextBlock)
|
|
.Text(LOCTEXT("CertificateMessage", "<RichTextBlock.TextHighlight>Note</>: If no certificate is selected then the one in green will be used to sign the IPA."))
|
|
.TextStyle(FEditorStyle::Get(), "MessageLog")
|
|
.DecoratorStyleSet(&FEditorStyle::Get())
|
|
.AutoWrapText(true)
|
|
]
|
|
+ SHorizontalBox::Slot()
|
|
.FillWidth(1.0f)
|
|
.HAlign(HAlign_Right)
|
|
[
|
|
SNew(STextBlock)
|
|
.Text(LOCTEXT("ViewLabel", "View:"))
|
|
]
|
|
|
|
+ SHorizontalBox::Slot()
|
|
.AutoWidth()
|
|
.Padding(8.0f, 0.0f)
|
|
[
|
|
// all provisions hyper link
|
|
SNew(SHyperlink)
|
|
.OnNavigate(this, &FIOSTargetSettingsCustomization::HandleAllCertificatesHyperlinkNavigate, true)
|
|
.Text(LOCTEXT("AllCertificatesHyperLinkLabel", "All"))
|
|
.ToolTipText(LOCTEXT("AllCertificatesButtonTooltip", "View all certificates."))
|
|
]
|
|
|
|
+ SHorizontalBox::Slot()
|
|
.AutoWidth()
|
|
[
|
|
// valid provisions hyper link
|
|
SNew(SHyperlink)
|
|
.OnNavigate(this, &FIOSTargetSettingsCustomization::HandleAllCertificatesHyperlinkNavigate, false)
|
|
.Text(LOCTEXT("ValidCertificatesHyperlinkLabel", "Valid Only"))
|
|
.ToolTipText(LOCTEXT("ValidCertificatesHyperlinkTooltip", "View Valid certificates."))
|
|
]
|
|
]
|
|
]
|
|
]
|
|
+ SVerticalBox::Slot()
|
|
.AutoHeight()
|
|
[
|
|
SNew(SHorizontalBox)
|
|
+ SHorizontalBox::Slot()
|
|
.Padding(FMargin(0, 5, 0, 10))
|
|
.AutoWidth()
|
|
[
|
|
SNew(SButton)
|
|
.HAlign(HAlign_Center)
|
|
.VAlign(VAlign_Center)
|
|
.OnClicked(this, &FIOSTargetSettingsCustomization::OnInstallCertificateClicked)
|
|
.IsEnabled(this, &FIOSTargetSettingsCustomization::IsImportEnabled)
|
|
[
|
|
SNew(STextBlock)
|
|
.Text(LOCTEXT("ImportCertificate", "Import Certificate"))
|
|
]
|
|
]
|
|
]
|
|
];
|
|
|
|
BundleCategory.AddCustomRow(LOCTEXT("UpgradeInfo", "Upgrade Info"), false)
|
|
.WholeRowWidget
|
|
[
|
|
SNew(SBorder)
|
|
.Padding(1)
|
|
[
|
|
SNew(SHorizontalBox)
|
|
+ SHorizontalBox::Slot()
|
|
.Padding(FMargin(10, 10, 10, 10))
|
|
.FillWidth(1.0f)
|
|
[
|
|
SNew(SRichTextBlock)
|
|
.Text(LOCTEXT("IOSUpgradeInfoMessage", "<RichTextBlock.TextHighlight>Note to users from 4.6 or earlier</>: We now <RichTextBlock.TextHighlight>GENERATE</> an Info.plist when building, so if you have customized your .plist file, you will need to put all of your changes into the below settings. Note that we don't touch the .plist file that is in your project directory, so you can use it as reference."))
|
|
.TextStyle(FEditorStyle::Get(), "MessageLog")
|
|
.DecoratorStyleSet(&FEditorStyle::Get())
|
|
.AutoWrapText(true)
|
|
// + SRichTextBlock::HyperlinkDecorator(TEXT("browser"), FSlateHyperlinkRun::FOnClick::CreateStatic(&OnBrowserLinkClicked))
|
|
]
|
|
]
|
|
];
|
|
|
|
// Show properties that are gated by the plist being present and writable
|
|
RunningIPPProcess = false;
|
|
|
|
#define SETUP_SOURCEONLY_PROP(PropName, Category) \
|
|
{ \
|
|
TSharedRef<IPropertyHandle> PropertyHandle = DetailLayout.GetProperty(GET_MEMBER_NAME_CHECKED(UIOSRuntimeSettings, PropName)); \
|
|
Category.AddProperty(PropertyHandle) \
|
|
.IsEnabled(FEngineBuildSettings::IsSourceDistribution()) \
|
|
.ToolTip(FEngineBuildSettings::IsSourceDistribution() ? PropertyHandle->GetToolTipText() : FIOSTargetSettingsCustomizationConstants::DisabledTip); \
|
|
}
|
|
|
|
#define SETUP_PLIST_PROP(PropName, Category) \
|
|
{ \
|
|
TSharedRef<IPropertyHandle> PropertyHandle = DetailLayout.GetProperty(GET_MEMBER_NAME_CHECKED(UIOSRuntimeSettings, PropName)); \
|
|
Category.AddProperty(PropertyHandle); \
|
|
}
|
|
|
|
#define SETUP_STATUS_PROP(PropName, Category) \
|
|
{ \
|
|
TSharedRef<IPropertyHandle> PropertyHandle = DetailLayout.GetProperty(GET_MEMBER_NAME_CHECKED(UIOSRuntimeSettings, PropName)); \
|
|
Category.AddProperty(PropertyHandle) \
|
|
.Visibility(EVisibility::Hidden); \
|
|
Category.AddCustomRow(LOCTEXT("BundleIdentifier", "BundleIdentifier"), false) \
|
|
.NameContent() \
|
|
[ \
|
|
SNew(SHorizontalBox) \
|
|
+ SHorizontalBox::Slot() \
|
|
.Padding(FMargin(0, 1, 0, 1)) \
|
|
.FillWidth(1.0f) \
|
|
[ \
|
|
SNew(STextBlock) \
|
|
.Text(LOCTEXT("BundleIdentifierLabel", "Bundle Identifier")) \
|
|
.Font(DetailLayout.GetDetailFont()) \
|
|
]\
|
|
] \
|
|
.ValueContent() \
|
|
.MinDesiredWidth( 0.0f ) \
|
|
.MaxDesiredWidth( 0.0f ) \
|
|
[ \
|
|
SNew(SHorizontalBox) \
|
|
+ SHorizontalBox::Slot() \
|
|
.FillWidth(1.0f) \
|
|
.HAlign(HAlign_Fill) \
|
|
[ \
|
|
SAssignNew(BundleIdTextBox, SEditableTextBox) \
|
|
.IsEnabled(this, &FIOSTargetSettingsCustomization::IsImportEnabled) \
|
|
.Text(this, &FIOSTargetSettingsCustomization::GetBundleText, PropertyHandle) \
|
|
.Font(DetailLayout.GetDetailFont()) \
|
|
.SelectAllTextOnCommit( true ) \
|
|
.SelectAllTextWhenFocused( true ) \
|
|
.ClearKeyboardFocusOnCommit(false) \
|
|
.ToolTipText(PropertyHandle->GetToolTipText()) \
|
|
.OnTextCommitted(this, &FIOSTargetSettingsCustomization::OnBundleIdentifierChanged, PropertyHandle) \
|
|
.OnTextChanged(this, &FIOSTargetSettingsCustomization::OnBundleIdentifierTextChanged, ETextCommit::Default, PropertyHandle) \
|
|
] \
|
|
]; \
|
|
}
|
|
|
|
const UIOSRuntimeSettings& Settings = *GetDefault<UIOSRuntimeSettings>();
|
|
|
|
SETUP_PLIST_PROP(BundleDisplayName, BundleCategory);
|
|
SETUP_PLIST_PROP(BundleName, BundleCategory);
|
|
SETUP_STATUS_PROP(BundleIdentifier, BundleCategory);
|
|
SETUP_PLIST_PROP(VersionInfo, BundleCategory);
|
|
SETUP_PLIST_PROP(bSupportsPortraitOrientation, OrientationCategory);
|
|
SETUP_PLIST_PROP(bSupportsUpsideDownOrientation, OrientationCategory);
|
|
SETUP_PLIST_PROP(bSupportsLandscapeLeftOrientation, OrientationCategory);
|
|
SETUP_PLIST_PROP(bSupportsLandscapeRightOrientation, OrientationCategory);
|
|
|
|
SETUP_PLIST_PROP(bSupportsMetal, RenderCategory);
|
|
SETUP_PLIST_PROP(bSupportsOpenGLES2, RenderCategory);
|
|
|
|
SETUP_PLIST_PROP(bSupportsIPad, DeviceCategory);
|
|
SETUP_PLIST_PROP(bSupportsIPhone, DeviceCategory);
|
|
|
|
SETUP_PLIST_PROP(MinimumiOSVersion, OSInfoCategory);
|
|
|
|
SETUP_PLIST_PROP(AdditionalPlistData, ExtraCategory);
|
|
|
|
#undef SETUP_SOURCEONLY_PROP
|
|
}
|
|
|
|
|
|
void FIOSTargetSettingsCustomization::BuildRemoteBuildingSection(IDetailLayoutBuilder& DetailLayout)
|
|
{
|
|
#if PLATFORM_WINDOWS
|
|
IDetailCategoryBuilder& BuildCategory = DetailLayout.EditCategory(TEXT("Build"));
|
|
|
|
// Sub group we wish to add remote building options to.
|
|
FText RemoteBuildingGroupName = LOCTEXT("RemoteBuildingGroupName", "Remote Build Options");
|
|
IDetailGroup& RemoteBuildingGroup = BuildCategory.AddGroup(*RemoteBuildingGroupName.ToString(), RemoteBuildingGroupName, false);
|
|
|
|
|
|
// Remote Server Name Property
|
|
TSharedRef<IPropertyHandle> RemoteServerNamePropertyHandle = DetailLayout.GetProperty(GET_MEMBER_NAME_CHECKED(UIOSRuntimeSettings, RemoteServerName));
|
|
IDetailPropertyRow& RemoteServerNamePropertyRow = RemoteBuildingGroup.AddPropertyRow(RemoteServerNamePropertyHandle);
|
|
RemoteServerNamePropertyRow
|
|
.ToolTip(LOCTEXT("RemoteServerNameToolTip", "The name or ip address of the remote mac which will be used to build IOS"))
|
|
.CustomWidget()
|
|
.NameContent()
|
|
[
|
|
SNew(SHorizontalBox)
|
|
+ SHorizontalBox::Slot()
|
|
.Padding(FMargin(0, 1, 0, 1))
|
|
.FillWidth(1.0f)
|
|
[
|
|
SNew(STextBlock)
|
|
.Text(LOCTEXT("RemoteServerNameLabel", "Remote Server Name"))
|
|
.Font(DetailLayout.GetDetailFont())
|
|
]
|
|
]
|
|
.ValueContent()
|
|
.MinDesiredWidth(150.0f)
|
|
[
|
|
SNew(SHorizontalBox)
|
|
+ SHorizontalBox::Slot()
|
|
.Padding(FMargin(0.0f, 8.0f))
|
|
[
|
|
SNew(SEditableTextBox)
|
|
.IsEnabled(this, &FIOSTargetSettingsCustomization::IsImportEnabled)
|
|
.Text(this, &FIOSTargetSettingsCustomization::GetBundleText, RemoteServerNamePropertyHandle)
|
|
.Font(DetailLayout.GetDetailFont())
|
|
.SelectAllTextOnCommit(true)
|
|
.SelectAllTextWhenFocused(true)
|
|
.ClearKeyboardFocusOnCommit(false)
|
|
.ToolTipText(RemoteServerNamePropertyHandle->GetToolTipText())
|
|
.OnTextCommitted(this, &FIOSTargetSettingsCustomization::OnRemoteServerChanged, RemoteServerNamePropertyHandle)
|
|
]
|
|
|
|
];
|
|
|
|
|
|
|
|
// Add Use RSync Property
|
|
TSharedRef<IPropertyHandle> UseRSyncPropertyHandle = DetailLayout.GetProperty(GET_MEMBER_NAME_CHECKED(UIOSRuntimeSettings, bUseRSync));
|
|
BuildCategory.AddProperty(UseRSyncPropertyHandle)
|
|
.Visibility(EVisibility::Hidden);
|
|
|
|
// Add RSync Username Property
|
|
TSharedRef<IPropertyHandle> RSyncUsernamePropertyHandle = DetailLayout.GetProperty(GET_MEMBER_NAME_CHECKED(UIOSRuntimeSettings, RSyncUsername));
|
|
IDetailPropertyRow& RSyncUsernamePropertyRow = RemoteBuildingGroup.AddPropertyRow(RSyncUsernamePropertyHandle);
|
|
RSyncUsernamePropertyRow
|
|
.ToolTip(LOCTEXT("RSyncUsernameToolTip", "The username of the mac user that matches the specified SSH Key."))
|
|
.CustomWidget()
|
|
.NameContent()
|
|
[
|
|
SNew(SHorizontalBox)
|
|
+ SHorizontalBox::Slot()
|
|
.Padding(FMargin(0, 1, 0, 1))
|
|
.FillWidth(1.0f)
|
|
[
|
|
SNew(STextBlock)
|
|
.Text(LOCTEXT("RSyncUserNameLabel", "RSync User Name"))
|
|
.Font(DetailLayout.GetDetailFont())
|
|
]
|
|
]
|
|
.ValueContent()
|
|
.MinDesiredWidth(150.0f)
|
|
[
|
|
SNew(SHorizontalBox)
|
|
+ SHorizontalBox::Slot()
|
|
.Padding(FMargin(0.0f, 8.0f))
|
|
[
|
|
SNew(SEditableTextBox)
|
|
.IsEnabled(this, &FIOSTargetSettingsCustomization::IsImportEnabled)
|
|
.Text(this, &FIOSTargetSettingsCustomization::GetBundleText, RSyncUsernamePropertyHandle)
|
|
.Font(DetailLayout.GetDetailFont())
|
|
.SelectAllTextOnCommit(true)
|
|
.SelectAllTextWhenFocused(true)
|
|
.ClearKeyboardFocusOnCommit(false)
|
|
.ToolTipText(RSyncUsernamePropertyHandle->GetToolTipText())
|
|
.OnTextCommitted(this, &FIOSTargetSettingsCustomization::OnRemoteServerChanged, RSyncUsernamePropertyHandle)
|
|
]
|
|
];
|
|
|
|
|
|
// Add existing SSH path label.
|
|
TSharedRef<IPropertyHandle> SSHPrivateKeyLocationPropertyHandle = DetailLayout.GetProperty(GET_MEMBER_NAME_CHECKED(UIOSRuntimeSettings, SSHPrivateKeyLocation));
|
|
IDetailPropertyRow& SSHPrivateKeyLocationPropertyRow = RemoteBuildingGroup.AddPropertyRow(SSHPrivateKeyLocationPropertyHandle);
|
|
SSHPrivateKeyLocationPropertyRow
|
|
.ToolTip(LOCTEXT("SSHPrivateKeyLocationToolTip", "The existing location of an SSH Key found by UE4."));
|
|
|
|
|
|
// Add SSH override path
|
|
TSharedRef<IPropertyHandle> SSHPrivateKeyOverridePathPropertyHandle = DetailLayout.GetProperty(GET_MEMBER_NAME_CHECKED(UIOSRuntimeSettings, SSHPrivateKeyOverridePath));
|
|
IDetailPropertyRow& SSHPrivateKeyOverridePathPropertyRow = RemoteBuildingGroup.AddPropertyRow(SSHPrivateKeyOverridePathPropertyHandle);
|
|
SSHPrivateKeyOverridePathPropertyRow
|
|
.ToolTip(LOCTEXT("SSHPrivateKeyOverridePathToolTip", "Override the existing SSH Private Key with one from a specified location."));
|
|
|
|
const FText GenerateSSHText = LOCTEXT("GenerateSSHKey", "Generate SSH Key");
|
|
|
|
// Add a generate key button
|
|
RemoteBuildingGroup.AddWidgetRow()
|
|
.FilterString(GenerateSSHText)
|
|
.WholeRowWidget
|
|
.MinDesiredWidth(0.f)
|
|
.MaxDesiredWidth(0.f)
|
|
.HAlign(HAlign_Fill)
|
|
[
|
|
SNew(SVerticalBox)
|
|
+ SVerticalBox::Slot()
|
|
.AutoHeight()
|
|
[
|
|
SNew(SHorizontalBox)
|
|
+ SHorizontalBox::Slot()
|
|
.Padding(FMargin(0, 5, 0, 10))
|
|
.AutoWidth()
|
|
[
|
|
SNew(SButton)
|
|
.HAlign(HAlign_Center)
|
|
.VAlign(VAlign_Center)
|
|
.OnClicked(this, &FIOSTargetSettingsCustomization::OnGenerateSSHKey)
|
|
.IsEnabled(this, &FIOSTargetSettingsCustomization::IsImportEnabled)
|
|
[
|
|
SNew(STextBlock)
|
|
.Text(GenerateSSHText)
|
|
]
|
|
]
|
|
]
|
|
];
|
|
#endif
|
|
}
|
|
|
|
|
|
void FIOSTargetSettingsCustomization::BuildIconSection(IDetailLayoutBuilder& DetailLayout)
|
|
{
|
|
IDetailCategoryBuilder& RequiredIconCategory = DetailLayout.EditCategory(TEXT("Required Icons"));
|
|
IDetailCategoryBuilder& OptionalIconCategory = DetailLayout.EditCategory(TEXT("Optional Icons"));
|
|
|
|
// Add the icons
|
|
for (const FPlatformIconInfo& Info : IconNames)
|
|
{
|
|
const FVector2D IconImageMaxSize(Info.IconRequiredSize);
|
|
IDetailCategoryBuilder& IconCategory = (Info.RequiredState == FPlatformIconInfo::Required) ? RequiredIconCategory : OptionalIconCategory;
|
|
BuildImageRow(DetailLayout, IconCategory, Info, IconImageMaxSize);
|
|
}
|
|
|
|
// Add the launch images
|
|
IDetailCategoryBuilder& LaunchImageCategory = DetailLayout.EditCategory(TEXT("Launch Images"));
|
|
const FVector2D LaunchImageMaxSize(150.0f, 150.0f);
|
|
for (const FPlatformIconInfo& Info : LaunchImageNames)
|
|
{
|
|
BuildImageRow(DetailLayout, LaunchImageCategory, Info, LaunchImageMaxSize);
|
|
}
|
|
}
|
|
|
|
|
|
FReply FIOSTargetSettingsCustomization::OpenPlistFolder()
|
|
{
|
|
const FString EditPlistFolder = FPaths::ConvertRelativePathToFull(FPaths::GetPath(GameInfoPath));
|
|
FPlatformProcess::ExploreFolder(*EditPlistFolder);
|
|
|
|
return FReply::Handled();
|
|
}
|
|
|
|
void FIOSTargetSettingsCustomization::CopySetupFilesIntoProject()
|
|
{
|
|
// First copy the plist, it must get copied
|
|
FText ErrorMessage;
|
|
if (!SourceControlHelpers::CopyFileUnderSourceControl(GameInfoPath, EngineInfoPath, LOCTEXT("InfoPlist", "Info.plist"), /*out*/ ErrorMessage))
|
|
{
|
|
FNotificationInfo Info(ErrorMessage);
|
|
Info.ExpireDuration = 3.0f;
|
|
FSlateNotificationManager::Get().AddNotification(Info);
|
|
}
|
|
else
|
|
{
|
|
// Now try to copy all of the icons, etc... (these can be ignored if the file already exists)
|
|
TArray<FPlatformIconInfo> Graphics;
|
|
Graphics.Empty(IconNames.Num() + LaunchImageNames.Num());
|
|
Graphics.Append(IconNames);
|
|
Graphics.Append(LaunchImageNames);
|
|
|
|
for (const FPlatformIconInfo& Info : Graphics)
|
|
{
|
|
const FString EngineImagePath = EngineGraphicsPath / Info.IconPath;
|
|
const FString ProjectImagePath = GameGraphicsPath / Info.IconPath;
|
|
|
|
if (!FPaths::FileExists(ProjectImagePath))
|
|
{
|
|
SourceControlHelpers::CopyFileUnderSourceControl(ProjectImagePath, EngineImagePath, Info.IconName, /*out*/ ErrorMessage);
|
|
}
|
|
}
|
|
}
|
|
|
|
SavedLayoutBuilder->ForceRefreshDetails();
|
|
}
|
|
|
|
void FIOSTargetSettingsCustomization::BuildImageRow(IDetailLayoutBuilder& DetailLayout, IDetailCategoryBuilder& Category, const FPlatformIconInfo& Info, const FVector2D& MaxDisplaySize)
|
|
{
|
|
const FString AutomaticImagePath = EngineGraphicsPath / Info.IconPath;
|
|
const FString TargetImagePath = GameGraphicsPath / Info.IconPath;
|
|
|
|
Category.AddCustomRow(Info.IconName)
|
|
.NameContent()
|
|
[
|
|
SNew(SHorizontalBox)
|
|
+ SHorizontalBox::Slot()
|
|
.Padding(FMargin(0, 1, 0, 1))
|
|
.FillWidth(1.0f)
|
|
[
|
|
SNew(STextBlock)
|
|
.Text(Info.IconName)
|
|
.Font(DetailLayout.GetDetailFont())
|
|
]
|
|
]
|
|
.ValueContent()
|
|
.MaxDesiredWidth(400.0f)
|
|
.MinDesiredWidth(100.0f)
|
|
[
|
|
SNew(SHorizontalBox)
|
|
+ SHorizontalBox::Slot()
|
|
.FillWidth(1.0f)
|
|
.VAlign(VAlign_Center)
|
|
[
|
|
SNew(SExternalImageReference, AutomaticImagePath, TargetImagePath)
|
|
.FileDescription(Info.IconDescription)
|
|
.RequiredSize(Info.IconRequiredSize)
|
|
.MaxDisplaySize(MaxDisplaySize)
|
|
]
|
|
];
|
|
}
|
|
|
|
void FIOSTargetSettingsCustomization::FindRequiredFiles()
|
|
{
|
|
const UIOSRuntimeSettings& Settings = *GetDefault<UIOSRuntimeSettings>();
|
|
FString BundleIdentifier = Settings.BundleIdentifier.Replace(*gProjectNameText, FApp::GetGameName());
|
|
#if PLATFORM_MAC
|
|
FString CmdExe = TEXT("/bin/sh");
|
|
FString ScriptPath = FPaths::ConvertRelativePathToFull(FPaths::EngineDir() / TEXT("Build/BatchFiles/Mac/RunMono.sh"));
|
|
FString IPPPath = FPaths::ConvertRelativePathToFull(FPaths::EngineDir() / TEXT("Binaries/DotNet/IOS/IPhonePackager.exe"));
|
|
FString CommandLine = FString::Printf(TEXT("\"%s\" \"%s\" certificates Engine -bundlename \"%s\""), *ScriptPath, *IPPPath, *(BundleIdentifier));
|
|
#else
|
|
FString CmdExe = FPaths::ConvertRelativePathToFull(FPaths::EngineDir() / TEXT("Binaries/DotNet/IOS/IPhonePackager.exe"));
|
|
FString CommandLine = FString::Printf(TEXT("certificates Engine -bundlename \"%s\""), *(BundleIdentifier));
|
|
#endif
|
|
IPPProcess = MakeShareable(new FMonitoredProcess(CmdExe, CommandLine, true));
|
|
OutputMessage = TEXT("");
|
|
IPPProcess->OnOutput().BindStatic(&OnOutput);
|
|
IPPProcess->Launch();
|
|
TickerHandle = FTicker::GetCoreTicker().AddTicker(FTickerDelegate::CreateRaw(this, &FIOSTargetSettingsCustomization::UpdateStatusDelegate), 1.0f);
|
|
if (ProvisionInfoSwitcher.IsValid())
|
|
{
|
|
ProvisionInfoSwitcher->SetActiveWidgetIndex(0);
|
|
}
|
|
if (CertificateInfoSwitcher.IsValid())
|
|
{
|
|
CertificateInfoSwitcher->SetActiveWidgetIndex(0);
|
|
}
|
|
RunningIPPProcess = true;
|
|
}
|
|
|
|
FReply FIOSTargetSettingsCustomization::OnInstallProvisionClicked()
|
|
{
|
|
// pass the file to IPP to install
|
|
FString ProjectPath = FPaths::IsProjectFilePathSet() ? FPaths::ConvertRelativePathToFull(FPaths::GetProjectFilePath()) : FPaths::RootDir() / FApp::GetGameName() / FApp::GetGameName() + TEXT(".uproject");
|
|
FString ProvisionPath;
|
|
|
|
// get the provision by popping up the file dialog
|
|
TArray<FString> OpenFilenames;
|
|
IDesktopPlatform* DesktopPlatform = FDesktopPlatformModule::Get();
|
|
bool bOpened = false;
|
|
int32 FilterIndex = -1;
|
|
FString FileTypes = TEXT("Provision Files (*.mobileprovision)|*.mobileprovision");
|
|
|
|
if ( DesktopPlatform )
|
|
{
|
|
void* ParentWindowWindowHandle = NULL;
|
|
|
|
IMainFrameModule& MainFrameModule = FModuleManager::LoadModuleChecked<IMainFrameModule>(TEXT("MainFrame"));
|
|
const TSharedPtr<SWindow>& MainFrameParentWindow = MainFrameModule.GetParentWindow();
|
|
if ( MainFrameParentWindow.IsValid() && MainFrameParentWindow->GetNativeWindow().IsValid() )
|
|
{
|
|
ParentWindowWindowHandle = MainFrameParentWindow->GetNativeWindow()->GetOSWindowHandle();
|
|
}
|
|
|
|
bOpened = DesktopPlatform->OpenFileDialog(
|
|
ParentWindowWindowHandle,
|
|
LOCTEXT("ImportProvisionDialogTitle", "Import Provision").ToString(),
|
|
FPaths::GetProjectFilePath(),
|
|
TEXT(""),
|
|
FileTypes,
|
|
EFileDialogFlags::None,
|
|
OpenFilenames,
|
|
FilterIndex
|
|
);
|
|
}
|
|
|
|
if ( bOpened )
|
|
{
|
|
ProvisionPath = FPaths::ConvertRelativePathToFull(OpenFilenames[0]);
|
|
|
|
// see if the provision is already installed
|
|
FString DestName = FPaths::GetBaseFilename(ProvisionPath);
|
|
TCHAR Path[4096];
|
|
#if PLATFORM_MAC
|
|
FPlatformMisc::GetEnvironmentVariable(TEXT("HOME"), Path, ARRAY_COUNT(Path));
|
|
FString Destination = FString::Printf(TEXT("\"%s/Library/MobileDevice/Provisioning Profiles/%s.mobileprovision\""), Path, *DestName);
|
|
FString Destination2 = FString::Printf(TEXT("\"%s/Library/MobileDevice/Provisioning Profiles/%s.mobileprovision\""), Path, FApp::GetGameName());
|
|
#else
|
|
FPlatformMisc::GetEnvironmentVariable(TEXT("LOCALAPPDATA"), Path, ARRAY_COUNT(Path));
|
|
FString Destination = FString::Printf(TEXT("%s\\Apple Computer\\MobileDevice\\Provisioning Profiles\\%s.mobileprovision"), Path, *DestName);
|
|
FString Destination2 = FString::Printf(TEXT("%s\\Apple Computer\\MobileDevice\\Provisioning Profiles\\%s.mobileprovision"), Path, FApp::GetGameName());
|
|
#endif
|
|
if (FPaths::FileExists(Destination) || FPaths::FileExists(Destination2))
|
|
{
|
|
FString MessagePrompt = FString::Printf(TEXT("%s mobile provision file already exists. Do you want to replace this provision?"), *DestName);
|
|
if (FPlatformMisc::MessageBoxExt(EAppMsgType::OkCancel, *MessagePrompt, TEXT("File Exists")) == EAppReturnType::Cancel)
|
|
{
|
|
return FReply::Handled();
|
|
}
|
|
}
|
|
|
|
const UIOSRuntimeSettings& Settings = *GetDefault<UIOSRuntimeSettings>();
|
|
#if PLATFORM_MAC
|
|
FString CmdExe = TEXT("/bin/sh");
|
|
FString ScriptPath = FPaths::ConvertRelativePathToFull(FPaths::EngineDir() / TEXT("Build/BatchFiles/Mac/RunMono.sh"));
|
|
FString IPPPath = FPaths::ConvertRelativePathToFull(FPaths::EngineDir() / TEXT("Binaries/DotNet/IOS/IPhonePackager.exe"));
|
|
FString CommandLine = FString::Printf(TEXT("\"%s\" \"%s\" Install Engine -project \"%s\" -provision \"%s\" -bundlename \"%s\""), *ScriptPath, *IPPPath, *ProjectPath, *ProvisionPath, *(Settings.BundleIdentifier));
|
|
#else
|
|
FString CmdExe = FPaths::ConvertRelativePathToFull(FPaths::EngineDir() / TEXT("Binaries/DotNet/IOS/IPhonePackager.exe"));
|
|
FString CommandLine = FString::Printf(TEXT("Install Engine -project \"%s\" -provision \"%s\" -bundlename \"%s\""), *ProjectPath, *ProvisionPath, *(Settings.BundleIdentifier));
|
|
#endif
|
|
IPPProcess = MakeShareable(new FMonitoredProcess(CmdExe, CommandLine, true));
|
|
OutputMessage = TEXT("");
|
|
IPPProcess->OnOutput().BindStatic(&OnOutput);
|
|
IPPProcess->Launch();
|
|
TickerHandle = FTicker::GetCoreTicker().AddTicker(FTickerDelegate::CreateRaw(this, &FIOSTargetSettingsCustomization::UpdateStatusDelegate), 10.0f);
|
|
if (ProvisionInfoSwitcher.IsValid())
|
|
{
|
|
ProvisionInfoSwitcher->SetActiveWidgetIndex(1);
|
|
}
|
|
RunningIPPProcess = true;
|
|
}
|
|
|
|
return FReply::Handled();
|
|
}
|
|
|
|
FReply FIOSTargetSettingsCustomization::OnInstallCertificateClicked()
|
|
{
|
|
// pass the file to IPP to install
|
|
FString ProjectPath = FPaths::IsProjectFilePathSet() ? FPaths::ConvertRelativePathToFull(FPaths::GetProjectFilePath()) : FPaths::RootDir() / FApp::GetGameName() / FApp::GetGameName() + TEXT(".uproject");
|
|
FString CertPath;
|
|
|
|
// get the provision by popping up the file dialog
|
|
TArray<FString> OpenFilenames;
|
|
IDesktopPlatform* DesktopPlatform = FDesktopPlatformModule::Get();
|
|
bool bOpened = false;
|
|
int32 FilterIndex = -1;
|
|
FString FileTypes = TEXT("Code Signing Certificates (*.cer;*.p12)|*.cer;*p12");
|
|
|
|
if ( DesktopPlatform )
|
|
{
|
|
void* ParentWindowWindowHandle = NULL;
|
|
|
|
IMainFrameModule& MainFrameModule = FModuleManager::LoadModuleChecked<IMainFrameModule>(TEXT("MainFrame"));
|
|
const TSharedPtr<SWindow>& MainFrameParentWindow = MainFrameModule.GetParentWindow();
|
|
if ( MainFrameParentWindow.IsValid() && MainFrameParentWindow->GetNativeWindow().IsValid() )
|
|
{
|
|
ParentWindowWindowHandle = MainFrameParentWindow->GetNativeWindow()->GetOSWindowHandle();
|
|
}
|
|
|
|
bOpened = DesktopPlatform->OpenFileDialog(
|
|
ParentWindowWindowHandle,
|
|
LOCTEXT("ImportCertificateDialogTitle", "Import Certificate").ToString(),
|
|
FPaths::GetProjectFilePath(),
|
|
TEXT(""),
|
|
FileTypes,
|
|
EFileDialogFlags::None,
|
|
OpenFilenames,
|
|
FilterIndex
|
|
);
|
|
}
|
|
|
|
if ( bOpened )
|
|
{
|
|
const UIOSRuntimeSettings& Settings = *GetDefault<UIOSRuntimeSettings>();
|
|
CertPath = FPaths::ConvertRelativePathToFull(OpenFilenames[0]);
|
|
#if PLATFORM_MAC
|
|
FString CmdExe = TEXT("/bin/sh");
|
|
FString ScriptPath = FPaths::ConvertRelativePathToFull(FPaths::EngineDir() / TEXT("Build/BatchFiles/Mac/RunMono.sh"));
|
|
FString IPPPath = FPaths::ConvertRelativePathToFull(FPaths::EngineDir() / TEXT("Binaries/DotNet/IOS/IPhonePackager.exe"));
|
|
FString CommandLine = FString::Printf(TEXT("\"%s\" \"%s\" Install Engine -project \"%s\" -certificate \"%s\" -bundlename \"%s\""), *ScriptPath, *IPPPath, *ProjectPath, *CertPath, *(Settings.BundleIdentifier));
|
|
#else
|
|
FString CmdExe = FPaths::ConvertRelativePathToFull(FPaths::EngineDir() / TEXT("Binaries/DotNet/IOS/IPhonePackager.exe"));
|
|
FString CommandLine = FString::Printf(TEXT("Install Engine -project \"%s\" -certificate \"%s\" -bundlename \"%s\""), *ProjectPath, *CertPath, *(Settings.BundleIdentifier));
|
|
#endif
|
|
IPPProcess = MakeShareable(new FMonitoredProcess(CmdExe, CommandLine, true));
|
|
OutputMessage = TEXT("");
|
|
IPPProcess->OnOutput().BindStatic(&OnOutput);
|
|
IPPProcess->Launch();
|
|
TickerHandle = FTicker::GetCoreTicker().AddTicker(FTickerDelegate::CreateRaw(this, &FIOSTargetSettingsCustomization::UpdateStatusDelegate), 10.0f);
|
|
if (CertificateInfoSwitcher.IsValid())
|
|
{
|
|
CertificateInfoSwitcher->SetActiveWidgetIndex(1);
|
|
}
|
|
RunningIPPProcess = true;
|
|
}
|
|
|
|
return FReply::Handled();
|
|
}
|
|
|
|
FReply FIOSTargetSettingsCustomization::OnCertificateRequestClicked()
|
|
{
|
|
// TODO: bring up an open file dialog and then install the provision
|
|
return FReply::Handled();
|
|
}
|
|
|
|
FReply FIOSTargetSettingsCustomization::OnGenerateSSHKey()
|
|
{
|
|
// see if the key is already generated
|
|
const UIOSRuntimeSettings& Settings = *GetDefault<UIOSRuntimeSettings>();
|
|
|
|
FString RemoteServerAddress;
|
|
FString RemoteServerPort;
|
|
int32 colonIndex;
|
|
|
|
if(Settings.RemoteServerName.FindChar(':', colonIndex))
|
|
{
|
|
RemoteServerAddress = Settings.RemoteServerName.Left(colonIndex);
|
|
RemoteServerPort = Settings.RemoteServerName.RightChop(colonIndex + 1);
|
|
}
|
|
else
|
|
{
|
|
RemoteServerAddress = Settings.RemoteServerName;
|
|
RemoteServerPort = "22";
|
|
}
|
|
|
|
TCHAR Path[4096];
|
|
FPlatformMisc::GetEnvironmentVariable(TEXT("APPDATA"), Path, ARRAY_COUNT(Path));
|
|
FString Destination = FString::Printf(TEXT("%s\\Unreal Engine\\UnrealBuildTool\\SSHKeys\\%s\\%s\\RemoteToolChainPrivate.key"), Path, *RemoteServerAddress, *(Settings.RSyncUsername));
|
|
if (FPaths::FileExists(Destination))
|
|
{
|
|
FString MessagePrompt = FString::Printf(TEXT("An SSH Key already exists. Do you want to replace this key?"));
|
|
if (FPlatformMisc::MessageBoxExt(EAppMsgType::OkCancel, *MessagePrompt, TEXT("Key Exists")) == EAppReturnType::Cancel)
|
|
{
|
|
return FReply::Handled();
|
|
}
|
|
}
|
|
|
|
FString CmdExe = FPaths::ConvertRelativePathToFull(FPaths::EngineDir() / TEXT("Build/BatchFiles/MakeAndInstallSSHKey.bat"));
|
|
FString DeltaCopyPath = Settings.DeltaCopyInstallPath.Path;
|
|
if (DeltaCopyPath.IsEmpty() || !FPaths::DirectoryExists(DeltaCopyPath))
|
|
{
|
|
// If no user specified directory try the UE4 bundled directory
|
|
DeltaCopyPath = FPaths::ConvertRelativePathToFull(FPaths::EngineDir() / TEXT("Extras\\ThirdPartyNotUE\\DeltaCopy\\Binaries"));
|
|
}
|
|
|
|
if (!FPaths::DirectoryExists(DeltaCopyPath))
|
|
{
|
|
// if no UE4 bundled version of DeltaCopy, try and use the default install location
|
|
TCHAR ProgramPath[4096];
|
|
FPlatformMisc::GetEnvironmentVariable(TEXT("PROGRAMFILES(X86)"), ProgramPath, ARRAY_COUNT(ProgramPath));
|
|
DeltaCopyPath = FPaths::Combine(ProgramPath, TEXT("DeltaCopy"));
|
|
}
|
|
|
|
if (!FPaths::DirectoryExists(DeltaCopyPath))
|
|
{
|
|
UE_LOG(LogIOSTargetSettings, Error, TEXT("DeltaCopy is not installed correctly"));
|
|
}
|
|
|
|
FString CygwinPath = TEXT("/cygdrive/") + FString(Path).Replace(TEXT(":"), TEXT("")).Replace(TEXT("\\"), TEXT("/"));
|
|
FString EnginePath = FPaths::EngineDir();
|
|
FString CommandLine = FString::Printf(TEXT("\"%s/ssh.exe\" %s \"%s\\rsync.exe\" %s %s \"%s\" \"%s\" \"%s\""),
|
|
*DeltaCopyPath,
|
|
*RemoteServerPort,
|
|
*DeltaCopyPath,
|
|
*(Settings.RSyncUsername),
|
|
*RemoteServerAddress,
|
|
Path,
|
|
*CygwinPath,
|
|
*EnginePath);
|
|
|
|
OutputMessage = TEXT("");
|
|
IPPProcess = MakeShareable(new FMonitoredProcess(CmdExe, CommandLine, false, false));
|
|
IPPProcess->Launch();
|
|
TickerHandle = FTicker::GetCoreTicker().AddTicker(FTickerDelegate::CreateRaw(this, &FIOSTargetSettingsCustomization::UpdateStatusDelegate), 10.0f);
|
|
RunningIPPProcess = true;
|
|
|
|
return FReply::Handled();
|
|
}
|
|
|
|
const FSlateBrush* FIOSTargetSettingsCustomization::GetProvisionStatus() const
|
|
{
|
|
if( bProvisionInstalled )
|
|
{
|
|
return FEditorStyle::GetBrush("Automation.Success");
|
|
}
|
|
else
|
|
{
|
|
return FEditorStyle::GetBrush("Automation.Fail");
|
|
}
|
|
}
|
|
|
|
const FSlateBrush* FIOSTargetSettingsCustomization::GetCertificateStatus() const
|
|
{
|
|
if( bCertificateInstalled )
|
|
{
|
|
return FEditorStyle::GetBrush("Automation.Success");
|
|
}
|
|
else
|
|
{
|
|
return FEditorStyle::GetBrush("Automation.Fail");
|
|
}
|
|
}
|
|
|
|
bool FIOSTargetSettingsCustomization::UpdateStatusDelegate(float DeltaTime)
|
|
{
|
|
if (IPPProcess.IsValid())
|
|
{
|
|
if (IPPProcess->IsRunning())
|
|
{
|
|
return true;
|
|
}
|
|
int RetCode = IPPProcess->GetReturnCode();
|
|
IPPProcess = NULL;
|
|
UpdateStatus();
|
|
UpdateSSHStatus();
|
|
}
|
|
RunningIPPProcess = false;
|
|
|
|
return false;
|
|
}
|
|
|
|
TSharedRef<ITableRow> FIOSTargetSettingsCustomization::HandleProvisionListGenerateRow( ProvisionPtr InProvision, const TSharedRef<STableViewBase>& OwnerTable )
|
|
{
|
|
return SNew(SProvisionListRow, OwnerTable)
|
|
.Provision(InProvision)
|
|
.ProvisionList(ProvisionList)
|
|
.OnProvisionChanged(this, &FIOSTargetSettingsCustomization::HandleProvisionChanged);
|
|
}
|
|
|
|
void FIOSTargetSettingsCustomization::HandleProvisionChanged(FString Provision)
|
|
{
|
|
FText OutText;
|
|
MobileProvisionProperty->GetValueAsFormattedText(OutText);
|
|
if (OutText.ToString() != Provision)
|
|
{
|
|
MobileProvisionProperty->SetValueFromFormattedString(Provision);
|
|
}
|
|
SignCertificateProperty->GetValueAsFormattedText(OutText);
|
|
if (Provision == TEXT("") && OutText.ToString() == TEXT(""))
|
|
{
|
|
bManuallySelected = false;
|
|
FilterLists();
|
|
}
|
|
else if (!bManuallySelected)
|
|
{
|
|
bManuallySelected = true;
|
|
FilterLists();
|
|
}
|
|
}
|
|
|
|
void FIOSTargetSettingsCustomization::HandleCertificateChanged(FString Certificate)
|
|
{
|
|
FText OutText;
|
|
SignCertificateProperty->GetValueAsFormattedText(OutText);
|
|
if (OutText.ToString() != Certificate)
|
|
{
|
|
SignCertificateProperty->SetValueFromFormattedString(Certificate);
|
|
}
|
|
MobileProvisionProperty->GetValueAsFormattedText(OutText);
|
|
if (Certificate == TEXT("") && OutText.ToString() == TEXT(""))
|
|
{
|
|
bManuallySelected = false;
|
|
FilterLists();
|
|
}
|
|
else if (!bManuallySelected)
|
|
{
|
|
bManuallySelected = true;
|
|
FilterLists();
|
|
}
|
|
}
|
|
|
|
TSharedRef<ITableRow> FIOSTargetSettingsCustomization::HandleCertificateListGenerateRow( CertificatePtr InCertificate, const TSharedRef<STableViewBase>& OwnerTable )
|
|
{
|
|
return SNew(SCertificateListRow, OwnerTable)
|
|
.Certificate(InCertificate)
|
|
.CertificateList(CertificateList)
|
|
.OnCertificateChanged(this, &FIOSTargetSettingsCustomization::HandleCertificateChanged);
|
|
}
|
|
|
|
void FIOSTargetSettingsCustomization::HandleAllProvisionsHyperlinkNavigate( bool AllProvisions )
|
|
{
|
|
bShowAllProvisions = AllProvisions;
|
|
FilterLists();
|
|
}
|
|
|
|
void FIOSTargetSettingsCustomization::HandleAllCertificatesHyperlinkNavigate( bool AllCertificates )
|
|
{
|
|
bShowAllCertificates = AllCertificates;
|
|
FilterLists();
|
|
}
|
|
|
|
void FIOSTargetSettingsCustomization::FilterLists()
|
|
{
|
|
FilteredProvisionList.Reset();
|
|
FilteredCertificateList.Reset();
|
|
|
|
for (int Index = 0; Index < ProvisionList->Num(); ++Index)
|
|
{
|
|
if (SelectedProvision.Contains((*ProvisionList)[Index]->Name) && SelectedFile.Contains((*ProvisionList)[Index]->FileName) && !bManuallySelected)
|
|
{
|
|
(*ProvisionList)[Index]->bSelected = true;
|
|
}
|
|
else
|
|
{
|
|
(*ProvisionList)[Index]->bSelected = false;
|
|
}
|
|
if (bShowAllProvisions || (*ProvisionList)[Index]->Status.Contains("VALID"))
|
|
{
|
|
FilteredProvisionList.Add((*ProvisionList)[Index]);
|
|
}
|
|
}
|
|
|
|
if (ProvisionList->Num() > 0)
|
|
{
|
|
if (ProvisionInfoSwitcher.IsValid())
|
|
{
|
|
ProvisionInfoSwitcher->SetActiveWidgetIndex(3);
|
|
}
|
|
if (FilteredProvisionList.Num() == 0 && !bShowAllProvisions)
|
|
{
|
|
FilteredProvisionList.Append(*ProvisionList);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (ProvisionInfoSwitcher.IsValid())
|
|
{
|
|
ProvisionInfoSwitcher->SetActiveWidgetIndex(2);
|
|
}
|
|
}
|
|
|
|
for (int Index = 0; Index < CertificateList->Num(); ++Index)
|
|
{
|
|
if (SelectedCert.Contains((*CertificateList)[Index]->Name) && !bManuallySelected)
|
|
{
|
|
(*CertificateList)[Index]->bSelected = true;
|
|
}
|
|
else
|
|
{
|
|
(*CertificateList)[Index]->bSelected = false;
|
|
}
|
|
if (bShowAllCertificates || (*CertificateList)[Index]->Status.Contains("VALID"))
|
|
{
|
|
FilteredCertificateList.Add((*CertificateList)[Index]);
|
|
}
|
|
}
|
|
|
|
if (CertificateList->Num() > 0)
|
|
{
|
|
if (CertificateInfoSwitcher.IsValid())
|
|
{
|
|
CertificateInfoSwitcher->SetActiveWidgetIndex(3);
|
|
}
|
|
if (FilteredCertificateList.Num() == 0 && !bShowAllCertificates)
|
|
{
|
|
FilteredCertificateList.Append(*CertificateList);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (CertificateInfoSwitcher.IsValid())
|
|
{
|
|
CertificateInfoSwitcher->SetActiveWidgetIndex(2);
|
|
}
|
|
}
|
|
|
|
CertificateListView->RequestListRefresh();
|
|
ProvisionListView->RequestListRefresh();
|
|
}
|
|
|
|
bool FIOSTargetSettingsCustomization::IsImportEnabled() const
|
|
{
|
|
return !RunningIPPProcess.Get();
|
|
}
|
|
|
|
void FIOSTargetSettingsCustomization::OnBundleIdentifierChanged(const FText& NewText, ETextCommit::Type CommitType, TSharedRef<IPropertyHandle> InPropertyHandle)
|
|
{
|
|
if(!IsBundleIdentifierValid(NewText.ToString()))
|
|
{
|
|
BundleIdTextBox->SetError( LOCTEXT("NameContainsInvalidCharacters", "Identifier may only contain the characters 0-9, A-Z, a-z, period, hyphen, or [PROJECT_NAME]") );
|
|
}
|
|
else
|
|
{
|
|
BundleIdTextBox->SetError(FText::GetEmpty());
|
|
|
|
FText OutText;
|
|
InPropertyHandle->GetValueAsFormattedText(OutText);
|
|
if (OutText.ToString() != NewText.ToString())
|
|
{
|
|
InPropertyHandle->SetValueFromFormattedString( NewText.ToString() );
|
|
FindRequiredFiles();
|
|
}
|
|
}
|
|
}
|
|
|
|
void FIOSTargetSettingsCustomization::OnBundleIdentifierTextChanged(const FText& NewText, ETextCommit::Type CommitType, TSharedRef<IPropertyHandle> InPropertyHandle)
|
|
{
|
|
if(!IsBundleIdentifierValid(NewText.ToString()))
|
|
{
|
|
BundleIdTextBox->SetError( LOCTEXT("NameContainsInvalidCharacters", "Identifier may only contain the characters 0-9, A-Z, a-z, period, hyphen, or [PROJECT_NAME]") );
|
|
}
|
|
else
|
|
{
|
|
BundleIdTextBox->SetError(FText::GetEmpty());
|
|
}
|
|
}
|
|
|
|
bool FIOSTargetSettingsCustomization::IsBundleIdentifierValid(const FString& inIdentifier)
|
|
{
|
|
for(int32 i = 0; i < inIdentifier.Len(); ++i)
|
|
{
|
|
TCHAR c = inIdentifier[i];
|
|
|
|
if(c == '[')
|
|
{
|
|
if(inIdentifier.Find(gProjectNameText, ESearchCase::CaseSensitive, ESearchDir::FromStart, i) != i)
|
|
{
|
|
return false;
|
|
}
|
|
i += gProjectNameText.Len();
|
|
}
|
|
else if((c < '0' || c > '9') && (c < 'a' || c > 'z') && (c < 'A' || c > 'Z') && c != '.' && c != '-')
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void FIOSTargetSettingsCustomization::OnRemoteServerChanged(const FText& NewText, ETextCommit::Type CommitType, TSharedRef<IPropertyHandle> InPropertyHandle)
|
|
{
|
|
FText OutText;
|
|
InPropertyHandle->GetValueAsFormattedText(OutText);
|
|
if (OutText.ToString() != NewText.ToString())
|
|
{
|
|
InPropertyHandle->SetValueFromFormattedString(NewText.ToString());
|
|
OutputMessage = TEXT("");
|
|
UpdateSSHStatus();
|
|
}
|
|
}
|
|
|
|
FText FIOSTargetSettingsCustomization::GetBundleText(TSharedRef<IPropertyHandle> InPropertyHandle) const
|
|
{
|
|
FText OutText;
|
|
InPropertyHandle->GetValueAsFormattedText(OutText);
|
|
return OutText;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
#undef LOCTEXT_NAMESPACE
|