Files
UnrealEngineUWP/Engine/Source/Editor/BlueprintGraph/Private/EdGraphSchema_K2.cpp
Matt Kuhlenschmidt 67a0d73fa0 Copying //UE4/Dev-Editor to //UE4/Dev-Main (Source: //UE4/Dev-Editor @ 3082391)
#lockdown Nick.Penwarden
#rb none

==========================
MAJOR FEATURES + CHANGES
==========================

Change 3051464 on 2016/07/15 by Nick.Darnell

	Regression Testing - Several upgrades to the functional testing system, better tracking of failure cases, some source line failure detection, trying to make it easier to run a specific test on a map.  Some UI improvements, easier access to the automation system.  Lots more refactoring to come, lots of improvements are still needed in transmitting screenshots and just generally building a automation report we could dump from the build machines.

Change 3051465 on 2016/07/15 by Nick.Darnell

	Adding the "Engine Test" project our one stop shope for running automation tests in the engine to try and reduce regressions.

Change 3051847 on 2016/07/15 by Matt.Kuhlenschmidt

	Fixed material editor viewport messages being blocked by viewport toolbar

Change 3052025 on 2016/07/15 by Nick.Darnell

	Moving the placement mode hooks out of functional testing module, moving them into the editor automation module.

Change 3053508 on 2016/07/18 by Stephan.Jiang

	Copy,Cut,Paste tracks, not for mastertracks yet.

	#UE-31808

Change 3054723 on 2016/07/18 by Stephan.Jiang

	Small fixes for typo & comments

Change 3055996 on 2016/07/19 by Trung.Le

	PIE: No longer auto resume game in PIE on focus received

Change 3056106 on 2016/07/19 by Trung.Le

	Back out changelist 3055996. Build break.

Change 3056108 on 2016/07/19 by Stephan.Jiang

	Updating "SoundConcurrency" asseticon

Change 3056389 on 2016/07/19 by Trung.Le

	PIE: No longer auto resume game in PIE on focus received
	#jira UE-33339

Change 3056396 on 2016/07/19 by Matt.Kuhlenschmidt

	More perf selection improvements:
	- Static meshes now go through the static draw path when rendered for selection outline instead of just rendering using the dynamic path

Change 3056758 on 2016/07/19 by Stephan.Jiang

	Update SelectedWidgets in WidgetblueprintEditor to match the selected tracks in sequencer.

Change 3057519 on 2016/07/20 by Matt.Kuhlenschmidt

	Another fix for selecting lots of objects taking forever.  This one is due to repeated Modify calls if there are groups in the selection.  Each group actor selected iterates through each object selected during USelection::Modify!

Change 3057635 on 2016/07/20 by Stephan.Jiang

	Updating visual logger icon UI

Change 3057645 on 2016/07/20 by Richard.TalbotWatkin

	Fixed single player PIE so the window position is correctly fetched and saved, even when running a dedicated server. This does not interfere with stored positions for multiple PIE, which uses ULevelEditorPlaySettings::MultipleInstancePositions.
	#jira UE-33416 - New Editor PIE window does not center to screen when running with a dedicated server

Change 3057868 on 2016/07/20 by Richard.TalbotWatkin

	Spline component improvements, both tools and runtime:

	- SplineComponentVisualizer now works within the Blueprint editor. This works via a generic extension added to the base ComponentVisualizer class which correctly propagates modified properties from the preview actor to the archetype, and then on to any instances whose properties are at the default value.

	- The above feature required a breaking change to USplineComponent - namely, the three FInterpCurve properties have been collected together into a struct and added as a single property. This is so that changes to the length of one of the FInterpCurves marks all three as dirty and needing rebuilding.

	- Added a custom version for SplineComponent and provded serialization fixes.

	- Added a details customization to SplineComponent to hide the raw FInterpCurve properties.

	- Added a custom detail builder category which polls the SplineComponentVisualizer each tick and provides numerical editing for spline points which are selected in the visualizer.

	- Relaxed the limitation that SplineComponent keys need to have an increment of 1.0.  Now any SplineComponent key can be set.  The details customization enforces that the sequence remains strictly ascending.

	- Allowed an explicit loop point to be specified for closed splines.

	- Allowed discontinuous splines by no longer forcing the ArriveTangent and LeaveTangent to be equal.

	- Added some new Blueprintable methods for building splines with an FSplinePoint struct, which allows all of a spline point's properties to be specified, and added to the FInterpCurves sorted by the input key.

	- Fixed the logic which determines whether the UCS has modified the spline curves.

	- Added UActorComponent::RemoveUCSModifiedProperties, which allows a component to remove any properties from the cached list which it doesn't want to be considered as 'modified'. This is used to distinguish the case of properties preserved by the SplineInstanceDataCache from those genuinely modified by the UCS.

	- Fixed "Apply Instance Changes to Blueprint" so that edited spline data can be applied to the archetype.

	- Fixed some issues with the spline component visualizer to make it generate appropriate up vectors if scale and rotation are enabled.

	#jira UETOOL-766 - Spline tool improvements
	#jira UE-33049 - Transform widget visible in blueprint viewport when editing spline points in editor viewport
	#jira UE-9062 - Spline editing: It would be nice to be able to type in a specific value for a point
	#jira UE-7476 - Add ability to edit SplineComponent in BP editor (not just instance in level)
	#jira UE-13082 - Users would like a snapping feature for splines
	#jira UE-13568 - Additional Spline Component Functionality
	#jira UE-17822 - It would be useful to be able to update a bp spline layout from the editor viewport.

Change 3057895 on 2016/07/20 by Richard.TalbotWatkin

	Mesh paint bugfixes and improvements.

	Changes to RerunConstructionScript so that OnObjectsReplaced is called correctly on all components, whether they have been created by the SCS or the UCS. Previously, components created by the UCS were not being handled, and components created by the SCS were not always being matched.  Now a serialized index is maintained for UCS-created objects, which is matched after the construction scripts have been executed.

	This will fix issues with the mesh paint tool, and any other editor tool which hooks into the OnObjectsReplaced callback in order to update its internal cache of component pointers, for example, the component visualizer render list.

	#jira UE-33010 - Crash changing mesh paint material in blueprint, then changing to a different mode tab
	#jira UE-32279 - Editor crashes when reselecting a mesh in paint mode
	#jira UE-31763 - [CrashReport] UE4Editor_MeshPaint!FMulticastDelegateBase<FWeakObjectPtr>::RemoveAll() [multicastdelegatebase.h:75]
	#jira UE-30661 - Vertex Painting changes collision complexity if the asset is saved while vertex painting

Change 3057966 on 2016/07/20 by Richard.TalbotWatkin

	Renamed IsEditingArchetype to IsVisualizingArchetype in the ComponentVisualizer API.
	#jira UE-33049 - Transform widget visible in blueprint viewport when editing spline points in editor viewport

Change 3058009 on 2016/07/20 by Richard.TalbotWatkin

	Fixed build failure due to changes to FComponentVisualizer API, as of CL 3057868.

Change 3058047 on 2016/07/20 by Stephan.Jiang

	Fixing error on previous CL: 3056758
	(extra qualification)

Change 3058266 on 2016/07/20 by Nick.Darnell

	Automation - Work continues on automation integrating some ideas form a licensee.  Continuing to work on the usability aspects, I've made it possible for tests to provide custom open commands, as well as have complex subclasses that do different things.  The functional tests now have a custom open command they emit that makes it so clicking on a test opens not the C++ location where the functional test macro lives, but instead the map, AND focuses the functional test actor.

Change 3058282 on 2016/07/20 by Matt.Kuhlenschmidt

	PR #2611: Fix spurious component diff when properties are in subcategories (Contributed by CA-ADuran)

Change 3059214 on 2016/07/21 by Richard.TalbotWatkin

	Further fixes to visualizers following Component Visualizer API change.

Change 3059260 on 2016/07/21 by Richard.TalbotWatkin

	Template specialization not allowed in class scope, but Visual Studio allows it anyway.  Fixed for clang.

Change 3059543 on 2016/07/21 by Stephan.Jiang

	Changeing level details icon

Change 3059732 on 2016/07/21 by Stephan.Jiang

	Directional Light icon update

Change 3060095 on 2016/07/21 by Stephan.Jiang

	Directional Light editor icon asset changed

Change 3060129 on 2016/07/21 by Nick.Darnell

	Automation - The session browser now attempts to select the app instance if no other thing is selected when it refreshes.  This is to try and make it easier to use when you first bring it up and nothing is selected when most of the time you're going to use it on your own instance.

Change 3061735 on 2016/07/22 by Stephan.Jiang

	Improve UMG replace with in HierarchyView function

	#UE-33582

Change 3062059 on 2016/07/22 by Stephan.Jiang

	Strip off "b" in propertyname in replace with function for tracks.

Change 3062146 on 2016/07/22 by Stephan.Jiang

	checkin with CL: 3061735

Change 3062182 on 2016/07/22 by Stephan.Jiang

	Change both animation bindings' widget name when renameing the widget so the slot content is still valid

Change 3062257 on 2016/07/22 by Stephan.Jiang

	comments

Change 3062381 on 2016/07/22 by Nick.Darnell

	Build - Adding #undef LOCTEXT_NAMESPACE to try and fix the build.

Change 3062924 on 2016/07/25 by Chris.Wood

	Fix a crash in CrashReportClient that happens when the CrashReportReceiver is not responding to pings and there are no PendingReportDirectories.

	This is a change in the UE4 stream depot based on a fix in the Fortnite stream depot -> JIRA FORT-27570

Change 3063017 on 2016/07/25 by Matt.Kuhlenschmidt

	PR #2618: DebuggerCommand not recording PlayLocationString (Contributed by ungalyant)

Change 3063021 on 2016/07/25 by Matt.Kuhlenschmidt

	PR #2619: added a search box to ModuleUI (Contributed by straymist)

Change 3063084 on 2016/07/25 by Matt.Kuhlenschmidt

	Fix "YesToAll" when deleting referenced actors overriding the "YesToAll" state for other referenced messages.

	https://jira.ol.epicgames.net/browse/UE-33651
	#jira UE-33651

Change 3063091 on 2016/07/25 by Alex.Delesky

	#jira UE-32949 - Truncating the hue inside the theme color block tooltip to only display whole numbers, to match how the color picker displays the hue value inside the hue scrubber.

Change 3063388 on 2016/07/25 by Matt.Kuhlenschmidt

	Selection Perf:
	- Fix large FName creation time when selecting thousands of objects

Change 3063568 on 2016/07/25 by Matt.Kuhlenschmidt

	Selection Perf:
	- Modified how USelection stores classes.  Classes are now in a  TSet and can be accessed efficiently using IsClassSelected.  The old unused way of checking if a selection has a class by iterating through them is deprecated
	- USelection no longer directly checks if an item is already selected with a costly n^2 search.  The check is done by using the already existing UObject selected annotation
	- Object property nodes no longer perform an n^2 check for object uniqueness when objects are added to details panels.  This is now left up to the caller to avoid
	- Eliminated useless work on FObjectPropertyNode::GetReadAddressUncached.  If a read address list is not passed in we'll not attempt to the work to populate it
	- Removed expensive checking for brush actors when any actor is selected

Change 3063749 on 2016/07/25 by Stephan.Jiang

	Disallow naming the widgetanimation to the same name with a override function in uuserwidget, because it will trigger a breakpoint in Rename()

	#jira UE-33711

Change 3064585 on 2016/07/26 by Matt.Kuhlenschmidt

	Merging //UE4/Dev-Main to Dev-Editor (//UE4/Dev-Editor)

Change 3064612 on 2016/07/26 by Alex.Delesky

	#jira UE-33712 - Deleting many assets at once will now batch SourceControl commands rather than executing one for each asset.

Change 3064647 on 2016/07/26 by Alexis.Matte

	#jira UE-33274 dont hash the same file over and over when importing multiple asset from one fbx file.

Change 3064739 on 2016/07/26 by Matt.Kuhlenschmidt

	Fixed typo

Change 3064795 on 2016/07/26 by Jamie.Dale

	Fixed typo in FLocalizationModule::GetLocalizationTargetByName

	#jira UE-32961

Change 3066461 on 2016/07/27 by Jamie.Dale

	Enabled stable localization keys

Change 3066463 on 2016/07/27 by Jamie.Dale

	Set "Build Engine Localization" to upload all cultures to ensure we don't lose translation due to the archive keying changes

Change 3066467 on 2016/07/27 by Jamie.Dale

	Updated internationalization archives to store translations per-identity

	This allows translators to translate each instance of a piece of text based upon their context, rather than requiring a content producer to go back and give the entry a unique namespace. It also allows us to optionally compile out-of-date translations, as they are now mapped to their source identity (namespace + key) rather than their source text.

	Major changes:
	 - Added FLocTextHelper. This acts as a high-level API for uncompiled localized text, and replaces all the old ad-hoc loading/saving of manifests and archives, ensuring that everything is consistently using source control, and that older archives can be upgraded correctly to the new format. It also takes care of some of the quirks of our archives, such as native translations. All major localization commandlets have been updated to use FLocTextHelper.
	 - Moved FTextLocalizationResourceGenerator from Core to Internationalization. This also allows IJsonInternationalizationManifestSerializer and IJsonInternationalizationArchiveSerializer to be removed, and for FJsonInternationalizationManifestSerializer and FJsonInternationalizationArchiveSerializer to have all their functions become static.
	 - FTextLocalizationResourceGenerator being moved from Core meant that FTextLocalizationManager::LoadFromManifestAndArchives was also removed. This functionality is now handled by FTextLocalizationResourceGenerator::GenerateAndUpdateLiveEntriesFromConfig.
	 - The RepairLocalizationData commandlet has been removed. This existed to fix a change that pre-dated 4.0 so no such data should exist in the wild, and the commandlet couldn't be updated to work with the new API (we handle format upgrades in-place now).
	 - Removed FInternationalizationArchive::FindEntryBySource as it is no-longer safe to use. All existing code has been updated to use FInternationalizationArchive::FindEntryByKey instead.

	Workflow changes:
	 - Archive conditioning now only adds new entries if they don't exist in the archive. This allows us to persist any existing translations, even if they're for old source text (caveat: native archives still update existing entries if the source is changed).
	 - PO export now sets the msgctx for each entry to be "namespace,key", rather than only doing it when the entry had key meta-data.
	 - PO import will now update both the source and translation stored in the archive to match the current PO data. This is the primary method by which stale source->translation pairs are updated.
	 - LocRes compilation may now optionally compile stale translations. There's an option controlling this (defaulted to off) that can be changed via the Localization Dashboard (or added to an existing config file).

	Format changes:
	 - The archive version was bumped to 2.
	 - Archive entries now use the "Key" entry to store the key from the source text. Previously this "Key" entry was used to store the key meta-data, but that now exists within a "MetaData" sub-object. Loading handles this correctly based upon the archive version.

	#jira UETOOL-897
	#jira UETOOL-898
	#jira UE-29481

Change 3066487 on 2016/07/27 by Matt.Kuhlenschmidt

	Attempt to fix linux compilation

Change 3066504 on 2016/07/27 by Matt.Kuhlenschmidt

	Fixed data tables with structs crashing due to recent editor selection optimizations

Change 3066886 on 2016/07/27 by Jamie.Dale

	Added required data to accurately detect TZ (needed for DST)

	#jira UE-28511

Change 3067122 on 2016/07/27 by Jamie.Dale

	Added AsTime, AsDateTime, and AsDate overrides to BP to let you format a UTC time in a given timezone (default is the local timezone).

	Previously you could only format times using the "invariant" timezone, which assumed that the time was already specified in the correct timezone for display.

Change 3067227 on 2016/07/27 by Jamie.Dale

	Added a test to verify that the ICU timezone is set correctly to produce local time (including DST)

Change 3067313 on 2016/07/27 by Richard.TalbotWatkin

	Fixed SplineComponent constructor so that old assets (prior to the property changes) load correctly if they had properties at default values.
	#jira UE-33669 - Crash in Dev-Editor

Change 3067736 on 2016/07/27 by Stephan.Jiang

	Border changes for experimental classes warning

Change 3067769 on 2016/07/27 by Stephan.Jiang

	HERE BE DRAGONS

	for experimental class warning

	#UE-33780

Change 3068192 on 2016/07/28 by Alexis.Matte

	#jira UE-33586 make sure we remove any false warning when running fbx automation test.

Change 3068264 on 2016/07/28 by Jamie.Dale

	Removed some code that was no longer needed and could cause a crash

	#jira UE-33342

Change 3068293 on 2016/07/28 by Alex.Delesky

	#jira UE-33620 - Comments on constant and parameter nodes in the Material Editor will now persist when converting them.

Change 3068481 on 2016/07/28 by Stephan.Jiang

	Adding Options to show/hide soft & hard references & dependencies in References Viewer

	#jira UE-33746

Change 3068585 on 2016/07/28 by Richard.TalbotWatkin

	Fix to Spline Mesh collision building so that geometry does not default to being auto-inflated in PhysX.

Change 3068701 on 2016/07/28 by Matt.Kuhlenschmidt

	Fixed some issues with the selected classes not updating when objects are deselected

Change 3069335 on 2016/07/28 by Jamie.Dale

	Fixed unintended error when trying to load a manifest/archive that didn't exist
	Fixed a warning when trying to load a PO file that didn't exist

Change 3069408 on 2016/07/28 by Alex.Delesky

	#jira UE-33429 - The editor should no longer hit an ensure if the user attempts to drop a tab into a tab well before the tab well has a chance to acknowledge its been dragged into a tab well.

Change 3069878 on 2016/07/29 by Jamie.Dale

	Fixed include casing

	#jira UE-33910

Change 3071807 on 2016/08/01 by Matt.Kuhlenschmidt

	PR #2654: Fix the spell'ing of "diff'ing" and "diff'd". (Contributed by geary)

Change 3071813 on 2016/08/01 by Jamie.Dale

	Fixed include casing

	#jira UE-33936

Change 3072043 on 2016/08/01 by Jamie.Dale

	Fixed FText formatting of pre-Gregorian dates

	We now convert to an ICU UDate via an ICU GregorianCalendar, as UE4 and ICU have a different time scale for pre-Gregorian dates.

	#jira UE-14504

Change 3072066 on 2016/08/01 by Jamie.Dale

	PR #2590: FEATURE: Collapse/expand folders in the outliner (Contributed by projectgheist)

Change 3072149 on 2016/08/01 by Jamie.Dale

	We no longer use the editor culture when running with -game

Change 3072169 on 2016/08/01 by Richard.TalbotWatkin

	A couple of changes to the BSP code:
	* Fixed longstanding issue where sometimes BSP geometry is not rebuilt correctly after editing it.  This was due to poly normals not being recalculated after translating vertices in Geometry Mode.
	* Fixed corruption to FPoly::iLink as it is overloaded to have two meanings: when building BSP, it represents the surface index of the next coplanar surface (and adding a new BSP node uses this to determine whether a new surface needs to be added or not). In other operations it represents an FPoly index, in general this is used more in editor geometry operations.  This fixes various crashes which arose from rebuilding BSP resulting in invalid FPoly indices.

	#jira UE-12157 - BSP brushes break when non-standard subtractive bsp brushes are used
	#jira UE-32087 - Crash occurs when creating Static Mesh from Trigger Volume

Change 3072221 on 2016/08/01 by Jamie.Dale

	Fixed "Launch On" not providing the correct cultures to StartCookByTheBookInEditor

	#jira UE-33001

Change 3073389 on 2016/08/02 by Matt.Kuhlenschmidt

	Added ability to vsync the editor.  Disabled by default.  Set r.VSyncEditor to 1 to enable it.

	Reimplemented this change from the siggraph demo stream

Change 3073396 on 2016/08/02 by Matt.Kuhlenschmidt

	Removed unused code as suggested by a pull request

Change 3073750 on 2016/08/02 by Richard.TalbotWatkin

	Fixed formatting (broken in CL 3057895) in anticipation of merge from Main.

Change 3073789 on 2016/08/02 by Jamie.Dale

	Added a way to mark text in text properties as culture invariant

	This allows you to flag properties containing text that doesn't need to be gathered.

	#jira UE-33713

Change 3073825 on 2016/08/02 by Stephan.Jiang

	Material Editor: Highligh all Nodes connect to an input.

	#jira UE-32502

Change 3073947 on 2016/08/02 by Stephan.Jiang

	UMG Project settings to show/hide different classes and categories in Palette view.

	--under Project Settings ->Editor->UMG Editor

Change 3074012 on 2016/08/02 by Stephan.Jiang

	Minor changes and comments for CL: 3073947

Change 3074029 on 2016/08/02 by Jamie.Dale

	Deleting folders in the Content Browser now removes the folder from disk

	#jira UE-24303

Change 3074054 on 2016/08/02 by Matt.Kuhlenschmidt

	Added missing stats to track pooled vertex and index buffer cpu memory
	A new slate allocator was added to track memory usage for this case.

Change 3074056 on 2016/08/02 by Matt.Kuhlenschmidt

	Renamed a few slate stats for consistency

Change 3074810 on 2016/08/02 by Matt.Kuhlenschmidt

	Moved geometry cache asset type to the animation category.  It is not a basic asset type

Change 3074826 on 2016/08/02 by Matt.Kuhlenschmidt

	Fix a few padding and sizing issues

Change 3075322 on 2016/08/03 by Matt.Kuhlenschmidt

	Settings UI improvements
	* Added the ability to search through all settings at once
	* Settings files which are not checked out are no longer grayed out.  The editor now attempts to check out the file automatically if connected to source control and if that fails it marks the settings file writiable so it can save the setting properly

	-------
	* This change adds a refactor to the details panel to support multiple top level objects existing in the details panel at once instead of combining all passed in objects to a single common base class.  This is disabled by default but can be turned on setting bAllowMultipleTopLevelObjects to true in FDetailsViewArgs when creating a details panel.
	*  Each top level object in a details panel will get their own customization instance.  This made it necessary to deprecate a IDetailsView::GetBaseClass since there is no longer guaranteed to be one base class.
	*Details panels can have their own customization for each "root object header" in order to customize the look of having multiple top level objects in the details panel.

Change 3075369 on 2016/08/03 by Matt.Kuhlenschmidt

	Removed FBX scene as a top level option in asset filter menu in the content browser.

Change 3075556 on 2016/08/03 by Matt.Kuhlenschmidt

	Mac warning fix

Change 3075603 on 2016/08/03 by Nick.Darnell

	Adding two new plugins to engine, one for editor and one for runtime based testing.  Currently the only consumer of these plugins is going to be the EngineTest project.

Change 3075605 on 2016/08/03 by Nick.Darnell

	Functional Testing - Continued work on cleanup, reorganization, trying to improve the workflow for using the session browser.

