Files
UnrealEngineUWP/Engine/Source/Editor/PropertyEditor/Private/PropertyNode.cpp
Max Preussner 8e9b51aeff Copying //UE4/Dev-Sequencer to //UE4/Dev-Main (Source: //UE4/Dev-Sequencer @ 3237992)
#lockdown Nick.Penwarden
#rb none

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

Change 3136778 on 2016/09/22 by Max.Preussner

	Merged Dev-Main to Dev-Sequencer

Change 3179199 on 2016/10/29 by Max.Chen

	Sequencer: Fade only oin the current player context, not on all worlds.

	Copy from Release-4.14. Copied fix to FadeTrackInstance to FadeTemplate.

	#jira UE-37939

Change 3179340 on 2016/10/29 by Max.Preussner

	PS4Media: Fixed audio track dropping first frame

Change 3180391 on 2016/10/31 by Max.Preussner

	UdpMessaging: nulling out message processor in destructor

Change 3180459 on 2016/10/31 by Max.Chen

	Sequencer: Fix copy/paste crash in UMG.

Change 3180607 on 2016/10/31 by Andrew.Rodham

	UMG: Fixed parent bindings not being adhered to correctly. Fixed slot widgets that get recreated not having their object bindings updated.

	#jira UE-38021
	#jira UE-38018

Change 3181405 on 2016/11/01 by Lina.Halper

	#ANIM/SEQUCNER: sequencer animation blending support including additive
	 - created multiway blend node - extension of two way blend
	 - created anim sequencer instance to be used in sequencer for blending multiple animations and additives
	 - hooked up to sequencer track players

	- renamed AnimationNode_TwoWay to AnimNode_TwoWay to be consistent with other node names.
	- Make sure you can't choose montage when selecting animation in Sequencer
	- Fixed Anim BP playing with multi group montages

	#code review: Max.Chen

Change 3181870 on 2016/11/01 by Andrew.Rodham

	Sequencer: Made sequence pointers stored in sequence template instances weak object ptrs

	  - We can't guarantee the lifetime of the objects here

	#jira UE-38051

Change 3182851 on 2016/11/02 by Andrew.Rodham

	Sequencer: Assert that a GetScriptStructImpl has been overridden correctly on templates

Change 3182852 on 2016/11/02 by Andrew.Rodham

	Sequencer: Added 'Restore Animated State' command (CTRL+R) and button to sequencer toolbar

Change 3183161 on 2016/11/02 by Max.Preussner

	Media: Added supported file extensions & URL schemes

Change 3183476 on 2016/11/02 by Max.Preussner

	Merged Dev-Main to Dev-Sequencer

Change 3185181 on 2016/11/03 by Max.Chen

	Sequencer: Refactor general options button menu into play options and select options. Add Select Sections in Selection Range and Select All in Selection Range.

	Fix issues with convert to spawanble and convert to possessable. Convert to possessable now deletes the spawn track so that it's not left lying around, which when deleted would end up deleting the converted possessable actor.

	#jira UE-37854

Change 3185184 on 2016/11/03 by Max.Chen

	Sequencer: Add hotkey to toggle camera cut track lock/unlock camera.

Change 3185409 on 2016/11/03 by Max.Chen

	Sequencer: Fix crash in skeletal mesh section drawing.

	#jira UE-38090

Change 3185444 on 2016/11/03 by Max.Chen

	UMG: Expose label browser for UMG

Change 3185662 on 2016/11/03 by Max.Chen

	Sequencer: Paste track fixes.

	 - Loosen restrictions on paste track destination.  This allows the paste to operate on spawnables and on properties that don't have an explicit Set function.
	 - Allow pasting onto all types of tracks, not just property tracks.
	 - Fix when pasting the copied tracks onto multiple objects.

	Tested pasting transform tracks from possessable to spawnables.
	Tested pasting skeletal animation tracks from spawnable to possessables.

	#jira UETOOL-1206

Change 3185920 on 2016/11/03 by Andrew.Porter

	Adding test content for multiple audio video tracks.

Change 3186404 on 2016/11/03 by Max.Preussner

	Merged Dev-Main to Dev-Sequencer

Change 3187957 on 2016/11/04 by Max.Preussner

	MediaAssets: Exposed CanPlaySource in BP

Change 3187988 on 2016/11/05 by Max.Preussner

	Fixed documentation

Change 3188035 on 2016/11/05 by Max.Chen

	Sequencer: Show camera name in cinematic viewport.

	#jira UE-28115

Change 3188603 on 2016/11/07 by Max.Preussner

	WmfMedia: Added missing nullptr check

Change 3188788 on 2016/11/07 by Max.Preussner

	MediaPlayerEditor: Removed property buttons from PlatformMediaSource customization (UE-37948)

	#jira UE-37948

Change 3188808 on 2016/11/07 by Max.Preussner

	MediaAssets: Moved media player implementation into reusable class
	Also moved overlay text handling into separate asset.

Change 3188919 on 2016/11/07 by Max.Preussner

	Media: Changed the handling of invalid media and media that failed to open (UE-38014)

	#jira UE-38014

Change 3189112 on 2016/11/07 by Max.Preussner

	WmfMedia: Added rudimentary H.265 HEVC support for Windows 10 (UE-38324)

	#jira UE-38324

Change 3189376 on 2016/11/07 by Max.Preussner

	WmfMedia: Removed Windows specific code from factory module

Change 3189381 on 2016/11/07 by Max.Preussner

	Atrac9Audio: Fixed log category

Change 3189497 on 2016/11/07 by Max.Preussner

	Media: Added binary sinks support

Change 3189666 on 2016/11/07 by Max.Chen

	Curve Editor: Add option to show time in frame numbers

	#jira UE-27210

Change 3190339 on 2016/11/08 by Max.Preussner

	MediaAssets: Removed SetDesiredPlayerName since the field is public

Change 3190342 on 2016/11/08 by Andrew.Porter

	Adding sequencer test content for animation blueprint

Change 3190398 on 2016/11/08 by Max.Preussner

	Media: Renamed binary tracks to metadata tracks

Change 3190458 on 2016/11/08 by andrew.porter

	Updating Skeleton with new slots.

Change 3191167 on 2016/11/08 by Max.Chen

	Sequencer: Fix crash in validating paste tracks buffer. Validate the tracks instead of actually pasting into temp.

	#jira UE-38353

Change 3191336 on 2016/11/09 by Andrew.Rodham

	Slate: Added the ability to set and retrieve a host tab manager from a details view

Change 3191338 on 2016/11/09 by Andrew.Rodham

	Editor: Added the ability to extend default layouts

	  - FLayoutExtender can be used to provide basic tab layout extensions on default themes.
	  - This can be used by external plugins to inject tabs to other interfaces where necessary.
	  - Currently this is supported by the blueprint editor's unified component layout, and the level editor layout.

Change 3191346 on 2016/11/09 by Andrew.Rodham

	Sequencer: Added new (experimental) ActorSequence module and editor

	  - Sequences can now be added to actors via the UActorSequenceComponent.
	  - An embedded sequencer will appear on details panels, with the option to break it out into a tab.
	  - Separated common playback elements from ULevelSequencePlayer into UMovieSceneSequencePlayer, from which specific players can derive.
	  - The majority of level editorintegration with sequencer has been separated out into a separate singleton class that can manage multiple sequencers.
	  - All movie scene data now defaults to instanced, such that it can be duplicated and instanced correctly.
	  - Added read-only mode for sequencer which is used for actor sequence components that come from a blueprint archetype to prevent erroneous editing.

Change 3191387 on 2016/11/09 by Andrew.Rodham

	Orion: Fixed deprecation warnings

Change 3191388 on 2016/11/09 by Andrew.Rodham

	Orion: Added dependency on MovieScene module

Change 3191403 on 2016/11/09 by Andrew.Rodham

	Sequencer: Fix initialization order warning

Change 3191428 on 2016/11/09 by Andrew.Rodham

	Sequencer: Added missing include

Change 3191510 on 2016/11/09 by Andrew.Rodham

	Header include fixes

Change 3191599 on 2016/11/09 by Max.Chen

	Sequencer: Add option to lock the playback range per movie scene. The toggle is stored as editor only and should be a saved value so that it can persist as the asset is passed from user to user.

	#jira UE-34677

Change 3191664 on 2016/11/09 by Andrew.Rodham

	Sequencer: Ensure keyframe handlers are only added once

Change 3192373 on 2016/11/09 by Max.Preussner

	MediaAssets: Fixed regression: playlists no longer open

Change 3192408 on 2016/11/09 by Max.Preussner

	MediaAssets: Fixed OpenPlaylistIndex crashing

Change 3192878 on 2016/11/09 by Max.Chen

	Camera Rig: Fix log spam trying to unregister component.

	#jira UE-38435

Change 3192989 on 2016/11/10 by Andrew.Rodham

	Slate: Added constructor to appease old VS2013 compiler warning about non-constructible type

Change 3192991 on 2016/11/10 by Andrew.Rodham

	Sequencer: Moved lambda out-of-line to fix static analysis warning

Change 3193420 on 2016/11/10 by Max.Preussner

	MediaAssets: Replaced CopyToResolveTarget with new TransitionTarget API

Change 3193478 on 2016/11/10 by Max.Chen

	Sequencer: Moved Fix Actor References back under the General Options menu.

Change 3193870 on 2016/11/10 by Max.Preussner

	MediaPlayerEditor: Removed additional buttons in per-platform overrides (UE-37948)

	#jira UE-37948

Change 3193873 on 2016/11/10 by Lina.Halper

	- Sequencer fix with anim instance reinit
	- Fixed TMap issue with memory by changing to pointer from ref.

	#code review: Max.Chen

Change 3194184 on 2016/11/10 by Max.Chen

	Sequencer: Only expand section when setting keys when there are keys. Otherwise if you set the default value while the time position is outside of the section range, the section will expand, which seems undesirable.

Change 3194187 on 2016/11/10 by Max.Chen

	Sequencer: Backwards compatibility if a track no longer supports multiple rows, its sections are split to other duplicate tracks.

Change 3194191 on 2016/11/10 by Max.Chen

	Sequencer: Add audio volume and pitch curves.

	#jira UE-30009

Change 3194256 on 2016/11/10 by Max.Chen

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

Change 3194282 on 2016/11/10 by Max.Chen

	Movie Capture: Add some frame rate bounds. Max frame rate for recording is 200. Min is 1.

	#jira UE-38502

Change 3194355 on 2016/11/11 by Max.Chen

	Sequencer: Minimum handle size for time slider scrubber.

	#jira UE-34676

Change 3194767 on 2016/11/11 by Max.Chen

	Sequencer: Mark duplicated tracks as changed so that their template gets regenerated.

Change 3195094 on 2016/11/11 by Max.Preussner

	Media: Removing game thread dependencies

	This change removes game thread dependencies from all media players so that we can use the media framework for startup movies where the game thread is block while loading the Engine. The players now have two new methods, TickPlayer and TickVideo, which need to be called from the external code that owns the players. On the Engine side, this is taken care of by UMediaPlayer, which calls TickPlayer from the game thread and TickVideo from the render thread. In startup movies, this will be taken care of by a special thread.

	AvfMedia: This change does not fully remove game thread dependencies in AvfMediaPlayer yet. There are some async callbacks scheduled to execute on the game thread that need to be refactored. The execution of these events should be performed in TickPlayer instead.

	All platform owners, please review these changes for your platform and make sure that everything still works. I have not had time to test all platforms yet.

Change 3195396 on 2016/11/11 by Max.Preussner

	AvfMedia: Removed remaining game thread dependencies

Change 3195670 on 2016/11/11 by Max.Preussner

	MediaUtils: Renamed function

Change 3195690 on 2016/11/11 by Max.Preussner

	MediaAssets: MediaPlayerBase instance is now a field instead of pointer.

Change 3195802 on 2016/11/11 by Max.Preussner

	Media: Removed UMediaPlayer::GetNativePlayer

Change 3195843 on 2016/11/11 by Max.Preussner

	Kismet: Fixed non-unity

Change 3195851 on 2016/11/11 by Max.Preussner

	Fixed typo.

Change 3195854 on 2016/11/11 by Max.Preussner

	MediaUtils: Added missing forward declaration

Change 3195937 on 2016/11/11 by Max.Chen

	Media: CIS Fix

Change 3196120 on 2016/11/13 by Max.Chen

	Sequencer: Weight curve for skeletal animation section.

	Changed skeletal template evaluation so that it works with multiple animation tracks. The shared track clears all the weights, the section gathers up all the data, and the shared track evaluates the data. Otherwise, the multiple track evaluations would conflict with each other in setting states back and forth.

	#jira UE-38374, UEFW-128

Change 3196265 on 2016/11/13 by Max.Chen

	Sequencer: Fix audio waveforms so that they're regenrated when audio start time is changed.

	#jira UE-38543

Change 3196421 on 2016/11/14 by Andrew.Rodham

	Sequencer: Fixed modified tracks not being written to the transaction buffer when replacing object bindings

	#jira UE-38423

Change 3197131 on 2016/11/14 by Max.Chen

	Sequencer: Null checks.

	#jira UE-38570, UE-38593

Change 3197209 on 2016/11/14 by Max.Chen

	Cine Camera: Reset focus smoothing interpolation on PostEditChangeProperty. This fixes an issue where if you enable focus smoothing, the manual focus distance that is input isn't used since the interpolation happens from the last current focus distance.

	#jira UE-27055

Change 3198691 on 2016/11/15 by Max.Chen

	Sequence Recorder: Optimize record transforms by setting all the keyframes at once.  Also, added option to toggle removing redundant keyframes from the recorded tracks.

	#jira UE-38489

Change 3198711 on 2016/11/15 by andrew.porter

	Adding test content for MEdia Framework Track Switching.

Change 3199174 on 2016/11/15 by Lina.Halper

	Sequencer backward compatibility fix with root motion
	Make sure you could remove root motion fine

	#jira : UE-38591

Change 3199260 on 2016/11/15 by tim.gautier

	Updated QA-Media_TrackSwitch - changed Trigger Collision to only detect overlap from PlayerPawn

Change 3199663 on 2016/11/15 by Max.Chen

	Anim Sequencer: Fix deprecation warning for bCanUseParallelUpdateAnimation. Updated to use bUseMultiThreadedAnimationUpdate.

Change 3199727 on 2016/11/15 by Max.Chen

	Matinee to Level Sequence: Set default scale when converting matinee move tracks to sequencer.

	#jira UE-38688

Change 3199847 on 2016/11/16 by Max.Chen

	Sequencer: Add menu option to reduce keys of all sections in the current level sequence

Change 3200351 on 2016/11/16 by Max.Chen

	Level Editor/Sequencer: Fixes to allow for component keyframing. The transform track operates on the components that changed, not the actor. The level editor viewport broadcasts begin/end movement on the components that changed.

	#jira UE-38649, UE-38646

Change 3200474 on 2016/11/16 by Max.Chen

	Sequencer: Move reduce keys to section context menu.

Change 3200888 on 2016/11/16 by Max.Chen

	Sequencer: Clamp skeletal animation evaluation remapping of time to section bounds. This is necessary when evaluating nearest is enabled and the time is beyond the section bounds.

	Also, set the shared track template to have higher priority so that it always clears/initializes weights before each section's template adds section params for evaluation.

Change 3201633 on 2016/11/17 by Max.Chen

	Matinee to Level Sequence: Fix matinee 3d scale track conversion to level sequence.

	Also, added paste matinee vector track to sequencer's vector track.

	#jira UE-38688

Change 3202458 on 2016/11/17 by Max.Chen

	Sequencer: Fix track editor commands getting unregistered when switching from one level sequence to another.  The sequence of events is: track editor commands get bound when a level sequence is edited. When switching to another level sequence, the existing track editor is released after the new one is registered, causing the commands to ultimately get unbound.

	#jira UE-38693

Change 3202606 on 2016/11/17 by Max.Chen

	Actor Sequence: Null check in CanPossessObject for a component's owner.

	#jira UE-38514

Change 3203522 on 2016/11/17 by Max.Chen

	Sequencer: Audio start time deprecated in favor of start offset which is an offset into the audio clip. Also, limit the start offset to positive values since you can just crop into the audio clip by dragging the section's start time.

	Audio track no longer supports multiple rows (should have been checked in along with the audio volume and pitch multiplier curves).

	#jira UE-38549, UE-38554, UE-38547

Change 3203863 on 2016/11/18 by Andrew.Rodham

	Engine: Ensure that world settings actor is considered by network object list when sorting the actor list for a level

Change 3203865 on 2016/11/18 by Andrew.Rodham

	Sequencer: Fixed play rate track interaction between servers and clients
	  - The logic for evaluation was previously flawed (it would only run in editor builds). Play rate is now only evaluated on servers and standalone clients, with the time dilation being replicated to network clients.

Change 3203900 on 2016/11/18 by Andrew.Rodham

	Sequencer: Changed CreateLevelSequencePlayer to create a transient level sequence actor

	#jira UE-37277

Change 3205038 on 2016/11/18 by Max.Preussner

	Slate: Corrected comment

Change 3205046 on 2016/11/18 by Max.Preussner

	WmfMedia: Added missing nullptr check

	#jira UE-38825

Change 3205073 on 2016/11/18 by Max.Chen

	Sequencer: Fix audio upgrade case when start time is 0.

Change 3205277 on 2016/11/19 by Max.Preussner

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

	Please take a look at SequencerEdMode.cpp and Sequencer.cpp. I ended up accepting latest Dev-Sequencer, which seemed to be the right thing to do.

Change 3205465 on 2016/11/20 by Max.Preussner

	MovieScene: Fixed non-unity build

Change 3205467 on 2016/11/20 by Max.Preussner

	Engine: Fixed spelling

Change 3206264 on 2016/11/21 by Max.Preussner

	Kismet: Added missing forward declaration

Change 3206493 on 2016/11/21 by Max.Preussner

	PS4Media: Added remaining changes for removing game thread dependencies

