classdef ModelReducerApp < matlab.task.LiveTask
    % Reduce model order live task

    % Copyright 2018-2022 The MathWorks, Inc.

    
    % UI Components
    properties (Access = public, Transient, Hidden)
        ParentGrid                          matlab.ui.container.GridLayout
        ModelGrid                           matlab.ui.container.GridLayout
        BalancedTruncationGrid              matlab.ui.container.GridLayout
        ModalTruncationGrid                 matlab.ui.container.GridLayout
        PoleZeroGrid                        matlab.ui.container.GridLayout
        VisualizationGrid                   matlab.ui.container.GridLayout
        
        ModelAndMethodSectionLabel          matlab.ui.control.Label
        ModelDropDownLabel                  matlab.ui.control.Label
        ModelDropDown                       matlab.ui.control.internal.model.WorkspaceDropDown
        MethodDropDownLabel                 matlab.ui.control.Label
        MethodDropDown                      matlab.ui.control.DropDown
        
        BalancedTruncationComponents        
        ModalTruncationComponents             
        PoleZeroComponents                  
        
        ComparisonPlotLabel                   matlab.ui.control.Label
        ComparisonPlotDropDown                matlab.ui.control.DropDown
        ComparisonPlotPanel                   matlab.ui.container.Panel
        
        VisualizationSectionLabel           matlab.ui.control.Label
        OutputPlotLabel                     matlab.ui.control.Label
        OutputPlotDropDown                  matlab.ui.control.DropDown
    end
    
    % App State and View
    properties
        State
        Summary
        View
    end
    
    % App Data
    properties (Hidden, Transient)
        ReducedSystem
        ModelReducerData
        BalancedTruncationData
        ModalTruncationData
        PoleZeroSimplificationData
        
        OutputVariableName = 'sysReduced';
        OutputPlotTypes = {'none','step','impulse','bode','sigma','pz'};
        ComparisonPlotTypes = {'modelResponse','absError','relError'};
        InitialSystem = tf(1,[1 1]);
        Model
        InputUpdated = false;
        ShowDefaultOutputPlot = true;
    end
    
    % Private Properties
    properties (Access = private, Transient)
        BarSelectorListener
        FrequencySelectorMovedListeners
        
        State_I
    end
    
    %% API for Live Tasks
    methods
        %% Constructor
        function app = ModelReducerApp
            
        end
        
        function delete(app)
            deleteWidgetListeners(app);
        end
        
        %% State
        function set.State(app,state)
            if ~isempty(state.FrequencyRange)
                state.FrequencyRange = state.FrequencyRange(:)'; %row vector
            end
            if ~strcmp(app.State_I.InputVariableName,state.InputVariableName) %#ok<*MCSUP>
                if ~isempty(app.View.CurrentComparisonPlotWidget) ...
                        && isvalid(app.View.CurrentComparisonPlotWidget)
                    setVisible(app.View.CurrentComparisonPlotWidget,'off');
                end
                if ~isempty(app.View.CurrentAnalysisPlotWidget) ...
                        && isvalid(app.View.CurrentAnalysisPlotWidget)
                    setVisible(app.View.CurrentAnalysisPlotWidget,'off');
                end
                app.MethodDropDown.Enable = 'on';
                app.OutputPlotLabel.Visible = 'on';
                app.OutputPlotDropDown.Visible = 'on';
            else
                app.State_I.OutputUpdated = false;
                app.updateOutputColor();
            end


            app.State_I = state;


            if ~contains(app.State_I.InputVariableName,app.ModelDropDown.Items)
                app.ModelDropDown.Items = [app.ModelDropDown.Items,{app.State_I.InputVariableName}];
                app.ModelDropDown.ItemsData = [app.ModelDropDown.ItemsData,{app.State_I.InputVariableName}];
            end

            updateModel(app);

            if ~isempty(app.BalancedTruncationData) && isvalid(app.BalancedTruncationData)
                app.updateData();
            else
                app.createData();
            end
            app.pushStateToData();

            updateUIComponentVisibility(app);
            if ~isempty(app.State_I.InputVariableName)
                updateUIComponentValues(app);
                app.ModelDropDown.Value = app.State_I.InputVariableName;
            else
                app.ModelDropDown.Value = app.ModelDropDown.ItemsData{1};
            end
            updateFrequencyRangeSelector(app);

        end
        
        function state = get.State(app)
            state = app.State_I;
        end
       
        function reset(app)
            resetData(app,'balanced');
            resetView(app);
            updateUIComponentVisibility(app);
            updateUIComponentValues(app);
            updateOutputColor(app);
            updateFrequencyRangeSelector(app);
        end
        
         %% Code and summary generation
        function [code, outputs] = generateCode(app)
            if isempty(app.State_I.InputVariableName) || isempty(app.ModelReducerData)
                code = '';
                outputs = {};
            else
                code = cell(0,1);
                outputs = {app.OutputVariableName};
                absorbDelayCode = cell(0,1);
                localInputVariableName = ['`',app.State_I.InputVariableName,'`'];
                tempVariablesToClear = '';
                clearVarComment = ['% ',m('Control:embedded_apps:clearVarComment')];
                switch app.State_I.Method
                    case 'balanced'
                        if app.State_I.GenerateAbsorbDelayCode
                            localInputVariableName = 'DelayAbsorbedSystem';
                            absorbDelayCode = controllib.internal.codegen.appendMATLABCode(...
                                absorbDelayCode,['% ',m('Control:embedded_apps:absorbDelayComment')]);
                            absorbDelayCode = controllib.internal.codegen.appendMATLABCode(...
                                absorbDelayCode,sprintf('%s = absorbDelay(%s);',localInputVariableName,app.State_I.InputVariableName));
                            absorbDelayCode = controllib.internal.codegen.appendMATLABCode(...
                                absorbDelayCode,' ');
                            tempVariablesToClear = [tempVariablesToClear, localInputVariableName, ' '];
                        end
                        data = app.BalancedTruncationData;
                        code = controllib.internal.codegen.appendMATLABCode(...
                            code,['% ' getString(message('Control:mrtool:CodegenBTTitle'))]);
                        code = controllib.internal.codegen.appendMATLABCode(code,' ');
                        if ~isempty(absorbDelayCode)
                            code = [code; absorbDelayCode];
                        end
                        if getFreqIntervalsUsed(data)
                            code = controllib.internal.codegen.appendMATLABCode(...
                                code,['% ' getString(message('Control:mrtool:CodegenBTFreqReduceCommand'))]);
                        else
                            code = controllib.internal.codegen.appendMATLABCode(...
                                code,['% ' getString(message('Control:mrtool:CodegenBTReduceCommand'))]);
                        end
                        if getUseNCF(data)
                            code = controllib.internal.codegen.appendMATLABCode(code,sprintf('R = reducespec(%s,''ncf'');',localInputVariableName));
                        else
                            code = controllib.internal.codegen.appendMATLABCode(code,sprintf('R = reducespec(%s,''balanced'');',localInputVariableName));
                            code = mrtool.util.appendMATLABCodeForOptions(code,getReduceSpec(data).Options);
                        end
                        tempVariablesToClear = [tempVariablesToClear,'R',' '];
                        code = controllib.internal.codegen.appendMATLABCode(code,['% ' getString(message('Control:mrtool:CodegenProcessMOR'))]);
                        code = controllib.internal.codegen.appendMATLABCode(code,'R = process(R);');
                        code = controllib.internal.codegen.appendMATLABCode(code,['% ' getString(message('Control:mrtool:CodegenGetromMOR'))]);
                        reduceSysCommand = sprintf('%s = getrom(R,Order=%s',app.OutputVariableName,num2str(getReducedOrder(data)));
                        if ~strcmpi(getMethod(data),'MatchDC')
                            reduceSysCommand = [reduceSysCommand sprintf(',Method=''%s'');',getMethod(data))];
                        else
                            reduceSysCommand = [reduceSysCommand ');'];
                        end
                        code = controllib.internal.codegen.appendMATLABCode(code,reduceSysCommand);

                    case 'modal'
                        data = app.ModalTruncationData;
                        code = controllib.internal.codegen.appendMATLABCode(...
                            code,['% ' getString(message('Control:mrtool:CodegenMTTitle'))]);
                        code = controllib.internal.codegen.appendMATLABCode(code,' ');
                        code = controllib.internal.codegen.appendMATLABCode(...
                            code,['% ' m('Control:mrtool:CodegenMTReduceCommand')]);
                        code = controllib.internal.codegen.appendMATLABCode(code,sprintf('R = reducespec(%s,''modal'');',localInputVariableName));
                        tempVariablesToClear = [tempVariablesToClear,'R',' '];
                        code = mrtool.util.appendMATLABCodeForOptions(code,getReduceSpec(data).Options);
                        code = controllib.internal.codegen.appendMATLABCode(code,['% ' getString(message('Control:mrtool:CodegenProcessMOR'))]);
                        code = controllib.internal.codegen.appendMATLABCode(code,'R = process(R);');
                        code = controllib.internal.codegen.appendMATLABCode(code,['% ' getString(message('Control:mrtool:CodegenGetromMOR'))]);
                        frequencyRange = getFrequencyRange(data);
                        reduceSysCommand = sprintf('%s = getrom(R',app.OutputVariableName);
                        if ~isequal(frequencyRange,[0 Inf])
                            reduceSysCommand = [reduceSysCommand sprintf(',Frequency=%s',mat2str(frequencyRange))];
                        end
                        if ~strcmpi(getMethod(data),'MatchDC')
                            reduceSysCommand = [reduceSysCommand sprintf(',Method=''%s'');',getMethod(data))];
                        else
                            reduceSysCommand = [reduceSysCommand ');'];
                        end
                        code = controllib.internal.codegen.appendMATLABCode(code,reduceSysCommand);

                    case 'minreal'
                        data = app.PoleZeroSimplificationData;
                        code = controllib.internal.codegen.appendMATLABCode(...
                            code,['% ' m('Control:mrtool:CodegenPZTitle')]);
                        code = controllib.internal.codegen.appendMATLABCode(code,' ');
                        if app.State_I.GeneratePrescaleCode
                            prescalecode = sprintf('ScaledSystem = prescale(%s);',localInputVariableName);
                            prescalecomment = ['% ',m('Control:embedded_apps:prescaleComment')];
                            code = controllib.internal.codegen.appendMATLABCode(code,prescalecomment);
                            code = controllib.internal.codegen.appendMATLABCode(code,prescalecode);
                            code = controllib.internal.codegen.appendMATLABCode(code,' ');
                            localInputVariableName = 'ScaledSystem';
                            tempVariablesToClear = [tempVariablesToClear,localInputVariableName,' '];
                        end
                        code = controllib.internal.codegen.appendMATLABCode(...
                            code,['% ',m('Control:mrtool:CodegenPZTolerance')]);
                        code = controllib.internal.codegen.appendMATLABCode(code,getTolerance(data),'Tol');
                        code = controllib.internal.codegen.appendMATLABCode(code,' ');
                        code = controllib.internal.codegen.appendMATLABCode(code,['% ' getString(message('Control:mrtool:CodegenPZReduceCommand'))]);
                        minrealcode = sprintf('%s = minreal(%s,Tol);',app.OutputVariableName,localInputVariableName);
                        code = controllib.internal.codegen.appendMATLABCode(code,minrealcode);
                        tempVariablesToClear = [tempVariablesToClear,'Tol',' '];
                end
                if ~isempty(tempVariablesToClear)
                    code = controllib.internal.codegen.appendMATLABCode(code,' ');
                    code = controllib.internal.codegen.appendMATLABCode(code,clearVarComment);
                    code = controllib.internal.codegen.appendMATLABCode(code,['clear ',tempVariablesToClear]);
                end
                code = controllib.internal.codegen.cellstr2char(code);

                % Create visualization code
                visualizationCode = generateVisualizationCode(app);
                if ~isempty(visualizationCode)
                    code = [code newline newline visualizationCode];
                end
            end
        end
        
        function code = generateVisualizationCode(app)
            if isempty(app.State_I.InputVariableName) || ...
                    strcmp(app.State_I.CurrentOutputPlotType,'none')
                code = '';
            else
                localInputVariableName = ['`',app.State_I.InputVariableName,'`'];
                originalSystemLegend = m('Control:embedded_apps:originalModel');
                reducedSystemLegend = m('Control:embedded_apps:reducedModel');
                code = cell(0,1);
                plotcomment = ['% ',m('Control:embedded_apps:compareModel')];
                code = controllib.internal.codegen.appendMATLABCode(code,plotcomment);
                plotcode = sprintf('figure');
                code = controllib.internal.codegen.appendMATLABCode(code,plotcode);
                plotcode = sprintf('%splot(%s,sysReduced);',app.State_I.CurrentOutputPlotType,localInputVariableName);
                code = controllib.internal.codegen.appendMATLABCode(code,plotcode);
                legendcode1 = sprintf('legend([''%s ('',num2str(order(%s)),'' states)''],...',...
                    originalSystemLegend,localInputVariableName);
                legendcode2 = sprintf('       [''%s ('',num2str(order(%s)),'' states)'']);',...
                    reducedSystemLegend,app.OutputVariableName);
                code = controllib.internal.codegen.appendMATLABCode(code,legendcode1);
                code = controllib.internal.codegen.appendMATLABCode(code,legendcode2);
                gridcode = sprintf('grid on');
                code = controllib.internal.codegen.appendMATLABCode(code,gridcode);
                code = controllib.internal.codegen.cellstr2char(code);
            end
        end
        
        function summary = get.Summary(app)
            summary = m('Control:embedded_apps:modelReducerStaticSummary');
            if ~isempty(app.State_I.InputVariableName) && ~isempty(app.ReducedSystem)
                methodIdx = strcmp(app.MethodDropDown.ItemsData,app.State_I.Method);
                method = app.MethodDropDown.Items{methodIdx};
                summary = m('Control:embedded_apps:modelReducerDynamicSummary',app.State_I.InputVariableName, method);
            end
        end
                
        %% Update
        function postExecutionUpdate(app,varargin)
            updatePreview(app,varargin{:});
        end
        
        function View = getView(app)
            View = app.View;
        end
    end

    %% Implement Abstract methods
    methods (Access = protected)
        function setup(app)
            app.initializeState();
            app.initializeView();
            app.createComponents();
            app.updateUIComponentVisibility(true);
        end
    end
    
    %% State and View
    methods (Access = private)
        function initializeState(app)
            state.InputVariableName = '';
            state.Method = 'balanced';
            app.Model = app.InitialSystem;
            app.ReducedSystem = app.InitialSystem;
            app.ModelReducerData = mrtool.data.ModelWrapper('appInitialSystem',...
                                                    app.InitialSystem);
            state.FrequencyRange = [];
            state.ReducedOrder = [];
            state.PreserveDCGain = true;
            state.FrequencyRangeSelected = false;
            state.Tolerance = [];
            state.CurrentOutputPlotType = 'none';
            state.CurrentResponsePlotType = 'modelResponse';
            state.OutputUpdated = false;
            state.GeneratePrescaleCode = false;
            state.GenerateAbsorbDelayCode = false;
            state.FrequencyRangeUpperBound = pi/app.Model.Ts;

            app.State_I = state;
        end
        
        function initializeView(app)
            % Initialize the 'View' structure
            app.View.ComparisonPlotWidgets = cell(1,length(app.ComparisonPlotTypes));
            app.FrequencySelectorMovedListeners = cell(1,length(app.ComparisonPlotTypes));
            app.View.CurrentComparisonPlotWidget = [];
            app.View.CurrentAnalysisPlotWidget = [];
            app.View.LeftMargin = 60;
            app.View.RightMargin = 10;
            app.View.TopMargin = 20;
            app.View.BottomMargin = 40;
            app.View.OldOutputColor = [0.7 0.7 0.7];
            app.View.CurrentOutputColor = [1 0 0];
        end
        
        function resetData(app,method)
            if nargin > 1
                app.State_I.Method = method;
            end
            % Create data objects for each method (with defaults)
            createData(app);
            pushDataToState(app);
            app.State_I.OutputUpdated = false;
            % Update Components
            updateUIComponentValues(app);
        end
        
        function resetView(app)
            cellfun(@(x) delete(x),app.View.ComparisonPlotWidgets);
            app.View.ComparisonPlotWidgets = cell(1,length(app.ComparisonPlotTypes));
            app.View.CurrentComparisonPlotWidget = [];            
            
            delete(app.View.CurrentAnalysisPlotWidget);
            app.View.CurrentAnalysisPlotWidget = [];
            
            deleteWidgetListeners(app);            
        end
    end
    
    %% UI Components creation
    methods(Access = private)
        function createComponents(app)
            app.createFigureAndGridComponents();
            app.createModelAndMethodRow();
            app.createBalancedTruncationComponents();
            app.createModalTruncationComponents();
            app.createPoleZeroSimplificationComponents();
            app.createPreviewComponents();
        end
        
        function createFigureAndGridComponents(app)
            app.LayoutManager.Padding = 0;            
            app.ParentGrid = uigridlayout(app.LayoutManager);
            app.ParentGrid.ColumnWidth = {'1x'};
            app.ParentGrid.RowHeight = {54,86,215,185,54};
            app.ParentGrid.RowSpacing = 20;
            
            app.ModelGrid = uigridlayout(app.ParentGrid);
            app.ModelGrid.Layout.Row = 1;
            app.ModelGrid.Layout.Column = 1;
            app.ModelGrid.RowHeight = {22,22,'1x'};
            app.ModelGrid.ColumnWidth = {'fit',170,20,'fit',170,'1x'};
            app.ModelGrid.Padding = [0 0 10 0];
            
            app.BalancedTruncationGrid = uigridlayout(app.ParentGrid);
            app.BalancedTruncationGrid.Layout.Row = 2;
            app.BalancedTruncationGrid.Layout.Column = 1;
            app.BalancedTruncationGrid.RowHeight = {22,22,22};
            app.BalancedTruncationGrid.ColumnWidth = {'fit',50,'fit','fit',60,60,'fit','1x'};
            app.BalancedTruncationGrid.Padding = [0 0 0 0];
            
            app.ModalTruncationGrid = uigridlayout(app.ParentGrid);
            app.ModalTruncationGrid.Layout.Row = 2;
            app.ModalTruncationGrid.Layout.Column = 1;
            app.ModalTruncationGrid.RowHeight = {22,22,22};
            app.ModalTruncationGrid.ColumnWidth = {'fit',60,60,'fit','1x'};
            app.ModalTruncationGrid.Padding = [0 0 0 0];
            app.ModalTruncationGrid.Visible = 'off';
            
            app.PoleZeroGrid = uigridlayout(app.ParentGrid);
            app.PoleZeroGrid.Layout.Row = 2;
            app.PoleZeroGrid.Layout.Column = 1;
            app.PoleZeroGrid.RowHeight = {22,22};
            app.PoleZeroGrid.ColumnWidth = {100,50,200,50,100,'1x'};
            app.PoleZeroGrid.Padding = [0 0 0 0];
            app.PoleZeroGrid.Visible = 'off';
   
            app.VisualizationGrid = uigridlayout(app.ParentGrid);
            app.VisualizationGrid.Layout.Row = 5;
            app.VisualizationGrid.Layout.Column = 1;
            app.VisualizationGrid.RowHeight = {22,22};
            app.VisualizationGrid.ColumnWidth = {'fit',120,'1x'};
            app.VisualizationGrid.Padding = [0 0 0 0];
        end
        
        function createModelAndMethodRow(app)
            app.ModelAndMethodSectionLabel = ...
                localBuildHeader(app.ModelGrid,1,m('Control:embedded_apps:selectModelAndMethod'));
            
            app.ModelDropDownLabel = uilabel(app.ModelGrid);
            app.ModelDropDownLabel.Layout.Row = 2;
            app.ModelDropDownLabel.Layout.Column = 1;
            app.ModelDropDownLabel.Text = m('Control:embedded_apps:model');
            
            app.ModelDropDown = matlab.ui.control.internal.model.WorkspaceDropDown('Parent',app.ModelGrid);
            app.ModelDropDown.Layout.Row = 2;
            app.ModelDropDown.Layout.Column = 2;
            app.ModelDropDown.Tag = 'InputWidget';
            app.ModelDropDown.FilterVariablesFcn = @(x) updateModelDropDownItems(x);
            app.ModelDropDown.ValueChangedFcn = @(~,e)cbModelDropDownValueChanged(app,e);
            app.ModelDropDown.Tooltip = m('Control:embedded_apps:ModelDropDownTooltip');
            
            app.MethodDropDownLabel = uilabel(app.ModelGrid);
            app.MethodDropDownLabel.Layout.Row = 2;
            app.MethodDropDownLabel.Layout.Column = 4;
            app.MethodDropDownLabel.Text = m('Control:embedded_apps:method');
            
            app.MethodDropDown = uidropdown(app.ModelGrid);
            app.MethodDropDown.Layout.Row = 2;
            app.MethodDropDown.Layout.Column = 5;
            app.MethodDropDown.Items = {m('Control:embedded_apps:balancedText'); m('Control:embedded_apps:modalText'); m('Control:embedded_apps:minrealText')};
            app.MethodDropDown.ItemsData = {'balanced'; 'modal'; 'minreal'};
            app.MethodDropDown.ValueChangedFcn = @(~,e)cbMethodDropDownValueChanged(app,e);
            app.MethodDropDown.Enable = 'off';
            app.MethodDropDown.Value = app.State_I.Method;
            app.MethodDropDown.Tooltip = m('Control:embedded_apps:MethodDropDownTooltip');
        end
        
        function createBalancedTruncationComponents(app)
            app.BalancedTruncationComponents.SectionLabel = ...
                localBuildHeader(app.BalancedTruncationGrid,1,m('Control:embedded_apps:specifyOrderAndFrequency'));
            
            app.BalancedTruncationComponents.ReducedOrderLabel = uilabel(app.BalancedTruncationGrid);
            app.BalancedTruncationComponents.ReducedOrderLabel.Layout.Row = 2;
            app.BalancedTruncationComponents.ReducedOrderLabel.Layout.Column = 1;
            app.BalancedTruncationComponents.ReducedOrderLabel.Text = ...
                                    m('Control:embedded_apps:reducedOrder');
            app.BalancedTruncationComponents.ReducedOrderLabel.Visible = 'on';
            
            app.BalancedTruncationComponents.ReducedOrderSpinner = uispinner(app.BalancedTruncationGrid);
            app.BalancedTruncationComponents.ReducedOrderSpinner.Layout.Row = 2;
            app.BalancedTruncationComponents.ReducedOrderSpinner.Layout.Column = 2;
            app.BalancedTruncationComponents.ReducedOrderSpinner.Visible = 'on';
            app.BalancedTruncationComponents.ReducedOrderSpinner.ValueChangedFcn = ...
                @(~,e) cbReducedOrderSpinnerValueChanged(app,e);
            app.BalancedTruncationComponents.ReducedOrderSpinner.Tooltip = ...
                                    m('Control:mrtool:BTOrderTooltip');
            
            app.BalancedTruncationComponents.PreserveDCGainCheckBox = uicheckbox(app.BalancedTruncationGrid);
            app.BalancedTruncationComponents.PreserveDCGainCheckBox.Layout.Row = 2;
            app.BalancedTruncationComponents.PreserveDCGainCheckBox.Layout.Column = 3;
            app.BalancedTruncationComponents.PreserveDCGainCheckBox.Value = true;
            app.BalancedTruncationComponents.PreserveDCGainCheckBox.Text = ...
                                    m('Control:mrtool:OptionsMethodLabel');
            app.BalancedTruncationComponents.PreserveDCGainCheckBox.Visible = 'on';
            app.BalancedTruncationComponents.PreserveDCGainCheckBox.ValueChangedFcn = ...
                @(~,e) cbPreserveDCGainCheckBoxValueChanged(app,e);
            app.BalancedTruncationComponents.PreserveDCGainCheckBox.Tooltip = ...
                                    m('Control:mrtool:OptionsMethodTooltip');
            
            
            app.BalancedTruncationComponents.FrequencyRangeCheckBox = uicheckbox(app.BalancedTruncationGrid);
            app.BalancedTruncationComponents.FrequencyRangeCheckBox.Layout.Row = 2;
            app.BalancedTruncationComponents.FrequencyRangeCheckBox.Layout.Column = 4;
            app.BalancedTruncationComponents.FrequencyRangeCheckBox.Value = true;
            app.BalancedTruncationComponents.FrequencyRangeCheckBox.Text = ...
                                    m('Control:embedded_apps:frequencyRange');
            app.BalancedTruncationComponents.FrequencyRangeCheckBox.Visible = 'on';
            app.BalancedTruncationComponents.FrequencyRangeCheckBox.ValueChangedFcn = ...
                @(~,e) cbFrequencyRangeCheckBoxValueChanged(app,e);
            app.BalancedTruncationComponents.FrequencyRangeCheckBox.Tooltip = ...
                                    m('Control:embedded_apps:FreqRangeCheckTooltip');
            
            app.BalancedTruncationComponents.FrequencyRangeLowerEditField = uieditfield(app.BalancedTruncationGrid,'numeric');
            app.BalancedTruncationComponents.FrequencyRangeLowerEditField.Layout.Row = 2;
            app.BalancedTruncationComponents.FrequencyRangeLowerEditField.Layout.Column = 5;
            app.BalancedTruncationComponents.FrequencyRangeLowerEditField.Limits = [0 Inf];
            app.BalancedTruncationComponents.FrequencyRangeLowerEditField.LowerLimitInclusive = 'off';
            app.BalancedTruncationComponents.FrequencyRangeLowerEditField.UpperLimitInclusive = 'off';
            app.BalancedTruncationComponents.FrequencyRangeLowerEditField.Visible = 'on';
            app.BalancedTruncationComponents.FrequencyRangeLowerEditField.Enable = 'off';
            app.BalancedTruncationComponents.FrequencyRangeLowerEditField.ValueChangedFcn = ...
                @(~,e) cbFrequencyRangeLowerEditFieldValueChanged(app,e);
            app.BalancedTruncationComponents.FrequencyRangeLowerEditField.Tooltip = ...
                                    m('Control:embedded_apps:LowerFreqRangeTooltip');
            
            app.BalancedTruncationComponents.FrequencyRangeUpperEditField = uieditfield(app.BalancedTruncationGrid,'numeric');
            app.BalancedTruncationComponents.FrequencyRangeUpperEditField.Layout.Row = 2;
            app.BalancedTruncationComponents.FrequencyRangeUpperEditField.Layout.Column = 6;
            app.BalancedTruncationComponents.FrequencyRangeUpperEditField.Limits = [0 Inf];
            app.BalancedTruncationComponents.FrequencyRangeUpperEditField.LowerLimitInclusive = 'off';
            app.BalancedTruncationComponents.FrequencyRangeUpperEditField.Visible = 'on';
            app.BalancedTruncationComponents.FrequencyRangeUpperEditField.Enable = 'off';
            app.BalancedTruncationComponents.FrequencyRangeUpperEditField.ValueChangedFcn = ...
                @(~,e) cbFrequencyRangeUpperEditFieldValueChanged(app,e);
            app.BalancedTruncationComponents.FrequencyRangeUpperEditField.Tooltip = ...
                                    m('Control:embedded_apps:UpperFreqRangeTooltip');
            
            app.BalancedTruncationComponents.FrequencyUnitLabel = uilabel(app.BalancedTruncationGrid);
            app.BalancedTruncationComponents.FrequencyUnitLabel.Layout.Row = 2;
            app.BalancedTruncationComponents.FrequencyUnitLabel.Layout.Column = 7;
            app.BalancedTruncationComponents.FrequencyUnitLabel.Text = m('Control:embedded_apps:strFreqUnit');
            app.BalancedTruncationComponents.FrequencyUnitLabel.Visible = 'on';
        end
        
        function createModalTruncationComponents(app)
            app.ModalTruncationComponents.SectionLabel = ...
                localBuildHeader(app.ModalTruncationGrid,1,m('Control:embedded_apps:specifyCutoffFrequency'));
            
            app.ModalTruncationComponents.FrequencyCutoffLabel = uilabel(app.ModalTruncationGrid);
            app.ModalTruncationComponents.FrequencyCutoffLabel.Layout.Row = 2;
            app.ModalTruncationComponents.FrequencyCutoffLabel.Layout.Column = 1;
            app.ModalTruncationComponents.FrequencyCutoffLabel.Text = m('Control:embedded_apps:cutoffFrequency');
            
            app.ModalTruncationComponents.FrequencyCutoffLowerEditField = uieditfield(app.ModalTruncationGrid,'numeric');
            app.ModalTruncationComponents.FrequencyCutoffLowerEditField.Layout.Row = 2;
            app.ModalTruncationComponents.FrequencyCutoffLowerEditField.Layout.Column = 2;
            app.ModalTruncationComponents.FrequencyCutoffLowerEditField.Limits = [0 Inf];
            app.ModalTruncationComponents.FrequencyCutoffLowerEditField.LowerLimitInclusive = 'off';
            app.ModalTruncationComponents.FrequencyCutoffLowerEditField.UpperLimitInclusive = 'off';
            app.ModalTruncationComponents.FrequencyCutoffLowerEditField.ValueChangedFcn = ...
                @(~,e) cbFrequencyCutoffLowerEditFieldValueChanged(app,e);
            app.ModalTruncationComponents.FrequencyCutoffLowerEditField.Tooltip = ...
                                    m('Control:embedded_apps:LowerCutoffTooltip');
            
            app.ModalTruncationComponents.FrequencyCutoffUpperEditField = uieditfield(app.ModalTruncationGrid,'numeric');
            app.ModalTruncationComponents.FrequencyCutoffUpperEditField.Layout.Row = 2;
            app.ModalTruncationComponents.FrequencyCutoffUpperEditField.Layout.Column = 3;
            app.ModalTruncationComponents.FrequencyCutoffUpperEditField.Limits = [0 Inf];
            app.ModalTruncationComponents.FrequencyCutoffUpperEditField.LowerLimitInclusive = 'off';
            app.ModalTruncationComponents.FrequencyCutoffUpperEditField.ValueChangedFcn = ...
                @(~,e) cbFrequencyCutoffUpperEditFieldValueChanged(app,e);
            app.ModalTruncationComponents.FrequencyCutoffUpperEditField.Tooltip = ...
                                    m('Control:embedded_apps:UpperCutoffTooltip');
            
            app.ModalTruncationComponents.FrequencyUnitLabel = uilabel(app.ModalTruncationGrid);
            app.ModalTruncationComponents.FrequencyUnitLabel.Layout.Row = 2;
            app.ModalTruncationComponents.FrequencyUnitLabel.Layout.Column = 4;
            app.ModalTruncationComponents.FrequencyUnitLabel.Text = m('Control:embedded_apps:strFreqUnit');
        end
        
        function createPoleZeroSimplificationComponents(app)
            app.PoleZeroComponents.SectionLabel = ...
                localBuildHeader(app.PoleZeroGrid,1,m('Control:embedded_apps:specifyTolerance'));
            
            app.PoleZeroComponents.ToleranceLabel = uilabel(app.PoleZeroGrid);
            app.PoleZeroComponents.ToleranceLabel.Layout.Row = 2;
            app.PoleZeroComponents.ToleranceLabel.Layout.Column = 1;
            app.PoleZeroComponents.ToleranceLabel.Text = m('Control:embedded_apps:tolerance');
         
            app.PoleZeroComponents.ToleranceLessLabel = uilabel(app.PoleZeroGrid);
            app.PoleZeroComponents.ToleranceLessLabel.Layout.Row = 2;
            app.PoleZeroComponents.ToleranceLessLabel.Layout.Column = 2;
            app.PoleZeroComponents.ToleranceLessLabel.Text = m('Control:embedded_apps:toleranceLess');
            app.PoleZeroComponents.ToleranceLessLabel.HorizontalAlignment = 'right';
           
            app.PoleZeroComponents.ToleranceSlider = uislider(app.PoleZeroGrid);
            app.PoleZeroComponents.ToleranceSlider.Layout.Row = 2;
            app.PoleZeroComponents.ToleranceSlider.Layout.Column = 3;
            app.PoleZeroComponents.ToleranceSlider.Limits = [-8 0];
            app.PoleZeroComponents.ToleranceSlider.MajorTicks = [];
            app.PoleZeroComponents.ToleranceSlider.MinorTicks = [];
            app.PoleZeroComponents.ToleranceSlider.ValueChangedFcn = ...
                @(~,e) cbToleranceSliderValueChanged(app,e);
            app.PoleZeroComponents.ToleranceSlider.ValueChangingFcn = ...
                @(~,e) cbToleranceSliderValueChanging(app,e);
            app.PoleZeroComponents.ToleranceSlider.Tooltip = ...
                                    m('Control:mrtool:ToleranceTooltip');
            
            app.PoleZeroComponents.ToleranceMoreLabel = uilabel(app.PoleZeroGrid);
            app.PoleZeroComponents.ToleranceMoreLabel.Layout.Row = 2;
            app.PoleZeroComponents.ToleranceMoreLabel.Layout.Column = 4;
            app.PoleZeroComponents.ToleranceMoreLabel.Text = m('Control:embedded_apps:toleranceMore');
            app.PoleZeroComponents.ToleranceMoreLabel.HorizontalAlignment = 'left';
            
            app.PoleZeroComponents.ToleranceEditField = uieditfield(app.PoleZeroGrid,'numeric');
            app.PoleZeroComponents.ToleranceEditField.Layout.Row = 2;
            app.PoleZeroComponents.ToleranceEditField.Layout.Column = 5;
            app.PoleZeroComponents.ToleranceEditField.Limits = [1e-8 1];
            app.PoleZeroComponents.ToleranceEditField.ValueChangedFcn = ...
                @(~,e) cbToleranceEditFieldValueChanged(app,e);
            app.PoleZeroComponents.ToleranceEditField.Tooltip = ...
                                    m('Control:mrtool:ToleranceTooltip');
        end
        
        function createPreviewComponents(app)          
            app.ComparisonPlotLabel = uilabel(app.BalancedTruncationGrid);
            app.ComparisonPlotLabel.Layout.Row = 3;
            app.ComparisonPlotLabel.Layout.Column = 1;
            app.ComparisonPlotLabel.Text = m('Control:mrtool:ComparisonPlot');
             
            app.ComparisonPlotDropDown = uidropdown(app.BalancedTruncationGrid);
            app.ComparisonPlotDropDown.Layout.Row = 3;
            app.ComparisonPlotDropDown.Layout.Column = [2 3];
            app.ComparisonPlotDropDown.ItemsData = app.ComparisonPlotTypes;
            app.ComparisonPlotDropDown.Items = {m('Control:mrtool:ResponsePlot'),...
                                              m('Control:mrtool:AbsoluteErrorPlot'),...
                                              m('Control:mrtool:RelativeErrorPlot')};
            app.ComparisonPlotDropDown.Value = app.State_I.CurrentResponsePlotType;
            app.ComparisonPlotDropDown.ValueChangedFcn = ...
                @(~,e) cbResponsePlotDropDownValueChangedFcn(app,e);
            app.ComparisonPlotDropDown.Visible = 'on';
                                                
            app.VisualizationSectionLabel = ...
                localBuildHeader(app.VisualizationGrid,1,m('Control:embedded_apps:visualizeResults'));

            app.OutputPlotLabel = uilabel(app.VisualizationGrid);
            app.OutputPlotLabel.Layout.Row = 2;
            app.OutputPlotLabel.Layout.Column = 1;
            app.OutputPlotLabel.Text = m('Control:embedded_apps:outputPlot');
            app.OutputPlotLabel.Visible = 'on';
            
            app.OutputPlotDropDown = uidropdown(app.VisualizationGrid);
            app.OutputPlotDropDown.Layout.Row = 2;
            app.OutputPlotDropDown.Layout.Column = 2;
            app.OutputPlotDropDown.Items = ...
                cellfun(@(x) m(['Control:embedded_apps:',x]), app.OutputPlotTypes,'UniformOutput',false);
            app.OutputPlotDropDown.ItemsData = app.OutputPlotTypes;
            app.OutputPlotDropDown.Value = app.State_I.CurrentOutputPlotType;
            app.OutputPlotDropDown.ValueChangedFcn = ...
                @(~,e) cbOutputPlotDropDownValueChangedFcn(app,e);
            app.OutputPlotDropDown.Visible = 'on';
        end
        
        function createToolbar(app,h,plotHandle,legendValue)
            arguments
                app
                h
                plotHandle
                legendValue = 'off'
            end

            % Create custom toolbar (pan,zoomin,zoomout,legend)
            controllib.plot.internal.createToolbar(h);
            
            % Set value of legend button
            tb = h.Toolbar;
            tb.Children(4).Value = legendValue;

            % Add fullview button
            btn = axtoolbarbtn(tb,'push');
            btn.Icon = 'restoreview';
            btn.Tooltip = getString(message('Controllib:plots:strFullView'));
            btn.ButtonPushedFcn = @(es,ed) cbFullView(app,plotHandle);
            % Reorder buttons
            tb.Children = [tb.Children(2:4); tb.Children(1); tb.Children(5)];

            % Modify legend button callback for toolbar on tiledlayout
            if ~isa(h,'matlab.graphics.axis.Axes')
                tb.Children(5).ValueChangedFcn = @(es,ed) localToggleLegend(es,ed);
            end

            % Local function
            function localToggleLegend(es,ed)
                % To Do when CurrentAxes is implemented
            end
        end
    end
    
    %% Update methods
    methods (Access = private)
        function updatePreview(app,data)
            w = warning('off','Control:analysis:InaccurateResponse');
            if strcmp(app.State_I.Method,'balanced')
                toolData = app.BalancedTruncationData;
            else
                toolData = app.ModalTruncationData;
            end
            if nargin > 1
                app.ReducedSystem = data.(app.OutputVariableName);
                setReducedSystem(toolData,app.ReducedSystem);
                app.State_I.OutputUpdated = true;
            end
            
            if ~isempty(app.State_I.InputVariableName) ...
                    && (~isequal(app.Model,evalin('base',app.State_I.InputVariableName)) ...
                            || ~app.InputUpdated)
                app.updateModel();
                app.updateData();
                app.pushStateToData();
                app.resetView();
                app.InputUpdated = true;
            end
            
            if strcmp(app.State_I.Method,'balanced') || strcmp(app.State_I.Method,'modal')
                if ~isempty(app.View.CurrentComparisonPlotWidget) ...
                        && isvalid(app.View.CurrentComparisonPlotWidget)
                    app.View.CurrentComparisonPlotWidget.setVisible('off');
                end
                        
                % Response Comparison
                idx = strcmp(app.ComparisonPlotTypes,app.State_I.CurrentResponsePlotType);
                localWidgetHandle = app.View.ComparisonPlotWidgets{idx};
                if isempty(localWidgetHandle)
                    % Create response comparison widget with frequency
                    % range selector
                    switch app.State_I.CurrentResponsePlotType
                        case 'modelResponse'
                            widget = localCreatePlotWidget(app.ParentGrid,3);
                            localWidgetHandle = mrtool.internal.plots.MRModelResponsePlot(widget,...
                                app.ModelReducerData,...
                                app.ReducedSystem,toolData);
                        case 'absError'
                            widget = localCreatePlotWidget(app.ParentGrid,3);
                            localWidgetHandle = mrtool.internal.plots.MRAbsoluteErrorPlot(widget,...
                                app.ModelReducerData,...
                                app.ReducedSystem,toolData);
                        case 'relError'
                            widget = localCreatePlotWidget(app.ParentGrid,3);
                            localWidgetHandle = mrtool.internal.plots.MRRelativeErrorPlot(widget,...
                                app.ModelReducerData,...
                                app.ReducedSystem,toolData);
                    end
                    % Add frequency selector listener
                    xRangeWidgets = getWidgets(localWidgetHandle);
                    delete(app.FrequencySelectorMovedListeners{idx});
                    app.FrequencySelectorMovedListeners{idx} = addlistener(xRangeWidgets,'SelectorMoved',...
                                                                    @(es,ed) cbFrequencyRangeSelectorMoved(app,ed));
                    % Add listener to resize response plot
                    localPlotHandle = getHandle(localWidgetHandle);

                    ag = qeGetAxesGrid(qeGetView(localPlotHandle));
                    ag.Padding = 'tight';
                    ag.Spacing = 'tight';
                    % Disable axes interactivity and add custom toolbar
                    ax = getAxes(localPlotHandle);
                    legend(ax(1),'show');

                    lm = getLayoutManager(qeGetAxesGrid(qeGetView(localPlotHandle)));
                    if strcmpi(localPlotHandle.Type,'bode')
                        tl = lm.SubTiledLayout(1);
                    else
                        tl = lm.TiledLayout;
                    end
                    createToolbar(app,tl,localPlotHandle,'on');

                    arrayfun(@(x) disableDefaultInteractivity(x), ax);
                    arrayfun(@(x) matlab.graphics.interaction.enableToolbarOnButtonDown(x), ax);
                    
                    % Update output color
                    app.View.CurrentOutputColor = localPlotHandle.Systems(end).Style.Color;
                    
                else
                    % Update existing response comparison widget data
                    setTargetModel(localWidgetHandle,app.ModelReducerData);
                    setReducedSystem(localWidgetHandle,app.ReducedSystem);
                    localPlotHandle = getHandle(localWidgetHandle);
                end
                app.View.CurrentComparisonPlotWidget = localWidgetHandle;
                app.View.ComparisonPlotWidgets{idx} = localWidgetHandle;
                % Set visibility and tooltip of response comparison widget
                if isempty(app.State_I.InputVariableName)
                    setVisible(localWidgetHandle,'off');
                    ax = getAxes(localPlotHandle);
                    legend(ax(1),'hide');
                else
                    setVisible(localWidgetHandle,'on');
                    updateOutputColor(app);
                    setLegend(localWidgetHandle);
                end
                updateStateFrequencyRange(app);
                updateFrequencyRangeSelector(app);
            end
            
                                    
            % Create HSV Plot with Bar selector
            if strcmp(app.State_I.Method,'balanced')
                localWidgetHandle = app.View.CurrentAnalysisPlotWidget;
                if isempty(localWidgetHandle) || ~isvalid(localWidgetHandle)
                    % Create reduced order bar selector widget
                    widget = localCreatePlotWidget(app.ParentGrid,4);
                    widget.Tooltip = m('Control:embedded_apps:BarSelectorTooltip');
                    localWidgetHandle = mrtool.internal.plots.MRHankelPlot(widget,...
                       app.ModelReducerData,app.ReducedSystem,app.BalancedTruncationData);
                    localPlotHandle = getHandle(localWidgetHandle);
                    ag = qeGetAxesGrid(qeGetView(localPlotHandle));
                    ag.Padding = 'tight';
                    ag.Spacing = 'tight';
                    
                    selectorWidget = getWidgets(localWidgetHandle);
                    selectorWidget.AllowMultiSelect = false;

                    % Add bar selector listener
                    app.BarSelectorListener = addlistener(localWidgetHandle,...
                                                            'HankelBarSelected',...
                                                            @(es,ed) cbReducedOrderBarSelected(app,es,ed));
                    % Disable axes interactivity and add custom toolbar
                    lm = getLayoutManager(qeGetAxesGrid(qeGetView(localPlotHandle)));
                    if strcmpi(localPlotHandle.Type,'bode')
                        tl = lm.SubTiledLayout(1);
                    else
                        tl = lm.TiledLayout;
                    end
                    ax = getAxes(localPlotHandle);
                    if isempty(ax(1).Legend)
                        legendVisible = 'off';
                    else
                        legendVisible = ax(1).Legend.Visible;
                    end
                    createToolbar(app,tl,localPlotHandle,legendVisible);
                    
                    disableDefaultInteractivity(ax);
                    matlab.graphics.interaction.enableToolbarOnButtonDown(ax);
                else
                    setReducedSystem(localWidgetHandle,app.ReducedSystem);
                    updateReducedSystemResponse(localWidgetHandle);
                    setData(localWidgetHandle,app.ModelReducerData,app.ReducedSystem);
                    
                    localXRangeSelector = getWidgets(localWidgetHandle);
                    localXRangeSelector.SelectedValues = order(app.ReducedSystem);
                    setLegend(localWidgetHandle);
                    localPlotHandle = getHandle(localWidgetHandle);
                end
                
                if isempty(app.State_I.InputVariableName)
                    setVisible(localWidgetHandle,'off');                  
                else
                    setVisible(localWidgetHandle,'on');
                end
                legend(getAxes(localPlotHandle),'show');
                app.View.CurrentAnalysisPlotWidget = localWidgetHandle;
            else
                
            end
            warning(w);
        end
    end
    
    %% Component Value changed functions
    methods (Access = private)
        function cbModelDropDownValueChanged(app, ~)
            if strcmp(app.ModelDropDown.Value,'select variable')
                app.State_I.InputVariableName = '';
            else
                app.State_I.InputVariableName = app.ModelDropDown.Value;
            end
            if isempty(app.State_I.InputVariableName)
                app.initializeState();
                app.MethodDropDown.Value = app.State_I.Method;
                updateUIComponentVisibility(app,true);
                if ~isempty(app.View.CurrentComparisonPlotWidget) && ...
                        isvalid(app.View.CurrentComparisonPlotWidget)
                    setVisible(app.View.CurrentComparisonPlotWidget,'off');
                end
                if ~isempty(app.View.CurrentAnalysisPlotWidget) && ...
                        isvalid(app.View.CurrentAnalysisPlotWidget)
                    setVisible(app.View.CurrentAnalysisPlotWidget,'off');
                end
            else
                updateModel(app);
                app.InputUpdated = true;
                if ~isequal(size(app.Model),size(app.ReducedSystem))
                    resetView(app);
                else
                    if ~isempty(app.View.CurrentComparisonPlotWidget) && ...
                            isvalid(app.View.CurrentComparisonPlotWidget)
                        setVisible(app.View.CurrentComparisonPlotWidget,'off');
                    end
                    if ~isempty(app.View.CurrentAnalysisPlotWidget) && ...
                            isvalid(app.View.CurrentAnalysisPlotWidget)
                        setVisible(app.View.CurrentAnalysisPlotWidget,'off');
                    end
                end
                resetData(app);
                updateUIComponentVisibility(app);
                app.MethodDropDown.Enable = 'on';
                app.OutputPlotLabel.Visible = 'on';
                app.OutputPlotDropDown.Visible = 'on';
            end
        end
        
        function cbMethodDropDownValueChanged(app, ~)
            app.State_I.Method = app.MethodDropDown.Value;
            if app.ShowDefaultOutputPlot
                switch app.State_I.Method
                    case {'balanced','modal'}
                        app.State_I.CurrentOutputPlotType = 'none';
                    case 'minreal'
                        app.State_I.CurrentOutputPlotType = 'pz';
                end
            end
            app.State_I.OutputUpdated = false;
            updateUIComponentVisibility(app);
            updateUIComponentValues(app);
            updatePreview(app);
            updateOutputColor(app);
            updateStateFrequencyRange(app);
            updateFrequencyRangeSelector(app);
        end
        
        function cbReducedOrderSpinnerValueChanged(app, ~)
            app.State_I.OutputUpdated = false;
            value = app.BalancedTruncationComponents.ReducedOrderSpinner.Value;
            setReducedOrder(app.BalancedTruncationData,value);
            app.State_I.ReducedOrder = value;
            if ~isempty(app.View.CurrentAnalysisPlotWidget) && isvalid(app.View.CurrentAnalysisPlotWidget)
                widgets = getWidgets(app.View.CurrentAnalysisPlotWidget);
                widgets.SelectedValues = value;
            end
            updateOutputColor(app);
        end
        
        function cbReducedOrderBarSelected(app,~,ed)
            value = ed.Data;
            if isempty(app.ReducedSystem) || value ~= order(app.ReducedSystem)
                setReducedOrder(app.BalancedTruncationData,value);
                app.BalancedTruncationComponents.ReducedOrderSpinner.Value = value;
                app.State_I.ReducedOrder = value;
                app.State_I.OutputUpdated = false;
                updateOutputColor(app);
                notify(app,'StateChanged');
            end
        end
        
        function cbPreserveDCGainCheckBoxValueChanged(app, ~)
            value = app.BalancedTruncationComponents.PreserveDCGainCheckBox.Value;
            if value
                setMethod(app.BalancedTruncationData,'matchDC');
            else
                setMethod(app.BalancedTruncationData,'truncate');
            end
            applyOptions(app.BalancedTruncationData);
            app.State_I.PreserveDCGain = value;
            app.State_I.OutputUpdated = false;
            updateOutputColor(app);
        end
        
        function cbFrequencyRangeCheckBoxValueChanged(app, ~)
            value = app.BalancedTruncationComponents.FrequencyRangeCheckBox.Value;
            app.State_I.FrequencyRangeSelected = value;
            if value
                fr(1,1) = app.BalancedTruncationComponents.FrequencyRangeLowerEditField.Value;
                fr(1,2) = app.BalancedTruncationComponents.FrequencyRangeUpperEditField.Value;
                app.BalancedTruncationComponents.FrequencyRangeLowerEditField.Enable = 'on';
                app.BalancedTruncationComponents.FrequencyRangeUpperEditField.Enable = 'on';                
                app.BalancedTruncationComponents.PreserveDCGainCheckBox.Value = false;

                R = getReduceSpec(app.BalancedTruncationData);
                R.Options.FreqIntervals = fr;
                setOptions(app.BalancedTruncationData,R.Options);
                setMethod(app.BalancedTruncationData,'truncate');
                applyOptions(app.BalancedTruncationData);

                app.State_I.PreserveDCGain = false;
            else
                app.BalancedTruncationComponents.FrequencyRangeLowerEditField.Enable = 'off';
                app.BalancedTruncationComponents.FrequencyRangeUpperEditField.Enable = 'off';
                
                if app.BalancedTruncationComponents.PreserveDCGainCheckBox.Value
                    setMethod(app.BalancedTruncationData,'matchDC');
                else
                    setMethod(app.BalancedTruncationData,'truncate');
                end
                R = getReduceSpec(app.BalancedTruncationData);
                R.Options.FreqIntervals = [];
                setOptions(app.BalancedTruncationData,R.Options);
                applyOptions(app.BalancedTruncationData);

                app.State_I.PreserveDCGain = app.BalancedTruncationComponents.PreserveDCGainCheckBox.Value;
            end
            app.State_I.OutputUpdated = false;
            updateStateFrequencyRange(app);
            updateFrequencyRangeSelector(app);
            updateOutputColor(app);
        end
        
        function cbFrequencyRangeLowerEditFieldValueChanged(app, ~)
            value = app.BalancedTruncationComponents.FrequencyRangeLowerEditField.Value;
            app.BalancedTruncationComponents.FrequencyRangeUpperEditField.Limits(1) = value;
            fr = [value app.BalancedTruncationComponents.FrequencyRangeUpperEditField.Value];
            R = getReduceSpec(app.BalancedTruncationData);
            R.Options.FreqIntervals = fr;
            setOptions(app.BalancedTruncationData,R.Options);
            applyOptions(app.BalancedTruncationData);
            app.State_I.OutputUpdated = false;
            app.State_I.FrequencyRange = fr;
            updateFrequencyRangeSelector(app);
            updateOutputColor(app);
        end
        
        function cbFrequencyRangeUpperEditFieldValueChanged(app, ~)
            value = app.BalancedTruncationComponents.FrequencyRangeUpperEditField.Value;
            app.BalancedTruncationComponents.FrequencyRangeLowerEditField.Limits(2) = value;
            fr = [app.BalancedTruncationComponents.FrequencyRangeLowerEditField.Value, value];
            R = getReduceSpec(app.BalancedTruncationData);
            R.Options.FreqIntervals = fr;
            setOptions(app.BalancedTruncationData,R.Options);
            applyOptions(app.BalancedTruncationData);
            app.State_I.OutputUpdated = false;
            app.State_I.FrequencyRange = fr;
            updateFrequencyRangeSelector(app);
            updateOutputColor(app);
        end
        
        function cbFrequencyCutoffUpperEditFieldValueChanged(app, ~)
            value = app.ModalTruncationComponents.FrequencyCutoffUpperEditField.Value;
            app.ModalTruncationComponents.FrequencyCutoffLowerEditField.Limits(2) = value;
            fr = getFrequencyRange(app.ModalTruncationData);
            fr(2) = value;
            setFrequencyRange(app.ModalTruncationData,fr);
            app.State_I.OutputUpdated = false;
            app.State_I.FrequencyRange(2) = value;
            updateFrequencyRangeSelector(app);
            updateOutputColor(app);
        end
        
        function cbFrequencyCutoffLowerEditFieldValueChanged(app, ~)
            value = app.ModalTruncationComponents.FrequencyCutoffLowerEditField.Value;
            app.ModalTruncationComponents.FrequencyCutoffUpperEditField.Limits(1) = value;
            fr = getFrequencyRange(app.ModalTruncationData);
            fr(1) = value;
            setFrequencyRange(app.ModalTruncationData,fr);
            app.State_I.OutputUpdated = false;
            app.State_I.FrequencyRange(1) = value;
            updateFrequencyRangeSelector(app);
            updateOutputColor(app);
        end
        
        function cbFrequencyRangeSelectorMoved(app,e)
            freqRange = e.Data.Range;
            freqRangeUpperBound = app.State_I.FrequencyRangeUpperBound;
            if freqRange(2) > freqRangeUpperBound
                freqRange(2) = freqRangeUpperBound;
            end
            switch app.State_I.Method
                case 'balanced'
                    app.BalancedTruncationComponents.FrequencyRangeLowerEditField.Limits = ...
                        [0 freqRange(2)];
                    app.BalancedTruncationComponents.FrequencyRangeLowerEditField.Value = ...
                        freqRange(1);
                    app.BalancedTruncationComponents.FrequencyRangeUpperEditField.Limits = ...
                        [freqRange(1) freqRangeUpperBound];
                    app.BalancedTruncationComponents.FrequencyRangeUpperEditField.Value = ...
                        freqRange(2);
                    R = getReduceSpec(app.BalancedTruncationData);
                    R.Options.FreqIntervals = freqRange;
                    setOptions(app.BalancedTruncationData,R.Options);
                    applyOptions(app.BalancedTruncationData);
                case 'modal'
                    app.ModalTruncationComponents.FrequencyCutoffLowerEditField.Limits = ...
                        [0 freqRange(2)];
                    app.ModalTruncationComponents.FrequencyCutoffLowerEditField.Value = ...
                        freqRange(1);
                    app.ModalTruncationComponents.FrequencyCutoffUpperEditField.Limits = ...
                        [freqRange(1) freqRangeUpperBound];
                    app.ModalTruncationComponents.FrequencyCutoffUpperEditField.Value = ...
                        freqRange(2);
                    setFrequencyRange(app.ModalTruncationData,freqRange)
            end
            app.State_I.OutputUpdated = false;
            app.State_I.FrequencyRange = freqRange;
            updateFrequencyRangeSelector(app);
            if strcmp(e.Data.Status,'InProgress')
                updateOutputColor(app);
            end
            if strcmp(e.Data.Status,'Finished')
                notify(app,'StateChanged');
            end
        end
        
        function cbToleranceSliderValueChanged(app, ~)
            value = app.PoleZeroComponents.ToleranceSlider.Value;
            setTolerance(app.PoleZeroSimplificationData,10^value);
            app.PoleZeroComponents.ToleranceEditField.Value = 10^value;
            app.State_I.Tolerance = 10^value;
            app.State_I.OutputUpdated = false;
        end
        
        function cbToleranceSliderValueChanging(app,e)
            app.PoleZeroComponents.ToleranceEditField.Value = 10^e.Value;
        end
        
        function cbToleranceEditFieldValueChanged(app, ~)
            value = app.PoleZeroComponents.ToleranceEditField.Value;
            setTolerance(app.PoleZeroSimplificationData,value);
            app.PoleZeroComponents.ToleranceSlider.Value = log10(value);
            app.State_I.Tolerance = value;
            app.State_I.OutputUpdated = false;
        end
        
        function cbResponsePlotDropDownValueChangedFcn(app, ~)
            app.State_I.CurrentResponsePlotType = app.ComparisonPlotDropDown.Value;
            if ~isempty(app.View.CurrentComparisonPlotWidget) && ...
                    isvalid(app.View.CurrentComparisonPlotWidget) && ...
                    strcmp(app.View.CurrentComparisonPlotWidget.getVisible(),'on')
                updatePreview(app);
            end
        end
        
        function cbOutputPlotDropDownValueChangedFcn(app,~)
            app.State_I.CurrentOutputPlotType = app.OutputPlotDropDown.Value;
            app.ShowDefaultOutputPlot = false;
        end
        
        function cbFullView(~,plotHandle)
            plotHandle.AxesGrid.XLimMode = 'auto';
            plotHandle.AxesGrid.YLimMode = 'auto';
        end
    end
    
    %% Listeners
    methods (Access = private)
        function deleteWidgetListeners(app)
            delete(app.BarSelectorListener);
            cellfun(@(x) delete(x),app.FrequencySelectorMovedListeners);
        end
    end
    
    %% Local functions
    methods (Access = private)
        function updateUIComponentVisibility(app,showDefault)
            btComponentNames = fieldnames(app.BalancedTruncationComponents);
            if nargin > 1 && showDefault
                app.VisualizationGrid.Layout.Row = 5;
                app.ParentGrid.RowHeight = {54,86,215,185,54};

                app.BalancedTruncationGrid.Visible = 'on';
                app.ModalTruncationGrid.Visible = 'off';
                app.PoleZeroGrid.Visible = 'off';
                cellfun(@(x) set(app.BalancedTruncationComponents.(x),'Enable',false),btComponentNames);
                app.ComparisonPlotLabel.Enable = false;
                app.ComparisonPlotDropDown.Enable = false;
                app.ComparisonPlotDropDown.Parent = app.BalancedTruncationGrid;
                app.ComparisonPlotLabel.Parent = app.BalancedTruncationGrid;
                app.ComparisonPlotDropDown.Layout.Column = [2 3];
                app.OutputPlotLabel.Enable = false;
                app.OutputPlotDropDown.Enable = false;
                app.MethodDropDown.Enable = 'off';
            else
                app.ComparisonPlotLabel.Enable = true;
                app.ComparisonPlotDropDown.Enable = true;
                app.OutputPlotLabel.Enable = true;
                app.OutputPlotDropDown.Enable = true;
                switch app.State_I.Method
                    case 'balanced'
                        app.VisualizationGrid.Layout.Row = 5;
                        app.ParentGrid.RowHeight = {54,86,215,185,54};
                        app.BalancedTruncationGrid.Visible = 'on';
                        app.ModalTruncationGrid.Visible = 'off';
                        app.PoleZeroGrid.Visible = 'off';
                        cellfun(@(x) set(app.BalancedTruncationComponents.(x),'Enable',true),...
                            btComponentNames);
                        if getFreqIntervalsUsed(app.BalancedTruncationData)
                            app.BalancedTruncationComponents.FrequencyRangeUpperEditField.Enable = 'on';
                            app.BalancedTruncationComponents.FrequencyRangeLowerEditField.Enable = 'on';
                        else
                            app.BalancedTruncationComponents.FrequencyRangeUpperEditField.Enable = 'off';
                            app.BalancedTruncationComponents.FrequencyRangeLowerEditField.Enable = 'off';
                        end
                        app.ComparisonPlotLabel.Visible = 'on';
                        app.ComparisonPlotDropDown.Visible = 'on';
                        app.ComparisonPlotDropDown.Parent = app.BalancedTruncationGrid;
                        app.ComparisonPlotDropDown.Layout.Column = [2 3];
                        app.ComparisonPlotLabel.Parent = app.BalancedTruncationGrid;
                        if ~isempty(app.View.CurrentComparisonPlotWidget)
                            setVisible(app.View.CurrentComparisonPlotWidget,'on');
                        end
                        if ~isempty(app.View.CurrentAnalysisPlotWidget)
                            setVisible(app.View.CurrentAnalysisPlotWidget,'on');
                        end
                    case 'modal'
                        app.VisualizationGrid.Layout.Row = 4;
                        app.ParentGrid.RowHeight = {54,86,215,54,0};
                        app.BalancedTruncationGrid.Visible = 'off';
                        app.ModalTruncationGrid.Visible = 'on';
                        app.PoleZeroGrid.Visible = 'off';
                        app.ComparisonPlotLabel.Visible = 'on';
                        app.ComparisonPlotDropDown.Visible = 'on';
                        app.ComparisonPlotDropDown.Parent = app.ModalTruncationGrid;
                        app.ComparisonPlotDropDown.Layout.Column = [2 4];
                        app.ComparisonPlotLabel.Parent = app.ModalTruncationGrid;
                        if ~isempty(app.View.CurrentComparisonPlotWidget)
                            setVisible(app.View.CurrentComparisonPlotWidget,'on');
                        end
                        if ~isempty(app.View.CurrentAnalysisPlotWidget)
                            setVisible(app.View.CurrentAnalysisPlotWidget,'off');
                        end
                    case 'minreal'
                        app.VisualizationGrid.Layout.Row = 3;
                        app.ParentGrid.RowHeight = {54,54,54,0,0};
                        app.BalancedTruncationGrid.Visible = 'off';
                        app.ModalTruncationGrid.Visible = 'off';
                        app.PoleZeroGrid.Visible = 'on';
                        app.ComparisonPlotDropDown.Visible = 'off';
                        app.ComparisonPlotLabel.Visible = 'off';
                        if ~isempty(app.View.CurrentComparisonPlotWidget)
                            setVisible(app.View.CurrentComparisonPlotWidget,'off');
                        end
                        if ~isempty(app.View.CurrentAnalysisPlotWidget)
                            setVisible(app.View.CurrentAnalysisPlotWidget,'off');
                        end
                end
            end
        end
        
        function updateUIComponentValues(app)
            app.MethodDropDown.Value = app.State_I.Method;
            
            app.BalancedTruncationComponents.ReducedOrderSpinner.Limits(1) = getMinimumOrder(app.BalancedTruncationData);
            app.BalancedTruncationComponents.ReducedOrderSpinner.Limits(2) = getMaximumOrder(app.BalancedTruncationData);
            
            app.BalancedTruncationComponents.ReducedOrderSpinner.Value = ...
                getReducedOrder(app.BalancedTruncationData);
            app.BalancedTruncationComponents.PreserveDCGainCheckBox.Value = ...
                strcmpi(getMethod(app.BalancedTruncationData),'matchDC');
            app.BalancedTruncationComponents.FrequencyRangeCheckBox.Value = ...
                getFreqIntervalsUsed(app.BalancedTruncationData);
            R = getReduceSpec(app.BalancedTruncationData);
            fr = R.Options.FreqIntervals;
            if isempty(fr)
                if issiso(getTargetSystem(app.BalancedTruncationData))
                    [~,~,w] = bode(getTargetSystem(app.BalancedTruncationData));
                else
                    [~,w] = sigma(getTargetSystem(app.BalancedTruncationData));
                end
                fr = mrtool.util.setSelectorFrequencyRange([w(1) w(end)]);
            end
            app.BalancedTruncationComponents.FrequencyRangeLowerEditField.Limits = [0 fr(2)];
            app.BalancedTruncationComponents.FrequencyRangeUpperEditField.Limits = ...
                [fr(1) app.State_I.FrequencyRangeUpperBound];
            app.BalancedTruncationComponents.FrequencyRangeLowerEditField.Value = fr(1);
            app.BalancedTruncationComponents.FrequencyRangeUpperEditField.Value = fr(2);
            
            fr = getFrequencyRange(app.ModalTruncationData);
            app.ModalTruncationComponents.FrequencyCutoffLowerEditField.Limits = [0 fr(2)];
            app.ModalTruncationComponents.FrequencyCutoffUpperEditField.Limits = ...
                [fr(1) app.State_I.FrequencyRangeUpperBound];
            app.ModalTruncationComponents.FrequencyCutoffLowerEditField.Value = fr(1);
            app.ModalTruncationComponents.FrequencyCutoffUpperEditField.Value = fr(2);
                        
            app.PoleZeroComponents.ToleranceSlider.Value = ...
                log10(getTolerance(app.PoleZeroSimplificationData));
            app.PoleZeroComponents.ToleranceEditField.Value = ...
                getTolerance(app.PoleZeroSimplificationData);
            
            app.ComparisonPlotDropDown.Value = app.State_I.CurrentResponsePlotType;
            app.OutputPlotDropDown.Value = app.State_I.CurrentOutputPlotType;
            
            app.pushDataToState();
        end
        
        function updateFrequencyRangeSelector(app)
            if ~isempty(app.View.CurrentComparisonPlotWidget) && ...
                    isvalid(app.View.CurrentComparisonPlotWidget) && ...
                    ~isempty(app.State_I.InputVariableName)
                if (strcmp(app.State_I.Method,'balanced') && app.State_I.FrequencyRangeSelected) || ...
                        strcmp(app.State_I.Method,'modal')
                    wdgt = app.View.CurrentComparisonPlotWidget.getWidgets();
                    for ii = 1:length(wdgt)
                        wdgt(ii).Visible = true;
                    end
                    app.View.CurrentComparisonPlotWidget.setSelection(app.State_I.FrequencyRange);
                else
                    wdgt = app.View.CurrentComparisonPlotWidget.getWidgets();
                    for ii = 1:length(wdgt)
                        wdgt(ii).Visible = false;
                    end
                end
            end
        end
        
        function updateStateFrequencyRange(app)
            if ~isempty(app.State_I.InputVariableName)
                switch app.State_I.Method
                    case 'balanced'
                        R = getReduceSpec(app.BalancedTruncationData);
                        if getFreqIntervalsUsed(app.BalancedTruncationData)
                            fr = R.Options.FreqIntervals;
                            panel = getParent(app.View.CurrentComparisonPlotWidget);
                            panel.Tooltip = ...
                                m('Control:embedded_apps:FrequencySelectorTooltip');
                        else
                            fr = [];
                            panel = getParent(app.View.CurrentComparisonPlotWidget);
                            panel.Tooltip = "";
                        end
                    case 'modal'
                        fr = getFrequencyRange(app.ModalTruncationData);
                        panel = getParent(app.View.CurrentComparisonPlotWidget);
                        panel.Tooltip = ...
                            m('Control:embedded_apps:FrequencySelectorTooltip');
                    otherwise
                        return;
                end
                app.State_I.FrequencyRange = fr;
            end
        end
                
        function updateOutputColor(app,varargin)
            % Update response plot
            localWidgetHandle = app.View.CurrentComparisonPlotWidget;
            if ~isempty(localWidgetHandle) && isvalid(localWidgetHandle)
                localPlotHandle = getHandle(localWidgetHandle);
                ax = getAxes(localPlotHandle);
                for k = 1:numel(ax)
                    if app.State_I.OutputUpdated
                        localColor = app.View.CurrentOutputColor;
                        toolbarParent = ax(k).Parent;
                        if strcmp(toolbarParent.Toolbar.Children(5).Value,'on') && k == 1
                            legend(ax(k),'show');
                        else
                            legend(ax(k),'hide');
                        end
                    else
                        localColor = app.View.OldOutputColor;
                        legend(ax(k),'hide');
                    end
                end
                localPlotHandle.Systems(end).Style.Color = localColor;
            end
            
            % Update Bar chart
            localWidgetHandle = app.View.CurrentAnalysisPlotWidget;
            if ~isempty(localWidgetHandle) && isvalid(localWidgetHandle)
                localPlotHandle = getHandle(localWidgetHandle);
                ax = getAxes(localPlotHandle);
                if app.State_I.OutputUpdated
                    toolbarParent = ax(1).Parent;
                    if strcmp(toolbarParent.Toolbar.Children(5).Value,'on')
                        legend(ax(1),'show');
                    else
                        legend(ax(1),'hide');
                    end
                else
                    legend(ax,'hide');
                end
            end
        end
   end
    
    %% State/Data functions
    methods (Access = private)
        function createData(app)
            app.BalancedTruncationData = mrtool.data.BalancedTruncationData(app.ModelReducerData,...
                                                                app.ModelReducerData);
            app.BalancedTruncationData.setUsedInEmbeddedApps(true);
            applyOptions(app.BalancedTruncationData);
            app.ModalTruncationData = mrtool.data.ModalTruncationData(app.ModelReducerData,...
                                                                app.ModelReducerData);
            app.ModalTruncationData.setUsedInEmbeddedApps(true);
            setMethod(app.ModalTruncationData,'truncate');
            applyOptions(app.ModalTruncationData);
            app.PoleZeroSimplificationData = mrtool.data.PoleZeroSimplificationData(app.ModelReducerData,...
                                                                app.ModelReducerData);
            app.PoleZeroSimplificationData.setUsedInEmbeddedApps(true);
        end
        
        function updateModel(app)
            if isempty(app.State_I.InputVariableName)
                app.Model = app.InitialSystem;
            else
                localModel = evalin('base',app.State_I.InputVariableName);
                if hasdelay(localModel)
                    app.Model = absorbDelay(localModel);
                    app.State_I.GenerateAbsorbDelayCode = true;
                else
                    app.Model = localModel;
                    app.State_I.GenerateAbsorbDelayCode = false;
                end
                updateModelData(app);
            end
        end
        
        function updateModelData(app)
            if isa(app.Model,'ss')
                app.State_I.GeneratePrescaleCode = true;
            else
                app.State_I.GeneratePrescaleCode = false;
            end
            app.Model.InputName = '';
            app.Model.OutputName = '';
            app.ModelReducerData = mrtool.data.ModelWrapper(app.State_I.InputVariableName,app.Model);
            app.State_I.FrequencyRangeUpperBound = pi/app.Model.Ts;
        end
        
        function updateData(app)
            setTarget(app.BalancedTruncationData,app.ModelReducerData);
            update(app.BalancedTruncationData);
            setTarget(app.ModalTruncationData,app.ModelReducerData);
            update(app.ModalTruncationData);
            setTarget(app.PoleZeroSimplificationData,app.ModelReducerData);
            update(app.PoleZeroSimplificationData);
        end
        
        function pushDataToState(app)
            switch app.State_I.Method
                case 'balanced'
                    app.State_I.ReducedOrder = getReducedOrder(app.BalancedTruncationData);
                    app.State_I.PreserveDCGain = strcmpi(getMethod(app.BalancedTruncationData),'matchDC');
                    app.State_I.FrequencyRangeSelected = getFreqIntervalsUsed(app.BalancedTruncationData);
                    R = getReduceSpec(app.BalancedTruncationData);
                    app.State_I.FrequencyRange = R.Options.FreqIntervals;
                case 'modal'
                    app.State_I.FrequencyRange = getFrequencyRange(app.ModalTruncationData);
                case 'minreal'
                    app.State_I.Tolerance = getTolerance(app.PoleZeroSimplificationData);
            end
        end
        
        function pushStateToData(app)
            switch app.State_I.Method
                case 'balanced'
                    setReducedOrder(app.BalancedTruncationData,app.State_I.ReducedOrder);
                    if app.State_I.PreserveDCGain
                        setMethod(app.BalancedTruncationData,'matchDC');
                    else
                        setMethod(app.BalancedTruncationData,'truncate');
                    end
                    R = getReduceSpec(app.BalancedTruncationData);
                    R.Options.FreqIntervals = app.State_I.FrequencyRange;
                    setOptions(app.BalancedTruncationData,R.Options);
                    applyOptions(app.BalancedTruncationData);
                case 'modal'
                    if ~isempty(app.State_I.FrequencyRange)
                        setFrequencyRange(app.ModalTruncationData,app.State_I.FrequencyRange);
                    else
                        app.State_I.FrequencyRange = getFrequencyRange(app.ModalTruncationData);
                    end
                case 'minreal'
                    app.PoleZeroSimplificationData.setTolerance(app.State_I.Tolerance);
            end
        end
    end
    
    %% QE Methods
    methods (Hidden)        
        function outputName = qeGetOutputVariableNames(app)
            outputName = {app.OutputVariableName};
        end
        
        function model = qeGetModel(app)
            model = app.Model;
        end
        
        function qeSetModel(app,modelName,~)
            app.State_I.InputVariableName = modelName;
            updateModel(app);
            app.resetData();
        end
        
        function qeSetMethod(app,method,resetFlag)
            if nargin < 3
                resetFlag = false;
            end
            app.State_I.Method = method;
            if resetFlag
                app.resetData(method);
            end
        end
    end
end

%% Helper functions
function s = m(id, varargin)
% Reads strings from the resource bundle
m = message(id, varargin{:});
s = m.getString;
end

function label = localBuildHeader(parentGrid,row,title)
label = uilabel(parentGrid);
label.Layout.Row = row;
if length(parentGrid.ColumnWidth)>1
    label.Layout.Column = [1 length(parentGrid.ColumnWidth)];
else
    label.Layout.Column = 1;
end
label.Text = title;
label.FontWeight = 'bold';
end

function idx = updateModelDropDownItems(x)
% Generate cell arrays of all workspace variables and their
% values
idx = mrtool.util.isValidSystem({x}) & isa(x,'lti') & ~isa(x,'frd') ...
    & ~isa(x,'genlti') & ~isa(x,'idproc') & ~isa(x,'pid') ...
    & ~isa(x,'sparss') & ~isa(x,'mechss');
end

function widget = localCreatePlotWidget(parent,row)
widget = uipanel(parent);
widget.Layout.Row = row;
widget.BorderType = 'none';
widget.AutoResizeChildren = 'off';
end