www.gusucode.com > mbctools 工具箱 matlab 源码程序 > mbctools/+xregdatagui/DataTableView.m

    classdef DataTableView < xregdatagui.AbstractDataView
    %xregdatagui.DataTableView class
    %   xregdatagui.DataTableView extends xregdatagui.AbstractDataView.
    %
    %    xregdatagui.DataTableView properties:
    %       Parent - Property is of type 'MATLAB array'
    %       Position - Property is of type 'rect'
    %       Enable - Property is of type 'on/off'
    %       Visible - Property is of type 'on/off'
    %       Userdata - Property is of type 'MATLAB array'
    %       Tag - Property is of type 'string'
    %       MessageService - Property is of type 'handle' (read only)
    %       Container - Property is of type 'handle'
    %       UIContextMenu - Property is of type 'MATLAB array'
    %       Listeners - Property is of type 'handle vector' (read only)
    %       Table - Property is of type 'handle'
    %       AllowEdit - Property is of type 'bool'
    %       ShowRemovedData - Property is of type 'bool'
    %       TStarts - Property is of type 'MATLAB array'
    %       TNums - Property is of type 'MATLAB array'
    %       ColumnNames - Property is of type 'MATLAB array'
    %       OutlierRows - Property is of type 'MATLAB array'
    %
    %    xregdatagui.DataTableView methods:
    %       copyRequest - Execute a copy operation on the table selection
    %       gettitle - returns string describing this view
    %       pPostSetDmsIsReadOnly - Recalculate whether table cells should be editable or not
    %       pRequestUpdate - Request updates to the table
    %       pUndoEdit -  Reverts any edited cells in the selected region
    %       pUpdateOptionsMenus - Setup the options menus correctly
    %       pasteRequest - Execute a paste operation on the table selection
    %       serializeView - Returns selected column names for storage
    
    %  Copyright 2015-2016 The MathWorks, Inc. and Ford Global Technologies, Inc.
    
    properties (Constant)
        %ViewInfo view description
        ViewInfo = { @xregdatagui.DataTableView;
            '&Data Table';
            xregrespath('dataimport_array_ok.bmp');
            'Data Table';
            1};
    end

    
    properties (AbortSet)
        %TABLE Property is of type 'handle'
        Table = [];
        %ALLOWEDIT Property is of type 'bool'
        AllowEdit = 0;
        %ShowRemovedData show removed data in table
        ShowRemovedData = false;
        %TSTARTS Property is of type 'MATLAB array'
        TStarts = [];
        %TNUMS Property is of type 'MATLAB array'
        TNums = [];
        %COLUMNNAMES storage of column names to display
        %  The columns in the Java table can be rearranged by dragging so you should DisplayedColumnNames   
        ColumnNames = [];
        %OUTLIERROWS Property is of type 'MATLAB array'
        OutlierRows = [];
    end
    
    properties (Access=protected, AbortSet)
        %UPDATESREQUESTED Property is of type 'MATLAB array'
        UpdatesRequested = [ false, false, false ];
    end
    
    properties(Dependent,SetAccess=private)
        %DisplayedColumnNames columns displayed in Java (allowing for rearrangement)
        DisplayedColumnNames
    end
    
    methods  % constructor block
        function obj = DataTableView(varargin)
        %DATATABLEVIEW constructor
        %  OUT = DATATABLEVIEW(VARARGIN)
        
        % do the setup stuff from inside the AbstractDataView constructor
        obj@xregdatagui.AbstractDataView(varargin{ : }); % converted super class constructor call
        
        % create the graphical objects and put in the Display field
        obj.pCreateDisplay;
        
        obj.pUpdateTable
        
        end  % datatableview
        
    end  % constructor block
    
    methods
        function colNames = get.DisplayedColumnNames(obj)
        % need to get names from Java as the columns might have been
        % rearranged
        colNames = javaMethodEDT('getColumnNames',obj.Table.Peer);
        colNames = cellstr(char(colNames));
        if isequal(colNames, get(obj.MessageService.Sweepset,'name'))
            % all names in correct order
            colNames = [];
        end
        end
    end   % set and get functions
    
    methods  % public methods
        %----------------------------------------
        function copyRequest(obj, ~,evt)
        %COPYREQUEST Execute a copy operation on the table selection
        %  COPYREQUEST(OBJ, hTable, evt) copies the data specified by the rows and
        %  columns in the event object evt.
        
        if obj.MessageService.isaSSF && ~obj.AllowEdit && ~obj.ShowRemovedData
            data = obj.MessageService.SweepsetFilter;
        elseif obj.MessageService.isaSSF
            data = obj.MessageService.SweepsetWithBadData;
        else
            data = obj.MessageService.Sweepset;
        end
        
        row_selection = evt.JavaEvent.getRowIndices+1;
        col_selection = evt.JavaEvent.getColumnIndices+1;
        updateColumnNames(obj);
        if isempty(obj.ColumnNames)
            data = data(row_selection, col_selection);
        else
            % get indices of visible columns in the sweepset
            data = data(row_selection, obj.ColumnNames(col_selection));
        end
        
        % Insert the real data where data is bad.  This operation must match
        % the data being viewed, ie the data created in asyncDataRequest
        badCells = isbad(data);
        isBadRow = all(badCells, 2);
        if any(isBadRow)
            badData  = spalloc(size(badCells, 1), size(badCells, 2), sum(badCells(:)));
            badData(badCells) = baddata(data);
            data(isBadRow, :) = badData(isBadRow, :);
        end
        
        % place data in clipboard
        mbcutils.Clipboard.guiCopy(double(data));
        
        end  % copyRequest
        
        %----------------------------------------
        function out = gettitle(obj) %#ok<MANU>
        %GETTITLE returns string describing this view
        %  OUT = GETTITLE(OBJ)
        
        out = 'Data Table';
        end  % gettitle
        
        %----------------------------------------
        function pPostSetDmsIsReadOnly(obj, ~,~)
        %PPOSTSETDMSISREADONLY Recalculate whether table cells should be editable or not
        %  PPOSTSETDMSISREADONLY(OBJ, src,EVENT)
        
        obj.pUpdateOptionsMenus;
        obj.Table.Editable = obj.AllowEdit;
        
        end  % pPostSetDmsIsReadOnly
        
        %----------------------------------------
        function pRequestUpdate(obj, updates)
        %PREQUESTUPDATE Request updates to the table
        %  PREQUESTUPDATE(OBJ, UPDATES) requests that updated data be sent to the
        %  table.  UPDATES is a 3-element logical vector where the value of each
        %  element indicates whether a part of the table needs to be updated:
        %
        %  Element 1:  Column data required
        %  Element 2:  Row data required
        %  Element 3:  Cell data required
        %
        %  PREQUESTUPDATE(OBJ) requests that completely new data is sent to the
        %  table.
        
        if nargin<2
            updates = [true true true];
        end
        
        % Combine update with any outstanding requests
        updates = updates | obj.UpdatesRequested;
        obj.UpdatesRequested = updates;
        
        % Check data message queue for outstanding events that will be processed.
        % If there are any there then defer this update request
        if ~i_findevents(obj)
            obj.pUpdateTable;
        end
        end  % pRequestUpdate
        
        function pUndoEdit(obj)
        %PUNDOEDIT  Reverts any edited cells in the selected region
        %  PUNDOEDIT(OBJ) reverts the currently selected cells to their unedited
        %  values.
        
        rows = obj.Table.getSelectedRows; % all selected rows
        columns = obj.Table.getSelectedColumns; % all selected columns
        if isempty(rows) || isempty(columns)
            return % no selection
        end
        
        % Get the sweepset currently on view, so that we can find the GUIDs of the selected rows
        ss = obj.pGetCurrentViewData;
        
        busy(obj.MessageService);
        
        updateColumnNames(obj)
        if ~isempty(obj.ColumnNames)
            names = obj.ColumnNames;
        else
            names = get(ss,'name');
        end
        
        clear_guids = getGuids(ss(rows));
        clear_vars = names(columns);
        
        ssf = obj.MessageService.SweepsetFilter;
        vars = get(ssf,'variables');
        sweepvars = get(ssf,'sweepvariables');
        % remove any columns which are derived variables and sweep variables, and aren't modifiable
        clear_vars = setdiff(clear_vars, {vars.varName,sweepvars.varName});
        
        % Clear the selected portion of the modified data.
        ssf = modifyData(ssf,clear_guids,clear_vars(:)',[]);
        
        obj.MessageService.flushEventQueue(ssf);
        idle(obj.MessageService);
        
        end  % pUndoEdit
        
        function pUpdateOptionsMenus(obj)
        %PUPDATEOPTIONSMENUS Setup the options menus correctly
        %  PUPDATEOPTIONSMENUS(OBJ) sets the enable status of the options menu
        %  items to match the current data status.
        
        % the "Allow Editing" menu
        if ~isempty(obj.MessageService) ...
                && obj.MessageService.isaSSF ...
                && ~obj.MessageService.isReadOnly
            % Enable the "Allow Edit" menu, but "Undo" or "Duplicate"
            % actions depend whether editing is on
            obj.Options.Actions(3).Enabled = true;
            obj.Options.Actions(3).Selected = obj.AllowEdit;
            set(obj.Options.Actions(4:5),'Enabled',obj.AllowEdit);
        else
            % Sweepset or no data.  Disable and un-check "Allow Edit", and disable
            % "Undo" and "Duplicate" too.
            set(obj.Options.Actions(3:5),'Enabled',false);
            obj.AllowEdit = false;
            obj.Options.Actions(3).Selected = false;
        end
        
        % show removed data
        obj.Options.Actions(2).Selected = obj.ShowRemovedData;
        
        end  % pUpdateOptionsMenus
        
        %----------------------------------------
        function pasteRequest(obj, ~, evt)
        %PASTEREQUEST Execute a paste operation on the table selection
        %  PASTEREQUEST(OBJ, hTable, evt) pastes the data specified by the rows
        %  and columns in the event object.
        
        % The appropriate-ness of the data to be pasted should already have
        % been checked.  The isNumeric check here is just the last hurdle prior
        % to performing the paste
        if mbcutils.Clipboard.isNumeric()
            new_data = mbcutils.Clipboard.paste();
            
            row_selection = evt.JavaEvent.getRowIndices+1;
            col_selection = evt.JavaEvent.getColumnIndices+1;
            if length(row_selection)==1 && size(new_data, 1)>1
                % Expand the row selection from the single cell selection
                row_selection = row_selection : (row_selection + size(new_data, 1) - 1);
            end
            if length(col_selection)==1 && size(new_data, 2)>1
                % Expand the column selection from the single cell selection
                col_selection = col_selection : (col_selection + size(new_data, 2) - 1);
            end
            
            % Use names instead of indices
            updateColumnNames(obj)
            if ~isempty(obj.ColumnNames)
                col_selection = obj.ColumnNames(col_selection);
            end
            
            % Which sweepset are we viewing - to get the correct guids to indicate
            % where the change is occurring
            g = getGuids(obj.MessageService.SweepsetWithBadData);
            
            ssf = obj.MessageService.SweepsetFilter;
            ssf = modifyData(ssf,g(row_selection), col_selection, new_data);
            obj.MessageService.flushEventQueue(ssf);
        end
        
        end  % pasteRequest
        
        %----------------------------------------
        function out = serializeView(obj)
        %SERIALIZEVIEW Returns selected column names for storage
        
       if obj.MessageService.hasData
           updateColumnNames(obj)
           out = {'ColumnNames' obj.ColumnNames,'ShowRemovedData',obj.ShowRemovedData};
       else
           out = {};
       end
        
        end  % serializeView
        
        function deserializeView(obj,varargin)
        %deserializeView appled saved settings for multiple data plots

        deserializeView@xregdatagui.AbstractDataView(obj,varargin{:});
        if nargin>1
            obj.pRequestUpdate([true true true]);
        end
        end
        
    end  % public methods
    
    
    methods (Hidden) % possibly private or hidden
        %----------------------------------------
        function asyncDataRequest(obj, ~,evt)
        %ASYNCDATAREQUEST Private method
        %  ASYNCDATAREQUEST(OBJ, EVT) is called when a dataRequest event is
        %  received from the table.
        
        if obj.MessageService.isaSSF
            showRemoved = obj.AllowEdit || obj.ShowRemovedData;
            if showRemoved 
                data = obj.MessageService.SweepsetWithBadData;
            else
                data = obj.MessageService.SweepsetFilter;
            end
        else
            showRemoved = false;
            data = obj.MessageService.Sweepset;
        end
        
        je = evt.JavaEvent;
        startRow = je.getRow+1;
        endRow = je.getLastRow+1;
        
        if startRow<1
            startRow = 1;
        end
        if endRow>size(data, 1)
            endRow = size(data, 1);
        end
        updateColumnNames(obj)
        colNames = obj.ColumnNames;
        names = get(data,'Name');
        if isempty(colNames)
            colNames = names;
        end
        subset_names = colNames;
        [isok, idx] = ismember(subset_names, names);
        if any(~isok)
            subset = NaN(endRow-startRow+1, length(colNames));
            subset(:, isok) = data(startRow:endRow, idx(isok));
        else
            subset = data(startRow:endRow, idx);
        end
        
        isOutlier = i_getOutliers(obj, subset);
        if ~showRemoved
            isBad = [];
            isModified = [];
        else
            badCells = isbad(subset);
            isBad = all(badCells, 2);
            if any(isBad)
                badData  = spalloc(size(badCells, 1), size(badCells, 2), sum(badCells(:)));
                badData(badCells) = baddata(subset);
                subset(isBad, :) = badData(isBad, :);
            end
            
            % Find modified data. We must retrieve the modifications
            % from the SSF, regardless of what we are displaying
            m = get(obj.MessageService.SweepsetFilter,'modifieddata');
            [modRowToCopy, modRowInSubSet] = ismember(m.rowGuid, getGuids(subset));
            [modColToCopy, modColInSubSet] = ismember(m.columnName, get(subset, 'Name'));
            
            isModified = false(size(subset, 1), size(subset, 2));
            isModified(modRowInSubSet(modRowToCopy), modColInSubSet(modColToCopy)) = ...
                m.dataPosition(modRowToCopy, modColToCopy);
        end
        obj.Table.Peer.setAsyncData(double(subset), isOutlier, isBad, isModified, ...
            je, startRow-1, 0);
        end  % asyncDataRequest
        
        %----------------------------------------
        function pAllowEdit(obj)
        %PALLOWEDIT Private method that switches between edit and view modes.
        %  PALLOWEDIT(obj) toggles the current editable state.
        
        % duplicate rows and undo edits are only valid when editing
        set(obj.Options.Actions(4:5),'Enabled',obj.AllowEdit);
        
        pShowRemovedData(obj)
        
        end
        
        function pShowRemovedData(obj)
        %pShowRemovedData show removed data
        
        sel_row = obj.Table.getSelectedRows; % first selected row
        if ~isempty(sel_row)
            sel_row = sel_row(1);
            sel_test = find( obj.TStarts > sel_row,1,'first' ) - 1; % test containing this row
            if isempty(sel_test) && ~isempty(obj.TStarts)
                sel_test = obj.TStarts(end);
            else
                sel_test = [];
            end
        else
            sel_test = [];
        end
        
        % find GUID of selected record
        ss = obj.pGetCurrentViewData;
        if ~isempty(ss) && ~isempty(sel_row)
            guid = getGuids(ss(sel_row));
        else
            guid = [];
        end
        
        % The outliers have not changed, but their row indices may have
        ss = obj.pGetCurrentViewData; % changed since ss above
        outlier_guids = obj.MessageService.OutlierLine.OutlierGuids;
        outlier_rows = getIndices( getGuids(ss) , outlier_guids );
        outlier_rows(outlier_rows==0) = []; % remove any unmatched entries
        obj.OutlierRows = outlier_rows;
        dispNames = obj.DisplayedColumnNames;
        if isequal(dispNames,obj.ColumnNames) || (isempty(obj.ColumnNames) && isequal(get(ss,'name'),dispNames))
            % quick redraw is possible if display order is the same as
            % column order
            updates = [false true true];
        else
            updateColumnNames(obj);
            updates = [true true true];
        end
        % redraw
        obj.pRequestUpdate(updates);
        
        % find the row containing the selected record in the updated table
        if ~isempty(guid)
            new_sel_row = getIndices( getGuids(obj.pGetCurrentViewData) , guid );
            if new_sel_row~=0
                obj.Table.selectRows(new_sel_row);
            elseif ~isempty(sel_test) && any(sel_test>0)
                % record is no longer present.  use the first one in the test
                x = find(obj.TNums==sel_test);
                if (x>0)
                    row = obj.TStarts(x);
                    obj.Table.selectRows(row);
                end
            end
        end
        
        end  % pAllowEdit
        
        %----------------------------------------
        function pCreateDisplay(obj)
        %PCREATEDISPLAY create display
        %  PCREATEDISPLAY(OBJ) creates all uicontrols and visual components for
        %  this view.
        
        
        dataEditor = com.mathworks.toolbox.mbc.gui.peer.DataEditorTablePeer;
        obj.Table = mbcwidgets.Table2D(dataEditor, ...
            'Parent', obj.Parent, ...
            'UIContextMenu', obj.UIContextMenu, ...
            'ValueChangedCallback', @obj.onEditTable);
        % Add a listener to select this view on a table mouse-press event
        obj.addListeners(handle.listener(obj.Table.Peer, 'MousePressed', {@i_SelectCallback,obj}));
        
        % Attach to asynchronous data requests
        asyncList = [...
            handle.listener(obj.Table.Peer, 'DataRequested',  @obj.asyncDataRequest); ...
            handle.listener(obj.Table.Peer, 'CopyRequested',  @obj.copyRequest); ...
            handle.listener(obj.Table.Peer, 'PasteRequested',  @obj.pasteRequest);
            ];
        set(asyncList, 'CallbackTarget', obj);
        obj.addListeners(asyncList);
        obj.ContentHandle = obj.Table;
        createActions(obj)
        obj.pRequestUpdate;
        obj.pUpdateOptionsMenus;
        end  % pCreateDisplay
        
        function createActions(obj)
        %createActions create view actions
        obj.Options.Actions = [
            mbcgui.actions.StatefulAction(@obj.onSelectColumns,...
            'Select &Columns to Display...','Select columns to display',[])
            mbcgui.actions.ToggleAction(@obj.onShowRemovedData,...
            'Show &Removed Data','Show removed data',[])
            mbcgui.actions.ToggleAction(@obj.onAllowEdit,...
            '&Allow Editing','Allow data editing',[])
            mbcgui.actions.StatefulAction(@obj.onUndoEdits,...
            '&Undo Edits in Selected Region','Undo edits in selected region',[])
            mbcgui.actions.StatefulAction(@obj.onDuplicateRecords,...
            '&Duplicate Selected Records','Duplicate selected records',[])
            ];
        
        end
        
        %----------------------------------------
        function ss = pGetCurrentViewData(obj)
        %PGETCURRENTVIEWDATA get data for view
        
        if obj.MessageService.isaSSF && (obj.AllowEdit || obj.ShowRemovedData)
            ss = obj.MessageService.SweepsetWithBadData;
        else
            ss = obj.MessageService.Sweepset;
        end
        end  % pGetCurrentViewData
        
        %----------------------------------------
        function pPostSetDataMessageService(obj, ~)
        %PPOSTSETDATAMESSAGESERVICE setup MessageService listeners
        %    PPOSTSETDATAMESSAGESERVICE(OBJ, SRC, EVT)
        
        % Call the super pPostSetDataMessageService
        pPostSetDataMessageService@xregdatagui.AbstractDataView(obj);
        
        dms = obj.MessageService;
        dmsListeners = [...
            event.listener(dms, 'ssDataChanged',         @(h,evt) obj.pRequestUpdate([false true true]));...
            event.listener(dms, 'ssfBadDataChanged',     @(h,evt) obj.pRequestUpdate([false true true]));...
            event.listener(dms, 'ssNamesChanged',        @(h,evt) obj.pRequestUpdate([true false false]));...
            event.listener(dms, 'dmsDataTypeChanged',    @obj.pdmsDataTypeChangedUpdate);...
            event.listener(dms, 'ssRecordsChanged',      @(h,evt) obj.pRequestUpdate([false false true]));...
            event.listener(dms, 'ssTestsChanged',        @(h,evt) obj.pRequestUpdate([false true false]));...
            event.listener(dms, 'SelectedTestsChanged',  @obj.pSelectedTestsChangedUpdate);...
            ];
        % listen for changes to selected outliers so the table can be
        % highlighted
        outlier_line = obj.MessageService.OutlierLine;
        OutlierListener = event.proplistener(outlier_line, outlier_line.findprop('OutlierIndices'),...
            'PostSet', @obj.onMarkOutliers);
        
        obj.Listeners = [obj.Listeners(:) ; dmsListeners(:);OutlierListener];
        end  % pPostSetDataMessageService
        
        %----------------------------------------
        function changed = pSelectColumns(obj)
        %PSELECTCOLUMNS select columns to display in table
        %  CHANGED = PSELECTCOLUMNS(OBJ) displays a dialog for selecting which
        %  columns to display.  CHANGED is set to true if any display changes are
        %  made.
        
        dlg = mbcgui.container.Dialog('Name','Columns To Display',...
            'Size',[300, 400]);
        xregcenterfigure(dlg.Figure, [300, 400], ancestor(obj.Parent,'figure'));
        
        listctrl = mbcwidgets.List('Parent',dlg.Figure,...
            'Editable',true,...
            'ValueChangedCallback',@iSelectColumn,...
            'SelectionMode','MultiRow');
        listctrl.setColumnData({'Column Name'});
        listctrl.setColumnWidths(250);
        listctrl.Peer.setDisplayChecks(true);
        
        names = get(obj.pGetCurrentViewData, 'Name');
        if ~isempty(obj.ColumnNames)
            Ticked = ismember(names, obj.ColumnNames);
        else
            % select all columns by default
            Ticked = true(size(names));
        end
        
        listctrl.setData( names(:) )
        listctrl.Peer.setChecks( Ticked(:) )
        
        check = uicontrol('Parent',dlg.Figure,...
            'Style','pushbutton',...
            'String','Check selected items',...
            'Tag','CheckSelected',...
            'Callback', @iCheckSelectedRows);
        uncheck = uicontrol('Parent',dlg.Figure,...
            'Style','pushbutton',...
            'Tag','UncheckSelected',...
            'String','Uncheck selected items',...
            'Callback', @iUncheckSelectedRows);
        
        dlg.Content = xreggridbaglayout(dlg.Figure,...
            'dimension',[3 2],...
            'rowsizes',[-1 25 5 ],...
            'mergeblock',{[1 1],[1 2]},...
            'gapx',2,'gapy',2,...
            'border',[10 10 10 10],...
            'elements',{listctrl,check,[],[],uncheck,[]});
        
        % store controls in UserData for testing
        ud.CheckSelected   = check;
        ud.UncheckSelected = uncheck;
        ud.Listview = listctrl;
        set(dlg.Figure,'UserData',ud);
        
        changed = 0;
        if strcmp(dlg.showDialog(),'OK');
            if all(Ticked)
                % all selected 
                obj.ColumnNames = [];
            else
                % store display names
                obj.ColumnNames = names(Ticked);
            end
            changed = 1;
        end
        
        delete(dlg);
        drawnow expose;
        
            function iCheckSelectedRows(~,~)
            %iCheckSelectedRows check all selected rows
            
            sel = listctrl.getSelectedRows;
            if ~isempty(sel)
                Ticked(sel) = true;
                listctrl.Peer.setChecks(Ticked)
                listctrl.selectRows(sel);
                % enable OK button
                enableButtons(dlg,'OK');
                drawnow expose;
            end
            end  % iCheck
        
        
            function iUncheckSelectedRows(~,~)
            %iUncheckSelectedRows uncheck all selected rows
            
            sel = listctrl.getSelectedRows;
            
            if ~isempty(sel)
                Ticked(sel) = false;
                listctrl.Peer.setChecks(Ticked)
                listctrl.selectRows(sel);
                if ~any(Ticked)
                    % can't select no columns
                    disableButtons(dlg,'OK');
                end
                drawnow expose;
            end
            end  % iUncheck
        
            function iSelectColumn(~,evt)
            %iSelectColumns select checkbox in table
            
            % toggle selected
            Ticked(evt.data.Rows) = ~Ticked(evt.data.Rows);
            % enable or disable OK button if required
            if any(Ticked)
                enableButtons(dlg,'OK');
            elseif ~any(Ticked)
                % can't select no columns
                disableButtons(dlg,'OK');
            end
            
            end
        
        end  % pSelectColumns
        
        %----------------------------------------
        function pSelectedTestsChangedUpdate(obj, ~,~)
        %PSELECTEDTESTSCHANGEDUPDATE Private method.  Selects the relevant tests in the table.
        %  PSELECTEDTESTSCHANGEDUPDATE(OBJ, EVENT)
        
        ts = obj.TStarts;
        tn = obj.TNums;
        
        if isempty(tn)
            return;
        end
        
        selected = obj.MessageService.SelectedTests;
        selected = tn(selected);
        if ~isempty(selected)
            ind = find(selected(1)==tn);
            if ~isempty(ind)
                row = ts(ind);
                obj.Table.selectRows(row);
            end
        end
        
        end  % pSelectedTestsChangedUpdate
        
        %----------------------------------------
        function pUpdateTable(obj)
        %PUPDATE Updates the contents of the table.
        %  PUPDATETABLE(OBJ) updates the parts of the table that have previously
        %  been requested as needing an update.
        
        updates = obj.UpdatesRequested;
        if ~isempty(obj.MessageService)
            data = obj.pGetCurrentViewData;
            if all(updates) || (updates(1) && (updates(2) || updates(3)))
                nR = size(data,1);
                [cn, locks] = i_getcolumndata(obj, data);
                [tnums, tstart] = i_getrowdata(obj, data);
                obj.Table.Peer.setData(nR, cn, locks, tstart, tnums);
            else
                if updates(1)
                    [cn, locks] = i_getcolumndata(obj, data);
                    obj.Table.Peer.setColumnData(cn, locks);
                end
                if updates(2)
                    [tnums, tstart] = i_getrowdata(obj, data);
                    obj.Table.Peer.setRowHeaderData(tstart, tnums);
                end
                if updates(3)
                    nR = size(data,1);
                    obj.Table.Peer.changeRowCount(nR);
                end
            end
        else
            obj.Table.Peer.setData(0, {}, [], [], []);
        end
        obj.Table.Editable = obj.AllowEdit;
        obj.Table.Peer.setShowRecords(obj.ShowRemovedData);
        obj.UpdatesRequested = [false false false];
        end  % pUpdateTable
        
        %----------------------------------------
        function pdmsDataTypeChangedUpdate(obj,varargin)
        %PDMSDATATYPECHANGEDUPDATE Private method.  Handles change in data type.
        %  PDMSDATATYPECHANGEDUPDATE(OBJ);
        %  Enables and disables the "Allow edit" button
        
        obj.pUpdateOptionsMenus;
        
        end  % pdmsDataTypeChangedUpdate
        
        function onAllowEdit(obj,action,~)
        %onAllowEdit allow edit 
        
        obj.AllowEdit = action.Selected;
        obj.pAllowEdit;
        end  % i_AllowEdit
        
        %----------------------------------------
        function onDuplicateRecords(obj,~,~)
        %onDuplicateRecords duplicate selected rows
        
        rows = obj.Table.getSelectedRows;
        if isempty(rows)
            % no selection
            return
        end
        
        % Get the sweepset currently on view, so that we can find the GUIDs of the selected rows
        ssf = obj.MessageService.SweepsetFilter;
        
        ssptr = dataptr(ssf); % underlying data
        ss = ssptr.info;
        
        rows = sort(double(rows));
        for i=1:length(rows)
            rows(i) = rows(i) + i - 1; % number of rows already inserted before this one.
            ss = [ss(1:rows(i),:) ; double(ss(rows(i),:)) ; ss(rows(i)+1:size(ss, 1),:)];
        end
        ssptr.info = ss;
        
        queueEvent(ssf, {'ssDataChanged' 'ssRecordsChanged'});
        ssf = updateAll(ssf);
        obj.MessageService.flushEventQueue(ssf);
        obj.Table.selectRows(rows);
        end  % i_DuplicateRecord
        
        %----------------------------------------
        function onSelectColumns(obj,~,~)
        %onSelectColumns select columns to display callbacks
        changed = obj.pSelectColumns;
        if changed
            obj.pRequestUpdate([true, false, true]);
        end
        end  % i_SelectColumns
        
        %----------------------------------------
        function onUndoEdits(obj,~,~)
        %onUndoEdits undo edits callback
        pUndoEdit(obj);
        end  % i_UndoEdit
        
        function onShowRemovedData(obj,action,~)
        %onShowRemovedData show removed records callback
        obj.ShowRemovedData = action.Selected;
        pShowRemovedData(obj)
        end
        
        function onMarkOutliers(obj,~,~)
        %onMarkOutliers outliers have changed
        
        if isgraphics(obj.Parent)
            ss = obj.pGetCurrentViewData;
            new_guids = obj.MessageService.OutlierLine.OutlierGuids;
            new_rows = getIndices( getGuids(ss) , new_guids );
            new_rows(new_rows==0) = []; % remove any unmatched entries
            old_rows = obj.OutlierRows;
            obj.OutlierRows = new_rows; % store for next time
            
            add_rows = setdiff(new_rows,old_rows);
            remove_rows = setdiff(old_rows,new_rows);
            obj.Table.Peer.setOutlierAt([add_rows(:);remove_rows(:)]-1, ...
                [true(length(add_rows),1); false(length(remove_rows), 1)]);
        end
        end  % onMarkOutliers
        
        function onEditTable(obj,~,evt)
        %onEditTable table edited callback
        
        % replace current table cursor with hourglass
        busy(obj.MessageService);
        
        updateColumnNames(obj)
        
        if ~isempty(obj.ColumnNames)
            column = obj.ColumnNames(evt.data.Columns); % use name instead of index
        else
            column = evt.data.Columns;
        end
        
        % Which sweepset are we viewing - to get the correct guids to indicate
        % where the change is occurring
        g = getGuids(obj.MessageService.SweepsetWithBadData);
        ssf = obj.MessageService.SweepsetFilter;
        
        ssf = modifyData(ssf,g(evt.data.Rows),column, evt.data.NewValue);
        obj.MessageService.flushEventQueue(ssf);
        
        % replace the original cursor
        idle(obj.MessageService);
        end
        
        function updateColumnNames(obj)
        %updateColumnNames update column names from the Java table
        
        dispColumns = obj.DisplayedColumnNames;
        allNames = get(obj.MessageService.Sweepset,'name');
        if ~isempty(dispColumns) 
            if isequal(allNames,dispColumns)
                obj.ColumnNames = [];
            else
                obj.ColumnNames = dispColumns;
            end
        end
        end
        
    end  % possibly private or hidden
    
end  % classdef

function isOut = i_getOutliers(obj,ss)
% Check which data guids are in the GUIDs of the outlier line
outliers = obj.MessageService.OutlierLine.OutlierGuids;
if ~isempty(ss)
    isOut = ismember(getGuids(ss), outliers);
else
    isOut = false(0,1);
end
end  % i_getOutliers

%------------------------------------
function i_SelectCallback(~, ~, obj)

% Send ButtonDown from this view so that the framework makes it
% the current view.
notify(obj,'ButtonDown');
end  % i_SelectCallback

%-------------------------------------
function hasevent = i_findevents(obj)
eventsToCheck = {'ssDataChanged', ...
    'ssfBadDataChanged', ...
    'ssNamesChanged', ...
    'ssRecordsChanged', ...
    'ssTestsChanged'};
if ~isempty(obj.MessageService)
    hasevent = any(obj.MessageService.isEventPending(eventsToCheck));
else
    hasevent = false;
end
end  % i_findevents

%-------------------------------------
function [colNames, locks] = i_getcolumndata(obj, data)
allNames = get(data,'Name');

% updateColumnNames(obj);
colNames = obj.ColumnNames;
    
if ~isempty(colNames)
    % choose only column names which appear in the current data
    colNames = colNames( ismember(colNames,allNames) );
    obj.ColumnNames = colNames; % store for next time
end
if isempty(colNames)
    % if no columns will appear, show everything
    colNames = allNames;
    obj.ColumnNames = [];
end

if obj.MessageService.isaSSF
    % lock variables and test variables so they are not editable
    ssf = obj.MessageService.SweepsetFilter;
    vars = get(ssf,'variables');
    sweepvars = get(ssf,'sweepvariables');
    locks = ismember( colNames, {vars.varName,sweepvars.varName} );
else
    locks = false(size(colNames));
end
end  % i_getcolumndata

%-------------------------------------
function [tNums, tStart] = i_getrowdata(obj, data)
tStart = double(tstart(data));
tNums = double(testnum(data));
if obj.AllowEdit || obj.ShowRemovedData
    map = obj.MessageService.GoodToBadIndexMap;
    % select the visible subset of tests
    tStart = tStart(map);
    tNums = tNums(map);
end
obj.TStarts = tStart;
obj.TNums = tNums;
end  % i_getrowdata