www.gusucode.com > mbctools 工具箱 matlab 源码程序 > mbctools/+xregdatagui/Editor.m

    classdef Editor < mbcgui.Application
    %Editor Data Editor application object.
    %
    %   XREGDATAGUI.Editor creates an Application object for the Data
    %   Editor.  In addition to the standard Application properties, this adds
    %   properties, methods and events that are specific to the Data Editor.
    
    %   Copyright 2008-2015 The MathWorks, Inc.
    
    properties(SetAccess=private)
        %MessageService data editor message service
        MessageService
        %MultiView multiview panel for plots and tables
        MultiView
        %Toolbar figure toolbar
        Toolbar
        %FileMenu main file menu
        FileMenu
        %ViewMenu main view menu
        ViewMenu
        %ToolsMenu main tools menu
        ToolsMenu
        %TestSnapPanel global test selector panel
        TestSnapPanel
        %TestSelector test number list for two-stage data selection
        TestSelector
        %AcceptDataAction action to process data for MBCMODEL
        %     AcceptDataAction closes the data editor
        AcceptDataAction
        %StorageActions import and export storage actions
        StorageActions
        %GenerateReportAction generate report for data editor
        GenerateReportAction
   end
    
    properties (Dependent)
        %NewData set and get data for data editor
        NewData
    end
    
    
    % Miscellaneous storage
    properties(SetAccess=private, GetAccess=private)
        StatusInfoListeners
        StatusInfoID = -1;
        TitleListeners
        PointerStack = cell(0,2);
    end
    
    events
        ViewsChanged
    end
    
    methods
        function obj = Editor(varargin)
        %Editor editor constructor
        obj@mbcgui.Application(...
            'Name', 'Data Editor',...
            'Visible','off',...
            varargin{:});
        obj.SleepOnClose = true;
        end
        
        function delete(obj)
        % Save the view layout
        if isgraphics(obj.Figure)
           delete(obj.Figure) 
        end
        end
        
        function newData = get.NewData(hData)
        newData = hData.MessageService.DataObject;
        % Ensure the new data is correctly unhooked from the editor
        newData = removeMessageService(newData);
        end
        
        function set.NewData(hData,ssf)
        
        % Ensure the object has it's data message service set
        ssf = addMessageService(ssf, hData.MessageService);
        % Set the new data object to a deep copy of the new data
        hData.MessageService.setDataObject(ssf);
        
        end
    end
    
    methods (Static)
        function obj = create()
        %create create or restore data editor
        Tag = 'dataEditor';
        obj = xregdatagui.Editor.find(Tag);
        if isempty(obj)
            % initial default size
            deSize =   [50 100 800 600];
            obj = xregdatagui.Editor('Tag',Tag,...
                'Position',deSize);
            obj.Figure.Visible = 'on';
        else
            restore(obj);
        end
        
        end
        
        function hData = open(pSSF,EditData,IS_TESTPLAN,fCloseEditor)
        %open open data editor with data
        %   hData = xregdatagui.Editor.open(pSSF,EditData,IS_TESTPLAN,fCloseEditor)
        %      pSSF pointer to sweepsetfilter
        %      EditData allow editing of data
        %      IS_TESTPLAN test plan data being edited 
        %      fCloseEditor callback to call on close
        
        if nargin<3
            IS_TESTPLAN = isa(info(pSSF),'testplansweepsetfilter');
        end
        if nargin<4
            fCloseEditor = [];
        end
        
        % Make a copy of the object
        ssf = pSSF.copy;
        
        % Some legacy issues with data
        if IS_TESTPLAN 
            if ~isempty( get(ssf,'reordersweeps') )
                % legacy issue as old tps used reordersweeps to define match
                ssf = reorderSweeps(ssf,Inf);
            end
            % Restore the object cache to speed up evaluation in the data editor
            ssf = restoreCachedInfo(ssf);
            [ssf,OK] = setupGroups(getTestplan(ssf),ssf);
            if ~OK
                hData = [];
                return
            end
        end
        % Turn on the object's internal cacheing
        ssf = setCacheState(ssf, true);
        % setup groups if necessary before going into the data editor

        % Open data edit facility
        hData = xregdatagui.Editor.create();
        
        hData.NewData = ssf;
        % Set the read-only status of the data
        hData.MessageService.isReadOnly = ~EditData;
        
        % restore data editor layout from data if available
        displayLayout = getDisplayLayout(ssf);
        if ~isempty(displayLayout)
            replaceDisplayLayout(hData,displayLayout);
        end
        %make modifications to cluster views if required
        setupClusterViews(hData)
        
        % Set the userdata of the figure
        S.ObjectBeingEdited = pSSF;
        if ~isempty(fCloseEditor)
            % setup user-defined close callback
            S.Close = event.listener(hData, 'Close', fCloseEditor);
        end
        %close down activities, including saving layout 
        S.PostClose = event.listener(hData, 'PostClose', @onPostClose);
        % store close listeners
        hData.UserData = S;
        
        
        end
        
        function s = convertStorageToStruct(ViewInfo,vl)
        %convertStorageToStruct convert legacy cell array storage to structure
        %   s = xregdatagui.Editor.convertStorageToStruct(ViewInfo,vl)
        
        if nargin<2
            app = xregdatagui.Editor.find('dataEditor');
            vl = app.MultiView.ViewList;
        end
        if strcmp(ViewInfo{1},'xregsplitlayout')
            s.Type = 'split';
            s.Left = xregdatagui.Editor.convertStorageToStruct(ViewInfo{3},vl);
            s.Right = xregdatagui.Editor.convertStorageToStruct(ViewInfo{5},vl);
            s.Orientation = ViewInfo{7};
            s.Split = ViewInfo{9};
            %collapse views (list views are now invalid)
            if isempty(s.Left)
                s = s.Right;
            elseif isempty(s.Right)
                s = s.Left;
            end
            
        else
            % individual views
            Index = vl.findViewClass(ViewInfo{1});
            if ~isempty(Index)
                s.Type = 'view';
                s.ViewLabel = vl.Labels{Index};
                s.ViewClass = func2str(vl.ConstructorFcns{Index});
                s.ViewData = ViewInfo(2:end);
            else
                % view no longer valid (list views)
                s = [];
            end
        end
        end

        
    end
    
    % mbcgui.Application overrides
    methods(Access=protected)
        function C = createContent(obj)
        %CREATECONTENT Create content layout.
        %
        %   C = CREATECONTENT(OBJ) constructs the basic content layout for the Data
        %   Editor.
        
        ms = xregdatagui.MessageService;
        obj.MessageService = ms;
        
        obj.Listeners = [obj.Listeners(:); event.listener(ms,'Busy',@obj.onBusy)
            event.listener(ms,'Idle',@obj.onIdle)
            event.listener(ms,'ssDataChanged',@obj.onTestsChanged)
            event.listener(ms,'ssTestsChanged',@obj.onTestsChanged)];
        
        hFig = obj.Figure;
        
        % Create the menuview object to generate and maintain the menus and toolbar
        % Top level menus
        obj.FileMenu = uimenu('Parent', hFig, 'Label', '&File');
        obj.ViewMenu = uimenu('Parent', hFig, 'Label', '&View');
        obj.ToolsMenu = uimenu('Parent', hFig, 'Label', '&Tools');
        % Add a window and help menu
        xregwinlist(hFig);
        mv_helpmenu(hFig, {'&Data Editor Help', 'xreg_dataEditor'});
        
        toolbarPanel = mbcgui.container.layoutpanel(...
            'Parent', obj.Figure, ...
            'BorderType', 'beveledin');
        % Create the toolbar
        toolbar = xregGui.uitoolbar('Parent', toolbarPanel,...
            'resourcelocation', xregrespath);
        
        obj.Toolbar = toolbar;
        set(toolbarPanel, 'LayoutComponent', toolbar);
        
        % Link the visible components together
        C = xreggridbaglayout(obj.Figure,...
            'dimension', [2 1],...
            'elements', {toolbarPanel []},...
            'rowsizes', [31 -1],...
            'gapy', 2);
        
        obj.setStatusProgress(0.25, 0, 1);
        msgID = obj.setStatusMessage('Creating information views...');
        
        % Create the specific data and testnumber views that will always be added
        % to the data editor
        dataInfoView   = xregdatagui.DataInfoView('Parent', hFig, ...
            'MessageService', obj.MessageService);
        obj.setStatusProgress(0.4);
        
        % test selector panel
        obj.TestSelector = xregdatagui.TestSelector('Parent', hFig, ...
            'MessageService', obj.MessageService);
        
        createMultiView(obj)
        
        % Create the layouts for the specific data views to sit in
        infoSnapPanel = xregsnapsplitlayout(hFig,...
            'packstatus', 'off', ...
            'barstyle',1,...
            'orientation', 'ud',...
            'style','totop',...
            'split', [0 1],...
            'minwidth', [150 100],...
            'top', dataInfoView,...
            'bottom', obj.MultiView);
        testSnapPanel = xregsnapsplitlayout(hFig,...
            'packstatus', 'off',...
            'barstyle',1,...
            'orientation', 'lr',...
            'style','toleft',...
            'split', [0 1],...
            'minwidth', [100 100],...
            'left', obj.TestSelector,...
            'right', infoSnapPanel);
        obj.setStatusMessage(msgID, 'Regenerating saved views...');
        obj.setStatusProgress(0.6);
        obj.TestSnapPanel = testSnapPanel;
        % Get the layout preferences
        obj.setStatusMessage(msgID, 'Redrawing...');
        obj.setStatusProgress(0.9);
        
        el = get(C, 'elements');
        el{2} = testSnapPanel;
        set(C, 'elements', el, 'packstatus', 'on');
        set(testSnapPanel, 'Visible', 'on');
        
        % How far through are we?
        obj.setStatusMessage(msgID, 'Completing...');
        obj.setStatusProgress(1);
        
        % Listen to changes in the size of the data info view to ensure it is
        % always 110 pixels high, except when it's snapped shut
        l = mbcgui.hgclassesutil.listener(hFig, 'SizeChanged', ...
            mbcutils.callback(@i_setInfoHeight, infoSnapPanel));
        set(C, 'UserData', l);
        
        % Install listeners to maintain the window title and statusbar information
        obj.createStatusInfo();
        obj.createTitle();
        
        createControls(obj)
        
        % Tell the user everything is ready
        obj.setStatusProgress(0);
        obj.setStatusMessage(msgID, 'Ready');
        
        end
        
        function sleep(obj)
        %SLEEP Take action when the window is made invisible
        %
        %  SLEEP(OBJ)
        %
        
        % Set the viewed data object to a null one to ensure the data editor
        % doesn't keep data copies alive
        obj.MessageService.setDataObject([]);
        end
        
    end
    
    methods
        function restore(obj)
        %RESTORE Make window visible again
        %   RESTORE(OBJ) will re-open an application that has been put into a
        %   "sleep" mode when closed.
        
        if strcmp(get(obj.Figure, 'Visible'), 'on')
            % Fake a close event before restoring the application
            obj.close(true);
        end
        
        obj.restore@mbcgui.Application();
        end
    end
    
    % Construction helper methods and callbacks
    methods(Access=private)
        function createControls(obj)
        %createControls create data editor controls
        
        ms = obj.MessageService;
        
        obj.StorageActions = mbcgui.actions.ActionGroup;
        obj.StorageActions.MenuType = 'separate';
        obj.StorageActions.Actions = [mbcgui.actions.StatefulAction(@obj.onStorage,...
            '&Import Expressions...','Import variables, filters and editor layout',xregrespath('storageIcon.bmp'));
            mbcgui.actions.StatefulAction(@obj.onExportStorage,...
            'Export Expressions...','Export variables, filters and editor layout',xregrespath('exportStorage.bmp'))];
        
        f = obj.Figure;
        obj.AcceptDataAction= mbcgui.actions.StatefulAction(@(h,evt) close(f),...
            '&Save && Close','Save data and close data editor',xregrespath('Confirm_16.bmp'));
        hFig = obj.Figure;
        % process data on closing figure
        hFig.CloseRequestFcn = @(h,evt) obj.AcceptDataAction.execute();
        
        % file menu
        createFileMenu(ms.Actions,obj.FileMenu);
        
        % create report generation if present
        doReport = license('test', 'MATLAB_Report_Gen') && ms.HasExtras && false;
        if doReport
            obj.GenerateReportAction= mbcgui.actions.StatefulAction(@obj.onGenerateReport,...
                'Generate &Report...','Generate report',xregrespath('Report.bmp'));
            hReport = createMenuItem(obj.GenerateReportAction,obj.FileMenu);
            hReport.Separator = 'on';
        end
        hClose = createMenuItem(obj.AcceptDataAction,obj.FileMenu);
        hClose.Separator = 'on';
            
        % tools menu
        createToolsMenu(ms.Actions,obj.ToolsMenu);

        createMenuItem(obj.StorageActions,obj.ToolsMenu);
        
        % create toolbar items
        
        obj.Toolbar.setRedraw(false);
        createToolbar(ms.Actions,obj.Toolbar);
        createToolbutton(obj.StorageActions,obj.Toolbar);
        
        % create multiview toolbar
        AG = mbcgui.actions.ActionGroup('','Toggle');
        AG.MenuType = 'separate';
        AG.Actions = obj.MultiView.Actions.ChangeView;
        btns = createToolbutton(AG,obj.Toolbar);
        set(btns(1),'Separator','on');
        
        if doReport
            % report generator toolbar
            tbReport = createToolbutton(obj.GenerateReportAction,obj.Toolbar);
            tbReport.Separator = 'on';
        end
        tbExport = createToolbutton(obj.MessageService.Actions.ExportWorkspace,obj.Toolbar);
        if ~doReport
            tbExport.Separator = 'on';
        end
        createToolbutton(obj.AcceptDataAction,obj.Toolbar);
        % Add a help toolbar button
        mv_helptoolbutton(obj.Toolbar, 'xreg_dataEditor');
        
        obj.Toolbar.setRedraw(true);
        
        
        if ms.HasExtras
            e = mbcextensions.Extensions.Model;
            mbccreateaddonmenus(e.DataEditorTools, obj.ToolsMenu)
        end

        
        end
        
        function createMultiView(obj)
        %createMultiView create data editor multiview area
        
        vlist = mbcgui.multiview.ViewList;
        % standard data editor views
        add(vlist,xregdatagui.SweepsetPlotView.ViewInfo);
        add(vlist,xregdatagui.DataPlot3DView.ViewInfo);
        add(vlist,xregdatagui.DataTableView.ViewInfo);
        add(vlist,xregdatagui.MonitorPlotView.ViewInfo);

        % cluster views only relevant to test plan data
        add(vlist,xregdatagui.TssfClusterView.ViewInfo);
        
        if obj.MessageService.HasExtras
            % add extra views
            e = mbcextensions.Extensions.Model;
            for i=1:length(e.DataEditorViews)
                add(vlist,e.DataEditorViews(i).Constructor);
            end
        end
        
        obj.MultiView = mbcgui.multiview.MultiViewPanel('Parent', obj.Figure, ...
            'Visible','off',...
            'AlwaysAllowPrintToFigure',false,...
            'MessageService', obj.MessageService, ...
            'ViewLayoutName', 'DataEditorViews', ...
            'ViewList', vlist);
        % include a table
        splitView(obj.MultiView,'lr',3);
        obj.MultiView.Visible = 'on';
        
        % add multiview menus to main view menu
        addViewMenuItems(obj.MultiView,obj.ViewMenu);
        end
        
        function createStatusInfo(obj)
        %CREATESTATUSINFO Set up the update mechanism for displaying status
        %
        %  CREATESTATUSINFO(obj)
        
        % Listen to changes in the DataMessageService.DataObject size
        dms = obj.MessageService;
        obj.StatusInfoListeners = [...
            event.proplistener(dms, dms.findprop('IsReadOnly'), 'PostSet', @obj.pPostSetDmsIsReadOnly);...
            event.listener(dms, 'ssRecordsChanged', @obj.updateStatusMessage);...
            event.listener(dms, 'ssNamesChanged', @obj.updateStatusMessage);...
            event.listener(dms, 'ssTestsChanged', @obj.updateStatusMessage);...
            ];
        
        end
        
        function updateStatusMessage(obj,~, ~)
        % Get a temp pointer to the dms
        dms = obj.MessageService;
        % Get the size of the data
        sizeSS = size(dms.sweepset);
        % Do something depending on the class of the data object
        if dms.isaSS
            messageString = sprintf('Data has %d Records, %d Variables, and %d Tests.', ...
                sizeSS(1), sizeSS(2), sizeSS(3));
        elseif dms.isaSSF
            % Get the sweepsetfilter
            ssf = dms.SweepsetFilter;
            % Get the parent sweepset
            parentSSSize = size(sweepset(info(dataptr(ssf))));
            % Create an appropriate message
            messageString = sprintf('Data has %d/%d Records, %d + %d Variables, and %d Tests.',...
                sizeSS(1), parentSSSize(1), sizeSS(2), sizeSS(2)-parentSSSize(2), sizeSS(3));
        else
            messageString = 'Unknown data type ... Ready';
        end
        
        if obj.StatusInfoID > 0
            % Just change the message
            obj.setStatusMessage(obj.StatusInfoID, messageString);
        else
            % Add the new message
            obj.StatusInfoID = obj.setStatusMessage(messageString);
        end
        end
        
        function createTitle(obj)
        %CREATESTATUSINFO Set up the update mechanism for displaying status
        %
        %  CREATESTATUSINFO(obj)
        
        % Listen to changes in the DataMessageService
        dms = obj.MessageService;
        obj.TitleListeners = [...
            event.listener(dms, 'ssfNameChanged', @obj.updateTitle);...
            event.proplistener(dms, dms.findprop('IsReadOnly'), 'PostSet', @obj.updateTitle);...
            ];
        end
        
        function updateTitle(obj,~, ~)
        dms = obj.MessageService;
        defaultTitle = 'Data Editor - ';
        % Name depending on the type of the object
        switch class(dms.DataObject)
            case {'sweepsetfilter', 'testplansweepsetfilter'}
                if dms.IsReadOnly
                    title = [defaultTitle dms.ObjectName ' [Read Only]'];
                else
                    title = [defaultTitle dms.ObjectName];
                end
            case 'sweepset'
                title = [defaultTitle dms.ObjectName ' [Read Only]'];
            otherwise
                title = [defaultTitle 'Other'];
        end
        % Set the title of the figure
        set(obj.Figure, 'Name', title);
        
        end
        
        function onPostClose(hData,~)
        %onPostClose post close action for data editor
        pSSF = hData.UserData.ObjectBeingEdited;
        if isvalid(pSSF)
            % save layout for data whether or not the data has changed
            pSSF.info = setDisplayLayout(pSSF.info,hData.serializeDisplayLayout);
        end
        % Free the inputs to the DataMessageService
        freeInternalPtrs(hData.MessageService.DataObject);
        
        % Clear close listeners
        hData.UserData = [];
        end
        
        function onBusy(obj,~,evt)
        %onBusy listen for MessageService.Busy and change the figure pointer
        ptrID = setPointer(obj,'watch');
        msgID = setStatusMessage(obj,evt.Data.Message);
        
        obj.PointerStack(end+1,:) = {ptrID,msgID};
        
        end
        
        function onIdle(obj,~,~)
        %onIdle listen for MessageService.Idle and change the figure pointer
        if ~isempty(obj.PointerStack)
            removePointer(obj,obj.PointerStack{end,1})
            removeStatusMessage(obj,obj.PointerStack{end,2})
            obj.PointerStack(end,:) = [];
        end
        end
        
        function onGenerateReport(obj,~,~)
        %onGenerateReport generate report callback
        
        busy(obj.MessageService,'Generating report...');
        
        [docFile,pth] = uiputfile({'*.docx';'*.pdf'},'Data Report');
        if ischar(docFile)
            [fname,ext] = fileparts(docFile);
            rpt = mlreportgen.dom.Document(fullfile(pth,fname),'docx');
            
            generateReport(obj,rpt)
            close(rpt);
            if strcmpi(ext,'.pdf')
                rptview(rpt.OutputPath,'pdf');
            else
                rptview(rpt.OutputPath);
            end
            
        end
        idle(obj.MessageService);
        end
        
        function onStorage(obj,~,~)
        %onStorage access storage functionality 
        
        % Create the storage dialog
        % Try getting the storage handle
        f = mvf('storage');
        
        % Do we already have a valid storage handle?
        if isgraphics(f)
            figure(f);
            return
        end
        
        % Indicate this task might take a little time
        busy(obj.MessageService,'Opening Storage Window...');

        f = xregdatagui.StorageView.createFigure(obj);
        % Register as a figure to delete later
        obj.addSubFigure(f);
        
        % Remove the message and the pointer
        idle(obj.MessageService);
        end
        
        function onExportStorage(obj,~,~)
        %onExportStorage export storage settings to MAT file 
        
        % Open the save file dialog
        [filename, pathname] = uiputfile({'*.mat' 'MAT-files (*.mat)';...
            '*.*' 'All files (*.*)'}, 'Save Variables, Filters and Editor Layout');
        % Did the user cancel
        if isequal(pathname, 0) || isequal(filename, 0)
            return
        end
        % store current layout in sweepsetfilter
        ViewInfo = serializeDisplayLayout(obj);
        ssf = obj.MessageService.SweepsetFilter;
        ssf = setDisplayLayout(ssf,ViewInfo);
        
        storage.data = getStorage(ssf);
        storage.version = 2; 
        
        save(fullfile(pathname,filename), 'storage');
        end
        
        function pPostSetDmsIsReadOnly(obj,~,~)
        %pPostSetDmsIsReadOnly disable storage actions if data editor is read-only
        
        obj.StorageActions.Actions(2).Enabled = ~obj.MessageService.IsReadOnly;
        
        end
        
        function onTestsChanged(obj,~,~)
        %onTestsChanged hide the test number panel for one-stage data
        
        ms = obj.MessageService;
        if ~isempty(ms.DataObject)
            % only toggle state if there is some data
            if ms.isOneStage
                % don't show the test selector list
                set(obj.TestSnapPanel,'State','left','SplitEnable','off');
            else
                % show the test selector list
                set(obj.TestSnapPanel,'State','center','SplitEnable','on');
                if isempty(ms.SelectedTests)
                    % select a test if required
                    setSelectedTests(ms,1);
                end
            end
        end
        
        end
        
    end
    
    %  View manipulation methods 
    methods
        function ViewInfo = serializeDisplayLayout(obj)
        %serializeDisplayLayout serialize data editor layout
        
        layoutdata = saveViewLayout(obj.MultiView);
        
        ViewInfo.Layout = layoutdata;
        % store selected tests
        ViewInfo.SelectedTests = obj.MessageService.SelectedTests;
        if isempty(ViewInfo.SelectedTests)
            ViewInfo.SelectedTests = 1;
        end
        
        end
        
        function replaceDisplayLayout(obj, ViewInfo)
        %replaceDisplayLayout replace data editor layout
        %   replaceDisplayLayout(obj, ViewInfo)
        
        if iscell(ViewInfo)
            % update from pre-R2016a form
            ViewInfo = xregdatagui.Editor.convertStorageToStruct(ViewInfo,obj.MultiView.ViewList);
        end
        if isfield(ViewInfo,'SelectedTests')
            SelectedTests = ViewInfo.SelectedTests;
            lytStruc = ViewInfo.Layout;
        else
            SelectedTests = obj.MessageService.SelectedTests;
            lytStruc = ViewInfo;
        end
        
        restoreViewLayout(obj.MultiView,'dataeditor',lytStruc);
        % flush a data changed event
        updateViews(obj.MessageService,SelectedTests);
        
        end
        
        function setupClusterViews(obj)
        %setupClusterViews setup cluster views
        
        ms = obj.MessageService;
        
        % design matching
        MatchDesign = ms.isaTSSF && ~isExpData( designdev(getTestplan(ms.TestplanSweepsetFilter)) );
        
        enableView(obj.MultiView,'xregdatagui.TssfClusterView',MatchDesign);
        
        if ~MatchDesign
            % remove cluster view 
            removeViews(obj.MultiView,'xregdatagui.TssfClusterView');
        end
        
        end
        
        function generateReport(obj,rpt,lvl)
        %generateReport generate report for view
        %   generateReport(obj,rpt,lvl)
        
        import mlreportgen.dom.*
        if nargin<3
            lvl = 1;
        end
        
        % Use object name for heading
        ms = obj.MessageService;
        h = Heading(lvl,ms.ObjectName);
        rpt.append(h);
        
        % generate report for SweepSetFilter data manipulation
        ssf = ms.SweepsetFilter;
        generateReport(ssf,rpt,lvl+1)
        
        % generate report for multiview plots
        obj.MultiView.generateReport(rpt,lvl+1)
        end

        function views = enumerateViews(obj)
        %enumerateViews get list of views
        
        viewContainers = getViewContainers(obj.MultiView);
        views = [viewContainers.View];
        end
        
    end
    
end


function i_setInfoHeight(~, ~, snapPanel)
% Push all the height change into the bottom split, keeping the minimum
% size of the top part 110 pixels. Note that the floor will ensure that
% these (110 and currentWidths(2) + deltaH) will not sum to more than
% the new possible height of the figure.
snapPos = get(snapPanel, 'Position');
currentWidths = get(snapPanel, 'minwidth');
newWidths = [currentWidths(1) snapPos(4)-currentWidths(1)];
if ~isequal(newWidths, currentWidths)
    set(snapPanel, 'split', [0 1]);
end

end