classdef uidialog < dialogmgr.uiobject
    % Render a dialog as a uicontrol.
    %
    % Creates a uicontrol that renders a DialogContent dialog.
    %    uidialog(dialog,parent,pvPairs)
    %    uidialog(dialog,pvPairs)
    %    uidialog(pvPairs)
    
    % Copyright 2014 The MathWorks, Inc.
    
    events
        % Thrown when public property of underlying dialog changes.
        % Public, GetObservable, not Constant, not Hidden.
        Action
        
        % Notify when height of dialog "widget" changes.
        %
        % This can happen when dialog has a DBTopBar and Rollershade
        % activates, for example
        HeightChanged
    end
    
    %{
    properties (Dependent)
        BackgroundColor
    end
    %}
    
    properties
        DialogBorderDecoration = {}
        DialogBorderFactory = @dialogmgr.DBTopBar
        Enable = 'on'
    end
    
    properties (Dependent)
        Extent
        ForegroundColor
    end
    
    properties %(Hidden)
        HorizontalAlignment = 'left'
    end
    
    properties
        % xxx consider:
        %  % Distance to maintain outside dialog border
        %  OuterMargin = 4
        
        Parent % graphical parent panel
        ParentDialogContent
    end
    
    properties
        Position = [20 20 100 100]
    end
    
    properties (Constant)
        Style = 'dialog'
    end
    
    properties
        Tag = ''
    end
    
    properties (Hidden)
        Units = 'pixels'
    end
    
    properties (Dependent)
        % Dialog participates in parent layout computation.
        %
        % Only occurs if number of columns of child and parent are
        % identical.
        ParticipateInParentLayout
    end
    
    properties
        UserData
        
        Visible = 'on'
    end
    
    properties (SetAccess=private)
        Dialog
        ChildDC
    end
    
    methods
        function h = uidialog(varargin)
            % Render a dialog as a uicontrol.
            %
            % Creates a uicontrol that renders a DialogContent dialog.
            %    uidialog(parent,childDialog,pvPairs)
            %    uidialog(pvPairs)
            %
            % Dialog must be specified as either a value-only argument or
            % as a parameter/value pair to create a uidialog.
            
            % Invoke static method
            %
            % (made static DCTableForm:uidialog can call this from outside
            % this class)
            %
            [pv,parentDC,childDC,useParentLayout] = ...
                dialogmgr.uidialog.parseArgs(varargin);
            
            if isempty(childDC)
                dialogmgr.dialogmgr.untranslatedError('Dialog must be specified.');
            end
            h.ParentDialogContent = parentDC;
            
            % childDC handle is handed back to parent, which establishes
            % the data hierarchy (data model) for the client.
            %
            % NOTE: This is returned during both lazy-execution and
            % later full-instantiation.  It is identical in both cases so
            % there is no harm to the caller.
            
            % If this is lazy loading phase, we are only interested in data
            % associations, not graphics or execution.
            %
            % But we need to recurse to get the full data hierarchy.
            %
            % Alternative:
            %  - try to get DPChildDialog lazy-instantiated
            %  - then invoke its createAndRegisterDialog method, and make
            %    sure it goes createAndRegisterDialog_lazy
            %  - in turn, that will recurse properly
            if parentDC.IsLazyLoadPhase
                lazyLoaderDataInit(childDC);
                h.ChildDC = childDC; % for caller
                return
            end
            
            % Record a GRAPHICAL parent handle.
            if isempty(parentDC)
                graphicalParent = gcf; % behave like a uicontrol
            elseif isa(parentDC,'dialogmgr.DialogContent')
                % convenience syntax - parent DialogContent specified
                % instead of a parent graphical handle.
                %
                % Get graphical parent from parent DialogContent:
                graphicalParent = parentDC.ContentPanel;
            end
            h.Parent = graphicalParent;
            
            % We MUST test for empty pv.  If we don't, and pv is empty
            % (which occurs with no PV pairs), the "set" method will
            % cause display output to the command window!
            if ~isempty(pv)
                set(h,pv{:});
            end
            
            % Establish border decoration
