classdef AbstractTunerDlg < handle
    % AbstractTunerDlg defines PID tuner in MATLAB and Simulink
    
    % Author(s): R. Chen
    % Copyright 2009-2010 The MathWorks, Inc.
 
    properties
        
        Handles
        Listeners
        
        DataSrc             % internal data object
        
        DesignModeContents  % basic and extended
        CurrentDesignMode   % changeable by the toolbar combobox
        
        BackgroundColor     % use java L&F background color for UI controls
        FormContents
        PIDTypeContents
        PIDSTDTypeContents
        
        MinWidth
        MinHeight
        
    end
    
    properties (SetObservable = true)
        CurrentWC
        CurrentPM
    end
    
    methods(Access = 'public')
        
        % Constructor
        function this = AbstractTunerDlg()
            % set constants
            this.setConstants;
        end
        
        % set visible
        function show(this)
            figure(this.Handles.Figure);
        end
        
        % update one-click PID design
        function design(this)
            % one click design with default PM
            options = pidtuneOptions;
            WC = this.DataSrc.oneclick(options.PhaseMargin);
            % set Current WC and PM
            this.CurrentWC = WC;
            this.CurrentPM = options.PhaseMargin;
            % update status text
            if ~this.DataSrc.IsStable
                this.setStatusText(pidtool.utPIDgetStrings('cst','tunerdlg_oneclick_failure'),'warning');
            elseif this.DataSrc.NeedsIntegrator
                this.setStatusText(ctrlMsgUtils.message('Control:design:pidtune11'),'warning');
            else
                this.setStatusText('');
            end
        end
        
        % update interactive PID design and refresh plot panel
        function fastdesign(this)
            % Callback when desired WC or PM is changed from the control
            % design panel and interactive design is executed
            % interactive design
            this.DataSrc.fastdesign(this.CurrentWC,this.CurrentPM);
            % update plot panel
            s = this.DataSrc.generateTunedStructure;
            this.Handles.PlotPanel.setTunedController(s);
            % update status text
            % update status text
            if ~this.DataSrc.IsStable
                this.setStatusText(pidtool.utPIDgetStrings('cst','tunerdlg_oneclick_failure'),'warning');
            elseif this.DataSrc.NeedsIntegrator
                this.setStatusText(ctrlMsgUtils.message('Control:design:pidtune11'),'warning');
            else
                this.setStatusText('');
            end
        end
        
        % set status text
        function setStatusText(this, text, type)
            if isempty(text)
                this.Handles.StatusLabel.setIcon([]);
                this.Handles.StatusLabel.setText('');
            else
                switch type
                    case 'info'
                        this.Handles.StatusLabel.setIcon(com.mathworks.common.icons.DialogIcon.INFO.getIcon);
                    case 'warning'
                        this.Handles.StatusLabel.setIcon(com.mathworks.common.icons.DialogIcon.WARNING.getIcon);
                    case 'error'
                        this.Handles.StatusLabel.setIcon(com.mathworks.common.icons.DialogIcon.ERROR.getIcon);
                end
                this.Handles.StatusLabel.setText(text);
            end
        end
        
        % close function
        function close(this)
            %CLOSE exit function
            set(this.Handles.Figure,'CloseRequestFcn',[]);
            set(this.Handles.Figure,'DeleteFcn',[]);
            delete(this.Handles.Figure);
            delete(this.DataSrc);
            delete(this.Handles.PlotPanel);
            delete(this.Handles.DesignPanelBasic);
            delete(this.Handles.DesignPanelAdvanced);
            delete(this.Handles.PreferenceDlg.Handles.Figure);
            delete(this.Handles.PreferenceDlg);
            delete(this)
        end
        
    end
    
    methods (Abstract)
        initialize(this)
    end

    methods (Access = 'protected', Abstract)
        build(this)
        layout(this)
        importplant(this)
        exportplant(this)
        s = loadPreferences(this)
        ResetCallback(this)
    end
    
    %Protected methods
    methods(Access = 'protected')
        
        function setConstants(this) 
            % set figure size limits
            h0 = figure('Visible','off');
            SZ = hgconvertunits(h0,[1 1 1024 768],'pixels','characters',0); % size based on sys-requirements for MOTW
            delete(h0);
            this.MinWidth = min(160,SZ(3));
            this.MinHeight = min(54,0.85*SZ(4));
            
            % available choices
            this.DesignModeContents = {'basic','extended'};
            this.FormContents = {'parallel','standard'};
            this.PIDTypeContents = {'p','i','pi','pd','pdf','pid','pidf','pi2','pd2','pdf2','pid2','pidf2','i-pd','i-pdf','id-p','idf-p','pi-d','pi-df'};
            this.PIDSTDTypeContents = {'p','pi','pd','pdf','pid','pidf'};
            % set default background color as java background color
            foo = javaObjectEDT('com.mathworks.mwswing.MJPanel');            
            Red = foo.getBackground.getRed;
            Green = foo.getBackground.getGreen;
            Blue = foo.getBackground.getBlue;
            this.BackgroundColor = [Red Green Blue]/255;
        end
        
        % set axes mode
        function sliderMouseAction(this, hDesignPanel)
            % when knob is pressed, set refresh mode to quick to speed up
            % plot refreshment; when knob is released, set the mode back to
            % normal and force DataSrc dirty to repaint.
            hw = ctrlMsgUtils.SuspendWarnings; %#ok<*NASGU>
            hResponses = this.Handles.PlotPanel.Handles.hResponsePlot.Responses;
            if hDesignPanel.MouseIsDragging
                set(hResponses,'RefreshMode','quick');
            else
                set(hResponses,'RefreshMode','normal');
                for ct=1:length(hResponses)
                    hResponses(ct).DataSrc.send('SourceChanged');
                end
                % Manually force restrict x limit to 20/wc (g778710)
                % because this callback may be called after "updateAxes" is
                % called (racing condition)
                this.Handles.PlotPanel.refreshXLIM;
            end
        end

        % add listeners
        function addListeners(this)
            % initialize design panel listeners here to avoid sending GUI events at the build stage 
            this.Handles.DesignPanelBasic.addGUIListeners;
            this.Handles.DesignPanelAdvanced.addGUIListeners;
            % initialize plot panel listeners here to avoid sending GUI events at the build stage 
            this.Handles.PlotPanel.addGUIListeners;
            % listen to WC and PM changed events from basic design panel
            this.Listeners = addlistener(this.Handles.DesignPanelBasic,'WC','PostSet',@(x,y) responseSpeedUpdated(this));
            this.Listeners(2) = addlistener(this.Handles.DesignPanelBasic,'PM','PostSet',@(x,y) transientUpdated(this));
            % listen to WC and PM changed events from extended design panel
            this.Listeners(3) = addlistener(this.Handles.DesignPanelAdvanced,'WC','PostSet',@(x,y) wcUpdated(this));
            this.Listeners(4) = addlistener(this.Handles.DesignPanelAdvanced,'PM','PostSet',@(x,y) pmUpdated(this));
            % listen to slider mouse actions
            this.Listeners(5) = addlistener(this.Handles.DesignPanelBasic,'MouseIsDragging','PostSet',@(x,y) sliderMouseAction(this, this.Handles.DesignPanelBasic));
            this.Listeners(6) = addlistener(this.Handles.DesignPanelAdvanced,'MouseIsDragging','PostSet',@(x,y) sliderMouseAction(this, this.Handles.DesignPanelAdvanced));
            % listen to current WC/PM change
            this.Listeners(7) = addlistener(this,'CurrentWC','PostSet',@(x,y) fastdesign(this));
            this.Listeners(8) = addlistener(this,'CurrentPM','PostSet',@(x,y) fastdesign(this));
        end
        
        %% Build common GUI components
        function buildFigure(this)
            this.Handles.Figure = figure('Color',this.BackgroundColor,...
                 'WindowStyle','normal',...
                 'IntegerHandle','off', ...
                 'Menubar','None',...
                 'Toolbar','None',...
                 'DockControl','off',...
                 'Name',pidtool.utPIDgetStrings('cst','tunerdlg_title'), ...
                 'NumberTitle','off', ...
                 'Unit','pixels', ...
                 'Visible','off', ...
                 'HandleVisibility','off',...
                 'UserData',this);
        end

        function buildStatusBar(this)
            this.Handles.StatusLabel = javaObjectEDT('com.mathworks.mwswing.MJLabel');            
            this.Handles.StatusLabel.setName('PIDTUNER_STATUSLABEL');
            this.Handles.StatusLabel.setHorizontalAlignment(javax.swing.SwingConstants.LEFT);
            StatusBarPanel = javaObjectEDT('com.mathworks.mwswing.MJPanel',java.awt.BorderLayout(0,0));
            StatusBarPanel.add(this.Handles.StatusLabel,java.awt.BorderLayout.WEST);
            [~, StatusBarPanelCONTAINER] = matlab.ui.internal.JavaMigrationTools.suppressedJavaComponent(StatusBarPanel,[0.1,0.1,0.9,0.9],this.Handles.Figure);
            set(StatusBarPanelCONTAINER,'units','character')
            this.Handles.StatusBarPanel = StatusBarPanel;
            this.Handles.StatusBarPanelCONTAINER = StatusBarPanelCONTAINER;
        end
        
        function buildDesignPanel(this)
            this.Handles.DesignPanelBasic = pidtool.ControllerDesignPanelBasic(this.DataSrc.TimeUnit);
            this.Handles.DesignPanelAdvanced = pidtool.ControllerDesignPanelAdvanced(this.DataSrc.TimeUnit);
            PMVisible = ~(strcmp(this.DataSrc.Type,'p') || strcmp(this.DataSrc.Type,'i'));
            this.Handles.DesignPanelBasic.setPMVisible(PMVisible);
            this.Handles.DesignPanelAdvanced.setPMVisible(PMVisible);
            CardPanel = javaObjectEDT('com.mathworks.mwswing.MJPanel',java.awt.CardLayout);
            CardPanel.add(this.Handles.DesignPanelBasic.Handles.Panel, 'basic');
            CardPanel.add(this.Handles.DesignPanelAdvanced.Handles.Panel, 'extended');
            [~, CardPanelCONTAINER] = matlab.ui.internal.JavaMigrationTools.suppressedJavaComponent(CardPanel,[0.1,0.1,0.9,0.9],this.Handles.Figure);
            set(CardPanelCONTAINER,'units','character')
            this.Handles.CardPanel = CardPanel;
            this.Handles.CardPanelCONTAINER = CardPanelCONTAINER;
        end
        
        function buildToolbar(this, legendOnOff, designmode)
            % Create toolbar
            this.Handles.Toolbar = uitoolbar('Parent',this.Handles.Figure,'HandleVisibility','off');
            set(this.Handles.Toolbar,'Tag','PIDTOOLBAR');
            % import plant
            Cdata = imread(fullfile(matlabroot,'toolbox','shared','controllib','graphics','Resources','pid_plantimport.png'),'BackgroundColor',this.BackgroundColor);
            this.Handles.ImportToolbar = uipushtool('Parent',this.Handles.Toolbar,'Cdata',Cdata,'HandleVisibility','off');
            % export plant
            Cdata = imread(fullfile(matlabroot,'toolbox','shared','controllib','graphics','Resources','pid_plantexport.png'),'BackgroundColor',this.BackgroundColor);
            this.Handles.ExportToolbar = uipushtool('Parent',this.Handles.Toolbar,'Cdata',Cdata,'HandleVisibility','off');
            set(this.Handles.ExportToolbar,'tooltip',pidtool.utPIDgetStrings('cst','toolbar_tooltip_ep'),'Tag','PIDTOOLBAR_EXPORT');
            % plot tools
            this.Handles.ZoomInToolbar = uitoolfactory(this.Handles.Toolbar,'Exploration.ZoomIn');
            set(this.Handles.ZoomInToolbar,'Separator','on','Tag','PIDTOOLBAR_ZOOMIN');
            this.Handles.ZoomOutToolbar = uitoolfactory(this.Handles.Toolbar,'Exploration.ZoomOut');
            set(this.Handles.ZoomOutToolbar,'Tag','PIDTOOLBAR_ZOOMOUT');
            this.Handles.PanToolbar = uitoolfactory(this.Handles.Toolbar,'Exploration.Pan');
            set(this.Handles.PanToolbar,'Tag','PIDTOOLBAR_PAN');
            % legend
            Cdata = imread(fullfile(matlabroot,'toolbox','shared','controllib','graphics','Resources','pid_legend.png'),'BackgroundColor',this.BackgroundColor);
            this.Handles.LegendToolbar = uitoggletool('Parent',this.Handles.Toolbar,'Cdata',Cdata,'HandleVisibility','off');
            set(this.Handles.LegendToolbar,'tooltip',pidtool.utPIDgetStrings('cst','toolbar_tooltip_lg'),'Tag','PIDTOOLBAR_LEGEND');
            set(this.Handles.LegendToolbar,'State',legendOnOff);
            % preference
            Cdata = imread(fullfile(matlabroot,'toolbox','shared','controllib','graphics','Resources','pid_preference.png'),'BackgroundColor',this.BackgroundColor);
            this.Handles.PreferenceToolbar = uipushtool('Parent',this.Handles.Toolbar,'Cdata',Cdata,'HandleVisibility','off');
            set(this.Handles.PreferenceToolbar,'Separator','on','tooltip',pidtool.utPIDgetStrings('cst','toolbar_tooltip_pref'),'Tag','PIDTOOLBAR_PREFERENCE');
            % reset
            Cdata = imread(fullfile(matlabroot,'toolbox','shared','controllib','graphics','Resources','pid_reset.png'),'BackgroundColor',this.BackgroundColor);
            this.Handles.ResetToolbar = uipushtool('Parent',this.Handles.Toolbar,'Cdata',Cdata,'HandleVisibility','off');
            set(this.Handles.ResetToolbar,'tooltip',pidtool.utPIDgetStrings('cst','toolbar_tooltip_reset'),'Tag','PIDTOOLBAR_RESET');
            % design mode
            this.Handles.DesignModeLabel = javaObjectEDT('com.mathworks.mwswing.MJLabel',[blanks(4) pidtool.utPIDgetStrings('cst','toolbar_dsnmode') blanks(1)]);
            this.Handles.DesignModeLabel.setName('PIDTUNER_DESIGNMODELABEL');
            pidtool.utPIDaddCSH('control',this.Handles.DesignModeLabel,'pidtuner_designmode');
            matlab.ui.internal.JavaMigrationTools.suppressedJavaComponent(this.Handles.DesignModeLabel,[.1,.1,.9,.9],this.Handles.Toolbar);
            this.Handles.DesignModeComboBox = javaObjectEDT('com.mathworks.mwswing.MJComboBox',pidtool.utPIDgetStrings('cst','prefdlg_ddm_combo',2));
            this.Handles.DesignModeComboBox.setName('PIDTUNER_DESIGNMODECOMBOBOX');
            this.Handles.DesignModeComboBox.setSelectedIndex(find(strcmp(designmode,this.DesignModeContents))-1);
            matlab.ui.internal.JavaMigrationTools.suppressedJavaComponent(this.Handles.DesignModeComboBox,[.1,.1,.9,.9],this.Handles.Toolbar);
            tmp = this.Handles.DesignModeComboBox.getMaximumSize;
            tmp.width = 130;
            this.Handles.DesignModeComboBox.setMaximumSize(tmp);
            % Callbacks
            set(this.Handles.ImportToolbar,'ClickedCallback',{@(x,y) ImportCallback(this)});
            set(this.Handles.ExportToolbar,'ClickedCallback',{@(x,y) ExportCallback(this)});
            set(this.Handles.LegendToolbar,'ClickedCallback',{@(x,y) LegendCallback(this)});
            set(this.Handles.PreferenceToolbar,'ClickedCallback',{@(x,y) PreferenceCallback(this)});
            set(this.Handles.ResetToolbar,'ClickedCallback',{@(x,y) ResetCallback(this)});
            h = handle(this.Handles.DesignModeComboBox,'callbackproperties');
            hListener = handle.listener(h, 'ActionPerformed',{@(x,y) DesignModeComboBoxCallback(this)});
            this.Handles.DesignModeComboBoxListener = hListener;
        end
        
        function plotchanged(this)
            % listen to the 'Settings' property of preference dialog
            Settings = this.Handles.PreferenceDlg.PlotSettings;
            % set property in plot panel
            Panel = this.Handles.PlotPanel;
            Panel.TunedAxesColor = Settings.TunedColor;
            Panel.BaseAxesColor = Settings.BaseColor;
            % refresh plot
            Panel.Handles.hResponsePlot.Responses(Panel.TunedAxesIndex).setstyle('Color',Panel.TunedAxesColor);
            Panel.Handles.hResponsePlot.Responses(Panel.BaseAxesIndex).setstyle('Color',Panel.BaseAxesColor);
            % refresh table
            tmp = javaObjectEDT('javax.swing.table.DefaultTableCellRenderer');
            color = Settings.TunedColor;
            jcolor = java.awt.Color(color(1),color(2),color(3));
            tmp.setForeground(jcolor);
            tmp.setBackground(java.awt.Color(this.BackgroundColor(1),this.BackgroundColor(2),this.BackgroundColor(3)));
            Panel.Handles.ParameterTable.getColumnModel.getColumn(Panel.TunedTableIndex).setCellRenderer(tmp);
            Panel.Handles.ParameterTable.repaint;
            tmp = javaObjectEDT('javax.swing.table.DefaultTableCellRenderer');
            tmp.setForeground(jcolor);
            tmp.setBackground(java.awt.Color(this.BackgroundColor(1),this.BackgroundColor(2),this.BackgroundColor(3)));
            Panel.Handles.MetricTable.getColumnModel.getColumn(Panel.TunedTableIndex).setCellRenderer(tmp);
            Panel.Handles.MetricTable.repaint;
        end
        
        function DesignModeComboBoxCallback(this)
            % set to new mode
            this.CurrentDesignMode = this.DesignModeContents{this.Handles.DesignModeComboBox.getSelectedIndex+1};
            % update design panel
            if strcmpi(this.CurrentDesignMode,'basic')
                this.Handles.DesignPanelBasic.CenterWC = this.Handles.DesignPanelAdvanced.CenterWC;
                this.Handles.DesignPanelBasic.WC = this.Handles.DesignPanelAdvanced.WC;
                this.Handles.DesignPanelBasic.PM = this.Handles.DesignPanelAdvanced.PM;
            else
                this.Handles.DesignPanelAdvanced.CenterWC = this.Handles.DesignPanelBasic.CenterWC;
                this.Handles.DesignPanelAdvanced.WC = this.Handles.DesignPanelBasic.WC;
                this.Handles.DesignPanelAdvanced.PM = this.Handles.DesignPanelBasic.PM;
            end
            % reset layout
            this.layout;
        end

        function ImportCallback(this)
            this.importplant;
        end

        function ExportCallback(this)
            this.exportplant;
        end

        function LegendCallback(this)
            this.Handles.PlotPanel.showLegend(strcmpi(get(this.Handles.LegendToolbar,'State'),'on'));
        end

        function PreferenceCallback(this)
            % reset current selection based on the GUI
            TunedColor = this.Handles.PlotPanel.TunedAxesColor;
            BaseColor = this.Handles.PlotPanel.BaseAxesColor;
            this.Handles.PreferenceDlg.refresh(TunedColor,BaseColor,this.DataSrc);
            this.Handles.PreferenceDlg.setVisible('on',this.Handles.Figure);
        end

        function [fw, fh] = setMinimumFigureWindow(this)
            p = get(this.Handles.Figure,'Position');
            fw = p(3);
            fh = p(4);
            if fw<this.MinWidth || fh<this.MinHeight
                % get current left top
                y = p(2)+p(4);
                p(3) = max(this.MinWidth,fw);
                p(4) = max(this.MinHeight,fh);
                p(2) = y-p(4);
                set(this.Handles.Figure,'Position',p);
                fw = p(3);
                fh = p(4);
            end
        end
        
    end
    
end

%% Listener Callbacks
function responseSpeedUpdated(this)
    % Callback when WC changed in basic mode
    if strcmpi(this.CurrentDesignMode,'basic')
        % update plot panel
        this.CurrentWC = this.Handles.DesignPanelBasic.WC;
    end
end

function transientUpdated(this)
    % Callback when WC changed in basic mode
    if strcmpi(this.CurrentDesignMode,'basic')
        % update plot panel
        this.CurrentPM = this.Handles.DesignPanelBasic.PM;
    end
end

function wcUpdated(this)
    % Callback when WC changed in extended mode
    if strcmpi(this.CurrentDesignMode,'extended')
        % update plot panel
        this.CurrentWC = this.Handles.DesignPanelAdvanced.WC;
    end
end

function pmUpdated(this)
    % Callback when WC changed in extended mode
    if strcmpi(this.CurrentDesignMode,'extended')
        % update plot panel
        this.CurrentPM = this.Handles.DesignPanelAdvanced.PM;
    end
end
