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