gusucode.com > datatools工具箱 matlab源码程序 > datatools/inspector/matlab/+internal/+matlab/+inspector/InspectorProxyMixin.m

    classdef (Abstract) InspectorProxyMixin < ...
        dynamicprops & matlab.mixin.CustomDisplay
    
    % This class is unsupported and might change or be removed without
    % notice in a future version.
    
    % Inspector Proxy Mixin class.  Classes which wish to provide a custom
    % view in the Property Inspector may extend this mixin, and define the
    % properties that should be displayed.  It acts as a proxy between the
    % original object and the inspector classes which introspect the
    % object.
    
    % Copyright 2015 The MathWorks, Inc.
    
    properties(Hidden = true)
        % Keep a reference to the original object
        OriginalObjects;
        
        % The list of defined groups
        GroupList;
        
        % Property change listeners created
        PropChangedListeners = {};
        
        % Track the property types in a map, since they cannot be set on a
        % dynamic object
        PropertyTypeMap;
        
        Workspace;
        
        PreviousData = {};
        
        % Specifies how to handle properties when multiple objects are
        % selected
        MultiplePropertyCombinationMode@internal.matlab.inspector.MultiplePropertyCombinationMode = ...
            internal.matlab.inspector.MultiplePropertyCombinationMode.FIRST;
        
        % Specifies how to handle values when multiple objects are selected
        MultipleValueCombinationMode@internal.matlab.inspector.MultipleValueCombinationMode = ...
            internal.matlab.inspector.MultipleValueCombinationMode.LAST
        
        InternalPropertySet = false;
        
        CategoricalProperties = {};
        
        AllGroupsExpanded = false;
    end
    
    methods
        % Create a new InspectorProxyMixin instance.
        function this = InspectorProxyMixin(OriginalObject)
            this.OriginalObjects = OriginalObject;
            this.PreviousData{1} = ...
                internal.matlab.inspector.Utils.createStructForObject(...
                OriginalObject);
            this.PropertyTypeMap = containers.Map;
            
            m = metaclass(this);
            p = m.PropertyList;
            for i = 1:length(p)
                prop = p(i);
                
                % For each property not defined by the one of the inspector
                % classes themselves
                if isempty(strfind(prop.DefiningClass.Name, ...
                        'InspectorProxyMixin'))
                    
                    if isprop(this.OriginalObjects, prop.Name)
                        try
                            % Assign the initial value of the property to that
                            % of the original object, if they are the same
                            this.(prop.Name) = this.OriginalObjects.(prop.Name);
                        catch
                            % Its possible the types are different
                            % (redefining a string as an enumerated value,
                            % for example).  Don't worry about setting the
                            % value and assume its done already
                        end
                        
                        % Assign a PostSet listener to the original
                        % object, so that the values can be kept in sync
                        originalProp = findprop(this.OriginalObjects, ...
                            prop.Name);
                        if originalProp.SetObservable
                            this.PropChangedListeners{...
                                length(this.PropChangedListeners)+1,1} = ...
                                event.proplistener(this.OriginalObjects, ...
                                originalProp, 'PostSet', ...
                                @this.propChangedCallback);
                        end
                        
                        if iscategorical(this.OriginalObjects.(prop.Name))
                            % Save list of categorical properties for
                            % comparison later, to check when categories
                            % are added or removed
                            this.CategoricalProperties{end+1} = prop.Name;
                        end
                        
                        if ~isequal(prop.Type.Name, 'any')
                            % The mixin class has redefined a property type
                            % to be more restrictive/different than the
                            % original type (for example, the original type
                            % may be text while the new type is an
                            % enumeration), so use this definition
                            this.PropertyTypeMap(prop.Name) = prop.Type;
                        else
                            % Otherwise, use the original property's type
                            % definition
                            this.PropertyTypeMap(prop.Name) = ...
                                originalProp.Type;
                        end
                    else
                        this.PropertyTypeMap(prop.Name) = prop.Type;
                    end
                end
            end
        end
        
        function delete(this)
            % Remove any Property Changed listeners which have been added
            if ~isempty(this.PropChangedListeners)
                cellfun(@(x) delete(x), this.PropChangedListeners);
                this.PropChangedListeners = {};
            end
        end
        
        function propChangedCallback(this, es, ~)
            % Handle this event by forwarding the setProperty call to the
            % original object
            propName = es.Name;
            setPropertyValue(this, propName, ...
                this.OriginalObjects.(propName));
        end
        
        % Returns the property value.  It first tries to access the value
        % directly.  If the result is empty, it checks to see if the
        % OriginalObject has the property value set.
        function val = getPropertyValue(this, propertyName)
            try
                % Should be able to access the property directly
                val = this.(propertyName);
            catch e
                rethrow(e);
            end
        end
        
        % Returns the property value.  Uses the getPropertyValue method to
        % check for direct access, and if not, access through the original
        % object.
        function val = get(this, propertyName)
            val = getPropertyValue(this, propertyName);
        end
        
        % set the property value
        function setPropertyValue(this, varargin)
            propertyName = varargin{1};
            value = varargin{2};
            try
                this.(propertyName) = value;
                
                this.setOriginalPropValue(propertyName, value);
            catch
            end
        end
        
        function setOriginalPropValue(this, propertyName, value)
            try
                if ~this.InternalPropertySet
                    % If we are doing an internal property set (usually on
                    % object creation), then don't set the original
                    % object's values
                    for i = 1:length(this.OriginalObjects)
                        this.OriginalObjects(i).(propertyName) = value;
                        
