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

    classdef PeerInspectorViewModel < ...
        internal.matlab.variableeditor.peer.PeerObjectViewModel & ...
        internal.matlab.inspector.InspectorViewModel
    
    % This class is unsupported and might change or be removed without
    % notice in a future version.
    
    % PeerViewModel for the Inspector.
    
    % Copyright 2015 The MathWorks, Inc.
    
    properties(Access = private)
        propertyChangedListener = [];
        propertyRemovedListener = [];
        propertyAddedListener = [];
        
        objectViewMap;
    end
    
    methods
        % Constructor - creates a new PeerInspectorViewModel for the given
        % parentNode and variable
        function this = PeerInspectorViewModel(parentNode, variable)
            this@internal.matlab.inspector.InspectorViewModel(...
                variable.DataModel);
            this = this@internal.matlab.variableeditor.peer.PeerObjectViewModel(...
                parentNode, variable);
            
            % Create listener for property changed events
            this.propertyChangedListener = event.listener(...
                variable.DataModel, 'PropertyChanged', ...
                @(es,ed) this.handlePropertyChanged(es, ed));
            this.propertyRemovedListener = event.listener(...
                variable.DataModel, 'PropertyRemoved', ...
                @(es,ed) this.handlePropertyRemoved(es, ed));
            this.propertyAddedListener = event.listener(...
                variable.DataModel, 'PropertyAdded', ...
                @(es,ed) this.handlePropertyAdded(es, ed));

            this.objectViewMap = containers.Map;
        end
        
        function [renderedData, renderedDims] = getRenderedData(this, ...
                startRow, endRow, startColumn, endColumn)
            % Overrides the getRenderedData so that the Group information
            % can be added.
            
            % Get the rendered data from the PeerObjectViewModel
            data = this.getRenderedData@internal.matlab.variableeditor.ObjectViewModel(...
                startRow, endRow, startColumn, endColumn);
            [propertySheetData, objectValueData, propsDims] = this.renderData(...
                data, startRow, endRow, startColumn, endColumn);            

            % Get the group data
            groupData = this.getRenderedGroupData();
            
            idx = 1;
            % Create a cell array of the appropriate size
            renderedData = cell(propsDims(1) + size(groupData, 1) + 2, 1);
            renderedData{idx,1} = sprintf('{\n\t"propertySheet":{\n\t\t"properties": [\n');
            idx = idx + 1;
            % Add in the properties from the propsData retrieved above
            for i = 1:propsDims(1)
                if (i>1)
                    renderedData{idx-1,1} = [renderedData{idx-1,1} ','];
                end
                renderedData{idx,1} = propertySheetData{i};
                idx = idx + 1;
            end
            
            if ~isempty(groupData)
                % Add in the groups, if there are any defined
                renderedData{idx,1} = sprintf('\t\t],\n\t\t"groups": [\n');
                idx = idx+1;
                for j = 1:size(groupData, 1)
                    if (j>1)
                        renderedData{idx-1,1} = [renderedData{idx-1,1} ','];
                    end
                    renderedData{idx,1} = groupData{j};
                    idx = idx + 1;
                end
            end
            
            renderedData{idx,1} = sprintf('\t\t]},\n');
            idx = idx + 1;

            renderedData{idx,1} = sprintf('\t"objects":[{\n');
            idx = idx + 1;
            for i = 1:propsDims(1)
                if (i>1)
                    renderedData{idx-1,1} = [renderedData{idx-1,1} ','];
                end
                renderedData{idx,1} = objectValueData{i};
                idx = idx + 1;
            end
            renderedData{idx,1} = sprintf('\t\t}]}\n');
            

            % Set the dimensions based on the full set of renderedData
            renderedDims = size(renderedData);
        end
        
        function groupData = getRenderedGroupData(this)
            % Retrieve the group information defined for the object being
            % inspected
            rawData = this.getData();
            groups = rawData.getGroups();
            groupData = cell(size(groups, 2), 1);
            if ~isempty(groups)
                for groupRow = 1:length(groups)
                    % For each group, create the JSON data which includes
                    % the groupID, title, description, and the properties
                    % included in the group
                    group = groups(groupRow);
                    groupData{groupRow, 1} = ...
                        internal.matlab.variableeditor.peer.PeerUtils.toJSON(...
                        true, ...
                        struct('name', group.GroupID, ...
                        'displayName', group.Title, ...
                        'tooltip', group.Description, ...
                        'expanded', group.Expanded, ...
                        'properties', {group.PropertyList} ...
                        ));
                end
            end
        end

        function varargout = handleClientSetData(this, varargin)
            % Handles setData from the client and calls MCOS setData.  Also
            % fires a dataChangeStatus peerEvent.
            property = this.getStructValue(varargin{1}, 'property');
            value = this.getStructValue(varargin{1}, 'value');
            
            this.logDebug('PeerInspectorView', 'handleClientSetData', '', ...
                'property', property, 'value', value);
            
            % Retrieve current value for the property
            currentValue = this.DataModel.getData.getPropertyValue(property);
            
            varargout{1} = '';
            try
                [dataType, isEnumeration] = this.getClassType(property);

                % Check for empty value passed from user and replace with
                % valid "empty" value
                if isempty(value)
                    
                    value = this.getEmptyValueReplacement(property);
                    if ~ischar(value)
                        value = mat2str(value);
                    end
                else
                    % TODO: Code below does not test for expressions in
                    % terms of variables in the current workspace (e.g.
                    % "x(2)") and it allows expression in terms of local
                    % variables in this workspace. We need a better method
                    % for testing validity. LXE may provide this
                    % capability.
                    if ~ischar(value)
                        L = lasterror; %#ok<*LERR>
                        try
                            % If mat2str fails, it may be ok as the
                            % EditorConverter below may handle this
                            % class type
                            value = mat2str(value);
                        catch
                        end
                        lasterror(L);
                    end
                    
                    widgetRegistry = internal.matlab.variableeditor.peer.WidgetRegistry.getInstance;
                    widgets = widgetRegistry.getWidgets(class(this), dataType);
                    if isempty(widgets.EditorConverter) && isEnumeration
                        widgets = widgetRegistry.getWidgets(class(this), ...
                            'categorical');
                    end
                    if ~isempty(widgets.EditorConverter)
                        converter = eval(widgets.EditorConverter);
                        converter.setClientValue(value);
                        value = converter.getServerValue();
                        if ~ischar(value) || ~isequal(strfind(value, '{'), 1)
                            value = mat2str(value);
                        end
                    end
                    
                    % Test for a valid expression. (assume cell arrays will
                    % be valid)
                    if ~isequal(strfind(value, '{'), 1)
                        [result] = evalin(this.DataModel.Workspace, value);
                        if ~this.validateInput(property, result, currentValue)
                            [rowData, ~] = this.getRowDataForProperty(property);
                            
                            this.PeerNode.dispatchEvent(struct(...
                                'type', 'dataChangeStatus', ...
                                'source', 'server', ...
                                'property', property, ...
                                'oldValue', rowData, ...
                                'status', 'error', ...
                                'message', getString(message(...
                                'MATLAB:codetools:variableeditor:InvalidInputType'))));
                            this.sendErrorMessage(...
                                getString(message('MATLAB:codetools:variableeditor:InvalidInputType')));
                            return;
                        end
                    end
                end
                
                % Send data change event for equal data
                eValue = evalin(this.DataModel.Workspace, value);
                if ~ischar(eValue)
                    if ~ischar(this.DataModel.Workspace) && ...
                            ismethod(this.DataModel.Workspace, 'disp')
                        try
                            dispValue = this.DataModel.Workspace.disp(value);
                        catch
                            dispValue = strtrim(evalc(...
                                'evalin(this.DataModel.Workspace, [''disp('' value '')''])'));
                        end
                    else
                        dispValue = strtrim(evalc(...
                            'evalin(this.DataModel.Workspace, [''disp('' value '')''])'));
                    end
                else
                    containsBreaks = strfind(eValue, sprintf('\n'));
                    if strcmp(eValue, strtrim(matlab.internal.display.getHeader({})))
                        % handle empty cell arrays
                        eValue = {};
                    elseif ~isempty(containsBreaks) || any(strfind(value, '{') == 1)
                        % This is a multi-line value, and should be treated
                        % as a cell array.  value will be something like:
                        % {'a';'b';'c'}, cellContents will be a 1x1 cell
                        % array, containing a 3x1 cell array like: {''a'';
                        % ''b'';''c''}, so just need to remove the extra
                        % quotes.
                        cellContents = textscan(value(2:end-1), '%q', ...
                            'Delimiter', ';', 'MultipleDelimsAsOne', true);
                        eValue = strrep(cellContents{1}, '''', '');
                    end
                    dispValue = value;
                end
                
                if isequaln(eValue, currentValue)
                    [rowData, ~] = this.getRowDataForProperty(property);
                    this.PeerNode.dispatchEvent(struct(...
                        'type', 'dataChangeStatus', ...
                        'source', 'server', ...
                        'status', 'noChange', ...
                        'oldValue', rowData, ...
                        'property', property));
                    
                    % Even though the data has not changed we will fire a
                    % data changed event to take care of the case that the
                    % user has typed in a value that was to be evaluated in
                    % order to clear the expression and replace it with the
                    % value (e.g. pi with 3.1416)
                    eventdata = internal.matlab.variableeditor.DataChangeEventData;
                    eventdata.Range = [];
                    eventdata.Values = value;
                    this.notify('DataChange',eventdata);
                end
                varargout{1} = '';
                if isEnumeration && ischar(eValue)
                    eValue = strrep(eValue, '''', '');
                    L = lasterror; %#ok<*LERR>
                    try
                        % Try to convert to actual enumeration if possible,
                        % but if not, just use the string representation
                        eValue = eval([dataType '.' eValue]);
                    catch
                    end
                    lasterror(L);
                end
                
                if isnumeric(eValue)
                    dispValue = ...
                        internal.matlab.variableeditor.peer.PeerStructureViewModel.getDisplayEditValue(...
                        eValue);
                end
                this.DataModel.getData.setPropertyValue(...
                    property, eValue, dispValue, this.DataModel.Name);
                
                this.PeerNode.dispatchEvent(struct(...
                    'type', 'dataChangeStatus', ...
                    'source', 'server', ...
                    'status', 'success', ...
                    'dispValue', dispValue, ...
                    'property', property));
            catch e
                % Send a status change and error message for the failure
                [rowData, ~] = this.getRowDataForProperty(property);

                this.PeerNode.dispatchEvent(struct(...
                    'type', 'dataChangeStatus', ...
                    'source', 'server', ...
                    'status', 'error', ...
                    'property', property, ...
                    'oldValue', rowData, ...
                    'message', e.message));
                this.sendErrorMessage(e.message);
            end
        end
        
        function delete(this)
            if ~isempty(this.propertyChangedListener)
                delete(this.propertyChangedListener);
            end
            
            if ~isempty(this.propertyAddedListener)
                delete(this.propertyAddedListener);
            end
            
            if ~isempty(this.propertyRemovedListener)
                delete(this.propertyRemovedListener);
            end
        end
        
        function handlePropertyChanged(this, ~, ed)
            % Only one property changed at a time
            propertyName = ed.Properties;
            
            [rowData, editorProps] = this.getRowDataForProperty(propertyName);
            
            % Make sure the row for the property is found
            if ~isempty(rowData)               
                eventData = struct(...
                    'source', 'server', ...
                    'type', 'propertyChanged', ...
                    'property', propertyName, ...
                    'value', rowData);
                
                if ~isempty(editorProps)
                    % Add in any editor properties as well.  This could
                    % include, for example, the categories for a
                    % categorical variable.
                    eventData.state = editorProps;
                end
                this.PeerNode.dispatchEvent(eventData);
            end
        end
        
        function handlePropertyAdded(this, ~, ed)
            this.PeerNode.dispatchEvent(struct(...
                'type', 'propertyAdded', ...
                'property', ed.Properties));
        end
        
        function handlePropertyRemoved(this, ~, ed)
            this.PeerNode.dispatchEvent(struct(...
                'type', 'propertyRemoved', ...
                'property', ed.Properties));
        end
    end
    
    methods (Access = protected)
        function setupPagedDataHandler(~, ~)
            % This isn't used by the Property Inspector
        end

        function [propertySheetData, objectValueData, renderedDims] = renderData(this, data, ...
                startRow, endRow, startColumn, endColumn)
            % Creates the rendered data specific to the Inspector
            
            % Get an instance of the WidgetRegistry, and cache some of the
            % commonly used widget sets
            widgetRegistry = internal.matlab.variableeditor.peer.WidgetRegistry.getInstance;
            objectWidgets = widgetRegistry.getWidgets(class(this), ...
                'object');
            charWidgets = widgetRegistry.getWidgets(class(this), ...
                'char');
            categoricalWidgets = widgetRegistry.getWidgets(class(this), ...
                'categorical');
            
            % Setup the start/end rows/columns
            if isempty(startRow)
                this.StartRow = 1;
            else
                this.StartRow = startRow;
            end
            if isempty(endRow)
                this.EndRow = 1;
            else
                this.EndRow = endRow;
            end
            this.StartColumn = startColumn;
            this.EndColumn = endColumn;
            rawData = this.getData();
            fieldNames = fieldnames(rawData);
            propertySheetData = cell(size(data,1), 1);
            objectValueData = cell(size(data,1), 1);
            
            if ~this.SortAscending
                fieldNames = fieldNames(end:-1:1);
            end
            
            % For each of the rows of rendered data, create the json object
            % string for each row's data.
            for row=1:size(propertySheetData, 1)
                varName = data{row,1};
                varValue = data{row,2};
                dataValue = this.getFieldData(rawData, ...
                    fieldNames{row+startRow-1});
                
                % Find the metaclass property data, so it can be used for
                % the description, detailed description, etc...
                prop = findprop(rawData, varName);
                                                
                % The type may have been defined with the property, for
                % example propName@logical
                if isKey(rawData.PropertyTypeMap, prop.Name)
                    propType = rawData.PropertyTypeMap(prop.Name);
                else
                    propType = class(rawData.(prop.Name));
                end

                [isCatOrEnum, dataType] = ...
                    internal.matlab.variableeditor.peer.editors.ComboBoxEditor.isCategoricalOrEnum(...
                    data{row,4}, propType, rawData.(prop.Name));
                inPlaceEditorProps = '';
                
                % Setup the widgets to be used.  First, check to see if
                % there is an editor in place for this data type
                widgets = widgetRegistry.getWidgets(class(this), dataType);
                if widgetRegistry.isUnknownView(widgets.Editor) || ...
                    strcmp(widgets.Editor, 'inspector/peer/InspectorViewModel')
                    % If there isn't one, try to get the editor based on
                    % the property's current data type
                    if isCatOrEnum
                        widgets = categoricalWidgets;
                    else
                        widgets = widgetRegistry.getWidgets(class(this), data{row,4});
                        if isempty(widgets.CellRenderer) && isobject(dataValue)
                            widgets = objectWidgets;
                        elseif isempty(widgets.CellRenderer)
                            widgets = charWidgets;
                        end
                    end
                end
                
                if ~isempty(widgets.EditorConverter)
                    % If a converter is set, use it to convert to the
                    % client value
                    converter = eval(widgets.EditorConverter);
                    converter.setServerValue(dataValue, propType);
                    varValue = converter.getClientValue();
                    
                    inPlaceEditorProps = converter.getEditorState;
                    if ~isempty(inPlaceEditorProps)
                        inPlaceEditorProps = ...
                            internal.matlab.variableeditor.peer.PeerUtils.toJSON(...
                            true, inPlaceEditorProps);
                    end
                end
                                
                % Get the editors to use for the field
                [cellEditor, cellInPlaceEditor] = this.getEditors(data, ...
                    row, widgets.Editor, widgets.InPlaceEditor);
                
                % Create the rendered data for the row
                objectValueData{row,1} = this.getObjectDataForProperty(...
                    varName, dataValue, varValue, data{row,4});
                
                hasSetAccess = true;
                if ~isempty(prop)
                    if ~strcmp(prop.SetAccess, 'public')
                        hasSetAccess = false;
                        
                        if isCatOrEnum
                            % If the property doesn't have setAccess, set
                            % it to not be an enumeration, so the client
                            % doesn't show a drop down menu for this
                            % property
                            widgets = charWidgets;
                        end
                    end
                end

                if ischar(this.DataModel.Workspace)
                    workspaceStr = this.DataModel.Workspace;
                else
                    workspaceStr = ['internal.matlab.inspector.peer.InspectorFactory.createInspector(''' ...
                        this.DataModel.Workspace.Application ''','''...
                        this.DataModel.Workspace.Channel ''')'];
                end
                
                % Create the property sheet data for the property, which
                % includes the display name, tooltip, and renderers.
                % (Specifying dataType as 'char' just effects the
                % justification - so the Property Inspector shows
                % everything left justified)
                propertySheetData{row,1} = ...
                    internal.matlab.variableeditor.peer.PeerUtils.toJSON(true, ...
                    struct('name', varName, ...  % Property Name
                    'displayName', this.getDisplayName(prop), ...
                    'tooltip', prop.DetailedDescription, ...
                    'dataType', 'char', ...
                    'className', dataType, ...
                    'renderer', widgets.CellRenderer,...
                    'inPlaceEditor', cellInPlaceEditor,...
                    'editor', cellEditor,...
                    'editable', hasSetAccess && ~isempty(cellInPlaceEditor),...
                    'workspace',workspaceStr...
                    ));
                if (~isempty(inPlaceEditorProps))
                    s = propertySheetData{row,1};
                    s(end) = ',';
                    s = [s '"inPlaceEditorProperties":' inPlaceEditorProps '}']; %#ok<AGROW>
                    propertySheetData{row,1} = s;
                end
            end
            
            renderedDims = size(propertySheetData);
        end
        
        function objectData = getObjectDataForProperty(this, propertyName, ...
                dataValue, varValue, classType)
            
            % Get the display value for the object
            [valueSummary, ~, metaData] = this.formatDataBlockForMixedView(1,1,1,1,{dataValue});
            editValue = varValue;
            
            if ischar(this.DataModel.Workspace)
                workspaceStr = this.DataModel.Workspace;
            else
                workspaceStr = ['internal.matlab.inspector.peer.InspectorFactory.createInspector(''' ...
                    this.DataModel.Workspace.Application ''','''...
                    this.DataModel.Workspace.Channel ''')'];
            end
            % If we have a numeric value, that isn't a value summary
            % create the full-precision representation of it.
            if isnumeric(dataValue)
                editValue = this.getEditValue(dataValue);
                if isscalar(dataValue)
                    varValue = num2str(varValue);
                    if length(editValue) > length(varValue)
                        varValue = [varValue '...'];
                    end
                end
            elseif strcmp(classType, 'logical') && isscalar(dataValue)
                % workspacefunc return a Java logical in the case of
                % scalar logicals so we need to correct for that here
                if strcmp(varValue, 'true') || strcmp(varValue, '1')
                    varValue = '1';
                else
                    varValue = '0';
                end
                editValue = varValue;
            elseif strcmp(classType, 'cell')
                editValue = this.getEditValue(dataValue);
            elseif strcmp(classType, 'categorical') && ~isscalar(dataValue)
                varValue = char(valueSummary);
                editValue = this.getEditValue(dataValue);
            elseif isnumeric(dataValue) && ~isscalar(dataValue)
                editValue = this.getEditValue(dataValue);
            end
            objectData = ['"' propertyName '": '...
                internal.matlab.variableeditor.peer.PeerUtils.toJSON(true, ...
                struct('value', varValue,...
                'editValue', editValue,...
                'editorValue', this.getSubVarName(this.DataModel.Name, propertyName),...
                'workspace', workspaceStr, ...
                'isMetaData', metaData ...
                ))];
            
            % Also save in a map for reuse
            this.objectViewMap(propertyName) = objectData;
        end
        
        function displayName = getDisplayName(~, prop)
            % Return the display name for the given property.  This will be
            % the Description of the property, if it is set, otherwise it
            % will be the property name.
            if isempty(prop.Description)
                displayName = prop.Name;
            else
                displayName = internal.matlab.inspector.Utils.getPossibleMessageCatalogString(prop.Description);
            end
        end
        
        function replacementValue = getEmptyValueReplacement(this, propName)
            % Called to return the replacement value for empties when
            % setting a new value
            [dataType,isEnumeration] = this.getClassType(propName);
            
            if internal.matlab.variableeditor.peer.PeerUtils.isNumericType(dataType)
                % For numerics, its 0
                replacementValue = '0';
            elseif isEnumeration
                % Return the original value for an enumeration
                replacementValue = this.DataModel.getData.getPropertyValue(propName);
            else
                switch dataType
                    case 'logical'
                        replacementValue = '0';
                    otherwise
                        % Default to empty for other cases
                        replacementValue = '[]';
                end
            end
        end
        
        function [dataType,isEnumeration] = getClassType(this, propName)
            % Called to get the class type for the property name, and if
            % it is an enumeration or categorical.
            rawData = this.DataModel.getData();
            dataType = 'any';
            isEnumeration = false;

            if isKey(rawData.PropertyTypeMap, propName)
                % The type may have been defined with the property, for
                % example propName@logical
                propType = rawData.PropertyTypeMap(propName);
                
                % The type will either be a meta.type object, or it could
                % be just a class name, depending on if the metaclass
                % object had data for the property or not
                if isa(propType, 'meta.type')
                    if ~strcmp(propType.Name, 'any')
                        % Use the type as defined
                        dataType = propType.Name;
                    end
                    
                    if isa(propType, 'meta.EnumeratedType')
                        % If its a meta.EnumeratedType, then its an
                        % enumeration
                        isEnumeration = true;
                    elseif iscategorical(rawData.(propName))
                        % The property may not be typed, but if the current
                        % value is categorical, then treat it as a
                        % categorical variable
                        isEnumeration = true;
                    elseif isobject(rawData.(propName))
                        % But it can also be a user-defined MCOS
                        % enumeration, in which case the call to
                        % enumeration() will return the valid values
                        [~, values] = enumeration(rawData.(propName));
                        isEnumeration = ~isempty(values);
                    end
                else
                    dataType = propType;
                end
            end
            
            % Treat categoricals as enumerations as well
            isEnumeration = isEnumeration || ismember(dataType, ...
                {'categorical', 'nominal', 'ordinal'});
            
            if isEnumeration
                prop = findprop(rawData, propName);
                if ~isempty(prop)
                    % If the property doesn't have setAccess, set it to not
                    % be an enumeration, so the client doesn't show a drop
                    % down menu for this property
                    isEnumeration = strcmp(prop.SetAccess, 'public');
                end
            end
        end
        
        function isValid = validateInput(this, propName, value, ...
                currentValue)
            % Called to see if the value is valid for the property propName
            [dataType,isEnumeration] = this.getClassType(propName);
            isValid = true;
            
            if internal.matlab.variableeditor.peer.PeerUtils.isNumericType(...
                    dataType)
                % If its numeric, just verify the new value is also numeric
                isValid = isnumeric(value);
            elseif isEnumeration
                % Check enumeration values
                propType = this.DataModel.getData.PropertyTypeMap(propName);
                if isa(propType, 'meta.EnumeratedType') && ischar(value)
                    % If the possible values is set, make sure that the new
                    % value is one of them
                    isValid = isempty(propType.PossibleValues) || ...
                        ismember(strrep(value, '''', ''), ...
                        propType.PossibleValues);
                elseif isobject(currentValue)
                    % Otherwise, if its currently an object, check to see
                    % if its a user-defined enumeration.  If it is,
                    % enumeration() will return the valid values.
                    [~, enumValues] = enumeration(currentValue);
                    isValid = isempty(enumValues) || ...
                        ismember(strrep(value, '''', ''), enumValues);
                end
            else
                % Default to valid for other cases
                isValid = true;
            end            
        end
        
        function [rowData, editorProps] = getRowDataForProperty(this, propertyName)
            rowData = [];
            editorProps = [];
            fieldNames = fieldnames(this.DataModel.getData);
            
            if ~this.SortAscending
                fieldNames = fieldNames(end:-1:1);
            end
            row = find(strcmp(fieldNames, propertyName));

            % Make sure the row for the property is found
            if ~isempty(row)
                % Get the rendered data for just a single property
                renderedData = this.getRenderedData(row, row, 1, 1);
                value = renderedData{end-1};
                rowData = value(strfind(value, '{'):end);
                
                editorPropsIdx = strfind(renderedData{2}, ...
                    'inPlaceEditorProperties');
                if ~isempty(editorPropsIdx)
                    % Add in any editor properties as well.  This could
                    % include, for example, the categories for a
                    % categorical variable.
                    editorProps = renderedData{2}(editorPropsIdx:end-1);
                    editorProps = editorProps(strfind(editorProps, '{'):end);
                end
            end
        end
    end
end