classdef DialogPresenter < handle
    % Base class for graphical frameworks that offer a visual presentation
    % of a Dialog to a user.   Examples include DialogPanel and
    % DialogTester.
    %
    % To retrieve the DialogPresenter object in an application, use:
    %   dp = get(hParent,'userdata')
    % or
    %   dp = dialogmgr.findDialogPresenter(hfig)
    
    
%   Copyright 2010-2015 The MathWorks, Inc.

    properties (SetAccess=private)
        hParent   % handle to top-level uipanel (owned by DialogPresenter)
    end
    
    properties (SetAccess=protected)
        % Mark this true so we can "shut down" unnecessary updates to
        % layouts, graphics, etc, until finalization.  This should
        % decrease time to bring up the system.
        Initializing = true
    end
    
    properties (SetAccess=private,SetObservable) % ,GetAccess=protected)
        hFig      % handle to app figure (not owned by DialogPresenter)
        hFigPanel % handle to app panel (not owned by DialogPresenter)
        HGRoot    % Graphics root of application figure
    end
    
    properties (SetAccess=protected)
        % Vector of top-level child Dialog object handles.
        % Can be empty, a scalar, or a vector of Dialog objects.
        %
        % Does not include child dialogs (i.e., uidialog).
        %
        % This is a subset of all dialogs, listing only top-level dialogs
        % from the perspective of the DP.  If the DP is DPSingleClient,
        % only one top-level dialog will be listed.  For DPVerticalPanel,
        % many top-level dialogs may be listed.  HOWEVER, each top-level
        % dialog may contain multiple child dialogs within them; those
        % child dialogs are NOT on this list.
        %
        % Initialize to empty list of Dialog objects, so we can reference
        % properties, etc, of the empty list without error.
        Dialogs = dialogmgr.Dialog.empty
        
        % List of ALL dialogs, including top-level AND child dialogs at ALL
        % hierarchy depths. Only non-empty for the top-most DP.
        AllDialogs = dialogmgr.Dialog.empty
        
        % List of DialogContent objects not yet instantiated within Dialog
        % objects.  None of these can be visible in the DP until fully
        % instantiated.
        %
        % Supports lazy-loading of DialogPresenter while dialogs remain
        % initially "closed" (invisible).
        DCQueue = {}
        
        % Flag to signal things like mouse cursor changes, etc.
        %
        % Only true during the lazy load itself, so we can present
        % affordances to customer to let them know the delay is for a good
        % reason.
        LazyLoadingUnderway = false
    end
        
    properties (Access=private)
        BeforeFirstDialogIsRegistered = true
    end
    
    properties (Access=protected)
        % Animate appearance and disappearance of dialogs within Presenter,
        % for manual open/close, auto-hide, or other optional services.
        %
        %  - This property may not have any visible effect, depending on
        %    the subclass of DP being used
        %  - This is not a global animation service switch!
        %  - Use Animation property for global animation control
        %  - This only affects animation behaviors local to DialogPresenter
        %  - Child dialogs, including DialogBorder, DialogContent, etc,
        %    have their own local animation service settings controlled by
        %    the global Animation property
        DialogPresenterAnimation = false
    end
    
    properties
        % Function handle to a DialogBorder constructor
        % Overwritten by a DialogPresenter subclass as appropriate.
        
        % Enable Animation in DialogPresenter and its child dialogs
        Animation = false
        
        % Default is to use the simplest Compact dialog border style
        DialogBorderFactory = @dialogmgr.DBCompact;
        %DialogBorderFactory = @dialogmgr.DBTopBar;
        %DialogBorderFactory = @dialogmgr.DBNoTitle;
        %DialogBorderFactory = @dialogmgr.DBInvisible;
        
        % Set of parameter/value pairs that are used to decorate all
        % DialogBorder objects created from the DialogBorderFactory.
        %
        % Cell vector of length 2*N, for N parameters.
        %
        % Typical use: Setting a custom background color on all dialog
        % borders.
        DialogBorderDecoration = {}
        
        % Highlights the specified Dialog.
        % Specify zero or one dialog handle.
        DialogSelect = []
        
        % Must be public so applications can reuse this
        hContextMenu % handle to reused context menu
        
        % Lazy loading control
        %
        %  0: Urgent loading (lazy loading disabled)
        %
        %  1: Lazy loading, one-time init
        %     Dialogs initialized when DP panel becomes visible; for
        %     DPVerticalPanel, this is when panel is not minimized to be
        %     just a splitter; for DPSingleClient, no lazy loading is
        %     provided since DP is always visible.
        %
        %  2: Lazy loading, incremental init
        %     Similar to 1, but after DP panel becomes visible, only
        %     docked/visible dialogs are initialized, leaving some dialogs
        %     still uninitialized.  -> NOT YET IMPLEMENTED
        LazyLoading = 0 %1
        
        % Font size of the dialog manager. The following default is
        % overriden by the MeasurementPanel in its constructor.
        FontSize = get(0,'DefaultUIControlFontSize');
        
    end
    
    properties (GetAccess=protected, Constant)
        % Define name of the AppData field that stores the Dialog handle
        % for use by the DialogPresenter context menu handler
        WidgetContextMenuAppDataName = 'DialogPresenter_ContextMenuHandler';
    end
    
    methods (Sealed)
        function setVisible(dp,state)
            % Change DialogPresenter visibility
            %
            % setVisible(dp) makes the dialog presenter visible.
            %
            % setVisible(dp,tf) makes the presenter visible if flag
            %     tf=true, or invisible if tf=false.
            %
            % setVisible(dp,oo) makes the dialog visible if string oo='on',
            %     or invisible if oo='off'.
            
            if nargin<2
                state = 'on';
            elseif ~ischar(state) && ~(isstring(state) && isscalar(state))
                state = uiservices.logicalToOnOff(state);
            end
            dp.hParent.Visible = state;
        end
        
        function setContextMenuHandler(dp,widget,obj)
            % Unlike initDialogContextMenuHandler(), this sets up the
            % context menu for ONE handle object.  Intended to be used to
            % specify context menus for DialogPresenter infrastructure
            % widgets.
            set(widget,'UIContextMenu',dp.hContextMenu);
            setappdata(widget,dp.WidgetContextMenuAppDataName,obj);
        end
        
        function buildContextMenu(dp)
            % Context menu handler for DialogPresenter
            % May be overridden in subclasses
            
            % Dynamically build context menu
            %
            % Assumes "gco" is set to the current graphical object
            % responsible for invoking the context menu.
            %
            % Invoked in response to a right-click within the hParent panel,
            % meaning both the DialogPanel and the BodyPanel
            
            hWidget = get(dp.hFig, 'CurrentObject'); % cache the current object handle
            
            % We reuse one context menu for the Dialog Presenter.
            %
            % So our first step must be to clear out any context menu
            % children created from last context menu construction:
            delete(get(dp.hContextMenu,'Children'));
            
            % Get object that will handle context menu creation for this
            % widget.
            %
            % Could be a DialogContent object, a DialogPresenter object, or
            % empty.  We will NOT have a DialogBorder or a Dialog, since
            % those can be changed dynamically and the widget is not going
            % to change its use of handles.  So we rule those out.
            %
            % DC,DP: Invoke method provided by these, with 2 args
            % Empty: Invoke method on DP, no 2nd arg
            %        Implies
            % DP:
            % Empty:
            handlerObj = getappdata(hWidget, ...
                dp.WidgetContextMenuAppDataName);
            
            if isempty(handlerObj)
                % Invoke DialogPresenter method for client-app specific
                % context menu invocation.
                buildClientContextMenu(dp);
            else
                % handlerObj is usually a dialogContent object, so an
                % override of buildDialogContextMenu() on a dialogContent
                % object is often invoked.
                %
                % handlerObj may be a DialogPresenter object, when a
                % generic, non-dialog specific context menu is requested.
                buildDialogContextMenu(handlerObj,dp);
            end
        end
    end
    
    methods (Access=private)
        function initDialogContextMenuHandler(dp,dlg)
            % Initialize the context menu handler for all widgets created
            % for the specified Dialog object dlg.
            
            widgets = getAllWidgets(dlg.DialogBorder);
            widgetsFilter = widgets.isprop('UIContextMenu');
            widgets = widgets(widgetsFilter);
            if ~isempty(dp.hContextMenu)
                dp.hContextMenu.Parent = ancestor(widgets(1), 'figure');
                set(widgets,'UIContextMenu',dp.hContextMenu);
            end
            if dlg.DialogContent.CustomContextHandler
                arg = dlg.DialogContent;
            else
                arg = dp;
            end
            
            % setappdata only takes scalar handles, so we loop:
            for i = 1:numel(widgets)
                setappdata(widgets(i), ...
                    dp.WidgetContextMenuAppDataName, arg);
            end
        end
    end
    
    methods (Access=protected)
        function registerFirstTime(dp) %#ok<MANU>
            % Update DialogPresenter before first dialog gets initialized
            % and registered.
            %
            % This is a class-specific override, and is used to react to
            % any public-property changes performed by client, after
            % construction by prior to first dialog.
            %
            % An example is reacting to a "presenter width" setting that is
            % changed in a property, but needs reaction prior to the first
            % dialog initialization.
            %
            % Gets called BEFORE register() and before dialog
            % initialization.
        end
    end
    
    methods
        function dlg = createAndRegisterDialog(dp,dialogContent)
            % Called by DialogContent client code.
            %
            % .LazyLoading is not dynamically changing; we're pretty much
            % expecting the client to turn it on permanently.  The _urgent
            % code here is for testing and isn't likely to execute.
            if dp.LazyLoading
                dlg = createAndRegisterDialog_lazy(dp,dialogContent);
            else
                dlg = createAndRegisterDialog_urgent(dp,dialogContent);
            end
        end
        
        function y = isDPChildDialog(dp) %#ok<MANU>
            % True if this is a DPChildDialog.
            y = false;
        end
        
        function [dlg_ref,tag] = mapHierarchicalRefToHandle(dp,path_ref)
            % Translate hierarchical path to dialog handle and tag.
            %    ref: '../anotherDialogTag/control1_tag'
            %    ref: 'childDlgTag/subchildDlgTag/control2_tag'
            %
            % NOTE: hierarchical paths MUST be resolved starting from the
            % dialog, or its immediate dialog presenter (DP), not a higher
            % level DP. That's because the hierarchy reference itself is
            % relative to that dialog.
            %
            % I.e., the reference '../SiblingDialog_Tag/Tag1' is relative
            % to "this" dialog.
            
            ref = dp.Dialogs;
            
            % Traverse hierarchy to find referenced Dialog
            while ~isempty(path_ref)
                % up a level?
                if strncmpi(path_ref,'../',3)
                    path_ref = path_ref(4:end);
                    ref = up(ref);
                    if isempty(ref)
                        break
                    end
                    continue
                end
                
                % down a level?
                idx = find(path_ref == '/',1);
                if isempty(idx)
                    break
                end
                name = path_ref(1:idx-1);
                path_ref = path_ref(idx+1:end);
                ref = down(ref,name);
                if isempty(ref)
                    break
                end
            end
            
            dlg_ref = ref;
            tag = path_ref; % tag name is left over
        end

        %{
        function [new_dlgs,actions] = mapHierarchicalRefsToHandles(dp,actions)
            % Translate hierarchical paths to dialog handls in actions.
            % actions: cell-matrix of hierarchical action references
            %   { {path1, visState1;
            %      path2, visState2;
            %      ... }
            %
            %    path1: '../anotherDialog/control_tag'
            % visState: logical scalar
            %
            % Replaces pathN by Dialog handles, and keeps just the tag
            % string.
            %  { hDialog1, Tag1, visState1;
            %    hDialog2, Tag2, visState2;
            %    ... }
            
            % NOTE: hierarchical paths MUST be resolved from the dialog, or
            % its immediate dialog presenter (DP), not a higher level DP.
            % That's because the hierarchy reference itself is relative to
            % that dialog.  I.e., the reference '../SiblingDialog/Tag1' is
            % relative to this dialog.
            
            ref = dp.Dialogs;
            N = size(actions,1);
            new_dlgs = [];
            for i = 1:N
                path_ref = actions{i,1};
                
                % Traverse hierarchy to find referenced Dialog
                while ~isempty(path_ref)
                    % up a level?
                    if strncmpi(path_ref,'../',3)
                        path_ref = path_ref(4:end);
                        ref = up(ref);
                        continue
                    end
                    
                    % down a level?
                    idx = find(path_ref == '/',1);
                    if isempty(idx)
                        break
                    end
                    name = path_ref(1:idx-1);
                    path_ref = path_ref(idx+1:end);
                    ref = down(ref,name);
                end
                
                % Record control handle
                if isempty(new_dlgs)
                    new_dlgs = ref;
                else
                    new_dlgs(end+1) = ref; %#ok<AGROW>
                end
                actions{i,1} = path_ref; % tag name is left over
            end
        end
        %}
        
        function success = register(dp,dlg)
            % Register a Dialog with the DialogPresenter.
            %
            % Subclasses should overload this method when they need to take
            % action when one or more dialogs register with the presenter.
            %
            % Subclasses MUST invoke this base-class method.
            %
            % Gets called AFTER dialog is constructed.
            
            assert(isa(dlg,'dialogmgr.Dialog'));
            success = true;
            
            addToAllDialogsList(dp,dlg);
        end
        
        function addToAllDialogsList(dp,dlg)
            % Add dialog to "all dialogs" cache, for fast traversal by
            % methods that need to do so.  This includes top-level and
            % child dialogs.
            %
            % One typical need for this list: visiting all dialogs
            % (top-level or child) to execute their initializeFinal()
            % method, which can only be executed after all dialogs have
            % been constructed and registered with the DP.
            %
            % This method can be overridden by subclasses such as
            % DPChildDialog so that children forward while top-level
            % dialogs gather.
            dp.AllDialogs(end+1) = dlg;
        end
        
        function dlg = getDialog(dp,dc)
            % Return Dialog object child from a DialogPresenter whose ID
            % matches the ID specified in DialogContent object dc.
            %
            % DialogPresenter may have zero, one, or multiple Dialog
            % children. If no matching Dialog object is found, empty is
            % returned.
            
            % xxx this method is suspect:
            %     why doesn't the caller just access via dc.Dialog?
            %     we may be able to remove this method.
            
            allIDs = getID(dp.Dialogs);
            dlg = dp.Dialogs(allIDs == dc.ID);
        end
        
        function dlg = getDialogByName(dp,theName)
            % Return handle to Dialog with matching name.
            
            if isempty(dp.Dialogs)
                dlg = [];
            else
                dlg = dp.Dialogs(strcmpi(theName,{dp.Dialogs.Name}));
            end
        end
        
        function [hParentForDB,width,height] = getDialogPanelAndSize(dp)
            % Return handle to uipanel that will contain DialogBorder.
            % This may be the DialogPresenter .Panel handle for simple
            % DialogPresenter systems such as DPSingleClient, but is
            % overridden for more complex presenters such as
            % DPVerticalPanel.
            %
            % Optionally return width and height of dialog panel, in pixels.
            
            hParentForDB = dp.hParent;
            if nargout > 1
                ppos = get(hParentForDB,'Position'); % pixels
                width = ppos(3);
                height = ppos(4);
            end
        end
        
        function createBaseContext(dp,dc) %#ok<INUSD>
            % Create the base DP context menu for the main panel or for a
            % dialog of a DialogPresenter.
            %
            % Intentionally left blank.
        end
        
        function y = isDialogVisible(dp,dialogName) %#ok<INUSD>
            % True if named dialog is visible
            y = true;
        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.
            % This gets called, for example, when a roller-shade feature is
            % invoked, or a re-docking of a multi-dialog panel occurs.
            
            % Indicate that there was nothing to do:
            updatedDialogs = false;
        end
        
        function set.Animation(dp,ena)
            % Record new value, then update animation state globally
            dp.Animation = ena;
            updateAnimationState(dp);
        end
        
        function set.DialogSelect(dp,dlg)
            % Select zero or one dialog.
            % Select generally causes a visual highlight to appear.
            %
            % Select the specified dialog, and de-selects the prior dialog
            % if one was specified.
            
            if ~isempty(dlg) && ~isscalar(dlg)
                error(message('Spcuilib:dialogmgr:ErrorInputMustBeEmptyOrOneDialog'));
            end
            old_dlg = dp.DialogSelect;
            if ~isempty(old_dlg)
                select(old_dlg,false);
            end
            if ~isempty(dlg)
                select(dlg,true);
            end
            dp.DialogSelect = dlg;
        end
        
        function [slideVis,slideInvis] = getAnimationSlideDir(dp) %#ok<MANU>
            % Determine which side to slide controls while animating.
            %   +1 = right
            %   -1 = left
            %
            % Baseline is to slide right.  DPVerticalPresenter and other
            % subclasses will override this.
            
            % direction to slide while making rows invisible
            slideVis = -1;
            slideInvis = 1;
        end
    end
    
    % Lazy and urgent dialog registration
    %
    methods % must be public for use by uidialog, etc
        function dlg = createAndRegisterDialog_urgent(dp,dialogContent)
            % Wraps dialogContent with a new instance of a Dialog,
            % containing a new DialogPresenter-specific DialogBorder
            % instance.
            %
            % Dialog visibility is set to OFF by default
            % Be sure to call setVisible(dlg,'on').
            %
            % This is a convenience method for application developers,
            % making it easy to hand off a DialogContent object and have
            % the DialogPresenter take care of the remaining steps to
            % construct and register a dialog.
            
            % First-time call:
            if dp.BeforeFirstDialogIsRegistered
                dp.BeforeFirstDialogIsRegistered = false;
                registerFirstTime(dp);
            end
            
            dlg = dialogmgr.Dialog(dialogContent, makeDialogBorder(dp));
            initialize(dlg,dp);
            
            % Register dialog with the DialogPresenter.
            % - Adds to the AllDialogs list.
            register(dp,dlg);

            % Determine which object will handle context menu generation
            % for widgets in the DialogPresenter
            initDialogContextMenuHandler(dp,dlg);
        end
    end
    
    methods (Access=protected)
        function dlg = createAndRegisterDialog_lazy(dp,dialogContent)
            % Deferred, lazy-loading of dialogContent.
            %
            % This should be called only for DC objects that will be
            % top-level dialogs, not child dialogs.
            %
            % List gets registered using createAndRegisterDialog when DP
            % panel is opened/made visible, and then, only the dialogs that
            % are docked will be fully instantiated.
            
            dp.DCQueue{end+1,1} = dialogContent;
            dlg = [];
            
            % Execute content to get hierarchical data definitions
            lazyLoaderDataInit(dialogContent);
        end
    end
    
    % Mouse methods
    %
    % These need to be public in order to allow these to be directly
    % called by figure mouse event callbacks.
    %
    methods
        function handled = mouseMove(dp) %#ok<MANU>
            % Returns true if mouse-up event was handled by override.
            %
            % Optionally overridden by subclass.
            handled = false;
        end
        
        function dragFcn = mouseDown(dp) %#ok<MANU>
            % Returns handle to mouse-drag function used to process mouse
            % motion while button is down.  If mouse-down event is not
            % handled, return empty to let caller know that caller should
            % handle event.
            %
            % Optionally overridden by subclass.
            
            dragFcn = [];
        end
        
        function handled = mouseUp(dp) %#ok<MANU>
            % Returns true if mouse-up event was handled by override.
            %
            % Optionally overridden by subclass.
            handled = false;
        end
        
        function handled = mouseScrollWheel(dp,ev) %#ok<INUSD>
            % Returns true if mouse-up event was handled by override.
            %
            % Optionally overridden by subclass.
            handled = false;
        end
    end
    
    methods (Access=protected)
        function updateAnimationState(dp)
            % Enable or disable animation in DialogPresenter, if supported.
            %
            % This is a protected service called by set.Animation method,
            % not by client apps, and is intended to be overridden by
            % subclasses as needed.
            %
            %  - Animate open/close of dialog presentation panel
            %  - Animate rollershade of DBTopBar dialogs within panel
            %  - Animate visibility of controls within dialogs
            %
            % ena may be: 'on', 'off', true, false
            
            % Get "global" animation state
            %
            % (dp.Animation calls this method as side-effect when changed)
            ena = dp.Animation;
            if ischar(ena) || (isstring(ena) && isscalar(ena))
                ena = strcmpi(ena,'on');
            end
            
            % Copy "global state" (dp.Animation) to local DialogPresenter
            dp.DialogPresenterAnimation = ena;
            
            % Send "global state" to DialogBorder object
            dlgs = dp.Dialogs;
            for i = 1:numel(dlgs)
                enableService(dlgs(i).DialogBorder,'DialogAnimate',ena);
            end
        end
        
        function dialogBorder = makeDialogBorder(dp)
            % Create a new instance of a DialogBorder object, used during
            % creation of new Dialog objects for use by DialogPresenter.
            %
            % See createAndRegisterDialog() for typical use.
            
            % This is a "factory" method
            dialogBorder = dp.DialogBorderFactory();
            
            % Decorate all DialogBorder instances using a common set of
            % param/value pairs.  It is NOT an error if a param/value pair
            % is not available on the chosen DialogBorder.
            %
            pv = dp.DialogBorderDecoration; % cellstr
            for i = 1:2:numel(pv)
                try %#ok<TRYNC>
                    dialogBorder.(pv{i}) = pv{i+1};
                end
            end
        end
        
        function initPresenter(dp,hUser)
            % First-time initialization DialogPresenter object with a new
            % graphical application context.
            
            % Establish the application parent handle
            [dp.hFigPanel,dp.hFig] = local_verifyHGContext(hUser);
            
            % Cache the graphics root of application figure
            if isa(dp.hFig, 'matlab.ui.Figure')
                dp.HGRoot = groot;
            else
                dp.HGRoot = 0;
            end
            
            % Create top-level context menu, with no menu items
            %  - Must be the child of a figure
            % Set context menu of parent uipanel
            %
            % This context can be attached to DP widgets that need
            % context menu support
            dp.hContextMenu = uicontextmenu( ...
                'Parent',dp.hFig, ...
                'Callback',@(h,e)buildContextMenu(dp));
            
            % Create master uipanel that is OWNED by DialogPresenter and
            % cached in hParent property.
            %
            % hFig is not owned by DialogPresenter, neither is hFigPanel.
            % These are supplied by a client such as a scope framework.
            %
            % Tag of hParent is set so we can find it in situations where
            % we only have a handle to the top-level figure
            %
            % Panel is created INVISIBLE by intention; use setVisible()
            % after all dialogs are added to make it visible.
            %
            % Use static method "dp = findDialogPanel(gcf)" to retrieve
            % the DialogPanel object from a figure handle.
            %   - DialogPanel object is stored in hParent userdata to
            %     support that retrieval
            %
            bg = get(dp.hFigPanel,'BackgroundColor');
            dp.hParent = uipanel( ...
                'Parent',dp.hFigPanel, ...
                'BorderType','none', ...
                'Units','norm', ...
                'BackgroundColor',bg, ...
                'Visible','off', ...
                'Units','pix', ...
                'UserData',dp, ...
                'Tag','DialogPresenterParent'); %#ok<*PROP>
            setContextMenuHandler(dp,dp.hParent,dp); % set context menu

            % We must flush this change through HG
            % The invisibility of the panel is important, to suppress the
            % incremental display of graphical elements until they're all
            % ready to be rendered.  Without the flush, the invisible state
            % isn't always fully respected, and some widgets bleed through
            % before an implicit drawnow occurs.  So we flush this now.
            %
            % NOTE: this cannot be a "drawnow expose", it must be a full
            %       "drawnow" flush event for the panel to flush properly.
            % drawnow; Remove drawnow to prevent flashing
        end
        
        function buildDialogContextMenu(dp,~)  %#ok<INUSD>
            % Optional subclass override for DP to add a context menu.
            %
            % Implementation intentionally blank in base class.
        end

        function buildClientContextMenu(dp) %#ok<MANU>
            % Optional subclass override for DP to add a context menu.
            %
            % Implementation intentionally blank in base class.
        end
        
        function [pt,parentPos] = getPointerLocationInParentRefFrame(dp)
            % Return screen PointerLocation in hParent panel reference
            % frame, in units of pixels.  This position is always updated
            % and is therefore always correct, unlike pointer positions in
            % figures which are incorrect when the cursor is outside the
            % figure.
            %
            % Optionally returns parent position.
            
            % Get mouse position, which is always updated,
            % in pixel units relative to screen reference frame.
            % NOTE: hgconvertunits won't work for root units conversion
            root_units = get(0,'Units');
            if strcmpi(root_units,'pixels')
                globalPtr = get(0,'PointerLocation');
            else
                set(0,'Units','pix');
                globalPtr = get(0,'PointerLocation');
                set(0,'Units',root_units);
            end
            
            % Translate cursor point into parent ref frame
            %
            % Get fig and figpanel pos, in pixels units, in fig ref frame
            figPos = getFigPosPixels(dp);
            figPanelPos = getFigPanelPosPixels(dp);
            % Get parent pos, in pixels units, in figpanel ref frame
            parentPos = get(dp.hParent,'Position');
            
            % Translate cursor position
            %
            % We must use an offset of [1,1] for each reference frame
            % subtraction to re-establish the origin
            % Ex: if fig orig = (1,1) and figpanel orig is (1,1),
            %     we don't want (0,0) as the subtraction - we want (1,1)
            %     ... so we must add back [1,1] for figpanel subtraction,
            %     and once again for the parent subtraction.
            pt = globalPtr - figPos(1:2) - figPanelPos(1:2) ...
                - parentPos(1:2) + [3 3];
            hPanel = dp.hFigPanel.Parent;            
            while strcmp(hPanel.Type,'uipanel') || strcmp(hPanel.Type,'uicontainer')
              figPanelParentPos = hPanel.Position;
              uni = hPanel.Units;
              if ~strcmpi(uni,'pixels')
                figPanelParentPos = hgconvertunits(dp.hFig, ...
                    figPanelParentPos,uni,'pixels',dp.hFig);
              end
              pt = pt + [1 1] - figPanelParentPos(1:2);
              hPanel = hPanel.Parent;
            end
        end
        
        function pt = getFigCurrentPointPixels(dp)
            % Return current point in figure as [x,y] coords, in pixels.
            %
            % This function may be safely called from window motion events
            
            hf = dp.hFig;
            pt = hf.CurrentPoint;
            uni = hf.Units;
            if ~strcmpi(uni,'pixels')
                pt = hgconvertunits(hf,pt,uni,'pixels',hf);
            end
        end

        function pt = getFigCurrentPointPixelsFromRoot(dp)

            % Return current point in figure as [x,y] coords, in pixels.
            %
            % Must use PointerLocation since event callbacks don't force
            % CurrentPoint to update.
            
            % Get mouse position, which is always updated,
            % in pixel units relative to screen reference frame.
            % NOTE: hgconvertunits won't work for root units conversion
            root_units = get(0,'Units');
            if strcmpi(root_units,'pixels')
                globalPtr = get(0,'PointerLocation');
            else
                set(0,'Units','pix');
                globalPtr = get(0,'PointerLocation');
                set(0,'Units',root_units);
            end
            
            % Translate cursor point into fig ref frame
            %
            % Get fig pos, in pixels units, in fig ref frame
            figPos = getFigPosPixels(dp);
            pt = globalPtr - figPos(1:2) + [1 1];
        end
        
        function pos = getFigPosPixels(dp)
            hf = dp.hFig;
            pos = get(hf,'Position');
            uni = get(hf,'Units');
            if ~strcmpi(uni,'pixels')
                pos = hgconvertunits(hf,pos,uni,'pixels',hf);
            end
            
            % Get figure starting coord in global screen coords.
            %
            % For a figure that is NOT docked or in a toolstrip, the
            % figure's origin in its Position vector is in screen coords.
            %
            % For toolstrip, the start coord is ALWAYS set to (1,1), which
            % is a bug.  We need to detect and correct this.
            %
            if strcmpi(hf.WindowStyle,'docked')
                % Adjust starting x,y coord of pos to reflect true screen
                % coords:
                w = warning('off','MATLAB:HandleGraphics:ObsoletedProperty:JavaFrame');
                jf = matlab.ui.internal.JavaMigrationTools.suppressedJavaFrame(hf);
                pt = getContainerLocation(jf);
                ss = get(0,'ScreenSize');
                actualFigOrigin = [pt.x ss(4)-pt.y-hf.Position(4)];
                pos(1:2) = pos(1:2) + actualFigOrigin;
                warning(w);
            end
        end
        
        function name = getFigName(dp)
            % Return name of figure window parent of DialogPresenter.
            %
            % Useful for as an opportunity for name of child dialogs.
            
            name = dp.hFig.Name;
        end
        
        function pos = getFigPanelPosPixels(dp)
            % Return current hFigPanel position in units of pixels,
            % in LOCAL PARENT ref frame
            hFigPanel = dp.hFigPanel;
            pos = get(hFigPanel,'Position');
            uni = get(hFigPanel,'Units');
            if ~strcmpi(uni,'pixels')
                pos = hgconvertunits(dp.hFig,pos,uni,'pixels', ...
                    get(hFigPanel,'Parent'));
            end
        end
        
        function pt = getMouseInParentRefFrame(dp)
            % Return mouse coords in hParent reference frame,
            % in pixel units.
            %
            % Layout: each indent has new coords relative to its parent
            %
            %  Screen
            %    Figure (not owned by DP)
            %      (... arbitrary additional child-depth)
            %         FigurePanel (not owned by DP)
            %            hParent uipanel (owned by DP; immediate child of FigurePanel)
            %              (children, like .hDialogPanel for a DPVerticalPanel)
            
            % Get mouse (x,y) coords relative to parent container
            
            % Cursor pos, in figure reference frame
            pt_fig = getFigCurrentPointPixels(dp);
            
            % Get figpanel, in pixel units, in LOCAL ref frame
            % xxx BUG: This needs to be in the FIGURE ref frame!
            hFigPanelParent = get(dp.hFigPanel,'Parent');
            
            h = get(hFigPanelParent,'Parent');
            if strcmpi(get(h,'Type'),'figure')
                % Don't use grandparent if it's the figure itself.
                % In that case, just use parent:
                h = hFigPanelParent;
            end
            figpanel_pos = get(h,'Position');
            uni = get(h,'Units');
            if ~strcmpi(uni,'pixels')
                figpanel_pos = hgconvertunits(dp.hFig, ...
                    figpanel_pos,uni,'pixels',dp.hFig);
            end

            % Get parent pos, in figpanel ref frame
            parent_pos = get(dp.hParent,'Position'); % in pixels
            
            % Translate cursor point, in parent ref frame
            % We must use an offset of [1,1] for each reference frame
            % subtraction to reestablish the (1,1) origin
            %
            % Ex: if fig orig = (1,1) and figpanel orig is (1,1),
            %     we don't want (0,0) as the subtraction - we want (1,1)
            %     ... so we must add back [1,1] for figpanel subtraction,
            %     and once again for the parent subtraction
            pt = pt_fig-figpanel_pos(1:2)-parent_pos(1:2)+[2 2];
        end
    end
end

function [hFigPanel,hFig] = local_verifyHGContext(hUser)
% Confirm valid graphical context for DialogPanel system.
%
% hUser must be a handle to a uipanel or uicontainer
% If empty matrix is passed, creates figure and uipanel
% Returns handle to uipanel within figure
%
% Changes figure renderer to zbuffer.

% User specified parent handle
%
% Can be a uipanel or uicontainer
if isempty(hUser) || ~ishghandle(hUser)
    % Internal message to help debugging. Not intended to be user-visible.
    error(message('Spcuilib:dialogmgr:ErrorNotHGHandle'));
end
if ~any(strcmpi(get(hUser,'Type'),{'uipanel','uicontainer'}))
    % Internal message to help debugging. Not intended to be user-visible.
    error(message('Spcuilib:dialogmgr:ErrorHandleNotUIPanelOrUIContainer'));
end
hFigPanel = hUser;
hFig = ancestor(hFigPanel,'figure');

end