%                         % Update the PreviousData struct as well
%                         this.PreviousData{1}.(propertyName) = value;
                    end
                end
                
                % Does a property inspector internal property exist?  If
                % so, set this value as well
                if isprop(this, [propertyName '_PI'])
                    this.([propertyName '_PI']) = value;
                end
            catch
            end
        end
        
        function set(this, propertyName, value)
            setPropertyValue(this, propertyName, value);
        end
        
        % Called to create an InspectorGroup.  Group ID, title, and
        % description must be specified.  Returns an InspectorGroup object,
        % which can have property names added to it.
        function group = createGroup(this, groupID, groupTitle, ...
                groupDescription)
            group = [];
            if ~isempty(this.GroupList)
                % Check to see if the group with the specified groupID has
                % already been created, and if it has, return it.
                idx = strcmp({this.GroupList.GroupID}, groupID);
                if any(idx)
                    group = this.GroupList(idx);
                end
            end
            
            if isempty(group)
                % Create new group
                group = internal.matlab.inspector.InspectorGroup(...
                    groupID,...
                    internal.matlab.inspector.Utils.getPossibleMessageCatalogString(groupTitle),...
                    internal.matlab.inspector.Utils.getPossibleMessageCatalogString(groupDescription));
                if this.AllGroupsExpanded
                    group.Expanded = true;
                end
                
                % Store all created groups in an array
                if isempty(this.GroupList)
                    this.GroupList = group;
                else
                    this.GroupList = [this.GroupList group];
                end
            end
        end
        
        % Return an array of groups which have been created
        function groups = getGroups(this)
            groups = this.GroupList;
        end
        
        % Sets all groups to be expanded
        function expandAllGroups(this)
            this.AllGroupsExpanded = true;
            for i = 1:length(this.GroupList)
                g = this.GroupList(i);
                g.Expanded = true;
            end
        end
        
        function setWorkspace(this, workspace)
            this.Workspace = workspace;
        end
        
        function [changed, changedProperties, changedProxyProperties] = ...
                OrigObjectChange(this)
            % Called to determine if any of the properties of the original
            % objects that this is the proxy for have changed.  Ideally, if
            % all properties are setObservable=true, there would never be a
            % difference.  However, for setObservable=false properties, its
            % possible that they can get out of sync.
            changed = false;
            numPropsChanged = false;
            changedProperties = {};
            changedProxyProperties = {};

            combinedValues = [];
            for i=1:length(this.OriginalObjects)
                % Compare against a struct of the original object
                % (necessary for handle objects)
                if ~isa(this.OriginalObjects(i), 'handle') || ...
                        (isa(this.OriginalObjects(i), 'handle') && ...
                        isvalid(this.OriginalObjects(i)))
                    
                    currObjStruct = ...
                        internal.matlab.inspector.Utils.createStructForObject(...
                        this.OriginalObjects(i));
                    currObjProps = fieldnames(currObjStruct);
                    prevDataProps = fieldnames(this.PreviousData{i});
                    
                    if isequal(sort(currObjProps), sort(prevDataProps))
                        % Check to see which specific properties have changed
                        changedIdx = cellfun(@(x) ~isequaln(...
                            currObjStruct.(x), this.PreviousData{i}.(x)), ...
                            currObjProps);
                        if any(changedIdx)
                            % Update the list of changed properties
                            changedProperties = unique(vertcat(changedProperties, ...
                                currObjProps(changedIdx)), 'stable');
                            changed = true;
                        end
                        
                        if ~isempty(this.CategoricalProperties)
                            % Compare the categories for any categorical
                            % variables
                            categoryChanges = cellfun(@(x) ...
                                ~isequal(categories(currObjStruct.(x)), ...
                                categories(this.PreviousData{i}.((x)))), ...
                                this.CategoricalProperties);
                            if any(categoryChanges)
                                % Update the list of changed properties
                                changedProperties = unique(vertcat(changedProperties, ...
                                    this.CategoricalProperties(categoryChanges)), ...
                                    'stable');
                                changed = true;
                            end
                        end
                        
                        if isempty(combinedValues)
                            combinedValues = currObjStruct;
                        else
                            objFields = fieldnames(currObjStruct);
                            for j=1:length(objFields)
                                combinedValues.(objFields{j}) = ...
                                    internal.matlab.inspector.InspectorProxyMixin.getCombinedValue(...
                                    combinedValues.(objFields{j}), currObjStruct.(objFields{j}), ...
                                    this.MultipleValueCombinationMode);
                            end
                        end
                    else
                        numPropsChanged = true;
                        changed = true;
                        changedProperties = unique(vertcat(...
                            changedProperties, ...
                            setdiff(currObjProps, prevDataProps)), ...
                            'stable');
                        
                        if length(currObjProps) > length(prevDataProps)
                            % Property was added to the original object,
                            % need to add it to the proxy as well
                            for c = 1:length(changedProperties)
                                this.addPropertyToProxy(changedProperties{c});
                            end
                        else
                            % Property was removed
                        end
                    end
                end
            end
            
            if ~isempty(combinedValues)
                objFields = fieldnames(combinedValues);
                changedIdx = cellfun(@(x) isprop(this, x) ...
                    && ~isequaln(combinedValues.(x), this.(x)), objFields);
                changedProxyProperties = unique(vertcat(changedProxyProperties, ...
                    objFields(changedIdx)), 'stable');
                changed = true;
            end
            
            % Limit the list of changed properties to those which are
            % currently displayed
            propertyList = this.getPropertyListForMode(this.OriginalObjects, ...
                this.MultiplePropertyCombinationMode);
            changedProperties = intersect(propertyList, changedProperties);
            
            % If any proxy properties changed, which are the same as the
            % changed properties, just report the property in the changed
            % property list
            changedProxyProperties = intersect(propertyList, changedProxyProperties);
            overlaps = ismember(changedProxyProperties, changedProperties);
            changedProxyProperties(overlaps) = [];
            
            if isempty(changedProperties) && ~numPropsChanged
                changed = false;
            end
        end
        
        function reinitializeFromOrigObject(this, changedProperties, ...
                changedProxyProperties)
            % Called to reinitialize the properties of the proxy from the
            % original object.  This is necessary because properties which
            % are SetObservable=false, they can get out of sync with the
            % original object.  changeProperties is a list of properties to
            % reinitialize.
            propertyList = this.getPropertyListForMode(this.OriginalObjects, ...
                this.MultiplePropertyCombinationMode);
            propChangedList = unique(union(changedProperties, changedProxyProperties));
            for j=1:length(propChangedList)
                propName = propChangedList{j};
                isProp = false;
                currValue = [];
                
                for i=1:length(this.OriginalObjects)
                    o = this.OriginalObjects(i);
                    
                    % Make sure the property being reported as changed is
                    % actually one of the properties which we are
                    % currently displaying (also private properties may
                    % show up as changed, which we don't care about)
                    if ismember(propName, propertyList)
                        isProp = true;
                        if ismember(propName, changedProperties)
                            % Take the object's property value - it was
                            % updated, but the proxy hasn't been updated
                            % yet
                            try
                                propValue = o.(propName);
                            catch
                                % Typically this won't fail, but it can
                                % sometimes with dependent properties that
                                % become invalid.  Set to empty in this
                                % case.
                                propValue = [];
                            end
                            currValue = ...
                                internal.matlab.inspector.InspectorProxyMixin.getCombinedValue(...
                                currValue, propValue, ...
                                this.MultipleValueCombinationMode);
                        else
                            % Take the proxy's property value - it must
                            % have changed to be different from the
                            % original object
                            currValue = ...
                                internal.matlab.inspector.InspectorProxyMixin.getCombinedValue(...
                                currValue, this.(propName), ...
                                this.MultipleValueCombinationMode);
                            o.(propName) = currValue;
                        end
                        
                        % Update the struct version of the data as well
                        this.PreviousData{i}.(propName) = propValue;
                    end
                end
                
                if isProp
                    % If a property was dynamically added to the object,
                    % we need to also add it to the proxy object
                    this.addPropertyToProxy(propName);
                    
                    if isprop(this, [propName '_PI'])
                        this.InternalPropertySet = true;
                        this.([propName '_PI']) = currValue;
                        this.InternalPropertySet = false;
                    else
                        this.(propName) = currValue;
                    end
                end
            end
        end
    end
    
    methods (Access = protected)
        % Override the displayScalarObject method so that a disp of an
        % InspectorProxyMixin will only show the properties defined by the
        % class which extends the mixin.
        function displayScalarObject(this)
            header = getHeader(this);
            disp(header);
            
            props = properties(this);
            try
                values = cellfun(@(x) this.(x), props, ...
                    'UniformOutput', false);
            catch
                try
                    values = cellfun(@(x) this.OriginalObjects.(x), props, ...
                        'UniformOutput', false);
                catch
                    values = repmat(' ', length(props), 1);
                end
            end
            if ~iscell(values)
                values = {values};
            end
            s = cell2struct(values, props, 1);
            disp(s);
        end
        
        % Returns a list of the names of the Public, non-hidden properties
        % for the given object obj.
        function propertyList = getPublicNonHiddenProps(~, obj)
            propertyList = properties(obj);
        end
        
        % Returns a list of property names for the properties in the list
        % of objects (objectList), based on the
        % multiplePropertyCombinationMode parameter.  This will be either
        % the union of properties, the intersection of properties, the
        % properties from the first object, or the properties from the last
        % object.
        function propertyList = getPropertyListForMode(this, objectList, ...
                multiplePropertyCombinationMode)
            switch(multiplePropertyCombinationMode)
                case internal.matlab.inspector.MultiplePropertyCombinationMode.UNION
                    propertyList = {};
                    for i = 1:length(objectList)
                        p = this.getPublicNonHiddenProps(...
                            objectList(i));
                        propertyList = union(propertyList, p, 'stable');
                    end
                    
                case internal.matlab.inspector.MultiplePropertyCombinationMode.INTERSECTION
                    propertyList = {};
                    for i = 1:length(objectList)
                        p = this.getPublicNonHiddenProps(...
                            objectList(i));
                        if isempty(propertyList)
                            propertyList = p;
                        else
                            propertyList = intersect(propertyList, p, ...
                                'stable');
                        end
                    end
                    
                case internal.matlab.inspector.MultiplePropertyCombinationMode.FIRST
                    propertyList = this.getPublicNonHiddenProps(...
                        objectList(1));
                    
                case internal.matlab.inspector.MultiplePropertyCombinationMode.LAST
                    propertyList = this.getPublicNonHiddenProps(...
                        objectList(end));
            end
        end
        
        function propertyAdded = addPropertyToProxy(this, propertyName)
            % Add a new property to the proxy. This is necessary when a new
            % property is dynamically added to the original object.
            propertyAdded = false;
            if ~isprop(this, propertyName)
                addprop(this, propertyName);
                propertyAdded = true;
            end
        end
    end
    
    methods (Static)
        % Returns the combined value for the current value and new value of
        % a property, based on the multipleValueCombinationMode parameter,
        % which can be all, blank, first or last.
        function value = getCombinedValue(currValue, newValue, ...
                multipleValueCombinationMode)
            switch(multipleValueCombinationMode)
                case internal.matlab.inspector.MultipleValueCombinationMode.ALL
                    % Combine property values into arrays for those
                    % properties which exist in multiple objects
                    if isempty(currValue) || isequal(currValue, newValue)
                        % If there's only one value, or if the new value is
                        % equal to the old value, just use it
                        value = newValue;
                    else
                        if ~isempty(currValue) && (ischar(currValue) || ...
                                ~isscalar(currValue))
                            if iscell(currValue)
                                currValue{end+1} = newValue;
                                value = currValue;
                            elseif isnumeric(currValue)
                                value = [currValue newValue];
                            else
                                value = {currValue newValue};
                            end
                        else
                            value = [currValue newValue];
                        end
                    end
                case internal.matlab.inspector.MultipleValueCombinationMode.BLANK
                    if isempty(currValue) || isequal(currValue, newValue)
                        % If there's only one value, or if the new value is
                        % equal to the old value, just keep it
                        value = newValue;
                    else
                        % Otherwise, the value should be blank (empty)
                        value = [];
                    end
                    
                case internal.matlab.inspector.MultipleValueCombinationMode.FIRST
                    % Value should always be the first value found for the
                    % property
                    value = currValue;
                    
                case internal.matlab.inspector.MultipleValueCombinationMode.LAST
                    % Value should come from the current object
                    % (overwriting the previous value)
                    value = newValue;
            end
        end
    end
end