Change 3206512 on 2016/11/21 by Andrew.Porter

	Adding test content to QAGame for Sequencer animation weight blending.

Change 3206529 on 2016/11/21 by Lina.Halper

	Fixed anim notifes to work in Sequencer Instance

	- Give proper delta in editor preview
	- Make sure not to recreate AnimInstance

	#jira: UE-38849
	#code review:Max.Chen

Change 3206552 on 2016/11/21 by Max.Preussner

	QAGame: Enabled looping by default

Change 3207462 on 2016/11/22 by andrew.porter

	QAGame: updating QA-Sequencer with changes to animation blending test cases

Change 3207499 on 2016/11/22 by tim.gautier

	Added Streaming Sources, added Streaming Source options for BP_MediaPlayer. Specified Media Option Categories with BP_MediaPlayer to clean up details panel.

	#jira none

Change 3207571 on 2016/11/22 by Max.Chen

	Curve Editor: Expose curve editor settings to Editor Preferences.

	#jira UE-38907

Change 3207690 on 2016/11/22 by Max.Chen

	Sequencer: Speculative crash fix for switching UMG animations.

	#jira UE-29333

Change 3207744 on 2016/11/22 by tim.gautier

	Removed unnecessary nodes from BP_MediaPlayer. Created a variable visible in the Details Panel to allow the user to specify a URL to Stream media without specifying a Source in-editor.

	#jira none

Change 3207935 on 2016/11/22 by Max.Chen

	Sequencer: Temporary fix for skeletal animation track scrubbing. Verified that anim notifies still fire when playing and scrubbing.

	#jira UE-38964

Change 3207938 on 2016/11/22 by Max.Chen

	Sequence Recorder: Set reduce keys back to true so that there's no change in current behavior. This should be toggled off for performance reasons but in general is nice to have reduced keys.

Change 3207950 on 2016/11/22 by Lina.Halper

	- Fixed so that mesh space additive won't show up in sequencer
	- Added warning if you change type later or existing ones

	#jira: UE-38062?

Change 3208278 on 2016/11/22 by andrew.porter

	QAGame: Adjusting level blueprint for test case.

Change 3208285 on 2016/11/22 by andrew.porter

	QAGame: adding SequencerBP animation blueprint.

Change 3208538 on 2016/11/23 by Max.Chen

	Actor Sequence: Fix plugin filename.

Change 3208916 on 2016/11/23 by Max.Chen

	Sequencer: Fix material parameter initialization so that the value is retrieved from the material instance and not the parent material.

	#jira UE-34317

Change 3208924 on 2016/11/23 by Max.Chen

	Save As: Cancel should not save over the existing asset. It should just return.

Change 3208939 on 2016/11/23 by andrew.porter

	QAGame: reset some content back to its default state for testing

Change 3209053 on 2016/11/23 by Max.Chen

	Sequencer: Ensure the section id is unique.

Change 3209161 on 2016/11/23 by Max.Chen

	Save As: Follow up fix for cancelling save as.

Change 3210540 on 2016/11/26 by Max.Preussner

	WmfMedia: Reworked fallback stride calculations to fix issues with some exotic video formats

Change 3210546 on 2016/11/26 by Max.Preussner

	WmfMedia: Fixed NV12 vertical buffer alignment

Change 3211567 on 2016/11/28 by Max.Preussner

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

	Step 1 of 2

Change 3212408 on 2016/11/28 by Max.Preussner

	Fixed fallout from Dev-Main merge

Change 3212456 on 2016/11/28 by Max.Preussner

	ActorSequenceEditor: Removed monolithic header dependencies

Change 3212562 on 2016/11/28 by Max.Preussner

	ActorSequenceEditor: Removed monolithic header usage

Change 3212649 on 2016/11/28 by Max.Chen

	Fix CIS

Change 3212671 on 2016/11/28 by Max.Chen

	Sequencer: Add option to restore to the pre animated state.

	#jira UE-38862
	#2953

Change 3212672 on 2016/11/28 by Max.Chen

	Sequencer: Select object binding node corresponding to selected components and vice versa (select components in level when object binding node is selected)

Change 3212673 on 2016/11/28 by Max.Chen

	Sequencer: Follow-up fix for component keyframing - key area needs to be updated by component.

	#jira UE-38649

Change 3212676 on 2016/11/28 by Max.Chen

	Level Editor: PostEditMove should only be called on the actor if it is moved.

	#jira UE-38646

Change 3212688 on 2016/11/29 by Max.Chen

	Sequencer: Force refresh event parameters customization when struct contents change but not a full refresh when struct child contents change.

	#jira UE-39094

Change 3212831 on 2016/11/29 by Andrew.Rodham

	Disabled ActorSequenceEditor plugin by default while it's experimental

Change 3213219 on 2016/11/29 by Max.Preussner

	AvfMedia: Added missing include

Change 3213333 on 2016/11/29 by Andrew.Rodham

	Sequencer: Added the ability to override bindings when playing back a level sequence on a level sequence actor

	#jira UETOOL-746

Change 3213905 on 2016/11/29 by Max.Preussner

	More IWYU fixes for macOS

Change 3214203 on 2016/11/29 by Michael.Gay

	Some demo files to test Sequencer timing.

Change 3214205 on 2016/11/29 by Max.Preussner

	More IWYU fixes for macOS

Change 3214548 on 2016/11/29 by Max.Preussner

	More IWYU fixes for macOS

Change 3214564 on 2016/11/29 by Max.Preussner

	More IWYU fixes

Change 3214567 on 2016/11/29 by Max.Chen

	More IWYU fixes for Win32

Change 3214573 on 2016/11/29 by Max.Preussner

	More IWYU fixes

Change 3214576 on 2016/11/29 by Max.Preussner

	More IWYU fixes

Change 3214621 on 2016/11/30 by Max.Preussner

	Atrac9Decoder: Fixed log category declaration

Change 3214630 on 2016/11/30 by Max.Preussner

	More IWYU fixes

Change 3214747 on 2016/11/30 by Andrew.Rodham

	Sequencer: Fixed shadow variable

Change 3214957 on 2016/11/30 by Andrew.Rodham

	Core: Changed Algo::Find to use TElementType
	  - This allows it to support c style arrays

Change 3215127 on 2016/11/30 by Andrew.Rodham

	Sequencer: Made burn-in options and init settings instanced

	  - This ensures they work correctly when defined on archetypes and blueprints

	#jira UE-38645

Change 3215754 on 2016/11/30 by Max.Chen

	Sequencer: Fix skeletal animation track evaluating tracks in the wrong time space. Cache the evalulation time and weight value in each section's template and then execute with those values in the shared track's template.

	#jira UE-39145

Change 3216603 on 2016/12/01 by Max.Chen

	Sequencer: Set audio volume/pitch only if changed.

Change 3216613 on 2016/12/01 by Max.Chen

	Sequencer: Add component selector when there are multiple components that have sockets. This fixes a crash when there are multiple components to attach to.

	#jira UE-39167

Change 3217175 on 2016/12/01 by Max.Chen

	Sequencer: Set skeletal animation track evaluation to be upper bound exclusive. This gives better behavior when two clips butt up against each other since the sections would overlap in time and evaluation would normalize they weighted contribution of each.

	#jira UE-37184

Change 3217292 on 2016/12/01 by Max.Chen

	Sequencer: Rework upgrading track rows to include overlapping sections. For skeletal animation sections, set weight values based on the evaluation bounds since there was no blending prior to 4.15.

Change 3217860 on 2016/12/01 by Max.Preussner

	Media: Fall-through for media options

Change 3217965 on 2016/12/01 by Max.Preussner

	MediaAssets: Renamed media option name

Change 3218470 on 2016/12/01 by Max.Chen

	Sequencer: Fix start time deprecation value so that negative values are supported.

	#jira UE-39259

Change 3218473 on 2016/12/01 by Max.Chen

	Sequencer: Fix crash if start seq length is negative.

Change 3219021 on 2016/12/02 by Max.Chen

	Sequencer: Add multiply and divide to transform box.

Change 3219374 on 2016/12/02 by Max.Chen

	Sequencer: Teleport simulating components when moving them through the transform track. This fixes bugs with recording simulating actors (ie. vehicle game) where recorded actors don't playback with the recorded positions and there are warnings about attempting to move a fully simulated skeletal mesh.

	#jira UE-38442, UE-38444, UE-38852

Change 3219638 on 2016/12/02 by Max.Preussner

	Projects: Fixed error message

Change 3220584 on 2016/12/03 by Andrew.Rodham

	Sequencer: Blueprint generated classes are now always removed from level sequences on load in the editor
	  - This ensures that old (and perhaps corrupt) BP generated classes are destroyed

	#jira UE-39173

Change 3220585 on 2016/12/03 by Andrew.Rodham

	Editor: Fix EditInstanceOnly properties that aren't variables on the generated class being editable in blueprints

Change 3220973 on 2016/12/04 by Max.Chen

	Fix CIS

Change 3222833 on 2016/12/05 by Max.Chen

	Sequencer: Fixed some recorded components not being generated.

	#jira UE-34289

Change 3224450 on 2016/12/06 by Max.Chen

	Sequencer: Fix convert spawnable to posessable. Logic for setting the parent was mistakenly removed in runtime eval.

	#jira UE-39419

Change 3225301 on 2016/12/07 by Max.Preussner

	AvfMedia: Added settings class

Change 3225304 on 2016/12/07 by Max.Preussner

	Fixed typo

Change 3225723 on 2016/12/07 by Max.Preussner

	Fixed typo.

Change 3225871 on 2016/12/07 by Max.Preussner

	Forgot to check in

Change 3225932 on 2016/12/07 by Max.Preussner

	Added missing header

Change 3226266 on 2016/12/07 by Max.Preussner

	Media: Fixed various module dependencies

Change 3226451 on 2016/12/07 by Max.Preussner

	Include fixes

Change 3226455 on 2016/12/07 by Max.Preussner

	LevelSequence: Added missing include

Change 3227135 on 2016/12/08 by Max.Preussner

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

Change 3227143 on 2016/12/08 by Max.Preussner

	LevelSequencer: Added missing header

Change 3227731 on 2016/12/08 by Max.Preussner

	LevelSequencer: Added missing include

Change 3228222 on 2016/12/08 by Max.Preussner

	UBT: Fixed delay load library support for remote compilation to macOS

Change 3228266 on 2016/12/08 by Max.Preussner

	PluginBrowser: Added missing includes

Change 3228755 on 2016/12/09 by Andrew.Rodham

	Sequencer: Fixed copy-paste of event keys
	  - Also added a key-value iterator to TCurveInterface (both const and non-const)

	#jira UE-39526

Change 3228777 on 2016/12/09 by Luke.Thatcher

	[PLATFORM] [PS4] [!] Reimplement fixes from Fortnite for PS4 media framework in //UE4/Dev-Sequencer.

	Based on Original CL 3227137

	 - Event callback from AvPlayer was enqueing the processing of events over to the player thread, so the "State" member of FPS4MediaPlayer doesn't get updated until the following frame. This breaks cases with multiple calls to SetRate within a single frame.
	 - Removed time check in FPS4MediavideoSampler::Tick. There are cases where the time check failed, even when a new frame was available from the AvPlayer libs. The video sampler now always calls sceAvPlayerGetVideoDataEx. This returns immediately if no frame data is available.
	 - FPS4MediaPlayer::Seek was failing if the video is in a playing/paused state. We now restart the stream if a seek command occurs after the video has stopped (e.g. due to EOF reached).
	 - Shared a single critical section between the FPS4MediaTracks, FPS4MediaVideoSampler and FPS4MediaPlayer objects. Fixes deadlocks between the decoder/player threads where each will be waiting on each others' critical section.

	[~] Enabled debug warnings from AvPlayer library in non-shipping builds.
	[~] Changed log levels of UE_LOGs to match their severity.

	-------------------------

	[!] Also, fixed rendering artifacts on videos using a cropping rectangle
	 - e.g. 1080p videos are actually decoded as 1920x1088, with an extra 8 pixels height, which contained garbage.
	 - We determine the final media texture size as the size of the cropping rectangle, and use modified UVs during the YCbCr->RGB converstion shader to do the mapping.

Change 3228793 on 2016/12/09 by Andrew.Rodham

	Sequencer: Edits to actor sequences now correctly mark their parent blueprints for compilation

	#jira UE-38723

Change 3228877 on 2016/12/09 by Luke.Thatcher

	[PLATFORM] [PS4] [!] Fix track switching issues in PS4 media player.
	 - Sony's AvPlayer library does not support switching tracks (audio or video) on-the-fly after a stream has begun playback.
	 - The higher level UMediaPlayer enables track 0 automatically, which would be committed to the AvPlayer, and therefore lock out other streams.
	 - Actual track selection is now deferred until the stream is started, after which changing tracks is prohibited.
	 - Tracks must be selected before calling SetRate for the first time.

	#jira UE-37225

Change 3229501 on 2016/12/09 by Max.Preussner

	Media: Better display names for media player plug-ins

Change 3229515 on 2016/12/09 by Max.Preussner

	MediaPlayerEditor: Sorting player plug-ins alphabetically; consistent display in both media player editor and media source customization

Change 3229716 on 2016/12/09 by andrew.porter

	Adding PlayRate sequence to my dev folder

Change 3230554 on 2016/12/12 by Andrew.Rodham

	Back out changelist 3220584
	  - Currently this causes actor instances to fail to load because they are instanced of dead classes. Need to think of a more robust solution here.

	#jira UE-39398

Change 3230922 on 2016/12/12 by Max.Preussner

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

Change 3232059 on 2016/12/12 by Max.Preussner

	MediaUtils: Better error message for when no suitable media player plug-in was found

Change 3232097 on 2016/12/13 by Max.Preussner

	Switch: Temp fix for borked folder name on case-sensitive platforms

Change 3232100 on 2016/12/13 by Max.Preussner

	MediaAssets: Split up UMediaSource into UBaseMediaSource

	Also added color space related properties

Change 3232101 on 2016/12/13 by Max.Preussner

	Media: Started to implement support for color spaces

Change 3232119 on 2016/12/13 by Max.Preussner

	MediaAssets: Fixed buffer not recreated if color space changed

Change 3232799 on 2016/12/13 by Max.Preussner

	PS4Media: Fixed build

	#jira UE-39706

Change 3233170 on 2016/12/13 by Max.Preussner

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

Change 3233250 on 2016/12/13 by Max.Preussner

	MediaPlayerEditor: Added separator in track menu

Change 3233309 on 2016/12/13 by andrew.porter

	QAGame: Edited text render actors in QA-Media_TrackSwitch

Change 3233439 on 2016/12/13 by Chris.Babcock

	Standardize Android media track DisplayName

Change 3233817 on 2016/12/13 by Chris.Babcock

	Fix virtual keyboard EditableTextBox update when comitted text matches current text from change updates
	#jira UE-39424
	#ue4
	#mobile

Change 3234421 on 2016/12/14 by Andrew.Rodham

	Sequencer: Fixed nullptr crash

Change 3234423 on 2016/12/14 by Andrew.Rodham

	Sequencer: Fixed incorrect copying of base-class from compiler rules

Change 3234429 on 2016/12/14 by Andrew.Rodham

	Sequencer: Fixed empty space not being added between the last and penultimate segments when required

	#jira UE-39442

Change 3234635 on 2016/12/14 by Max.Preussner

	MediaAssets: Exposed UTexture properties in UMediaTexture

Change 3234681 on 2016/12/14 by Max.Preussner

	MediaAssets: Made MediaTextureResources support -onethread

Change 3234878 on 2016/12/14 by Andrew.Rodham

	Sequencer: Fixed crash with "Evaluate Sub Sequences in Isolation" enabled
	  - This occurred when there were tracks at the root level of the sub sequence, because it would incorrectly hash in the parent ID, rather than just using it directly

Change 3234901 on 2016/12/14 by Max.Preussner

	MediaPlayerEditor: Detail customization improvements

Change 3235275 on 2016/12/14 by Chris.Babcock

	Fix WMF stream ordering to match other players
	#jira UE-39703
	#ue4
	#mediaframework

Change 3235390 on 2016/12/14 by Max.Preussner

	DesktopPlatform: Added IniPlatformName to FPlatformInfo; fixed up indentation

Change 3235402 on 2016/12/14 by Max.Preussner

	MediaAssets: Fixed platform player name overrides ignored in packaged builds (UE-39771)

	#jira UE-39771

Change 3235667 on 2016/12/14 by Max.Preussner

	Media: Moved enums into separate header file, so they can be shared

Change 3235984 on 2016/12/14 by Max.Preussner

	Back out changelist 3235667

Change 3236040 on 2016/12/14 by Max.Preussner

	Core: Added modulus operator to FTimespan

Change 3236139 on 2016/12/15 by Max.Preussner

	Core: Added FTimespan::IsZero

Change 3236527 on 2016/12/15 by Max.Preussner

	Fixed initialization order

Change 3237101 on 2016/12/15 by Andrew.Rodham

	Sequencer: Skeletal animation and audio tracks now support multiple rows again.
	  - In practice there were too many edge-cases to account for whilst considering backwards compatability
	  - The impossible scenario was 2 sections on different rows, but evaluating nearest section - this cannot be represented as separate tracks.
	  - Reorganised animation runtime template to use execution tokens rather than ::Initialize to ensure that animation operates correctly on the first frame for spawned objects

	#jira UE-39442
	#jira UE-39725

Change 3237213 on 2016/12/15 by Andrew.Rodham

	Sequencer: Fixed crash when setting event key properties

	#jira UE-39347

Change 3237255 on 2016/12/15 by Chris.Babcock

	Fix Multi with ETC2 and PVRTC selecting ES3.0 instead of 2.0
	#jira UE-39839
	#ue4
	#android

Change 3237294 on 2016/12/15 by Andrew.Rodham

	Sequencer: Fixed shadowed variable warnings

Change 3237366 on 2016/12/15 by Max.Preussner

	Media: Removed color space changes; we'll do these in material graphs instead

