classdef DPSingleClient < dialogmgr.DialogPresenter
    % Construct a DPSingleClient object, which is a DialogPresenter.
        
    % Copyright 2010-2015 The MathWorks, Inc.
        
    properties (SetAccess=private)
        % Handle to parent DialogPresenter, used to coordinate
        % re-dock event.
        ApplicationDP
        
        % figure handle of parent application, used to coordinate
        % closing of DPSingleClient when parent figure closes, etc
        ApplicationFigure
    end
    
    properties
        % Position vector over which the new figure is centered
        % Leave empty to center new figure over ApplicationFigure position,
        % if provided, or over default figure position.
        OpenPosition = []
        
        % Presenter figure will use this name if not empty.
        % Otherwise Presenter will use Name property from its child dialog.
        Name = ''
    end
    
    methods
        function dp = DPSingleClient(parentDP,pos)
            % Construct a DialogPresenter object that presents a single
            % dialog in an HG figure.  DPSingleClient creates its own HG
            % figure in which the dialog is presented.
            %
            % DPSingleClient(parentDP) sets the ApplicationDP handle to
            % parent DialogPresenter handle.  This allows DPSingleClient to
            % manage its position and lifecycle based on the application.
            %
            % Management includes:
            % * DPSingleClient figure will open centered on the application
            %   dialog
            % * DPSingleClient figure will close in response to a close
            %   event of the application
            % * xxx DPSingleClient figure name will be based on an ID
            %   provided by the application figure (TBD)
            %
            % To retrieve this object in an application, use:
            %   dp = dialogmgr.findDialogPresenter(gcf)
            
            if nargin>0
                dp.ApplicationDP = parentDP;
                
                % Cache figure handle of parent DialogPresenter
                dp.ApplicationFigure = parentDP.hFig;
            end
            dp.DialogBorderFactory = @dialogmgr.DBNoTitle;
            %dp.DialogBorderFactory = @dialogmgr.DBTopBar;
            
            if nargin>1
                dp.OpenPosition = pos;
            end
            
            init(dp);
            
            % Use the same font size as the parent
            dp.FontSize = parentDP.FontSize;
        end
        
        function success = register(dp,thisDialog)
            % Register a Dialog with the DialogPresenter.
            
            % Invoke base-class method;
            success = register@dialogmgr.DialogPresenter(dp,thisDialog);
            if ~success
                return
            end

            % For DPSingleClient, there should only be one dialog that
            % registers with the DP:
            success = isempty(dp.Dialogs);
            if ~success
                warn(thisDialog,'DuplicateDialogName');
                return % EARLY EXIT
            end
            dp.Dialogs = thisDialog;
            
            figName = dp.Name;
            if isempty(figName)
                figName = thisDialog.Name;
            end
            
            % Update the figure
            set(dp.hFig, ...
                'Name',figName, ...
                'Position',getFigPosition(dp,thisDialog), ...
                'Resize','on', ...
                'Visible','on');
            
            % Enable dialog title and docking services
            engageServices( ...
                thisDialog.DialogBorder, ...
                {'DialogTitle',[], ...
                'DialogDock',@(h,e)dockUndockedDialog( ...
                dp.ApplicationDP,thisDialog)});
        end
        
        function updatedDialogs = resizeChildPanels(dp,opt) %#ok<INUSD>
            % Resize DialogPresenter uipanels responsible for high-level
            % layout management, such as in DPVertPanel, where the dialog
            % panel is embedded within a multi-panel layout.
            %
            % This is NOT intended to resize the dialogs themselves.
            % We resize the DPSingleClient figure itself, based on a change
            % in height of the child dialog, for example.
            
            resizeFigToDialog(dp);
            
            % Indicate that there was nothing to do:
            updatedDialogs = false;
        end
        
        function keyPressHandler(dp,ev)
            % Handle keypress in figure.
            
            handled = local_fontSizeChange(dp,ev);
            if handled
                return
            end
            
            % Other handlers can go here.
        end
    end
    
    methods (Access=private)
        function init(dp)
            % Create a figure and a panel
            % Figure is hidden until register() is called
            
            % If caller initialized the ApplicationDP, use its background
            % color for this figure as well.
            if ~isempty(dp.ApplicationDP)
                bg = get(dp.ApplicationDP.hFigPanel,'BackgroundColor');
            else
                % Unmanaged - make up the color ourselves
                bg = get(0,'DefaultFigureColor');
            end
            
            if ~isempty(dp.ApplicationFigure) && ...
                    isa(dp.ApplicationFigure, 'matlab.ui.Figure')
                hgroot = groot;
            else
                hgroot = 0;
            end

            hFig = figure( ...
                'Parent', hgroot, ...
                'MenuBar','none', ...
                'NumberTitle','off', ...
                'IntegerHandle','off', ...
                'Name','', ...
                'Units','pix', ...
                'HandleVisibility','off', ...
                'KeyPressFcn',@(~,ev)keyPressHandler(dp,ev), ...
                'Visible','off');
            
            hFigPanel = uipanel('Parent',hFig, ...
                'BackgroundColor',bg, ...
                'Tag','DPSingleClient_hFigPanel', ...
                'BorderType','none', ...
                'Units','norm', ...
                'Position',[0 0 1 1]);
            
            % initPresenter() sets hFig and hFigPanel base class properties
            initPresenter(dp,hFigPanel); % base class method
            
            % Set size of hParent
            %set(dp.hParent,'pos',[1 1 dp.OpenPosition(3:4)]);
            
            % Setup app-level management, if an application figure handle
            % has been registered.
            %
            % - use figure parent of AppParent handle to determine where
            % this figure should be positioned
            %
            % - listen to AppParent for figure close event
            appFig = dp.ApplicationFigure;
            if ~isempty(appFig)
                addlistener(appFig, ...
                    'ObjectBeingDestroyed',@(h,e)closeDueToAppFig(dp));
            end
            
            % Setup listeners
            addlistener(dp.hFig,'SizeChanged',@(h,e)resizeFig(dp));
            %addlistener(dp.hFig,'LocationChange',@(h,e)resizeFig(dp));
        end
        
        function resizeFigToDialog(dp)
            % Resize the DPSingleClient figure to tightly wrap the child
            % dialog.
            
            % ext is dialog dimension needed
            ext = getExtent(dp.Dialogs.DialogBorder);
            
            % Resize fig height
            figPos = getFigPosPixels(dp);
            figPos(2) = figPos(2) + figPos(4)-ext(4);
            figPos(4) = ext(4);
            dp.hFig.Position = figPos;
            
            % Resize presenter panel height
            dp.hParent.Position(4) = ext(4);
        end
        
        function resizeFig(dp)
            % External resize event on figure
            %
            % Keep vertical height fixed
            %   - maintain top height
            %   - update bottom height
            %
            % Allow width change
            %   - Apply min/max panel width limit
            %   - update layout via width-change
            
            hfig = dp.hFig;
            pos = hfig.Position;
            ext = getExtent(dp.Dialogs.DialogBorder);
            
            % Maintain height, keeping top of dialog "fixed" on screen
            dy_change = ext(4)-pos(4);
            pos(2) = pos(2) - dy_change;
            pos(4) = ext(4);
            hfig.Position = pos;

            % Allow width change
            %
            % xxx apply min/max panel width settings?
            %     we don't have such properties in DPSingleClient
            %
            dp.hParent.Position(3) = pos(3);
            setWidth(dp.Dialogs,pos(3));
        end
        
        function figPos = getFigPosition(dp,thisDialog)
            
            % Center new figure over specified OpenPosition, if provided
            parentPos = dp.OpenPosition;
            if isempty(parentPos)
                % Determine if this DPSingleClient is going to be managed
                % in response to a parent figure
                appFig = dp.ApplicationFigure;
                if isempty(appFig)
                    % Center new fig over default figure location
                    parentPos = get(0,'DefaultFigurePosition');
                else
                    % Center new fig over app figure location
                    parentPos = get(appFig,'Position');
                end
            end
            
            % Determine size of dialog being registered,
            % based on initial DialogContent
            hPanel = getDialogContentParent(thisDialog.DialogBorder);
            dcPos = get(hPanel,'Position');
            
            % Copy over the height of the dialog (that includes the
            % dialog border panel) that is being created.
            dcPos(4) = parentPos(4);
            
            % Final figure size
            figSiz = [dcPos(1)+dcPos(3) dcPos(2)+dcPos(4)];
            
            % Center the new figure over the "parentPos" rectangle
            figPos = centerOverPos(figSiz,parentPos);
        end
        
        function handled = local_fontSizeChange(dp,ev)
            % Key handler for font-size change
            %
            % Ctrl + ... applies font size change to all dialogs
            %   Ctrl + plus           - increase font size (by 1 point)
            %   Ctrl + minus          - decrease font size
            %   Ctrl + 0              - restore original font size
            %
            % Shift + Ctrl + ... applies change to current dialog only, if mouse is
            % over a dialog
            %   Shift + Ctrl + plus   - increase
            %   Shift + Ctrl + minus  - decrease
            %   Shift + Ctrl + 0      - restore
            
            handled = false;
            
            % May get called when mouse is not over a diaog (or over the panel)
            if any(strcmpi('control',ev.Modifier)) % Modifier is a cell-vector
                
                fontSizeStep = []; % fall-thru if no defined key was hit
                switch ev.Key
                    case 'equal'
                        fontSizeStep = +1; % Increase font size
                    case 'hyphen'
                        fontSizeStep = -1; % Decrease font size
                    case '0'
                        fontSizeStep =  0; % Restore font size
                end
                if ~isempty(fontSizeStep)
                    if any(strcmpi('shift',ev.Modifier))
                        % Change font size in current dialog only
                        dlg = dp.MouseOverDialogDlg;
                        if ~isempty(dlg)
                            changeFontSize(dlg.DialogContent,fontSizeStep);
                            handled = true;
                        end
                    else
                        % Change font size in all dialogs
                        %   (docked+undocked+closed)
                        %
                        % This is so that they will open with proper font size
                        % xxx could possibly defer this, applying font size when the
                        %     dialogs open
                        %
                        dlgs = dp.Dialogs;
                        for i = 1:numel(dlgs);
                            changeFontSize(dlgs(i).DialogContent,fontSizeStep);
                        end
                        handled = true;
                    end
                end
            end
        end
    end
end

function closeDueToAppFig(dp)
% Close DPSingleClient figure in response to the closing of a managed
% application figure.

if ishghandle(dp.hFig)
    close(dp.hFig);
end

end

function figPos = centerOverPos(figSiz,parentPos)
% Determine position rectangle for new figure, based on figSiz, that
% centers new figure over parentPos.

figx = max(1,ceil(parentPos(1) + (parentPos(3)-figSiz(1))/2));
figy = max(1,ceil(parentPos(2) + (parentPos(4)-figSiz(2))/2));
figPos = [figx figy figSiz];

end