Change 3076084 on 2016/08/03 by Jamie.Dale

	Added basic support for localizing plugins

	You can now localize plugins! There's no localization dashboard integration for this so it has to be done manually.

	You need to define the localization targets your plugin uses in its .uplugin file, eg)

		"LocalizationTargets": [
			{
				"Name": "Paper2D",
				"LoadingPolicy": "Always"
			}
		]

	"Name" should match a localization config under the Config/Localization folder for your plugin. These configs are set-up the same as any other localization config.

	"LoadingPolicy" may be one of Never, Always, Editor, Game, PropertyNames, or ToolTips. This allows you to control under what conditions your localizations should be loaded (eg, if your plugin has both game and editor data, you can separate the editor data off into its own localization target that's only loaded by the editor).

	UAT has been updated to support gathering from plugins. You can use the "IncludePlugins" flag to have it gather all plugins, or you can specify a whitelist of plugins to gather as an argument to "IncludePlugins", or alternatively, may blacklist certain plugins via "ExcludePlugins". It can now also support out-of-source gathering via the "UEProjectRoot" argument (previously it assumed that everything would be under the UE4 install/checkout directory).

	UAT has been updated to support staging plugin LocRes files. It will stage any plugin targets that are enabled for a game/client build, and are also from a plugin that's enabled for your project.

	#jira UE-4217

Change 3076123 on 2016/08/03 by Stephan.Jiang

	Extend "Select all input nodes" function to general blueprint editor

Change 3077103 on 2016/08/04 by Jamie.Dale

	Added support for underlined text rendering (including with drop-shadows)

	FTextBlockStyle can now specify a brush to use to draw an underline for text (a suitable default would be "DefaultTextUnderline" from FCoreStyle). When a brush is specified here, we inject FSlateTextUnderlineLineHighlighter highlights into the text layout to draw the underline under the relevant pieces of text, using the correct color, position, and thickness.

	FSlateFontCache::GetUnderlineMetrics and FSlateFontRenderer::GetUnderlineMetrics have been added to handle getting the underline metrics (which are slightly different to the baseline).

	This change also adds FTextLayout::RemoveRunRenderer and FTextLayout::RemoveLineHighlight to fix some bad assumptions that FSlateEditableTextLayout and FTextBlockLayout were making about ownership of run renderers and line highlighters that could cause them to remove instances they didn't own (such as the new underline highlighter) when updating things like the cursor position or highlight.

Change 3077842 on 2016/08/04 by Jamie.Dale

	Fixed fallout from API changes

Change 3077999 on 2016/08/04 by Jamie.Dale

	Ensured that BULKDATA_SingleUse is only set by UFontBulkData::Serialize when loading

	This prevents it being incorrectly set by other operations, such as counting memory used by font data.

	#jira UE-34252

Change 3078000 on 2016/08/04 by Trung.Le

	Categories VREditor-specific UMG widget assets as "VR Editor"
	#jira UE-34134

Change 3078056 on 2016/08/04 by Nick.Darnell

	Build - Fixing a mac compiler warning, reodering constructor initializers.

Change 3078813 on 2016/08/05 by Nick.Darnell

	Reorganizing editor tests, establishing plugins in the EditorTest project that will house the tests.

Change 3078818 on 2016/08/05 by Nick.Darnell

	Additional rename and cleanup associated with test moving.

Change 3078819 on 2016/08/05 by Nick.Darnell

	Removing the Oculus performance automation test, not running, and was unclaimed.

Change 3078842 on 2016/08/05 by Nick.Darnell

	Continued reorganizing tests.

Change 3078897 on 2016/08/05 by Nick.Darnell

	Additional changes to get some moved tests compiling

Change 3079157 on 2016/08/05 by Nick.Darnell

	Making it possible to browse provider names thorugh the source control module interface.

Change 3079176 on 2016/08/05 by Stephan.Jiang

	Add shortcut Ctrl+Shift+Space to rotate through different viewport options

	#jira UE-34140

Change 3079208 on 2016/08/05 by Stephan.Jiang

	Fix new animation name check in UMG

Change 3079278 on 2016/08/05 by Nick.Darnell

	Fixing the build

Change 3080555 on 2016/08/08 by Matt.Kuhlenschmidt

	Merging //UE4/Dev-Main to Dev-Editor (//UE4/Dev-Editor)

Change 3081155 on 2016/08/08 by Nick.Darnell

	Fixing some issues with the editor tests / runtime tests under certain build configs.

Change 3081243 on 2016/08/08 by Stephan.Jiang

	Add gesture in LevelViewport to switch between Top/Bottom...etc.

Change 3082226 on 2016/08/09 by Matt.Kuhlenschmidt

	Work around animations not playing in paragon due to bsp rebuilds (UE-34391)

Change 3082254 on 2016/08/09 by Stephan.Jiang

	DragTool_ViewportChange init changes

[CL 3082411 by Matt Kuhlenschmidt in Main branch]
2016-08-09 11:28:56 -04:00

6532 lines
233 KiB
C++

// Copyright 1998-2016 Epic Games, Inc. All Rights Reserved.
#include "BlueprintGraphPrivatePCH.h"
#include "UObject/DevObjectVersion.h"
#include "Engine/LevelScriptBlueprint.h"
#include "Kismet/KismetSystemLibrary.h"
#include "Kismet/KismetArrayLibrary.h"
#include "Kismet/KismetMathLibrary.h"
#include "GraphEditorActions.h"
#include "GraphEditorSettings.h"
#include "ScopedTransaction.h"
#include "ComponentAssetBroker.h"
#include "BlueprintEditorSettings.h"
#include "Kismet2/KismetEditorUtilities.h"
#include "Kismet2/KismetDebugUtilities.h"
#include "KismetCompiler.h"
#include "ComponentAssetBroker.h"
#include "AssetData.h"
#include "Editor/UnrealEd/Public/EdGraphUtilities.h"
#include "DefaultValueHelper.h"
#include "ObjectEditorUtils.h"
#include "ActorEditorUtils.h"
#include "TextPackageNamespaceUtil.h"
#include "ComponentTypeRegistry.h"
#include "BlueprintComponentNodeSpawner.h"
#include "AssetRegistryModule.h"
#include "Blueprint/AIBlueprintHelperLibrary.h"
#include "HotReloadInterface.h"
#include "K2Node_CastByteToEnum.h"
#include "K2Node_ClassDynamicCast.h"
#include "K2Node_GetEnumeratorName.h"
#include "K2Node_GetEnumeratorNameAsString.h"
#include "K2Node_Tunnel.h"
#include "K2Node_SetFieldsInStruct.h"
#include "K2Node_ConvertAsset.h"
#include "GenericCommands.h"
//////////////////////////////////////////////////////////////////////////
// FBlueprintMetadata
const FName FBlueprintMetadata::MD_AllowableBlueprintVariableType(TEXT("BlueprintType"));
const FName FBlueprintMetadata::MD_NotAllowableBlueprintVariableType(TEXT("NotBlueprintType"));
const FName FBlueprintMetadata::MD_BlueprintSpawnableComponent(TEXT("BlueprintSpawnableComponent"));
const FName FBlueprintMetadata::MD_IsBlueprintBase(TEXT("IsBlueprintBase"));
const FName FBlueprintMetadata::MD_RestrictedToClasses(TEXT("RestrictedToClasses"));
const FName FBlueprintMetadata::MD_ChildCanTick(TEXT("ChildCanTick"));
const FName FBlueprintMetadata::MD_ChildCannotTick(TEXT("ChildCannotTick"));
const FName FBlueprintMetadata::MD_IgnoreCategoryKeywordsInSubclasses(TEXT("IgnoreCategoryKeywordsInSubclasses"));
const FName FBlueprintMetadata::MD_Protected(TEXT("BlueprintProtected"));
const FName FBlueprintMetadata::MD_Latent(TEXT("Latent"));
const FName FBlueprintMetadata::MD_UnsafeForConstructionScripts(TEXT("UnsafeDuringActorConstruction"));
const FName FBlueprintMetadata::MD_FunctionCategory(TEXT("Category"));
const FName FBlueprintMetadata::MD_DeprecatedFunction(TEXT("DeprecatedFunction"));
const FName FBlueprintMetadata::MD_DeprecationMessage(TEXT("DeprecationMessage"));
const FName FBlueprintMetadata::MD_CompactNodeTitle(TEXT("CompactNodeTitle"));
const FName FBlueprintMetadata::MD_DisplayName(TEXT("DisplayName"));
const FName FBlueprintMetadata::MD_InternalUseParam(TEXT("InternalUseParam"));
const FName FBlueprintMetadata::MD_ExposeOnSpawn(TEXT("ExposeOnSpawn"));
const FName FBlueprintMetadata::MD_HideSelfPin(TEXT("HideSelfPin"));
const FName FBlueprintMetadata::MD_DefaultToSelf(TEXT("DefaultToSelf"));
const FName FBlueprintMetadata::MD_WorldContext(TEXT("WorldContext"));
const FName FBlueprintMetadata::MD_CallableWithoutWorldContext(TEXT("CallableWithoutWorldContext"));
const FName FBlueprintMetadata::MD_DevelopmentOnly(TEXT("DevelopmentOnly"));
const FName FBlueprintMetadata::MD_AutoCreateRefTerm(TEXT("AutoCreateRefTerm"));
const FName FBlueprintMetadata::MD_ShowWorldContextPin(TEXT("ShowWorldContextPin"));
const FName FBlueprintMetadata::MD_Private(TEXT("BlueprintPrivate"));
const FName FBlueprintMetadata::MD_BlueprintInternalUseOnly(TEXT("BlueprintInternalUseOnly"));
const FName FBlueprintMetadata::MD_NeedsLatentFixup(TEXT("NeedsLatentFixup"));
const FName FBlueprintMetadata::MD_LatentCallbackTarget(TEXT("LatentCallbackTarget"));
const FName FBlueprintMetadata::MD_AllowPrivateAccess(TEXT("AllowPrivateAccess"));
const FName FBlueprintMetadata::MD_ExposeFunctionCategories(TEXT("ExposeFunctionCategories"));
const FName FBlueprintMetadata::MD_CannotImplementInterfaceInBlueprint(TEXT("CannotImplementInterfaceInBlueprint"));
const FName FBlueprintMetadata::MD_ProhibitedInterfaces(TEXT("ProhibitedInterfaces"));
const FName FBlueprintMetadata::MD_FunctionKeywords(TEXT("Keywords"));
const FName FBlueprintMetadata::MD_ExpandEnumAsExecs(TEXT("ExpandEnumAsExecs"));
const FName FBlueprintMetadata::MD_CommutativeAssociativeBinaryOperator(TEXT("CommutativeAssociativeBinaryOperator"));
const FName FBlueprintMetadata::MD_MaterialParameterCollectionFunction(TEXT("MaterialParameterCollectionFunction"));
const FName FBlueprintMetadata::MD_Tooltip(TEXT("Tooltip"));
const FName FBlueprintMetadata::MD_CallInEditor(TEXT("CallInEditor"));
const FName FBlueprintMetadata::MD_DataTablePin(TEXT("DataTablePin"));
const FName FBlueprintMetadata::MD_NativeMakeFunction(TEXT("HasNativeMake"));
const FName FBlueprintMetadata::MD_NativeBreakFunction(TEXT("HasNativeBreak"));
const FName FBlueprintMetadata::MD_DynamicOutputType(TEXT("DeterminesOutputType"));
const FName FBlueprintMetadata::MD_DynamicOutputParam(TEXT("DynamicOutputParam"));
const FName FBlueprintMetadata::MD_ArrayParam(TEXT("ArrayParm"));
const FName FBlueprintMetadata::MD_ArrayDependentParam(TEXT("ArrayTypeDependentParams"));
const FName FBlueprintMetadata::MD_Bitmask(TEXT("Bitmask"));
const FName FBlueprintMetadata::MD_BitmaskEnum(TEXT("BitmaskEnum"));
const FName FBlueprintMetadata::MD_Bitflags(TEXT("Bitflags"));
//////////////////////////////////////////////////////////////////////////
#define LOCTEXT_NAMESPACE "KismetSchema"
UEdGraphSchema_K2::FPinTypeTreeInfo::FPinTypeTreeInfo(const FText& InFriendlyName, const FString& CategoryName, const UEdGraphSchema_K2* Schema, const FText& InTooltip, bool bInReadOnly/*=false*/, FTypesDatabase* TypesDatabase /*=nullptr*/)
: PossibleObjectReferenceTypes(0)
{
Init(InFriendlyName, CategoryName, Schema, InTooltip, bInReadOnly, TypesDatabase);
}
struct FUnloadedAssetData
{
FStringAssetReference StringAssetReference;
FText AssetFriendlyName;
FText Tooltip;
uint8 PossibleObjectReferenceTypes;
FUnloadedAssetData()
: PossibleObjectReferenceTypes(0)
{}
FUnloadedAssetData(const FAssetData& InAsset, uint8 InPossibleObjectReferenceTypes = 0)
: StringAssetReference(InAsset.ToStringReference())
, AssetFriendlyName(FText::FromString(FName::NameToDisplayString(InAsset.AssetName.ToString(), false)))
, PossibleObjectReferenceTypes(InPossibleObjectReferenceTypes)
{
InAsset.GetTagValue("Tooltip", Tooltip);
if (Tooltip.IsEmpty())
{
Tooltip = FText::FromString(InAsset.ObjectPath.ToString());
}
}
};
struct FLoadedAssetData
{
FText Tooltip;
UObject* Object;
uint8 PossibleObjectReferenceTypes;
FLoadedAssetData()
: Object(nullptr)
, PossibleObjectReferenceTypes(0) {}
FLoadedAssetData(UObject* InObject, uint8 InPossibleObjectReferenceTypes = 0)
: Object(InObject)
, PossibleObjectReferenceTypes(InPossibleObjectReferenceTypes)
{
UStruct* Struct = Cast<UStruct>(Object);
Tooltip = Struct ? Struct->GetToolTipText() : FText::GetEmpty();
}
};
struct FTypesDatabase
{
typedef TSharedPtr<TArray<FLoadedAssetData>> FLoadedTypesList;
TMap<FString, FLoadedTypesList> LoadedTypesMap;
typedef TSharedPtr<TArray<FUnloadedAssetData>> FUnLoadedTypesList;
TMap<FString, FUnLoadedTypesList> UnLoadedTypesMap;
};
/** Helper class to gather variable types */
class FGatherTypesHelper
{
private:
typedef TSharedPtr<UEdGraphSchema_K2::FPinTypeTreeInfo> FPinTypeTreeInfoPtr;
struct FCompareChildren
{
FORCEINLINE bool operator()(const FPinTypeTreeInfoPtr A, const FPinTypeTreeInfoPtr B) const
{
return (A->GetDescription().ToString() < B->GetDescription().ToString());
}
};
public:
static void FillLoadedTypesDatabase(FTypesDatabase& TypesDatabase, bool bIndexTypesOnly)
{
// Loaded types
TypesDatabase.LoadedTypesMap.Reset();
//(Type == UEdGraphSchema_K2::PC_Enum)
{
FTypesDatabase::FLoadedTypesList LoadedTypesList = MakeShareable(new TArray<FLoadedAssetData>());
// Generate a list of all potential enums which have "BlueprintType=true" in their metadata
for (TObjectIterator<UEnum> EnumIt; EnumIt; ++EnumIt)
{
UEnum* CurrentEnum = *EnumIt;
if (UEdGraphSchema_K2::IsAllowableBlueprintVariableType(CurrentEnum))
{
LoadedTypesList->Add(FLoadedAssetData(CurrentEnum));
}
}
TypesDatabase.LoadedTypesMap.Add(UEdGraphSchema_K2::PC_Enum, LoadedTypesList);
}
if (!bIndexTypesOnly)
{
//(Type == UEdGraphSchema_K2::PC_Struct)
{
FTypesDatabase::FLoadedTypesList LoadedTypesList = MakeShareable(new TArray<FLoadedAssetData>());
// Find script structs marked with "BlueprintType=true" in their metadata, and add to the list
for (TObjectIterator<UScriptStruct> StructIt; StructIt; ++StructIt)
{
UScriptStruct* ScriptStruct = *StructIt;
if (UEdGraphSchema_K2::IsAllowableBlueprintVariableType(ScriptStruct))
{
LoadedTypesList->Add(FLoadedAssetData(ScriptStruct));
}
}
TypesDatabase.LoadedTypesMap.Add(UEdGraphSchema_K2::PC_Struct, LoadedTypesList);
}
//(Type == UEdGraphSchema_K2::PC_Class || Type == UEdGraphSchema_K2::PC_AssetClass) UEdGraphSchema_K2::PC_Interface)
//(Type == UEdGraphSchema_K2::PC_Object || Type == UEdGraphSchema_K2::PC_Asset)
{
FTypesDatabase::FLoadedTypesList InterfaceLoadedTypesList = MakeShareable(new TArray<FLoadedAssetData>());
FTypesDatabase::FLoadedTypesList AllObjectLoadedTypesList = MakeShareable(new TArray<FLoadedAssetData>());
// Generate a list of all potential objects which have "BlueprintType=true" in their metadata
for (TObjectIterator<UClass> ClassIt; ClassIt; ++ClassIt)
{
UClass* CurrentClass = *ClassIt;
const bool bIsInterface = CurrentClass->IsChildOf(UInterface::StaticClass());
const bool bIsBlueprintType = UEdGraphSchema_K2::IsAllowableBlueprintVariableType(CurrentClass);
const bool bIsDeprecated = CurrentClass->HasAnyClassFlags(CLASS_Deprecated);
if (bIsBlueprintType && !bIsDeprecated)
{
if (bIsInterface)
{
InterfaceLoadedTypesList->Add(FLoadedAssetData(CurrentClass));
}
else
{
AllObjectLoadedTypesList->Add(FLoadedAssetData(CurrentClass, static_cast<uint8>(EObjectReferenceType::AllTypes)));
}
}
}
TypesDatabase.LoadedTypesMap.Add(UEdGraphSchema_K2::AllObjectTypes, AllObjectLoadedTypesList);
TypesDatabase.LoadedTypesMap.Add(UEdGraphSchema_K2::PC_Interface, InterfaceLoadedTypesList);
}
}
}
static void FillUnLoadedTypesDatabase(FTypesDatabase& TypesDatabase, bool bIndexTypesOnly)
{
// Loaded types
TypesDatabase.UnLoadedTypesMap.Reset();
const FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked<FAssetRegistryModule>(TEXT("AssetRegistry"));
{
TArray<FAssetData> AssetData;
AssetRegistryModule.Get().GetAssetsByClass(UUserDefinedEnum::StaticClass()->GetFName(), AssetData);
FTypesDatabase::FUnLoadedTypesList UnLoadedTypesList = MakeShareable(new TArray<FUnloadedAssetData>());
for (int32 AssetIndex = 0; AssetIndex < AssetData.Num(); ++AssetIndex)
{
const FAssetData& Asset = AssetData[AssetIndex];
if (Asset.IsValid() && !Asset.IsAssetLoaded())
{
UnLoadedTypesList->Add(FUnloadedAssetData(Asset));
}
}
TypesDatabase.UnLoadedTypesMap.Add(UEdGraphSchema_K2::PC_Enum, UnLoadedTypesList);
}
if (!bIndexTypesOnly)
{
{
TArray<FAssetData> AssetData;
AssetRegistryModule.Get().GetAssetsByClass(UUserDefinedStruct::StaticClass()->GetFName(), AssetData);
FTypesDatabase::FUnLoadedTypesList UnLoadedTypesList = MakeShareable(new TArray<FUnloadedAssetData>());
for (int32 AssetIndex = 0; AssetIndex < AssetData.Num(); ++AssetIndex)
{
const FAssetData& Asset = AssetData[AssetIndex];
if (Asset.IsValid() && !Asset.IsAssetLoaded())
{
UnLoadedTypesList->Add(FUnloadedAssetData(Asset));
}
}
TypesDatabase.UnLoadedTypesMap.Add(UEdGraphSchema_K2::PC_Struct, UnLoadedTypesList);
}
//else if (Schema->PC_Object == CategoryName || Schema->PC_Class == CategoryName || Schema->PC_Interface == CategoryName || Schema->PC_Asset == CategoryName || Schema->PC_AssetClass == CategoryName)
{
TArray<FAssetData> AssetData;
AssetRegistryModule.Get().GetAssetsByClass(UBlueprint::StaticClass()->GetFName(), AssetData);
const FString BPInterfaceTypeAllowed(TEXT("BPTYPE_Interface"));
const FString BPNormalTypeAllowed(TEXT("BPTYPE_Normal"));
FTypesDatabase::FUnLoadedTypesList UnLoadedInterfacesList = MakeShareable(new TArray<FUnloadedAssetData>());
FTypesDatabase::FUnLoadedTypesList UnLoadedClassesList = MakeShareable(new TArray<FUnloadedAssetData>());
for (int32 AssetIndex = 0; AssetIndex < AssetData.Num(); ++AssetIndex)
{
const FAssetData& Asset = AssetData[AssetIndex];
if (Asset.IsValid() && !Asset.IsAssetLoaded())
{
const FString BlueprintTypeStr = Asset.GetTagValueRef<FString>("BlueprintType");
const bool bNormalBP = BlueprintTypeStr == BPNormalTypeAllowed;
const bool bInterfaceBP = BlueprintTypeStr == BPInterfaceTypeAllowed;
if (bNormalBP || bInterfaceBP)
{
const uint32 ClassFlags = Asset.GetTagValueRef<uint32>("ClassFlags");
if (!(ClassFlags & CLASS_Deprecated))
{
if (bNormalBP)
{
UnLoadedClassesList->Add(FUnloadedAssetData(Asset, static_cast<uint8>(EObjectReferenceType::AllTypes)));
}
else if (bInterfaceBP)
{
UnLoadedInterfacesList->Add(FUnloadedAssetData(Asset));
}
}
}
}
}
TypesDatabase.UnLoadedTypesMap.Add(UEdGraphSchema_K2::PC_Interface, UnLoadedInterfacesList);
TypesDatabase.UnLoadedTypesMap.Add(UEdGraphSchema_K2::AllObjectTypes, UnLoadedClassesList);
}
}
}
/**
* Gathers all valid sub-types (loaded and unloaded) of a passed category and sorts them alphabetically
* @param FriendlyName Friendly name to be used for the tooltip if there is no available data
* @param CategoryName Category (type) to find sub-types of
* @param TypesDatabase Types database
* @param OutChildren All the gathered children
*/
static void Gather(const FText& FriendlyName, const FString& CategoryName, FTypesDatabase& TypesDatabase, TArray<FPinTypeTreeInfoPtr>& OutChildren)
{
FEdGraphPinType LoadedPinSubtype;
LoadedPinSubtype.PinCategory = (CategoryName == UEdGraphSchema_K2::PC_Enum ? UEdGraphSchema_K2::PC_Byte : CategoryName);
LoadedPinSubtype.PinSubCategory = TEXT("");
LoadedPinSubtype.PinSubCategoryObject = NULL;
auto LoadedSubTypesPtr = TypesDatabase.LoadedTypesMap.Find(CategoryName);
if (LoadedSubTypesPtr && LoadedSubTypesPtr->IsValid())
{
for (auto LoadedAssetData : *LoadedSubTypesPtr->Get())
{
OutChildren.Add(MakeShareable(new UEdGraphSchema_K2::FPinTypeTreeInfo(LoadedPinSubtype.PinCategory
, LoadedAssetData.Object
, LoadedAssetData.Tooltip.IsEmpty() ? FriendlyName : LoadedAssetData.Tooltip
, false
, LoadedAssetData.PossibleObjectReferenceTypes)));
}
}
auto UnLoadedSubTypesPtr = TypesDatabase.UnLoadedTypesMap.Find(CategoryName);
if (UnLoadedSubTypesPtr && UnLoadedSubTypesPtr->IsValid())
{
for (FUnloadedAssetData& It : *UnLoadedSubTypesPtr->Get())
{
FPinTypeTreeInfoPtr TypeTreeInfo = MakeShareable(new UEdGraphSchema_K2::FPinTypeTreeInfo(It.AssetFriendlyName
, CategoryName
, It.StringAssetReference
, It.Tooltip
, false
, It.PossibleObjectReferenceTypes));
OutChildren.Add(TypeTreeInfo);
}
}
OutChildren.Sort(FCompareChildren());
}
/** Loads an asset based on the AssetReference through the asset registry */
static UObject* LoadAsset(const FStringAssetReference& AssetReference)
{
if (AssetReference.IsValid())
{
const FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked<FAssetRegistryModule>(TEXT("AssetRegistry"));
const FAssetData AssetData = AssetRegistryModule.Get().GetAssetByObjectPath(*AssetReference.ToString());
return AssetData.GetAsset();
}
return nullptr;
}
};
const FEdGraphPinType& UEdGraphSchema_K2::FPinTypeTreeInfo::GetPinType(bool bForceLoadedSubCategoryObject)
{
if (bForceLoadedSubCategoryObject)
{
// Only attempt to load the sub category object if we need to
if ( SubCategoryObjectAssetReference.IsValid() && (!PinType.PinSubCategoryObject.IsValid() || FStringAssetReference(PinType.PinSubCategoryObject.Get()) != SubCategoryObjectAssetReference) )
{
UObject* LoadedObject = FGatherTypesHelper::LoadAsset(SubCategoryObjectAssetReference);
if(UBlueprint* BlueprintObject = Cast<UBlueprint>(LoadedObject))
{
PinType.PinSubCategoryObject = *BlueprintObject->GeneratedClass;
}
else
{
PinType.PinSubCategoryObject = LoadedObject;
}
}
}
else
{
if (SubCategoryObjectAssetReference.IsValid())
{
const FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked<FAssetRegistryModule>(TEXT("AssetRegistry"));
const FAssetData AssetData = AssetRegistryModule.Get().GetAssetByObjectPath(*SubCategoryObjectAssetReference.ToString());
if(!AssetData.IsAssetLoaded())
{
UObject* LoadedObject = FindObject<UClass>(ANY_PACKAGE, *AssetData.AssetClass.ToString());
// If the unloaded asset is a Blueprint, we need to pull the generated class and assign that
if(UBlueprint* BlueprintObject = Cast<UBlueprint>(LoadedObject))
{
PinType.PinSubCategoryObject = *BlueprintObject->GeneratedClass;
}
else
{
PinType.PinSubCategoryObject = LoadedObject;
}
}
else
{
PinType.PinSubCategoryObject = AssetData.GetAsset();
}
}
}
return PinType;
}
void UEdGraphSchema_K2::FPinTypeTreeInfo::Init(const FText& InFriendlyName, const FString& CategoryName, const UEdGraphSchema_K2* Schema, const FText& InTooltip, bool bInReadOnly, FTypesDatabase* TypesDatabase)
{
check( !CategoryName.IsEmpty() );
check( Schema );
FriendlyName = InFriendlyName;
Tooltip = InTooltip;
PinType.PinCategory = (CategoryName == PC_Enum? PC_Byte : CategoryName);
PinType.PinSubCategory = TEXT("");
PinType.PinSubCategoryObject = NULL;
bReadOnly = bInReadOnly;
CachedDescription = GenerateDescription();
if (Schema->DoesTypeHaveSubtypes(CategoryName))
{
if (TypesDatabase)
{
FGatherTypesHelper::Gather(InFriendlyName, CategoryName, *TypesDatabase, Children);
}
}
}
UEdGraphSchema_K2::FPinTypeTreeInfo::FPinTypeTreeInfo(const FString& CategoryName, UObject* SubCategoryObject, const FText& InTooltip, bool bInReadOnly/*=false*/, uint8 InPossibleObjectReferenceTypes)
: PossibleObjectReferenceTypes(InPossibleObjectReferenceTypes)
{
check( !CategoryName.IsEmpty() );
check( SubCategoryObject );
Tooltip = InTooltip;
PinType.PinCategory = CategoryName;
PinType.PinSubCategoryObject = SubCategoryObject;
bReadOnly = bInReadOnly;
CachedDescription = GenerateDescription();
}
UEdGraphSchema_K2::FPinTypeTreeInfo::FPinTypeTreeInfo(const FText& InFriendlyName, const FString& CategoryName, const FStringAssetReference& SubCategoryObject, const FText& InTooltip, bool bInReadOnly, uint8 InPossibleObjectReferenceTypes)
: PossibleObjectReferenceTypes(InPossibleObjectReferenceTypes)
{
FriendlyName = InFriendlyName;
check(!CategoryName.IsEmpty());
check(SubCategoryObject.IsValid());
Tooltip = InTooltip;
PinType.PinCategory = CategoryName;
SubCategoryObjectAssetReference = SubCategoryObject;
PinType.PinSubCategoryObject = SubCategoryObjectAssetReference.ResolveObject();
bReadOnly = bInReadOnly;
CachedDescription = GenerateDescription();
}
FText UEdGraphSchema_K2::FPinTypeTreeInfo::GenerateDescription()
{
if (!FriendlyName.IsEmpty())
{
return FriendlyName;
}
else if (PinType.PinSubCategoryObject.IsValid())
{
FText DisplayName;
if (UField* SubCategoryField = Cast<UField>(PinType.PinSubCategoryObject.Get()))
{
DisplayName = SubCategoryField->GetDisplayNameText();
}
else
{
DisplayName = FText::FromString(FName::NameToDisplayString(PinType.PinSubCategoryObject->GetName(), PinType.PinCategory == PC_Boolean));
}
return DisplayName;
}
else
{
return LOCTEXT("PinDescriptionError", "Error!");
}
}
FText UEdGraphSchema_K2::FPinTypeTreeInfo::GetDescription() const
{
return CachedDescription;
}
const FString UEdGraphSchema_K2::PC_Exec(TEXT("exec"));
const FString UEdGraphSchema_K2::PC_Boolean(TEXT("bool"));
const FString UEdGraphSchema_K2::PC_Byte(TEXT("byte"));
const FString UEdGraphSchema_K2::PC_Class(TEXT("class"));
const FString UEdGraphSchema_K2::PC_Int(TEXT("int"));
const FString UEdGraphSchema_K2::PC_Float(TEXT("float"));
const FString UEdGraphSchema_K2::PC_Name(TEXT("name"));
const FString UEdGraphSchema_K2::PC_Delegate(TEXT("delegate"));
const FString UEdGraphSchema_K2::PC_MCDelegate(TEXT("mcdelegate"));
const FString UEdGraphSchema_K2::PC_Object(TEXT("object"));
const FString UEdGraphSchema_K2::PC_Interface(TEXT("interface"));
const FString UEdGraphSchema_K2::PC_String(TEXT("string"));
const FString UEdGraphSchema_K2::PC_Text(TEXT("text"));
const FString UEdGraphSchema_K2::PC_Struct(TEXT("struct"));
const FString UEdGraphSchema_K2::PC_Wildcard(TEXT("wildcard"));
const FString UEdGraphSchema_K2::PC_Enum(TEXT("enum"));
const FString UEdGraphSchema_K2::PC_Asset(TEXT("asset"));
const FString UEdGraphSchema_K2::PC_AssetClass(TEXT("assetclass"));
const FString UEdGraphSchema_K2::PSC_Self(TEXT("self"));
const FString UEdGraphSchema_K2::PSC_Index(TEXT("index"));
const FString UEdGraphSchema_K2::PSC_Bitmask(TEXT("bitmask"));
const FString UEdGraphSchema_K2::PN_Execute(TEXT("execute"));
const FString UEdGraphSchema_K2::PN_Then(TEXT("then"));
const FString UEdGraphSchema_K2::PN_Completed(TEXT("Completed"));
const FString UEdGraphSchema_K2::PN_DelegateEntry(TEXT("delegate"));
const FString UEdGraphSchema_K2::PN_EntryPoint(TEXT("EntryPoint"));
const FString UEdGraphSchema_K2::PN_Self(TEXT("self"));
const FString UEdGraphSchema_K2::PN_Else(TEXT("else"));
const FString UEdGraphSchema_K2::PN_Loop(TEXT("loop"));
const FString UEdGraphSchema_K2::PN_After(TEXT("after"));
const FString UEdGraphSchema_K2::PN_ReturnValue(TEXT("ReturnValue"));
const FString UEdGraphSchema_K2::PN_ObjectToCast(TEXT("Object"));
const FString UEdGraphSchema_K2::PN_Condition(TEXT("Condition"));
const FString UEdGraphSchema_K2::PN_Start(TEXT("Start"));
const FString UEdGraphSchema_K2::PN_Stop(TEXT("Stop"));
const FString UEdGraphSchema_K2::PN_Index(TEXT("Index"));
const FString UEdGraphSchema_K2::PN_Item(TEXT("Item"));
const FString UEdGraphSchema_K2::PN_CastSucceeded(TEXT("then"));
const FString UEdGraphSchema_K2::PN_CastFailed(TEXT("CastFailed"));
const FString UEdGraphSchema_K2::PN_CastedValuePrefix(TEXT("As"));
const FString UEdGraphSchema_K2::PN_MatineeFinished(TEXT("Finished"));
const FName UEdGraphSchema_K2::FN_UserConstructionScript(TEXT("UserConstructionScript"));
const FName UEdGraphSchema_K2::FN_ExecuteUbergraphBase(TEXT("ExecuteUbergraph"));
const FName UEdGraphSchema_K2::GN_EventGraph(TEXT("EventGraph"));
const FName UEdGraphSchema_K2::GN_AnimGraph(TEXT("AnimGraph"));
const FText UEdGraphSchema_K2::VR_DefaultCategory(LOCTEXT("Default", "Default"));
const int32 UEdGraphSchema_K2::AG_LevelReference = 100;
const UScriptStruct* UEdGraphSchema_K2::VectorStruct = nullptr;
const UScriptStruct* UEdGraphSchema_K2::RotatorStruct = nullptr;
const UScriptStruct* UEdGraphSchema_K2::TransformStruct = nullptr;
const UScriptStruct* UEdGraphSchema_K2::LinearColorStruct = nullptr;
const UScriptStruct* UEdGraphSchema_K2::ColorStruct = nullptr;
bool UEdGraphSchema_K2::bGeneratingDocumentation = false;
int32 UEdGraphSchema_K2::CurrentCacheRefreshID = 0;
const FString UEdGraphSchema_K2::AllObjectTypes(TEXT("ObjectTypes"));
UEdGraphSchema_K2::UEdGraphSchema_K2(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
{
// Initialize cached static references to well-known struct types
if (VectorStruct == nullptr)
{
VectorStruct = TBaseStructure<FVector>::Get();
RotatorStruct = TBaseStructure<FRotator>::Get();
TransformStruct = TBaseStructure<FTransform>::Get();
LinearColorStruct = TBaseStructure<FLinearColor>::Get();
ColorStruct = TBaseStructure<FColor>::Get();
}
}
bool UEdGraphSchema_K2::DoesFunctionHaveOutParameters( const UFunction* Function ) const
{
if ( Function != NULL )
{
for ( TFieldIterator<UProperty> PropertyIt(Function); PropertyIt; ++PropertyIt )
{
if ( PropertyIt->PropertyFlags & CPF_OutParm )
{
return true;
}
}
}
return false;
}
bool UEdGraphSchema_K2::CanFunctionBeUsedInGraph(const UClass* InClass, const UFunction* InFunction, const UEdGraph* InDestGraph, uint32 InAllowedFunctionTypes, bool bInCalledForEach, FText* OutReason) const
{
if (CanUserKismetCallFunction(InFunction))
{
bool bLatentFuncsAllowed = true;
bool bIsConstructionScript = false;
if(InDestGraph != nullptr)
{
bLatentFuncsAllowed = (GetGraphType(InDestGraph) == GT_Ubergraph || (GetGraphType(InDestGraph) == GT_Macro));
bIsConstructionScript = IsConstructionScript(InDestGraph);
}
const bool bIsPureFunc = (InFunction->HasAnyFunctionFlags(FUNC_BlueprintPure) != false);
if (bIsPureFunc)
{
const bool bAllowPureFuncs = (InAllowedFunctionTypes & FT_Pure) != 0;
if (!bAllowPureFuncs)
{
if(OutReason != nullptr)
{
*OutReason = LOCTEXT("PureFunctionsNotAllowed", "Pure functions are not allowed.");
}
return false;
}
}
else
{
const bool bAllowImperativeFuncs = (InAllowedFunctionTypes & FT_Imperative) != 0;
if (!bAllowImperativeFuncs)
{
if(OutReason != nullptr)
{
*OutReason = LOCTEXT("ImpureFunctionsNotAllowed", "Impure functions are not allowed.");
}
return false;
}
}
const bool bIsConstFunc = (InFunction->HasAnyFunctionFlags(FUNC_Const) != false);
const bool bAllowConstFuncs = (InAllowedFunctionTypes & FT_Const) != 0;
if (bIsConstFunc && !bAllowConstFuncs)
{
if(OutReason != nullptr)
{
*OutReason = LOCTEXT("ConstFunctionsNotAllowed", "Const functions are not allowed.");
}
return false;
}
const bool bIsLatent = InFunction->HasMetaData(FBlueprintMetadata::MD_Latent);
if (bIsLatent && !bLatentFuncsAllowed)
{
if(OutReason != nullptr)
{
*OutReason = LOCTEXT("LatentFunctionsNotAllowed", "Latent functions cannot be used here.");
}
return false;
}
const bool bIsProtected = InFunction->GetBoolMetaData(FBlueprintMetadata::MD_Protected);
const bool bFuncBelongsToSubClass = InClass->IsChildOf(InFunction->GetOuterUClass());
if (bIsProtected)
{
const bool bAllowProtectedFuncs = (InAllowedFunctionTypes & FT_Protected) != 0;
if (!bAllowProtectedFuncs)
{
if(OutReason != nullptr)
{
*OutReason = LOCTEXT("ProtectedFunctionsNotAllowed", "Protected functions are not allowed.");
}
return false;
}
if (!bFuncBelongsToSubClass)
{
if(OutReason != nullptr)
{
*OutReason = LOCTEXT("ProtectedFunctionInaccessible", "Function is protected and inaccessible.");
}
return false;
}
}
const bool bIsPrivate = InFunction->GetBoolMetaData(FBlueprintMetadata::MD_Private);
const bool bFuncBelongsToClass = bFuncBelongsToSubClass && (InFunction->GetOuterUClass() == InClass);
if (bIsPrivate && !bFuncBelongsToClass)
{
if(OutReason != nullptr)
{
*OutReason = LOCTEXT("PrivateFunctionInaccessible", "Function is private and inaccessible.");
}
return false;
}
const bool bIsUnsafeForConstruction = InFunction->GetBoolMetaData(FBlueprintMetadata::MD_UnsafeForConstructionScripts);
if (bIsUnsafeForConstruction && bIsConstructionScript)
{
if(OutReason != nullptr)
{
*OutReason = LOCTEXT("FunctionUnsafeForConstructionScript", "Function cannot be used in a Construction Script.");
}
return false;
}
const bool bRequiresWorldContext = InFunction->HasMetaData(FBlueprintMetadata::MD_WorldContext);
if (bRequiresWorldContext)
{
if (InDestGraph && !InFunction->HasMetaData(FBlueprintMetadata::MD_CallableWithoutWorldContext))
{
const FString& ContextParam = InFunction->GetMetaData(FBlueprintMetadata::MD_WorldContext);
if (InFunction->FindPropertyByName(FName(*ContextParam)) != nullptr)
{
auto BP = FBlueprintEditorUtils::FindBlueprintForGraph(InDestGraph);
const bool bIsFunctLib = BP && (EBlueprintType::BPTYPE_FunctionLibrary == BP->BlueprintType);
UClass* ParentClass = BP ? BP->ParentClass : NULL;
const bool bIncompatibleParrent = ParentClass && (!ParentClass->GetDefaultObject()->ImplementsGetWorld() && !ParentClass->HasMetaData(FBlueprintMetadata::MD_ShowWorldContextPin));
if (!bIsFunctLib && bIncompatibleParrent)
{
if (OutReason != nullptr)
{
*OutReason = LOCTEXT("FunctionRequiresWorldContext", "Function requires a world context.");
}
return false;
}
}
}
}
const bool bFunctionStatic = InFunction->HasAllFunctionFlags(FUNC_Static);
const bool bHasReturnParams = (InFunction->GetReturnProperty() != NULL);
const bool bHasArrayPointerParms = InFunction->HasMetaData(FBlueprintMetadata::MD_ArrayParam);
const bool bAllowForEachCall = !bFunctionStatic && !bIsLatent && !bIsPureFunc && !bIsConstFunc && !bHasReturnParams && !bHasArrayPointerParms;
if (bInCalledForEach && !bAllowForEachCall)
{
if(OutReason != nullptr)
{
if(bFunctionStatic)
{
*OutReason = LOCTEXT("StaticFunctionsNotAllowedInForEachContext", "Static functions cannot be used within a ForEach context.");
}
else if(bIsLatent)
{
*OutReason = LOCTEXT("LatentFunctionsNotAllowedInForEachContext", "Latent functions cannot be used within a ForEach context.");
}
else if(bIsPureFunc)
{
*OutReason = LOCTEXT("PureFunctionsNotAllowedInForEachContext", "Pure functions cannot be used within a ForEach context.");
}
else if(bIsConstFunc)
{
*OutReason = LOCTEXT("ConstFunctionsNotAllowedInForEachContext", "Const functions cannot be used within a ForEach context.");
}
else if(bHasReturnParams)
{
*OutReason = LOCTEXT("FunctionsWithReturnValueNotAllowedInForEachContext", "Functions that return a value cannot be used within a ForEach context.");
}
else if(bHasArrayPointerParms)
{
*OutReason = LOCTEXT("FunctionsWithArrayParmsNotAllowedInForEachContext", "Functions with array parameters cannot be used within a ForEach context.");
}
else
{
*OutReason = LOCTEXT("FunctionNotAllowedInForEachContext", "Function cannot be used within a ForEach context.");
}
}
return false;
}
return true;
}
if(OutReason != nullptr)
{
*OutReason = LOCTEXT("FunctionInvalid", "Invalid function.");
}
return false;
}
UFunction* UEdGraphSchema_K2::GetCallableParentFunction(UFunction* Function) const
{
if( Function && Cast<UClass>(Function->GetOuter()) )
{
const FName FunctionName = Function->GetFName();
// Search up the parent scopes
UClass* ParentClass = CastChecked<UClass>(Function->GetOuter())->GetSuperClass();
UFunction* ClassFunction = ParentClass->FindFunctionByName(FunctionName);
return ClassFunction;
}
return NULL;
}
bool UEdGraphSchema_K2::CanUserKismetCallFunction(const UFunction* Function)
{
return Function &&
(Function->HasAllFunctionFlags(FUNC_BlueprintCallable) && !Function->HasAllFunctionFlags(FUNC_Delegate) && !Function->GetBoolMetaData(FBlueprintMetadata::MD_BlueprintInternalUseOnly) && !Function->HasMetaData(FBlueprintMetadata::MD_DeprecatedFunction));
}
bool UEdGraphSchema_K2::CanKismetOverrideFunction(const UFunction* Function)
{
return Function &&
(Function->HasAllFunctionFlags(FUNC_BlueprintEvent) && !Function->HasAllFunctionFlags(FUNC_Delegate) && !Function->GetBoolMetaData(FBlueprintMetadata::MD_BlueprintInternalUseOnly) && !Function->HasMetaData(FBlueprintMetadata::MD_DeprecatedFunction));
}
bool UEdGraphSchema_K2::HasFunctionAnyOutputParameter(const UFunction* InFunction)
{
check(InFunction);
for (TFieldIterator<UProperty> PropIt(InFunction); PropIt && (PropIt->PropertyFlags & CPF_Parm); ++PropIt)
{
UProperty* FuncParam = *PropIt;
if (FuncParam->HasAnyPropertyFlags(CPF_ReturnParm) || (FuncParam->HasAnyPropertyFlags(CPF_OutParm) && !FuncParam->HasAnyPropertyFlags(CPF_ReferenceParm) && !FuncParam->HasAnyPropertyFlags(CPF_ConstParm)))
{
return true;
}
}
return false;
}
bool UEdGraphSchema_K2::FunctionCanBePlacedAsEvent(const UFunction* InFunction)
{
// First check we are override-able, non-static and non-const
if (!InFunction || !CanKismetOverrideFunction(InFunction) || InFunction->HasAnyFunctionFlags(FUNC_Static|FUNC_Const))
{
return false;
}
// Then look to see if we have any output, return, or reference params
return !HasFunctionAnyOutputParameter(InFunction);
}
bool UEdGraphSchema_K2::FunctionCanBeUsedInDelegate(const UFunction* InFunction)
{
if (!InFunction ||
!CanUserKismetCallFunction(InFunction) ||
InFunction->HasMetaData(FBlueprintMetadata::MD_Latent) ||
InFunction->HasAllFunctionFlags(FUNC_BlueprintPure))
{
return false;
}
return true;
}
FText UEdGraphSchema_K2::GetFriendlySignatureName(const UFunction* Function)
{
return UK2Node_CallFunction::GetUserFacingFunctionName( Function );
}
void UEdGraphSchema_K2::GetAutoEmitTermParameters(const UFunction* Function, TArray<FString>& AutoEmitParameterNames) const
{
AutoEmitParameterNames.Empty();
if( Function->HasMetaData(FBlueprintMetadata::MD_AutoCreateRefTerm) )
{
FString MetaData = Function->GetMetaData(FBlueprintMetadata::MD_AutoCreateRefTerm);
MetaData.ParseIntoArray(AutoEmitParameterNames, TEXT(","), true);
for (int32 NameIndex = 0; NameIndex < AutoEmitParameterNames.Num();)
{
FString& ParameterName = AutoEmitParameterNames[NameIndex];
ParameterName.Trim();
ParameterName.TrimTrailing();
if (ParameterName.IsEmpty())
{
AutoEmitParameterNames.RemoveAtSwap(NameIndex);
}
else
{
++NameIndex;
}
}
}
}
bool UEdGraphSchema_K2::FunctionHasParamOfType(const UFunction* InFunction, UEdGraph const* InGraph, const FEdGraphPinType& DesiredPinType, bool bWantOutput) const
{
TSet<FString> HiddenPins;
FBlueprintEditorUtils::GetHiddenPinsForFunction(InGraph, InFunction, HiddenPins);
// Iterate over all params of function
for (TFieldIterator<UProperty> PropIt(InFunction); PropIt && (PropIt->PropertyFlags & CPF_Parm); ++PropIt)
{
UProperty* FuncParam = *PropIt;
// Ensure that this isn't a hidden parameter
if (!HiddenPins.Contains(FuncParam->GetName()))
{
// See if this is the direction we want (input or output)
const bool bIsFunctionInput = !FuncParam->HasAnyPropertyFlags(CPF_OutParm) || FuncParam->HasAnyPropertyFlags(CPF_ReferenceParm);
if (bIsFunctionInput != bWantOutput)
{
// See if this pin has compatible types
FEdGraphPinType ParamPinType;
bool bConverted = ConvertPropertyToPinType(FuncParam, ParamPinType);
if (bConverted)
{
UClass* Context = nullptr;
UBlueprint* Blueprint = Cast<UBlueprint>(InGraph->GetOuter());
if (Blueprint)
{
Context = Blueprint->GeneratedClass;
}
if (bIsFunctionInput && ArePinTypesCompatible(DesiredPinType, ParamPinType, Context))
{
return true;
}
else if (!bIsFunctionInput && ArePinTypesCompatible(ParamPinType, DesiredPinType, Context))
{
return true;
}
}
}
}
}
// Boo, no pin of this type
return false;
}
void UEdGraphSchema_K2::AddExtraFunctionFlags(const UEdGraph* CurrentGraph, int32 ExtraFlags) const
{
for (auto It = CurrentGraph->Nodes.CreateConstIterator(); It; ++It)
{
if (UK2Node_FunctionEntry* Node = Cast<UK2Node_FunctionEntry>(*It))
{
Node->AddExtraFlags(ExtraFlags);
}
}
}
void UEdGraphSchema_K2::MarkFunctionEntryAsEditable(const UEdGraph* CurrentGraph, bool bNewEditable) const
{
for (auto It = CurrentGraph->Nodes.CreateConstIterator(); It; ++It)
{
if (UK2Node_EditablePinBase* Node = Cast<UK2Node_EditablePinBase>(*It))
{
Node->bIsEditable = bNewEditable;
}
}
}
void UEdGraphSchema_K2::ListFunctionsMatchingSignatureAsDelegates(FGraphContextMenuBuilder& ContextMenuBuilder, const UClass* Class, const UFunction* SignatureToMatch) const
{
check(Class);
for (TFieldIterator<UFunction> FunctionIt(Class, EFieldIteratorFlags::IncludeSuper); FunctionIt; ++FunctionIt)
{
const UFunction* TrialFunction = *FunctionIt;
if (CanUserKismetCallFunction(TrialFunction) && TrialFunction->IsSignatureCompatibleWith(SignatureToMatch))
{
FString Description(TrialFunction->GetName());
FString Tooltip = FString::Printf(TEXT("Existing function '%s' as delegate"), *(TrialFunction->GetName())); //@TODO: Need a better tooltip
// @TODO
}
}
}
bool UEdGraphSchema_K2::IsActorValidForLevelScriptRefs(const AActor* TestActor, const UBlueprint* Blueprint) const
{
check(Blueprint);
return TestActor
&& FBlueprintEditorUtils::IsLevelScriptBlueprint(Blueprint)
&& (TestActor->GetLevel() == FBlueprintEditorUtils::GetLevelFromBlueprint(Blueprint))
&& FKismetEditorUtilities::IsActorValidForLevelScript(TestActor);
}
void UEdGraphSchema_K2::ReplaceSelectedNode(UEdGraphNode* SourceNode, AActor* TargetActor)
{
check(SourceNode);
if (TargetActor != NULL)
{
UK2Node_Literal* LiteralNode = (UK2Node_Literal*)(SourceNode);
if (LiteralNode)
{
const FScopedTransaction Transaction( LOCTEXT("ReplaceSelectedNodeUndoTransaction", "Replace Selected Node") );
LiteralNode->Modify();
LiteralNode->SetObjectRef( TargetActor );
LiteralNode->ReconstructNode();
UBlueprint* Blueprint = FBlueprintEditorUtils::FindBlueprintForGraphChecked(CastChecked<UEdGraph>(SourceNode->GetOuter()));
FBlueprintEditorUtils::MarkBlueprintAsModified(Blueprint);
}
}
}
void UEdGraphSchema_K2::AddSelectedReplaceableNodes( UBlueprint* Blueprint, const UEdGraphNode* InGraphNode, FMenuBuilder* MenuBuilder ) const
{
//Only allow replace object reference functionality for literal nodes
const UK2Node_Literal* LiteralNode = Cast<UK2Node_Literal>(InGraphNode);
if (LiteralNode)
{
USelection* SelectedActors = GEditor->GetSelectedActors();
for(FSelectionIterator Iter(*SelectedActors); Iter; ++Iter)
{
// We only care about actors that are referenced in the world for literals, and also in the same level as this blueprint
AActor* Actor = Cast<AActor>(*Iter);
if( LiteralNode->GetObjectRef() != Actor && IsActorValidForLevelScriptRefs(Actor, Blueprint) )
{
FText Description = FText::Format( LOCTEXT("ChangeToActorName", "Change to <{0}>"), FText::FromString( Actor->GetActorLabel() ) );
FText ToolTip = LOCTEXT("ReplaceNodeReferenceToolTip", "Replace node reference");
MenuBuilder->AddMenuEntry( Description, ToolTip, FSlateIcon(), FUIAction(
FExecuteAction::CreateUObject((UEdGraphSchema_K2*const)this, &UEdGraphSchema_K2::ReplaceSelectedNode, const_cast< UEdGraphNode* >(InGraphNode), Actor) ) );
}
}
}
}
bool UEdGraphSchema_K2::CanUserKismetAccessVariable(const UProperty* Property, const UClass* InClass, EDelegateFilterMode FilterMode)
{
const bool bIsDelegate = Property->IsA(UMulticastDelegateProperty::StaticClass());
const bool bIsAccessible = Property->HasAllPropertyFlags(CPF_BlueprintVisible);
const bool bIsAssignableOrCallable = Property->HasAnyPropertyFlags(CPF_BlueprintAssignable | CPF_BlueprintCallable);
const bool bPassesDelegateFilter = (bIsAccessible && !bIsDelegate && (FilterMode != MustBeDelegate)) ||
(bIsAssignableOrCallable && bIsDelegate && (FilterMode != CannotBeDelegate));
const bool bHidden = FObjectEditorUtils::IsVariableCategoryHiddenFromClass(Property, InClass);
return !Property->HasAnyPropertyFlags(CPF_Parm) && bPassesDelegateFilter && !bHidden;
}
bool UEdGraphSchema_K2::ClassHasBlueprintAccessibleMembers(const UClass* InClass) const
{
// @TODO Don't show other blueprints yet...
UBlueprint* ClassBlueprint = UBlueprint::GetBlueprintFromClass(InClass);
if (!InClass->HasAnyClassFlags(CLASS_Deprecated | CLASS_NewerVersionExists) && (ClassBlueprint == NULL))
{
// Find functions
for (TFieldIterator<UFunction> FunctionIt(InClass, EFieldIteratorFlags::IncludeSuper); FunctionIt; ++FunctionIt)
{
UFunction* Function = *FunctionIt;
const bool bIsBlueprintProtected = Function->GetBoolMetaData(FBlueprintMetadata::MD_Protected);
const bool bHidden = FObjectEditorUtils::IsFunctionHiddenFromClass(Function, InClass);
if (UEdGraphSchema_K2::CanUserKismetCallFunction(Function) && !bIsBlueprintProtected && !bHidden)
{
return true;
}
}
// Find vars
for (TFieldIterator<UProperty> PropertyIt(InClass, EFieldIteratorFlags::IncludeSuper); PropertyIt; ++PropertyIt)
{
UProperty* Property = *PropertyIt;
if (CanUserKismetAccessVariable(Property, InClass, CannotBeDelegate))
{
return true;
}
}
}
return false;
}
bool UEdGraphSchema_K2::IsAllowableBlueprintVariableType(const class UEnum* InEnum)
{
return InEnum && (InEnum->GetBoolMetaData(FBlueprintMetadata::MD_AllowableBlueprintVariableType) || InEnum->IsA<UUserDefinedEnum>());
}
bool UEdGraphSchema_K2::IsAllowableBlueprintVariableType(const class UClass* InClass)
{
if (InClass)
{
// No Skeleton classes or reinstancing classes (they would inherit the BlueprintType metadata)
if (FKismetEditorUtilities::IsClassABlueprintSkeleton(InClass)
|| InClass->HasAnyClassFlags(CLASS_NewerVersionExists))
{
return false;
}
// No Blueprint Macro Libraries
if (FKismetEditorUtilities::IsClassABlueprintMacroLibrary(InClass))
{
return false;
}
// UObject is an exception, and is always a blueprint-able type
if(InClass == UObject::StaticClass())
{
return true;
}
static const FBoolConfigValueHelper NotBlueprintType(TEXT("EditoronlyBP"), TEXT("bBlueprintIsNotBlueprintType"));
if (NotBlueprintType && InClass->IsChildOf(UBlueprint::StaticClass()))
{
return false;
}
// cannot have level script variables
if (InClass->IsChildOf(ALevelScriptActor::StaticClass()))
{
return false;
}
const UClass* ParentClass = InClass;
while(ParentClass)
{
// Climb up the class hierarchy and look for "BlueprintType" and "NotBlueprintType" to see if this class is allowed.
if(ParentClass->GetBoolMetaData(FBlueprintMetadata::MD_AllowableBlueprintVariableType)
|| ParentClass->HasMetaData(FBlueprintMetadata::MD_BlueprintSpawnableComponent))
{
return true;
}
else if(ParentClass->GetBoolMetaData(FBlueprintMetadata::MD_NotAllowableBlueprintVariableType))
{
return false;
}
ParentClass = ParentClass->GetSuperClass();
}
}
return false;
}
bool UEdGraphSchema_K2::IsAllowableBlueprintVariableType(const class UScriptStruct *InStruct)
{
if (auto UDStruct = Cast<const UUserDefinedStruct>(InStruct))
{
if (EUserDefinedStructureStatus::UDSS_UpToDate != UDStruct->Status.GetValue())
{
return false;
}
}
return InStruct && (InStruct->GetBoolMetaDataHierarchical(FBlueprintMetadata::MD_AllowableBlueprintVariableType));
}
bool UEdGraphSchema_K2::DoesGraphSupportImpureFunctions(const UEdGraph* InGraph) const
{
const EGraphType GraphType = GetGraphType(InGraph);
const bool bAllowImpureFuncs = GraphType != GT_Animation; //@TODO: It's really more nuanced than this (e.g., in a function someone wants to write as pure)
return bAllowImpureFuncs;
}
bool UEdGraphSchema_K2::IsPropertyExposedOnSpawn(const UProperty* Property)
{
if (Property)
{
const bool bMeta = Property->HasMetaData(FBlueprintMetadata::MD_ExposeOnSpawn);
const bool bFlag = Property->HasAllPropertyFlags(CPF_ExposeOnSpawn);
if (bMeta != bFlag)
{
UE_LOG(LogBlueprint, Warning
, TEXT("ExposeOnSpawn ambiguity. Property '%s', MetaData '%s', Flag '%s'")
, *Property->GetFullName()
, bMeta ? *GTrue.ToString() : *GFalse.ToString()
, bFlag ? *GTrue.ToString() : *GFalse.ToString());
}
return bMeta || bFlag;
}
return false;
}
// if node is a get/set variable and the variable it refers to does not exist
static bool IsUsingNonExistantVariable(const UEdGraphNode* InGraphNode, UBlueprint* OwnerBlueprint)
{
bool bNonExistantVariable = false;
const bool bBreakOrMakeStruct =
InGraphNode->IsA(UK2Node_BreakStruct::StaticClass()) ||
InGraphNode->IsA(UK2Node_MakeStruct::StaticClass());
if (!bBreakOrMakeStruct)
{
if (const UK2Node_Variable* Variable = Cast<const UK2Node_Variable>(InGraphNode))
{
if (Variable->VariableReference.IsSelfContext())
{
TArray<FName> CurrentVars;
FBlueprintEditorUtils::GetClassVariableList(OwnerBlueprint, CurrentVars);
if ( false == CurrentVars.Contains(Variable->GetVarName()) )
{
bNonExistantVariable = true;
}
}
else if(Variable->VariableReference.IsLocalScope())
{
// If there is no member scope, or we can't find the local variable in the member scope, then it's non-existant
UStruct* MemberScope = Variable->VariableReference.GetMemberScope(Variable->GetBlueprintClassFromNode());
if (MemberScope == nullptr || !FBlueprintEditorUtils::FindLocalVariable(OwnerBlueprint, MemberScope, Variable->GetVarName()))
{
bNonExistantVariable = true;
}
}
}
}
return bNonExistantVariable;
}
bool UEdGraphSchema_K2::PinHasSplittableStructType(const UEdGraphPin* InGraphPin) const
{
const FEdGraphPinType& PinType = InGraphPin->PinType;
bool bCanSplit = (!PinType.bIsArray && PinType.PinCategory == PC_Struct);
if (bCanSplit)
{
UScriptStruct* StructType = CastChecked<UScriptStruct>(InGraphPin->PinType.PinSubCategoryObject.Get());
if (InGraphPin->Direction == EGPD_Input)
{
bCanSplit = UK2Node_MakeStruct::CanBeMade(StructType, true, true);
if (!bCanSplit)
{
const FString& MetaData = StructType->GetMetaData(TEXT("HasNativeMake"));
UFunction* Function = FindObject<UFunction>(NULL, *MetaData, true);
bCanSplit = (Function != NULL);
}
}
else
{
bCanSplit = UK2Node_BreakStruct::CanBeBroken(StructType, true, true);
if (!bCanSplit)
{
const FString& MetaData = StructType->GetMetaData(TEXT("HasNativeBreak"));
UFunction* Function = FindObject<UFunction>(NULL, *MetaData, true);
bCanSplit = (Function != NULL);
}
}
}
return bCanSplit;
}
bool UEdGraphSchema_K2::PinDefaultValueIsEditable(const UEdGraphPin& InGraphPin) const
{
// Array types are not currently assignable without a 'make array' node:
if( InGraphPin.PinType.bIsArray )
{
return false;
}
// User defined structures (from code or from data) cannot accept default values:
if( InGraphPin.PinType.PinCategory == PC_Struct )
{
// Only the built in struct types are editable as 'default' values on a pin.
// See FNodeFactory::CreatePinWidget for justification of the above statement!
UObject const& SubCategoryObject = *InGraphPin.PinType.PinSubCategoryObject;
return &SubCategoryObject == VectorStruct
|| &SubCategoryObject == RotatorStruct
|| &SubCategoryObject == TransformStruct
|| &SubCategoryObject == LinearColorStruct
|| &SubCategoryObject == ColorStruct
|| &SubCategoryObject == FCollisionProfileName::StaticStruct();
}
return true;
}
void UEdGraphSchema_K2::SelectAllInputNodes(UEdGraph* Graph, UEdGraphPin* InGraphPin)
{
TArray<UEdGraphPin*> AllPins = InGraphPin->LinkedTo;
if (AllPins.Num() == 0)
{
return;
}
for (UEdGraphPin* Pin : AllPins)
{
UEdGraphNode* OwningNode = Pin->GetOwningNode();
FKismetEditorUtilities::AddToSelection(Graph, OwningNode);
TArray<UEdGraphPin*> LinkedPins = Pin->GetOwningNode()->GetAllPins();
for (UEdGraphPin* InputPin : LinkedPins)
{
if (InputPin->Direction == EEdGraphPinDirection::EGPD_Output)
{
continue;
}
else
{
SelectAllInputNodes(Graph, InputPin);
}
}
}
}
void UEdGraphSchema_K2::GetContextMenuActions(const UEdGraph* CurrentGraph, const UEdGraphNode* InGraphNode, const UEdGraphPin* InGraphPin, FMenuBuilder* MenuBuilder, bool bIsDebugging) const
{
check(CurrentGraph);
UBlueprint* OwnerBlueprint = FBlueprintEditorUtils::FindBlueprintForGraphChecked(CurrentGraph);
if (InGraphPin != NULL)
{
MenuBuilder->BeginSection("EdGraphSchemaPinActions", LOCTEXT("PinActionsMenuHeader", "Pin Actions"));
{
if (!bIsDebugging)
{
// Break pin links
if (InGraphPin->LinkedTo.Num() > 1)
{
MenuBuilder->AddMenuEntry( FGraphEditorCommands::Get().BreakPinLinks );
}
// Add the change pin type action, if this is a select node
if (InGraphNode->IsA(UK2Node_Select::StaticClass()))
{
MenuBuilder->AddMenuEntry(FGraphEditorCommands::Get().ChangePinType);
}
// add sub menu for break link to
if (InGraphPin->LinkedTo.Num() > 0)
{
MenuBuilder->AddMenuEntry(
LOCTEXT("SelectAllInputNodes", "Select All Input Nodes"),
LOCTEXT("SelectAllInputNodesTooltip", "Adds all input Nodes linked to this Pin to selection"),
FSlateIcon(),
FUIAction(FExecuteAction::CreateUObject((UEdGraphSchema_K2*const)this, &UEdGraphSchema_K2::SelectAllInputNodes, const_cast<UEdGraph*>(CurrentGraph), const_cast<UEdGraphPin*>(InGraphPin))));
if(InGraphPin->LinkedTo.Num() > 1)
{
MenuBuilder->AddSubMenu(
LOCTEXT("BreakLinkTo", "Break Link To..."),
LOCTEXT("BreakSpecificLinks", "Break a specific link..."),
FNewMenuDelegate::CreateUObject( (UEdGraphSchema_K2*const)this, &UEdGraphSchema_K2::GetBreakLinkToSubMenuActions, const_cast<UEdGraphPin*>(InGraphPin)));
MenuBuilder->AddSubMenu(
LOCTEXT("JumpToConnection", "Jump to Connection..."),
LOCTEXT("JumpToSpecificConnection", "Jump to specific connection..."),
FNewMenuDelegate::CreateUObject( (UEdGraphSchema_K2*const)this, &UEdGraphSchema_K2::GetJumpToConnectionSubMenuActions, const_cast<UEdGraphPin*>(InGraphPin)));
MenuBuilder->AddSubMenu(
LOCTEXT("StraightenConnection", "Straighten Connection To..."),
LOCTEXT("StraightenConnection_Tip", "Straighten a specific connection"),
FNewMenuDelegate::CreateUObject( this, &UEdGraphSchema_K2::GetStraightenConnectionToSubMenuActions, const_cast<UEdGraphPin*>(InGraphPin)));
}
else
{
((UEdGraphSchema_K2*const)this)->GetBreakLinkToSubMenuActions(*MenuBuilder, const_cast<UEdGraphPin*>(InGraphPin));
((UEdGraphSchema_K2*const)this)->GetJumpToConnectionSubMenuActions(*MenuBuilder, const_cast<UEdGraphPin*>(InGraphPin));
UEdGraphPin* Pin = InGraphPin->LinkedTo[0];
FText PinName = Pin->GetDisplayName();
FText NodeName = Pin->GetOwningNode()->GetNodeTitle(ENodeTitleType::ListView);
MenuBuilder->AddMenuEntry(
FGraphEditorCommands::Get().StraightenConnections,
NAME_None,
FText::Format(LOCTEXT("StraightenDescription_SinglePin", "Straighten Connection to {0} ({1})"), NodeName, PinName),
FText::Format(LOCTEXT("StraightenDescription_SinglePin_Node_Tip", "Straighten the connection between this pin, and {0} ({1})"), NodeName, PinName),
FSlateIcon(NAME_None, NAME_None, NAME_None)
);
}
}
// Conditionally add the var promotion pin if this is an output pin and it's not an exec pin
if (InGraphPin->PinType.PinCategory != PC_Exec)
{
MenuBuilder->AddMenuEntry( FGraphEditorCommands::Get().PromoteToVariable );
if (FBlueprintEditorUtils::DoesSupportLocalVariables(CurrentGraph))
{
MenuBuilder->AddMenuEntry( FGraphEditorCommands::Get().PromoteToLocalVariable );
}
}
if (InGraphPin->PinType.PinCategory == PC_Struct && InGraphNode->AllowSplitPins())
{
// If the pin cannot be split, create an error tooltip to use
FText Tooltip;
if (PinHasSplittableStructType(InGraphPin))
{
Tooltip = FGraphEditorCommands::Get().SplitStructPin->GetDescription();
}
else
{
Tooltip = LOCTEXT("SplitStructPin_Error", "Cannot split the struct pin, it may be missing Blueprint exposed properties!");
}
MenuBuilder->AddMenuEntry( FGraphEditorCommands::Get().SplitStructPin, NAME_None, FGraphEditorCommands::Get().SplitStructPin->GetLabel(), Tooltip );
}
if (InGraphPin->ParentPin != NULL)
{
MenuBuilder->AddMenuEntry( FGraphEditorCommands::Get().RecombineStructPin );
}
// Conditionally add the execution path pin removal if this is an execution branching node
if( InGraphPin->Direction == EGPD_Output && InGraphPin->GetOwningNode())
{
if (CastChecked<UK2Node>(InGraphPin->GetOwningNode())->CanEverRemoveExecutionPin())
{
MenuBuilder->AddMenuEntry( FGraphEditorCommands::Get().RemoveExecutionPin );
}
}
if (UK2Node_SetFieldsInStruct::ShowCustomPinActions(InGraphPin, true))
{
MenuBuilder->AddMenuEntry(FGraphEditorCommands::Get().RemoveThisStructVarPin);
MenuBuilder->AddMenuEntry(FGraphEditorCommands::Get().RemoveOtherStructVarPins);
}
}
}
MenuBuilder->EndSection();
// Add the watch pin / unwatch pin menu items
MenuBuilder->BeginSection("EdGraphSchemaWatches", LOCTEXT("WatchesHeader", "Watches"));
{
if (!IsMetaPin(*InGraphPin))
{
const UEdGraphPin* WatchedPin = ((InGraphPin->Direction == EGPD_Input) && (InGraphPin->LinkedTo.Num() > 0)) ? InGraphPin->LinkedTo[0] : InGraphPin;
if (FKismetDebugUtilities::IsPinBeingWatched(OwnerBlueprint, WatchedPin))
{
MenuBuilder->AddMenuEntry( FGraphEditorCommands::Get().StopWatchingPin );
}
else
{
MenuBuilder->AddMenuEntry( FGraphEditorCommands::Get().StartWatchingPin );
}
}
}
MenuBuilder->EndSection();
}
else if (InGraphNode != NULL)
{
if (IsUsingNonExistantVariable(InGraphNode, OwnerBlueprint))
{
MenuBuilder->BeginSection("EdGraphSchemaNodeActions", LOCTEXT("NodeActionsMenuHeader", "Node Actions"));
{
GetNonExistentVariableMenu(InGraphNode,OwnerBlueprint, MenuBuilder);
}
MenuBuilder->EndSection();
}
else
{
MenuBuilder->BeginSection("EdGraphSchemaNodeActions", LOCTEXT("NodeActionsMenuHeader", "Node Actions"));
{
if (!bIsDebugging)
{
// Replaceable node display option
AddSelectedReplaceableNodes( OwnerBlueprint, InGraphNode, MenuBuilder );
// Node contextual actions
MenuBuilder->AddMenuEntry( FGenericCommands::Get().Delete );
MenuBuilder->AddMenuEntry( FGenericCommands::Get().Cut );
MenuBuilder->AddMenuEntry( FGenericCommands::Get().Copy );
MenuBuilder->AddMenuEntry( FGenericCommands::Get().Duplicate );
MenuBuilder->AddMenuEntry( FGraphEditorCommands::Get().ReconstructNodes );
MenuBuilder->AddMenuEntry( FGraphEditorCommands::Get().BreakNodeLinks );
// Conditionally add the action to add an execution pin, if this is an execution node
if( InGraphNode->IsA(UK2Node_ExecutionSequence::StaticClass()) || InGraphNode->IsA(UK2Node_Switch::StaticClass()) )
{
MenuBuilder->AddMenuEntry( FGraphEditorCommands::Get().AddExecutionPin );
}
// Conditionally add the action to create a super function call node, if this is an event or function entry
if( InGraphNode->IsA(UK2Node_Event::StaticClass()) || InGraphNode->IsA(UK2Node_FunctionEntry::StaticClass()) )
{
MenuBuilder->AddMenuEntry( FGraphEditorCommands::Get().AddParentNode );
}
// Conditionally add the actions to add or remove an option pin, if this is a select node
if (InGraphNode->IsA(UK2Node_Select::StaticClass()))
{
MenuBuilder->AddMenuEntry(FGraphEditorCommands::Get().AddOptionPin);
MenuBuilder->AddMenuEntry(FGraphEditorCommands::Get().RemoveOptionPin);
}
// Don't show the "Assign selected Actor" option if more than one actor is selected
if (InGraphNode->IsA(UK2Node_ActorBoundEvent::StaticClass()) && GEditor->GetSelectedActorCount() == 1)
{
MenuBuilder->AddMenuEntry(FGraphEditorCommands::Get().AssignReferencedActor);
}
// Add the goto source code action for native functions
if (InGraphNode->IsA(UK2Node_CallFunction::StaticClass()))
{
const UEdGraphNode* ResultEventNode= NULL;
if(Cast<UK2Node_CallFunction>(InGraphNode)->GetFunctionGraph(ResultEventNode))
{
MenuBuilder->AddMenuEntry(FGraphEditorCommands::Get().GoToDefinition);
}
else
{
MenuBuilder->AddMenuEntry(FGraphEditorCommands::Get().GotoNativeFunctionDefinition);
}
}
// Functions, macros, and composite nodes support going to a definition
if (InGraphNode->IsA(UK2Node_MacroInstance::StaticClass()) || InGraphNode->IsA(UK2Node_Composite::StaticClass()))
{
MenuBuilder->AddMenuEntry(FGraphEditorCommands::Get().GoToDefinition);
}
MenuBuilder->AddMenuEntry(FGraphEditorCommands::Get().FindReferences);
// show search for references for variable nodes and goto source code action
if (InGraphNode->IsA(UK2Node_Variable::StaticClass()))
{
MenuBuilder->AddMenuEntry(FGraphEditorCommands::Get().GotoNativeVariableDefinition);
GetReplaceVariableMenu(InGraphNode,OwnerBlueprint, MenuBuilder, true);
}
if (InGraphNode->IsA(UK2Node_SetFieldsInStruct::StaticClass()))
{
MenuBuilder->AddMenuEntry(FGraphEditorCommands::Get().RestoreAllStructVarPins);
}
MenuBuilder->AddMenuEntry(FGenericCommands::Get().Rename, NAME_None, LOCTEXT("Rename", "Rename"), LOCTEXT("Rename_Tooltip", "Renames selected function or variable in blueprint.") );
}
// Select referenced actors in the level
MenuBuilder->AddMenuEntry( FGraphEditorCommands::Get().SelectReferenceInLevel );
}
MenuBuilder->EndSection(); //EdGraphSchemaNodeActions
if (!bIsDebugging)
{
// Collapse/expand nodes
MenuBuilder->BeginSection("EdGraphSchemaOrganization", LOCTEXT("OrganizationHeader", "Organization"));
{
MenuBuilder->AddMenuEntry( FGraphEditorCommands::Get().CollapseNodes );
MenuBuilder->AddMenuEntry( FGraphEditorCommands::Get().CollapseSelectionToFunction );
MenuBuilder->AddMenuEntry( FGraphEditorCommands::Get().CollapseSelectionToMacro );
MenuBuilder->AddMenuEntry( FGraphEditorCommands::Get().ExpandNodes );
if(InGraphNode->IsA(UK2Node_Composite::StaticClass()))
{
MenuBuilder->AddMenuEntry( FGraphEditorCommands::Get().PromoteSelectionToFunction );
MenuBuilder->AddMenuEntry( FGraphEditorCommands::Get().PromoteSelectionToMacro );
}
MenuBuilder->AddSubMenu(LOCTEXT("AlignmentHeader", "Alignment"), FText(), FNewMenuDelegate::CreateLambda([](FMenuBuilder& InMenuBuilder){
InMenuBuilder.BeginSection("EdGraphSchemaAlignment", LOCTEXT("AlignHeader", "Align"));
InMenuBuilder.AddMenuEntry( FGraphEditorCommands::Get().AlignNodesTop );
InMenuBuilder.AddMenuEntry( FGraphEditorCommands::Get().AlignNodesMiddle );
InMenuBuilder.AddMenuEntry( FGraphEditorCommands::Get().AlignNodesBottom );
InMenuBuilder.AddMenuEntry( FGraphEditorCommands::Get().AlignNodesLeft );
InMenuBuilder.AddMenuEntry( FGraphEditorCommands::Get().AlignNodesCenter );
InMenuBuilder.AddMenuEntry( FGraphEditorCommands::Get().AlignNodesRight );
InMenuBuilder.AddMenuEntry( FGraphEditorCommands::Get().StraightenConnections );
InMenuBuilder.EndSection();
InMenuBuilder.BeginSection("EdGraphSchemaDistribution", LOCTEXT("DistributionHeader", "Distribution"));
InMenuBuilder.AddMenuEntry( FGraphEditorCommands::Get().DistributeNodesHorizontally );
InMenuBuilder.AddMenuEntry( FGraphEditorCommands::Get().DistributeNodesVertically );
InMenuBuilder.EndSection();
}));
}
MenuBuilder->EndSection();
}
if (const UK2Node* K2Node = Cast<const UK2Node>(InGraphNode))
{
if (!K2Node->IsNodePure())
{
const UBlueprintEditorSettings* EditorSettings = GetDefault<UBlueprintEditorSettings>();
if (EditorSettings && EditorSettings->bAllowExplicitImpureNodeDisabling)
{
// Don't expose the enabled state for disabled nodes that were not explicitly disabled by the user
if (!bIsDebugging && (K2Node->bUserSetEnabledState || K2Node->EnabledState != ENodeEnabledState::Disabled))
{
// Add compile options
MenuBuilder->BeginSection("EdGraphSchemaCompileOptions", LOCTEXT("CompileOptionsHeader", "Compile Options"));
{
MenuBuilder->AddMenuEntry(
FGraphEditorCommands::Get().DisableNodes,
NAME_None,
LOCTEXT("DisableCompile", "Disable (Do Not Compile)"),
LOCTEXT("DisableCompileToolTip", "Selected node(s) will not be compiled."));
TSharedPtr<const FUICommandList> MenuCommandList = MenuBuilder->GetTopCommandList();
if(ensure(MenuCommandList.IsValid()))
{
const FUIAction* SubMenuUIAction = MenuCommandList->GetActionForCommand(FGraphEditorCommands::Get().EnableNodes);
if(ensure(SubMenuUIAction))
{
MenuBuilder->AddSubMenu(
LOCTEXT("EnableCompileSubMenu", "Enable Compile"),
LOCTEXT("EnableCompileSubMenuToolTip", "Options to enable selected node(s) for compile."),
FNewMenuDelegate::CreateLambda([MenuCommandList](FMenuBuilder& SubMenuBuilder)
{
SubMenuBuilder.PushCommandList(MenuCommandList.ToSharedRef());
SubMenuBuilder.AddMenuEntry(
FGraphEditorCommands::Get().EnableNodes_Always,
NAME_None,
LOCTEXT("EnableCompileAlways", "Always"),
LOCTEXT("EnableCompileAlwaysToolTip", "Always compile selected node(s)."));
SubMenuBuilder.AddMenuEntry(
FGraphEditorCommands::Get().EnableNodes_DevelopmentOnly,
NAME_None,
LOCTEXT("EnableCompileDevelopmentOnly", "Development Only"),
LOCTEXT("EnableCompileDevelopmentOnlyToolTip", "Compile selected node(s) for development only."));
SubMenuBuilder.PopCommandList();
}),
*SubMenuUIAction,
NAME_None, FGraphEditorCommands::Get().EnableNodes->GetUserInterfaceType());
}
}
}
MenuBuilder->EndSection();
}
}
// Add breakpoint actions
MenuBuilder->BeginSection("EdGraphSchemaBreakpoints", LOCTEXT("BreakpointsHeader", "Breakpoints"));
{
MenuBuilder->AddMenuEntry( FGraphEditorCommands::Get().ToggleBreakpoint );
MenuBuilder->AddMenuEntry( FGraphEditorCommands::Get().AddBreakpoint );
MenuBuilder->AddMenuEntry( FGraphEditorCommands::Get().RemoveBreakpoint );
MenuBuilder->AddMenuEntry( FGraphEditorCommands::Get().EnableBreakpoint );
MenuBuilder->AddMenuEntry( FGraphEditorCommands::Get().DisableBreakpoint );
}
MenuBuilder->EndSection();
}
}
MenuBuilder->BeginSection("EdGraphSchemaDocumentation", LOCTEXT("DocumentationHeader", "Documentation"));
{
MenuBuilder->AddMenuEntry( FGraphEditorCommands::Get().GoToDocumentation );
}
MenuBuilder->EndSection();
}
}
Super::GetContextMenuActions(CurrentGraph, InGraphNode, InGraphPin, MenuBuilder, bIsDebugging);
}
void UEdGraphSchema_K2::OnCreateNonExistentVariable( UK2Node_Variable* Variable, UBlueprint* OwnerBlueprint)
{
if (UEdGraphPin* Pin = Variable->FindPin(Variable->GetVarNameString()))
{
const FScopedTransaction Transaction( LOCTEXT("CreateMissingVariable", "Create Missing Variable") );
if (FBlueprintEditorUtils::AddMemberVariable(OwnerBlueprint,Variable->GetVarName(), Pin->PinType))
{
FGuid Guid = FBlueprintEditorUtils::FindMemberVariableGuidByName(OwnerBlueprint, Variable->GetVarName());
Variable->VariableReference.SetSelfMember( Variable->GetVarName(), Guid );
}
}
}
void UEdGraphSchema_K2::OnCreateNonExistentLocalVariable( UK2Node_Variable* Variable, UBlueprint* OwnerBlueprint)
{
if (UEdGraphPin* Pin = Variable->FindPin(Variable->GetVarNameString()))
{
const FScopedTransaction Transaction( LOCTEXT("CreateMissingLocalVariable", "Create Missing Local Variable") );
FName VarName = Variable->GetVarName();
if (FBlueprintEditorUtils::AddLocalVariable(OwnerBlueprint, Variable->GetGraph(), VarName, Pin->PinType))
{
FGuid LocalVarGuid = FBlueprintEditorUtils::FindLocalVariableGuidByName(OwnerBlueprint, Variable->GetGraph(), VarName);
if (LocalVarGuid.IsValid())
{
// Loop through every variable in the graph, check if the variable references are the same, and update them
FMemberReference OldReference = Variable->VariableReference;
TArray<UK2Node_Variable*> VariableNodeList;
Variable->GetGraph()->GetNodesOfClass(VariableNodeList);
for( UK2Node_Variable* VariableNode : VariableNodeList)
{
if (VariableNode->VariableReference.IsSameReference(OldReference))
{
VariableNode->VariableReference.SetLocalMember(VarName, FBlueprintEditorUtils::GetTopLevelGraph(Variable->GetGraph())->GetName(), LocalVarGuid);
VariableNode->ReconstructNode();
}
}
}
}
}
}
void UEdGraphSchema_K2::OnReplaceVariableForVariableNode( UK2Node_Variable* Variable, UBlueprint* OwnerBlueprint, FString VariableName, bool bIsSelfMember)
{
if(UEdGraphPin* Pin = Variable->FindPin(Variable->GetVarNameString()))
{
const FScopedTransaction Transaction( NSLOCTEXT("UnrealEd", "GraphEd_ReplaceVariable", "Replace Variable") );
Variable->Modify();
Pin->Modify();
if (bIsSelfMember)
{
FName VarName = FName(*VariableName);
FGuid Guid = FBlueprintEditorUtils::FindMemberVariableGuidByName(OwnerBlueprint, VarName);
Variable->VariableReference.SetSelfMember( VarName, Guid );
}
else
{
UEdGraph* FunctionGraph = FBlueprintEditorUtils::GetTopLevelGraph(Variable->GetGraph());
Variable->VariableReference.SetLocalMember( FName(*VariableName), FunctionGraph->GetName(), FBlueprintEditorUtils::FindLocalVariableGuidByName(OwnerBlueprint, FunctionGraph, *VariableName));
}
Pin->PinName = VariableName;
Variable->ReconstructNode();
FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(OwnerBlueprint);
}
}
void UEdGraphSchema_K2::GetReplaceVariableMenu(FMenuBuilder& MenuBuilder, UK2Node_Variable* Variable, UBlueprint* OwnerBlueprint, bool bReplaceExistingVariable/*=false*/)
{
if (UEdGraphPin* Pin = Variable->FindPin(Variable->GetVarNameString()))
{
FName ExistingVariableName = bReplaceExistingVariable? Variable->GetVarName() : NAME_None;
FText ReplaceVariableWithTooltipFormat;
if(!bReplaceExistingVariable)
{
ReplaceVariableWithTooltipFormat = LOCTEXT("ReplaceNonExistantVarToolTip", "Variable '{OldVariable}' does not exist, replace with matching variable '{AlternateVariable}'?");
}
else
{
ReplaceVariableWithTooltipFormat = LOCTEXT("ReplaceExistantVarToolTip", "Replace Variable '{OldVariable}' with matching variable '{AlternateVariable}'?");
}
TArray<FName> Variables;
FBlueprintEditorUtils::GetNewVariablesOfType(OwnerBlueprint, Pin->PinType, Variables);
MenuBuilder.BeginSection(NAME_None, LOCTEXT("Variables", "Variables"));
for (TArray<FName>::TIterator VarIt(Variables); VarIt; ++VarIt)
{
if(*VarIt != ExistingVariableName)
{
const FText AlternativeVar = FText::FromName( *VarIt );
FFormatNamedArguments TooltipArgs;
TooltipArgs.Add(TEXT("OldVariable"), Variable->GetVarNameText());
TooltipArgs.Add(TEXT("AlternateVariable"), AlternativeVar);
const FText Desc = FText::Format(ReplaceVariableWithTooltipFormat, TooltipArgs);
MenuBuilder.AddMenuEntry( AlternativeVar, Desc, FSlateIcon(), FUIAction(
FExecuteAction::CreateStatic(&UEdGraphSchema_K2::OnReplaceVariableForVariableNode, const_cast<UK2Node_Variable* >(Variable),OwnerBlueprint, (*VarIt).ToString(), /*bIsSelfMember=*/true ) ) );
}
}
MenuBuilder.EndSection();
FText ReplaceLocalVariableWithTooltipFormat;
if(!bReplaceExistingVariable)
{
ReplaceLocalVariableWithTooltipFormat = LOCTEXT("ReplaceNonExistantLocalVarToolTip", "Variable '{OldVariable}' does not exist, replace with matching local variable '{AlternateVariable}'?");
}
else
{
ReplaceLocalVariableWithTooltipFormat = LOCTEXT("ReplaceExistantLocalVarToolTip", "Replace Variable '{OldVariable}' with matching local variable '{AlternateVariable}'?");
}
TArray<FName> LocalVariables;
FBlueprintEditorUtils::GetLocalVariablesOfType(Variable->GetGraph(), Pin->PinType, LocalVariables);
MenuBuilder.BeginSection(NAME_None, LOCTEXT("LocalVariables", "LocalVariables"));
for (TArray<FName>::TIterator VarIt(LocalVariables); VarIt; ++VarIt)
{
if(*VarIt != ExistingVariableName)
{
const FText AlternativeVar = FText::FromName( *VarIt );
FFormatNamedArguments TooltipArgs;
TooltipArgs.Add(TEXT("OldVariable"), Variable->GetVarNameText());
TooltipArgs.Add(TEXT("AlternateVariable"), AlternativeVar);
const FText Desc = FText::Format( ReplaceLocalVariableWithTooltipFormat, TooltipArgs );
MenuBuilder.AddMenuEntry( AlternativeVar, Desc, FSlateIcon(), FUIAction(
FExecuteAction::CreateStatic(&UEdGraphSchema_K2::OnReplaceVariableForVariableNode, const_cast<UK2Node_Variable* >(Variable),OwnerBlueprint, (*VarIt).ToString(), /*bIsSelfMember=*/false ) ) );
}
}
MenuBuilder.EndSection();
}
}
void UEdGraphSchema_K2::GetNonExistentVariableMenu( const UEdGraphNode* InGraphNode, UBlueprint* OwnerBlueprint, FMenuBuilder* MenuBuilder ) const
{
if (const UK2Node_Variable* Variable = Cast<const UK2Node_Variable>(InGraphNode))
{
// Creating missing variables should never occur in a Macro Library or Interface, they do not support variables
if(OwnerBlueprint->BlueprintType != BPTYPE_MacroLibrary && OwnerBlueprint->BlueprintType != BPTYPE_Interface )
{
// Creating missing member variables should never occur in a Function Library, they do not support variables
if(OwnerBlueprint->BlueprintType != BPTYPE_FunctionLibrary)
{
// create missing variable
const FText Label = FText::Format( LOCTEXT("CreateNonExistentVar", "Create variable '{0}'"), Variable->GetVarNameText());
const FText Desc = FText::Format( LOCTEXT("CreateNonExistentVarToolTip", "Variable '{0}' does not exist, create it?"), Variable->GetVarNameText());
MenuBuilder->AddMenuEntry( Label, Desc, FSlateIcon(), FUIAction(
FExecuteAction::CreateStatic( &UEdGraphSchema_K2::OnCreateNonExistentVariable, const_cast<UK2Node_Variable* >(Variable),OwnerBlueprint) ) );
}
// Only allow creating missing local variables if in a function graph
if(InGraphNode->GetGraph()->GetSchema()->GetGraphType(InGraphNode->GetGraph()) == GT_Function)
{
const FText Label = FText::Format( LOCTEXT("CreateNonExistentLocalVar", "Create local variable '{0}'"), Variable->GetVarNameText());
const FText Desc = FText::Format( LOCTEXT("CreateNonExistentLocalVarToolTip", "Local variable '{0}' does not exist, create it?"), Variable->GetVarNameText());
MenuBuilder->AddMenuEntry( Label, Desc, FSlateIcon(), FUIAction(
FExecuteAction::CreateStatic( &UEdGraphSchema_K2::OnCreateNonExistentLocalVariable, const_cast<UK2Node_Variable* >(Variable),OwnerBlueprint) ) );
}
}
// delete this node
{
const FText Desc = FText::Format( LOCTEXT("DeleteNonExistentVarToolTip", "Referenced variable '{0}' does not exist, delete this node?"), Variable->GetVarNameText());
MenuBuilder->AddMenuEntry( FGenericCommands::Get().Delete, NAME_None, FGenericCommands::Get().Delete->GetLabel(), Desc);
}
GetReplaceVariableMenu(InGraphNode, OwnerBlueprint, MenuBuilder);
}
}
void UEdGraphSchema_K2::GetReplaceVariableMenu(const UEdGraphNode* InGraphNode, UBlueprint* InOwnerBlueprint, FMenuBuilder* InMenuBuilder, bool bInReplaceExistingVariable/* = false*/) const
{
if (const UK2Node_Variable* Variable = Cast<const UK2Node_Variable>(InGraphNode))
{
// replace with matching variables
if (UEdGraphPin* Pin = Variable->FindPin(Variable->GetVarNameString()))
{
FName ExistingVariableName = bInReplaceExistingVariable? Variable->GetVarName() : NAME_None;
TArray<FName> Variables;
FBlueprintEditorUtils::GetNewVariablesOfType(InOwnerBlueprint, Pin->PinType, Variables);
Variables.RemoveSwap(ExistingVariableName);
TArray<FName> LocalVariables;
FBlueprintEditorUtils::GetLocalVariablesOfType(Variable->GetGraph(), Pin->PinType, LocalVariables);
LocalVariables.RemoveSwap(ExistingVariableName);
if (Variables.Num() > 0 || LocalVariables.Num() > 0)
{
FText ReplaceVariableWithTooltip;
if(bInReplaceExistingVariable)
{
ReplaceVariableWithTooltip = LOCTEXT("ReplaceVariableWithToolTip", "Replace Variable '{0}' with another variable?");
}
else
{
ReplaceVariableWithTooltip = LOCTEXT("ReplaceMissingVariableWithToolTip", "Variable '{0}' does not exist, replace with another variable?");
}
InMenuBuilder->AddSubMenu(
FText::Format( LOCTEXT("ReplaceVariableWith", "Replace variable '{0}' with..."), Variable->GetVarNameText()),
FText::Format( ReplaceVariableWithTooltip, Variable->GetVarNameText()),
FNewMenuDelegate::CreateStatic( &UEdGraphSchema_K2::GetReplaceVariableMenu,
const_cast<UK2Node_Variable*>(Variable),InOwnerBlueprint, bInReplaceExistingVariable));
}
}
}
}
void UEdGraphSchema_K2::GetBreakLinkToSubMenuActions( class FMenuBuilder& MenuBuilder, UEdGraphPin* InGraphPin )
{
// Make sure we have a unique name for every entry in the list
TMap< FString, uint32 > LinkTitleCount;
// Add all the links we could break from
for(TArray<class UEdGraphPin*>::TConstIterator Links(InGraphPin->LinkedTo); Links; ++Links)
{
UEdGraphPin* Pin = *Links;
FText Title = Pin->GetOwningNode()->GetNodeTitle(ENodeTitleType::ListView);
FString TitleString = Title.ToString();
if ( Pin->PinName != TEXT("") )
{
TitleString = FString::Printf(TEXT("%s (%s)"), *TitleString, *Pin->GetDisplayName().ToString());
// Add name of connection if possible
FFormatNamedArguments Args;
Args.Add( TEXT("NodeTitle"), Title );
Args.Add( TEXT("PinName"), Pin->GetDisplayName() );
Title = FText::Format( LOCTEXT("BreakDescPin", "{NodeTitle} ({PinName})"), Args );
}
uint32 &Count = LinkTitleCount.FindOrAdd( TitleString );
FText Description;
FFormatNamedArguments Args;
Args.Add( TEXT("NodeTitle"), Title );
Args.Add( TEXT("NumberOfNodes"), Count );
if ( Count == 0 )
{
Description = FText::Format( LOCTEXT("BreakDesc", "Break link to {NodeTitle}"), Args );
}
else
{
Description = FText::Format( LOCTEXT("BreakDescMulti", "Break link to {NodeTitle} ({NumberOfNodes})"), Args );
}
++Count;
MenuBuilder.AddMenuEntry( Description, Description, FSlateIcon(), FUIAction(
FExecuteAction::CreateUObject(this, &UEdGraphSchema_K2::BreakSinglePinLink, const_cast< UEdGraphPin* >(InGraphPin), *Links)));
}
}
void UEdGraphSchema_K2::GetJumpToConnectionSubMenuActions( class FMenuBuilder& MenuBuilder, UEdGraphPin* InGraphPin )
{
// Make sure we have a unique name for every entry in the list
TMap< FString, uint32 > LinkTitleCount;
// Add all the links we could break from
for(const UEdGraphPin* PinLink : InGraphPin->LinkedTo )
{
FText Title = PinLink->GetOwningNode()->GetNodeTitle(ENodeTitleType::ListView);
FString TitleString = Title.ToString();
if ( PinLink->PinName != TEXT("") )
{
TitleString = FString::Printf(TEXT("%s (%s)"), *TitleString, *PinLink->GetDisplayName().ToString());
// Add name of connection if possible
FFormatNamedArguments Args;
Args.Add( TEXT("NodeTitle"), Title );
Args.Add( TEXT("PinName"), PinLink->GetDisplayName() );
Title = FText::Format( LOCTEXT("JumpToDescPin", "{NodeTitle} ({PinName})"), Args );
}
uint32 &Count = LinkTitleCount.FindOrAdd( TitleString );
FText Description;
FFormatNamedArguments Args;
Args.Add( TEXT("NodeTitle"), Title );
Args.Add( TEXT("NumberOfNodes"), Count );
if ( Count == 0 )
{
Description = FText::Format( LOCTEXT("JumpDesc", "Jump to {NodeTitle}"), Args );
}
else
{
Description = FText::Format( LOCTEXT("JumpDescMulti", "Jump to {NodeTitle} ({NumberOfNodes})"), Args );
}
++Count;
MenuBuilder.AddMenuEntry( Description, Description, FSlateIcon(), FUIAction(
FExecuteAction::CreateStatic(&FKismetEditorUtilities::BringKismetToFocusAttentionOnPin, PinLink)));
}
}
// todo: this is a long way off ideal, but we can't pass context from our menu items onto the graph panel implementation
// It'd be better to be able to pass context through to menu/ui commands
namespace { UEdGraphPin* StraightenDestinationPin = nullptr; }
UEdGraphPin* UEdGraphSchema_K2::GetAndResetStraightenDestinationPin()
{
UEdGraphPin* Temp = StraightenDestinationPin;
StraightenDestinationPin = nullptr;
return Temp;
}
void UEdGraphSchema_K2::GetStraightenConnectionToSubMenuActions( class FMenuBuilder& MenuBuilder, UEdGraphPin* InGraphPin ) const
{
auto MenuCommandList = MenuBuilder.GetTopCommandList();
if(!ensure(MenuCommandList.IsValid()))
{
return;
}
// Make sure we have a unique name for every entry in the list
TMap<FString, uint32> LinkTitleCount;
TMap<UEdGraphNode*, TArray<UEdGraphPin*>> NodeToPins;
for (UEdGraphPin* Pin : InGraphPin->LinkedTo)
{
UEdGraphNode* Node = Pin->GetOwningNode();
if (Node)
{
NodeToPins.FindOrAdd(Node).Add(Pin);
}
}
MenuBuilder.AddMenuEntry( FGraphEditorCommands::Get().StraightenConnections,
NAME_None, LOCTEXT("StraightenAllConnections", "All Connected Pins"),
TAttribute<FText>(), FSlateIcon(NAME_None, NAME_None, NAME_None) );
for (auto& Pair : NodeToPins)
{
FText NodeName = Pair.Key->GetNodeTitle(ENodeTitleType::ListView);
for (UEdGraphPin* Pin : Pair.Value)
{
FText PinName = Pin->GetDisplayName();
MenuBuilder.AddMenuEntry(
FText::Format(LOCTEXT("StraightenDescription_Node", "{0} ({1})"), NodeName, Pin->GetDisplayName()),
FText::Format(LOCTEXT("StraightenDescription_Node_Tip", "Straighten the connection between this pin, and {0} ({1})"), NodeName, Pin->GetDisplayName()),
FSlateIcon(),
FExecuteAction::CreateLambda([=]{
if (const FUIAction* UIAction = MenuCommandList->GetActionForCommand(FGraphEditorCommands::Get().StraightenConnections))
{
StraightenDestinationPin = Pin;
UIAction->ExecuteAction.Execute();
}
}));
}
}
}
const FPinConnectionResponse UEdGraphSchema_K2::DetermineConnectionResponseOfCompatibleTypedPins(const UEdGraphPin* PinA, const UEdGraphPin* PinB, const UEdGraphPin* InputPin, const UEdGraphPin* OutputPin) const
{
// Now check to see if there are already connections and this is an 'exclusive' connection
const bool bBreakExistingDueToExecOutput = IsExecPin(*OutputPin) && (OutputPin->LinkedTo.Num() > 0);
const bool bBreakExistingDueToDataInput = !IsExecPin(*InputPin) && (InputPin->LinkedTo.Num() > 0);
bool bMultipleSelfException = false;
const UK2Node* OwningNode = Cast<UK2Node>(InputPin->GetOwningNode());
if (bBreakExistingDueToDataInput &&
IsSelfPin(*InputPin) &&
OwningNode &&
OwningNode->AllowMultipleSelfs(false) &&
!InputPin->PinType.bIsArray &&
!OutputPin->PinType.bIsArray)
{
//check if the node wont be expanded as foreach call, if there is a link to an array
bool bAnyArrayInput = false;
for(int InputLinkIndex = 0; InputLinkIndex < InputPin->LinkedTo.Num(); InputLinkIndex++)
{
if(const UEdGraphPin* Pin = InputPin->LinkedTo[InputLinkIndex])
{
if(Pin->PinType.bIsArray)
{
bAnyArrayInput = true;
break;
}
}
}
bMultipleSelfException = !bAnyArrayInput;
}
if (bBreakExistingDueToExecOutput)
{
const ECanCreateConnectionResponse ReplyBreakOutputs = (PinA == OutputPin) ? CONNECT_RESPONSE_BREAK_OTHERS_A : CONNECT_RESPONSE_BREAK_OTHERS_B;
return FPinConnectionResponse(ReplyBreakOutputs, TEXT("Replace existing output connections"));
}
else if (bBreakExistingDueToDataInput && !bMultipleSelfException)
{
const ECanCreateConnectionResponse ReplyBreakInputs = (PinA == InputPin) ? CONNECT_RESPONSE_BREAK_OTHERS_A : CONNECT_RESPONSE_BREAK_OTHERS_B;
return FPinConnectionResponse(ReplyBreakInputs, TEXT("Replace existing input connections"));
}
else
{
return FPinConnectionResponse(CONNECT_RESPONSE_MAKE, TEXT(""));
}
}
static FText GetPinIncompatibilityReason(const UEdGraphPin* PinA, const UEdGraphPin* PinB, bool* bIsFatalOut = nullptr)
{
const FEdGraphPinType& PinAType = PinA->PinType;
const FEdGraphPinType& PinBType = PinB->PinType;
FFormatNamedArguments MessageArgs;
MessageArgs.Add(TEXT("PinAName"), PinA->GetDisplayName());
MessageArgs.Add(TEXT("PinBName"), PinB->GetDisplayName());
MessageArgs.Add(TEXT("PinAType"), UEdGraphSchema_K2::TypeToText(PinAType));
MessageArgs.Add(TEXT("PinBType"), UEdGraphSchema_K2::TypeToText(PinBType));
const UEdGraphPin* InputPin = (PinA->Direction == EGPD_Input) ? PinA : PinB;
const FEdGraphPinType& InputType = InputPin->PinType;
const UEdGraphPin* OutputPin = (InputPin == PinA) ? PinB : PinA;
const FEdGraphPinType& OutputType = OutputPin->PinType;
FText MessageFormat = LOCTEXT("DefaultPinIncompatibilityMessage", "{PinAType} is not compatible with {PinBType}.");
if (OutputType.PinCategory == UEdGraphSchema_K2::PC_Struct)
{
if (InputType.PinCategory == UEdGraphSchema_K2::PC_Struct)
{
MessageFormat = LOCTEXT("StructsIncompatible", "Only exactly matching structures are considered compatible.");
const UStruct* OutStruct = Cast<const UStruct>(OutputType.PinSubCategoryObject.Get());
const UStruct* InStruct = Cast<const UStruct>(InputType.PinSubCategoryObject.Get());
if ((OutStruct != nullptr) && (InStruct != nullptr) && OutStruct->IsChildOf(InStruct))
{
MessageFormat = LOCTEXT("ChildStructIncompatible", "Only exactly matching structures are considered compatible. Derived structures are disallowed.");
}
if (bIsFatalOut != nullptr)
{
*bIsFatalOut = true;
}
}
}
else if (OutputType.PinCategory == UEdGraphSchema_K2::PC_Class)
{
if ((InputType.PinCategory == UEdGraphSchema_K2::PC_Object) ||
(InputType.PinCategory == UEdGraphSchema_K2::PC_Interface))
{
MessageArgs.Add(TEXT("OutputName"), OutputPin->GetDisplayName());
MessageArgs.Add(TEXT("InputName"), InputPin->GetDisplayName());
MessageFormat = LOCTEXT("ClassObjectIncompatible", "'{PinAName}' and '{PinBName}' are incompatible ('{OutputName}' is an object type, and '{InputName}' is a reference to an object instance).");
}
}
else if ((OutputType.PinCategory == UEdGraphSchema_K2::PC_Object) )//|| (OutputType.PinCategory == UEdGraphSchema_K2::PC_Interface))
{
if (InputType.PinCategory == UEdGraphSchema_K2::PC_Class)
{
MessageArgs.Add(TEXT("OutputName"), OutputPin->GetDisplayName());
MessageArgs.Add(TEXT("InputName"), InputPin->GetDisplayName());
MessageArgs.Add(TEXT("InputType"), UEdGraphSchema_K2::TypeToText(InputType));
MessageFormat = LOCTEXT("CannotGetClass", "'{PinAName}' and '{PinBName}' are not inherently compatible ('{InputName}' is an object type, and '{OutputName}' is a reference to an object instance).\nWe cannot use {OutputName}'s class because it is not a child of {InputType}.");
}
}
return FText::Format(MessageFormat, MessageArgs);
}
const FPinConnectionResponse UEdGraphSchema_K2::CanCreateConnection(const UEdGraphPin* PinA, const UEdGraphPin* PinB) const
{
const UK2Node* OwningNodeA = Cast<UK2Node>(PinA->GetOwningNodeUnchecked());
const UK2Node* OwningNodeB = Cast<UK2Node>(PinB->GetOwningNodeUnchecked());
if (!OwningNodeA || !OwningNodeB)
{
return FPinConnectionResponse(CONNECT_RESPONSE_DISALLOW, TEXT("Invalid nodes"));
}
// Make sure the pins are not on the same node
if (OwningNodeA == OwningNodeB)
{
return FPinConnectionResponse(CONNECT_RESPONSE_DISALLOW, TEXT("Both are on the same node"));
}
// node can disallow the connection
{
FString RespondMessage;
if(OwningNodeA && OwningNodeA->IsConnectionDisallowed(PinA, PinB, RespondMessage))
{
return FPinConnectionResponse(CONNECT_RESPONSE_DISALLOW, RespondMessage);
}
if(OwningNodeB && OwningNodeB->IsConnectionDisallowed(PinB, PinA, RespondMessage))
{
return FPinConnectionResponse(CONNECT_RESPONSE_DISALLOW, RespondMessage);
}
}
// Compare the directions
const UEdGraphPin* InputPin = NULL;
const UEdGraphPin* OutputPin = NULL;
if (!CategorizePinsByDirection(PinA, PinB, /*out*/ InputPin, /*out*/ OutputPin))
{
return FPinConnectionResponse(CONNECT_RESPONSE_DISALLOW, TEXT("Directions are not compatible"));
}
bool bIgnoreArray = false;
if(const UK2Node* OwningNode = Cast<UK2Node>(InputPin->GetOwningNode()))
{
const bool bAllowMultipleSelfs = OwningNode->AllowMultipleSelfs(true); // it applies also to ForEachCall
const bool bNotAnArrayFunction = !InputPin->PinType.bIsArray;
const bool bSelfPin = IsSelfPin(*InputPin);
bIgnoreArray = bAllowMultipleSelfs && bNotAnArrayFunction && bSelfPin;
}
// Find the calling context in case one of the pins is of type object and has a value of Self
UClass* CallingContext = NULL;
const UBlueprint* Blueprint = FBlueprintEditorUtils::FindBlueprintForNode(PinA->GetOwningNodeUnchecked());
if (Blueprint)
{
CallingContext = (Blueprint->GeneratedClass != NULL) ? Blueprint->GeneratedClass : Blueprint->ParentClass;
}
// Compare the types
const bool bTypesMatch = ArePinsCompatible(OutputPin, InputPin, CallingContext, bIgnoreArray);
if (bTypesMatch)
{
return DetermineConnectionResponseOfCompatibleTypedPins(PinA, PinB, InputPin, OutputPin);
}
else
{
// Autocasting
FName DummyName;
UClass* DummyClass;
UK2Node* DummyNode;
const bool bCanAutocast = SearchForAutocastFunction(OutputPin, InputPin, /*out*/ DummyName, DummyClass);
const bool bCanAutoConvert = FindSpecializedConversionNode(OutputPin, InputPin, false, /* out */ DummyNode);
if (bCanAutocast || bCanAutoConvert)
{
return FPinConnectionResponse(CONNECT_RESPONSE_MAKE_WITH_CONVERSION_NODE, FString::Printf(TEXT("Convert %s to %s"), *TypeToText(OutputPin->PinType).ToString(), *TypeToText(InputPin->PinType).ToString()));
}
else
{
bool bIsFatal = false;
FText IncompatibilityReasonText = GetPinIncompatibilityReason(PinA, PinB, &bIsFatal);
FPinConnectionResponse ConnectionResponse(CONNECT_RESPONSE_DISALLOW, IncompatibilityReasonText.ToString());
if (bIsFatal)
{
ConnectionResponse.SetFatal();
}
return ConnectionResponse;
}
}
}
bool UEdGraphSchema_K2::TryCreateConnection(UEdGraphPin* PinA, UEdGraphPin* PinB) const
{
UBlueprint* Blueprint = FBlueprintEditorUtils::FindBlueprintForNodeChecked(PinA->GetOwningNode());
bool bModified = UEdGraphSchema::TryCreateConnection(PinA, PinB);
if (bModified && !PinA->IsPendingKill())
{
FBlueprintEditorUtils::MarkBlueprintAsModified(Blueprint);
}
return bModified;
}
struct FAutocastFunctionMap : private FNoncopyable
{
private:
static FAutocastFunctionMap* AutocastFunctionMap;
TMap<FString, TWeakObjectPtr<UFunction>> InnerMap;
FDelegateHandle OnHotReloadDelegateHandle;
FDelegateHandle OnModulesChangedDelegateHandle;
static FString GenerateTypeData(const FEdGraphPinType& PinType)
{
auto Obj = PinType.PinSubCategoryObject.Get();
FString PinSubCategory = PinType.PinSubCategory;
if (PinSubCategory.StartsWith(UEdGraphSchema_K2::PSC_Bitmask))
{
// Exclude the bitmask subcategory string from integral types so that autocast will work.
PinSubCategory = TEXT("");
}
return FString::Printf(TEXT("%s;%s;%s"), *PinType.PinCategory, *PinSubCategory, Obj ? *Obj->GetPathName() : TEXT(""));
}
static FString GenerateCastData(const FEdGraphPinType& InputPinType, const FEdGraphPinType& OutputPinType)
{
return FString::Printf(TEXT("%s;%s"), *GenerateTypeData(InputPinType), *GenerateTypeData(OutputPinType));
}
static bool IsInputParam(uint64 PropertyFlags)
{
const uint64 ConstOutParamFlag = CPF_OutParm | CPF_ConstParm;
const uint64 IsConstOut = PropertyFlags & ConstOutParamFlag;
return (CPF_Parm == (PropertyFlags & (CPF_Parm | CPF_ReturnParm)))
&& ((0 == IsConstOut) || (ConstOutParamFlag == IsConstOut));
}
static const UProperty* GetFirstInputProperty(const UFunction* Function)
{
for (auto Property : TFieldRange<const UProperty>(Function))
{
if (Property && IsInputParam(Property->PropertyFlags))
{
return Property;
}
}
return nullptr;
}
void InsertFunction(UFunction* Function, const UEdGraphSchema_K2* Schema)
{
FEdGraphPinType InputPinType;
Schema->ConvertPropertyToPinType(GetFirstInputProperty(Function), InputPinType);
FEdGraphPinType OutputPinType;
Schema->ConvertPropertyToPinType(Function->GetReturnProperty(), OutputPinType);
InnerMap.Add(GenerateCastData(InputPinType, OutputPinType), Function);
}
public:
static bool IsAutocastFunction(const UFunction* Function)
{
const FName BlueprintAutocast(TEXT("BlueprintAutocast"));
return Function
&& Function->HasMetaData(BlueprintAutocast)
&& Function->HasAllFunctionFlags(FUNC_Static | FUNC_Native | FUNC_Public | FUNC_BlueprintPure)
&& Function->GetReturnProperty()
&& GetFirstInputProperty(Function);
}
void Refresh()
{
#ifdef SCHEMA_K2_AUTOCASTFUNCTIONMAP_LOG_TIME
static_assert(false, "Macro redefinition.");
#endif
#define SCHEMA_K2_AUTOCASTFUNCTIONMAP_LOG_TIME 0
#if SCHEMA_K2_AUTOCASTFUNCTIONMAP_LOG_TIME
const auto StartTime = FPlatformTime::Seconds();
#endif //SCHEMA_K2_AUTOCASTFUNCTIONMAP_LOG_TIME
InnerMap.Empty();
auto Schema = GetDefault<UEdGraphSchema_K2>();
TArray<UClass*> Libraries;
GetDerivedClasses(UBlueprintFunctionLibrary::StaticClass(), Libraries);
for (auto Library : Libraries)
{
if (Library && (CLASS_Native == (Library->ClassFlags & (CLASS_Native | CLASS_Deprecated | CLASS_NewerVersionExists))))
{
for (auto Function : TFieldRange<UFunction>(Library, EFieldIteratorFlags::ExcludeSuper, EFieldIteratorFlags::ExcludeDeprecated))
{
if (IsAutocastFunction(Function))
{
InsertFunction(Function, Schema);
}
}
}
}
#if SCHEMA_K2_AUTOCASTFUNCTIONMAP_LOG_TIME
const auto EndTime = FPlatformTime::Seconds();
UE_LOG(LogBlueprint, Warning, TEXT("FAutocastFunctionMap::Refresh took %fs"), EndTime - StartTime);
#endif //SCHEMA_K2_AUTOCASTFUNCTIONMAP_LOG_TIME
#undef SCHEMA_K2_AUTOCASTFUNCTIONMAP_LOG_TIME
}
UFunction* Find(const FEdGraphPinType& InputPinType, const FEdGraphPinType& OutputPinType) const
{
const TWeakObjectPtr<UFunction>* FuncPtr = InnerMap.Find(GenerateCastData(InputPinType, OutputPinType));
return FuncPtr ? FuncPtr->Get() : nullptr;
}
static FAutocastFunctionMap& Get()
{
if (AutocastFunctionMap == nullptr)
{
AutocastFunctionMap = new FAutocastFunctionMap();
}
return *AutocastFunctionMap;
}
static void OnProjectHotReloaded(bool bWasTriggeredAutomatically)
{
if (AutocastFunctionMap)
{
AutocastFunctionMap->Refresh();
}
}
static void OnModulesChanged(FName ModuleThatChanged, EModuleChangeReason ReasonForChange)
{
if (AutocastFunctionMap)
{
AutocastFunctionMap->Refresh();
}
}
FAutocastFunctionMap()
{
Refresh();
IHotReloadInterface& HotReloadSupport = FModuleManager::LoadModuleChecked<IHotReloadInterface>("HotReload");
OnHotReloadDelegateHandle = HotReloadSupport.OnHotReload().AddStatic(&FAutocastFunctionMap::OnProjectHotReloaded);
OnModulesChangedDelegateHandle = FModuleManager::Get().OnModulesChanged().AddStatic(&OnModulesChanged);
}
~FAutocastFunctionMap()
{
if (auto HotReloadSupport = FModuleManager::GetModulePtr<IHotReloadInterface>("HotReload"))
{
HotReloadSupport->OnHotReload().Remove(OnHotReloadDelegateHandle);
}
FModuleManager::Get().OnModulesChanged().Remove(OnModulesChangedDelegateHandle);
}
};
FAutocastFunctionMap* FAutocastFunctionMap::AutocastFunctionMap = nullptr;
bool UEdGraphSchema_K2::SearchForAutocastFunction(const UEdGraphPin* OutputPin, const UEdGraphPin* InputPin, /*out*/ FName& TargetFunction, /*out*/ UClass*& FunctionOwner) const
{
// NOTE: Under no circumstances should anyone *ever* add a questionable cast to this function.
// If it could be at all confusing why a function is provided, to even a novice user, err on the side of do not cast!!!
// This includes things like string->int (does it do length, atoi, or what?) that would be autocasts in a traditional scripting language
TargetFunction = NAME_None;
FunctionOwner = nullptr;
if (OutputPin->PinType.bIsArray != InputPin->PinType.bIsArray)
{
return false;
}
// SPECIAL CASES, not supported by FAutocastFunctionMap
if ((OutputPin->PinType.PinCategory == PC_Interface) && (InputPin->PinType.PinCategory == PC_Object))
{
UClass const* InputClass = Cast<UClass const>(InputPin->PinType.PinSubCategoryObject.Get());
bool const bInputIsUObject = ((InputClass != NULL) && (InputClass == UObject::StaticClass()));
if (bInputIsUObject)
{
UFunction* Function = UKismetSystemLibrary::StaticClass()->FindFunctionByName(GET_MEMBER_NAME_CHECKED(UKismetSystemLibrary, Conv_InterfaceToObject));
TargetFunction = Function->GetFName();
FunctionOwner = Function->GetOwnerClass();
}
}
else if (OutputPin->PinType.PinCategory == PC_Object)
{
UClass const* OutputClass = Cast<UClass const>(OutputPin->PinType.PinSubCategoryObject.Get());
if (InputPin->PinType.PinCategory == PC_Class)
{
UClass const* InputClass = Cast<UClass const>(InputPin->PinType.PinSubCategoryObject.Get());
if ((OutputClass != nullptr) &&
(InputClass != nullptr) &&
OutputClass->IsChildOf(InputClass))
{
UFunction* Function = UGameplayStatics::StaticClass()->FindFunctionByName(GET_MEMBER_NAME_CHECKED(UGameplayStatics, GetObjectClass));
TargetFunction = Function->GetFName();
FunctionOwner = Function->GetOwnerClass();
}
}
else if (InputPin->PinType.PinCategory == PC_String)
{
UFunction* Function = UKismetSystemLibrary::StaticClass()->FindFunctionByName(GET_MEMBER_NAME_CHECKED(UKismetSystemLibrary, GetDisplayName));
TargetFunction = Function->GetFName();
FunctionOwner = Function->GetOwnerClass();
}
}
else if (OutputPin->PinType.PinCategory == PC_Struct)
{
const UScriptStruct* OutputStructType = Cast<const UScriptStruct>(OutputPin->PinType.PinSubCategoryObject.Get());
if (OutputStructType == TBaseStructure<FRotator>::Get())
{
const UScriptStruct* InputStructType = Cast<const UScriptStruct>(InputPin->PinType.PinSubCategoryObject.Get());
if ((InputPin->PinType.PinCategory == PC_Struct) && (InputStructType == TBaseStructure<FTransform>::Get()))
{
UFunction* Function = UKismetMathLibrary::StaticClass()->FindFunctionByName(GET_MEMBER_NAME_CHECKED(UKismetMathLibrary, MakeTransform));
TargetFunction = Function->GetFName();
FunctionOwner = Function->GetOwnerClass();
}
}
}
if (TargetFunction == NAME_None)
{
const auto& AutocastFunctionMap = FAutocastFunctionMap::Get();
if (auto Func = AutocastFunctionMap.Find(OutputPin->PinType, InputPin->PinType))
{
TargetFunction = Func->GetFName();
FunctionOwner = Func->GetOwnerClass();
}
}
return TargetFunction != NAME_None;
}
bool UEdGraphSchema_K2::FindSpecializedConversionNode(const UEdGraphPin* OutputPin, const UEdGraphPin* InputPin, bool bCreateNode, /*out*/ UK2Node*& TargetNode) const
{
bool bCanConvert = false;
TargetNode = NULL;
// Conversion for scalar -> array
if( (!OutputPin->PinType.bIsArray && InputPin->PinType.bIsArray) && ArePinTypesCompatible(OutputPin->PinType, InputPin->PinType, NULL, true) )
{
bCanConvert = true;
if(bCreateNode)
{
TargetNode = NewObject<UK2Node_MakeArray>();
}
}
// If connecting an object to a 'call function' self pin, and not currently compatible, see if there is a property we can call a function on
else if (InputPin->GetOwningNode()->IsA(UK2Node_CallFunction::StaticClass()) && IsSelfPin(*InputPin) &&
((OutputPin->PinType.PinCategory == PC_Object) || (OutputPin->PinType.PinCategory == PC_Interface)))
{
UK2Node_CallFunction* CallFunctionNode = (UK2Node_CallFunction*)(InputPin->GetOwningNode());
UClass* OutputPinClass = Cast<UClass>(OutputPin->PinType.PinSubCategoryObject.Get());
UClass* FunctionClass = CallFunctionNode->FunctionReference.GetMemberParentClass(CallFunctionNode->GetBlueprintClassFromNode());
if(FunctionClass != NULL && OutputPinClass != NULL)
{
// Iterate over object properties..
for (TFieldIterator<UObjectProperty> PropIt(OutputPinClass, EFieldIteratorFlags::IncludeSuper); PropIt; ++PropIt)
{
UObjectProperty* ObjProp = *PropIt;
// .. if we have a blueprint visible var, and is of the type which contains this function..
if(ObjProp->HasAllPropertyFlags(CPF_BlueprintVisible) && ObjProp->PropertyClass->IsChildOf(FunctionClass))
{
// say we can convert
bCanConvert = true;
// Create 'get variable' node
if(bCreateNode)
{
UK2Node_VariableGet* GetNode = NewObject<UK2Node_VariableGet>();
GetNode->VariableReference.SetFromField<UProperty>(ObjProp, false);
TargetNode = GetNode;
}
}
}
}
}
if(!bCanConvert)
{
// CHECK ENUM TO NAME CAST
const bool bInoputMatch = InputPin && !InputPin->PinType.bIsArray && ((PC_Name == InputPin->PinType.PinCategory) || (PC_String == InputPin->PinType.PinCategory));
const bool bOutputMatch = OutputPin && !OutputPin->PinType.bIsArray && (PC_Byte == OutputPin->PinType.PinCategory) && (NULL != Cast<UEnum>(OutputPin->PinType.PinSubCategoryObject.Get()));
if(bOutputMatch && bInoputMatch)
{
bCanConvert = true;
if(bCreateNode)
{
check(NULL == TargetNode);
if(PC_Name == InputPin->PinType.PinCategory)
{
TargetNode = NewObject<UK2Node_GetEnumeratorName>();
}
else if(PC_String == InputPin->PinType.PinCategory)
{
TargetNode = NewObject<UK2Node_GetEnumeratorNameAsString>();
}
}
}
}
if (!bCanConvert && InputPin && OutputPin)
{
FEdGraphPinType const& InputType = InputPin->PinType;
FEdGraphPinType const& OutputType = OutputPin->PinType;
// CHECK BYTE TO ENUM CAST
UEnum* Enum = Cast<UEnum>(InputType.PinSubCategoryObject.Get());
const bool bInputIsEnum = !InputType.bIsArray && (PC_Byte == InputType.PinCategory) && Enum;
const bool bOutputIsByte = !OutputType.bIsArray && (PC_Byte == OutputType.PinCategory);
if (bInputIsEnum && bOutputIsByte)
{
bCanConvert = true;
if(bCreateNode)
{
auto CastByteToEnum = NewObject<UK2Node_CastByteToEnum>();
CastByteToEnum->Enum = Enum;
CastByteToEnum->bSafe = true;
TargetNode = CastByteToEnum;
}
}
else
{
UClass* InputClass = Cast<UClass>(InputType.PinSubCategoryObject.Get());
UClass* OutputClass = Cast<UClass>(OutputType.PinSubCategoryObject.Get());
if ((OutputType.PinCategory == PC_Interface) && (InputType.PinCategory == PC_Object))
{
bCanConvert = (InputClass && OutputClass) && (InputClass->ImplementsInterface(OutputClass) || OutputClass->IsChildOf(InputClass));
}
else if (OutputType.PinCategory == PC_Object)
{
UBlueprintEditorSettings const* BlueprintSettings = GetDefault<UBlueprintEditorSettings>();
if ((InputType.PinCategory == PC_Object) && BlueprintSettings->bAutoCastObjectConnections)
{
bCanConvert = (InputClass && OutputClass) && InputClass->IsChildOf(OutputClass);
}
}
if (bCanConvert && bCreateNode)
{
UK2Node_DynamicCast* DynCastNode = NewObject<UK2Node_DynamicCast>();
DynCastNode->TargetType = InputClass;
DynCastNode->SetPurity(true);
TargetNode = DynCastNode;
}
if (!bCanConvert && InputClass && OutputClass && OutputClass->IsChildOf(InputClass))
{
const bool bConvertAsset = (OutputType.PinCategory == PC_Asset) && (InputType.PinCategory == PC_Object);
const bool bConvertAssetClass = (OutputType.PinCategory == PC_AssetClass) && (InputType.PinCategory == PC_Class);
if (bConvertAsset || bConvertAssetClass)
{
bCanConvert = true;
if (bCreateNode)
{
UK2Node_ConvertAsset* ConvertAssetNode = NewObject<UK2Node_ConvertAsset>();
TargetNode = ConvertAssetNode;
}
}
}
}
}
return bCanConvert;
}
void UEdGraphSchema_K2::AutowireConversionNode(UEdGraphPin* InputPin, UEdGraphPin* OutputPin, UEdGraphNode* ConversionNode) const
{
bool bAllowInputConnections = true;
bool bAllowOutputConnections = true;
for (int32 PinIndex = 0; PinIndex < ConversionNode->Pins.Num(); ++PinIndex)
{
UEdGraphPin* TestPin = ConversionNode->Pins[PinIndex];
UClass* Context = nullptr;
UK2Node* K2Node = Cast<UK2Node>(OutputPin->GetOwningNode());
if (K2Node != nullptr)
{
UBlueprint* Blueprint = K2Node->GetBlueprint();
if (Blueprint)
{
Context = Blueprint->GeneratedClass;
}
}
if ((TestPin->Direction == EGPD_Input) && (ArePinTypesCompatible(OutputPin->PinType, TestPin->PinType, Context)))
{
if(bAllowOutputConnections && TryCreateConnection(TestPin, OutputPin))
{
// Successful connection, do not allow more output connections
bAllowOutputConnections = false;
}
}
else if ((TestPin->Direction == EGPD_Output) && (ArePinTypesCompatible(TestPin->PinType, InputPin->PinType, Context)))
{
if(bAllowInputConnections && TryCreateConnection(TestPin, InputPin))
{
// Successful connection, do not allow more input connections
bAllowInputConnections = false;
}
}
}
}
bool UEdGraphSchema_K2::CreateAutomaticConversionNodeAndConnections(UEdGraphPin* PinA, UEdGraphPin* PinB) const
{
// Determine which pin is an input and which pin is an output
UEdGraphPin* InputPin = NULL;
UEdGraphPin* OutputPin = NULL;
if (!CategorizePinsByDirection(PinA, PinB, /*out*/ InputPin, /*out*/ OutputPin))
{
return false;
}
FName TargetFunctionName;
UClass* ClassContainingConversionFunction = nullptr;
TSubclassOf<UK2Node> ConversionNodeClass;
UK2Node* TemplateConversionNode = NULL;
if (SearchForAutocastFunction(OutputPin, InputPin, /*out*/ TargetFunctionName, /*out*/ClassContainingConversionFunction))
{
// Create a new call function node for the casting operator
UK2Node_CallFunction* TemplateNode = NewObject<UK2Node_CallFunction>();
TemplateNode->FunctionReference.SetExternalMember(TargetFunctionName, ClassContainingConversionFunction);
//TemplateNode->bIsBeadFunction = true;
TemplateConversionNode = TemplateNode;
}
else
{
FindSpecializedConversionNode(OutputPin, InputPin, true, /*out*/ TemplateConversionNode);
}
if (TemplateConversionNode != NULL)
{
// Determine where to position the new node (assuming it isn't going to get beaded)
FVector2D AverageLocation = CalculateAveragePositionBetweenNodes(InputPin, OutputPin);
UK2Node* ConversionNode = FEdGraphSchemaAction_K2NewNode::SpawnNodeFromTemplate<UK2Node>(InputPin->GetOwningNode()->GetGraph(), TemplateConversionNode, AverageLocation);
// Connect the cast node up to the output/input pins
AutowireConversionNode(InputPin, OutputPin, ConversionNode);
return true;
}
return false;
}
FString UEdGraphSchema_K2::IsPinDefaultValid(const UEdGraphPin* Pin, const FString& NewDefaultValue, UObject* NewDefaultObject, const FText& InNewDefaultText) const
{
check(Pin);
FFormatNamedArguments MessageArgs;
MessageArgs.Add(TEXT("PinName"), Pin->GetDisplayName());
const UBlueprint* OwningBP = FBlueprintEditorUtils::FindBlueprintForNode(Pin->GetOwningNodeUnchecked());
if (!OwningBP)
{
FText MsgFormat = LOCTEXT("NoBlueprintFoundForPin", "No Blueprint was found for the pin '{PinName}'.");
return FText::Format(MsgFormat, MessageArgs).ToString();
}
const bool bIsArray = Pin->PinType.bIsArray;
const bool bIsReference = Pin->PinType.bIsReference;
const bool bIsAutoCreateRefTerm = IsAutoCreateRefTerm(Pin);
if (OwningBP->BlueprintType != BPTYPE_Interface)
{
if( !bIsAutoCreateRefTerm )
{
if( bIsArray )
{
FText MsgFormat = LOCTEXT("BadArrayDefaultVal", "Array inputs (like '{PinName}') must have an input wired into them (try connecting a MakeArray node).");
return FText::Format(MsgFormat, MessageArgs).ToString();
}
else if( bIsReference )
{
FText MsgFormat = LOCTEXT("BadRefDefaultVal", "'{PinName}' must have an input wired into it (\"by ref\" params expect a valid input to operate on).");
return FText::Format(MsgFormat, MessageArgs).ToString();
}
}
}
FString ReturnMsg;
DefaultValueSimpleValidation(Pin->PinType, Pin->PinName, NewDefaultValue, NewDefaultObject, InNewDefaultText, &ReturnMsg);
return ReturnMsg;
}
bool UEdGraphSchema_K2::DoesSupportPinWatching() const
{
return true;
}
bool UEdGraphSchema_K2::IsPinBeingWatched(UEdGraphPin const* Pin) const
{
// Note: If you crash here; it is likely that you forgot to call Blueprint->OnBlueprintChanged.Broadcast(Blueprint) to invalidate the cached UI state
UBlueprint* Blueprint = FBlueprintEditorUtils::FindBlueprintForNode(Pin ? Pin->GetOwningNodeUnchecked() : nullptr);
return ensure(Blueprint) ? FKismetDebugUtilities::IsPinBeingWatched(Blueprint, Pin) : false;
}
void UEdGraphSchema_K2::ClearPinWatch(UEdGraphPin const* Pin) const
{
UBlueprint* Blueprint = FBlueprintEditorUtils::FindBlueprintForNodeChecked(Pin->GetOwningNode());
FKismetDebugUtilities::RemovePinWatch(Blueprint, Pin);
}
FLinearColor UEdGraphSchema_K2::GetPinTypeColor(const FEdGraphPinType& PinType) const
{
const FString& TypeString = PinType.PinCategory;
const UGraphEditorSettings* Settings = GetDefault<UGraphEditorSettings>();
if (TypeString == PC_Exec)
{
return Settings->ExecutionPinTypeColor;
}
else if (TypeString == PC_Object)
{
return Settings->ObjectPinTypeColor;
}
else if (TypeString == PC_Interface)
{
return Settings->InterfacePinTypeColor;
}
else if (TypeString == PC_Float)
{
return Settings->FloatPinTypeColor;
}
else if (TypeString == PC_Boolean)
{
return Settings->BooleanPinTypeColor;
}
else if (TypeString == PC_Byte)
{
return Settings->BytePinTypeColor;
}
else if (TypeString == PC_Int)
{
return Settings->IntPinTypeColor;
}
else if (TypeString == PC_Struct)
{
if (PinType.PinSubCategoryObject == VectorStruct)
{
// vector
return Settings->VectorPinTypeColor;
}
else if (PinType.PinSubCategoryObject == RotatorStruct)
{
// rotator
return Settings->RotatorPinTypeColor;
}
else if (PinType.PinSubCategoryObject == TransformStruct)
{
// transform
return Settings->TransformPinTypeColor;
}
else
{
return Settings->StructPinTypeColor;
}
}
else if (TypeString == PC_String)
{
return Settings->StringPinTypeColor;
}
else if (TypeString == PC_Text)
{
return Settings->TextPinTypeColor;
}
else if (TypeString == PC_Wildcard)
{
if (PinType.PinSubCategory == PSC_Index)
{
return Settings->IndexPinTypeColor;
}
else
{
return Settings->WildcardPinTypeColor;
}
}
else if (TypeString == PC_Name)
{
return Settings->NamePinTypeColor;
}
else if (TypeString == PC_Asset)
{
return Settings->AssetPinTypeColor;
}
else if (TypeString == PC_AssetClass)
{
return Settings->AssetClassPinTypeColor;
}
else if (TypeString == PC_Delegate)
{
return Settings->DelegatePinTypeColor;
}
else if (TypeString == PC_Class)
{
return Settings->ClassPinTypeColor;
}
// Type does not have a defined color!
return Settings->DefaultPinTypeColor;
}
FText UEdGraphSchema_K2::GetPinDisplayName(const UEdGraphPin* Pin) const
{
FText DisplayName = FText::GetEmpty();
if (Pin != NULL)
{
UEdGraphNode* Node = Pin->GetOwningNode();
if (Node->ShouldOverridePinNames())
{
DisplayName = Node->GetPinNameOverride(*Pin);
}
else
{
DisplayName = Super::GetPinDisplayName(Pin);
// bit of a hack to hide 'execute' and 'then' pin names
if ((Pin->PinType.PinCategory == PC_Exec) &&
((DisplayName.ToString() == PN_Execute) || (DisplayName.ToString() == PN_Then)))
{
DisplayName = FText::GetEmpty();
}
}
if( GEditor && GetDefault<UEditorStyleSettings>()->bShowFriendlyNames )
{
DisplayName = FText::FromString(FName::NameToDisplayString(DisplayName.ToString(), Pin->PinType.PinCategory == PC_Boolean));
}
}
return DisplayName;
}
void UEdGraphSchema_K2::ConstructBasicPinTooltip(const UEdGraphPin& Pin, const FText& PinDescription, FString& TooltipOut) const
{
if (Pin.bWasTrashed)
{
return;
}
if (bGeneratingDocumentation)
{
TooltipOut = PinDescription.ToString();
}
else
{
FFormatNamedArguments Args;
Args.Add(TEXT("PinType"), TypeToText(Pin.PinType));
if (UEdGraphNode* PinNode = Pin.GetOwningNode())
{
UEdGraphSchema_K2 const* const K2Schema = Cast<const UEdGraphSchema_K2>(PinNode->GetSchema());
if (ensure(K2Schema != NULL)) // ensure that this node belongs to this schema
{
Args.Add(TEXT("DisplayName"), GetPinDisplayName(&Pin));
Args.Add(TEXT("LineFeed1"), FText::FromString(TEXT("\n")));
}
}
else
{
Args.Add(TEXT("DisplayName"), FText::GetEmpty());
Args.Add(TEXT("LineFeed1"), FText::GetEmpty());
}
if (!PinDescription.IsEmpty())
{
Args.Add(TEXT("Description"), PinDescription);
Args.Add(TEXT("LineFeed2"), FText::FromString(TEXT("\n\n")));
}
else
{
Args.Add(TEXT("Description"), FText::GetEmpty());
Args.Add(TEXT("LineFeed2"), FText::GetEmpty());
}
TooltipOut = FText::Format(LOCTEXT("PinTooltip", "{DisplayName}{LineFeed1}{PinType}{LineFeed2}{Description}"), Args).ToString();
}
}
EGraphType UEdGraphSchema_K2::GetGraphType(const UEdGraph* TestEdGraph) const
{
if (TestEdGraph)
{
//@TODO: Should there be a GT_Subgraph type?
UEdGraph* GraphToTest = const_cast<UEdGraph*>(TestEdGraph);
for (UObject* TestOuter = GraphToTest; TestOuter; TestOuter = TestOuter->GetOuter())
{
// reached up to the blueprint for the graph
if (UBlueprint* Blueprint = Cast<UBlueprint>(TestOuter))
{
if (Blueprint->BlueprintType == BPTYPE_MacroLibrary ||
Blueprint->MacroGraphs.Contains(GraphToTest))
{
return GT_Macro;
}
else if (Blueprint->UbergraphPages.Contains(GraphToTest))
{
return GT_Ubergraph;
}
else if (Blueprint->FunctionGraphs.Contains(GraphToTest))
{
return GT_Function;
}
}
else
{
GraphToTest = Cast<UEdGraph>(TestOuter);
}
}
}
return Super::GetGraphType(TestEdGraph);
}
bool UEdGraphSchema_K2::IsTitleBarPin(const UEdGraphPin& Pin) const
{
return IsExecPin(Pin);
}
void UEdGraphSchema_K2::CreateMacroGraphTerminators(UEdGraph& Graph, UClass* Class) const
{
const FName GraphName = Graph.GetFName();
UBlueprint* Blueprint = FBlueprintEditorUtils::FindBlueprintForGraphChecked(&Graph);
// Create the entry/exit tunnels
{
FGraphNodeCreator<UK2Node_Tunnel> EntryNodeCreator(Graph);
UK2Node_Tunnel* EntryNode = EntryNodeCreator.CreateNode();
EntryNode->bCanHaveOutputs = true;
EntryNodeCreator.Finalize();
SetNodeMetaData(EntryNode, FNodeMetadata::DefaultGraphNode);
}
{
FGraphNodeCreator<UK2Node_Tunnel> ExitNodeCreator(Graph);
UK2Node_Tunnel* ExitNode = ExitNodeCreator.CreateNode();
ExitNode->bCanHaveInputs = true;
ExitNode->NodePosX = 240;
ExitNodeCreator.Finalize();
SetNodeMetaData(ExitNode, FNodeMetadata::DefaultGraphNode);
}
}
void UEdGraphSchema_K2::LinkDataPinFromOutputToInput(UEdGraphNode* InOutputNode, UEdGraphNode* InInputNode) const
{
for (auto PinIter = InOutputNode->Pins.CreateIterator(); PinIter; ++PinIter)
{
UEdGraphPin* const OutputPin = *PinIter;
if ((OutputPin->Direction == EGPD_Output) && (!IsExecPin(*OutputPin)))
{
UEdGraphPin* const InputPin = InInputNode->FindPinChecked(OutputPin->PinName);
OutputPin->MakeLinkTo(InputPin);
}
}
}
void UEdGraphSchema_K2::CreateFunctionGraphTerminators(UEdGraph& Graph, UClass* Class) const
{
const FName GraphName = Graph.GetFName();
UBlueprint* Blueprint = FBlueprintEditorUtils::FindBlueprintForGraphChecked(&Graph);
check(Blueprint->BlueprintType != BPTYPE_MacroLibrary);
// Create a function entry node
FGraphNodeCreator<UK2Node_FunctionEntry> FunctionEntryCreator(Graph);
UK2Node_FunctionEntry* EntryNode = FunctionEntryCreator.CreateNode();
EntryNode->SignatureClass = Class;
EntryNode->SignatureName = GraphName;
FunctionEntryCreator.Finalize();
SetNodeMetaData(EntryNode, FNodeMetadata::DefaultGraphNode);
// See if we need to implement a return node
UFunction* InterfaceToImplement = FindField<UFunction>(Class, GraphName);
if (InterfaceToImplement)
{
// Add modifier flags from the declaration
EntryNode->AddExtraFlags(InterfaceToImplement->FunctionFlags & (FUNC_Const | FUNC_Static | FUNC_BlueprintPure));
UK2Node* NextNode = EntryNode;
UEdGraphPin* NextExec = FindExecutionPin(*EntryNode, EGPD_Output);
bool bHasParentNode = false;
// Create node for call parent function
if (((Class->GetClassFlags() & CLASS_Interface) == 0) &&
(InterfaceToImplement->FunctionFlags & FUNC_BlueprintCallable))
{
FGraphNodeCreator<UK2Node_CallParentFunction> FunctionParentCreator(Graph);
UK2Node_CallParentFunction* ParentNode = FunctionParentCreator.CreateNode();
ParentNode->SetFromFunction(InterfaceToImplement);
ParentNode->NodePosX = EntryNode->NodePosX + EntryNode->NodeWidth + 256;
ParentNode->NodePosY = EntryNode->NodePosY;
FunctionParentCreator.Finalize();
UEdGraphPin* ParentNodeExec = FindExecutionPin(*ParentNode, EGPD_Input);
// If the parent node has an execution pin, then we should as well (we're overriding them, after all)
// but perhaps this assumption is not valid in the case where a function becomes pure after being
// initially declared impure - for that reason I'm checking for validity on both ParentNodeExec and NextExec
if (ParentNodeExec && NextExec)
{
NextExec->MakeLinkTo(ParentNodeExec);
NextExec = FindExecutionPin(*ParentNode, EGPD_Output);
}
NextNode = ParentNode;
bHasParentNode = true;
}
// See if any function params are marked as out
bool bHasOutParam = false;
for( TFieldIterator<UProperty> It(InterfaceToImplement); It && (It->PropertyFlags & CPF_Parm); ++It )
{
if( It->PropertyFlags & CPF_OutParm )
{
bHasOutParam = true;
break;
}
}
if( bHasOutParam )
{
FGraphNodeCreator<UK2Node_FunctionResult> NodeCreator(Graph);
UK2Node_FunctionResult* ReturnNode = NodeCreator.CreateNode();
ReturnNode->SignatureClass = Class;
ReturnNode->SignatureName = GraphName;
ReturnNode->NodePosX = NextNode->NodePosX + NextNode->NodeWidth + 256;
ReturnNode->NodePosY = EntryNode->NodePosY;
NodeCreator.Finalize();
SetNodeMetaData(ReturnNode, FNodeMetadata::DefaultGraphNode);
// Auto-connect the pins for entry and exit, so that by default the signature is properly generated
UEdGraphPin* ResultNodeExec = FindExecutionPin(*ReturnNode, EGPD_Input);
if (ResultNodeExec && NextExec)
{
NextExec->MakeLinkTo(ResultNodeExec);
}
if (bHasParentNode)
{
LinkDataPinFromOutputToInput(NextNode, ReturnNode);
}
}
}
}
void UEdGraphSchema_K2::CreateFunctionGraphTerminators(UEdGraph& Graph, UFunction* FunctionSignature) const
{
const FName GraphName = Graph.GetFName();
UBlueprint* Blueprint = FBlueprintEditorUtils::FindBlueprintForGraphChecked(&Graph);
check(Blueprint->BlueprintType != BPTYPE_MacroLibrary);
// Create a function entry node
FGraphNodeCreator<UK2Node_FunctionEntry> FunctionEntryCreator(Graph);
UK2Node_FunctionEntry* EntryNode = FunctionEntryCreator.CreateNode();
EntryNode->SignatureClass = NULL;
EntryNode->SignatureName = GraphName;
FunctionEntryCreator.Finalize();
SetNodeMetaData(EntryNode, FNodeMetadata::DefaultGraphNode);
// We don't have a signature class to base this on permanently, because it's not an override function.
// so we need to define the pins as user defined so that they are serialized.
EntryNode->CreateUserDefinedPinsForFunctionEntryExit(FunctionSignature, /*bIsFunctionEntry=*/ true);
// See if any function params are marked as out
bool bHasOutParam = false;
for ( TFieldIterator<UProperty> It(FunctionSignature); It && ( It->PropertyFlags & CPF_Parm ); ++It )
{
if ( It->PropertyFlags & CPF_OutParm )
{
bHasOutParam = true;
break;
}
}
if ( bHasOutParam )
{
FGraphNodeCreator<UK2Node_FunctionResult> NodeCreator(Graph);
UK2Node_FunctionResult* ReturnNode = NodeCreator.CreateNode();
ReturnNode->SignatureClass = NULL;
ReturnNode->SignatureName = GraphName;
ReturnNode->NodePosX = EntryNode->NodePosX + EntryNode->NodeWidth + 256;
ReturnNode->NodePosY = EntryNode->NodePosY;
NodeCreator.Finalize();
SetNodeMetaData(ReturnNode, FNodeMetadata::DefaultGraphNode);
ReturnNode->CreateUserDefinedPinsForFunctionEntryExit(FunctionSignature, /*bIsFunctionEntry=*/ false);
// Auto-connect the pins for entry and exit, so that by default the signature is properly generated
UEdGraphPin* EntryNodeExec = FindExecutionPin(*EntryNode, EGPD_Output);
UEdGraphPin* ResultNodeExec = FindExecutionPin(*ReturnNode, EGPD_Input);
EntryNodeExec->MakeLinkTo(ResultNodeExec);
}
}
bool UEdGraphSchema_K2::ConvertPropertyToPinType(const UProperty* Property, /*out*/ FEdGraphPinType& TypeOut) const
{
if (Property == NULL)
{
TypeOut.PinCategory = TEXT("bad_type");
return false;
}
TypeOut.PinSubCategory = TEXT("");
// Handle whether or not this is an array property
const UArrayProperty* ArrayProperty = Cast<const UArrayProperty>(Property);
const UProperty* TestProperty = ArrayProperty ? ArrayProperty->Inner : Property;
TypeOut.bIsArray = (ArrayProperty != NULL);
TypeOut.bIsReference = Property->HasAllPropertyFlags(CPF_OutParm|CPF_ReferenceParm);
TypeOut.bIsConst = Property->HasAllPropertyFlags(CPF_ConstParm);
// Check to see if this is the wildcard property for the target array type
UFunction* Function = Cast<UFunction>(Property->GetOuter());
if( UK2Node_CallArrayFunction::IsWildcardProperty(Function, Property)
|| UK2Node_CallFunction::IsStructureWildcardProperty(Function, Property->GetName()))
{
TypeOut.PinCategory = PC_Wildcard;
}
else if (const UInterfaceProperty* InterfaceProperty = Cast<const UInterfaceProperty>(TestProperty))
{
TypeOut.PinCategory = PC_Interface;
TypeOut.PinSubCategoryObject = InterfaceProperty->InterfaceClass;
}
else if (const UClassProperty* ClassProperty = Cast<const UClassProperty>(TestProperty))
{
TypeOut.PinCategory = PC_Class;
TypeOut.PinSubCategoryObject = ClassProperty->MetaClass;
}
else if (const UAssetClassProperty* AssetClassProperty = Cast<const UAssetClassProperty>(TestProperty))
{
TypeOut.PinCategory = PC_AssetClass;
TypeOut.PinSubCategoryObject = AssetClassProperty->MetaClass;
}
else if (const UAssetObjectProperty* AssetObjectProperty = Cast<const UAssetObjectProperty>(TestProperty))
{
TypeOut.PinCategory = PC_Asset;
TypeOut.PinSubCategoryObject = AssetObjectProperty->PropertyClass;
}
else if (const UObjectPropertyBase* ObjectProperty = Cast<const UObjectPropertyBase>(TestProperty))
{
TypeOut.PinCategory = PC_Object;
TypeOut.PinSubCategoryObject = ObjectProperty->PropertyClass;
TypeOut.bIsWeakPointer = TestProperty->IsA(UWeakObjectProperty::StaticClass());
}
else if (const UStructProperty* StructProperty = Cast<const UStructProperty>(TestProperty))
{
TypeOut.PinCategory = PC_Struct;
TypeOut.PinSubCategoryObject = StructProperty->Struct;
}
else if (Cast<const UFloatProperty>(TestProperty) != NULL)
{
TypeOut.PinCategory = PC_Float;
}
else if (Cast<const UIntProperty>(TestProperty) != NULL)
{
TypeOut.PinCategory = PC_Int;
if (TestProperty->HasMetaData(FBlueprintMetadata::MD_Bitmask))
{
TypeOut.PinSubCategory = PSC_Bitmask;
}
}
else if (const UByteProperty* ByteProperty = Cast<const UByteProperty>(TestProperty))
{
TypeOut.PinCategory = PC_Byte;
if (TestProperty->HasMetaData(FBlueprintMetadata::MD_Bitmask))
{
TypeOut.PinSubCategory = PSC_Bitmask;
}
else
{
TypeOut.PinSubCategoryObject = ByteProperty->Enum;
}
}
else if (Cast<const UNameProperty>(TestProperty) != NULL)
{
TypeOut.PinCategory = PC_Name;
}
else if (Cast<const UBoolProperty>(TestProperty) != NULL)
{
TypeOut.PinCategory = PC_Boolean;
}
else if (Cast<const UStrProperty>(TestProperty) != NULL)
{
TypeOut.PinCategory = PC_String;
}
else if (Cast<const UTextProperty>(TestProperty) != NULL)
{
TypeOut.PinCategory = PC_Text;
}
else if (const UMulticastDelegateProperty* MulticastDelegateProperty = Cast<const UMulticastDelegateProperty>(TestProperty))
{
TypeOut.PinCategory = PC_MCDelegate;
FMemberReference::FillSimpleMemberReference<UFunction>(MulticastDelegateProperty->SignatureFunction, TypeOut.PinSubCategoryMemberReference);
}
else if (const UDelegateProperty* DelegateProperty = Cast<const UDelegateProperty>(TestProperty))
{
TypeOut.PinCategory = PC_Delegate;
FMemberReference::FillSimpleMemberReference<UFunction>(DelegateProperty->SignatureFunction, TypeOut.PinSubCategoryMemberReference);
}
else
{
TypeOut.PinCategory = TEXT("bad_type");
return false;
}
if (TypeOut.PinSubCategory == PSC_Bitmask)
{
FString BitmaskEnumName = TestProperty->GetMetaData(TEXT("BitmaskEnum"));
if(!BitmaskEnumName.IsEmpty())
{
// @TODO: Potentially replace this with a serialized UEnum reference on the UProperty (e.g. UByteProperty::Enum)
TypeOut.PinSubCategoryObject = FindObject<UEnum>(ANY_PACKAGE, *BitmaskEnumName);
}
}
return true;
}
FString UEdGraphSchema_K2::TypeToString(const FEdGraphPinType& Type)
{
return TypeToText(Type).ToString();
}
FString UEdGraphSchema_K2::TypeToString(UProperty* const Property)
{
return TypeToText(Property).ToString();
}
FText UEdGraphSchema_K2::TypeToText(UProperty* const Property)
{
if (UStructProperty* Struct = Cast<UStructProperty>(Property))
{
if (Struct->Struct)
{
FEdGraphPinType PinType;
PinType.PinCategory = PC_Struct;
PinType.PinSubCategoryObject = Struct->Struct;
return TypeToText(PinType);
}
}
else if (UClassProperty* Class = Cast<UClassProperty>(Property))
{
if (Class->MetaClass)
{
FEdGraphPinType PinType;
PinType.PinCategory = PC_Class;
PinType.PinSubCategoryObject = Class->MetaClass;
return TypeToText(PinType);
}
}
else if (UInterfaceProperty* Interface = Cast<UInterfaceProperty>(Property))
{
if (Interface->InterfaceClass != nullptr)
{
FEdGraphPinType PinType;
PinType.PinCategory = PC_Interface;
PinType.PinSubCategoryObject = Interface->InterfaceClass;
return TypeToText(PinType);
}
}
else if (UObjectPropertyBase* Obj = Cast<UObjectPropertyBase>(Property))
{
if( Obj->PropertyClass )
{
FEdGraphPinType PinType;
PinType.PinCategory = PC_Object;
PinType.PinSubCategoryObject = Obj->PropertyClass;
PinType.bIsWeakPointer = Property->IsA(UWeakObjectProperty::StaticClass());
return TypeToText(PinType);
}
return FText::GetEmpty();
}
else if (UArrayProperty* Array = Cast<UArrayProperty>(Property))
{
if (Array->Inner)
{
FFormatNamedArguments Args;
Args.Add(TEXT("ArrayType"), TypeToText(Array->Inner));
return FText::Format(LOCTEXT("ArrayPropertyText", "Array of {ArrayType}"), Args);
}
}
return FText::FromString(Property->GetClass()->GetName());
}
FText UEdGraphSchema_K2::GetCategoryText(const FString& Category, const bool bForMenu)
{
if (Category.IsEmpty())
{
return FText::GetEmpty();
}
static TMap<FString, FText> CategoryDescriptions;
if (CategoryDescriptions.Num() == 0)
{
CategoryDescriptions.Add(PC_Exec, LOCTEXT("Exec", "Exec"));
CategoryDescriptions.Add(PC_Boolean, LOCTEXT("BoolCategory","Boolean"));
CategoryDescriptions.Add(PC_Byte, LOCTEXT("ByteCategory","Byte"));
CategoryDescriptions.Add(PC_Class, LOCTEXT("ClassCategory","Class"));
CategoryDescriptions.Add(PC_Int, LOCTEXT("IntCategory","Integer"));
CategoryDescriptions.Add(PC_Float, LOCTEXT("FloatCategory","Float"));
CategoryDescriptions.Add(PC_Name, LOCTEXT("NameCategory","Name"));
CategoryDescriptions.Add(PC_Delegate, LOCTEXT("DelegateCategory","Delegate"));
CategoryDescriptions.Add(PC_MCDelegate, LOCTEXT("MulticastDelegateCategory","Multicast Delegate"));
CategoryDescriptions.Add(PC_Object, LOCTEXT("ObjectCategory","Reference"));
CategoryDescriptions.Add(PC_Interface, LOCTEXT("InterfaceCategory","Interface"));
CategoryDescriptions.Add(PC_String, LOCTEXT("StringCategory","String"));
CategoryDescriptions.Add(PC_Text, LOCTEXT("TextCategory","Text"));
CategoryDescriptions.Add(PC_Struct, LOCTEXT("StructCategory","Structure"));
CategoryDescriptions.Add(PC_Wildcard, LOCTEXT("WildcardCategory","Wildcard"));
CategoryDescriptions.Add(PC_Enum, LOCTEXT("EnumCategory","Enum"));
CategoryDescriptions.Add(PC_Asset, LOCTEXT("AssetCategory", "Asset ID"));
CategoryDescriptions.Add(PC_AssetClass, LOCTEXT("AssetClassCategory", "Class Asset ID"));
CategoryDescriptions.Add(AllObjectTypes, LOCTEXT("AllObjectTypes", "Object Types"));
}
if (bForMenu)
{
if (Category == PC_Object)
{
return LOCTEXT("ObjectCategoryForMenu", "Object Reference");
}
}
if (FText const* TypeDesc = CategoryDescriptions.Find(Category))
{
return *TypeDesc;
}
else
{
return FText::FromString(Category);
}
}
FText UEdGraphSchema_K2::TypeToText(const FEdGraphPinType& Type)
{
FText PropertyText;
const UEdGraphSchema_K2* Schema = GetDefault<UEdGraphSchema_K2>();
if (Type.PinSubCategory != Schema->PSC_Bitmask && Type.PinSubCategoryObject != NULL)
{
if (Type.PinCategory == Schema->PC_Byte)
{
FFormatNamedArguments Args;
Args.Add(TEXT("EnumName"), FText::FromString(Type.PinSubCategoryObject->GetName()));
PropertyText = FText::Format(LOCTEXT("EnumAsText", "{EnumName} Enum"), Args);
}
else
{
UObject* SubCategoryObj = Type.PinSubCategoryObject.Get();
FString SubCategoryObjName = SubCategoryObj->GetName();
if (UField* SubCategoryField = Cast<UField>(SubCategoryObj))
{
SubCategoryObjName = SubCategoryField->GetDisplayNameText().ToString();
}
if( !Type.bIsWeakPointer )
{
UClass* PSCOAsClass = Cast<UClass>(Type.PinSubCategoryObject.Get());
const bool bIsInterface = PSCOAsClass && PSCOAsClass->HasAnyClassFlags(CLASS_Interface);
FFormatNamedArguments Args;
// Don't display the category for "well-known" struct types
if (Type.PinCategory == PC_Struct && (Type.PinSubCategoryObject == VectorStruct || Type.PinSubCategoryObject == RotatorStruct || Type.PinSubCategoryObject == TransformStruct))
{
Args.Add(TEXT("Category"), FText::GetEmpty());
}
else
{
Args.Add(TEXT("Category"), (!bIsInterface ? GetCategoryText(Type.PinCategory) : GetCategoryText(PC_Interface)));
}
Args.Add(TEXT("ObjectName"), FText::FromString(FName::NameToDisplayString(SubCategoryObjName, /*bIsBool =*/false)));
PropertyText = FText::Format(LOCTEXT("ObjectAsText", "{ObjectName} {Category}"), Args);
}
else
{
FFormatNamedArguments Args;
Args.Add(TEXT("Category"), FText::FromString(Type.PinCategory));
Args.Add(TEXT("ObjectName"), FText::FromString(SubCategoryObjName));
PropertyText = FText::Format(LOCTEXT("WeakPtrAsText", "{ObjectName} Weak {Category}"), Args);
}
}
}
else if (Type.PinSubCategory != TEXT(""))
{
FFormatNamedArguments Args;
Args.Add(TEXT("Category"), GetCategoryText(Type.PinCategory));
Args.Add(TEXT("ObjectName"), FText::FromString(FName::NameToDisplayString(Type.PinSubCategory, false)));
PropertyText = FText::Format(LOCTEXT("ObjectAsText", "{ObjectName} {Category}"), Args);
}
else
{
PropertyText = GetCategoryText(Type.PinCategory);
}
if (Type.bIsArray)
{
FFormatNamedArguments Args;
Args.Add(TEXT("PropertyTitle"), PropertyText);
PropertyText = FText::Format(LOCTEXT("ArrayAsText", "Array of {PropertyTitle}s"), Args);
}
else if (Type.bIsReference)
{
FFormatNamedArguments Args;
Args.Add(TEXT("PropertyTitle"), PropertyText);
PropertyText = FText::Format(LOCTEXT("PropertyByRef", "{PropertyTitle} (by ref)"), Args);
}
return PropertyText;
}
void UEdGraphSchema_K2::GetVariableTypeTree(TArray< TSharedPtr<FPinTypeTreeInfo> >& TypeTree, ETypeTreeFilter TypeTreeFilter) const
{
bool bAllowExec = (TypeTreeFilter & ETypeTreeFilter::AllowExec) == ETypeTreeFilter::AllowExec;
bool bAllowWildCard = (TypeTreeFilter & ETypeTreeFilter::AllowWildcard) == ETypeTreeFilter::AllowWildcard;
bool bIndexTypesOnly = (TypeTreeFilter & ETypeTreeFilter::IndexTypesOnly) == ETypeTreeFilter::IndexTypesOnly;
bool bRootTypesOnly = (TypeTreeFilter & ETypeTreeFilter::RootTypesOnly) == ETypeTreeFilter::RootTypesOnly;
#ifdef SCHEMA_K2_GETVARIABLETYPETREE_LOG_TIME
static_assert(false, "Macro redefinition.");
#endif
#define SCHEMA_K2_GETVARIABLETYPETREE_LOG_TIME 0
#if SCHEMA_K2_GETVARIABLETYPETREE_LOG_TIME
const auto StartTime = FPlatformTime::Seconds();
#endif //SCHEMA_K2_GETVARIABLETYPETREE_LOG_TIME
FTypesDatabase TypesDatabase;
FTypesDatabase* TypesDatabasePtr = nullptr;
if (!bRootTypesOnly)
{
TypesDatabasePtr = &TypesDatabase;
FGatherTypesHelper::FillLoadedTypesDatabase(TypesDatabase, bIndexTypesOnly);
}
#if SCHEMA_K2_GETVARIABLETYPETREE_LOG_TIME
const auto DatabaseLoadedTime = FPlatformTime::Seconds();
#endif //SCHEMA_K2_GETVARIABLETYPETREE_LOG_TIME
if (!bRootTypesOnly)
{
FGatherTypesHelper::FillUnLoadedTypesDatabase(TypesDatabase, bIndexTypesOnly);
}
#if SCHEMA_K2_GETVARIABLETYPETREE_LOG_TIME
const auto DatabaseUnLoadedTime = FPlatformTime::Seconds();
#endif //SCHEMA_K2_GETVARIABLETYPETREE_LOG_TIME
// Clear the list
TypeTree.Empty();
if( bAllowExec )
{
TypeTree.Add( MakeShareable( new FPinTypeTreeInfo(GetCategoryText(PC_Exec, true), PC_Exec, this, LOCTEXT("ExecType", "Execution pin")) ) );
}
TypeTree.Add( MakeShareable( new FPinTypeTreeInfo(GetCategoryText(PC_Boolean, true), PC_Boolean, this, LOCTEXT("BooleanType", "True or false value")) ) );
TypeTree.Add( MakeShareable( new FPinTypeTreeInfo(GetCategoryText(PC_Byte, true), PC_Byte, this, LOCTEXT("ByteType", "8 bit number")) ) );
TypeTree.Add( MakeShareable( new FPinTypeTreeInfo(GetCategoryText(PC_Int, true), PC_Int, this, LOCTEXT("IntegerType", "Integer number")) ) );
if (!bIndexTypesOnly)
{
TypeTree.Add(MakeShareable(new FPinTypeTreeInfo(GetCategoryText(PC_Float, true), PC_Float, this, LOCTEXT("FloatType", "Floating point number"))));
TypeTree.Add(MakeShareable(new FPinTypeTreeInfo(GetCategoryText(PC_Name, true), PC_Name, this, LOCTEXT("NameType", "A text name"))));
TypeTree.Add(MakeShareable(new FPinTypeTreeInfo(GetCategoryText(PC_String, true), PC_String, this, LOCTEXT("StringType", "A text string"))));
TypeTree.Add(MakeShareable(new FPinTypeTreeInfo(GetCategoryText(PC_Text, true), PC_Text, this, LOCTEXT("TextType", "A localizable text string"))));
// Add in special first-class struct types
if (!bRootTypesOnly)
{
TypeTree.Add(MakeShareable(new FPinTypeTreeInfo(PC_Struct, TBaseStructure<FVector>::Get(), LOCTEXT("VectorType", "A 3D vector"))));
TypeTree.Add(MakeShareable(new FPinTypeTreeInfo(PC_Struct, TBaseStructure<FRotator>::Get(), LOCTEXT("RotatorType", "A 3D rotation"))));
TypeTree.Add(MakeShareable(new FPinTypeTreeInfo(PC_Struct, TBaseStructure<FTransform>::Get(), LOCTEXT("TransformType", "A 3D transformation, including translation, rotation and 3D scale."))));
}
}
// Add wildcard type
if (bAllowWildCard)
{
TypeTree.Add( MakeShareable( new FPinTypeTreeInfo(GetCategoryText(PC_Wildcard, true), PC_Wildcard, this, LOCTEXT("WildcardType", "Wildcard type (unspecified).")) ) );
}
// Add the types that have subtrees
if (!bIndexTypesOnly)
{
TypeTree.Add(MakeShareable(new FPinTypeTreeInfo(GetCategoryText(PC_Struct, true), PC_Struct, this, LOCTEXT("StructType", "Struct (value) types."), true, TypesDatabasePtr)));
TypeTree.Add(MakeShareable(new FPinTypeTreeInfo(GetCategoryText(PC_Interface, true), PC_Interface, this, LOCTEXT("InterfaceType", "Interface pointer."), true, TypesDatabasePtr)));
if (!bRootTypesOnly)
{
TypeTree.Add(MakeShareable(new FPinTypeTreeInfo(GetCategoryText(AllObjectTypes, true), AllObjectTypes, this, LOCTEXT("ObjectType", "Object pointer."), true, TypesDatabasePtr)));
}
else
{
TypeTree.Add(MakeShareable(new FPinTypeTreeInfo(GetCategoryText(PC_Object, true), PC_Object, this, LOCTEXT("ObjectType", "Object pointer."), true, TypesDatabasePtr)));
TypeTree.Add(MakeShareable(new FPinTypeTreeInfo(GetCategoryText(PC_Class, true), PC_Class, this, LOCTEXT("ClassType", "Class pointer."), true, TypesDatabasePtr)));
TypeTree.Add(MakeShareable(new FPinTypeTreeInfo(GetCategoryText(PC_AssetClass, true), PC_AssetClass, this, LOCTEXT("AssetClassType", "Class ID."), true, TypesDatabasePtr)));
TypeTree.Add(MakeShareable(new FPinTypeTreeInfo(GetCategoryText(PC_Asset, true), PC_Asset, this, LOCTEXT("AssetType", "Asset ID."), true, TypesDatabasePtr)));
}
}
TypeTree.Add( MakeShareable( new FPinTypeTreeInfo(GetCategoryText(PC_Enum, true), PC_Enum, this, LOCTEXT("EnumType", "Enumeration types."), true, TypesDatabasePtr) ) );
#if SCHEMA_K2_GETVARIABLETYPETREE_LOG_TIME
const auto EndTime = FPlatformTime::Seconds();
UE_LOG(LogBlueprint, Log, TEXT("UEdGraphSchema_K2::GetVariableTypeTree times - LoadedTypesDatabase: %f UnLoadedTypesDatabase: %f FPinTypeTreeInfo: %f"), DatabaseLoadedTime - StartTime, DatabaseUnLoadedTime - DatabaseLoadedTime, EndTime - DatabaseUnLoadedTime);
#endif //SCHEMA_K2_GETVARIABLETYPETREE_LOG_TIME
#undef SCHEMA_K2_GETVARIABLETYPETREE_LOG_TIME
}
bool UEdGraphSchema_K2::DoesTypeHaveSubtypes(const FString& Category) const
{
return (Category == PC_Struct) || (Category == PC_Object) || (Category == PC_Asset) || (Category == PC_AssetClass) || (Category == PC_Interface) || (Category == PC_Class) || (Category == PC_Enum) || (Category == AllObjectTypes);
}
struct FWildcardArrayPinHelper
{
static bool CheckArrayCompatibility(const UEdGraphPin* OutputPin, const UEdGraphPin* InputPin, bool bIgnoreArray)
{
if (bIgnoreArray)
{
return true;
}
const UK2Node* OwningNode = InputPin ? Cast<UK2Node>(InputPin->GetOwningNode()) : NULL;
const bool bInputWildcardPinAcceptsArray = !OwningNode || OwningNode->DoesInputWildcardPinAcceptArray(InputPin);
if (bInputWildcardPinAcceptsArray)
{
return true;
}
const bool bCheckInputPin = (InputPin->PinType.PinCategory == GetDefault<UEdGraphSchema_K2>()->PC_Wildcard) && !InputPin->PinType.bIsArray;
const bool bArrayOutputPin = OutputPin && OutputPin->PinType.bIsArray;
return !(bCheckInputPin && bArrayOutputPin);
}
};
bool UEdGraphSchema_K2::ArePinsCompatible(const UEdGraphPin* PinA, const UEdGraphPin* PinB, const UClass* CallingContext, bool bIgnoreArray /*= false*/) const
{
if ((PinA->Direction == EGPD_Input) && (PinB->Direction == EGPD_Output))
{
return FWildcardArrayPinHelper::CheckArrayCompatibility(PinB, PinA, bIgnoreArray)
&& ArePinTypesCompatible(PinB->PinType, PinA->PinType, CallingContext, bIgnoreArray);
}
else if ((PinB->Direction == EGPD_Input) && (PinA->Direction == EGPD_Output))
{
return FWildcardArrayPinHelper::CheckArrayCompatibility(PinA, PinB, bIgnoreArray)
&& ArePinTypesCompatible(PinA->PinType, PinB->PinType, CallingContext, bIgnoreArray);
}
else
{
return false;
}
}
namespace
{
static UClass* GetOriginalClassToFixCompatibilit(const UClass* InClass)
{
const UBlueprint* BP = InClass ? Cast<const UBlueprint>(InClass->ClassGeneratedBy) : nullptr;
return BP ? Cast<UClass>(BP->OriginalClass) : nullptr;
}
// During compilation, pins are moved around for node expansion and the Blueprints may still inherit from REINST_ classes
// which causes problems for IsChildOf. Because we do not want to modify IsChildOf we must use a separate function
// that can check to see if classes have an AuthoritativeClass that IsChildOf a Target class.
static bool IsAuthoritativeChildOf(const UStruct* InSourceStruct, const UStruct* InTargetStruct)
{
bool bResult = false;
bool bIsNonNativeClass = false;
if (UClass* SourceAsClass = const_cast<UClass*>(Cast<UClass>(InSourceStruct)))
{
if (SourceAsClass->ClassGeneratedBy)
{
// We have a non-native (Blueprint) class which means it can exist in a semi-compiled state and inherit from a REINST_ class.
bIsNonNativeClass = true;
while (SourceAsClass)
{
if (SourceAsClass->GetAuthoritativeClass() == InTargetStruct)
{
bResult = true;
break;
}
SourceAsClass = SourceAsClass->GetSuperClass();
}
}
}
// We have a native (C++) class, do a normal IsChildOf check
if (!bIsNonNativeClass)
{
bResult = InSourceStruct->IsChildOf(InTargetStruct);
}
return bResult;
}
static bool ExtendedIsChildOf(const UClass* Child, const UClass* Parent)
{
if (Child->IsChildOf(Parent))
{
return true;
}
const UClass* OriginalChild = GetOriginalClassToFixCompatibilit(Child);
if (OriginalChild && OriginalChild->IsChildOf(Parent))
{
return true;
}
const UClass* OriginalParent = GetOriginalClassToFixCompatibilit(Parent);
if (OriginalParent && Child->IsChildOf(OriginalParent))
{
return true;
}
return false;
}
static bool ExtendedImplementsInterface(const UClass* Class, const UClass* Interface)
{
if (Class->ImplementsInterface(Interface))
{
return true;
}
const UClass* OriginalClass = GetOriginalClassToFixCompatibilit(Class);
if (OriginalClass && OriginalClass->ImplementsInterface(Interface))
{
return true;
}
const UClass* OriginalInterface = GetOriginalClassToFixCompatibilit(Interface);
if (OriginalInterface && Class->ImplementsInterface(OriginalInterface))
{
return true;
}
return false;
}
};
bool UEdGraphSchema_K2::DefaultValueSimpleValidation(const FEdGraphPinType& PinType, const FString& PinName, const FString& NewDefaultValue, UObject* NewDefaultObject, const FText& InNewDefaultText, FString* OutMsg /*= NULL*/) const
{
#ifdef DVSV_RETURN_MSG
static_assert(false, "Macro redefinition.");
#endif
#define DVSV_RETURN_MSG(Str) if(NULL != OutMsg) { *OutMsg = Str; } return false;
const FString& PinCategory = PinType.PinCategory;
const FString& PinSubCategory = PinType.PinSubCategory;
const UObject* PinSubCategoryObject = PinType.PinSubCategoryObject.Get();
if (PinType.bIsArray)
{
// arrays are validated separately
}
//@TODO: FCString::Atoi, FCString::Atof, and appStringToBool will 'accept' any input, but we should probably catch and warn
// about invalid input (non numeric for int/byte/float, and non 0/1 or yes/no/true/false for bool)
else if (PinCategory == PC_Boolean)
{
// All input is acceptable to some degree
}
else if (PinCategory == PC_Byte)
{
const UEnum* EnumPtr = Cast<const UEnum>(PinSubCategoryObject);
if (EnumPtr)
{
if (EnumPtr->FindEnumIndex(*NewDefaultValue) == INDEX_NONE)
{
DVSV_RETURN_MSG(FString::Printf(TEXT("'%s' is not a valid enumerant of '<%s>'"), *NewDefaultValue, *(EnumPtr->GetName())));
}
}
else if (!NewDefaultValue.IsEmpty())
{
int32 Value;
if (!FDefaultValueHelper::ParseInt(NewDefaultValue, Value))
{
DVSV_RETURN_MSG(TEXT("Expected a valid unsigned number for a byte property"));
}
if ((Value < 0) || (Value > 255))
{
DVSV_RETURN_MSG(TEXT("Expected a value between 0 and 255 for a byte property"));
}
}
}
else if ((PinCategory == PC_Class) || (PinCategory == PC_AssetClass))
{
// Should have an object set but no string
if (!NewDefaultValue.IsEmpty())
{
DVSV_RETURN_MSG(FString::Printf(TEXT("String NewDefaultValue '%s' specified on class pin '%s'"), *NewDefaultValue, *(PinName)));
}
if (NewDefaultObject == NULL)
{
// Valid self-reference or empty reference
}
else
{
// Otherwise, we expect to be able to resolve the type at least
const UClass* DefaultClassType = Cast<const UClass>(NewDefaultObject);
if (DefaultClassType == NULL)
{
DVSV_RETURN_MSG(FString::Printf(TEXT("Literal on pin %s is not a class."), *(PinName)));
}
else
{
// @TODO support PinSubCategory == 'self'
const UClass* PinClassType = Cast<const UClass>(PinSubCategoryObject);
if (PinClassType == NULL)
{
DVSV_RETURN_MSG(FString::Printf(TEXT("Failed to find class for pin %s"), *(PinName)));
}
else
{
// Have both types, make sure the specified type is a valid subtype
if (!IsAuthoritativeChildOf(DefaultClassType, PinClassType))
{
DVSV_RETURN_MSG(FString::Printf(TEXT("%s isn't a valid subclass of %s (specified on pin %s)"), *NewDefaultObject->GetPathName(), *PinClassType->GetName(), *(PinName)));
}
}
}
}
}
else if (PinCategory == PC_Float)
{
if (!NewDefaultValue.IsEmpty())
{
if (!FDefaultValueHelper::IsStringValidFloat(NewDefaultValue))
{
DVSV_RETURN_MSG(TEXT("Expected a valid number for an float property"));
}
}
}
else if (PinCategory == PC_Int)
{
if (!NewDefaultValue.IsEmpty())
{
if (!FDefaultValueHelper::IsStringValidInteger(NewDefaultValue))
{
DVSV_RETURN_MSG(TEXT("Expected a valid number for an integer property"));
}
}
}
else if (PinCategory == PC_Name)
{
// Anything is allowed
}
else if ((PinCategory == PC_Object) || (PinCategory == PC_Interface) || (PinCategory == PC_Asset))
{
if (PinSubCategoryObject == NULL && (PinSubCategory != PSC_Self))
{
DVSV_RETURN_MSG(FString::Printf(TEXT("PinSubCategoryObject on pin '%s' is NULL and PinSubCategory is '%s' not 'self'"), *(PinName), *PinSubCategory));
}
if (PinSubCategoryObject != NULL && PinSubCategory != TEXT(""))
{
DVSV_RETURN_MSG(FString::Printf(TEXT("PinSubCategoryObject on pin '%s' is non-NULL but PinSubCategory is '%s', should be empty"), *(PinName), *PinSubCategory));
}
// Should have an object set but no string - 'self' is not a valid NewDefaultValue for PC_Object pins
if (!NewDefaultValue.IsEmpty())
{
DVSV_RETURN_MSG(FString::Printf(TEXT("String NewDefaultValue '%s' specified on object pin '%s'"), *NewDefaultValue, *(PinName)));
}
// Check that the object that is set is of the correct class
const UClass* ObjectClass = Cast<const UClass>(PinSubCategoryObject);
if (NewDefaultObject != NULL && ObjectClass != NULL && !NewDefaultObject->IsA(ObjectClass))
{
DVSV_RETURN_MSG(FString::Printf(TEXT("%s isn't a %s (specified on pin %s)"), *NewDefaultObject->GetPathName(), *ObjectClass->GetName(), *(PinName)));
}
if ((PinCategory == PC_Asset) && NewDefaultObject && !NewDefaultObject->IsAsset())
{
DVSV_RETURN_MSG(FString::Printf(TEXT("%s is not an asset (specified on pin %s)"), *NewDefaultObject->GetPathName(), *(PinName)));
}
}
else if (PinCategory == PC_String)
{
// All strings are valid
}
else if (PinCategory == PC_Text)
{
// Neither of these should ever be true
if (InNewDefaultText.IsTransient())
{
DVSV_RETURN_MSG(TEXT("Invalid text literal, text is transient!"));
}
}
else if (PinCategory == PC_Struct)
{
if (PinSubCategory != TEXT(""))
{
DVSV_RETURN_MSG(FString::Printf(TEXT("Invalid PinSubCategory value '%s' (it should be empty)"), *PinSubCategory));
}
// Only FRotator and FVector properties are currently allowed to have a valid default value
const UScriptStruct* StructType = Cast<const UScriptStruct>(PinSubCategoryObject);
if (StructType == NULL)
{
//@TODO: MessageLog.Error(*FString::Printf(TEXT("Failed to find struct named %s (passed thru @@)"), *PinSubCategory), SourceObject);
DVSV_RETURN_MSG(FString::Printf(TEXT("No struct specified for pin '%s'"), *(PinName)));
}
else if (!NewDefaultValue.IsEmpty())
{
if (StructType == VectorStruct)
{
if (!FDefaultValueHelper::IsStringValidVector(NewDefaultValue))
{
DVSV_RETURN_MSG(TEXT("Invalid value for an FVector"));
}
}
else if (StructType == RotatorStruct)
{
FRotator Rot;
if (!FDefaultValueHelper::IsStringValidRotator(NewDefaultValue))
{
DVSV_RETURN_MSG(TEXT("Invalid value for an FRotator"));
}
}
else if (StructType == TransformStruct)
{
FTransform Transform;
if (!Transform.InitFromString(NewDefaultValue))
{
DVSV_RETURN_MSG(TEXT("Invalid value for an FTransform"));
}
}
else if (StructType == LinearColorStruct)
{
FLinearColor Color;
// Color form: "(R=%f,G=%f,B=%f,A=%f)"
if (!Color.InitFromString(NewDefaultValue))
{
DVSV_RETURN_MSG(TEXT("Invalid value for an FLinearColor"));
}
}
else
{
// Structs must pass validation at this point, because we need a UStructProperty to run ImportText
// They'll be verified in FKCHandler_CallFunction::CreateFunctionCallStatement()
}
}
}
else if (PinCategory == TEXT("CommentType"))
{
// Anything is allowed
}
else
{
//@TODO: MessageLog.Error(*FString::Printf(TEXT("Unsupported type %s on @@"), *UEdGraphSchema_K2::TypeToText(Type).ToString()), SourceObject);
DVSV_RETURN_MSG(FString::Printf(TEXT("Unsupported type %s on pin %s"), *UEdGraphSchema_K2::TypeToText(PinType).ToString(), *(PinName)));
}
#undef DVSV_RETURN_MSG
return true;
}
bool UEdGraphSchema_K2::ArePinTypesCompatible(const FEdGraphPinType& Output, const FEdGraphPinType& Input, const UClass* CallingContext, bool bIgnoreArray /*= false*/) const
{
if( !bIgnoreArray && (Output.bIsArray != Input.bIsArray) && (Input.PinCategory != PC_Wildcard || Input.bIsArray) )
{
return false;
}
else if (Output.PinCategory == Input.PinCategory)
{
if ((Output.PinSubCategory == Input.PinSubCategory)
&& (Output.PinSubCategoryObject == Input.PinSubCategoryObject)
&& (Output.PinSubCategoryMemberReference == Input.PinSubCategoryMemberReference))
{
return true;
}
else if (Output.PinCategory == PC_Interface)
{
UClass const* OutputClass = Cast<UClass const>(Output.PinSubCategoryObject.Get());
UClass const* InputClass = Cast<UClass const>(Input.PinSubCategoryObject.Get());
if (!OutputClass || !InputClass
|| !OutputClass->IsChildOf(UInterface::StaticClass())
|| !InputClass->IsChildOf(UInterface::StaticClass()))
{
UE_LOG(LogBlueprint, Error,
TEXT("UEdGraphSchema_K2::ArePinTypesCompatible invalid interface types - OutputClass: %s, InputClass: %s, CallingContext: %s"),
*GetPathNameSafe(OutputClass), *GetPathNameSafe(InputClass), *GetPathNameSafe(CallingContext));
return false;
}
return ExtendedIsChildOf(OutputClass, InputClass);
}
else if (((Output.PinCategory == PC_Asset) && (Input.PinCategory == PC_Asset))
|| ((Output.PinCategory == PC_AssetClass) && (Input.PinCategory == PC_AssetClass)))
{
const UClass* OutputObject = (Output.PinSubCategory == PSC_Self) ? CallingContext : Cast<const UClass>(Output.PinSubCategoryObject.Get());
const UClass* InputObject = (Input.PinSubCategory == PSC_Self) ? CallingContext : Cast<const UClass>(Input.PinSubCategoryObject.Get());
if ((OutputObject != NULL) && (InputObject != NULL))
{
return ExtendedIsChildOf(OutputObject ,InputObject);
}
}
else if ((Output.PinCategory == PC_Object) || (Output.PinCategory == PC_Struct) || (Output.PinCategory == PC_Class))
{
// Subcategory mismatch, but the two could be castable
// Only allow a match if the input is a superclass of the output
UStruct const* OutputObject = (Output.PinSubCategory == PSC_Self) ? CallingContext : Cast<UStruct>(Output.PinSubCategoryObject.Get());
UStruct const* InputObject = (Input.PinSubCategory == PSC_Self) ? CallingContext : Cast<UStruct>(Input.PinSubCategoryObject.Get());
if ((OutputObject != NULL) && (InputObject != NULL))
{
if (Output.PinCategory == PC_Struct)
{
return OutputObject->IsChildOf(InputObject) && FStructUtils::TheSameLayout(OutputObject, InputObject);
}
// Special Case: Cannot mix interface and non-interface calls, because the pointer size is different under the hood
const bool bInputIsInterface = InputObject->IsChildOf(UInterface::StaticClass());
const bool bOutputIsInterface = OutputObject->IsChildOf(UInterface::StaticClass());
UClass const* OutputClass = Cast<const UClass>(OutputObject);
UClass const* InputClass = Cast<const UClass>(InputObject);
if (bInputIsInterface != bOutputIsInterface)
{
if (bInputIsInterface && (OutputClass != NULL))
{
return ExtendedImplementsInterface(OutputClass, InputClass);
}
else if (bOutputIsInterface && (InputClass != NULL))
{
return ExtendedImplementsInterface(InputClass, OutputClass);
}
}
return (IsAuthoritativeChildOf(OutputObject, InputObject) || (OutputClass && InputClass && ExtendedIsChildOf(OutputClass, InputClass)))
&& (bInputIsInterface == bOutputIsInterface);
}
}
else if ((Output.PinCategory == PC_Byte) && (Output.PinSubCategory == Input.PinSubCategory))
{
// NOTE: This allows enums to be converted to bytes. Long-term we don't want to allow that, but we need it
// for now until we have == for enums in order to be able to compare them.
if (Input.PinSubCategoryObject == NULL)
{
return true;
}
}
else if (PC_Byte == Output.PinCategory || PC_Int == Output.PinCategory)
{
// Bitmask integral types are compatible with non-bitmask integral types (of the same word size).
return Output.PinSubCategory.StartsWith(PSC_Bitmask) || Input.PinSubCategory.StartsWith(PSC_Bitmask);
}
else if (PC_Delegate == Output.PinCategory || PC_MCDelegate == Output.PinCategory)
{
auto CanUseFunction = [](const UFunction* Func) -> bool
{
return Func && (Func->HasAllFlags(RF_LoadCompleted) || !Func->HasAnyFlags(RF_NeedLoad | RF_WasLoaded));
};
const UFunction* OutFunction = FMemberReference::ResolveSimpleMemberReference<UFunction>(Output.PinSubCategoryMemberReference);
if (!CanUseFunction(OutFunction))
{
OutFunction = NULL;
}
if (!OutFunction && Output.PinSubCategoryMemberReference.GetMemberParentClass())
{
const UClass* ParentClass = Output.PinSubCategoryMemberReference.GetMemberParentClass();
const UBlueprint* BPOwner = Cast<UBlueprint>(ParentClass->ClassGeneratedBy);
if (BPOwner && BPOwner->SkeletonGeneratedClass && (BPOwner->SkeletonGeneratedClass != ParentClass))
{
OutFunction = BPOwner->SkeletonGeneratedClass->FindFunctionByName(Output.PinSubCategoryMemberReference.MemberName);
}
}
const UFunction* InFunction = FMemberReference::ResolveSimpleMemberReference<UFunction>(Input.PinSubCategoryMemberReference);
if (!CanUseFunction(InFunction))
{
InFunction = NULL;
}
if (!InFunction && Input.PinSubCategoryMemberReference.GetMemberParentClass())
{
const UClass* ParentClass = Input.PinSubCategoryMemberReference.GetMemberParentClass();
const UBlueprint* BPOwner = Cast<UBlueprint>(ParentClass->ClassGeneratedBy);
if (BPOwner && BPOwner->SkeletonGeneratedClass && (BPOwner->SkeletonGeneratedClass != ParentClass))
{
InFunction = BPOwner->SkeletonGeneratedClass->FindFunctionByName(Input.PinSubCategoryMemberReference.MemberName);
}
}
return !OutFunction || !InFunction || OutFunction->IsSignatureCompatibleWith(InFunction);
}
}
else if (Output.PinCategory == PC_Wildcard || Input.PinCategory == PC_Wildcard)
{
// If this is an Index Wildcard we have to check compatibility for indexing types
if (Output.PinSubCategory == PSC_Index)
{
return IsIndexWildcardCompatible(Input);
}
else if (Input.PinSubCategory == PSC_Index)
{
return IsIndexWildcardCompatible(Output);
}
return true;
}
else if ((Output.PinCategory == PC_Object) && (Input.PinCategory == PC_Interface))
{
UClass const* OutputClass = Cast<UClass const>(Output.PinSubCategoryObject.Get());
UClass const* InterfaceClass = Cast<UClass const>(Input.PinSubCategoryObject.Get());
if ((OutputClass == nullptr) && (Output.PinSubCategory == PSC_Self))
{
OutputClass = CallingContext;
}
return OutputClass && (ExtendedImplementsInterface(OutputClass, InterfaceClass) || ExtendedIsChildOf(OutputClass, InterfaceClass));
}
// Pins representing BLueprint objects and subclass of UObject can match when EditoronlyBP.bAllowClassAndBlueprintPinMatching=true (BaseEngine.ini)
// It's required for converting all UBlueprint references into UClass.
struct FObjClassAndBlueprintHelper
{
private:
bool bAllow;
public:
FObjClassAndBlueprintHelper() : bAllow(false)
{
GConfig->GetBool(TEXT("EditoronlyBP"), TEXT("bAllowClassAndBlueprintPinMatching"), bAllow, GEditorIni);
}
bool Match(const FEdGraphPinType& A, const FEdGraphPinType& B, const UEdGraphSchema_K2& Schema) const
{
if (bAllow && (B.PinCategory == Schema.PC_Object) && (A.PinCategory == Schema.PC_Class))
{
const bool bAIsObjectClass = (UObject::StaticClass() == A.PinSubCategoryObject.Get());
const UClass* BClass = Cast<UClass>(B.PinSubCategoryObject.Get());
const bool bBIsBlueprintObj = BClass && BClass->IsChildOf(UBlueprint::StaticClass());
return bAIsObjectClass && bBIsBlueprintObj;
}
return false;
}
};
static FObjClassAndBlueprintHelper MatchHelper;
if (MatchHelper.Match(Input, Output, *this) || MatchHelper.Match(Output, Input, *this))
{
return true;
}
return false;
}
void UEdGraphSchema_K2::BreakNodeLinks(UEdGraphNode& TargetNode) const
{
UBlueprint* Blueprint = FBlueprintEditorUtils::FindBlueprintForNodeChecked(&TargetNode);
Super::BreakNodeLinks(TargetNode);
FBlueprintEditorUtils::MarkBlueprintAsModified(Blueprint);
}
void UEdGraphSchema_K2::BreakPinLinks(UEdGraphPin& TargetPin, bool bSendsNodeNotifcation) const
{
const FScopedTransaction Transaction( NSLOCTEXT("UnrealEd", "GraphEd_BreakPinLinks", "Break Pin Links") );
// cache this here, as BreakPinLinks can trigger a node reconstruction invalidating the TargetPin referenceS
UBlueprint* const Blueprint = FBlueprintEditorUtils::FindBlueprintForNodeChecked(TargetPin.GetOwningNode());
Super::BreakPinLinks(TargetPin, bSendsNodeNotifcation);
FBlueprintEditorUtils::MarkBlueprintAsModified(Blueprint);
}
void UEdGraphSchema_K2::BreakSinglePinLink(UEdGraphPin* SourcePin, UEdGraphPin* TargetPin)
{
const FScopedTransaction Transaction( NSLOCTEXT("UnrealEd", "GraphEd_BreakSinglePinLink", "Break Pin Link") );
UBlueprint* Blueprint = FBlueprintEditorUtils::FindBlueprintForNodeChecked(TargetPin->GetOwningNode());
Super::BreakSinglePinLink(SourcePin, TargetPin);
FBlueprintEditorUtils::MarkBlueprintAsModified(Blueprint);
}
void UEdGraphSchema_K2::ReconstructNode(UEdGraphNode& TargetNode, bool bIsBatchRequest/*=false*/) const
{
Super::ReconstructNode(TargetNode, bIsBatchRequest);
// If the reconstruction is being handled by something doing a batch (i.e. the blueprint autoregenerating itself), defer marking the blueprint as modified to prevent multiple recompiles
if (!bIsBatchRequest)
{
const UK2Node* K2Node = Cast<UK2Node>(&TargetNode);
if (K2Node && K2Node->NodeCausesStructuralBlueprintChange())
{
UBlueprint* Blueprint = FBlueprintEditorUtils::FindBlueprintForNodeChecked(&TargetNode);
FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(Blueprint);
}
}
}
bool UEdGraphSchema_K2::CanEncapuslateNode(UEdGraphNode const& TestNode) const
{
// Can't encapsulate entry points (may relax this restriction in the future, but it makes sense for now)
return !TestNode.IsA(UK2Node_FunctionTerminator::StaticClass()) &&
TestNode.GetClass() != UK2Node_Tunnel::StaticClass(); //Tunnel nodes getting sucked into collapsed graphs fails badly, want to allow derived types though(composite node/Macroinstances)
}
void UEdGraphSchema_K2::HandleGraphBeingDeleted(UEdGraph& GraphBeingRemoved) const
{
if (UBlueprint* Blueprint = FBlueprintEditorUtils::FindBlueprintForGraph(&GraphBeingRemoved))
{
// Look for collapsed graph nodes that reference this graph
TArray<UK2Node_Composite*> CompositeNodes;
FBlueprintEditorUtils::GetAllNodesOfClass<UK2Node_Composite>(Blueprint, /*out*/ CompositeNodes);
TSet<UK2Node_Composite*> NodesToDelete;
for (int32 i = 0; i < CompositeNodes.Num(); ++i)
{
UK2Node_Composite* CompositeNode = CompositeNodes[i];
if (CompositeNode->BoundGraph == &GraphBeingRemoved)
{
NodesToDelete.Add(CompositeNode);
}
}
// Delete the node that owns us
ensure(NodesToDelete.Num() <= 1);
for (TSet<UK2Node_Composite*>::TIterator It(NodesToDelete); It; ++It)
{
UK2Node_Composite* NodeToDelete = *It;
// Prevent re-entrancy here
NodeToDelete->BoundGraph = NULL;
NodeToDelete->Modify();
NodeToDelete->DestroyNode();
}
}
}
void UEdGraphSchema_K2::TrySetDefaultValue(UEdGraphPin& Pin, const FString& NewDefaultValue) const
{
FString UseDefaultValue;
UObject* UseDefaultObject = nullptr;
FText UseDefaultText;
if ((Pin.PinType.PinCategory == PC_Object)
|| (Pin.PinType.PinCategory == PC_Class)
|| (Pin.PinType.PinCategory == PC_Interface)
|| (Pin.PinType.PinCategory == PC_Asset)
|| (Pin.PinType.PinCategory == PC_AssetClass))
{
FString ObjectPathLocal = NewDefaultValue;
ConstructorHelpers::StripObjectClass(ObjectPathLocal);
UseDefaultObject = FindObject<UObject>(ANY_PACKAGE, *ObjectPathLocal);
UseDefaultValue.Empty();
}
else if(Pin.PinType.PinCategory == PC_Text)
{
FString PackageNamespace;
#if USE_STABLE_LOCALIZATION_KEYS
if (GIsEditor)
{
PackageNamespace = TextNamespaceUtil::EnsurePackageNamespace(Pin.GetOwningNodeUnchecked());
}
#endif // USE_STABLE_LOCALIZATION_KEYS
FText NewTextValue;
if (!FTextStringHelper::ReadFromString(*NewDefaultValue, NewTextValue, nullptr, *PackageNamespace))
{
NewTextValue = FText::FromString(NewDefaultValue);
}
TrySetDefaultText(Pin, NewTextValue);
UseDefaultObject = nullptr;
UseDefaultValue.Empty();
return;
}
else
{
UseDefaultObject = nullptr;
UseDefaultValue = NewDefaultValue;
}
// Check the default value and make it an error if it's bogus
if (IsPinDefaultValid(&Pin, UseDefaultValue, UseDefaultObject, UseDefaultText) == TEXT(""))
{
Pin.DefaultObject = UseDefaultObject;
Pin.DefaultValue = UseDefaultValue;
Pin.DefaultTextValue = UseDefaultText;
}
UEdGraphNode* Node = Pin.GetOwningNode();
check(Node);
Node->PinDefaultValueChanged(&Pin);
UBlueprint* Blueprint = FBlueprintEditorUtils::FindBlueprintForNodeChecked(Node);
FBlueprintEditorUtils::MarkBlueprintAsModified(Blueprint);
}
void UEdGraphSchema_K2::TrySetDefaultObject(UEdGraphPin& Pin, UObject* NewDefaultObject) const
{
FText UseDefaultText;
// Check the default value and make it an error if it's bogus
if (IsPinDefaultValid(&Pin, FString(TEXT("")), NewDefaultObject, UseDefaultText) == TEXT(""))
{
Pin.DefaultObject = NewDefaultObject;
Pin.DefaultValue = NULL;
Pin.DefaultTextValue = UseDefaultText;
}
UEdGraphNode* Node = Pin.GetOwningNode();
check(Node);
Node->PinDefaultValueChanged(&Pin);
UBlueprint* Blueprint = FBlueprintEditorUtils::FindBlueprintForNodeChecked(Node);
FBlueprintEditorUtils::MarkBlueprintAsModified(Blueprint);
}
void UEdGraphSchema_K2::TrySetDefaultText(UEdGraphPin& InPin, const FText& InNewDefaultText) const
{
// No reason to set the FText if it is not a PC_Text.
if(InPin.PinType.PinCategory == PC_Text)
{
// Check the default value and make it an error if it's bogus
if (IsPinDefaultValid(&InPin, TEXT(""), NULL, InNewDefaultText) == TEXT(""))
{
InPin.DefaultObject = NULL;
InPin.DefaultValue = NULL;
InPin.DefaultTextValue = InNewDefaultText;
}
UEdGraphNode* Node = InPin.GetOwningNode();
check(Node);
Node->PinDefaultValueChanged(&InPin);
UBlueprint* Blueprint = FBlueprintEditorUtils::FindBlueprintForNodeChecked(Node);
FBlueprintEditorUtils::MarkBlueprintAsModified(Blueprint);
}
}
bool UEdGraphSchema_K2::IsAutoCreateRefTerm(const UEdGraphPin* Pin) const
{
check(Pin != NULL);
bool bIsAutoCreateRefTerm = false;
UEdGraphNode* OwningNode = Pin->GetOwningNode();
UK2Node_CallFunction* FuncNode = Cast<UK2Node_CallFunction>(OwningNode);
if (FuncNode)
{
UFunction* TargetFunction = FuncNode->GetTargetFunction();
if (TargetFunction && !Pin->PinName.IsEmpty())
{
TArray<FString> AutoCreateParameterNames;
GetAutoEmitTermParameters(TargetFunction, AutoCreateParameterNames);
bIsAutoCreateRefTerm = AutoCreateParameterNames.Contains(Pin->PinName);
}
}
return bIsAutoCreateRefTerm;
}
bool UEdGraphSchema_K2::ShouldHidePinDefaultValue(UEdGraphPin* Pin) const
{
check(Pin != NULL);
if (Pin->bDefaultValueIsIgnored || Pin->PinType.bIsArray || (Pin->PinName == PN_Self && Pin->LinkedTo.Num() > 0) || (Pin->PinType.PinCategory == PC_Exec) || (Pin->PinType.bIsReference && !IsAutoCreateRefTerm(Pin)))
{
return true;
}
return false;
}
bool UEdGraphSchema_K2::ShouldShowAssetPickerForPin(UEdGraphPin* Pin) const
{
bool bShow = true;
if (Pin->PinType.PinCategory == PC_Object)
{
UClass* ObjectClass = Cast<UClass>(Pin->PinType.PinSubCategoryObject.Get());
if (ObjectClass)
{
// Don't show literal buttons for component type objects
bShow = !ObjectClass->IsChildOf(UActorComponent::StaticClass());
if (bShow && ObjectClass->IsChildOf(AActor::StaticClass()))
{
// Only show the picker for Actor classes if the class is placeable and we are in the level script
bShow = !ObjectClass->HasAllClassFlags(CLASS_NotPlaceable)
&& FBlueprintEditorUtils::IsLevelScriptBlueprint(FBlueprintEditorUtils::FindBlueprintForNode(Pin->GetOwningNode()));
}
if (bShow)
{
if (UK2Node_CallFunction* CallFunctionNode = Cast<UK2Node_CallFunction>(Pin->GetOwningNode()))
{
if ( UFunction* FunctionRef = CallFunctionNode->GetTargetFunction() )
{
const UEdGraphPin* WorldContextPin = CallFunctionNode->FindPin(FunctionRef->GetMetaData(FBlueprintMetadata::MD_WorldContext));
bShow = ( WorldContextPin != Pin );
}
}
else if (Cast<UK2Node_CreateDelegate>( Pin->GetOwningNode()))
{
bShow = false;
}
}
}
}
return bShow;
}
void UEdGraphSchema_K2::SetPinDefaultValue(UEdGraphPin* Pin, const UFunction* Function, const UProperty* Param) const
{
if ((Function != nullptr) && (Param != nullptr))
{
bool bHasAutomaticValue = false;
const FString MetadataDefaultValue = Function->GetMetaData(*Param->GetName());
if (!MetadataDefaultValue.IsEmpty())
{
// Specified default value in the metadata
Pin->AutogeneratedDefaultValue = MetadataDefaultValue;
bHasAutomaticValue = true;
}
else
{
const FName MetadataCppDefaultValueKey( *(FString(TEXT("CPP_Default_")) + Param->GetName()) );
const FString MetadataCppDefaultValue = Function->GetMetaData(MetadataCppDefaultValueKey);
if (!MetadataCppDefaultValue.IsEmpty())
{
Pin->AutogeneratedDefaultValue = MetadataCppDefaultValue;
bHasAutomaticValue = true;
}
}
if (bHasAutomaticValue)
{
if (Pin->PinType.PinCategory == PC_Text)
{
FString PackageNamespace;
#if USE_STABLE_LOCALIZATION_KEYS
if (GIsEditor)
{
PackageNamespace = TextNamespaceUtil::EnsurePackageNamespace(Pin->GetOwningNodeUnchecked());
}
#endif // USE_STABLE_LOCALIZATION_KEYS
if (!FTextStringHelper::ReadFromString(*Pin->AutogeneratedDefaultValue, Pin->DefaultTextValue, nullptr, *PackageNamespace))
{
Pin->DefaultTextValue = FText::FromString(Pin->AutogeneratedDefaultValue);
}
}
else
{
Pin->DefaultValue = Pin->AutogeneratedDefaultValue;
}
}
}
if (Pin->DefaultValue.Len() == 0)
{
// Set the default value to (T)0
SetPinDefaultValueBasedOnType(Pin);
}
}
void UEdGraphSchema_K2::SetPinDefaultValueBasedOnType(UEdGraphPin* Pin) const
{
// Create a useful default value based on the pin type
if(Pin->PinType.bIsArray)
{
Pin->AutogeneratedDefaultValue = TEXT("");
}
else if (Pin->PinType.PinCategory == PC_Int)
{
Pin->AutogeneratedDefaultValue = TEXT("0");
}
else if(Pin->PinType.PinCategory == PC_Byte)
{
UEnum* EnumPtr = Cast<UEnum>(Pin->PinType.PinSubCategoryObject.Get());
if(EnumPtr)
{
// First element of enum can change. If the enum is { A, B, C } and the default value is A,
// the defult value should not change when enum will be changed into { N, A, B, C }
Pin->AutogeneratedDefaultValue = TEXT("");
Pin->DefaultValue = EnumPtr->GetEnumName(0);
return;
}
else
{
Pin->AutogeneratedDefaultValue = TEXT("0");
}
}
else if (Pin->PinType.PinCategory == PC_Float)
{
Pin->AutogeneratedDefaultValue = TEXT("0.0");
}
else if (Pin->PinType.PinCategory == PC_Boolean)
{
Pin->AutogeneratedDefaultValue = TEXT("false");
}
else if (Pin->PinType.PinCategory == PC_Name)
{
Pin->AutogeneratedDefaultValue = TEXT("None");
}
else if ((Pin->PinType.PinCategory == PC_Struct) && ((Pin->PinType.PinSubCategoryObject == VectorStruct) || (Pin->PinType.PinSubCategoryObject == RotatorStruct)))
{
Pin->AutogeneratedDefaultValue = TEXT("0, 0, 0");
}
else
{
Pin->AutogeneratedDefaultValue = TEXT("");
}
Pin->DefaultValue = Pin->AutogeneratedDefaultValue;
}
void UEdGraphSchema_K2::ValidateExistingConnections(UEdGraphPin* Pin)
{
const UEdGraphSchema_K2* K2Schema = GetDefault<UEdGraphSchema_K2>();
const UBlueprint* Blueprint = FBlueprintEditorUtils::FindBlueprintForNode(Pin->GetOwningNodeUnchecked());
const UClass* CallingContext = Blueprint
? ((Blueprint->GeneratedClass != nullptr) ? Blueprint->GeneratedClass : Blueprint->ParentClass)
: nullptr;
// Break any newly invalid links
TArray<UEdGraphPin*> BrokenLinks;
for (int32 Index = 0; Index < Pin->LinkedTo.Num();)
{
UEdGraphPin* OtherPin = Pin->LinkedTo[Index];
if (K2Schema->ArePinsCompatible(Pin, OtherPin, CallingContext))
{
++Index;
}
else
{
OtherPin->LinkedTo.Remove(Pin);
Pin->LinkedTo.RemoveAtSwap(Index);
BrokenLinks.Add(OtherPin);
}
}
// Cascade the check for changed pin types
for (TArray<UEdGraphPin*>::TIterator PinIt(BrokenLinks); PinIt; ++PinIt)
{
UEdGraphPin* OtherPin = *PinIt;
OtherPin->GetOwningNode()->PinConnectionListChanged(OtherPin);
}
}
UFunction* UEdGraphSchema_K2::FindSetVariableByNameFunction(const FEdGraphPinType& PinType)
{
//!!!! Keep this function synced with FExposeOnSpawnValidator::IsSupported !!!!
struct FIsCustomStructureParamHelper
{
static bool Is(const UObject* Obj)
{
static const FName BlueprintTypeName(TEXT("BlueprintType"));
const auto Struct = Cast<const UScriptStruct>(Obj);
return Struct ? Struct->GetBoolMetaData(BlueprintTypeName) : false;
}
};
const UEdGraphSchema_K2* K2Schema = GetDefault<UEdGraphSchema_K2>();
FName SetFunctionName = NAME_None;
if(PinType.PinCategory == K2Schema->PC_Int)
{
static FName SetIntName(GET_FUNCTION_NAME_CHECKED(UKismetSystemLibrary, SetIntPropertyByName));
SetFunctionName = SetIntName;
}
else if(PinType.PinCategory == K2Schema->PC_Byte)
{
static FName SetByteName(GET_FUNCTION_NAME_CHECKED(UKismetSystemLibrary, SetBytePropertyByName));
SetFunctionName = SetByteName;
}
else if(PinType.PinCategory == K2Schema->PC_Float)
{
static FName SetFloatName(GET_FUNCTION_NAME_CHECKED(UKismetSystemLibrary, SetFloatPropertyByName));
SetFunctionName = SetFloatName;
}
else if(PinType.PinCategory == K2Schema->PC_Boolean)
{
static FName SetBoolName(GET_FUNCTION_NAME_CHECKED(UKismetSystemLibrary, SetBoolPropertyByName));
SetFunctionName = SetBoolName;
}
else if(PinType.PinCategory == K2Schema->PC_Object)
{
static FName SetObjectName(GET_FUNCTION_NAME_CHECKED(UKismetSystemLibrary, SetObjectPropertyByName));
SetFunctionName = SetObjectName;
}
else if (PinType.PinCategory == K2Schema->PC_Class)
{
static FName SetObjectName(GET_FUNCTION_NAME_CHECKED(UKismetSystemLibrary, SetClassPropertyByName));
SetFunctionName = SetObjectName;
}
else if(PinType.PinCategory == K2Schema->PC_Interface)
{
static FName SetObjectName(GET_FUNCTION_NAME_CHECKED(UKismetSystemLibrary, SetInterfacePropertyByName));
SetFunctionName = SetObjectName;
}
else if(PinType.PinCategory == K2Schema->PC_String)
{
static FName SetStringName(GET_FUNCTION_NAME_CHECKED(UKismetSystemLibrary, SetStringPropertyByName));
SetFunctionName = SetStringName;
}
else if ( PinType.PinCategory == K2Schema->PC_Text)
{
static FName SetTextName(GET_FUNCTION_NAME_CHECKED(UKismetSystemLibrary, SetTextPropertyByName));
SetFunctionName = SetTextName;
}
else if (PinType.PinCategory == K2Schema->PC_Asset)
{
static FName SetAssetName(GET_FUNCTION_NAME_CHECKED(UKismetSystemLibrary, SetAssetPropertyByName));
SetFunctionName = SetAssetName;
}
else if (PinType.PinCategory == K2Schema->PC_AssetClass)
{
static FName SetAssetClassName(GET_FUNCTION_NAME_CHECKED(UKismetSystemLibrary, SetAssetClassPropertyByName));
SetFunctionName = SetAssetClassName;
}
else if(PinType.PinCategory == K2Schema->PC_Name)
{
static FName SetNameName(GET_FUNCTION_NAME_CHECKED(UKismetSystemLibrary, SetNamePropertyByName));
SetFunctionName = SetNameName;
}
else if(PinType.PinCategory == K2Schema->PC_Struct && PinType.PinSubCategoryObject == VectorStruct)
{
static FName SetVectorName(GET_FUNCTION_NAME_CHECKED(UKismetSystemLibrary, SetVectorPropertyByName));
SetFunctionName = SetVectorName;
}
else if(PinType.PinCategory == K2Schema->PC_Struct && PinType.PinSubCategoryObject == RotatorStruct)
{
static FName SetRotatorName(GET_FUNCTION_NAME_CHECKED(UKismetSystemLibrary, SetRotatorPropertyByName));
SetFunctionName = SetRotatorName;
}
else if(PinType.PinCategory == K2Schema->PC_Struct && PinType.PinSubCategoryObject == ColorStruct)
{
static FName SetLinearColorName(GET_FUNCTION_NAME_CHECKED(UKismetSystemLibrary, SetLinearColorPropertyByName));
SetFunctionName = SetLinearColorName;
}
else if(PinType.PinCategory == K2Schema->PC_Struct && PinType.PinSubCategoryObject == TransformStruct)
{
static FName SetTransformName(GET_FUNCTION_NAME_CHECKED(UKismetSystemLibrary, SetTransformPropertyByName));
SetFunctionName = SetTransformName;
}
else if (PinType.PinCategory == K2Schema->PC_Struct && PinType.PinSubCategoryObject == FCollisionProfileName::StaticStruct())
{
static FName SetStructureName(GET_FUNCTION_NAME_CHECKED(UKismetSystemLibrary, SetCollisionProfileNameProperty));
SetFunctionName = SetStructureName;
}
else if (PinType.PinCategory == K2Schema->PC_Struct && FIsCustomStructureParamHelper::Is(PinType.PinSubCategoryObject.Get()))
{
static FName SetStructureName(GET_FUNCTION_NAME_CHECKED(UKismetSystemLibrary, SetStructurePropertyByName));
SetFunctionName = SetStructureName;
}
UFunction* Function = NULL;
if(SetFunctionName != NAME_None)
{
if(PinType.bIsArray)
{
static FName SetArrayName(GET_FUNCTION_NAME_CHECKED(UKismetArrayLibrary, SetArrayPropertyByName));
Function = FindField<UFunction>(UKismetArrayLibrary::StaticClass(), SetArrayName);
}
else
{
Function = FindField<UFunction>(UKismetSystemLibrary::StaticClass(), SetFunctionName);
}
}
return Function;
}
bool UEdGraphSchema_K2::CanPromotePinToVariable( const UEdGraphPin& Pin ) const
{
const FEdGraphPinType& PinType = Pin.PinType;
bool bCanPromote = (PinType.PinCategory != PC_Wildcard && PinType.PinCategory != PC_Exec ) ? true : false;
const UK2Node* Node = Cast<UK2Node>(Pin.GetOwningNode());
const UBlueprint* OwningBlueprint = Node->GetBlueprint();
if (Pin.bNotConnectable)
{
bCanPromote = false;
}
else if (!OwningBlueprint || (OwningBlueprint->BlueprintType == BPTYPE_MacroLibrary) || (OwningBlueprint->BlueprintType == BPTYPE_FunctionLibrary) || IsStaticFunctionGraph(Node->GetGraph()))
{
// Never allow promotion in macros, because there's not a scope to define them in
bCanPromote = false;
}
else
{
if (PinType.PinCategory == PC_Delegate)
{
bCanPromote = false;
}
else if ((PinType.PinCategory == PC_Object) || (PinType.PinCategory == PC_Interface))
{
if (PinType.PinSubCategoryObject != NULL)
{
if (UClass* Class = Cast<UClass>(PinType.PinSubCategoryObject.Get()))
{
bCanPromote = UEdGraphSchema_K2::IsAllowableBlueprintVariableType(Class);
}
}
}
else if ((PinType.PinCategory == PC_Struct) && (PinType.PinSubCategoryObject != NULL))
{
if (UScriptStruct* Struct = Cast<UScriptStruct>(PinType.PinSubCategoryObject.Get()))
{
bCanPromote = UEdGraphSchema_K2::IsAllowableBlueprintVariableType(Struct);
}
}
}
return bCanPromote;
}
bool UEdGraphSchema_K2::CanSplitStructPin( const UEdGraphPin& Pin ) const
{
return (!Pin.bNotConnectable && Pin.LinkedTo.Num() == 0 && PinHasSplittableStructType(&Pin) && Pin.GetOwningNode()->AllowSplitPins());
}
bool UEdGraphSchema_K2::CanRecombineStructPin( const UEdGraphPin& Pin ) const
{
bool bCanRecombine = (Pin.ParentPin != NULL && Pin.LinkedTo.Num() == 0);
if (bCanRecombine)
{
// Go through all the other subpins and ensure they also are not connected to anything
TArray<UEdGraphPin*> PinsToExamine = Pin.ParentPin->SubPins;
int32 PinIndex = 0;
while (bCanRecombine && PinIndex < PinsToExamine.Num())
{
UEdGraphPin* SubPin = PinsToExamine[PinIndex];
if (SubPin->LinkedTo.Num() > 0)
{
bCanRecombine = false;
}
else if (SubPin->SubPins.Num() > 0)
{
PinsToExamine.Append(SubPin->SubPins);
}
++PinIndex;
}
}
return bCanRecombine;
}
void UEdGraphSchema_K2::GetGraphDisplayInformation(const UEdGraph& Graph, /*out*/ FGraphDisplayInfo& DisplayInfo) const
{
DisplayInfo.DocLink = TEXT("Shared/Editors/BlueprintEditor/GraphTypes");
DisplayInfo.PlainName = FText::FromString( Graph.GetName() ); // Fallback is graph name
UFunction* Function = NULL;
UBlueprint* Blueprint = FBlueprintEditorUtils::FindBlueprintForGraph(&Graph);
if (Blueprint && Blueprint->SkeletonGeneratedClass)
{
Function = Blueprint->SkeletonGeneratedClass->FindFunctionByName(Graph.GetFName());
}
const EGraphType GraphType = GetGraphType(&Graph);
if (GraphType == GT_Ubergraph)
{
DisplayInfo.DocExcerptName = TEXT("EventGraph");
if (Graph.GetFName() == GN_EventGraph)
{
// localized name for the first event graph
DisplayInfo.PlainName = LOCTEXT("GraphDisplayName_EventGraph", "EventGraph");
DisplayInfo.Tooltip = DisplayInfo.PlainName.ToString();
}
else
{
DisplayInfo.Tooltip = Graph.GetName();
}
}
else if (GraphType == GT_Function)
{
if ( Graph.GetFName() == FN_UserConstructionScript )
{
DisplayInfo.PlainName = LOCTEXT("GraphDisplayName_ConstructionScript", "ConstructionScript");
DisplayInfo.Tooltip = LOCTEXT("GraphTooltip_ConstructionScript", "Function executed when Blueprint is placed or modified.").ToString();
DisplayInfo.DocExcerptName = TEXT("ConstructionScript");
}
else
{
// If we found a function from this graph..
if (Function)
{
DisplayInfo.PlainName = FText::FromString(Function->GetName());
DisplayInfo.Tooltip = UK2Node_CallFunction::GetDefaultTooltipForFunction(Function); // grab its tooltip
}
else
{
DisplayInfo.Tooltip = Graph.GetName();
}
DisplayInfo.DocExcerptName = TEXT("FunctionGraph");
}
}
else if (GraphType == GT_Macro)
{
// Show macro description if set
FKismetUserDeclaredFunctionMetadata* MetaData = UK2Node_MacroInstance::GetAssociatedGraphMetadata(&Graph);
DisplayInfo.Tooltip = (MetaData && MetaData->ToolTip.Len() > 0) ? MetaData->ToolTip : Graph.GetName();
DisplayInfo.DocExcerptName = TEXT("MacroGraph");
}
else if (GraphType == GT_Animation)
{
DisplayInfo.PlainName = LOCTEXT("GraphDisplayName_AnimGraph", "AnimGraph");
DisplayInfo.Tooltip = LOCTEXT("GraphTooltip_AnimGraph", "Graph used to blend together different animations.").ToString();
DisplayInfo.DocExcerptName = TEXT("AnimGraph");
}
else if (GraphType == GT_StateMachine)
{
DisplayInfo.Tooltip = Graph.GetName();
DisplayInfo.DocExcerptName = TEXT("StateMachine");
}
// Add pure/static/const to notes if set
if (Function)
{
if(Function->HasAnyFunctionFlags(FUNC_BlueprintPure))
{
DisplayInfo.Notes.Add(TEXT("pure"));
}
// since 'static' is implied in a function library, not going to display it (to be consistent with previous behavior)
if(Function->HasAnyFunctionFlags(FUNC_Static) && Blueprint->BlueprintType != BPTYPE_FunctionLibrary)
{
DisplayInfo.Notes.Add(TEXT("static"));
}
else if(Function->HasAnyFunctionFlags(FUNC_Const))
{
DisplayInfo.Notes.Add(TEXT("const"));
}
}
// Mark transient graphs as obviously so
if (Graph.HasAllFlags(RF_Transient))
{
DisplayInfo.PlainName = FText::FromString( FString::Printf(TEXT("$$ %s $$"), *DisplayInfo.PlainName.ToString()) );
DisplayInfo.Notes.Add(TEXT("intermediate build product"));
}
if( GEditor && GetDefault<UEditorStyleSettings>()->bShowFriendlyNames )
{
if (GraphType == GT_Function && Function)
{
DisplayInfo.DisplayName = GetFriendlySignatureName(Function);
}
else
{
DisplayInfo.DisplayName = FText::FromString(FName::NameToDisplayString(DisplayInfo.PlainName.ToString(), false));
}
}
else
{
DisplayInfo.DisplayName = DisplayInfo.PlainName;
}
}
bool UEdGraphSchema_K2::IsSelfPin(const UEdGraphPin& Pin) const
{
return (Pin.PinName == PN_Self);
}
bool UEdGraphSchema_K2::IsDelegateCategory(const FString& Category) const
{
return (Category == PC_Delegate);
}
FVector2D UEdGraphSchema_K2::CalculateAveragePositionBetweenNodes(UEdGraphPin* InputPin, UEdGraphPin* OutputPin)
{
UEdGraphNode* InputNode = InputPin->GetOwningNode();
UEdGraphNode* OutputNode = OutputPin->GetOwningNode();
const FVector2D InputCorner(InputNode->NodePosX, InputNode->NodePosY);
const FVector2D OutputCorner(OutputNode->NodePosX, OutputNode->NodePosY);
return (InputCorner + OutputCorner) * 0.5f;
}
bool UEdGraphSchema_K2::IsConstructionScript(const UEdGraph* TestEdGraph) const
{
TArray<class UK2Node_FunctionEntry*> EntryNodes;
TestEdGraph->GetNodesOfClass<UK2Node_FunctionEntry>(EntryNodes);
bool bIsConstructionScript = false;
if (EntryNodes.Num() > 0)
{
UK2Node_FunctionEntry const* const EntryNode = EntryNodes[0];
bIsConstructionScript = (EntryNode->SignatureName == FN_UserConstructionScript);
}
return bIsConstructionScript;
}
bool UEdGraphSchema_K2::IsCompositeGraph( const UEdGraph* TestEdGraph ) const
{
check(TestEdGraph);
const EGraphType GraphType = GetGraphType(TestEdGraph);
if(GraphType == GT_Function)
{
//Find the Tunnel node for composite graph and see if its output is a composite node
for(auto I = TestEdGraph->Nodes.CreateConstIterator();I;++I)
{
UEdGraphNode* Node = *I;
if(auto Tunnel = Cast<UK2Node_Tunnel>(Node))
{
if(auto OutNode = Tunnel->OutputSourceNode)
{
if(Cast<UK2Node_Composite>(OutNode))
{
return true;
}
}
}
}
}
return false;
}
bool UEdGraphSchema_K2::IsConstFunctionGraph( const UEdGraph* TestEdGraph, bool* bOutIsEnforcingConstCorrectness ) const
{
check(TestEdGraph);
const EGraphType GraphType = GetGraphType(TestEdGraph);
if(GraphType == GT_Function)
{
// Find the entry node for the function graph and see if the 'const' flag is set
for(auto I = TestEdGraph->Nodes.CreateConstIterator(); I; ++I)
{
UEdGraphNode* Node = *I;
if(auto EntryNode = Cast<UK2Node_FunctionEntry>(Node))
{
if(bOutIsEnforcingConstCorrectness != nullptr)
{
*bOutIsEnforcingConstCorrectness = EntryNode->bEnforceConstCorrectness;
}
return (EntryNode->GetFunctionFlags() & FUNC_Const) != 0;
}
}
}
if(bOutIsEnforcingConstCorrectness != nullptr)
{
*bOutIsEnforcingConstCorrectness = false;
}
return false;
}
bool UEdGraphSchema_K2::IsStaticFunctionGraph( const UEdGraph* TestEdGraph ) const
{
check(TestEdGraph);
const auto Blueprint = FBlueprintEditorUtils::FindBlueprintForGraph(TestEdGraph);
if (Blueprint && (EBlueprintType::BPTYPE_FunctionLibrary == Blueprint->BlueprintType))
{
return true;
}
const EGraphType GraphType = GetGraphType(TestEdGraph);
if(GraphType == GT_Function)
{
// Find the entry node for the function graph and see if the 'static' flag is set
for(auto I = TestEdGraph->Nodes.CreateConstIterator(); I; ++I)
{
UEdGraphNode* Node = *I;
if(auto EntryNode = Cast<UK2Node_FunctionEntry>(Node))
{
return (EntryNode->GetFunctionFlags() & FUNC_Static) != 0;
}
}
}
return false;
}
void UEdGraphSchema_K2::DroppedAssetsOnGraph(const TArray<FAssetData>& Assets, const FVector2D& GraphPosition, UEdGraph* Graph) const
{
UBlueprint* Blueprint = FBlueprintEditorUtils::FindBlueprintForGraph(Graph);
if ((Blueprint != NULL) && FBlueprintEditorUtils::IsActorBased(Blueprint))
{
float XOffset = 0.0f;
for(int32 AssetIdx=0; AssetIdx < Assets.Num(); AssetIdx++)
{
FVector2D Position = GraphPosition + (AssetIdx * FVector2D(XOffset, 0.0f));
UObject* Asset = Assets[AssetIdx].GetAsset();
UClass* AssetClass = Asset->GetClass();
if (UBlueprint* BlueprintAsset = Cast<UBlueprint>(Asset))
{
AssetClass = BlueprintAsset->GeneratedClass;
}
TSubclassOf<UActorComponent> DestinationComponentType = FComponentAssetBrokerage::GetPrimaryComponentForAsset(AssetClass);
if ((DestinationComponentType == NULL) && AssetClass->IsChildOf(AActor::StaticClass()))
{
DestinationComponentType = UChildActorComponent::StaticClass();
}
// Make sure we have an asset type that's registered with the component list
if (DestinationComponentType != NULL)
{
UEdGraph* TempOuter = NewObject<UEdGraph>((UObject*)Blueprint);
TempOuter->SetFlags(RF_Transient);
FComponentTypeEntry ComponentType = { FString(), FString(), DestinationComponentType };
IBlueprintNodeBinder::FBindingSet Bindings;
Bindings.Add(Asset);
UBlueprintComponentNodeSpawner::Create(ComponentType)->Invoke(Graph, Bindings, GraphPosition);
}
}
}
}
void UEdGraphSchema_K2::DroppedAssetsOnNode(const TArray<FAssetData>& Assets, const FVector2D& GraphPosition, UEdGraphNode* Node) const
{
// @TODO: Should dropping on component node change the component?
}
void UEdGraphSchema_K2::DroppedAssetsOnPin(const TArray<FAssetData>& Assets, const FVector2D& GraphPosition, UEdGraphPin* Pin) const
{
// If dropping onto an 'object' pin, try and set the literal
if ((Pin->PinType.PinCategory == PC_Object) || (Pin->PinType.PinCategory == PC_Interface))
{
UClass* PinClass = Cast<UClass>(Pin->PinType.PinSubCategoryObject.Get());
if(PinClass != NULL)
{
// Find first asset of type of the pin
UObject* Asset = FAssetData::GetFirstAssetDataOfClass(Assets, PinClass).GetAsset();
if(Asset != NULL)
{
TrySetDefaultObject(*Pin, Asset);
}
}
}
}
void UEdGraphSchema_K2::GetAssetsNodeHoverMessage(const TArray<FAssetData>& Assets, const UEdGraphNode* HoverNode, FString& OutTooltipText, bool& OutOkIcon) const
{
// No comment at the moment because this doesn't do anything
OutTooltipText = TEXT("");
OutOkIcon = false;
}
void UEdGraphSchema_K2::GetAssetsPinHoverMessage(const TArray<FAssetData>& Assets, const UEdGraphPin* HoverPin, FString& OutTooltipText, bool& OutOkIcon) const
{
OutTooltipText = TEXT("");
OutOkIcon = false;
// If dropping onto an 'object' pin, try and set the literal
if ((HoverPin->PinType.PinCategory == PC_Object) || (HoverPin->PinType.PinCategory == PC_Interface))
{
UClass* PinClass = Cast<UClass>(HoverPin->PinType.PinSubCategoryObject.Get());
if(PinClass != NULL)
{
// Find first asset of type of the pin
FAssetData AssetData = FAssetData::GetFirstAssetDataOfClass(Assets, PinClass);
if(AssetData.IsValid())
{
OutOkIcon = true;
OutTooltipText = FString::Printf(TEXT("Assign %s to this pin"), *(AssetData.AssetName.ToString()));
}
else
{
OutOkIcon = false;
OutTooltipText = FString::Printf(TEXT("Not compatible with this pin"));
}
}
}
}
bool UEdGraphSchema_K2::FadeNodeWhenDraggingOffPin(const UEdGraphNode* Node, const UEdGraphPin* Pin) const
{
if(Node && Pin && (PC_Delegate == Pin->PinType.PinCategory) && (EGPD_Input == Pin->Direction))
{
//When dragging off a delegate pin, we should duck the alpha of all nodes except the Custom Event nodes that are compatible with the delegate signature
//This would help reinforce the connection between delegates and their matching events, and make it easier to see at a glance what could be matched up.
if(const UK2Node_Event* EventNode = Cast<const UK2Node_Event>(Node))
{
const UEdGraphPin* DelegateOutPin = EventNode->FindPin(UK2Node_Event::DelegateOutputName);
if ((NULL != DelegateOutPin) &&
(ECanCreateConnectionResponse::CONNECT_RESPONSE_DISALLOW != CanCreateConnection(DelegateOutPin, Pin).Response))
{
return false;
}
}
if(const UK2Node_CreateDelegate* CreateDelegateNode = Cast<const UK2Node_CreateDelegate>(Node))
{
const UEdGraphPin* DelegateOutPin = CreateDelegateNode->GetDelegateOutPin();
if ((NULL != DelegateOutPin) &&
(ECanCreateConnectionResponse::CONNECT_RESPONSE_DISALLOW != CanCreateConnection(DelegateOutPin, Pin).Response))
{
return false;
}
}
return true;
}
return false;
}
struct FBackwardCompatibilityConversionHelper
{
static bool ConvertNode(
UK2Node* OldNode,
const FString& BlueprintPinName,
UK2Node* NewNode,
const FString& ClassPinName,
const UEdGraphSchema_K2& Schema,
bool bOnlyWithDefaultBlueprint)
{
check(OldNode && NewNode);
const auto Blueprint = OldNode->GetBlueprint();
auto Graph = OldNode->GetGraph();
if (!Graph)
{
UE_LOG(LogBlueprint, Warning, TEXT("BackwardCompatibilityNodeConversion Error bp: '%s' node: '%s'. No graph containing the node."),
Blueprint ? *Blueprint->GetName() : TEXT("Unknown"),
*OldNode->GetName(),
*BlueprintPinName);
return false;
}
auto OldBlueprintPin = OldNode->FindPin(BlueprintPinName);
if (!OldBlueprintPin)
{
UE_LOG(LogBlueprint, Warning, TEXT("BackwardCompatibilityNodeConversion Error bp: '%s' node: '%s'. No bp pin found '%s'"),
Blueprint ? *Blueprint->GetName() : TEXT("Unknown"),
*OldNode->GetName(),
*BlueprintPinName);
return false;
}
const bool bNondefaultBPConnected = (OldBlueprintPin->LinkedTo.Num() > 0);
const bool bTryConvert = !bNondefaultBPConnected || !bOnlyWithDefaultBlueprint;
if (bTryConvert)
{
// CREATE NEW NODE
NewNode->SetFlags(RF_Transactional);
Graph->AddNode(NewNode, false, false);
NewNode->CreateNewGuid();
NewNode->PostPlacedNewNode();
NewNode->AllocateDefaultPins();
NewNode->NodePosX = OldNode->NodePosX;
NewNode->NodePosY = OldNode->NodePosY;
const auto ClassPin = NewNode->FindPin(ClassPinName);
if (!ClassPin)
{
UE_LOG(LogBlueprint, Warning, TEXT("BackwardCompatibilityNodeConversion Error bp: '%s' node: '%s'. No class pin found '%s'"),
Blueprint ? *Blueprint->GetName() : TEXT("Unknown"),
*NewNode->GetName(),
*ClassPinName);
return false;
}
auto TargetClass = Cast<UClass>(ClassPin->PinType.PinSubCategoryObject.Get());
if (!TargetClass)
{
UE_LOG(LogBlueprint, Warning, TEXT("BackwardCompatibilityNodeConversion Error bp: '%s' node: '%s'. No class found '%s'"),
Blueprint ? *Blueprint->GetName() : TEXT("Unknown"),
*NewNode->GetName(),
*ClassPinName);
return false;
}
// REPLACE BLUEPRINT WITH CLASS
if (!bNondefaultBPConnected)
{
// DEFAULT VALUE
const auto UsedBlueprint = Cast<UBlueprint>(OldBlueprintPin->DefaultObject);
ensure(!OldBlueprintPin->DefaultObject || UsedBlueprint);
ensure(!UsedBlueprint || *UsedBlueprint->GeneratedClass);
UClass* UsedClass = UsedBlueprint ? *UsedBlueprint->GeneratedClass : NULL;
Schema.TrySetDefaultObject(*ClassPin, UsedClass);
if (ClassPin->DefaultObject != UsedClass)
{
auto ErrorStr = Schema.IsPinDefaultValid(ClassPin, FString(), UsedClass, FText());
UE_LOG(LogBlueprint, Warning, TEXT("BackwardCompatibilityNodeConversion Error 'cannot set class' in blueprint: %s node: '%s' actor bp: %s, reason: %s"),
Blueprint ? *Blueprint->GetName() : TEXT("Unknown"),
*OldNode->GetName(),
UsedBlueprint ? *UsedBlueprint->GetName() : TEXT("Unknown"),
ErrorStr.IsEmpty() ? TEXT("Unknown") : *ErrorStr);
return false;
}
}
else
{
// LINK
auto CastNode = NewObject<UK2Node_ClassDynamicCast>(Graph);
CastNode->SetFlags(RF_Transactional);
CastNode->TargetType = TargetClass;
Graph->AddNode(CastNode, false, false);
CastNode->CreateNewGuid();
CastNode->PostPlacedNewNode();
CastNode->AllocateDefaultPins();
const int32 OffsetOnGraph = 200;
CastNode->NodePosX = OldNode->NodePosX - OffsetOnGraph;
CastNode->NodePosY = OldNode->NodePosY;
auto ExecPin = OldNode->GetExecPin();
auto ExecCastPin = CastNode->GetExecPin();
check(ExecCastPin);
if (!ExecPin || !Schema.MovePinLinks(*ExecPin, *ExecCastPin).CanSafeConnect())
{
UE_LOG(LogBlueprint, Warning, TEXT("BackwardCompatibilityNodeConversion Error 'cannot connect' in blueprint: %s, pin: %s"),
Blueprint ? *Blueprint->GetName() : TEXT("Unknown"),
*ExecCastPin->PinName);
return false;
}
auto ValidCastPin = CastNode->GetValidCastPin();
check(ValidCastPin);
if (!Schema.TryCreateConnection(ValidCastPin, ExecPin))
{
UE_LOG(LogBlueprint, Warning, TEXT("BackwardCompatibilityNodeConversion Error 'cannot connect' in blueprint: %s, pin: %s"),
Blueprint ? *Blueprint->GetName() : TEXT("Unknown"),
*ValidCastPin->PinName);
return false;
}
auto InValidCastPin = CastNode->GetInvalidCastPin();
check(InValidCastPin);
if (!Schema.TryCreateConnection(InValidCastPin, ExecPin))
{
UE_LOG(LogBlueprint, Warning, TEXT("BackwardCompatibilityNodeConversion Error 'cannot connect' in blueprint: %s, pin: %s"),
Blueprint ? *Blueprint->GetName() : TEXT("Unknown"),
*InValidCastPin->PinName);
return false;
}
auto CastSourcePin = CastNode->GetCastSourcePin();
check(CastSourcePin);
if (!Schema.MovePinLinks(*OldBlueprintPin, *CastSourcePin).CanSafeConnect())
{
UE_LOG(LogBlueprint, Warning, TEXT("BackwardCompatibilityNodeConversion Error 'cannot connect' in blueprint: %s, pin: %s"),
Blueprint ? *Blueprint->GetName() : TEXT("Unknown"),
*CastSourcePin->PinName);
return false;
}
auto CastResultPin = CastNode->GetCastResultPin();
check(CastResultPin);
if (!Schema.TryCreateConnection(CastResultPin, ClassPin))
{
UE_LOG(LogBlueprint, Warning, TEXT("BackwardCompatibilityNodeConversion Error 'cannot connect' in blueprint: %s, pin: %s"),
Blueprint ? *Blueprint->GetName() : TEXT("Unknown"),
*CastResultPin->PinName);
return false;
}
}
// MOVE OTHER PINS
TArray<UEdGraphPin*> OldPins;
OldPins.Add(OldBlueprintPin);
for (auto PinIter = NewNode->Pins.CreateIterator(); PinIter; ++PinIter)
{
UEdGraphPin* const Pin = *PinIter;
check(Pin);
if (ClassPin != Pin)
{
const auto OldPin = OldNode->FindPin(Pin->PinName);
if(NULL != OldPin)
{
OldPins.Add(OldPin);
if (!Schema.MovePinLinks(*OldPin, *Pin).CanSafeConnect())
{
UE_LOG(LogBlueprint, Warning, TEXT("BackwardCompatibilityNodeConversion Error 'cannot connect' in blueprint: %s, pin: %s"),
Blueprint ? *Blueprint->GetName() : TEXT("Unknown"),
*Pin->PinName);
}
}
else
{
UE_LOG(LogBlueprint, Warning, TEXT("BackwardCompatibilityNodeConversion Error 'missing old pin' in blueprint: %s"),
Blueprint ? *Blueprint->GetName() : TEXT("Unknown"),
Pin ? *Pin->PinName : TEXT("Unknown"));
}
}
}
OldNode->BreakAllNodeLinks();
for (auto PinIter = OldNode->Pins.CreateIterator(); PinIter; ++PinIter)
{
if(!OldPins.Contains(*PinIter))
{
UEdGraphPin* Pin = *PinIter;
UE_LOG(LogBlueprint, Warning, TEXT("BackwardCompatibilityNodeConversion Error 'missing new pin' in blueprint: %s"),
Blueprint ? *Blueprint->GetName() : TEXT("Unknown"),
Pin ? *Pin->PinName : TEXT("Unknown"));
}
}
Graph->RemoveNode(OldNode);
return true;
}
return false;
}
struct FFunctionCallParams
{
const FName OldFuncName;
const FName NewFuncName;
const FString& BlueprintPinName;
const FString& ClassPinName;
const UClass* FuncScope;
FFunctionCallParams(FName InOldFunc, FName InNewFunc, const FString& InBlueprintPinName, const FString& InClassPinName, const UClass* InFuncScope)
: OldFuncName(InOldFunc), NewFuncName(InNewFunc), BlueprintPinName(InBlueprintPinName), ClassPinName(InClassPinName), FuncScope(InFuncScope)
{
check(FuncScope);
}
FFunctionCallParams(const FBlueprintCallableFunctionRedirect& FunctionRedirect)
: OldFuncName(*FunctionRedirect.OldFunctionName)
, NewFuncName(*FunctionRedirect.NewFunctionName)
, BlueprintPinName(FunctionRedirect.BlueprintParamName)
, ClassPinName(FunctionRedirect.ClassParamName)
, FuncScope(NULL)
{
FuncScope = FindObject<UClass>(ANY_PACKAGE, *FunctionRedirect.ClassName);
}
};
static void ConvertFunctionCallNodes(const FFunctionCallParams& ConversionParams, TArray<UK2Node_CallFunction*>& Nodes, UEdGraph* Graph, const UEdGraphSchema_K2& Schema, bool bOnlyWithDefaultBlueprint)
{
if (ConversionParams.FuncScope)
{
const UFunction* OldFunc = ConversionParams.FuncScope->FindFunctionByName(ConversionParams.OldFuncName);
check(OldFunc);
const UFunction* NewFunc = ConversionParams.FuncScope->FindFunctionByName(ConversionParams.NewFuncName);
check(NewFunc);
for (auto It = Nodes.CreateIterator(); It; ++It)
{
if (OldFunc == (*It)->GetTargetFunction())
{
auto NewNode = NewObject<UK2Node_CallFunction>(Graph);
NewNode->SetFromFunction(NewFunc);
ConvertNode(*It, ConversionParams.BlueprintPinName, NewNode,
ConversionParams.ClassPinName, Schema, bOnlyWithDefaultBlueprint);
}
}
}
}
};
void UEdGraphSchema_K2::BackwardCompatibilityNodeConversion(UEdGraph* Graph, bool bOnlySafeChanges) const
{
if (Graph)
{
{
static const FString BlueprintPinName(TEXT("Blueprint"));
static const FString ClassPinName(TEXT("Class"));
TArray<UK2Node_SpawnActor*> SpawnActorNodes;
Graph->GetNodesOfClass(SpawnActorNodes);
for (auto It = SpawnActorNodes.CreateIterator(); It; ++It)
{
FBackwardCompatibilityConversionHelper::ConvertNode(
*It, BlueprintPinName, NewObject<UK2Node_SpawnActorFromClass>(Graph),
ClassPinName, *this, bOnlySafeChanges);
}
}
{
auto Blueprint = FBlueprintEditorUtils::FindBlueprintForGraph(Graph);
if (Blueprint && *Blueprint->SkeletonGeneratedClass)
{
TArray<UK2Node_CallFunction*> Nodes;
Graph->GetNodesOfClass(Nodes);
for (const auto& FunctionRedirect : EditoronlyBPFunctionRedirects)
{
FBackwardCompatibilityConversionHelper::ConvertFunctionCallNodes(
FBackwardCompatibilityConversionHelper::FFunctionCallParams(FunctionRedirect),
Nodes, Graph, *this, bOnlySafeChanges);
}
}
else
{
UE_LOG(LogBlueprint, Log, TEXT("BackwardCompatibilityNodeConversion: Blueprint '%s' cannot be fully converted. It has no skeleton class!"),
Blueprint ? *Blueprint->GetName() : TEXT("Unknown"));
}
}
/** Fix the old Make/Break Vector, Make/Break Vector 2D, and Make/Break Rotator nodes to use the native function call versions */
TArray<UK2Node_MakeStruct*> MakeStructNodes;
Graph->GetNodesOfClass<UK2Node_MakeStruct>(MakeStructNodes);
for(auto It = MakeStructNodes.CreateIterator(); It; ++It)
{
UK2Node_MakeStruct* OldMakeStructNode = *It;
check(NULL != OldMakeStructNode);
// user may have since deleted the struct type
if (OldMakeStructNode->StructType == NULL)
{
continue;
}
// Check to see if the struct has a native make/break that we should try to convert to.
if (OldMakeStructNode->StructType && OldMakeStructNode->StructType->HasMetaData(TEXT("HasNativeMake")))
{
UFunction* MakeNodeFunction = NULL;
// If any pins need to change their names during the conversion, add them to the map.
TMap<FString, FString> OldPinToNewPinMap;
if(OldMakeStructNode->StructType->GetName() == TEXT("Rotator"))
{
MakeNodeFunction = UKismetMathLibrary::StaticClass()->FindFunctionByName(GET_FUNCTION_NAME_CHECKED(UKismetMathLibrary, MakeRotator));
OldPinToNewPinMap.Add(TEXT("Rotator"), TEXT("ReturnValue"));
}
else if(OldMakeStructNode->StructType->GetName() == TEXT("Vector"))
{
MakeNodeFunction = UKismetMathLibrary::StaticClass()->FindFunctionByName(GET_FUNCTION_NAME_CHECKED(UKismetMathLibrary, MakeVector));
OldPinToNewPinMap.Add(TEXT("Vector"), TEXT("ReturnValue"));
}
else if(OldMakeStructNode->StructType->GetName() == TEXT("Vector2D"))
{
MakeNodeFunction = UKismetMathLibrary::StaticClass()->FindFunctionByName(GET_FUNCTION_NAME_CHECKED(UKismetMathLibrary, MakeVector2D));
OldPinToNewPinMap.Add(TEXT("Vector2D"), TEXT("ReturnValue"));
}
if(MakeNodeFunction)
{
UK2Node_CallFunction* CallFunctionNode = NewObject<UK2Node_CallFunction>(Graph);
check(CallFunctionNode);
CallFunctionNode->SetFlags(RF_Transactional);
Graph->AddNode(CallFunctionNode, false, false);
CallFunctionNode->SetFromFunction(MakeNodeFunction);
CallFunctionNode->CreateNewGuid();
CallFunctionNode->PostPlacedNewNode();
CallFunctionNode->AllocateDefaultPins();
CallFunctionNode->NodePosX = OldMakeStructNode->NodePosX;
CallFunctionNode->NodePosY = OldMakeStructNode->NodePosY;
for(int32 PinIdx = 0; PinIdx < OldMakeStructNode->Pins.Num(); ++PinIdx)
{
UEdGraphPin* OldPin = OldMakeStructNode->Pins[PinIdx];
UEdGraphPin* NewPin = NULL;
// Check to see if the pin name is mapped to a new one, if it is use it, otherwise just search for the pin under the old name
FString* NewPinNamePtr = OldPinToNewPinMap.Find(OldPin->PinName);
if(NewPinNamePtr)
{
NewPin = CallFunctionNode->FindPin(*NewPinNamePtr);
}
else
{
NewPin = CallFunctionNode->FindPin(OldPin->PinName);
}
check(NewPin);
if(!Graph->GetSchema()->MovePinLinks(*OldPin, *NewPin).CanSafeConnect())
{
const UBlueprint* Blueprint = OldMakeStructNode->GetBlueprint();
UE_LOG(LogBlueprint, Warning, TEXT("BackwardCompatibilityNodeConversion Error 'cannot safetly move pin %s to %s' in blueprint: %s"),
*OldPin->PinName,
*NewPin->PinName,
Blueprint ? *Blueprint->GetName() : TEXT("Unknown"));
}
}
OldMakeStructNode->DestroyNode();
}
}
}
TArray<UK2Node_BreakStruct*> BreakStructNodes;
Graph->GetNodesOfClass<UK2Node_BreakStruct>(BreakStructNodes);
for(auto It = BreakStructNodes.CreateIterator(); It; ++It)
{
UK2Node_BreakStruct* OldBreakStructNode = *It;
check(NULL != OldBreakStructNode);
// user may have since deleted the struct type
if (OldBreakStructNode->StructType == NULL)
{
continue;
}
// Check to see if the struct has a native make/break that we should try to convert to.
if (OldBreakStructNode->StructType && OldBreakStructNode->StructType->HasMetaData(TEXT("HasNativeBreak")))
{
UFunction* BreakNodeFunction = NULL;
// If any pins need to change their names during the conversion, add them to the map.
TMap<FString, FString> OldPinToNewPinMap;
if(OldBreakStructNode->StructType->GetName() == TEXT("Rotator"))
{
BreakNodeFunction = UKismetMathLibrary::StaticClass()->FindFunctionByName(GET_FUNCTION_NAME_CHECKED(UKismetMathLibrary, BreakRotator));
OldPinToNewPinMap.Add(TEXT("Rotator"), TEXT("InRot"));
}
else if(OldBreakStructNode->StructType->GetName() == TEXT("Vector"))
{
BreakNodeFunction = UKismetMathLibrary::StaticClass()->FindFunctionByName(GET_FUNCTION_NAME_CHECKED(UKismetMathLibrary, BreakVector));
OldPinToNewPinMap.Add(TEXT("Vector"), TEXT("InVec"));
}
else if(OldBreakStructNode->StructType->GetName() == TEXT("Vector2D"))
{
BreakNodeFunction = UKismetMathLibrary::StaticClass()->FindFunctionByName(GET_FUNCTION_NAME_CHECKED(UKismetMathLibrary, BreakVector2D));
OldPinToNewPinMap.Add(TEXT("Vector2D"), TEXT("InVec"));
}
if(BreakNodeFunction)
{
UK2Node_CallFunction* CallFunctionNode = NewObject<UK2Node_CallFunction>(Graph);
check(CallFunctionNode);
CallFunctionNode->SetFlags(RF_Transactional);
Graph->AddNode(CallFunctionNode, false, false);
CallFunctionNode->SetFromFunction(BreakNodeFunction);
CallFunctionNode->CreateNewGuid();
CallFunctionNode->PostPlacedNewNode();
CallFunctionNode->AllocateDefaultPins();
CallFunctionNode->NodePosX = OldBreakStructNode->NodePosX;
CallFunctionNode->NodePosY = OldBreakStructNode->NodePosY;
for(int32 PinIdx = 0; PinIdx < OldBreakStructNode->Pins.Num(); ++PinIdx)
{
UEdGraphPin* OldPin = OldBreakStructNode->Pins[PinIdx];
UEdGraphPin* NewPin = NULL;
// Check to see if the pin name is mapped to a new one, if it is use it, otherwise just search for the pin under the old name
FString* NewPinNamePtr = OldPinToNewPinMap.Find(OldPin->PinName);
if(NewPinNamePtr)
{
NewPin = CallFunctionNode->FindPin(*NewPinNamePtr);
}
else
{
NewPin = CallFunctionNode->FindPin(OldPin->PinName);
}
check(NewPin);
if(!Graph->GetSchema()->MovePinLinks(*OldPin, *NewPin).CanSafeConnect())
{
const UBlueprint* Blueprint = OldBreakStructNode->GetBlueprint();
UE_LOG(LogBlueprint, Warning, TEXT("BackwardCompatibilityNodeConversion Error 'cannot safetly move pin %s to %s' in blueprint: %s"),
*OldPin->PinName,
*NewPin->PinName,
Blueprint ? *Blueprint->GetName() : TEXT("Unknown"));
}
}
OldBreakStructNode->DestroyNode();
}
}
}
if (Graph->GetLinker() == nullptr)
{
return;
}
}
}
UEdGraphNode* UEdGraphSchema_K2::CreateSubstituteNode(UEdGraphNode* Node, const UEdGraph* Graph, FObjectInstancingGraph* InstanceGraph, TArray<FName>& InOutExtraNames) const
{
// If this is an event node, create a unique custom event node as a substitute
UK2Node_Event* EventNode = Cast<UK2Node_Event>(Node);
if(EventNode)
{
if(!Graph)
{
// Use the node's graph (outer) if an explicit graph was not specified
Graph = Node->GetGraph();
}
// Can only place events in ubergraphs
if (GetGraphType(Graph) != EGraphType::GT_Ubergraph)
{
return NULL;
}
// Find the Blueprint that owns the graph
UBlueprint* Blueprint = Graph ? FBlueprintEditorUtils::FindBlueprintForGraph(Graph) : NULL;
if(Blueprint && Blueprint->SkeletonGeneratedClass)
{
// Gather all names in use by the Blueprint class
TArray<FName> ExistingNamesInUse = InOutExtraNames;
FBlueprintEditorUtils::GetFunctionNameList(Blueprint, ExistingNamesInUse);
FBlueprintEditorUtils::GetClassVariableList(Blueprint, ExistingNamesInUse);
const ERenameFlags RenameFlags = (Blueprint->bIsRegeneratingOnLoad ? REN_ForceNoResetLoaders : 0);
// Allow the old object name to be used in the graph
FName ObjName = EventNode->GetFName();
UObject* Found = FindObject<UObject>(EventNode->GetOuter(), *ObjName.ToString());
if(Found)
{
Found->Rename(NULL, NULL, REN_DontCreateRedirectors | RenameFlags | (Found->HasAnyFlags(RF_NeedLoad | RF_NeedPostLoad | RF_NeedPostLoadSubobjects) ? REN_ForceNoResetLoaders : RF_NoFlags));
}
// Create a custom event node to replace the original event node imported from text
UK2Node_CustomEvent* CustomEventNode = NewObject<UK2Node_CustomEvent>(EventNode->GetOuter(), ObjName, EventNode->GetFlags(), nullptr, true, InstanceGraph);
// Ensure that it is editable
CustomEventNode->bIsEditable = true;
// Set grid position to match that of the target node
CustomEventNode->NodePosX = EventNode->NodePosX;
CustomEventNode->NodePosY = EventNode->NodePosY;
// Build a function name that is appropriate for the event we're replacing
FString FunctionName;
const UK2Node_ActorBoundEvent* ActorBoundEventNode = Cast<const UK2Node_ActorBoundEvent>(EventNode);
const UK2Node_ComponentBoundEvent* CompBoundEventNode = Cast<const UK2Node_ComponentBoundEvent>(EventNode);
const UEdGraphNode* PreExistingNode = nullptr;
if (InstanceGraph)
{
// Use a generic name for the new custom event
FunctionName = TEXT("CustomEvent");
}
else
{
// Create a name for the custom event based off the original function
if (ActorBoundEventNode)
{
FString TargetName = TEXT("None");
if (ActorBoundEventNode->EventOwner)
{
TargetName = ActorBoundEventNode->EventOwner->GetActorLabel();
}
FunctionName = FString::Printf(TEXT("%s_%s"), *ActorBoundEventNode->DelegatePropertyName.ToString(), *TargetName);
PreExistingNode = FKismetEditorUtilities::FindBoundEventForActor(ActorBoundEventNode->GetReferencedLevelActor(), ActorBoundEventNode->DelegatePropertyName);
}
else if (CompBoundEventNode)
{
FunctionName = FString::Printf(TEXT("%s_%s"), *CompBoundEventNode->DelegatePropertyName.ToString(), *CompBoundEventNode->ComponentPropertyName.ToString());
PreExistingNode = FKismetEditorUtilities::FindBoundEventForComponent(Blueprint, CompBoundEventNode->DelegatePropertyName, CompBoundEventNode->ComponentPropertyName);
}
else if (EventNode->CustomFunctionName != NAME_None)
{
FunctionName = EventNode->CustomFunctionName.ToString();
}
else if (EventNode->bOverrideFunction)
{
FunctionName = EventNode->EventReference.GetMemberName().ToString();
}
else
{
FunctionName = CustomEventNode->GetName().Replace(TEXT("K2Node_"), TEXT(""), ESearchCase::CaseSensitive);
}
}
// Ensure the name does not overlap with other names
CustomEventNode->CustomFunctionName = FName(*FunctionName, FNAME_Find);
if (CustomEventNode->CustomFunctionName != NAME_None
&& ExistingNamesInUse.Contains(CustomEventNode->CustomFunctionName))
{
int32 i = 0;
FString TempFuncName;
do
{
TempFuncName = FString::Printf(TEXT("%s_%d"), *FunctionName, ++i);
CustomEventNode->CustomFunctionName = FName(*TempFuncName, FNAME_Find);
} while (CustomEventNode->CustomFunctionName != NAME_None
&& ExistingNamesInUse.Contains(CustomEventNode->CustomFunctionName));
FunctionName = TempFuncName;
}
if (ActorBoundEventNode)
{
PreExistingNode = FKismetEditorUtilities::FindBoundEventForActor(ActorBoundEventNode->GetReferencedLevelActor(), ActorBoundEventNode->DelegatePropertyName);
}
else if (CompBoundEventNode)
{
PreExistingNode = FKismetEditorUtilities::FindBoundEventForComponent(Blueprint, CompBoundEventNode->DelegatePropertyName, CompBoundEventNode->ComponentPropertyName);
}
else
{
if (Cast<UK2Node_CustomEvent>(EventNode))
{
PreExistingNode = FBlueprintEditorUtils::FindCustomEventNode(Blueprint, EventNode->CustomFunctionName);
}
else if (ensure(EventNode->FindEventSignatureFunction() != nullptr))
{
// EventNode::FindEventSignatureFunction will return null if it is deleted (for instance, someone declared a
// BlueprintImplementableEvent, and some blueprint implements it, but then the declaration is deleted)
UClass* ClassOwner = EventNode->FindEventSignatureFunction()->GetOwnerClass()->GetAuthoritativeClass();
PreExistingNode = FBlueprintEditorUtils::FindOverrideForFunction(Blueprint, ClassOwner, EventNode->FindEventSignatureFunction()->GetFName());
}
}
// Should be a unique name now, go ahead and assign it
CustomEventNode->CustomFunctionName = FName(*FunctionName);
InOutExtraNames.Add(CustomEventNode->CustomFunctionName);
// Copy the pins from the old node to the new one that's replacing it
CustomEventNode->Pins = EventNode->Pins;
CustomEventNode->UserDefinedPins = EventNode->UserDefinedPins;
// Clear out the pins from the old node so that links aren't broken later when it's destroyed
EventNode->Pins.Empty();
EventNode->UserDefinedPins.Empty();
bool bOriginalWasCustomEvent = Cast<UK2Node_CustomEvent>(Node) != nullptr;
// Fixup pins
for(int32 PinIndex = 0; PinIndex < CustomEventNode->Pins.Num(); ++PinIndex)
{
UEdGraphPin* Pin = CustomEventNode->Pins[PinIndex];
check(Pin);
// Reparent the pin to the new custom event node
Pin->SetOwningNode(CustomEventNode);
// Don't include execution or delegate output pins as user-defined pins
if(!bOriginalWasCustomEvent && !IsExecPin(*Pin) && !IsDelegateCategory(Pin->PinType.PinCategory))
{
// Check to see if this pin already exists as a user-defined pin on the custom event node
bool bFoundUserDefinedPin = false;
for(int32 UserDefinedPinIndex = 0; UserDefinedPinIndex < CustomEventNode->UserDefinedPins.Num() && !bFoundUserDefinedPin; ++UserDefinedPinIndex)
{
const FUserPinInfo& UserDefinedPinInfo = *CustomEventNode->UserDefinedPins[UserDefinedPinIndex].Get();
bFoundUserDefinedPin = Pin->PinName == UserDefinedPinInfo.PinName && Pin->PinType == UserDefinedPinInfo.PinType;
}
if(!bFoundUserDefinedPin)
{
// Add a new entry into the user-defined pin array for the custom event node
TSharedPtr<FUserPinInfo> UserPinInfo = MakeShareable(new FUserPinInfo());
UserPinInfo->PinName = Pin->PinName;
UserPinInfo->PinType = Pin->PinType;
CustomEventNode->UserDefinedPins.Add(UserPinInfo);
}
}
}
if (PreExistingNode)
{
if (!Blueprint->PreCompileLog.IsValid())
{
Blueprint->PreCompileLog = TSharedPtr<FCompilerResultsLog>(new FCompilerResultsLog(false));
Blueprint->PreCompileLog->bSilentMode = false;
Blueprint->PreCompileLog->bAnnotateMentionedNodes = false;
}
// Append a warning to the node and to the logs
CustomEventNode->bHasCompilerMessage = true;
CustomEventNode->ErrorType = EMessageSeverity::Warning;
FFormatNamedArguments Args;
Args.Add(TEXT("NodeName"), CustomEventNode->GetNodeTitle(ENodeTitleType::ListView));
Args.Add(TEXT("OriginalNodeName"), FText::FromString(PreExistingNode->GetName()));
CustomEventNode->ErrorMsg = FText::Format(LOCTEXT("ReverseUpgradeWarning", "Conflicted with {OriginalNodeName} and was replaced as a Custom Event!"), Args).ToString();
Blueprint->PreCompileLog->Warning(*LOCTEXT("ReverseUpgradeWarning_Log", "Pasted node @@ conflicted with @@ and was replaced as a Custom Event!").ToString(), CustomEventNode, PreExistingNode);
}
// Return the new custom event node that we just created as a substitute for the original event node
return CustomEventNode;
}
}
// Use the default logic in all other cases
return UEdGraphSchema::CreateSubstituteNode(Node, Graph, InstanceGraph, InOutExtraNames);
}
int32 UEdGraphSchema_K2::GetNodeSelectionCount(const UEdGraph* Graph) const
{
UBlueprint* Blueprint = FBlueprintEditorUtils::FindBlueprintForGraph(Graph);
int32 SelectionCount = 0;
if( Blueprint )
{
SelectionCount = FKismetEditorUtilities::GetNumberOfSelectedNodes(Blueprint);
}
return SelectionCount;
}
TSharedPtr<FEdGraphSchemaAction> UEdGraphSchema_K2::GetCreateCommentAction() const
{
return TSharedPtr<FEdGraphSchemaAction>(static_cast<FEdGraphSchemaAction*>(new FEdGraphSchemaAction_K2AddComment));
}
bool UEdGraphSchema_K2::CanDuplicateGraph(UEdGraph* InSourceGraph) const
{
EGraphType GraphType = GetGraphType(InSourceGraph);
return GraphType == GT_Function || GraphType == GT_Macro;
}
UEdGraph* UEdGraphSchema_K2::DuplicateGraph(UEdGraph* GraphToDuplicate) const
{
UEdGraph* NewGraph = NULL;
if (CanDuplicateGraph(GraphToDuplicate))
{
UBlueprint* Blueprint = FBlueprintEditorUtils::FindBlueprintForGraph(GraphToDuplicate);
NewGraph = FEdGraphUtilities::CloneGraph(GraphToDuplicate, Blueprint);
if (NewGraph)
{
bool bIsOverrideGraph = false;
if (Blueprint->BlueprintType == BPTYPE_Interface)
{
bIsOverrideGraph = true;
}
else if (FBlueprintEditorUtils::FindFunctionInImplementedInterfaces(Blueprint, GraphToDuplicate->GetFName()))
{
bIsOverrideGraph = true;
}
else if (FindField<UFunction>(Blueprint->ParentClass, GraphToDuplicate->GetFName()))
{
bIsOverrideGraph = true;
}
// When duplicating an override function, we must put the graph through some extra work to properly own the data being duplicated, instead of expecting pin information will come from a parent
if (bIsOverrideGraph)
{
FBlueprintEditorUtils::PromoteGraphFromInterfaceOverride(Blueprint, NewGraph);
// Remove all calls to the parent function, fix any exec pin links to pass through
TArray< UK2Node_CallParentFunction* > ParentFunctionCalls;
NewGraph->GetNodesOfClass(ParentFunctionCalls);
for (UK2Node_CallParentFunction* ParentFunctionCall : ParentFunctionCalls)
{
UEdGraphPin* ExecPin = ParentFunctionCall->GetExecPin();
UEdGraphPin* ThenPin = ParentFunctionCall->GetThenPin();
if (ExecPin->LinkedTo.Num() && ThenPin->LinkedTo.Num())
{
MovePinLinks(*ExecPin, *ThenPin->LinkedTo[0]);
}
NewGraph->RemoveNode(ParentFunctionCall);
}
}
FName NewGraphName = FBlueprintEditorUtils::FindUniqueKismetName(Blueprint, GraphToDuplicate->GetFName().GetPlainNameString());
FEdGraphUtilities::RenameGraphCloseToName(NewGraph,NewGraphName.ToString());
// can't have two graphs with the same guid... that'd be silly!
NewGraph->GraphGuid = FGuid::NewGuid();
//Rename the entry node or any further renames will not update the entry node, also fixes a duplicate node issue on compile
for (int32 NodeIndex = 0; NodeIndex < NewGraph->Nodes.Num(); ++NodeIndex)
{
UEdGraphNode* Node = NewGraph->Nodes[NodeIndex];
if (UK2Node_FunctionEntry* EntryNode = Cast<UK2Node_FunctionEntry>(Node))
{
if (EntryNode->SignatureName == GraphToDuplicate->GetFName())
{
EntryNode->Modify();
EntryNode->SignatureName = NewGraph->GetFName();
break;
}
}
// Rename any custom events to be unique
else if (Node->GetClass()->GetFName() == TEXT("K2Node_CustomEvent"))
{
UK2Node_CustomEvent* CustomEvent = Cast<UK2Node_CustomEvent>(Node);
CustomEvent->RenameCustomEventCloseToName();
}
}
// Potentially adjust variable names for any child blueprints
FBlueprintEditorUtils::ValidateBlueprintChildVariables(Blueprint, NewGraph->GetFName());
}
}
return NewGraph;
}
/**
* Attempts to best-guess the height of the node. This is necessary because we don't know the actual
* size of the node until the next Slate tick
*
* @param Node The node to guess the height of
* @return The estimated height of the specified node
*/
float UEdGraphSchema_K2::EstimateNodeHeight( UEdGraphNode* Node )
{
float HeightEstimate = 0.0f;
if ( Node != NULL )
{
float BaseNodeHeight = 48.0f;
bool bConsiderNodePins = false;
float HeightPerPin = 18.0f;
if ( Node->IsA( UK2Node_CallFunction::StaticClass() ) )
{
BaseNodeHeight = 80.0f;
bConsiderNodePins = true;
HeightPerPin = 18.0f;
}
else if ( Node->IsA( UK2Node_Event::StaticClass() ) )
{
BaseNodeHeight = 48.0f;
bConsiderNodePins = true;
HeightPerPin = 16.0f;
}
HeightEstimate = BaseNodeHeight;
if ( bConsiderNodePins )
{
int32 NumInputPins = 0;
int32 NumOutputPins = 0;
for ( int32 PinIndex = 0; PinIndex < Node->Pins.Num(); PinIndex++ )
{
UEdGraphPin* CurrentPin = Node->Pins[PinIndex];
if ( CurrentPin != NULL && !CurrentPin->bHidden )
{
switch ( CurrentPin->Direction )
{
case EGPD_Input:
{
NumInputPins++;
}
break;
case EGPD_Output:
{
NumOutputPins++;
}
break;
}
}
}
float MaxNumPins = float(FMath::Max<int32>( NumInputPins, NumOutputPins ));
HeightEstimate += MaxNumPins * HeightPerPin;
}
}
return HeightEstimate;
}
bool UEdGraphSchema_K2::CollapseGatewayNode(UK2Node* InNode, UEdGraphNode* InEntryNode, UEdGraphNode* InResultNode, FKismetCompilerContext* CompilerContext) const
{
bool bSuccessful = true;
// We iterate the array in reverse so we can both remove the subpins safely after we've read them and
// so we have split nested structs we combine them back together in the right order
for (int32 BoundaryPinIndex = InNode->Pins.Num() - 1; BoundaryPinIndex >= 0; --BoundaryPinIndex)
{
UEdGraphPin* const BoundaryPin = InNode->Pins[BoundaryPinIndex];
bool bFunctionNode = InNode->IsA(UK2Node_CallFunction::StaticClass());
// For each pin in the gateway node, find the associated pin in the entry or result node.
UEdGraphNode* const GatewayNode = (BoundaryPin->Direction == EGPD_Input) ? InEntryNode : InResultNode;
UEdGraphPin* GatewayPin = NULL;
if (GatewayNode)
{
// First handle struct combining if necessary
if (BoundaryPin->SubPins.Num() > 0)
{
InNode->ExpandSplitPin(CompilerContext, InNode->GetGraph(), BoundaryPin);
}
for (int32 PinIdx = GatewayNode->Pins.Num() - 1; PinIdx >= 0; --PinIdx)
{
UEdGraphPin* const Pin = GatewayNode->Pins[PinIdx];
// Expand any gateway pins as needed
if (Pin->SubPins.Num() > 0)
{
InNode->ExpandSplitPin(CompilerContext, GatewayNode->GetGraph(), Pin);
}
// Function graphs have a single exec path through them, so only one exec pin for input and another for output. In this fashion, they must not be handled by name.
if(InNode->GetClass() == UK2Node_CallFunction::StaticClass() && Pin->PinType.PinCategory == PC_Exec && BoundaryPin->PinType.PinCategory == PC_Exec && (Pin->Direction != BoundaryPin->Direction))
{
GatewayPin = Pin;
break;
}
else if ((Pin->PinName == BoundaryPin->PinName) && (Pin->Direction != BoundaryPin->Direction))
{
GatewayPin = Pin;
break;
}
}
}
if (GatewayPin)
{
CombineTwoPinNetsAndRemoveOldPins(BoundaryPin, GatewayPin);
}
else
{
if (BoundaryPin->LinkedTo.Num() > 0 && BoundaryPin->ParentPin == NULL)
{
UBlueprint* OwningBP = InNode->GetBlueprint();
if( OwningBP )
{
// We had an input/output with a connection that wasn't twinned
bSuccessful = false;
OwningBP->Message_Warn( FString::Printf(*NSLOCTEXT("K2Node", "PinOnBoundryNode_Warning", "Warning: Pin '%s' on boundary node '%s' could not be found in the composite node '%s'").ToString(),
*(BoundaryPin->PinName),
(GatewayNode != NULL) ? *(GatewayNode->GetName()) : TEXT("(null)"),
*(GetName()))
);
}
else
{
UE_LOG(LogBlueprint, Warning, TEXT("%s"), *FString::Printf(*NSLOCTEXT("K2Node", "PinOnBoundryNode_Warning", "Warning: Pin '%s' on boundary node '%s' could not be found in the composite node '%s'").ToString(),
*(BoundaryPin->PinName),
(GatewayNode != NULL) ? *(GatewayNode->GetName()) : TEXT("(null)"),
*(GetName()))
);
}
}
else
{
// Associated pin was not found but there were no links on this side either, so no harm no foul
}
}
}
return bSuccessful;
}
void UEdGraphSchema_K2::CombineTwoPinNetsAndRemoveOldPins(UEdGraphPin* InPinA, UEdGraphPin* InPinB) const
{
check(InPinA != NULL);
check(InPinB != NULL);
ensure(InPinA->Direction != InPinB->Direction);
if ((InPinA->LinkedTo.Num() == 0) && (InPinA->Direction == EGPD_Input))
{
// Push the literal value of A to InPinB's connections
for (int32 IndexB = 0; IndexB < InPinB->LinkedTo.Num(); ++IndexB)
{
UEdGraphPin* FarB = InPinB->LinkedTo[IndexB];
// TODO: Michael N. says this if check should be unnecessary once the underlying issue is fixed.
// (Probably should use a check() instead once it's removed though. See additional cases below.
if (FarB != NULL)
{
FarB->DefaultValue = InPinA->DefaultValue;
FarB->DefaultObject = InPinA->DefaultObject;
FarB->DefaultTextValue = InPinA->DefaultTextValue;
}
}
}
else if ((InPinB->LinkedTo.Num() == 0) && (InPinB->Direction == EGPD_Input))
{
// Push the literal value of B to InPinA's connections
for (int32 IndexA = 0; IndexA < InPinA->LinkedTo.Num(); ++IndexA)
{
UEdGraphPin* FarA = InPinA->LinkedTo[IndexA];
// TODO: Michael N. says this if check should be unnecessary once the underlying issue is fixed.
// (Probably should use a check() instead once it's removed though. See additional cases above and below.
if (FarA != NULL)
{
FarA->DefaultValue = InPinB->DefaultValue;
FarA->DefaultObject = InPinB->DefaultObject;
FarA->DefaultTextValue = InPinB->DefaultTextValue;
}
}
}
else
{
// Make direct connections between the things that connect to A or B, removing A and B from the picture
for (int32 IndexA = 0; IndexA < InPinA->LinkedTo.Num(); ++IndexA)
{
UEdGraphPin* FarA = InPinA->LinkedTo[IndexA];
// TODO: Michael N. says this if check should be unnecessary once the underlying issue is fixed.
// (Probably should use a check() instead once it's removed though. See additional cases above.
if (FarA != NULL)
{
for (int32 IndexB = 0; IndexB < InPinB->LinkedTo.Num(); ++IndexB)
{
UEdGraphPin* FarB = InPinB->LinkedTo[IndexB];
FarA->Modify();
FarB->Modify();
FarA->MakeLinkTo(FarB);
}
}
}
}
InPinA->BreakAllPinLinks();
InPinB->BreakAllPinLinks();
}
UK2Node* UEdGraphSchema_K2::CreateSplitPinNode(UEdGraphPin* Pin, FKismetCompilerContext* CompilerContext, UEdGraph* SourceGraph) const
{
UEdGraphNode* GraphNode = Pin->GetOwningNode();
UEdGraph* Graph = GraphNode->GetGraph();
UScriptStruct* StructType = CastChecked<UScriptStruct>(Pin->PinType.PinSubCategoryObject.Get(), ECastCheckedType::NullAllowed);
if (!StructType)
{
if (CompilerContext)
{
CompilerContext->MessageLog.Error(TEXT("No structure in SubCategoryObject in pin @@"), Pin);
}
StructType = GetFallbackStruct();
}
UK2Node* SplitPinNode = NULL;
if (Pin->Direction == EGPD_Input)
{
if (UK2Node_MakeStruct::CanBeMade(StructType))
{
UK2Node_MakeStruct* MakeStructNode = (CompilerContext ? CompilerContext->SpawnIntermediateNode<UK2Node_MakeStruct>(GraphNode, SourceGraph) : NewObject<UK2Node_MakeStruct>(Graph));
MakeStructNode->StructType = StructType;
MakeStructNode->bMadeAfterOverridePinRemoval = true;
SplitPinNode = MakeStructNode;
}
else
{
const FString& MetaData = StructType->GetMetaData(TEXT("HasNativeMake"));
const UFunction* Function = FindObject<UFunction>(NULL, *MetaData, true);
UK2Node_CallFunction* CallFunctionNode = (CompilerContext ? CompilerContext->SpawnIntermediateNode<UK2Node_CallFunction>(GraphNode, SourceGraph) : NewObject<UK2Node_CallFunction>(Graph));
CallFunctionNode->SetFromFunction(Function);
SplitPinNode = CallFunctionNode;
}
}
else
{
if (UK2Node_BreakStruct::CanBeBroken(StructType))
{
UK2Node_BreakStruct* BreakStructNode = (CompilerContext ? CompilerContext->SpawnIntermediateNode<UK2Node_BreakStruct>(GraphNode, SourceGraph) : NewObject<UK2Node_BreakStruct>(Graph));
BreakStructNode->StructType = StructType;
BreakStructNode->bMadeAfterOverridePinRemoval = true;
SplitPinNode = BreakStructNode;
}
else
{
const FString& MetaData = StructType->GetMetaData(TEXT("HasNativeBreak"));
const UFunction* Function = FindObject<UFunction>(NULL, *MetaData, true);
UK2Node_CallFunction* CallFunctionNode = (CompilerContext ? CompilerContext->SpawnIntermediateNode<UK2Node_CallFunction>(GraphNode, SourceGraph) : NewObject<UK2Node_CallFunction>(Graph));
CallFunctionNode->SetFromFunction(Function);
SplitPinNode = CallFunctionNode;
}
}
SplitPinNode->AllocateDefaultPins();
return SplitPinNode;
}
void UEdGraphSchema_K2::SplitPin(UEdGraphPin* Pin) const
{
// Under some circumstances we can get here when PinSubCategoryObject is not set, so we just can't split the pin in that case
UScriptStruct* StructType = Cast<UScriptStruct>(Pin->PinType.PinSubCategoryObject.Get());
if (StructType == nullptr)
{
return;
}
UEdGraphNode* GraphNode = Pin->GetOwningNode();
UK2Node* K2Node = Cast<UK2Node>(GraphNode);
UEdGraph* Graph = CastChecked<UEdGraph>(GraphNode->GetOuter());
GraphNode->Modify();
Pin->Modify();
Pin->bHidden = true;
UK2Node* ProtoExpandNode = CreateSplitPinNode(Pin);
for (UEdGraphPin* ProtoPin : ProtoExpandNode->Pins)
{
if (ProtoPin->Direction == Pin->Direction && !ProtoPin->bHidden)
{
const FString PinName = FString::Printf(TEXT("%s_%s"), *Pin->PinName, *ProtoPin->PinName);
const FEdGraphPinType& ProtoPinType = ProtoPin->PinType;
UEdGraphPin* SubPin = GraphNode->CreatePin(Pin->Direction, ProtoPinType.PinCategory, ProtoPinType.PinSubCategory, ProtoPinType.PinSubCategoryObject.Get(), ProtoPinType.bIsArray, false, PinName);
if (K2Node != nullptr && K2Node->ShouldDrawCompact() && !Pin->ParentPin)
{
SubPin->PinFriendlyName = ProtoPin->GetDisplayName();
}
else
{
FFormatNamedArguments Arguments;
Arguments.Add(TEXT("PinDisplayName"), Pin->GetDisplayName());
Arguments.Add(TEXT("ProtoPinDisplayName"), ProtoPin->GetDisplayName());
SubPin->PinFriendlyName = FText::Format(LOCTEXT("SplitPinFriendlyNameFormat", "{PinDisplayName} {ProtoPinDisplayName}"), Arguments);
}
SubPin->DefaultValue = ProtoPin->DefaultValue;
SubPin->AutogeneratedDefaultValue = ProtoPin->AutogeneratedDefaultValue;
SubPin->ParentPin = Pin;
// CreatePin puts the Pin in the array, but we are going to insert it later, so pop it back out
GraphNode->Pins.Pop(/*bAllowShrinking=*/ false);
Pin->SubPins.Add(SubPin);
}
}
ProtoExpandNode->DestroyNode();
if (Pin->Direction == EGPD_Input)
{
TArray<FString> OriginalDefaults;
if ( StructType == TBaseStructure<FVector>::Get()
|| StructType == TBaseStructure<FRotator>::Get())
{
Pin->DefaultValue.ParseIntoArray(OriginalDefaults, TEXT(","), false);
for (FString& Default : OriginalDefaults)
{
Default = FString::SanitizeFloat(FCString::Atof(*Default));
}
// In some cases (particularly wildcards) the default value may not accurately reflect the normal component elements
while (OriginalDefaults.Num() < 3)
{
OriginalDefaults.Add(TEXT("0.0"));
}
// Rotator OriginalDefaults are in the form of Y,Z,X but our pins are in the form of X,Y,Z
// so we have to change the OriginalDefaults order here to match our pins
if (StructType == TBaseStructure<FRotator>::Get())
{
OriginalDefaults.Swap(0, 2);
OriginalDefaults.Swap(1, 2);
}
}
else if (StructType == TBaseStructure<FVector2D>::Get())
{
FVector2D V2D;
V2D.InitFromString(Pin->DefaultValue);
OriginalDefaults.Add(FString::SanitizeFloat(V2D.X));
OriginalDefaults.Add(FString::SanitizeFloat(V2D.Y));
}
else if (StructType == TBaseStructure<FLinearColor>::Get())
{
FLinearColor LC;
LC.InitFromString(Pin->DefaultValue);
OriginalDefaults.Add(FString::SanitizeFloat(LC.R));
OriginalDefaults.Add(FString::SanitizeFloat(LC.G));
OriginalDefaults.Add(FString::SanitizeFloat(LC.B));
OriginalDefaults.Add(FString::SanitizeFloat(LC.A));
}
check(OriginalDefaults.Num() == 0 || OriginalDefaults.Num() == Pin->SubPins.Num());
for (int32 SubPinIndex = 0; SubPinIndex < OriginalDefaults.Num(); ++SubPinIndex)
{
UEdGraphPin* SubPin = Pin->SubPins[SubPinIndex];
SubPin->DefaultValue = OriginalDefaults[SubPinIndex];
}
}
GraphNode->Pins.Insert(Pin->SubPins, GraphNode->Pins.Find(Pin) + 1);
Graph->NotifyGraphChanged();
UBlueprint* Blueprint = FBlueprintEditorUtils::FindBlueprintForGraphChecked(Graph);
FBlueprintEditorUtils::MarkBlueprintAsModified(Blueprint);
}
void UEdGraphSchema_K2::RecombinePin(UEdGraphPin* Pin) const
{
UEdGraphNode* GraphNode = Pin->GetOwningNode();
UEdGraphPin* ParentPin = Pin->ParentPin;
GraphNode->Modify();
ParentPin->Modify();
ParentPin->bHidden = false;
UEdGraph* Graph = CastChecked<UEdGraph>(GraphNode->GetOuter());
UBlueprint* Blueprint = FBlueprintEditorUtils::FindBlueprintForGraphChecked(Graph);
for (int32 SubPinIndex = 0; SubPinIndex < ParentPin->SubPins.Num(); ++SubPinIndex)
{
UEdGraphPin* SubPin = ParentPin->SubPins[SubPinIndex];
if (SubPin->SubPins.Num() > 0)
{
RecombinePin(SubPin->SubPins[0]);
}
GraphNode->Pins.Remove(SubPin);
Blueprint->WatchedPins.Remove(SubPin);
}
if (Pin->Direction == EGPD_Input)
{
UScriptStruct* StructType = CastChecked<UScriptStruct>(ParentPin->PinType.PinSubCategoryObject.Get());
TArray<FString> OriginalDefaults;
if (StructType == TBaseStructure<FVector>::Get())
{
ParentPin->DefaultValue = ParentPin->SubPins[0]->DefaultValue + TEXT(",")
+ ParentPin->SubPins[1]->DefaultValue + TEXT(",")
+ ParentPin->SubPins[2]->DefaultValue;
}
else if (StructType == TBaseStructure<FRotator>::Get())
{
// Our pins are in the form X,Y,Z but the Rotator pin type expects the form Y,Z,X
// so we need to make sure they are added in that order here
ParentPin->DefaultValue = ParentPin->SubPins[1]->DefaultValue + TEXT(",")
+ ParentPin->SubPins[2]->DefaultValue + TEXT(",")
+ ParentPin->SubPins[0]->DefaultValue;
}
else if (StructType == TBaseStructure<FVector2D>::Get())
{
FVector2D V2D;
V2D.X = FCString::Atof(*ParentPin->SubPins[0]->DefaultValue);
V2D.Y = FCString::Atof(*ParentPin->SubPins[1]->DefaultValue);
ParentPin->DefaultValue = V2D.ToString();
}
else if (StructType == TBaseStructure<FLinearColor>::Get())
{
FLinearColor LC;
LC.R = FCString::Atof(*ParentPin->SubPins[0]->DefaultValue);
LC.G = FCString::Atof(*ParentPin->SubPins[1]->DefaultValue);
LC.B = FCString::Atof(*ParentPin->SubPins[2]->DefaultValue);
LC.A = FCString::Atof(*ParentPin->SubPins[3]->DefaultValue);
ParentPin->DefaultValue = LC.ToString();
}
}
// Clear out subpins:
TArray<UEdGraphPin*>& ParentSubPins = ParentPin->SubPins;
while (ParentSubPins.Num())
{
// To ensure that MarkPendingKill does not mutate ParentSubPins, we null out the ParentPin
// if we assume that MarkPendingKill *will* mutate ParentSubPins we could introduce an infinite
// loop. No known case of this being possible, but it would be trivial to write bad node logic
// that introduces this problem:
ParentSubPins.Last()->ParentPin = nullptr;
ParentSubPins.Last()->MarkPendingKill();
ParentSubPins.RemoveAt(ParentSubPins.Num()-1);
}
Graph->NotifyGraphChanged();
FBlueprintEditorUtils::MarkBlueprintAsModified(Blueprint);
}
void UEdGraphSchema_K2::OnPinConnectionDoubleCicked(UEdGraphPin* PinA, UEdGraphPin* PinB, const FVector2D& GraphPosition) const
{
const FScopedTransaction Transaction(LOCTEXT("CreateRerouteNodeOnWire", "Create Reroute Node"));
//@TODO: This constant is duplicated from inside of SGraphNodeKnot
const FVector2D NodeSpacerSize(42.0f, 24.0f);
const FVector2D KnotTopLeft = GraphPosition - (NodeSpacerSize * 0.5f);
// Create a new knot
UEdGraph* ParentGraph = PinA->GetOwningNode()->GetGraph();
if (!FBlueprintEditorUtils::IsGraphReadOnly(ParentGraph))
{
UK2Node_Knot* NewKnot = FEdGraphSchemaAction_K2NewNode::SpawnNodeFromTemplate<UK2Node_Knot>(ParentGraph, NewObject<UK2Node_Knot>(), KnotTopLeft);
// Move the connections across (only notifying the knot, as the other two didn't really change)
PinA->BreakLinkTo(PinB);
PinA->MakeLinkTo((PinA->Direction == EGPD_Output) ? NewKnot->GetInputPin() : NewKnot->GetOutputPin());
PinB->MakeLinkTo((PinB->Direction == EGPD_Output) ? NewKnot->GetInputPin() : NewKnot->GetOutputPin());
NewKnot->PostReconstructNode();
// Dirty the blueprint
UBlueprint* Blueprint = FBlueprintEditorUtils::FindBlueprintForGraphChecked(CastChecked<UEdGraph>(ParentGraph));
FBlueprintEditorUtils::MarkBlueprintAsModified(Blueprint);
}
}
void UEdGraphSchema_K2::ConfigureVarNode(UK2Node_Variable* InVarNode, FName InVariableName, UStruct* InVariableSource, UBlueprint* InTargetBlueprint)
{
// See if this is a 'self context' (ie. blueprint class is owner (or child of owner) of dropped var class)
if ((InVariableSource == NULL) || InTargetBlueprint->SkeletonGeneratedClass->IsChildOf(InVariableSource))
{
FGuid Guid = FBlueprintEditorUtils::FindMemberVariableGuidByName(InTargetBlueprint, InVariableName);
InVarNode->VariableReference.SetSelfMember(InVariableName, Guid);
}
else if (InVariableSource->IsA(UClass::StaticClass()))
{
FGuid Guid;
if (UBlueprint* VariableOwnerBP = Cast<UBlueprint>(Cast<UClass>(InVariableSource)->ClassGeneratedBy))
{
Guid = FBlueprintEditorUtils::FindMemberVariableGuidByName(VariableOwnerBP, InVariableName);
}
InVarNode->VariableReference.SetExternalMember(InVariableName, CastChecked<UClass>(InVariableSource), Guid);
}
else
{
FGuid LocalVarGuid = FBlueprintEditorUtils::FindLocalVariableGuidByName(InTargetBlueprint, InVariableSource, InVariableName);
if (LocalVarGuid.IsValid())
{
InVarNode->VariableReference.SetLocalMember(InVariableName, InVariableSource, LocalVarGuid);
}
}
}
UK2Node_VariableGet* UEdGraphSchema_K2::SpawnVariableGetNode(const FVector2D GraphPosition, class UEdGraph* ParentGraph, FName VariableName, UStruct* Source) const
{
UK2Node_VariableGet* NodeTemplate = NewObject<UK2Node_VariableGet>();
UBlueprint* Blueprint = FBlueprintEditorUtils::FindBlueprintForGraph(ParentGraph);
UEdGraphSchema_K2::ConfigureVarNode(NodeTemplate, VariableName, Source, Blueprint);
return FEdGraphSchemaAction_K2NewNode::SpawnNodeFromTemplate<UK2Node_VariableGet>(ParentGraph, NodeTemplate, GraphPosition);
}
UK2Node_VariableSet* UEdGraphSchema_K2::SpawnVariableSetNode(const FVector2D GraphPosition, class UEdGraph* ParentGraph, FName VariableName, UStruct* Source) const
{
UK2Node_VariableSet* NodeTemplate = NewObject<UK2Node_VariableSet>();
UBlueprint* Blueprint = FBlueprintEditorUtils::FindBlueprintForGraph(ParentGraph);
UEdGraphSchema_K2::ConfigureVarNode(NodeTemplate, VariableName, Source, Blueprint);
return FEdGraphSchemaAction_K2NewNode::SpawnNodeFromTemplate<UK2Node_VariableSet>(ParentGraph, NodeTemplate, GraphPosition);
}
UEdGraphPin* UEdGraphSchema_K2::DropPinOnNode(UEdGraphNode* InTargetNode, const FString& InSourcePinName, const FEdGraphPinType& InSourcePinType, EEdGraphPinDirection InSourcePinDirection) const
{
UEdGraphPin* ResultPin = nullptr;
if (UK2Node_EditablePinBase* EditablePinNode = Cast<UK2Node_EditablePinBase>(InTargetNode))
{
TArray<UK2Node_EditablePinBase*> EditablePinNodes;
EditablePinNode->Modify();
if (InSourcePinDirection == EGPD_Output && Cast<UK2Node_FunctionEntry>(InTargetNode))
{
if (UK2Node_FunctionResult* ResultNode = FBlueprintEditorUtils::FindOrCreateFunctionResultNode(EditablePinNode))
{
EditablePinNodes.Add(ResultNode);
}
else
{
// If we did not successfully find or create a result node, just fail out
return nullptr;
}
}
else if (InSourcePinDirection == EGPD_Input && Cast<UK2Node_FunctionResult>(InTargetNode))
{
TArray<UK2Node_FunctionEntry*> FunctionEntryNode;
InTargetNode->GetGraph()->GetNodesOfClass(FunctionEntryNode);
if (FunctionEntryNode.Num() == 1)
{
EditablePinNodes.Add(FunctionEntryNode[0]);
}
else
{
// If we did not successfully find the entry node, just fail out
return nullptr;
}
}
else
{
if (UK2Node_FunctionResult* ResultNode = Cast<UK2Node_FunctionResult>(EditablePinNode))
{
EditablePinNodes.Append(ResultNode->GetAllResultNodes());
}
else
{
EditablePinNodes.Add(EditablePinNode);
}
}
FString NewPinName = InSourcePinName;
for (UK2Node_EditablePinBase* CurrentEditablePinNode : EditablePinNodes)
{
CurrentEditablePinNode->Modify();
UEdGraphPin* CreatedPin = nullptr;
CreatedPin = CurrentEditablePinNode->CreateUserDefinedPin(NewPinName, InSourcePinType, (InSourcePinDirection == EGPD_Input)? EGPD_Output : EGPD_Input);
// The final ResultPin is from the node the user dragged and dropped to
if (EditablePinNode == CurrentEditablePinNode)
{
ResultPin = CreatedPin;
}
}
FParamsChangedHelper ParamsChangedHelper;
ParamsChangedHelper.ModifiedBlueprints.Add(FBlueprintEditorUtils::FindBlueprintForNode(InTargetNode));
FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(FBlueprintEditorUtils::FindBlueprintForNode(InTargetNode));
ParamsChangedHelper.Broadcast(FBlueprintEditorUtils::FindBlueprintForNode(InTargetNode), EditablePinNode, InTargetNode->GetGraph());
for (auto GraphIt = ParamsChangedHelper.ModifiedGraphs.CreateIterator(); GraphIt; ++GraphIt)
{
if(UEdGraph* ModifiedGraph = *GraphIt)
{
ModifiedGraph->NotifyGraphChanged();
}
}
// Now update all the blueprints that got modified
for (auto BlueprintIt = ParamsChangedHelper.ModifiedBlueprints.CreateIterator(); BlueprintIt; ++BlueprintIt)
{
if(UBlueprint* Blueprint = *BlueprintIt)
{
Blueprint->BroadcastChanged();
}
}
}
return ResultPin;
}
bool UEdGraphSchema_K2::SupportsDropPinOnNode(UEdGraphNode* InTargetNode, const FEdGraphPinType& InSourcePinType, EEdGraphPinDirection InSourcePinDirection, FText& OutErrorMessage) const
{
bool bIsSupported = false;
if (UK2Node_EditablePinBase* EditablePinNode = Cast<UK2Node_EditablePinBase>(InTargetNode))
{
if (InSourcePinDirection == EGPD_Output && Cast<UK2Node_FunctionEntry>(InTargetNode))
{
// Just check with the Function Entry and see if it's legal, we'll create/use a result node if the user drops
bIsSupported = EditablePinNode->CanCreateUserDefinedPin(InSourcePinType, InSourcePinDirection, OutErrorMessage);
if (bIsSupported)
{
OutErrorMessage = LOCTEXT("AddConnectResultNode", "Add Pin to Result Node");
}
}
else if (InSourcePinDirection == EGPD_Input && Cast<UK2Node_FunctionResult>(InTargetNode))
{
// Just check with the Function Result and see if it's legal, we'll create/use a result node if the user drops
bIsSupported = EditablePinNode->CanCreateUserDefinedPin(InSourcePinType, InSourcePinDirection, OutErrorMessage);
if (bIsSupported)
{
OutErrorMessage = LOCTEXT("AddPinEntryNode", "Add Pin to Entry Node");
}
}
else
{
bIsSupported = EditablePinNode->CanCreateUserDefinedPin(InSourcePinType, (InSourcePinDirection == EGPD_Input)? EGPD_Output : EGPD_Input, OutErrorMessage);
if (bIsSupported)
{
OutErrorMessage = LOCTEXT("AddPinToNode", "Add Pin to Node");
}
}
}
return bIsSupported;
}
bool UEdGraphSchema_K2::IsCacheVisualizationOutOfDate(int32 InVisualizationCacheID) const
{
return CurrentCacheRefreshID != InVisualizationCacheID;
}
int32 UEdGraphSchema_K2::GetCurrentVisualizationCacheID() const
{
return CurrentCacheRefreshID;
}
void UEdGraphSchema_K2::ForceVisualizationCacheClear() const
{
++CurrentCacheRefreshID;
}
/////////////////////////////////////////////////////
#undef LOCTEXT_NAMESPACE