Change 3237436 on 2016/12/15 by Andrew.Rodham

	Sequencer: Fixed montages not being stopped for specific animation slots when animation sections were no longer evaluated

	#jira UE-39847

Change 3237458 on 2016/12/15 by Andrew.Rodham

	Sequencer: Always force regeneration of templates when PIE  to eliminate the posibility of combining stale data

Change 3237516 on 2016/12/15 by Max.Preussner

	Media: Attempting to fix Crash in fortnite just before exiting onboarding (UE-39841)

	#jira UE-39841

Change 3237532 on 2016/12/15 by Max.Preussner

	Added missing scope lock

Change 3237991 on 2016/12/16 by Max.Preussner

	PS4Media: Fixed build

[CL 3238204 by Max Preussner in Main branch]
2016-12-16 11:17:44 -05:00

3068 lines
97 KiB
C++

// Copyright 1998-2017 Epic Games, Inc. All Rights Reserved.
#include "PropertyNode.h"
#include "Misc/ConfigCacheIni.h"
#include "UObject/MetaData.h"
#include "Serialization/ArchiveReplaceObjectRef.h"
#include "Components/ActorComponent.h"
#include "Editor/UnrealEdEngine.h"
#include "Engine/UserDefinedStruct.h"
#include "UnrealEdGlobals.h"
#include "ScopedTransaction.h"
#include "PropertyRestriction.h"
#include "Kismet2/StructureEditorUtils.h"
#include "Kismet2/BlueprintEditorUtils.h"
#include "Misc/ScopeExit.h"
#include "Editor.h"
#include "ObjectPropertyNode.h"
#include "PropertyHandleImpl.h"
#include "EditorSupportDelegates.h"
#include "ConstructorHelpers.h"
#include "Framework/Notifications/NotificationManager.h"
#include "Widgets/Notifications/SNotificationList.h"
#define LOCTEXT_NAMESPACE "PropertyNode"
FPropertySettings& FPropertySettings::Get()
{
static FPropertySettings Settings;
return Settings;
}
FPropertySettings::FPropertySettings()
: bShowFriendlyPropertyNames( true )
, bExpandDistributions( false )
, bShowHiddenProperties(false)
{
GConfig->GetBool(TEXT("PropertySettings"), TEXT("ShowHiddenProperties"), bShowHiddenProperties, GEditorPerProjectIni);
GConfig->GetBool(TEXT("PropertySettings"), TEXT("ShowFriendlyPropertyNames"), bShowFriendlyPropertyNames, GEditorPerProjectIni);
GConfig->GetBool(TEXT("PropertySettings"), TEXT("ExpandDistributions"), bExpandDistributions, GEditorPerProjectIni);
}
DEFINE_LOG_CATEGORY(LogPropertyNode);
static FObjectPropertyNode* NotifyFindObjectItemParent(FPropertyNode* InNode)
{
FObjectPropertyNode* Result = NULL;
check(InNode);
FPropertyNode* ParentNode = InNode->GetParentNode();
if (ParentNode)
{
Result = ParentNode->FindObjectItemParent();
}
return Result;
}
FPropertyNode::FPropertyNode(void)
: ParentNode(NULL)
, Property(NULL)
, ArrayOffset(0)
, ArrayIndex(-1)
, MaxChildDepthAllowed(FPropertyNodeConstants::NoDepthRestrictions)
, PropertyNodeFlags (EPropertyNodeFlags::NoFlags)
, bRebuildChildrenRequested( false )
, PropertyPath(TEXT(""))
, bIsEditConst(false)
, bUpdateEditConstState(true)
, bDiffersFromDefault(false)
, bUpdateDiffersFromDefault(true)
{
}
FPropertyNode::~FPropertyNode(void)
{
DestroyTree();
}
void FPropertyNode::InitNode( const FPropertyNodeInitParams& InitParams )
{
//Dismantle the previous tree
DestroyTree();
//tree hierarchy
check(InitParams.ParentNode.Get() != this);
ParentNode = InitParams.ParentNode.Get();
ParentNodeWeakPtr = InitParams.ParentNode;
if (ParentNode)
{
//default to parents max child depth
MaxChildDepthAllowed = ParentNode->MaxChildDepthAllowed;
//if limitless or has hit the full limit
if (MaxChildDepthAllowed > 0)
{
--MaxChildDepthAllowed;
}
}
//Property Data
Property = InitParams.Property;
ArrayOffset = InitParams.ArrayOffset;
ArrayIndex = InitParams.ArrayIndex;
// Property is advanced if it is marked advanced or the entire class is advanced and the property not marked as simple
bool bAdvanced = Property.IsValid() ? ( Property->HasAnyPropertyFlags(CPF_AdvancedDisplay) || ( !Property->HasAnyPropertyFlags( CPF_SimpleDisplay ) && Property->GetOwnerClass() && Property->GetOwnerClass()->HasAnyClassFlags( CLASS_AdvancedDisplay ) ) ): false;
PropertyNodeFlags = EPropertyNodeFlags::NoFlags;
//default to copying from the parent
if (ParentNode)
{
SetNodeFlags(EPropertyNodeFlags::ShowCategories, !!ParentNode->HasNodeFlags(EPropertyNodeFlags::ShowCategories));
// We are advanced if our parent is advanced or our property is marked as advanced
SetNodeFlags(EPropertyNodeFlags::IsAdvanced, ParentNode->HasNodeFlags(EPropertyNodeFlags::IsAdvanced) || bAdvanced );
}
else
{
SetNodeFlags(EPropertyNodeFlags::ShowCategories, InitParams.bCreateCategoryNodes );
}
SetNodeFlags(EPropertyNodeFlags::ShouldShowHiddenProperties, InitParams.bForceHiddenPropertyVisibility);
SetNodeFlags(EPropertyNodeFlags::ShouldShowDisableEditOnInstance, InitParams.bCreateDisableEditOnInstanceNodes);
//Custom code run prior to setting property flags
//needs to happen after the above SetNodeFlags calls so that ObjectPropertyNode can properly respond to CollapseCategories
InitBeforeNodeFlags();
bool bIsEditInlineNew = false;
bool bShowInnerObjectProperties = false;
if ( !Property.IsValid() )
{
// Disable all flags if no property is bound.
SetNodeFlags(EPropertyNodeFlags::SingleSelectOnly | EPropertyNodeFlags::EditInlineNew | EPropertyNodeFlags::ShowInnerObjectProperties, false);
}
else
{
const bool GotReadAddresses = GetReadAddressUncached( *this, false, nullptr, false );
const bool bSingleSelectOnly = GetReadAddressUncached( *this, true, nullptr);
SetNodeFlags(EPropertyNodeFlags::SingleSelectOnly, bSingleSelectOnly);
UProperty* MyProperty = Property.Get();
const bool bIsObjectOrInterface = Cast<UObjectPropertyBase>(MyProperty) || Cast<UInterfaceProperty>(MyProperty);
// true if the property can be expanded into the property window; that is, instead of seeing
// a pointer to the object, you see the object's properties.
static const FName Name_EditInline("EditInline");
static const FName Name_ShowInnerProperties("ShowInnerProperties");
bIsEditInlineNew = bIsObjectOrInterface && GotReadAddresses && MyProperty->HasMetaData(Name_EditInline);
bShowInnerObjectProperties = bIsObjectOrInterface && MyProperty->HasMetaData(Name_ShowInnerProperties);
if(bIsEditInlineNew)
{
SetNodeFlags(EPropertyNodeFlags::EditInlineNew, true);
}
else if(bShowInnerObjectProperties)
{
SetNodeFlags(EPropertyNodeFlags::ShowInnerObjectProperties, true);
}
//Get the property max child depth
static const FName Name_MaxPropertyDepth("MaxPropertyDepth");
if (Property->HasMetaData(Name_MaxPropertyDepth))
{
int32 NewMaxChildDepthAllowed = Property->GetINTMetaData(Name_MaxPropertyDepth);
//Ensure new depth is valid. Otherwise just let the parent specified value stand
if (NewMaxChildDepthAllowed > 0)
{
//if there is already a limit on the depth allowed, take the minimum of the allowable depths
if (MaxChildDepthAllowed >= 0)
{
MaxChildDepthAllowed = FMath::Min(MaxChildDepthAllowed, NewMaxChildDepthAllowed);
}
else
{
//no current limit, go ahead and take the new limit
MaxChildDepthAllowed = NewMaxChildDepthAllowed;
}
}
}
}
InitExpansionFlags();
UProperty* MyProperty = Property.Get();
bool bRequiresValidation = bIsEditInlineNew || bShowInnerObjectProperties || ( MyProperty && (MyProperty->IsA<UArrayProperty>() || MyProperty->IsA<USetProperty>() || MyProperty->IsA<UMapProperty>() ));
// We require validation if our parent also needs validation (if an array parent was resized all the addresses of children are invalid)
bRequiresValidation |= (GetParentNode() && GetParentNode()->HasNodeFlags( EPropertyNodeFlags::RequiresValidation ) != 0);
SetNodeFlags( EPropertyNodeFlags::RequiresValidation, bRequiresValidation );
if ( InitParams.bAllowChildren )
{
RebuildChildren();
}
PropertyPath = FPropertyNode::CreatePropertyPath(this->AsShared())->ToString();
}
/**
* Used for rebuilding a sub portion of the tree
*/
void FPropertyNode::RebuildChildren()
{
CachedReadAddresses.Reset();
bool bDestroySelf = false;
DestroyTree(bDestroySelf);
if (MaxChildDepthAllowed != 0)
{
//the case where we don't want init child nodes is when an Item has children that we don't want to display
//the other option would be to make each node "Read only" under that item.
//The example is a material assigned to a static mesh.
if (HasNodeFlags(EPropertyNodeFlags::CanBeExpanded) && (ChildNodes.Num() == 0))
{
InitChildNodes();
}
}
//see if they support some kind of edit condition
if (Property.IsValid() && Property->GetBoolMetaData(TEXT("FullyExpand")))
{
bool bExpand = true;
bool bRecurse = true;
}
// Children have been rebuilt, clear any pending rebuild requests
bRebuildChildrenRequested = false;
// Notify any listener that children have been rebuilt
OnRebuildChildren.ExecuteIfBound();
}
void FPropertyNode::AddChildNode(TSharedPtr<FPropertyNode> InNode)
{
ChildNodes.Add(InNode);
}
void FPropertyNode::ClearCachedReadAddresses( bool bRecursive )
{
CachedReadAddresses.Reset();
if( bRecursive )
{
for( int32 ChildIndex = 0; ChildIndex < ChildNodes.Num(); ++ChildIndex )
{
ChildNodes[ChildIndex]->ClearCachedReadAddresses( bRecursive );
}
}
}
// Follows the chain of items upwards until it finds the object window that houses this item.
FComplexPropertyNode* FPropertyNode::FindComplexParent()
{
FPropertyNode* Cur = this;
FComplexPropertyNode* Found = NULL;
while( true )
{
Found = Cur->AsComplexNode();
if( Found )
{
break;
}
Cur = Cur->GetParentNode();
if( !Cur )
{
// There is a break in the parent chain
break;
}
}
return Found;
}
// Follows the chain of items upwards until it finds the object window that houses this item.
const FComplexPropertyNode* FPropertyNode::FindComplexParent() const
{
const FPropertyNode* Cur = this;
const FComplexPropertyNode* Found = NULL;
while( true )
{
Found = Cur->AsComplexNode();
if( Found )
{
break;
}
Cur = Cur->GetParentNode();
if( !Cur )
{
// There is a break in the parent chain
break;
}
}
return Found;
}
class FObjectPropertyNode* FPropertyNode::FindObjectItemParent()
{
auto ComplexParent = FindComplexParent();
if (!ComplexParent)
{
return nullptr;
}
if (FObjectPropertyNode* ObjectNode = ComplexParent->AsObjectNode())
{
return ObjectNode;
}
else if (FPropertyNode* ParentNodePtr = ComplexParent->GetParentNode())
{
return ParentNodePtr->FindObjectItemParent();
}
return nullptr;
}
const class FObjectPropertyNode* FPropertyNode::FindObjectItemParent() const
{
const auto ComplexParent = FindComplexParent();
if (!ComplexParent)
{
return nullptr;
}
if (const FObjectPropertyNode* ObjectNode = ComplexParent->AsObjectNode())
{
return ObjectNode;
}
else if (const FPropertyNode* ParentNodePtr = ComplexParent->GetParentNode())
{
return ParentNodePtr->FindObjectItemParent();
}
return nullptr;
}
/**
* Follows the top-most object window that contains this property window item.
*/
FObjectPropertyNode* FPropertyNode::FindRootObjectItemParent()
{
// not every type of change to property values triggers a proper refresh of the hierarchy, so find the topmost container window and trigger a refresh manually.
FObjectPropertyNode* TopmostObjectItem=NULL;
FObjectPropertyNode* NextObjectItem = FindObjectItemParent();
while ( NextObjectItem != NULL )
{
TopmostObjectItem = NextObjectItem;
FPropertyNode* NextObjectParent = NextObjectItem->GetParentNode();
if ( NextObjectParent != NULL )
{
NextObjectItem = NextObjectParent->FindObjectItemParent();
}
else
{
break;
}
}
return TopmostObjectItem;
}
bool FPropertyNode::DoesChildPropertyRequireValidation(UProperty* InChildProp)
{
return InChildProp != nullptr && (Cast<UObjectProperty>(InChildProp) != nullptr || Cast<UStructProperty>(InChildProp) != nullptr);
}
/**
* Used to see if any data has been destroyed from under the property tree. Should only be called by PropertyWindow::OnIdle
*/
EPropertyDataValidationResult FPropertyNode::EnsureDataIsValid()
{
bool bValidateChildren = !HasNodeFlags(EPropertyNodeFlags::SkipChildValidation);
bool bValidateChildrenKeyNodes = false; // by default, we don't check this, since it's just for Map properties
// The root must always be validated
if( GetParentNode() == NULL || HasNodeFlags(EPropertyNodeFlags::RequiresValidation) != 0 )
{
CachedReadAddresses.Reset();
//Figure out if an array mismatch can be ignored
bool bIgnoreAllMismatch = false;
//make sure that force depth-limited trees don't cause a refresh
bIgnoreAllMismatch |= (MaxChildDepthAllowed==0);
//check my property
if (Property.IsValid())
{
UProperty* MyProperty = Property.Get();
//verify that the number of container children is correct
UArrayProperty* ArrayProperty = Cast<UArrayProperty>(MyProperty);
USetProperty* SetProperty = Cast<USetProperty>(MyProperty);
UMapProperty* MapProperty = Cast<UMapProperty>(MyProperty);
UStructProperty* StructProperty = Cast<UStructProperty>(MyProperty);
//default to unknown array length
int32 NumArrayChildren = -1;
//assume all arrays have the same length
bool bArraysHaveEqualNum = true;
//assume all arrays match the number of property window children
bool bArraysMatchChildNum = true;
bool bArrayHasNewItem = false;
UProperty* ContainerElementProperty = MyProperty;
if (ArrayProperty)
{
ContainerElementProperty = ArrayProperty->Inner;
}
else if (SetProperty)
{
ContainerElementProperty = SetProperty->ElementProp;
}
else if (MapProperty)
{
// Need to attempt to validate both the key and value properties...
bValidateChildrenKeyNodes = DoesChildPropertyRequireValidation(MapProperty->KeyProp);
ContainerElementProperty = MapProperty->ValueProp;
}
bValidateChildren = DoesChildPropertyRequireValidation(ContainerElementProperty);
//verify that the number of object children are the same too
UObjectPropertyBase* ObjectProperty = Cast<UObjectPropertyBase>(MyProperty);
//check to see, if this an object property, whether the contents are NULL or not.
//This is the check to see if an object property was changed from NULL to non-NULL, or vice versa, from non-property window code.
bool bObjectPropertyNull = true;
//Edit inline properties can change underneath the window
bool bIgnoreChangingChildren = !(HasNodeFlags(EPropertyNodeFlags::EditInlineNew) || HasNodeFlags(EPropertyNodeFlags::ShowInnerObjectProperties)) ;
//ignore this node if the consistency check should happen for the children
bool bIgnoreStaticArray = (Property->ArrayDim > 1) && (ArrayIndex == -1);
//if this node can't possibly have children (or causes a circular reference loop) then ignore this as a object property
if (bIgnoreChangingChildren || bIgnoreStaticArray || HasNodeFlags(EPropertyNodeFlags::NoChildrenDueToCircularReference))
{
//this will bypass object property consistency checks
ObjectProperty = NULL;
}
FReadAddressList ReadAddresses;
const bool bSuccess = GetReadAddress( ReadAddresses );
//make sure we got the addresses correctly
if (!bSuccess)
{
UE_LOG( LogPropertyNode, Verbose, TEXT("Object is invalid %s"), *Property->GetName() );
return EPropertyDataValidationResult::ObjectInvalid;
}
// If an object property with ShowInnerProperties changed object values out from under the property
bool bShowInnerObjectPropertiesObjectChanged = false;
//check for null, if we find one, there is a problem.
for (int32 Scan = 0; Scan < ReadAddresses.Num(); ++Scan)
{
uint8* Addr = ReadAddresses.GetAddress(Scan);
//make sure the data still exists
if (Addr==NULL)
{
UE_LOG( LogPropertyNode, Verbose, TEXT("Object is invalid %s"), *Property->GetName() );
return EPropertyDataValidationResult::ObjectInvalid;
}
if( ArrayProperty && !bIgnoreAllMismatch)
{
//ensure that array structures have the proper number of children
int32 ArrayNum = FScriptArrayHelper::Num(Addr);
//if first child
if (NumArrayChildren == -1)
{
NumArrayChildren = ArrayNum;
}
bArrayHasNewItem = GetNumChildNodes() < ArrayNum;
//make sure multiple arrays match
bArraysHaveEqualNum = bArraysHaveEqualNum && (NumArrayChildren == ArrayNum);
//make sure the array matches the number of property node children
bArraysMatchChildNum = bArraysMatchChildNum && (GetNumChildNodes() == ArrayNum);
}
if (SetProperty && !bIgnoreAllMismatch)
{
// like arrays, ensure that set structures have the proper number of children
int32 SetNum = FScriptSetHelper::Num(Addr);
if (NumArrayChildren == -1)
{
NumArrayChildren = SetNum;
}
bArrayHasNewItem = GetNumChildNodes() < SetNum;
bArraysHaveEqualNum = bArraysHaveEqualNum && (NumArrayChildren == SetNum);
bArraysMatchChildNum = bArraysMatchChildNum && (GetNumChildNodes() == SetNum);
}
if (MapProperty && !bIgnoreAllMismatch)
{
int32 MapNum = FScriptMapHelper::Num(Addr);
if (NumArrayChildren == -1)
{
NumArrayChildren = MapNum;
}
bArrayHasNewItem = GetNumChildNodes() < MapNum;
bArraysHaveEqualNum = bArraysHaveEqualNum && (NumArrayChildren == MapNum);
bArraysMatchChildNum = bArraysMatchChildNum && (GetNumChildNodes() == MapNum);
}
if (ObjectProperty && !bIgnoreAllMismatch)
{
UObject* Obj = ObjectProperty->GetObjectPropertyValue(Addr);
if (!bShowInnerObjectPropertiesObjectChanged && HasNodeFlags(EPropertyNodeFlags::ShowInnerObjectProperties) && ChildNodes.Num() == 1)
{
bool bChildObjectFound = false;
// should never have more than one node (0 is ok if the object property is null)
check(ChildNodes.Num() == 1);
bool bNeedRebuild = false;
FObjectPropertyNode* ChildObjectNode = ChildNodes[0]->AsObjectNode();
for(int32 ObjectIndex = 0; ObjectIndex < ChildObjectNode->GetNumObjects(); ++ObjectIndex)
{
if(Obj == ChildObjectNode->GetUObject(ObjectIndex))
{
bChildObjectFound = true;
break;
}
}
bShowInnerObjectPropertiesObjectChanged = !bChildObjectFound;
}
if (Obj != NULL)
{
bObjectPropertyNull = false;
break;
}
}
}
//if all arrays match each other but they do NOT match the property structure, cause a rebuild
if (bArraysHaveEqualNum && !bArraysMatchChildNum)
{
RebuildChildren();
if( bArrayHasNewItem && ChildNodes.Num() )
{
TSharedPtr<FPropertyNode> LastChildNode = ChildNodes.Last();
// Don't expand huge children
if( LastChildNode->GetNumChildNodes() > 0 && LastChildNode->GetNumChildNodes() < 10 )
{
// Expand the last item for convenience since generally the user will want to edit the new value they added.
LastChildNode->SetNodeFlags(EPropertyNodeFlags::Expanded, true);
}
}
return EPropertyDataValidationResult::ArraySizeChanged;
}
if(bShowInnerObjectPropertiesObjectChanged)
{
RebuildChildren();
return EPropertyDataValidationResult::EditInlineNewValueChanged;
}
const bool bHasChildren = (GetNumChildNodes() != 0);
// If the object property is not null and has no children, its children need to be rebuilt
// If the object property is null and this node has children, the node needs to be rebuilt
if (!HasNodeFlags(EPropertyNodeFlags::ShowInnerObjectProperties) && ObjectProperty && ((!bObjectPropertyNull && !bHasChildren) || (bObjectPropertyNull && bHasChildren)))
{
RebuildChildren();
return EPropertyDataValidationResult::PropertiesChanged;
}
}
}
if( bRebuildChildrenRequested )
{
RebuildChildren();
// If this property is editinline and not edit const then its editinline new and we can optimize some of the refreshing in some cases. Otherwise we need to refresh all properties in the view
return HasNodeFlags(EPropertyNodeFlags::ShowInnerObjectProperties) || (HasNodeFlags(EPropertyNodeFlags::EditInlineNew) && !IsEditConst()) ? EPropertyDataValidationResult::EditInlineNewValueChanged : EPropertyDataValidationResult::PropertiesChanged;
}
EPropertyDataValidationResult FinalResult = EPropertyDataValidationResult::DataValid;
// Validate children and/or their key nodes.
if (bValidateChildren || bValidateChildrenKeyNodes)
{
for (int32 Scan = 0; Scan < ChildNodes.Num(); ++Scan)
{
TSharedPtr<FPropertyNode>& ChildNode = ChildNodes[Scan];
check(ChildNode.IsValid());
if (bValidateChildren)
{
EPropertyDataValidationResult ChildDataResult = ChildNode->EnsureDataIsValid();
if (FinalResult == EPropertyDataValidationResult::DataValid && ChildDataResult != EPropertyDataValidationResult::DataValid)
{
FinalResult = ChildDataResult;
}
}
// If the child property has a key node that needs validation, validate it here
TSharedPtr<FPropertyNode>& ChildKeyNode = ChildNode->GetPropertyKeyNode();
if (bValidateChildrenKeyNodes && ChildKeyNode.IsValid())
{
EPropertyDataValidationResult ChildDataResult = ChildKeyNode->EnsureDataIsValid();
if (FinalResult == EPropertyDataValidationResult::DataValid && ChildDataResult != EPropertyDataValidationResult::DataValid)
{
FinalResult = ChildDataResult;
}
}
}
}
return FinalResult;
}
/**
* Sets the flags used by the window and the root node
* @param InFlags - flags to turn on or off
* @param InOnOff - whether to toggle the bits on or off
*/
void FPropertyNode::SetNodeFlags (const EPropertyNodeFlags::Type InFlags, const bool InOnOff)
{
if (InOnOff)
{
PropertyNodeFlags |= InFlags;
}
else
{
PropertyNodeFlags &= (~InFlags);
}
}
bool FPropertyNode::GetChildNode(const int32 ChildArrayIndex, TSharedPtr<FPropertyNode>& OutChildNode)
{
OutChildNode = nullptr;
for (auto Child = ChildNodes.CreateIterator(); Child; ++Child)
{
if (Child->IsValid() && (*Child)->ArrayIndex == ChildArrayIndex)
{
OutChildNode = *Child;
return true;
}
}
return false;
}
bool FPropertyNode::GetChildNode(const int32 ChildArrayIndex, TSharedPtr<FPropertyNode>& OutChildNode) const
{
OutChildNode = nullptr;
for (auto Child = ChildNodes.CreateConstIterator(); Child; ++Child)
{
if (Child->IsValid() && (*Child)->ArrayIndex == ChildArrayIndex)
{
OutChildNode = *Child;
return true;
}
}
return false;
}
TSharedPtr<FPropertyNode> FPropertyNode::FindChildPropertyNode( const FName InPropertyName, bool bRecurse )
{
// Search Children
for(int32 ChildIndex=0; ChildIndex<ChildNodes.Num(); ChildIndex++)
{
TSharedPtr<FPropertyNode>& ChildNode = ChildNodes[ChildIndex];
if( ChildNode->GetProperty() && ChildNode->GetProperty()->GetFName() == InPropertyName )
{
return ChildNode;
}
else if( bRecurse )
{
TSharedPtr<FPropertyNode> PropertyNode = ChildNode->FindChildPropertyNode(InPropertyName, bRecurse );
if( PropertyNode.IsValid() )
{
return PropertyNode;
}
}
}
// Return nullptr if not found...
return nullptr;
}
/** @return whether this window's property is constant (can't be edited by the user) */
bool FPropertyNode::IsEditConst() const
{
if( bUpdateEditConstState )
{
// Ask the objects whether this property can be changed
const FObjectPropertyNode* ObjectPropertyNode = FindObjectItemParent();
bIsEditConst = (HasNodeFlags(EPropertyNodeFlags::IsReadOnly) != 0);
if(!bIsEditConst && Property != nullptr && ObjectPropertyNode)
{
bIsEditConst = (Property->PropertyFlags & CPF_EditConst) ? true : false;
if(!bIsEditConst)
{
// travel up the chain to see if this property's owner struct is editconst - if it is, so is this property
FPropertyNode* NextParent = ParentNode;
while(NextParent != nullptr && Cast<UStructProperty>(NextParent->GetProperty()) != NULL)
{
if(NextParent->IsEditConst())
{
bIsEditConst = true;
break;
}
NextParent = NextParent->ParentNode;
}
}
if(!bIsEditConst)
{
for(TPropObjectConstIterator CurObjectIt(ObjectPropertyNode->ObjectConstIterator()); CurObjectIt; ++CurObjectIt)
{
const TWeakObjectPtr<UObject> CurObject = *CurObjectIt;
if(CurObject.IsValid())
{
if(!CurObject->CanEditChange(Property.Get()))
{
// At least one of the objects didn't like the idea of this property being changed.
bIsEditConst = true;
break;
}
}
}
}
}
bUpdateEditConstState = false;
}
return bIsEditConst;
}
/**
* Appends my path, including an array index (where appropriate)
*/
bool FPropertyNode::GetQualifiedName( FString& PathPlusIndex, const bool bWithArrayIndex, const FPropertyNode* StopParent, bool bIgnoreCategories ) const
{
bool bAddedAnything = false;
if( ParentNodeWeakPtr.IsValid() && StopParent != ParentNode )
{
bAddedAnything = ParentNode->GetQualifiedName(PathPlusIndex, bWithArrayIndex, StopParent, bIgnoreCategories);
if( bAddedAnything )
{
PathPlusIndex += TEXT(".");
}
}
if( Property.IsValid() )
{
bAddedAnything = true;
Property->AppendName(PathPlusIndex);
}
if ( bWithArrayIndex && (ArrayIndex != INDEX_NONE) )
{
bAddedAnything = true;
PathPlusIndex += TEXT("[");
PathPlusIndex.AppendInt(ArrayIndex);
PathPlusIndex += TEXT("]");
}
return bAddedAnything;
}
bool FPropertyNode::GetReadAddressUncached( FPropertyNode& InPropertyNode,
bool InRequiresSingleSelection,
FReadAddressListData* OutAddresses,
bool bComparePropertyContents,
bool bObjectForceCompare,
bool bArrayPropertiesCanDifferInSize ) const
{
if (ParentNodeWeakPtr.IsValid())
{
return ParentNode->GetReadAddressUncached( InPropertyNode, InRequiresSingleSelection, OutAddresses, bComparePropertyContents, bObjectForceCompare, bArrayPropertiesCanDifferInSize );
}
return false;
}
bool FPropertyNode::GetReadAddressUncached( FPropertyNode& InPropertyNode, FReadAddressListData& OutAddresses ) const
{
if (ParentNodeWeakPtr.IsValid())
{
return ParentNode->GetReadAddressUncached( InPropertyNode, OutAddresses );
}
return false;
}
bool FPropertyNode::GetReadAddress(bool InRequiresSingleSelection,
FReadAddressList& OutAddresses,
bool bComparePropertyContents,
bool bObjectForceCompare,
bool bArrayPropertiesCanDifferInSize)
{
// @todo PropertyEditor Nodes which require validation cannot be cached
if( CachedReadAddresses.Num() && !CachedReadAddresses.bRequiresCache && !HasNodeFlags(EPropertyNodeFlags::RequiresValidation) )
{
OutAddresses.ReadAddressListData = &CachedReadAddresses;
return CachedReadAddresses.bAllValuesTheSame;
}
CachedReadAddresses.Reset();
bool bAllValuesTheSame = false;
if (ParentNodeWeakPtr.IsValid())
{
bAllValuesTheSame = GetReadAddressUncached( *this, InRequiresSingleSelection, &CachedReadAddresses, bComparePropertyContents, bObjectForceCompare, bArrayPropertiesCanDifferInSize );
OutAddresses.ReadAddressListData = &CachedReadAddresses;
CachedReadAddresses.bAllValuesTheSame = bAllValuesTheSame;
CachedReadAddresses.bRequiresCache = false;
}
return bAllValuesTheSame;
}
/**
* fills in the OutAddresses array with the addresses of all of the available objects.
* @param InItem The property to get objects from.
* @param OutAddresses Storage array for all of the objects' addresses.
*/
bool FPropertyNode::GetReadAddress( FReadAddressList& OutAddresses )
{
// @todo PropertyEditor Nodes which require validation cannot be cached
if( CachedReadAddresses.Num() && !HasNodeFlags(EPropertyNodeFlags::RequiresValidation) )
{
OutAddresses.ReadAddressListData = &CachedReadAddresses;
return true;
}
CachedReadAddresses.Reset();
bool bSuccess = false;
if (ParentNodeWeakPtr.IsValid())
{
bSuccess = GetReadAddressUncached( *this, CachedReadAddresses );
if( bSuccess )
{
OutAddresses.ReadAddressListData = &CachedReadAddresses;
}
CachedReadAddresses.bRequiresCache = false;
}
return bSuccess;
}
/**
* Calculates the memory address for the data associated with this item's property. This is typically the value of a UProperty or a UObject address.
*
* @param StartAddress the location to use as the starting point for the calculation; typically the address of the object that contains this property.
*
* @return a pointer to a UProperty value or UObject. (For dynamic arrays, you'd cast this value to an FArray*)
*/
uint8* FPropertyNode::GetValueBaseAddress( uint8* StartAddress )
{
uint8* Result = NULL;
if ( ParentNodeWeakPtr.IsValid() )
{
Result = ParentNode->GetValueAddress(StartAddress);
}
return Result;
}
/**
* Calculates the memory address for the data associated with this item's value. For most properties, identical to GetValueBaseAddress. For items corresponding
* to dynamic array elements, the pointer returned will be the location for that element's data.
*
* @param StartAddress the location to use as the starting point for the calculation; typically the address of the object that contains this property.
*
* @return a pointer to a UProperty value or UObject. (For dynamic arrays, you'd cast this value to whatever type is the Inner for the dynamic array)
*/
uint8* FPropertyNode::GetValueAddress( uint8* StartAddress )
{
return GetValueBaseAddress( StartAddress );
}
/*-----------------------------------------------------------------------------
FPropertyItemValueDataTrackerSlate
-----------------------------------------------------------------------------*/
/**
* Calculates and stores the address for both the current and default value of
* the associated property and the owning object.
*/
class FPropertyItemValueDataTrackerSlate
{
public:
/**
* A union which allows a single address to be represented as a pointer to a uint8
* or a pointer to a UObject.
*/
union FPropertyValueRoot
{
UObject* OwnerObject;
uint8* ValueAddress;
};
void Reset(FPropertyNode* InPropertyNode, UObject* InOwnerObject)
{
OwnerObject = InOwnerObject;
PropertyNode = InPropertyNode;
bHasDefaultValue = false;
InnerInitialize();
}
void InnerInitialize()
{
{
PropertyValueRoot.OwnerObject = NULL;
PropertyDefaultValueRoot.OwnerObject = NULL;
PropertyValueAddress = NULL;
PropertyValueBaseAddress = NULL;
PropertyDefaultBaseAddress = NULL;
PropertyDefaultAddress = NULL;
}
PropertyValueRoot.OwnerObject = OwnerObject.Get();
check(PropertyNode);
UProperty* Property = PropertyNode->GetProperty();
check(Property);
check(PropertyValueRoot.OwnerObject);
FPropertyNode* ParentNode = PropertyNode->GetParentNode();
// if the object specified is a class object, transfer to the CDO instead
if ( Cast<UClass>(PropertyValueRoot.OwnerObject) != NULL )
{
PropertyValueRoot.OwnerObject = Cast<UClass>(PropertyValueRoot.OwnerObject)->GetDefaultObject();
}
UArrayProperty* ArrayProp = Cast<UArrayProperty>(Property);
UArrayProperty* OuterArrayProp = Cast<UArrayProperty>(Property->GetOuter());
USetProperty* SetProp = Cast<USetProperty>(Property);
USetProperty* OuterSetProp = Cast<USetProperty>(Property->GetOuter());
UMapProperty* MapProp = Cast<UMapProperty>(Property);
UMapProperty* OuterMapProp = Cast<UMapProperty>(Property->GetOuter());
// calculate the values for the current object
{
PropertyValueBaseAddress = (OuterArrayProp == NULL && OuterSetProp == NULL && OuterMapProp == NULL)
? PropertyNode->GetValueBaseAddress(PropertyValueRoot.ValueAddress)
: ParentNode->GetValueBaseAddress(PropertyValueRoot.ValueAddress);
PropertyValueAddress = PropertyNode->GetValueAddress(PropertyValueRoot.ValueAddress);
}
if( IsValidTracker() )
{
bHasDefaultValue = Private_HasDefaultValue();
// calculate the values for the default object
if ( bHasDefaultValue )
{
PropertyDefaultValueRoot.OwnerObject = PropertyValueRoot.OwnerObject ? PropertyValueRoot.OwnerObject->GetArchetype() : NULL;
PropertyDefaultBaseAddress = (OuterArrayProp == NULL && OuterSetProp == NULL && OuterMapProp == NULL)
? PropertyNode->GetValueBaseAddress(PropertyDefaultValueRoot.ValueAddress)
: ParentNode->GetValueBaseAddress(PropertyDefaultValueRoot.ValueAddress);
PropertyDefaultAddress = PropertyNode->GetValueAddress(PropertyDefaultValueRoot.ValueAddress);
//////////////////////////
// If this is a container property, we must take special measures to use the base address of the property's value; for instance,
// the array property's PropertyDefaultBaseAddress points to an FScriptArray*, while PropertyDefaultAddress points to the
// FScriptArray's Data pointer.
if ( ArrayProp != NULL || SetProp != NULL || MapProp != NULL )
{
PropertyValueAddress = PropertyValueBaseAddress;
PropertyDefaultAddress = PropertyDefaultBaseAddress;
}
}
}
}
/**
* Constructor
*
* @param InPropItem the property window item this struct will hold values for
* @param InOwnerObject the object which contains the property value
*/
FPropertyItemValueDataTrackerSlate( FPropertyNode* InPropertyNode, UObject* InOwnerObject )
: OwnerObject( InOwnerObject )
, PropertyNode(InPropertyNode)
, bHasDefaultValue(false)
{
InnerInitialize();
}
/**
* @return Whether or not this tracker has a valid address to a property and object
*/
bool IsValidTracker() const
{
return PropertyValueBaseAddress != 0 && OwnerObject.IsValid();
}
/**
* @return a pointer to the subobject root (outer-most non-subobject) of the owning object.
*/
UObject* GetTopLevelObject()
{
check(PropertyNode);
FObjectPropertyNode* RootNode = PropertyNode->FindRootObjectItemParent();
check(RootNode);
TArray<UObject*> RootObjects;
for ( TPropObjectIterator Itor( RootNode->ObjectIterator() ) ; Itor ; ++Itor )
{
TWeakObjectPtr<UObject> Object = *Itor;
if( Object.IsValid() )
{
RootObjects.Add(Object.Get());
}
}
UObject* Result;
for ( Result = PropertyValueRoot.OwnerObject; Result; Result = Result->GetOuter() )
{
if ( RootObjects.Contains(Result) )
{
break;
}
}
if( !Result )
{
// The result is not contained in the root so it is the top level object
Result = PropertyValueRoot.OwnerObject;
}
return Result;
}
/**
* Whether or not we have a default value
*/
bool HasDefaultValue() const { return bHasDefaultValue; }
/**
* @return The property node we are inspecting
*/
FPropertyNode* GetPropertyNode() const { return PropertyNode; }
/**
* @return The address of the property's value.
*/
uint8* GetPropertyValueAddress() const { return PropertyValueAddress; }
/**
* @return The base address of the property's default value.
*/
uint8* GetPropertyDefaultBaseAddress() const { return PropertyDefaultBaseAddress; }
/**
* @return The address of the property's default value.
*/
uint8* GetPropertyDefaultAddress() const { return PropertyDefaultAddress; }
/**
* @return The address of the owning object's archetype
*/
FPropertyValueRoot GetPropertyValueRoot() const { return PropertyValueRoot; }
private:
/**
* Determines whether the property bound to this struct exists in the owning object's archetype.
*
* @return true if this property exists in the owning object's archetype; false if the archetype is e.g. a
* CDO for a base class and this property is declared in the owning object's class.
*/
bool Private_HasDefaultValue() const
{
bool bResult = false;
if( IsValidTracker() )
{
check(PropertyValueBaseAddress);
check(PropertyValueRoot.OwnerObject);
UObject* ParentDefault = PropertyValueRoot.OwnerObject->GetArchetype();
check(ParentDefault);
if (PropertyValueRoot.OwnerObject->GetClass() == ParentDefault->GetClass())
{
// if the archetype is of the same class, then we must have a default
bResult = true;
}
else
{
// Find the member property which contains this item's property
FPropertyNode* MemberPropertyNode = PropertyNode;
for ( ;MemberPropertyNode != NULL; MemberPropertyNode = MemberPropertyNode->GetParentNode() )
{
UProperty* MemberProperty = MemberPropertyNode->GetProperty();
if ( MemberProperty != NULL )
{
if ( Cast<UClass>(MemberProperty->GetOuter()) != NULL )
{
break;
}
}
}
if ( MemberPropertyNode != NULL && MemberPropertyNode->GetProperty())
{
// we check to see that this property is in the defaults class
bResult = MemberPropertyNode->GetProperty()->IsInContainer(ParentDefault->GetClass());
}
}
}
return bResult;
}
private:
TWeakObjectPtr<UObject> OwnerObject;
/** The property node we are inspecting */
FPropertyNode* PropertyNode;
/** The address of the owning object */
FPropertyValueRoot PropertyValueRoot;
/**
* The address of the owning object's archetype
*/
FPropertyValueRoot PropertyDefaultValueRoot;
/**
* The address of this property's value.
*/
uint8* PropertyValueAddress;
/**
* The base address of this property's value. i.e. for dynamic arrays, the location of the FScriptArray which
* contains the array property's value
*/
uint8* PropertyValueBaseAddress;
/**
* The base address of this property's default value (see other comments for PropertyValueBaseAddress)
*/
uint8* PropertyDefaultBaseAddress;
/**
* The address of this property's default value.
*/
uint8* PropertyDefaultAddress;
/** Whether or not we have a default value */
bool bHasDefaultValue;
};
/* ==========================================================================================================
FPropertyItemComponentCollector
Given a property and the address for that property's data, searches for references to components and
keeps a list of any that are found.
========================================================================================================== */
/**
* Given a property and the address for that property's data, searches for references to components and keeps a list of any that are found.
*/
struct FPropertyItemComponentCollector
{
/** contains the property to search along with the value address to use */
const FPropertyItemValueDataTrackerSlate& ValueTracker;
/** holds the list of instanced objects found */
TArray<UObject*> Components;
/** Whether or not we have an edit inline new */
bool bContainsEditInlineNew;
/** Constructor */
FPropertyItemComponentCollector( const FPropertyItemValueDataTrackerSlate& InValueTracker )
: ValueTracker(InValueTracker)
, bContainsEditInlineNew( false )
{
check(ValueTracker.GetPropertyNode());
FPropertyNode* PropertyNode = ValueTracker.GetPropertyNode();
check(PropertyNode);
UProperty* Prop = PropertyNode->GetProperty();
if ( PropertyNode->GetArrayIndex() == INDEX_NONE )
{
// either the associated property is not an array property, or it's the header for the property (meaning the entire array)
for ( int32 ArrayIndex = 0; ArrayIndex < Prop->ArrayDim; ArrayIndex++ )
{
ProcessProperty(Prop, ValueTracker.GetPropertyValueAddress() + ArrayIndex * Prop->ElementSize);
}
}
else
{
// single element of either a dynamic or static array
ProcessProperty(Prop, ValueTracker.GetPropertyValueAddress());
}
}
/**
* Routes the processing to the appropriate method depending on the type of property.
*
* @param Property the property to process
* @param PropertyValueAddress the address of the property's value
*/
void ProcessProperty( UProperty* Property, uint8* PropertyValueAddress )
{
if ( Property != NULL )
{
bContainsEditInlineNew |= Property->HasMetaData(TEXT("EditInline")) && ((Property->PropertyFlags & CPF_EditConst) == 0);
if ( ProcessObjectProperty(Cast<UObjectPropertyBase>(Property), PropertyValueAddress) )
{
return;
}
if ( ProcessStructProperty(Cast<UStructProperty>(Property), PropertyValueAddress) )
{
return;
}
if ( ProcessInterfaceProperty(Cast<UInterfaceProperty>(Property), PropertyValueAddress) )
{
return;
}
if ( ProcessDelegateProperty(Cast<UDelegateProperty>(Property), PropertyValueAddress) )
{
return;
}
if ( ProcessMulticastDelegateProperty(Cast<UMulticastDelegateProperty>(Property), PropertyValueAddress) )
{
return;
}
if ( ProcessArrayProperty(Cast<UArrayProperty>(Property), PropertyValueAddress) )
{
return;
}
if ( ProcessSetProperty(Cast<USetProperty>(Property), PropertyValueAddress) )
{
return;
}
if ( ProcessMapProperty(Cast<UMapProperty>(Property), PropertyValueAddress) )
{
return;
}
}
}
private:
/**
* UArrayProperty version - invokes ProcessProperty on the array's Inner member for each element in the array.
*
* @param ArrayProp the property to process
* @param PropertyValueAddress the address of the property's value
*
* @return true if the property was handled by this method
*/
bool ProcessArrayProperty( UArrayProperty* ArrayProp, uint8* PropertyValueAddress )
{
bool bResult = false;
if ( ArrayProp != NULL )
{
FScriptArray* ArrayValuePtr = ArrayProp->GetPropertyValuePtr(PropertyValueAddress);
uint8* ArrayValue = (uint8*)ArrayValuePtr->GetData();
for ( int32 ArrayIndex = 0; ArrayIndex < ArrayValuePtr->Num(); ArrayIndex++ )
{
ProcessProperty(ArrayProp->Inner, ArrayValue + ArrayIndex * ArrayProp->Inner->ElementSize);
}
bResult = true;
}
return bResult;
}
/**
* USetProperty version - invokes ProcessProperty on the each item in the set
*
* @param SetProp the property to process
* @param PropertyValueAddress the address of the property's value
*
* @return true if the property was handled by this method
*/
bool ProcessSetProperty( USetProperty* SetProp, uint8* PropertyValueAddress )
{
bool bResult = false;
if (SetProp != NULL)
{
FScriptSet* SetValuePtr = SetProp->GetPropertyValuePtr(PropertyValueAddress);
FScriptSetLayout SetLayout = SetValuePtr->GetScriptLayout(SetProp->ElementProp->ElementSize, SetProp->ElementProp->GetMinAlignment());
int32 ItemsLeft = SetValuePtr->Num();
for (int32 Index = 0; ItemsLeft > 0; ++Index)
{
if (SetValuePtr->IsValidIndex(Index))
{
--ItemsLeft;
ProcessProperty(SetProp->ElementProp, (uint8*)SetValuePtr->GetData(Index, SetLayout));
}
}
bResult = true;
}
return bResult;
}
/**
* UMapProperty version - invokes ProcessProperty on each item in the map
*
* @param MapProp the property to process
* @param PropertyValueAddress the address of the property's value
*
* @return true if the property was handled by this method
*/
bool ProcessMapProperty( UMapProperty* MapProp, uint8* PropertyValueAddress )
{
bool bResult = false;
if (MapProp != NULL)
{
FScriptMap* MapValuePtr = MapProp->GetPropertyValuePtr(PropertyValueAddress);
FScriptMapLayout MapLayout = MapValuePtr->GetScriptLayout(MapProp->KeyProp->ElementSize, MapProp->KeyProp->GetMinAlignment(), MapProp->ValueProp->ElementSize, MapProp->ValueProp->GetMinAlignment());
int32 ItemsLeft = MapValuePtr->Num();
for (int32 Index = 0; ItemsLeft > 0; ++Index)
{
if (MapValuePtr->IsValidIndex(Index))
{
--ItemsLeft;
uint8* Data = (uint8*)MapValuePtr->GetData(Index, MapLayout);
ProcessProperty(MapProp->KeyProp, MapProp->KeyProp->ContainerPtrToValuePtr<uint8>(Data));
ProcessProperty(MapProp->ValueProp, MapProp->ValueProp->ContainerPtrToValuePtr<uint8>(Data));
}
}
bResult = true;
}
return bResult;
}
/**
* UStructProperty version - invokes ProcessProperty on each property in the struct
*
* @param StructProp the property to process
* @param PropertyValueAddress the address of the property's value
*
* @return true if the property was handled by this method
*/
bool ProcessStructProperty( UStructProperty* StructProp, uint8* PropertyValueAddress )
{
bool bResult = false;
if ( StructProp != NULL )
{
for ( UProperty* Prop = StructProp->Struct->PropertyLink; Prop; Prop = Prop->PropertyLinkNext )
{
for ( int32 ArrayIndex = 0; ArrayIndex < Prop->ArrayDim; ArrayIndex++ )
{
ProcessProperty(Prop, Prop->ContainerPtrToValuePtr<uint8>(PropertyValueAddress, ArrayIndex));
}
}
bResult = true;
}
return bResult;
}
/**
* UObjectProperty version - if the object located at the specified address is instanced, adds the component the list.
*
* @param ObjectProp the property to process
* @param PropertyValueAddress the address of the property's value
*
* @return true if the property was handled by this method
*/
bool ProcessObjectProperty( UObjectPropertyBase* ObjectProp, uint8* PropertyValueAddress )
{
bool bResult = false;
if ( ObjectProp != NULL )
{
UObject* ObjValue = ObjectProp->GetObjectPropertyValue(PropertyValueAddress);
if (ObjectProp->PropertyFlags & CPF_InstancedReference)
{
Components.AddUnique(ObjValue);
}
bResult = true;
}
return bResult;
}
/**
* UInterfaceProperty version - if the FScriptInterface located at the specified address contains a reference to an instance, add the component to the list.
*
* @param InterfaceProp the property to process
* @param PropertyValueAddress the address of the property's value
*
* @return true if the property was handled by this method
*/
bool ProcessInterfaceProperty( UInterfaceProperty* InterfaceProp, uint8* PropertyValueAddress )
{
bool bResult = false;
if ( InterfaceProp != NULL )
{
FScriptInterface* InterfaceValue = InterfaceProp->GetPropertyValuePtr(PropertyValueAddress);
UObject* InterfaceObj = InterfaceValue->GetObject();
if (InterfaceObj && InterfaceObj->IsDefaultSubobject())
{
Components.AddUnique(InterfaceValue->GetObject());
}
bResult = true;
}
return bResult;
}
/**
* UDelegateProperty version - if the FScriptDelegate located at the specified address contains a reference to an instance, add the component to the list.
*
* @param DelegateProp the property to process
* @param PropertyValueAddress the address of the property's value
*
* @return true if the property was handled by this method
*/
bool ProcessDelegateProperty( UDelegateProperty* DelegateProp, uint8* PropertyValueAddress )
{
bool bResult = false;
if ( DelegateProp != NULL )
{
FScriptDelegate* DelegateValue = DelegateProp->GetPropertyValuePtr(PropertyValueAddress);
if (DelegateValue->GetUObject() && DelegateValue->GetUObject()->IsDefaultSubobject())
{
Components.AddUnique(DelegateValue->GetUObject());
}
bResult = true;
}
return bResult;
}
/**
* UMulticastDelegateProperty version - if the FMulticastScriptDelegate located at the specified address contains a reference to an instance, add the component to the list.
*
* @param MulticastDelegateProp the property to process
* @param PropertyValueAddress the address of the property's value
*
* @return true if the property was handled by this method
*/
bool ProcessMulticastDelegateProperty( UMulticastDelegateProperty* MulticastDelegateProp, uint8* PropertyValueAddress )
{
bool bResult = false;
if ( MulticastDelegateProp != NULL )
{
FMulticastScriptDelegate* MulticastDelegateValue = MulticastDelegateProp->GetPropertyValuePtr(PropertyValueAddress);
TArray<UObject*> AllObjects = MulticastDelegateValue->GetAllObjects();
for( TArray<UObject*>::TConstIterator CurObjectIt( AllObjects ); CurObjectIt; ++CurObjectIt )
{
if ((*CurObjectIt)->IsDefaultSubobject())
{
Components.AddUnique((*CurObjectIt));
}
}
bResult = true;
}
return bResult;
}
};
bool FPropertyNode::GetDiffersFromDefaultForObject( FPropertyItemValueDataTrackerSlate& ValueTracker, UProperty* InProperty )
{
check( InProperty );
bool bDiffersFromDefaultForObject = false;
if ( ValueTracker.IsValidTracker() && ValueTracker.HasDefaultValue() && GetParentNode() != NULL )
{
//////////////////////////
// Check the property against its default.
// If the property is an object property, we have to take special measures.
UArrayProperty* OuterArrayProperty = Cast<UArrayProperty>(InProperty->GetOuter());
USetProperty* OuterSetProperty = Cast<USetProperty>(InProperty->GetOuter());
UMapProperty* OuterMapProperty = Cast<UMapProperty>(InProperty->GetOuter());
if ( OuterArrayProperty != NULL )
{
// make sure we're not trying to compare against an element that doesn't exist
if ( ValueTracker.GetPropertyDefaultBaseAddress() != NULL && GetArrayIndex() >= FScriptArrayHelper::Num(ValueTracker.GetPropertyDefaultBaseAddress()) )
{
bDiffersFromDefaultForObject = true;
}
}
else if (OuterSetProperty != NULL)
{
FScriptSetHelper SetHelper(OuterSetProperty, ValueTracker.GetPropertyDefaultBaseAddress());
if ( ValueTracker.GetPropertyDefaultBaseAddress() != NULL && !SetHelper.IsValidIndex(GetArrayIndex()) )
{
bDiffersFromDefaultForObject = true;
}
}
else if (OuterMapProperty != NULL)
{
FScriptMapHelper MapHelper(OuterMapProperty, ValueTracker.GetPropertyDefaultBaseAddress());
if ( ValueTracker.GetPropertyDefaultBaseAddress() != NULL && !MapHelper.IsValidIndex(GetArrayIndex()) )
{
bDiffersFromDefaultForObject = true;
}
}
// The property is a simple field. Compare it against the enclosing object's default for that property.
if ( !bDiffersFromDefaultForObject)
{
uint32 PortFlags = 0;
UObjectPropertyBase* ObjectProperty = Cast<UObjectPropertyBase>(InProperty);
if (InProperty->ContainsInstancedObjectProperty())
{
// Use PPF_DeepCompareInstances for component objects
if (ObjectProperty)
{
PortFlags |= PPF_DeepCompareInstances;
}
// Use PPF_DeltaComparison for instanced objects
else
{
PortFlags |= PPF_DeltaComparison;
}
}
if ( ValueTracker.GetPropertyValueAddress() == NULL || ValueTracker.GetPropertyDefaultAddress() == NULL )
{
// if either are NULL, we had a dynamic array somewhere in our parent chain and the array doesn't
// have enough elements in either the default or the object
bDiffersFromDefaultForObject = true;
}
else if ( GetArrayIndex() == INDEX_NONE && InProperty->ArrayDim > 1 )
{
for ( int32 Idx = 0; !bDiffersFromDefaultForObject && Idx < InProperty->ArrayDim; Idx++ )
{
bDiffersFromDefaultForObject = !InProperty->Identical(
ValueTracker.GetPropertyValueAddress() + Idx * InProperty->ElementSize,
ValueTracker.GetPropertyDefaultAddress() + Idx * InProperty->ElementSize,
PortFlags
);
}
}
else
{
uint8* PropertyValueAddr = ValueTracker.GetPropertyValueAddress();
uint8* DefaultPropertyValueAddr = ValueTracker.GetPropertyDefaultAddress();
if( PropertyValueAddr != NULL && DefaultPropertyValueAddr != NULL )
{
bDiffersFromDefaultForObject = !InProperty->Identical(
PropertyValueAddr,
DefaultPropertyValueAddr,
PortFlags
);
}
}
}
}
return bDiffersFromDefaultForObject;
}
/**
* If there is a property, sees if it matches. Otherwise sees if the entire parent structure matches
*/
bool FPropertyNode::GetDiffersFromDefault()
{
if( bUpdateDiffersFromDefault )
{
bUpdateDiffersFromDefault = false;
bDiffersFromDefault = false;
FObjectPropertyNode* ObjectNode = FindObjectItemParent();
if(ObjectNode && Property.IsValid() && !IsEditConst())
{
// Get an iterator for the enclosing objects.
for(int32 ObjIndex = 0; ObjIndex < ObjectNode->GetNumObjects(); ++ObjIndex)
{
UObject* Object = ObjectNode->GetUObject(ObjIndex);
TSharedPtr<FPropertyItemValueDataTrackerSlate> ValueTracker = GetValueTracker(Object, ObjIndex);
if(ValueTracker.IsValid() && Object && GetDiffersFromDefaultForObject(*ValueTracker, Property.Get()))
{
// If any object being observed differs from the result then there is no need to keep searching
bDiffersFromDefault = true;
break;
}
}
}
}
return bDiffersFromDefault;
}
FString FPropertyNode::GetDefaultValueAsStringForObject( FPropertyItemValueDataTrackerSlate& ValueTracker, UObject* InObject, UProperty* InProperty )
{
check( InObject );
check( InProperty );
bool bDiffersFromDefaultForObject = false;
FString DefaultValue;
// special case for Object class - no defaults to compare against
if ( InObject != UObject::StaticClass() && InObject != UObject::StaticClass()->GetDefaultObject() )
{
if ( ValueTracker.IsValidTracker() && ValueTracker.HasDefaultValue() )
{
//////////////////////////
// Check the property against its default.
// If the property is an object property, we have to take special measures.
UArrayProperty* OuterArrayProperty = Cast<UArrayProperty>(InProperty->GetOuter());
USetProperty* OuterSetProperty = Cast<USetProperty>(InProperty->GetOuter());
UMapProperty* OuterMapProperty = Cast<UMapProperty>(InProperty->GetOuter());
if ( OuterArrayProperty != NULL )
{
// make sure we're not trying to compare against an element that doesn't exist
if ( ValueTracker.GetPropertyDefaultBaseAddress() != NULL && GetArrayIndex() >= FScriptArrayHelper::Num(ValueTracker.GetPropertyDefaultBaseAddress()) )
{
bDiffersFromDefaultForObject = true;
DefaultValue = NSLOCTEXT("PropertyEditor", "ArrayLongerThanDefault", "Array is longer than the default.").ToString();
}
}
// The property is a simple field. Compare it against the enclosing object's default for that property.
if ( !bDiffersFromDefaultForObject)
{
uint32 PortFlags = 0;
UObjectPropertyBase* ObjectProperty = Cast<UObjectPropertyBase>(InProperty);
if (InProperty->ContainsInstancedObjectProperty())
{
// Use PPF_DeepCompareInstances for component objects
if (ObjectProperty)
{
PortFlags |= PPF_DeepCompareInstances;
}
// Use PPF_DeltaComparison for instanced objects
else
{
PortFlags |= PPF_DeltaComparison;
}
}
if ( ValueTracker.GetPropertyValueAddress() == NULL || ValueTracker.GetPropertyDefaultAddress() == NULL )
{
if ( !OuterSetProperty && !OuterMapProperty )
{
// if either are NULL, we had a dynamic array somewhere in our parent chain and the array doesn't
// have enough elements in either the default or the object
DefaultValue = NSLOCTEXT("PropertyEditor", "DifferentArrayLength", "Array has different length than the default.").ToString();
}
}
else if ( GetArrayIndex() == INDEX_NONE && InProperty->ArrayDim > 1 )
{
for ( int32 Idx = 0; !bDiffersFromDefaultForObject && Idx < InProperty->ArrayDim; Idx++ )
{
uint8* DefaultAddress = ValueTracker.GetPropertyDefaultAddress() + Idx * InProperty->ElementSize;
FString DefaultItem;
InProperty->ExportTextItem( DefaultItem, DefaultAddress, DefaultAddress, InObject, PortFlags, NULL );
if ( DefaultValue.Len() > 0 && DefaultItem.Len() > 0 )
{
DefaultValue += TEXT( ", " );
}
DefaultValue += DefaultItem;
}
}
else
{
InProperty->ExportTextItem( DefaultValue, ValueTracker.GetPropertyDefaultAddress(), ValueTracker.GetPropertyDefaultAddress(), InObject, PortFlags, NULL );
UEnum* Enum = nullptr;
if (UByteProperty* ByteProperty = Cast<UByteProperty>(InProperty))
{
Enum = ByteProperty->Enum;
}
else if (UEnumProperty* EnumProperty = Cast<UEnumProperty>(InProperty))
{
Enum = EnumProperty->GetEnum();
}
if ( Enum )
{
AdjustEnumPropDisplayName(Enum, DefaultValue);
}
}
}
}
}
return DefaultValue;
}
FString FPropertyNode::GetDefaultValueAsString()
{
FObjectPropertyNode* ObjectNode = FindObjectItemParent();
FString DefaultValue;
if ( ObjectNode && Property.IsValid() )
{
// Get an iterator for the enclosing objects.
for ( int32 ObjIndex = 0; ObjIndex < ObjectNode->GetNumObjects(); ++ObjIndex )
{
UObject* Object = ObjectNode->GetUObject( ObjIndex );
TSharedPtr<FPropertyItemValueDataTrackerSlate> ValueTracker = GetValueTracker(Object, ObjIndex);
if( Object && ValueTracker.IsValid() )
{
FString NodeDefaultValue = GetDefaultValueAsStringForObject( *ValueTracker, Object, Property.Get() );
if ( DefaultValue.Len() > 0 && NodeDefaultValue.Len() > 0)
{
DefaultValue += TEXT(", ");
}
DefaultValue += NodeDefaultValue;
}
}
}
return DefaultValue;
}
FText FPropertyNode::GetResetToDefaultLabel()
{
FString DefaultValue = GetDefaultValueAsString();
FText OutLabel = GetDisplayName();
if ( DefaultValue.Len() )
{
const int32 MaxValueLen = 60;
if ( DefaultValue.Len() > MaxValueLen )
{
DefaultValue = DefaultValue.Left( MaxValueLen );
DefaultValue += TEXT( "..." );
}
return FText::Format(NSLOCTEXT("FPropertyNode", "ResetToDefaultLabelFmt", "{0}: {1}"), OutLabel, FText::FromString(DefaultValue));
}
return OutLabel;
}
void FPropertyNode::ResetToDefault( FNotifyHook* InNotifyHook )
{
UProperty* TheProperty = GetProperty();
check(TheProperty);
// Get an iterator for the enclosing objects.
FObjectPropertyNode* ObjectNode = FindObjectItemParent();
if( ObjectNode )
{
// The property is a simple field. Compare it against the enclosing object's default for that property.
////////////////
FScopedTransaction Transaction( NSLOCTEXT("UnrealEd", "PropertyWindowEditProperties", "Edit Properties") );
// Whether or not we've process prechange already
bool bNotifiedPreChange = false;
// Whether or not an edit inline new was reset as a result of this reset to default
bool bEditInlineNewWasReset = false;
TArray< TMap<FString, int32> > ArrayIndicesPerObject;
for( int32 ObjIndex = 0; ObjIndex < ObjectNode->GetNumObjects(); ++ObjIndex )
{
TWeakObjectPtr<UObject> ObjectWeakPtr = ObjectNode->GetUObject( ObjIndex );
UObject* Object = ObjectWeakPtr.Get();
// special case for UObject class - it has no defaults
if( Object && Object != UObject::StaticClass() && Object != UObject::StaticClass()->GetDefaultObject() )
{
TSharedPtr<FPropertyItemValueDataTrackerSlate> ValueTrackerPtr = GetValueTracker(Object, ObjIndex);
if( ValueTrackerPtr.IsValid() && ValueTrackerPtr->IsValidTracker() && ValueTrackerPtr->HasDefaultValue() )
{
FPropertyItemValueDataTrackerSlate& ValueTracker = *ValueTrackerPtr;
bool bIsGameWorld = false;
// If the object we are modifying is in the PIE world, than make the PIE world the active
// GWorld. Assumes all objects managed by this property window belong to the same world.
UWorld* OldGWorld = nullptr;
if ( GUnrealEd && GUnrealEd->PlayWorld && !GUnrealEd->bIsSimulatingInEditor && Object->IsIn(GUnrealEd->PlayWorld))
{
OldGWorld = SetPlayInEditorWorld(GUnrealEd->PlayWorld);
bIsGameWorld = true;
}
FPropertyNode* ParentPropertyNode = GetParentNode();
UProperty* ParentProperty = ParentPropertyNode != nullptr ? ParentPropertyNode->GetProperty() : nullptr;
// If we're about to modify an element in a set, check to ensure that we're not duplicating a default value
if (Cast<USetProperty>(ParentProperty) != nullptr)
{
FScriptSetHelper SetHelper(Cast<USetProperty>(ParentProperty), ParentPropertyNode->GetValueBaseAddress((uint8*)Object));
FDefaultConstructedPropertyElement DefaultElementValue(SetHelper.ElementProp);
int32 ThisElementIndex = SetHelper.FindElementIndex(TheProperty->ContainerPtrToValuePtr<uint8>(ValueTrackerPtr->GetPropertyValueAddress()));
int32 DefaultIndex = SetHelper.FindElementIndex(DefaultElementValue.GetObjAddress());
if (DefaultIndex != INDEX_NONE && ThisElementIndex != DefaultIndex)
{
FNotificationInfo ResetToDefaultErrorInfo = LOCTEXT("SetElementResetToDefault_Duplicate", "Cannot reset the element back to its default value because the default already exists in the set");
ResetToDefaultErrorInfo.ExpireDuration = 3.0f;
FSlateNotificationManager::Get().AddNotification(ResetToDefaultErrorInfo);
return;
}
}
// If we're about to modify a map, ensure that the default key value is not duplicated
if (Cast<UMapProperty>(ParentProperty) != nullptr)
{
if (PropertyKeyNode.IsValid())
{
// This is the value node; it should always be reset to default. The key node should be checked separately.
PropertyKeyNode->ResetToDefault( InNotifyHook );
}
else
{
// Key node, so perform the default check here
FScriptMapHelper MapHelper(Cast<UMapProperty>(ParentProperty), ParentPropertyNode->GetValueBaseAddress((uint8*)Object));
FDefaultConstructedPropertyElement DefaultKeyValue(MapHelper.KeyProp);
uint8* PairPtr = MapHelper.GetPairPtr(ArrayIndex);
int32 ThisKeyIndex = MapHelper.FindMapIndexWithKey(TheProperty->ContainerPtrToValuePtr<uint8>(ValueTrackerPtr->GetPropertyValueAddress()));
int32 DefaultIndex = MapHelper.FindMapIndexWithKey(DefaultKeyValue.GetObjAddress());
if (DefaultIndex != INDEX_NONE && ThisKeyIndex != DefaultIndex)
{
FNotificationInfo ResetToDefaultErrorInfo = LOCTEXT("MapKeyResetToDefault_Duplicate", "Cannot reset the key back to its default value because the default already exists in the map");
ResetToDefaultErrorInfo.ExpireDuration = 3.0f;
FSlateNotificationManager::Get().AddNotification(ResetToDefaultErrorInfo);
return;
}
}
}
if( !bNotifiedPreChange )
{
// Call preedit change on all the objects
NotifyPreChange( GetProperty(), InNotifyHook );
bNotifiedPreChange = true;
}
// Cache the value of the property before modifying it.
FString PreviousValue;
TheProperty->ExportText_Direct(PreviousValue, ValueTracker.GetPropertyValueAddress(), ValueTracker.GetPropertyValueAddress(), NULL, 0);
FString PreviousArrayValue;
if( ValueTracker.GetPropertyDefaultAddress() != NULL )
{
UObject* RootObject = ValueTracker.GetTopLevelObject();
FPropertyItemComponentCollector ComponentCollector(ValueTracker);
// dynamic arrays are the only property type that do not support CopySingleValue correctly due to the fact that they cannot
// be used in a static array
if(Cast<UArrayProperty>(ParentProperty) != nullptr)
{
UArrayProperty* ArrayProp = Cast<UArrayProperty>(ParentProperty);
if(ArrayProp->Inner == TheProperty)
{
uint8* Addr = ParentPropertyNode->GetValueBaseAddress((uint8*)Object);
ArrayProp->ExportText_Direct(PreviousArrayValue, Addr, Addr, NULL, 0);
}
}
UArrayProperty* ArrayProp = Cast<UArrayProperty>(TheProperty);
if( ArrayProp != NULL )
{
TheProperty->CopyCompleteValue(ValueTracker.GetPropertyValueAddress(), ValueTracker.GetPropertyDefaultAddress());
}
else
{
if( GetArrayIndex() == INDEX_NONE && TheProperty->ArrayDim > 1 )
{
TheProperty->CopyCompleteValue(ValueTracker.GetPropertyValueAddress(), ValueTracker.GetPropertyDefaultAddress());
}
else
{
TheProperty->CopySingleValue(ValueTracker.GetPropertyValueAddress(), ValueTracker.GetPropertyDefaultAddress());
}
}
if( ComponentCollector.Components.Num() > 0 )
{
TMap<UObject*,UObject*> ReplaceMap;
FPropertyItemComponentCollector DefaultComponentCollector(ValueTracker);
for ( int32 CompIndex = 0; CompIndex < ComponentCollector.Components.Num(); CompIndex++ )
{
UObject* Component = ComponentCollector.Components[CompIndex];
if (Component != NULL)
{
if ( DefaultComponentCollector.Components.Contains(Component->GetArchetype()) )
{
ReplaceMap.Add(Component, Component->GetArchetype());
}
else if( DefaultComponentCollector.Components.IsValidIndex(CompIndex) )
{
ReplaceMap.Add(Component, DefaultComponentCollector.Components[CompIndex]);
}
}
}
FArchiveReplaceObjectRef<UObject> ReplaceAr(RootObject, ReplaceMap, false, true, true);
FObjectInstancingGraph InstanceGraph(RootObject);
TArray<UObject*> Subobjects;
FReferenceFinder Collector(
Subobjects, // InObjectArray
RootObject, // LimitOuter
false, // bRequireDirectOuter
true, // bIgnoreArchetypes
true, // bSerializeRecursively
false // bShouldIgnoreTransient
);
Collector.FindReferences( RootObject );
for( UObject* SubObj : Subobjects )
{
InstanceGraph.AddNewInstance(SubObj);
}
RootObject->InstanceSubobjectTemplates(&InstanceGraph);
}
bEditInlineNewWasReset = ComponentCollector.bContainsEditInlineNew;
}
else
{
TheProperty->ClearValue(ValueTracker.GetPropertyValueAddress());
}
// Cache the value of the property after having modified it.
FString ValueAfterImport;
TheProperty->ExportText_Direct(ValueAfterImport, ValueTracker.GetPropertyValueAddress(), ValueTracker.GetPropertyValueAddress(), NULL, 0);
// If this is an instanced component property we must move the old component to the
// transient package so resetting owned components on the parent doesn't find it
UObjectProperty* ObjectProperty = Cast<UObjectProperty>(TheProperty);
if (ObjectProperty
&& ObjectProperty->HasAnyPropertyFlags(CPF_InstancedReference)
&& ObjectProperty->PropertyClass->IsChildOf(UActorComponent::StaticClass())
&& PreviousValue != ValueAfterImport)
{
FString ComponentName = PreviousValue;
ConstructorHelpers::StripObjectClass(ComponentName);
if (UActorComponent* Component = Cast<UActorComponent>(StaticFindObject(UActorComponent::StaticClass(), ANY_PACKAGE, *ComponentName)))
{
Component->Modify();
Component->Rename(nullptr, GetTransientPackage(), REN_DontCreateRedirectors);
}
}
if((Object->HasAnyFlags(RF_ClassDefaultObject | RF_ArchetypeObject) ||
(Object->HasAnyFlags(RF_DefaultSubObject) && Object->GetOuter()->HasAnyFlags(RF_ClassDefaultObject | RF_ArchetypeObject))) &&
!bIsGameWorld)
{
PropagatePropertyChange(Object, *ValueAfterImport, PreviousArrayValue.IsEmpty() ? PreviousValue : PreviousArrayValue);
}
if(OldGWorld)
{
// restore the original (editor) GWorld
RestoreEditorWorld( OldGWorld );
}
ArrayIndicesPerObject.Add(TMap<FString, int32>());
FPropertyValueImpl::GenerateArrayIndexMapToObjectNode(ArrayIndicesPerObject[ObjIndex], this);
}
}
}
if( bNotifiedPreChange )
{
// Call PostEditchange on all the objects
// Assume reset to default, can change topology
FPropertyChangedEvent ChangeEvent( TheProperty, EPropertyChangeType::ValueSet );
ChangeEvent.SetArrayIndexPerObject(ArrayIndicesPerObject);
NotifyPostChange( ChangeEvent, InNotifyHook );
}
if( bEditInlineNewWasReset )
{
RequestRebuildChildren();
}
}
}
/**
* Helper function to obtain the display name for an enum property
* @param InEnum The enum whose metadata to pull from
* @param DisplayName The name of the enum value to adjust
*
* @return true if the DisplayName has been changed
*/
bool FPropertyNode::AdjustEnumPropDisplayName( UEnum *InEnum, FString& DisplayName ) const
{
// see if we have alternate text to use for displaying the value
UMetaData* PackageMetaData = InEnum->GetOutermost()->GetMetaData();
if ( PackageMetaData )
{
FName AltDisplayName = FName(*(DisplayName+TEXT(".DisplayName")));
FString ValueText = PackageMetaData->GetValue(InEnum, AltDisplayName);
if ( ValueText.Len() > 0 )
{
// use the alternate text for this enum value
DisplayName = ValueText;
return true;
}
}
//DisplayName has been unmodified
return false;
}
/**Walks up the hierachy and return true if any parent node is a favorite*/
bool FPropertyNode::IsChildOfFavorite (void) const
{
for (const FPropertyNode* TestParentNode = GetParentNode(); TestParentNode != NULL; TestParentNode = TestParentNode->GetParentNode())
{
if (TestParentNode->HasNodeFlags(EPropertyNodeFlags::IsFavorite))
{
return true;
}
}
return false;
}
/**
* Destroys all node within the hierarchy
*/
void FPropertyNode::DestroyTree(const bool bInDestroySelf)
{
ChildNodes.Empty();
}
/**
* Marks windows as visible based on the filter strings (EVEN IF normally NOT EXPANDED)
*/
void FPropertyNode::FilterNodes( const TArray<FString>& InFilterStrings, const bool bParentSeenDueToFiltering )
{
//clear flags first. Default to hidden
SetNodeFlags(EPropertyNodeFlags::IsSeenDueToFiltering | EPropertyNodeFlags::IsSeenDueToChildFiltering | EPropertyNodeFlags::IsParentSeenDueToFiltering, false);
SetNodeFlags(EPropertyNodeFlags::IsBeingFiltered, InFilterStrings.Num() > 0 );
//FObjectPropertyNode* ParentPropertyNode = FindObjectItemParent();
//@todo slate property window
bool bMultiObjectOnlyShowDiffering = false;/*TopPropertyWindow->HasFlags(EPropertyWindowFlags::ShowOnlyDifferingItems) && (ParentPropertyNode->GetNumObjects()>1)*/;
if (InFilterStrings.Num() > 0 /*|| (TopPropertyWindow->HasFlags(EPropertyWindowFlags::ShowOnlyModifiedItems)*/ || bMultiObjectOnlyShowDiffering)
{
//if filtering, default to NOT-seen
bool bPassedFilter = false; //assuming that we aren't filtered
//see if this is a filter-able primitive
FText DisplayName = GetDisplayName();
const FString& DisplayNameStr = DisplayName.ToString();
TArray <FString> AcceptableNames;
AcceptableNames.Add(DisplayNameStr);
//get the basic name as well of the property
UProperty* TheProperty = GetProperty();
if (TheProperty && (TheProperty->GetName() != DisplayNameStr))
{
AcceptableNames.Add(TheProperty->GetName());
}
bPassedFilter = IsFilterAcceptable(AcceptableNames, InFilterStrings);
if (bPassedFilter)
{
SetNodeFlags(EPropertyNodeFlags::IsSeenDueToFiltering, true);
}
SetNodeFlags(EPropertyNodeFlags::IsParentSeenDueToFiltering, bParentSeenDueToFiltering);
}
else
{
//indicating that this node should not be force displayed, but opened normally
SetNodeFlags(EPropertyNodeFlags::IsParentSeenDueToFiltering, true);
}
//default to doing only one pass
//bool bCategoryOrObject = (GetObjectNode()) || (GetCategoryNode()!=NULL);
int32 StartRecusionPass = HasNodeFlags(EPropertyNodeFlags::IsSeenDueToFiltering) ? 1 : 0;
//Pass 1, if a pass 1 exists (object or category), is to see if there are any children that pass the filter, if any do, trim the tree to the leaves.
// This will stop categories from showing ALL properties if they pass the filter AND a child passes the filter
//Pass 0, if no child exists that passes the filter OR this node didn't pass the filter
for (int32 RecursionPass = StartRecusionPass; RecursionPass >= 0; --RecursionPass)
{
for (int32 scan = 0; scan < ChildNodes.Num(); ++scan)
{
TSharedPtr<FPropertyNode>& ScanNode = ChildNodes[scan];
check(ScanNode.IsValid());
//default to telling the children this node is NOT visible, therefore if not in the base pass, only filtered nodes will survive the filtering process.
bool bChildParamParentVisible = false;
//if we're at the base pass, tell the children the truth about visibility
if (RecursionPass == 0)
{
bChildParamParentVisible = bParentSeenDueToFiltering || HasNodeFlags(EPropertyNodeFlags::IsSeenDueToFiltering);
}
ScanNode->FilterNodes(InFilterStrings, bChildParamParentVisible);
if (ScanNode->HasNodeFlags(EPropertyNodeFlags::IsSeenDueToFiltering | EPropertyNodeFlags::IsSeenDueToChildFiltering))
{
SetNodeFlags(EPropertyNodeFlags::IsSeenDueToChildFiltering, true);
}
}
//now that we've tried a pass at our children, if any of them have been successfully seen due to filtering, just quit now
if (HasNodeFlags(EPropertyNodeFlags::IsSeenDueToChildFiltering))
{
break;
}
}
}
void FPropertyNode::ProcessSeenFlags(const bool bParentAllowsVisible )
{
// Set initial state first
SetNodeFlags(EPropertyNodeFlags::IsSeen, false);
SetNodeFlags(EPropertyNodeFlags::IsSeenDueToChildFavorite, false );
bool bAllowChildrenVisible;
if ( AsObjectNode() )
{
bAllowChildrenVisible = true;
}
else
{
//can't show children unless they are seen due to child filtering
bAllowChildrenVisible = !!HasNodeFlags(EPropertyNodeFlags::IsSeenDueToChildFiltering);
}
//process children
for (int32 scan = 0; scan < ChildNodes.Num(); ++scan)
{
TSharedPtr<FPropertyNode>& ScanNode = ChildNodes[scan];
check(ScanNode.IsValid());
ScanNode->ProcessSeenFlags(bParentAllowsVisible && bAllowChildrenVisible ); //both parent AND myself have to allow children
}
if (HasNodeFlags(EPropertyNodeFlags::IsSeenDueToFiltering | EPropertyNodeFlags::IsSeenDueToChildFiltering))
{
SetNodeFlags(EPropertyNodeFlags::IsSeen, true);
}
else
{
//Finally, apply the REAL IsSeen
SetNodeFlags(EPropertyNodeFlags::IsSeen, bParentAllowsVisible && HasNodeFlags(EPropertyNodeFlags::IsParentSeenDueToFiltering));
}
}
/**
* Marks windows as visible based their favorites status
*/
void FPropertyNode::ProcessSeenFlagsForFavorites(void)
{
if( !HasNodeFlags(EPropertyNodeFlags::IsFavorite) )
{
bool bAnyChildFavorites = false;
//process children
for (int32 scan = 0; scan < ChildNodes.Num(); ++scan)
{
TSharedPtr<FPropertyNode>& ScanNode = ChildNodes[scan];
check(ScanNode.IsValid());
ScanNode->ProcessSeenFlagsForFavorites();
bAnyChildFavorites = bAnyChildFavorites || ScanNode->HasNodeFlags(EPropertyNodeFlags::IsFavorite | EPropertyNodeFlags::IsSeenDueToChildFavorite);
}
if (bAnyChildFavorites)
{
SetNodeFlags(EPropertyNodeFlags::IsSeenDueToChildFavorite, true);
}
}
}
void FPropertyNode::NotifyPreChange( UProperty* PropertyAboutToChange, FNotifyHook* InNotifyHook )
{
TSharedRef<FEditPropertyChain> PropertyChain = BuildPropertyChain( PropertyAboutToChange );
// Call through to the property window's notify hook.
if( InNotifyHook )
{
if ( PropertyChain->Num() == 0 )
{
InNotifyHook->NotifyPreChange( PropertyAboutToChange );
}
else
{
InNotifyHook->NotifyPreChange( &PropertyChain.Get() );
}
}
FObjectPropertyNode* ObjectNode = FindObjectItemParent();
if( ObjectNode )
{
UProperty* CurProperty = PropertyAboutToChange;
// Call PreEditChange on the object chain.
while ( true )
{
for( TPropObjectIterator Itor( ObjectNode->ObjectIterator() ) ; Itor ; ++Itor )
{
UObject* Object = Itor->Get();
if ( ensure( Object ) && PropertyChain->Num() == 0 )
{
Object->PreEditChange( Property.Get() );
}
else if( ensure( Object ) )
{
Object->PreEditChange( *PropertyChain );
}
}
// Pass this property to the parent's PreEditChange call.
CurProperty = ObjectNode->GetStoredProperty();
FObjectPropertyNode* PreviousObjectNode = ObjectNode;
// Traverse up a level in the nested object tree.
ObjectNode = NotifyFindObjectItemParent( ObjectNode );
if ( !ObjectNode )
{
// We've hit the root -- break.
break;
}
else if ( PropertyChain->Num() > 0 )
{
PropertyChain->SetActivePropertyNode( CurProperty->GetOwnerProperty() );
for ( FPropertyNode* BaseItem = PreviousObjectNode; BaseItem && BaseItem != ObjectNode; BaseItem = BaseItem->GetParentNode())
{
UProperty* ItemProperty = BaseItem->GetProperty();
if ( ItemProperty == NULL )
{
// if this property item doesn't have a Property, skip it...it may be a category item or the virtual
// item used as the root for an inline object
continue;
}
// skip over property window items that correspond to a single element in a static array, or
// the inner property of another UProperty (e.g. UArrayProperty->Inner)
if ( BaseItem->ArrayIndex == INDEX_NONE && ItemProperty->GetOwnerProperty() == ItemProperty )
{
PropertyChain->SetActiveMemberPropertyNode(ItemProperty);
}
}
}
}
}
}
void FPropertyNode::NotifyPostChange( FPropertyChangedEvent& InPropertyChangedEvent, class FNotifyHook* InNotifyHook )
{
TSharedRef<FEditPropertyChain> PropertyChain = BuildPropertyChain( InPropertyChangedEvent.Property );
// remember the property that was the chain's original active property; this will correspond to the outermost property of struct/array that was modified
UProperty* const OriginalActiveProperty = PropertyChain->GetActiveMemberNode()->GetValue();
FObjectPropertyNode* ObjectNode = FindObjectItemParent();
if( ObjectNode )
{
ObjectNode->InvalidateCachedState();
UProperty* CurProperty = InPropertyChangedEvent.Property;
// Fire ULevel::LevelDirtiedEvent when falling out of scope.
FScopedLevelDirtied LevelDirtyCallback;
// Call PostEditChange on the object chain.
while ( true )
{
int32 CurrentObjectIndex = 0;
for( TPropObjectIterator Itor( ObjectNode->ObjectIterator() ) ; Itor ; ++Itor )
{
UObject* Object = Itor->Get();
if ( PropertyChain->Num() == 0 )
{
//copy
FPropertyChangedEvent ChangedEvent = InPropertyChangedEvent;
if (CurProperty != InPropertyChangedEvent.Property)
{
//parent object node property. Reset other internals and leave the event type as unspecified
ChangedEvent = FPropertyChangedEvent(CurProperty, InPropertyChangedEvent.ChangeType);
}
ChangedEvent.ObjectIteratorIndex = CurrentObjectIndex;
if( Object )
{
Object->PostEditChangeProperty( ChangedEvent );
}
}
else
{
FPropertyChangedEvent ChangedEvent = InPropertyChangedEvent;
if (CurProperty != InPropertyChangedEvent.Property)
{
//parent object node property. Reset other internals and leave the event type as unspecified
ChangedEvent = FPropertyChangedEvent(CurProperty, InPropertyChangedEvent.ChangeType);
}
FPropertyChangedChainEvent ChainEvent(*PropertyChain, ChangedEvent);
ChainEvent.ObjectIteratorIndex = CurrentObjectIndex;
if( Object )
{
Object->PostEditChangeChainProperty(ChainEvent);
}
}
LevelDirtyCallback.Request();
++CurrentObjectIndex;
}
// Pass this property to the parent's PostEditChange call.
CurProperty = ObjectNode->GetStoredProperty();
FObjectPropertyNode* PreviousObjectNode = ObjectNode;
// Traverse up a level in the nested object tree.
ObjectNode = NotifyFindObjectItemParent( ObjectNode );
if ( !ObjectNode )
{
// We've hit the root -- break.
break;
}
else if ( PropertyChain->Num() > 0 )
{
PropertyChain->SetActivePropertyNode(CurProperty->GetOwnerProperty());
for ( FPropertyNode* BaseItem = PreviousObjectNode; BaseItem && BaseItem != ObjectNode; BaseItem = BaseItem->GetParentNode())
{
UProperty* ItemProperty = BaseItem->GetProperty();
if ( ItemProperty == NULL )
{
// if this property item doesn't have a Property, skip it...it may be a category item or the virtual
// item used as the root for an inline object
continue;
}
// skip over property window items that correspond to a single element in a static array, or
// the inner property of another UProperty (e.g. UArrayProperty->Inner)
if ( BaseItem->GetArrayIndex() == INDEX_NONE && ItemProperty->GetOwnerProperty() == ItemProperty )
{
PropertyChain->SetActiveMemberPropertyNode(ItemProperty);
}
}
}
}
}
// Broadcast the change to any listeners
BroadcastPropertyChangedDelegates();
// Call through to the property window's notify hook.
if( InNotifyHook )
{
if ( PropertyChain->Num() == 0 )
{
InNotifyHook->NotifyPostChange( InPropertyChangedEvent, InPropertyChangedEvent.Property );
}
else
{
PropertyChain->SetActiveMemberPropertyNode( OriginalActiveProperty );
PropertyChain->SetActivePropertyNode( InPropertyChangedEvent.Property);
InNotifyHook->NotifyPostChange( InPropertyChangedEvent, &PropertyChain.Get() );
}
}
if( OriginalActiveProperty )
{
//if i have metadata forcing other property windows to rebuild
FString MetaData = OriginalActiveProperty->GetMetaData(TEXT("ForceRebuildProperty"));
if( MetaData.Len() > 0 )
{
// We need to find the property node beginning at the root/parent, not at our own node.
ObjectNode = FindObjectItemParent();
check(ObjectNode != NULL);
TSharedPtr<FPropertyNode> ForceRebuildNode = ObjectNode->FindChildPropertyNode( FName(*MetaData), true );
if( ForceRebuildNode.IsValid() )
{
ForceRebuildNode->RequestRebuildChildren();
}
}
}
// The value has changed so the cached value could be invalid
// Need to recurse here as we might be editing a struct with child properties that need re-caching
ClearCachedReadAddresses(true);
// Redraw viewports
FEditorSupportDelegates::RedrawAllViewports.Broadcast();
}
void FPropertyNode::BroadcastPropertyChangedDelegates()
{
PropertyValueChangedEvent.Broadcast();
// Walk through the parents and broadcast
FPropertyNode* LocalParentNode = GetParentNode();
while( LocalParentNode )
{
if( LocalParentNode->OnChildPropertyValueChanged().IsBound() )
{
LocalParentNode->OnChildPropertyValueChanged().Broadcast();
}
LocalParentNode = LocalParentNode->GetParentNode();
}
}
void FPropertyNode::SetOnRebuildChildren( FSimpleDelegate InOnRebuildChildren )
{
OnRebuildChildren = InOnRebuildChildren;
}
TSharedPtr< FPropertyItemValueDataTrackerSlate > FPropertyNode::GetValueTracker( UObject* Object, uint32 ObjIndex )
{
ensure( AsItemPropertyNode() );
TSharedPtr< FPropertyItemValueDataTrackerSlate > RetVal;
if( Object && Object != UObject::StaticClass() && Object != UObject::StaticClass()->GetDefaultObject() )
{
if( !ObjectDefaultValueTrackers.IsValidIndex(ObjIndex) )
{
uint32 NumToAdd = (ObjIndex - ObjectDefaultValueTrackers.Num()) + 1;
while( NumToAdd > 0 )
{
ObjectDefaultValueTrackers.Add( TSharedPtr<FPropertyItemValueDataTrackerSlate> () );
--NumToAdd;
}
}
TSharedPtr<FPropertyItemValueDataTrackerSlate>& ValueTracker = ObjectDefaultValueTrackers[ObjIndex];
if( !ValueTracker.IsValid() )
{
ValueTracker = MakeShareable( new FPropertyItemValueDataTrackerSlate( this, Object ) );
}
else
{
ValueTracker->Reset(this, Object);
}
RetVal = ValueTracker;
}
return RetVal;
}
TSharedRef<FEditPropertyChain> FPropertyNode::BuildPropertyChain( UProperty* InProperty )
{
TSharedRef<FEditPropertyChain> PropertyChain( MakeShareable( new FEditPropertyChain ) );
FPropertyNode* ItemNode = this;
FComplexPropertyNode* ComplexNode = FindComplexParent();
UProperty* MemberProperty = InProperty;
do
{
if (ItemNode == ComplexNode)
{
MemberProperty = PropertyChain->GetHead()->GetValue();
}
UProperty* TheProperty = ItemNode->GetProperty();
if ( TheProperty )
{
// Skip over property window items that correspond to a single element in a static array,
// or the inner property of another UProperty (e.g. UArrayProperty->Inner).
if ( ItemNode->GetArrayIndex() == INDEX_NONE && TheProperty->GetOwnerProperty() == TheProperty )
{
PropertyChain->AddHead( TheProperty );
}
}
ItemNode = ItemNode->GetParentNode();
}
while( ItemNode != NULL );
// If the modified property was a property of the object at the root of this property window, the member property will not have been set correctly
if (ItemNode == ComplexNode)
{
MemberProperty = PropertyChain->GetHead()->GetValue();
}
PropertyChain->SetActivePropertyNode( InProperty );
PropertyChain->SetActiveMemberPropertyNode( MemberProperty );
return PropertyChain;
}
FPropertyChangedEvent& FPropertyNode::FixPropertiesInEvent(FPropertyChangedEvent& Event)
{
ensure(Event.Property);
auto PropertyChain = BuildPropertyChain(Event.Property);
auto MemberProperty = PropertyChain->GetActiveMemberNode() ? PropertyChain->GetActiveMemberNode()->GetValue() : NULL;
if (ensure(MemberProperty))
{
Event.SetActiveMemberProperty(MemberProperty);
}
return Event;
}
void FPropertyNode::SetInstanceMetaData(const FName& Key, const FString& Value)
{
InstanceMetaData.Add(Key, Value);
}
const FString* FPropertyNode::GetInstanceMetaData(const FName& Key) const
{
return InstanceMetaData.Find(Key);
}
bool FPropertyNode::ParentOrSelfHasMetaData(const FName& MetaDataKey) const
{
return (Property.IsValid() && Property->HasMetaData(MetaDataKey)) || (ParentNode && ParentNode->ParentOrSelfHasMetaData(MetaDataKey));
}
void FPropertyNode::InvalidateCachedState()
{
bUpdateDiffersFromDefault = true;
bUpdateEditConstState = true;
for( TSharedPtr<FPropertyNode>& ChildNode : ChildNodes )
{
ChildNode->InvalidateCachedState();
}
}
/**
* Does the string compares to ensure this Name is acceptable to the filter that is passed in
* @return Return True if this property should be displayed. False if it should be culled
*/
bool FPropertyNode::IsFilterAcceptable(const TArray<FString>& InAcceptableNames, const TArray<FString>& InFilterStrings)
{
bool bCompleteMatchFound = true;
if (InFilterStrings.Num())
{
//we have to make sure one name matches all criteria
for (int32 TestNameIndex = 0; TestNameIndex < InAcceptableNames.Num(); ++TestNameIndex)
{
bCompleteMatchFound = true;
FString TestName = InAcceptableNames[TestNameIndex];
for (int32 scan = 0; scan < InFilterStrings.Num(); scan++)
{
if (!TestName.Contains(InFilterStrings[scan]))
{
bCompleteMatchFound = false;
break;
}
}
if (bCompleteMatchFound)
{
break;
}
}
}
return bCompleteMatchFound;
}
void FPropertyNode::AdditionalInitializationUDS(UProperty* Property, uint8* RawPtr)
{
if (const UStructProperty* StructProperty = Cast<const UStructProperty>(Property))
{
if (!FStructureEditorUtils::Fill_MakeStructureDefaultValue(Cast<const UUserDefinedStruct>(StructProperty->Struct), RawPtr))
{
UE_LOG(LogPropertyNode, Warning, TEXT("MakeStructureDefaultValue parsing error. Property: %s "), *StructProperty->GetPathName());
}
}
}
void FPropertyNode::PropagateContainerPropertyChange( UObject* ModifiedObject, const FString& OriginalContainerContent, EPropertyArrayChangeType::Type ChangeType, int32 Index )
{
UProperty* NodeProperty = GetProperty();
UArrayProperty* ArrayProperty = NULL;
USetProperty* SetProperty = NULL;
UMapProperty* MapProperty = NULL;
FPropertyNode* ParentPropertyNode = GetParentNode();
UProperty* ConvertedProperty = NULL;
if (ChangeType == EPropertyArrayChangeType::Add || ChangeType == EPropertyArrayChangeType::Clear)
{
ConvertedProperty = NodeProperty;
}
else
{
ConvertedProperty = Cast<UProperty>(NodeProperty->GetOuter());
}
ArrayProperty = Cast<UArrayProperty>(ConvertedProperty);
SetProperty = Cast<USetProperty>(ConvertedProperty);
MapProperty = Cast<UMapProperty>(ConvertedProperty);
check(ArrayProperty || SetProperty || MapProperty);
TArray<UObject*> ArchetypeInstances, ObjectsToChange;
FPropertyNode* SubobjectPropertyNode = NULL;
UObject* Object = ModifiedObject;
if (Object->HasAnyFlags(RF_ClassDefaultObject|RF_ArchetypeObject))
{
// Object is a default suobject, collect all instances.
Object->GetArchetypeInstances(ArchetypeInstances);
}
else if (Object->HasAnyFlags(RF_DefaultSubObject) && Object->GetOuter()->HasAnyFlags(RF_ClassDefaultObject|RF_ArchetypeObject))
{
// Object is a default subobject of a default object. Get the subobject property node and use its owner instead.
for (SubobjectPropertyNode = FindObjectItemParent(); SubobjectPropertyNode && !SubobjectPropertyNode->GetProperty(); SubobjectPropertyNode = SubobjectPropertyNode->GetParentNode());
if (SubobjectPropertyNode != NULL)
{
// Switch the object to the owner default object and collect its instances.
Object = Object->GetOuter();
Object->GetArchetypeInstances(ArchetypeInstances);
}
}
ObjectsToChange.Push(Object);
while (ObjectsToChange.Num() > 0)
{
check(ObjectsToChange.Num() > 0);
// Pop the first object to change
UObject* ObjToChange = ObjectsToChange[0];
UObject* ActualObjToChange = NULL;
ObjectsToChange.RemoveAt(0);
if (SubobjectPropertyNode)
{
// If the original object is a subobject, get the current object's subobject too.
// In this case we're not going to modify ObjToChange but its default subobject.
ActualObjToChange = *(UObject**)SubobjectPropertyNode->GetValueBaseAddress((uint8*)ObjToChange);
}
else
{
ActualObjToChange = ObjToChange;
}
if (ActualObjToChange != ModifiedObject)
{
uint8* Addr = NULL;
if (ChangeType == EPropertyArrayChangeType::Add || ChangeType == EPropertyArrayChangeType::Clear)
{
Addr = GetValueBaseAddress((uint8*)ActualObjToChange);
}
else
{
Addr = ParentPropertyNode->GetValueBaseAddress((uint8*)ActualObjToChange);
}
FString OriginalContent;
ConvertedProperty->ExportText_Direct(OriginalContent, Addr, Addr, NULL, PPF_Localized);
bool bIsDefaultContainerContent = OriginalContent == OriginalContainerContent;
if (Addr != NULL && ArrayProperty)
{
FScriptArrayHelper ArrayHelper(ArrayProperty, Addr);
// Check if the original value was the default value and change it only then
if (bIsDefaultContainerContent)
{
int32 ElementToInitialize = -1;
switch (ChangeType)
{
case EPropertyArrayChangeType::Add:
ElementToInitialize = ArrayHelper.AddValue();
break;
case EPropertyArrayChangeType::Clear:
ArrayHelper.EmptyValues();
break;
case EPropertyArrayChangeType::Insert:
ArrayHelper.InsertValues(ArrayIndex, 1);
ElementToInitialize = ArrayIndex;
break;
case EPropertyArrayChangeType::Delete:
ArrayHelper.RemoveValues(ArrayIndex, 1);
break;
case EPropertyArrayChangeType::Duplicate:
ArrayHelper.InsertValues(ArrayIndex, 1);
// Copy the selected item's value to the new item.
NodeProperty->CopyCompleteValue(ArrayHelper.GetRawPtr(ArrayIndex), ArrayHelper.GetRawPtr(ArrayIndex + 1));
Object->InstanceSubobjectTemplates();
break;
}
if (ElementToInitialize >= 0)
{
AdditionalInitializationUDS(ArrayProperty->Inner, ArrayHelper.GetRawPtr(ElementToInitialize));
}
}
} // End Array
else if ( Addr != NULL && SetProperty )
{
FScriptSetHelper SetHelper(SetProperty, Addr);
// Check if the original value was the default value and change it only then
if (bIsDefaultContainerContent)
{
int32 ElementToInitialize = -1;
switch (ChangeType)
{
case EPropertyArrayChangeType::Add:
ElementToInitialize = SetHelper.AddDefaultValue_Invalid_NeedsRehash();
SetHelper.Rehash();
break;
case EPropertyArrayChangeType::Clear:
SetHelper.EmptyElements();
break;
case EPropertyArrayChangeType::Insert:
check(false); // Insert is not supported for sets
break;
case EPropertyArrayChangeType::Delete:
SetHelper.RemoveAt(ArrayIndex);
SetHelper.Rehash();
break;
case EPropertyArrayChangeType::Duplicate:
check(false); // Duplicate not supported on sets
break;
}
if (ElementToInitialize >= 0)
{
AdditionalInitializationUDS(SetProperty->ElementProp, SetHelper.GetElementPtr(ElementToInitialize));
}
}
} // End Set
else if (Addr != NULL && MapProperty)
{
FScriptMapHelper MapHelper(MapProperty, Addr);
// Check if the original value was the default value and change it only then
if (bIsDefaultContainerContent)
{
int32 ElementToInitialize = -1;
switch (ChangeType)
{
case EPropertyArrayChangeType::Add:
ElementToInitialize = MapHelper.AddDefaultValue_Invalid_NeedsRehash();
MapHelper.Rehash();
break;
case EPropertyArrayChangeType::Clear:
MapHelper.EmptyValues();
break;
case EPropertyArrayChangeType::Insert:
check(false); // Insert is not supported for maps
break;
case EPropertyArrayChangeType::Delete:
MapHelper.RemoveAt(ArrayIndex);
MapHelper.Rehash();
break;
case EPropertyArrayChangeType::Duplicate:
check(false); // Duplicate is not supported for maps
break;
}
if (ElementToInitialize >= 0)
{
uint8* PairPtr = MapHelper.GetPairPtr(ElementToInitialize);
AdditionalInitializationUDS(MapProperty->KeyProp, MapProperty->KeyProp->ContainerPtrToValuePtr<uint8>(PairPtr));
AdditionalInitializationUDS(MapProperty->ValueProp, MapProperty->ValueProp->ContainerPtrToValuePtr<uint8>(PairPtr));
}
}
} // End Map
}
for (int32 i=0; i < ArchetypeInstances.Num(); ++i)
{
UObject* Obj = ArchetypeInstances[i];
if (Obj->GetArchetype() == ObjToChange)
{
ObjectsToChange.Push(Obj);
ArchetypeInstances.RemoveAt(i--);
}
}
}
}
void FPropertyNode::PropagatePropertyChange( UObject* ModifiedObject, const TCHAR* NewValue, const FString& PreviousValue )
{
TArray<UObject*> ArchetypeInstances, ObjectsToChange;
FPropertyNode* SubobjectPropertyNode = NULL;
UObject* Object = ModifiedObject;
if (Object->HasAnyFlags(RF_ClassDefaultObject|RF_ArchetypeObject))
{
// Object is a default subobject, collect all instances.
Object->GetArchetypeInstances(ArchetypeInstances);
}
else if (Object->HasAnyFlags(RF_DefaultSubObject) && Object->GetOuter()->HasAnyFlags(RF_ClassDefaultObject|RF_ArchetypeObject))
{
// Object is a default subobject of a default object. Get the subobject property node and use its owner instead.
for (SubobjectPropertyNode = FindObjectItemParent(); SubobjectPropertyNode && !SubobjectPropertyNode->GetProperty(); SubobjectPropertyNode = SubobjectPropertyNode->GetParentNode());
if (SubobjectPropertyNode != NULL)
{
// Switch the object to the owner default object and collect its instances.
Object = Object->GetOuter();
Object->GetArchetypeInstances(ArchetypeInstances);
}
}
static FName FNAME_EditableWhenInherited = GET_MEMBER_NAME_CHECKED(UActorComponent,bEditableWhenInherited);
if (GetProperty()->GetFName() == FNAME_EditableWhenInherited && ModifiedObject->IsA<UActorComponent>() && FString(TEXT("False")) == NewValue)
{
FBlueprintEditorUtils::HandleDisableEditableWhenInherited(ModifiedObject, ArchetypeInstances);
}
FPropertyNode* Parent = GetParentNode();
UProperty* ParentProp = Parent->GetProperty();
UArrayProperty* ParentArrayProp = Cast<UArrayProperty>(ParentProp);
UMapProperty* ParentMapProp = Cast<UMapProperty>(ParentProp);
USetProperty* ParentSetProp = Cast<USetProperty>(ParentProp);
UProperty* Prop = GetProperty();
if (ParentArrayProp && ParentArrayProp->Inner != Prop)
{
ParentArrayProp = nullptr;
}
if (ParentMapProp && ParentMapProp->KeyProp != Prop && ParentMapProp->ValueProp != Prop)
{
ParentMapProp = nullptr;
}
if (ParentSetProp && ParentSetProp->ElementProp != Prop)
{
ParentSetProp = nullptr;
}
ObjectsToChange.Push(Object);
while (ObjectsToChange.Num() > 0)
{
check(ObjectsToChange.Num() > 0);
// Pop the first object to change
UObject* ObjToChange = ObjectsToChange[0];
UObject* ActualObjToChange = NULL;
ObjectsToChange.RemoveAt(0);
if (SubobjectPropertyNode)
{
// If the original object is a subobject, get the current object's subobject too.
// In this case we're not going to modify ObjToChange but its default subobject.
ActualObjToChange = *(UObject**)SubobjectPropertyNode->GetValueBaseAddress((uint8*)ObjToChange);
}
else
{
ActualObjToChange = ObjToChange;
}
if (ActualObjToChange != ModifiedObject)
{
uint8* DestSimplePropAddr = GetValueBaseAddress( (uint8*)ActualObjToChange );
if (DestSimplePropAddr != nullptr)
{
UProperty* ComplexProperty = Prop;
FPropertyNode* ComplexPropertyNode = this;
if (ParentArrayProp || ParentMapProp || ParentSetProp)
{
ComplexProperty = ParentProp;
ComplexPropertyNode = ParentNode;
}
uint8* DestComplexPropAddr = ComplexPropertyNode->GetValueBaseAddress((uint8*)ActualObjToChange);
uint8* ModifiedComplexPropAddr = ComplexPropertyNode->GetValueBaseAddress((uint8*)ModifiedObject);
bool bShouldImport = false;
{
uint8* TempComplexPropAddr = (uint8*)FMemory::Malloc(ComplexProperty->GetSize(), ComplexProperty->GetMinAlignment());
ON_SCOPE_EXIT
{
FMemory::Free(TempComplexPropAddr);
};
ComplexProperty->InitializeValue(TempComplexPropAddr);
ON_SCOPE_EXIT
{
ComplexProperty->DestroyValue(TempComplexPropAddr);
};
// Importing the previous value into the temporary property can potentially affect shared state (such as FText display string values), so we back-up the current value
// before we do this, so that we can restore it once we've checked whether the two properties are identical
// This ensures that shared state keeps the correct value, even if the destination property itself isn't imported (or only partly imported, as is the case with arrays/maps/sets)
FString CurrentValue;
ComplexProperty->ExportText_Direct(CurrentValue, ModifiedComplexPropAddr, nullptr, ModifiedObject, PPF_None);
ComplexProperty->ImportText(*PreviousValue, TempComplexPropAddr, PPF_None, ModifiedObject);
bShouldImport = ComplexProperty->Identical(DestComplexPropAddr, TempComplexPropAddr, PPF_None);
ComplexProperty->ImportText(*CurrentValue, TempComplexPropAddr, PPF_None, ModifiedObject);
}
// Only import if the value matches the previous value of the property that changed
if (bShouldImport)
{
Prop->ImportText(NewValue, DestSimplePropAddr, PPF_None, ActualObjToChange);
}
}
}
for (int32 InstanceIndex = 0; InstanceIndex < ArchetypeInstances.Num(); ++InstanceIndex)
{
UObject* Obj = ArchetypeInstances[InstanceIndex];
if (Obj->GetArchetype() == ObjToChange)
{
ObjectsToChange.Push(Obj);
ArchetypeInstances.RemoveAt(InstanceIndex--);
}
}
}
}
void FPropertyNode::AddRestriction( TSharedRef<const class FPropertyRestriction> Restriction )
{
Restrictions.AddUnique(Restriction);
}
bool FPropertyNode::IsHidden(const FString& Value, TArray<FText>* OutReasons) const
{
bool bIsHidden = false;
for( auto It = Restrictions.CreateConstIterator() ; It ; ++It )
{
TSharedRef<const FPropertyRestriction> Restriction = (*It);
if( Restriction->IsValueHidden(Value) )
{
bIsHidden = true;
if (OutReasons)
{
OutReasons->Add(Restriction->GetReason());
}
else
{
break;
}
}
}
return bIsHidden;
}
bool FPropertyNode::IsDisabled(const FString& Value, TArray<FText>* OutReasons) const
{
bool bIsDisabled = false;
for (const TSharedRef<const FPropertyRestriction>& Restriction : Restrictions)
{
if( Restriction->IsValueDisabled(Value) )
{
bIsDisabled = true;
if (OutReasons)
{
OutReasons->Add(Restriction->GetReason());
}
else
{
break;
}
}
}
return bIsDisabled;
}
bool FPropertyNode::IsRestricted(const FString& Value, TArray<FText>& OutReasons) const
{
const bool bIsHidden = IsHidden(Value, &OutReasons);
const bool bIsDisabled = IsDisabled(Value, &OutReasons);
return (bIsHidden || bIsDisabled);
}
bool FPropertyNode::GenerateRestrictionToolTip(const FString& Value, FText& OutTooltip) const
{
static FText ToolTipFormat = NSLOCTEXT("PropertyRestriction", "TooltipFormat ", "{0}{1}");
static FText MultipleRestrictionsToolTopAdditionFormat = NSLOCTEXT("PropertyRestriction", "MultipleRestrictionToolTipAdditionFormat ", "({0} restrictions...)");
TArray<FText> Reasons;
const bool bRestricted = IsRestricted(Value, Reasons);
FText Ret;
if( bRestricted && Reasons.Num() > 0 )
{
if( Reasons.Num() > 1 )
{
FText NumberOfRestrictions = FText::AsNumber(Reasons.Num());
OutTooltip = FText::Format(ToolTipFormat, Reasons[0], FText::Format(MultipleRestrictionsToolTopAdditionFormat,NumberOfRestrictions));
}
else
{
OutTooltip = FText::Format(ToolTipFormat, Reasons[0], FText());
}
}
return bRestricted;
}
#undef LOCTEXT_NAMESPACE