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