www.gusucode.com > appdesigner工具箱matlab源码程序 > appdesigner/+appdesigner/+internal/+model/AppModel.m
classdef AppModel < ... appdesigner.internal.model.AbstractAppDesignerModel & ... appdesservices.internal.interfaces.model.ParentingModel % The "Model" class of the App % % This class is responsible for managing the state of the App and % holding onto the App Figure Window. % % % Copyright 2013-2016 The MathWorks, Inc. properties % A handle to the figure, by default this is empty UIFigure = matlab.ui.Figure.empty(); % Name of the AppModel, corresponds to the file name Name; % File location of the AppModel file FullFileName; CodeData; % Current running app, if value is empty, there is no running App RunningApp; % When App is in the process of launching, IsLaunching will be true % This is true from when the user initates the run to when the % UIFigure frame appears IsLaunching % App's debugging state. It will be true if the App's full filename % is found anywhere in the debug call stack. IsDebugging = false; end properties(Access = 'private') % appdesigner.internal.model.AppDesignerModel % % This AppDesignerModel is the owner of this AppModel % % This property is analogous to 'Parent' AppDesignerModel; % Store the running app's UIFigure CodeName. If the user changes % the UIFigure CodeName while the app is running, we will close % the running app when the user saves the app since the MCOS object % of the UIFigure is no longer valid RunningUIFigureCodeName; % Loaded component data for the app CodeNameComponentMap = containers.Map(); end methods function obj = AppModel(appDesignerModel, proxyView, appData) % Constructor for AppModel % % Inputs: % % appDesignerModel - The appdesigner.internal.model.AppDesignerModel % that will edit this App Model % Error Checking narginchk(2, 3); validateattributes(appDesignerModel, ... {'appdesigner.internal.model.AppDesignerModel'}, ... {}); validateattributes(proxyView, ... {'appdesservices.internal.peermodel.PeerNodeProxyView'}, ... {}); % Store the AppDesignerModel and ProxyView obj.AppDesignerModel = appDesignerModel; % set the model's name properties fullFileName = proxyView.getProperty('FullFileName'); if ~isempty(fullFileName) obj.FullFileName = fullFileName; [~, obj.Name, ~] = fileparts(fullFileName); else obj.FullFileName = ''; obj.Name = proxyView.getProperty('Name'); end % Store loaded app data for later using during creating % component models % Do this before controller creation because it will call % processClientCreatedPeerNode() from % DesignTimeParentingController's processProxyView(), which % will try to create children objects if nargin == 3 && ~isempty(appData) obj.storeLoadedAppData(appData); end % create the controller obj.createController(obj.AppDesignerModel.Controller, proxyView); % add this model as a child obj.AppDesignerModel.addChild(obj); end function delete(obj) % Delete figure if deleting AppModel instance % directly from server side, which happens in test or % development workflow % In App Designer, it will be deleted through the % AppController if ~isempty(obj.UIFigure) && isvalid(obj.UIFigure) delete(obj.UIFigure); end end function set.UIFigure(obj, newUIFigure) % Error Checking validateattributes(newUIFigure, ... {'matlab.ui.Figure'}, ... {}); % Storage obj.UIFigure = newUIFigure; end function set.CodeData(obj, codeData) validateattributes(codeData, ... {'appdesigner.internal.codegeneration.model.CodeData'}, ... {}); % Storage obj.CodeData = codeData; end function set.Name(obj, newName) if ~isvarname(newName) error(message('MATLAB:appdesigner:appdesigner:FileNameFailsIsVarName', newName)); else obj.Name = newName; markPropertiesDirty(obj, 'Name'); end end function set.IsLaunching(obj, status) obj.IsLaunching = status; markPropertiesDirty(obj, 'IsLaunching'); end function set.IsDebugging(obj, status) obj.IsDebugging = status; markPropertiesDirty(obj, 'IsDebugging'); end function set.FullFileName(obj, newFileName) obj.FullFileName = newFileName; markPropertiesDirty(obj, 'FullFileName'); end function adapterClassName = getAdapterClassName(obj, adapterType) % return the adapter class name for the given adapter type adapterMap = obj.AppDesignerModel.ComponentAdapterMap; if ( isKey(adapterMap,adapterType) ) adapterClassName = adapterMap(adapterType); else % if the adapter is not found then return [] adapterClassName = []; end end function adapterMap = getAdapterMap(obj) % return the adapter map adapterMap = obj.AppDesignerModel.ComponentAdapterMap; end function save(obj, fileName) % SAVE - fileName is the full file name with path information % where the item is to be saved. if nargin == 1 fileName = obj.FullFileName; end [~, appName] = fileparts(fileName); % If the app is running and either the UIFigure CodeName % changed or the current app code contains a parsing error, % close the running app. % % A parsing error will prevent the MCOS auto-update to occur % and so if the user tries to interact with the app such as % executing a callback, it will not work properly and no live % error alert will display because the app is broken (g1249971). % Closing the running app will prevent the user from % interacting with a broken app. if ~isempty(obj.RunningApp) uiFigureCodeName = obj.getUIFigureCodeName(); % Determine if the code has a parsing error T = mtree(strjoin(obj.CodeData.GeneratedCode, '\n')); if ~strcmp(uiFigureCodeName, obj.RunningUIFigureCodeName) || ... (T.count == 1 && strcmp(T.kind(), 'ERR')) % Need to try/catch the delete of the running app in % case there is a syntax error on a previous save that % was not detected by mtree (see g1290751). try obj.RunningApp.delete(); obj.RunningApp = []; catch exception % Because the app can not be closed, report the % error as a live error alert. appController = obj.getController(); appController.sendErrorAlertToClient(exception, fileName); end end end try % Let the codeModel update itself in response to a save % This is here to support the command line API obj.CodeData.GeneratedClassName = appName; % Write the AppModel to the filename [fullFileName] = obj.writeAppToFile(fileName); % Update model because writeAppToFile returned no errors obj.Name = appName; obj.FullFileName = fullFileName; catch me % Restore CodeModel state to the same as it was before the % save was attempted obj.CodeData.GeneratedClassName = obj.Name; rethrow(me); end end function [fullFileName] = writeAppToFile(obj, fileName) % WRITEAPPTOFILE - Validate inputs and write App to file [path, name, ext] = fileparts(fileName); if ~isvarname(name) error(message('MATLAB:appdesigner:appdesigner:FileNameFailsIsVarName', name)); end if isempty(path) % The case of saving to the current directory path = cd; end % Check if directory exists [success, dirAttrib] = fileattrib(path); % Directory should exist if ~success error(message('MATLAB:appdesigner:appdesigner:NotWritableLocation', fileName)) end % Reassemble fullFileName in case the path has changed. fullFileName = fullfile(path, [name, ext]); if dirAttrib.directory && (numel(path) < numel(dirAttrib.Name)) % if path was a relative path, path will not be the same as % the Name as returned by FILEATTRIB fullFileName = fullfile(dirAttrib.Name, [name, ext]); end % Get the metaData object to be serialized appMetadata = obj.getGroupMetaData(); % create the AppObject to be serialized containing the figure, % codeData and metadata appData = appdesigner.internal.serialization.app.AppData(obj.UIFigure, obj.CodeData, appMetadata); % Write MATLAB code and AppDesigner specific data to file fileWriter = ... appdesigner.internal.serialization.FileWriter(fullFileName); % Write MATLAB code and AppDesigner specific data to % file codeForMlappFile = strjoin(appData.CodeData.GeneratedCode', '\n'); fileWriter.writeMATLABCodeText(codeForMlappFile); % update code data with the saved generated code obj.CodeData.AppFileCode = codeForMlappFile; % write the appData to the file fileWriter.writeAppDesignerData(appData); % Check if file was created if exist(fileWriter.FileName, 'file') ~= 2 error(message('MATLAB:appdesigner:appdesigner:SaveFailed', fileWriter.FileName)); end % Only re-sync the breakpoints when performing a save (the % current filename is the same as the target filename). On a % Save As, it is not necessary to re-sync the breakpoints and % it can cause breakpoints not to be cleared when the Save As % is used to overwrite an existing app (g1078401). if strcmp(obj.FullFileName, fileWriter.FileName) try % This try-catch is necessary because it is only required % if the client is up and running and the breakpoint % services are running. % Re-sync breakpoints upon save of file bpms = com.mathworks.mde.editor.plugins.matlab.MatlabBreakpointMessageService.getInstance(); bpms.synchronizeBreakpoints(java.io.File(fileWriter.FileName)) catch end end % Clear the class clear(name); end function runApp(obj, fullFileName) % Run the App as if by command line [~, appName, appExt] = fileparts(fullFileName); % Silently save the app if it no longer exists because the user % deleted or renamed it using the file system prior to running % the app from App Designer. if exist(fullFileName, 'file') ~= 2 try save(obj, fullFileName); catch % Saved failed and so display runtime error alert exception = MException(... message('MATLAB:appdesigner:appdesigner:RunFailedFileNotFound', [appName appExt], fullFileName)); appController = obj.getController(); appController.sendErrorAlertToClient(exception, fullFileName); obj.IsLaunching = false; return; end end funcHandle = @()appdesigner.internal.model.AppModel.runAppHelper(obj); % This is being used to defer the eval call to MATLAB until % after the fevals produced by the synchronization effort have % been complete. This bumps the eval that creates the App to % the bottom of the list. appdesigner.internal.serialization.defer(funcHandle); end function component = popComponent(obj, codeName) component = []; if ~isempty(obj.CodeNameComponentMap) && ... obj.CodeNameComponentMap.isKey(codeName) component = obj.CodeNameComponentMap(codeName); % After retrieving the component, remove it from the map % because it has been created successfully when loading the % app obj.CodeNameComponentMap.remove(codeName); end end function openPackageApp(obj, fullFileName) % Launch the Package App dialog for the specified .mlapp file. % Search for .prj project files in the same directory, and which % specify the .mlapp file as the Main File. If multiple such % .prj files are found, use the most recently modified. Launch % the Package App dialog with the most recent matching .prj file. % If no matching .prj file is found, open a new Package App % dialog, with some fields pre-populated from the .mlapp. [filePath, mlappFile, ext] = fileparts(fullFileName); aps = com.mathworks.toolbox.apps.services.AppsPackagingService; % Only call doesProjectContainMainFile() if that jar method has % reached this cluster. When updates reach all clusters, this % try block will be replaced with: % mostRecentPrjFullFileName = getMostRecentPackageProject(obj, fullFileName); try prjFullFileName = fullfile(filePath, [mlappFile '.prj']); % Make dummy call to see if API is available testForMethodExistence = aps.doesProjectContainMainFile(prjFullFileName, fullFileName); % If no UndefinedFunction exception, it is safe to use that API method mostRecentPrjFullFileName = getMostRecentPackageProject(obj, fullFileName); catch ex if strcmp(ex.identifier, 'MATLAB:UndefinedFunction') % Use fallback technique to get most recently modified .prj. mostRecentPrjFullFileName = getMostRecentPackageProjectUsingXML(obj, fullFileName); else % doesProjectContainMainFile() method is safe to call. % It may throw an exception other than UndefinedFunction, but let it. mostRecentPrjFullFileName = getMostRecentPackageProject(obj, fullFileName); end end if ~isempty(mostRecentPrjFullFileName) try matlab.apputil.create(mostRecentPrjFullFileName); % does not do dependency checking catch ex % Note: if .prj is in a read-only directory, user will % not get an error message until an edit is made to any % field in the Package App dialog. The error message % will be generated by the Package App dialog. if strcmp(ex.identifier, 'MATLAB:apputil:create:filenotfound') error(message('MATLAB:appdesigner:appdesigner:PackageAppPackageFileNotFound', fullFileName)); else % Unknown error using packaging API -> return generic PackageError error(message('MATLAB:appdesigner:appdesigner:PackageAppFailed', fullFileName)); end end else % No .prj file found for this .mlapp file % Create new .prj file % Note: due to packaging service restrictions, the % packaging tool will use App Name as .prj filename. % For now, this should be valid as a filename, but the % service will strip out illegal characters later (below). prjFilename = obj.UIFigure.Name; % If user has not changed the title of their app from % the default ('UI Figure') then use the .mlapp file % basename instead. This will show up in the "App Name" % field of the Package App dialog window. if strcmp(prjFilename, 'UI Figure') prjFilename = mlappFile; end originalPrjFileName = prjFilename; foundUniquePrjName = false; uniqueCounter = 0; while ~foundUniquePrjName try % .createAppsProject throws exception if filename conflict encountered key = aps.createAppsProject(filePath, prjFilename); % The packaging API will strip illegal characters out of .prj % filename, so API must be queried to determine what was used. actualPrjFullFileName = aps.getProjectFileLocation(key); foundUniquePrjName = true; catch ex if (strcmp(ex.identifier,'MATLAB:Java:GenericException') && ... isa(ex.ExceptionObject, 'com.mathworks.deployment.services.NameCollisionException')) % Name conflict with another .prj file. Append '_<int>' to name. % Loop until a unique filename is found (e.g. App1_3.prj'). uniqueCounter = uniqueCounter + 1; prjFilename = [originalPrjFileName '_' num2str(uniqueCounter)]; elseif (strcmp(ex.identifier,'MATLAB:Java:GenericException') && ... isa(ex.ExceptionObject, 'java.io.FileNotFoundException')) error(message('MATLAB:appdesigner:appdesigner:PackageAppPackageFileNotFound', fullFileName)); elseif (strcmp(ex.identifier,'MATLAB:Java:GenericException') && ... isa(ex.ExceptionObject, 'com.mathworks.deployment.services.ReadOnlyException')) error(message('MATLAB:appdesigner:appdesigner:PackageAppFolderNotWritable', fullFileName)); else % Unknown error using packaging API error(message('MATLAB:appdesigner:appdesigner:PackageAppFailed', fullFileName)); end end end % Save the .mlappinstall to the same directory as the .mlapp file. aps.setOutputFolder(key, filePath); % Specify the .mlapp as the main file aps.addMainFile(key, fullFileName); aps.closeProject(key); aps.openProjectInGUIandRunAnalysis(actualPrjFullFileName); % open dialog (starts dependency checking) end end end methods (Access = private) function uiFigureCodeName = getUIFigureCodeName(obj) uiFigureCodeName = obj.UIFigure.DesignTimeProperties.CodeName; end function mostRecentPrjFullFileName = getMostRecentPackageProject(obj, mlappFullFileName) % Find most recent .prj file in the same directory as the .mlapp file, % which has the Main File field set to the specified mlapp file. [filePath, mlappFile, ext] = fileparts(mlappFullFileName); % Find all .prj files in the same directory as the .mlapp file % Returns struct array with name and datenum (double) fields prjFiles = dir(fullfile(filePath, '*.prj')); aps = com.mathworks.toolbox.apps.services.AppsPackagingService; mostRecentPrjFullFileName = []; mostRecentPrjFileDatenum = 0; for file = prjFiles' if file.isdir continue; end try prjFullFileName = fullfile(filePath, file.name); if aps.doesProjectContainMainFile(prjFullFileName, mlappFullFileName) if file.datenum > mostRecentPrjFileDatenum mostRecentPrjFullFileName = fullfile(filePath, file.name); mostRecentPrjFileDatenum = file.datenum; end end catch ex % Unknown error using packaging API -> return generic PackageError error(message('MATLAB:appdesigner:appdesigner:PackageAppFailed', mlappFullFileName)); end end end % Temporary code to find most recent .prj file by directly reading % XML in each .prj file. % This code will be deleted when new jar with doesProjectContainMainFile() % is accepted in BaT and available. function mostRecentPrjFullFileName = getMostRecentPackageProjectUsingXML(obj, fullMlappFileName) % Find most recent of the .prj files that have their Main File % set to the mlapp file being packaged. [filePath, mlappFile, ext] = fileparts(fullMlappFileName); % Find all .prj files in the same directory as the .mlapp file % Returns struct array with name and datenum (double) fields prjFiles = dir(fullfile(filePath, '*.prj')); mostRecentPrjFullFileName = []; mostRecentPrjFileDatenum = 0; for file = prjFiles' if file.isdir continue; end prjFullFileName = fullfile(filePath, file.name); [filePath, fileNameSansExtension, prjExt] = fileparts(prjFullFileName); try % The XML in the .prj file contains e.g.: % <fileset.main> % <file>${PROJECT_ROOT}\App1.mlapp</file> % </fileset.main> % Extract Main File field from XML in .prj xDoc = xmlread(prjFullFileName); filesetMainElts = xDoc.getElementsByTagName('fileset.main'); if filesetMainElts.getLength == 0 continue; end mainFileElt = filesetMainElts.item(0).getElementsByTagName('file'); if mainFileElt.getLength == 0 continue; end mainFileName = char(mainFileElt.item(0).getFirstChild.getData); if isempty(mainFileName) continue; end % Strip off path component from main file name if ispc slashes = strfind(mainFileName, '\'); else slashes = strfind(mainFileName, '/'); end if ~isempty(slashes) idx = slashes(end) + 1; mainFileName = mainFileName(idx:end); end % See if the Main File for this .prj matches the .mlapp % file name for the app being packaged. If so, save % the .prj file name if it is the most recently % modified .prj file found so far. if strcmp(mainFileName, [mlappFile, ext]) if file.datenum > mostRecentPrjFileDatenum mostRecentPrjFullFileName = fullfile(filePath, file.name); mostRecentPrjFileDatenum = file.datenum; end end catch ex % Unknown error using packaging API -> return generic PackageError error(message('MATLAB:appdesigner:appdesigner:PackageAppFailed', fullMlappFileName)); end end end end methods (Static) function runAppHelper(currentAppModel) appController = currentAppModel.getController(); appFullFileName = currentAppModel.FullFileName; % Guarentee IsLaunching is set to false and restoring current % folder isLaunchingCleanup = onCleanup(@()set(currentAppModel, 'IsLaunching', false)); % Delete existing instance of the current app. % This IF statement has been moved from the runApp method to % this defered method to resolve an issue with the Run button % being disabled when the code has a syntax error and the % RunningApp instance is deleted (see g1098581). if(~isempty(currentAppModel.RunningApp)) try % The deletion of the previously running app could % throw an exception if the running app's code was % updated and fails when parsed. currentAppModel.RunningApp.delete(); currentAppModel.RunningApp = []; catch % Allow the exception to pass through because it will % also fail when we attempt to eval the app in the code % below. Reporting the eval failure is more relevant % and useful than reporting the delete failure. end end % Save the current UIFigure CodeName for next saving % to decide if needed to close the running app or not by % comparing the old and new CodeName currentAppModel.RunningUIFigureCodeName = currentAppModel.getUIFigureCodeName(); % Listen for run time errors that occur in the running % app's callbacks appController.addErrorAlertListener(currentAppModel); try % Store newly generated app in the obj.RunningApp. This % line could throw an exception if the app's code fails % in parsing or an error occurs in the app's constructor. currentAppModel.RunningApp = ... appdesigner.internal.service.AppManagementService.instance().runApp(appFullFileName); catch exception if isa(exception, 'appdesigner.internal.appalert.CallbackException') % Exception in startup function from app's constructor, % but app already created currentAppModel.RunningApp = exception.App; end rethrow(exception); end end end methods(Access = 'private') function metaData = getGroupMetaData(obj) groupMetaData = {}; % This group meta data is neede on Save only % Get group manager peer node groupManager = []; peerNodes = obj.Controller.ProxyView.PeerNode.getChildren().toArray(); for index = 1:numel(peerNodes) if strcmp('GroupsManager', char(peerNodes(index).getType())) groupManager = peerNodes(index); break; end end if ~isempty(groupManager) % There are groups in the app groups = groupManager.getChildren().toArray(); for index = 1:numel(groups) group = groups(index); groupMetaData{end+1} = struct('Id', char(group.getId()), ... 'ParentGroupId', char(group.getProperty('GroupId'))); end end % create the object that will be serialized metaData = appdesigner.internal.serialization.app.AppMetadata(groupMetaData); end function storeLoadedAppData(obj, appData) % Store the loaded app data % Flat all components to store as a map with CodeName as key % for quick retrieving when creating design time comopnent % models childrenList = appdesigner.internal.application.getDescendants(appData.UIFigure); for ix = 1:numel(childrenList) codeName = childrenList(ix).DesignTimeProperties.CodeName; obj.CodeNameComponentMap(codeName) = childrenList(ix); end end end methods(Access = 'public') function controller = createController(obj, parentController, proxyView) % Creates the controller for this Model controller = appdesigner.internal.controller.AppController(obj, parentController, proxyView); end end end