%             pv = {'TitlePanelBackgroundColorSource','Auto', ...
%                 'TitlePanelForegroundColorSource','Custom', ...
%                 'TitlePanelForegroundColor',[0 0 0]};
            
            % Create dialog widget
            dp = dialogmgr.DPChildDialog(graphicalParent,h);
            dp.DialogBorderFactory = h.DialogBorderFactory;
            dp.DialogBorderDecoration = h.DialogBorderDecoration;
            h.Dialog = createAndRegisterDialog_urgent(dp,childDC);
            
            % Can only be set AFTER h.Dialog.DialogContent is available.
            h.ParticipateInParentLayout = useParentLayout;
            
            % Not all requested services may be available, based on the
            % type of DialogBorder in use.  Request the maximum set of
            % services we would like to make available:
            services = {'DialogTitle','DialogRoller'};
            enableService(h.Dialog.DialogBorder,services);

            listen_for_parent_animation_changes(h);
            
            % Dialog-specific updates:
            update(h.Dialog);
            
            % Make DialogPresenter and Dialog visible explicitly
            setVisible(h.Dialog);
            
            % not needed for a child, gets handled by parent
            % setVisible(dp);
            
            % Listen for public property changes on underlying dialog.
            %
            % Throws an 'Action' event from this object when underlying
            % dialog throws its 'Action' event.  Conveys the same event
            % payload.
            onPropertyChange(childDC, @(~,~)notify(h,'Action'));
        end
        
        function changeDialogContentHeight(h)
            % Update our child dialog
            
            % Send event so parent dialog updates
            notify(h,'HeightChanged');
        end
        
        function delete(h)
            % Delete uidialog control.
            
            delete(h.Dialog);
            h.Dialog = [];
        end
        
        %{
        function set.BackgroundColor(h,val)
            keyboard % xxx
        end
        
        function val = get.BackgroundColor(h)
            keyboard % xxx
        end
        %}
        
        %{
        function set.Enable(h,val)
            keyboard % xxx
            
            if ~ischar(val) || ~any(strcmpi(val,{'on','off','inactive'}))
                dialogmgr.dialogmgr.untranslatedError('Enable must be ''on'', ''off'' or ''inactive''.');
            end
            h.Enable = val;
        end
        %}
        
        function ext = get.Extent(h)
            % Get extent of dialog.
            ext = getExtent(h.Dialog.DialogBorder);
        end
        
        %{
        function val = get.ForegroundColor(h)
            keyboard % xxx
            
        end
        
        function set.ForegroundColor(h,val)
            keyboard % xxx
        end
        %}
        
        function set.Position(h,pos)
            % Set position of dialog.
            %
            % We don't set position of DialogContent, as it is always set
            % at the origin (1,1) of the parent.  Instead we set the
            % position of the DialogPresenter containing the DialogBorder
            % and DialogContent.
            
            setPosition(h.Dialog.DialogBorder,pos); %#ok<MCSUP>
        end
        
        function pos = get.Position(h)
            % Get position of rendered control.
            pos = getPosition(h.Dialog.DialogBorder); % xxx .DialogContent
        end
        
        function set.ParticipateInParentLayout(h,val)
            % React to property setting
            
            % If this is lazy-load phase, do nothing.
            %
            % Lazy-load is only for data-model creation, not for graphics,
            % layout or interaction.
            if h.ParentDialogContent.IsLazyLoadPhase
                return
            end
            
            dc = h.Dialog.DialogContent;
            if ~isa(dc,'dialogmgr.DCTableForm')
                dialogmgr.dialogmgr.untranslatedError('Cannot set ParticipateInParentLayout unless dialog is a DCTableForm.');
            end
            o = getLayoutParent(dc);
            if val
                % dc will have a parentlayout dialog
                %
                % Is this a re-parenting for dc?
                p = h.ParentDialogContent;
                if ~isempty(o)
                    if (o~=p)
                        removeLayoutChild(o,dc);
                        addLayoutChild(p,dc);
                        setLayoutParent(dc,p);
                    end
                else
                    addLayoutChild(p,dc);
                    setLayoutParent(dc,p);
                end
            else
                % dc will not have a parentlayout dialog
                %
                % Is this a de-parenting for dc?
                if ~isempty(o)
                    removeLayoutChild(o,dc);
                end
                setLayoutParent(dc,[]);
            end
        end
        
        function val = get.ParticipateInParentLayout(h)
            dc = h.Dialog.DialogContent;
            if ~isa(dc,'dialogmgr.DCTableForm')
                dialogmgr.dialogmgr.untranslatedError('Cannot get ParticipateInParentLayout unless dialog is a DCTableForm.');
            end
            val = ~isempty(getLayoutParent(dc));
        end
        
        function set.Visible(h,val)
            h.Visible = val;
            setVisible(h.Dialog.DialogBorder,val); %#ok<MCSUP>
        end
        
        %{
        function app = getappdata(h,name)
            keyboard % xxx
            app = getappdata(h.xxx,name);
        end
        
        function setappdata(h,name,val)
            keyboard % xxx
            setappdata(h.xxx,name,val);
        end
        %}
    end
    
    methods
        function changeFontSize_CacheOrig(h)
            changeFontSize_CacheOrig(h.Dialog.DialogContent);
        end
        function changeFontSize_Size(h,dir)
            changeFontSize_Size(h.Dialog.DialogContent,dir);
        end
        function changeFontSize_Layout(h)
            changeFontSize_Layout(h.Dialog.DialogContent);
        end
        
        function [dx,dy] = getMinSize(h)
            % Return minimum size of uidialog as [width height].
            %
            % Minimum size is the smallest width and height that the
            % uidialog content can be rendered without clipping.  It is
            % generally a smaller size than the current extent of the
            % uidialog.
            
            % Get current size
            pos = getPosition(h.Dialog.DialogBorder);
            dx = pos(3);
            dy = pos(4);
            
            % The difference between actual and minimum width is used to
            % adjust current size info.  Only width has a minimum; actual
            % height is the minimum height we report.
            %
            dcW_curr = getWidth(h.Dialog.DialogContent);
            dcW_min = getMinWidth(h.Dialog.DialogContent);
            dx = dx - (dcW_curr-dcW_min);
        end
    end
    
    methods (Access=private)
        function listen_for_parent_animation_changes(h)
            % Find parent DialogBorder.
            % Listen for changes to its DialogAnimate service.
            % Change our DialogAnimate service state to match.
            
            parentDC = h.ParentDialogContent;
            
            parentDB = parentDC.DialogBorder;
            if hasService(parentDB,'DialogAnimate')
                % Set state of our local DialogBorder,
                % based on parentDB
                enableService(h.Dialog.DialogBorder, ...
                    'DialogAnimate',parentDB.Animation);
            end
            
            % Listen for changes to parentDC
            %
            % Do NOT listen to parentDB, as we re-parent the DB when we
            % undock/redock, and the listener-connection will be lost.
            if isprop(parentDC,'Animation')
                addlistener(parentDC,'Animation','PostSet', ...
                    @(src,ev)change_animation_state(h));
            end
        end
        
        %{
        function listen_for_parent_animation_changes(h)
            % Find parent DialogBorder.
            % Listen for changes to its DialogAnimate service.
            % Change our DialogAnimate service state to match.
            
            parentDB = h.ParentDialogContent.DialogBorder;
            if hasService(parentDB,'DialogAnimate')
                % Set state now
                enableService(h.Dialog.DialogBorder, ...
                    'DialogAnimate',parentDB.Animation);
                
                % Listen for future changes
                addlistener(parentDB,'Animation','PostSet', ...
                    @(src,ev)change_animation_state(h));
            end
        end
        %}
        
        function change_animation_state(h)
            % Dynamically update DialogAnimate state of child dialog border
            enableService(h.Dialog.DialogBorder,'DialogAnimate', ...
                h.ParentDialogContent.DialogBorder.Animation);
        end
    end
    
    methods (Static)
        function [pv,parentDlg,childDialog,useParentLayout] = parseArgs(pv)
            % Parse arguments and set property values
            % Syntax:
            %    (parent,dialogContent,pvPairs...)
            %    (dialogContent,pvPairs...)
            %    (pvPairs...)
            %
            % Separately returns 'Dialog' and 'Parent' values if either
            % property is specified as a value-only argument, or if they appear as
            % parameter/value pairs. 'Dialog' and 'Parent' will not appear in the
            % returned pv cell-vector. All other properties are returned in the pv-pair
            % cell-vector.
            
            parentDlg = [];
            childDialog = [];
            useParentLayout = false; % default
            if ~isempty(pv)
                % See if dialog and parent were specified as value-only args
                char1 = ischar(pv{1}) || (isstring(pv{1}) && isscalar(pv{1}));
                char2 = ischar(pv{2}) || (isstring(pv{2}) && isscalar(pv{2}));
                if ~char1 && ~char2
                    % First two args are dialog and parent handles.
                    % All remaining args are Param/Value pairs.
                    parentDlg = pv{1};
                    childDialog = pv{2};
                    pv = pv(3:end);
                elseif ~char1
                    childDialog = pv{1};
                    pv = pv(2:end);
                end
                
                % Check for 'Parent', 'Dialog' and 'ParticipateInParentLayout' properties.
                % Return these separately from the pv-pair list.
                %
                % NOTE: 'Parent' and 'Dialog' may be specified as value-only arguments,
                % as param/value pairs, or both.  They may be specified multiple times.
                
                idx = find(strcmpi('Parent',pv(1:2:end)));
                if ~isempty(idx)
                    % Copy the Parent property and remove pair from pv
                    parentDlg = pv{2*idx};
                    pv(2*idx-1 : 2*idx) = [];
                end
                
                idx = find(strcmpi('Dialog',pv(1:2:end)));
                if ~isempty(idx)
                    % Copy the Dialog property and remove pair from pv
                    childDialog = pv{2*idx};
                    pv(2*idx-1 : 2*idx) = [];
                end
                
                idx = find(strcmpi('ParticipateInParentLayout',pv(1:2:end)));
                if ~isempty(idx)
                    % Copy the ParticipateInParentLayout property and remove pair from pv
                    useParentLayout = pv{2*idx};
                    pv(2*idx-1 : 2*idx) = [];
                end
            end
        end
    end
end


% [EOF]
