classdef ScrollPanel < hgsetget
%SCROLLPANEL A uipanel container with scrolling capabilities
%   Uipanels provide containment and groups axes and their children.  The
%   ScrollPanel provides the ability to define a scrollable area and the
%   panel is the viewport onto that area.
%
%   ScrollPanel does support the border properties of the uipanel object,
%   but in a slightly different way.  The border of the ScrollPanel does
%   not intrude upon the client area, but extends outward from it.  The
%   border of a panel is lightweight and would be obscured by the scrolling
%   objects in the scroll area.  The position of the object with the border
%   may be set using the OuterPosition property.
%
%   ScrollPanel properties:
%       Parent        - Identifies the figure or uipanel that contains the
%                       ScrollPanel object.
%       ScrollArea    - Defines the area relative the the object's position
%                       that can be scrolled through.  This area may never
%                       be less than the object's area.
%       Position      - The position of the object relative to its
%                       containing parent.  This position also serves as a
%                       viewport or window into the ScrollArea.
%       OuterPosition - The position of the object relative to its
%                       container, but including the border.
%       Backgroundcolor - The color of the viewport area.
%       Units         - The units of the ScrollPanel object as well as the
%                       ScrollArea.  The units of th me.
%       Visible       - Sets the visibility of the object.
%       ScrollMode          - Controls the appearance of scrollbars on the panel.
%                       'auto'       - automatically shows the appropriate
%                                      verticle or horizontal scrollbar if the
%                                      height or width of the ScrollArea is
%                                      greater than the height or width of the
%                                      object's position. This setting is the
%                                      default.
%                       'verticle'   - Will always display only a verticle
%                                      scrollbar.
%                       'horizontal' - will always display only a
%                                      horizontal scrollbar.
%                       'both'       - will always display both scrollbars.
%       handle        - This handle value should be used parent Handle
%                       Graphics objects to the ScrollPanel object, e.g.
%                       set(ax, 'Parent', h.handle).
%       BorderWidth, BorderPanel, Highlight Color, Shadow color - the
%                       properties of the ScrollPanel object equivalent to
%                       the ones on the uipanel object.
%
%
%   Examples:
%
%       Create a plot larger than the viewport panels position:
%           h=ScrollPanel;
%           set(h,'Position',[.25 .25 .5 .5];
%           set(h, 'ScrollArea', [0 0 2 2])
%           ax=axes('parent',h.handle)
%           plot(ax,magic(3))
%
%       Scroll around a large image:
%           imdata = imread('ngc6543a.jpg');
%           h=ScrollPanel('Position',[.25 .25 .5 .5], ...
%                         'ScrollArea', [0 0 3 3]);
%           axes('Parent', h.handle, 'Position', [0 0 1 1]);
%           image(imdata);
%
%   An important point to note is that the ScrollPanel object has the same
%   limitations as the uipanel object in terms of clipping.  Heavyweight
%   objects such as uicontrols will not be clipped.  Since scrolling will
%   tend to move these heavyweight object outside the viewport position,
%   care much be used to achieve the desired result.
%
%   The use of OpenGL and surface objects in general is also problematic
%   since they are heavyweight.  So, it is advisable to stick to 2D without
%   transparency.
%
%   See also UIPANEL.

%   Author: Jason Kinchen, MATLAB 7.11.0.514 (R2010b)
%   Contact: jason.kinchen@gmail.com
%   Revision: 1.0 (12-Dec-2010)
%
%   Comments:
%

    properties (Hidden = true, Access = private)
        hBorderPanel            % A panel to manage the borders that won't clipped.
        hViewportPanel          % The main panel
        hScrollingPanel         % The container for the ScrollingArea
        hVScrollBar
        hHScrollBar
        scrollBarWidth = 15;
        privateMode = 'auto'    % The dependent workaround for the ScrollMode property
    end
    properties (Dependent)
        %PARENT Parent object of the SCROLLBAR.
        %   Since ScrollPanel is not an internal Handle Graphics object,
        %   parenting must be done explicitly.  Use this property to set
        %   the parent container of the ScrollPanel object.
        %
        %   See also UIPANEL.
        Parent

        %SCROLLAREA The position vector relative to the object's Position.
        %   The ScrollArea property of the ScrollPanel defines the region
        %   that the main viewport panel may scroll.
        %
        %   See also UIPANEL.
        ScrollArea

        %POSITION This position vector defines where the visible viewport
        %   of the ScrollPanel appears relative to it's parent.
        %
        %   See also UIPANEL.
        Position

        %OUTERPOSITION This position vector defines the postion of the
        %   including the client region and the border.  Use this property
        %   to place the object if its place in the container is more
        %   important than the size of the scrolling viewport.
        OuterPosition

        %BACKGROUNDCOLOR The background color of the ScrollArea using a
        %   ColorSpec.
        %
        %   See also UIPANEL.
        BackgroundColor

        %UNITS The units (pixels, points, characters, inches, centimeters,
        %   normalized) of both the main viewport and the ScrollArea.
        %
        %   See also UIPANEL.
        Units

        %VISIBLE Hides or shows the ScrollPanel without changing any other
        %   property values.
        %
        %   See also UIPANEL.
        Visible

        %SCROLLMODE Determines how the scrollbars appear.  Valid values are:
        %      'auto'       - automatically shows the appropriate
        %                     verticle or horizontal scrollbar if the
        %                     height or width of the ScrollArea is
        %                     greater than the height or width of the
        %                     object's position.
        %      'verticle'   - Will always display only a verticle
        %                     scrollbar.
        %      'horizontal' - will always display only a
        %                     horizontal scrollbar.
        %       'both'      - will always display both scrollbars.
        %
        %   See also UIPANEL.
        ScrollMode = 'auto';

        %BORDERWIDTH The width of the main viewport panel's border in
        %   pixels.
        %
        %   See also UIPANEL.
        BorderWidth = 1;

        %BORDERTYPE
        %   none | {etchedin} | etchedout | beveledin | beveledout | line
        %
        %   Border of the main viewport. Used to define the panel area graphically.
        %   Etched and beveled borders provide a 3-D look. Use the HighlightColor
        %   and ShadowColor properties to specify the border color of etched and
        %   beveled borders. A line border is 2-D. Use the ForegroundColor
        %   property to specify its color.
        %
        %   See also UIPANEL.
        BorderType = 'etchedin';

        %FOREGROUNDCOLOR Color used for the 2-D border line. A three-element
        %   RGB vector or one of the MATLAB predefined names, specifying the
        %   font or line color. See the ColorSpec reference page for more
        %   information on specifying color.
        %
        %   See also UIPANEL.
        ForegroundColor = [1 1 1];

        %HIGHLIGHTCOLOR 3-D frame highlight color. A three-element RGB vector
        %   or one of the MATLAB predefined names, specifying the highlight
        %   color. See the ColorSpec reference page for more information on
        %   specifying color.
        %
        %   See also UIPANEL, COLORSPEC.
        HighlightColor = [1 1 1];

        %SHADOWCOLOR 3-D frame shadow color. A three-element RGB vector or
        %   one of the MATLAB predefined names, specifying the shadow color.
        %   See the ColorSpec reference page for more information on specifying
        %   color.
        %
        %   See also UIPANEL, COLORSPEC.
        ShadowColor = [.5 .5 .5];

    end
    properties (SetAccess = private)
        %HANDLE Since ScrollPanel is not a builtin Handle Graphics object,
        %   this property is used to directly parent object to the
        %   ScrollArea.  This handle should not be used to parent the
        %   ScrollPanel object to some other container.  Use the Parent
        %   property for that.
        %
        %   See also UIPANEL.
        handle
    end

    methods
        % The ScrollPanel constructor which takes parameter-value pairs.
        function obj = ScrollPanel(varargin)
            fig = gcf;
            % Detect if the Parent was passed in, otherwise use current
            % figure.
            for i = 1 : 2 : length(varargin)
                name = varargin{i};
                value = varargin{i+1};
                switch name
                    case 'Parent'
                      	fig = value;
                    break;
                    otherwise;
                end
            end

            % Create the objects that make up the ScrollPanel.
            obj.hBorderPanel = uipanel('Parent', fig, 'BackgroundColor', 'b',...
                                       'HandleVisibility', 'off');

            obj.hViewportPanel = uipanel('Parent', fig, 'BackgroundColor', 'r',...
                                         'BorderType', 'none',...
                                         'HandleVisibility', 'off');
            obj.Parent = fig;
            % The ScrollArea defaults to the same size as the Main Viewport
            % panel.
            obj.hScrollingPanel = uipanel('Parent', obj.hViewportPanel, ...
                                          'BorderType', 'none',...
                                          'HandleVisibility','off');

            % The slider uicontrols (scrollbars) are created invisible, but
            % are positioned in anticipation of being used.
            obj.hVScrollBar = uicontrol('Style', 'slider',...
                                       'Parent', obj.hViewportPanel,...
                                       'Units','pixels',...
                                       'Visible', 'off',...
                                       'Enable', 'off',...
                                       'HandleVisibility', 'off');

            obj.hHScrollBar = uicontrol('Style', 'slider',...
                                       'Parent', obj.hViewportPanel,...
                                       'Units','pixels',...
                                       'Position', [0 0 1 .05],...
                                       'Visible', 'off',...
                                       'Enable', 'off',...
                                       'HandleVisibility', 'off');

            % Setup the scrollbar listeners.
            addlistener(obj.hVScrollBar, 'Action', @obj.scroll);
            addlistener(obj.hHScrollBar, 'Action', @obj.scroll);
            % Setup the main viewport's size listener.  Lot's of stuff
            % needs to happen when that viewport changes.
            addlistener(obj.hViewportPanel, 'SizeChange', @obj.resize);

            % The ScrollingPanel is the exposed handle so that objects can
            % be children of it.
            obj.handle = obj.hScrollingPanel;

            % Work through the parameter-value pairs and set the initial
            % state of the properties.
            for i = 1 : 2 : length(varargin)
                name = varargin{i};
                value = varargin{i+1};
                switch name
                    case 'Units'
                        obj.Units = value;
                    case 'Position'
                        obj.Position = value;
                        drawnow;
                    case 'OuterPostion'
                        obj.ScrollArea = value;
                    case 'ScrollArea'
                        obj.ScrollArea = value;
                    case 'ScrollMode'
                        obj.ScrollMode = value;
                    case 'Visible'
                        obj.Visible = value;
                    case 'BorderWidth'
                        obj.BorderWidth = value;
                    case 'BorderType'
                        obj.BorderType = value;
                    case 'ForeGroundColor'
                        obj.ForeGroundColor = value;
                    case 'HighlightColor'
                        obj.HighlightColor = value;
                    case 'ShadowColor'
                        obj.ShadowColor = value;
                    otherwise
                        error(['Invalid property ' name]);
                end
            end
            obj.privateMode = obj.ScrollMode;
            obj.setSBPosition('both');
        end

        function set.Parent(obj, val)
            set(obj.hViewportPanel, 'Parent', val);
        end
        function val = get.Parent(obj)
            val = get(obj.hViewportPanel, 'Parent');
        end
        function set.ScrollArea(obj, pos)
            % If normalized, the position relative to the figure window is
            % not useful.  From the ScrollArea's perspective, it is from
            % zero -> one.
            if (strcmp(get(obj.hViewportPanel, 'Units'), 'normalized'))
                mPos = [0 0 1 1];
            else
                mPos = get(obj.hViewportPanel, 'Position');
                mPos(1:2)=[0 0];
            end

            % The ScrollArea must completely contain the main viewport
            % area.  If it is larger than the viewport, it must either be
            % inside it's position rectangle or lined up on the sides.
            newSPos(1:2:3) = limitLines(mPos(1:2:3), pos(1:2:3));
            newSPos(2:2:4) = limitLines(mPos(2:2:4), pos(2:2:4));
            if (strcmp(get(obj.hViewportPanel, 'Units'), 'pixels'))
                newSPos(1:2) = newSPos(1:2) + 1;
            end

            if pos(3) > mPos(3) || pos(4) > mPos(4)
                set(obj.hScrollingPanel, 'Position', newSPos);

                % In auto mode, the appropriate scrollbars must appear if the
                % length or width of the ScrollArea is greater than that of
                % the main viewport.
                if strcmp(obj.ScrollMode, 'auto')
                    if newSPos(3) > mPos(3)
                        set(obj.hHScrollBar, 'Visible', 'on', 'Enable', 'on');
                    else
                        set(obj.hHScrollBar, 'Visible', 'off', 'Enable', 'off');
                    end
                    if newSPos(4) > mPos(4)
                        set(obj.hVScrollBar, 'Visible', 'on', 'Enable', 'on');
                    else
                        set(obj.hVScrollBar, 'Visible', 'off', 'Enable', 'off');
                    end
                end
            else
                % The ScrollArea cannot be smaller than the main viewport,
                % so if we get here, the height and width is stored in the
                % position vector, the origin needs to be zero, unless the
                % units are pixels, which are 1 based.
                if strcmp(get(obj.hViewportPanel, 'Units'), 'pixels')
                    set(obj.hScrollingPanel, 'Position', [1 1 mPos(3) mPos(4)]);
                else
                    set(obj.hScrollingPanel, 'Position', [0 0 mPos(3) mPos(4)]);
                end

                %  Scrollbars need to go away if the ScrollArea and main
                %  viewport are the same size.
                if strcmp(obj.ScrollMode, 'auto')
                    set(obj.hHScrollBar, 'Visible', 'off', 'Enable', 'off');
                    set(obj.hVScrollBar, 'Visible', 'off', 'Enable', 'off');
                end
            end

            % Make sure the scrollbars are the right size and the thumbs
            % are set to the right position and width.
            obj.setSBPosition('both');
        end
        function pos = get.ScrollArea(obj)
            pos = get(obj.hScrollingPanel, 'Position');
        end
        function set.Position(obj, pos)
            % Go ahead and set the main viewport's position.
            drawnow;
            set(obj.hViewportPanel, 'Position', pos);
            setBorderPosition(obj);

            if (strcmp(get(obj.hViewportPanel, 'Units'), 'normalized'))
                pos = [0 0 1 1];
            end
            sPos = get(obj.hScrollingPanel, 'Position');
            if sPos(3) <= pos(3)
                % Don't let the width become smaller than the viewport, and
                % turn off the scrollbar if in auto mode.
                sPos(3) = max([pos(3) sPos(3)]);
                if strcmp(obj.ScrollMode, 'auto')
                    set(obj.hHScrollBar, 'Enable', 'off', 'Visible', 'off');
                end
            else
                if strcmp(obj.ScrollMode, 'auto')
                    set(obj.hHScrollBar, 'Enable', 'on', 'Visible', 'on');
                end
            end
            if sPos(4) <= pos(4)
                % Don't let the height become smaller than the viewport,
                % and turn off the scrollbar if in auto mode.
                sPos(4) = max([pos(4) sPos(4)]);
                if strcmp(obj.ScrollMode, 'auto')
                    set(obj.hVScrollBar, 'Enable', 'off', 'Visible', 'off');
                end
            else
                if strcmp(obj.ScrollMode, 'auto')
                    set(obj.hVScrollBar, 'Enable', 'on', 'Visible', 'on');
                end
            end
            set(obj.hScrollingPanel, 'Position', sPos);
            setSBPosition(obj, 'both')
        end

        % Set/Get the following properties on the appropriate container.
        function pos = get.Position(obj)
            pos = get(obj.hViewportPanel, 'Position');
        end
        function set.OuterPosition(obj, pos)
        % The OuterPostion extends out from the position. The borderwidth
        % is always in pixels so we do all the adjustments in pixels.
            set(obj.hBorderPanel, 'Position', pos);
            drawnow;
            % The above lines set the postion in the current units and the
            % line below gets the new positoin in pixels.
            pxPos = getpixelposition(obj.hBorderPanel);

            % The position of the viewport panel must be adjusted in by the
            % BorderWidth.
            pxPos = [pxPos(1) + borderAdjust(obj) ...
                     pxPos(2) + borderAdjust(obj) ...
                     pxPos(3) - 2 * borderAdjust(obj) ...
                     pxPos(4) - 2 * borderAdjust(obj)];
            drawnow;
            setpixelposition(obj.hViewportPanel, pxPos);
            obj.setSBPosition('both');
        end
        function pos = get.OuterPosition(obj)
            pos = get(obj.hBorderPanel, 'Position');
        end
        function set.BackgroundColor(obj, color)
            set(obj.hScrollingPanel, 'BackgroundColor', color);
        end
        function color = get.BackgroundColor(obj)
            color = set(obj.hScrollingPanel, 'BackgroundColor');
        end
        function set.Units(obj, units)
            set(obj.hBorderPanel', 'Units', units');
            set(obj.hViewportPanel, 'Units', units);
            set(obj.hScrollingPanel, 'Units', units);
        end
        function units = get.Units(obj)
            units = get(obj.hViewportPanel, 'Units');
        end
        function set.Visible(obj, val)
            set(obj.hViewportPanel, 'Visible', val)
        end
        function val = get.Visible(obj)
            val = get(obj.hViewportPanel, 'Visible');
        end
        function set.ScrollMode(obj, val)
            % Check that the value passed in is a valid value.
            valid = {'auto', 'verticle', 'horizontal', 'both'};
            if any(cellfun(@(x) strcmp(x,val),valid))
                % Since this is a dependent property, we need to save the
                % actual value in the private property.
                obj.privateMode = val;
                switch(val)
                    case 'auto'
                        % If auto mode is turned on, all the right things
                        % happen if we call the set method using the
                        % current value.
                        set(obj, 'ScrollArea', get(obj, 'ScrollArea'));
                    case 'verticle'
                        % Enable the verticle, disable the horizontal.
                        set(obj.hVScrollBar, 'Enable', 'on', 'Visible', 'on');
                        set(obj.hHScrollBar, 'Enable', 'off', 'Visible', 'off');
                        obj.setSBPosition('verticle');
                    case 'horizontal'
                        % Enable the horizontal, disable the verticle.
                        set(obj.hHScrollBar, 'Enable', 'on', 'Visible', 'on');
                        set(obj.hVScrollBar, 'Enable', 'off', 'Visible', 'off');
                        obj.setSBPosition('horizontal');
                    case 'both'
                        % Enable the horizontal and the verticle.
                        set(obj.hVScrollBar, 'Enable', 'on', 'Visible', 'on');
                        set(obj.hHScrollBar, 'Enable', 'on', 'Visible', 'on');
                        obj.setSBPosition('both');
                end

            else
                error('ScrollPanel:setScrollMode:badScrollModeValue',...
                      ['Bad property value ' val ' for ScrollMode.']);
            end
        end
        function val = get.ScrollMode(obj)
            val = obj.privateMode;
        end
        function set.BorderWidth(obj, val)
            set(obj.hBorderPanel, 'BorderWidth', val);
            setBorderPosition(obj);
        end
        function val = get.BorderWidth(obj)
            val = get(obj.hBorderPanel, 'BorderWidth');
        end
        function set.BorderType(obj, val)
            set(obj.hBorderPanel, 'BorderType', val);
            setBorderPosition(obj);
        end
        function val = get.BorderType(obj)
            val = get(obj.hBorderPanel, 'BorderType');
        end
        function set.ForegroundColor(obj, val)
            set(obj.hBorderPanel, 'ForegroundColor', val);
        end
        function val = get.ForegroundColor(obj)
            val = get(obj.hBorderPanel, 'ForegroundColor');
        end
        function set.HighlightColor(obj, val)
            set(obj.hBorderPanel, 'HighlightColor', val);
        end
        function val = get.HighlightColor(obj)
            val = get(obj.hBorderPanel, 'HighlightColor');
        end
        function set.ShadowColor(obj, val)
            set(obj.hBorderPanel, 'ShadowColor', val);
        end
        function val = get.ShadowColor(obj)
            val = get(obj.hBorderPanel, 'ShadowColor');
        end
        function delete(obj)
            if ishghandle(obj.hBorderPanel)
                delete(obj.hBorderPanel);
            end
            if ishghandle(obj.hViewportPanel)
                delete(obj.hViewportPanel);
            end
        end
    end
    methods (Access = protected)
        % Don't want to allow the user to call these.
        function setSBPosition(obj, scrollbar)
            % The scrollbars in in pixel units, so we need to get the
            % main viewport's position in pixels.
            pos = getpixelposition(obj.hViewportPanel);

            % We are doing similiar position calculations for horizontal
            % and verticle scrollbars.  The following just sets up the
            % appropriate variables.
            if strcmp(scrollbar, 'verticle') || strcmp(scrollbar, 'both')
                hScrollBar = obj.hVScrollBar;
                otherhScrollBar = obj.hHScrollBar;
                origin_i = 2;
                length_i = 4;
                scrPos = [1 + pos(3) - obj.scrollBarWidth ...
                          1 ...
                          obj.scrollBarWidth ...
                          pos(length_i)];
                bothPos = [scrPos(1) ...
                           scrPos(2) + obj.scrollBarWidth ...
                           scrPos(3) ...
                           scrPos(4) - obj.scrollBarWidth];
            else
                hScrollBar = obj.hHScrollBar;
                otherhScrollBar = obj.hVScrollBar;
                origin_i = 1;
                length_i = 3;
                scrPos = [1 ...
                          1 ...
                          pos(3)...
                          obj.scrollBarWidth];
                bothPos = [scrPos(1) ...
                           scrPos(2) ...
                           scrPos(3) - obj.scrollBarWidth ...
                           scrPos(4)];
            end

            % Use the private property scrollBarWidth and set the
            % one-based position based on the viewport's area.  The length
            % of the scrollbar is slightly shorter if the other one is
            % visible.
            if strcmp(get(otherhScrollBar, 'Visible'), 'on')
                set(hScrollBar, 'Position', bothPos);
            else
                set(hScrollBar, 'Position', scrPos);
            end
            sPos = get(obj.hScrollingPanel', 'Position');

            % Get the viewport panel's position to calculate the thumbsize
            % and positon of the thumb.
            if strcmp(get(obj.hViewportPanel, 'Units'), 'normalized')
                mPos = [0 0 1 1];
            else
                mPos = get(obj.hViewportPanel, 'Position');
            end

            %Stepsize determines the thumb size.
            step = get(hScrollBar, 'SliderStep');
            step(2) = max(0, mPos(length_i)/(sPos(length_i) - mPos(length_i)));
            set(hScrollBar, 'SliderStep', step);

            if mPos(origin_i) == sPos(origin_i)
                slpos = 0;
            else if mPos(origin_i) + mPos(length_i) == sPos(origin_i) + sPos(length_i)
                slpos = 1;
                else
                    slpos = min([1, (mPos(origin_i) - sPos(origin_i))/(sPos(length_i)-mPos(length_i))]);
                end
            end

            set(hScrollBar, 'Value', slpos);

            if strcmp(scrollbar, 'both')
                setSBPosition(obj, 'horizontal');
            end
        end
        function val = borderAdjust(obj)
            val = get(obj.hBorderPanel, 'BorderWidth');
            switch get(obj.hBorderPanel, 'BorderType')
                case 'etchedin'
                    val = val * 2;
                case 'etchedout'
                    val = val * 2;
                case 'none'
                    val = 0;
                otherwise
            end
        end
        function setBorderPosition(obj)
            pxPos = getpixelposition(obj.hViewportPanel);
            setpixelposition(obj.hBorderPanel, [pxPos(1) - borderAdjust(obj) ...
                                                pxPos(2) - borderAdjust(obj) ...
                                                pxPos(3) + 2*borderAdjust(obj) ...
                                                pxPos(4) + 2*borderAdjust(obj)]);
        end
        function resize(obj, ~, ~)
            % After the viewport position is set, we need to move the
            % scrollbars back to the edges.
            drawnow;
            obj.setSBPosition('both');
            setBorderPosition(obj);
        end
        function scroll(obj, src, ~)
            % Again, normalized coordinates will need to be set manually
            % because it returns those relative to the figure window.
            if strcmp(get(obj.hViewportPanel, 'Units'), 'normalized')
                mPos = [0 0 1 1];
            else
                mPos = get(obj.hViewportPanel, 'Position');
            end

            sPos = get(obj.hScrollingPanel, 'Position');

            % Setup the variables for the appropriate scrollbar.
            if src == obj.hVScrollBar
                hScrollBar = obj.hVScrollBar;
                length_i = 4;
            else
                hScrollBar = obj.hHScrollBar;
                length_i = 3;
            end

            % Get the position of the slider, calcuate the ratio of
            % viewport to ScrollArea and set the new position of the
            % ScrollArea.
            value = get(hScrollBar, 'Value');
            pos = (sPos(length_i) - mPos(length_i))*(-value);
            if strcmp(get(obj.hScrollingPanel, 'Units'), 'pixels')
                pos = pos + 1;
            end
            if src == obj.hVScrollBar
                sPos = [sPos(1) pos sPos(3) sPos(4)];
            else
                sPos = [pos sPos(2) sPos(3) sPos(4)];
            end
            set(obj.hScrollingPanel, 'Position', sPos);
        end
    end
    methods (Hidden = true)
        function [h v] = getSBPosition(obj)
            h = get(obj.hHScrollBar, 'Position');
            v = get(obj.hVScrollBar, 'Position');
        end
    end
end

function newLine = limitLines(smallLine, largeLine)
% Limit a longer line to the endpoints of the smaller line.  In other
% words, the extents of the smaller line must be within those of the larger
% line.
    sStart = smallLine(1);
    sEnd = smallLine(1) + smallLine(2);

    lStart = largeLine(1);
    lEnd = largeLine(1) + largeLine(2);
    lLength = largeLine(2);

    if lStart < sStart && lEnd < sEnd
        newLine(2) = lLength;
        newLine(1) = sEnd - lLength;
    else if lStart > sStart
            newLine(1) = sStart;
            newLine(2) = lLength;
        else
            newLine = largeLine;
        end
    end
end


function outfilenames = SaveFigLotsOfWays(hfig,basename,formats)

if exist('savefig1','var'),
  savefigfun = @savefig1;
else
  savefigfun = @savefig;
end

if nargin < 3,
  formats = {'pdf','png','svg','fig'};
end
[path,basename] = myfileparts(basename);

outfilenames = {};
if ismember('svg',formats),
  filename = [basename,'.svg'];
  plot2svg(fullfile(path,filename),hfig,'png');
  outfilenames{end+1} = fullfile(path,filename);
end
formats = setdiff(formats,{'svg'});

if ismember('ai',formats),
  filename = [basename,'.ai'];
  saveas(hfig,fullfile(path,filename),'ai');
  outfilenames{end+1} = fullfile(path,filename);
end
formats = setdiff(formats,{'ai'});

if ismember('fig',formats),
  filename = [basename,'.fig'];
  saveas(hfig,fullfile(path,filename),'fig');
  outfilenames{end+1} = fullfile(path,filename);
end
formats = setdiff(formats,{'fig'});

tmpbasename = regexprep(basename,'[^a-zA-Z_0-9]','_');
for i = 1:numel(formats),
  filename = [basename,'.',formats{i}];
  tmpfilename = [tmpbasename,'.',formats{i}];
  try
    savefig_pa(tmpfilename,hfig,formats{i});
    if ~isempty(path),
      movefile(tmpfilename,fullfile(path,filename));
    end
  catch ME,
    warning('Error while creating %s: %s',filename,getReport(ME));
    continue;
  end
  outfilenames{end+1} = fullfile(path,filename);
end


function dt = getMetadata(fileName)
	c = benchmarkPaths(0);
	dt = load(sprintf('%s/metadata/%s.mat', c.benchmarkDataDir, fileName));
end


function T = slreadtext(filename)
%SLREADTEXT Reads in a text file into a cell array
%
% $ Syntax $
%   T = slreadtext(filename)
%
% $ Description $
%   - T = slreadtext(filename) Reads in a text file to a cell array with
%     each cell storing a line string.
%
% $ Remarks $
%   1. No extra operations will be performed on the strings besides deleting
%      the trailing spaces in end of lines.
%
% $ History $
%   Created by Dahua Lin, on 2005-06-02
%

fid = fopen(filename, 'r');
if (fid <= 0)
    error(['Fail to open file ', filename]);
end

T = {};
icount = 0;
strline = fgets(fid);
while ~isequal(strline, -1)
    icount = icount + 1;
    T{icount, 1} = deblank(strline);
    strline = fgets(fid);
end

fclose(fid);


function [success] = writeHDF5Data(inputData,fileSavePath,varargin)
	% Saves input data to a HDF5 file, tries to preserve datatype.
	% Biafra Ahanonu
	% started: 2013.11.01
	%
	% inputs
		% inputData: matrix, [x y frames] preferred.
		% fileSavePath: str, path where HDF5 file should be saved.
	% outputs
		% success = 1 if successful save, 0 if error.
	% options
		% datasetname = HDF5 hierarchy where data should be stored

	[success] = ciapkg.hdf5.writeHDF5Data(inputData,fileSavePath,'passArgs', varargin);
end


function str = printor(strs, quote)

% PRINTOR prints a single or mutiple strings as "x1, x2, x3 or x4". If there is
% only one string, that string is returned without additional formatting.
%
% See also PRINTAND

if nargin<2
  quote = false;
end

if quote
  if numel(strs)>2
    str = sprintf('''%s'', ', strs{1:(end-2)});
    str = sprintf('%s''%s'' or ''%s''', str, strs{end-1}, strs{end});
  elseif numel(strs)==2
    str = sprintf('''%s'' or ''%s''', strs{end-1}, strs{end});
  else
    str = strs{1};
  end
else
  if numel(strs)>1
    str = sprintf('%s, ', strs{1:(end-2)});
    str = sprintf('%s%s or %s', str, strs{end-1}, strs{end});
  else
    str = strs{1};
  end
end


function out = runtests(varargin)
%runtests Run unit tests
%   runtests runs all the test cases that can be found in the current directory
%   and summarizes the results in the Command Window.
%
%   Test cases can be found in the following places in the current directory:
%
%       * An M-file function whose name starts or ends with "test" or
%         "Test" and that returns no output arguments.
%
%       * An M-file function whose name starts or ends with "test" or
%         "Test" and that contains subfunction tests and uses the
%         initTestSuite script to return a TestSuite object.
%
%       * An M-file defining a subclass of TestCase.
%
%   runtests(dirname) runs all the test cases found in the specified directory.
%
%   runtests(packagename) runs all the test cases found in the specified
%   package. (This option requires R2009a or later).
%
%   runtests(mfilename) runs test cases found in the specified function or class
%   name. The function or class needs to be in the current directory or on the
%   MATLAB path.
%
%   runtests('mfilename:testname') runs the specific test case named 'testname'
%   found in the function or class 'name'.
%
%   Multiple directories or file names can be specified by passing multiple
%   names to runtests, as in runtests(name1, name2, ...) or
%   runtests({name1, name2, ...}, ...)
%
%   runtests(..., '-verbose') displays the name and result, result, and time
%   taken for each test case to the Command Window.
%
%   runtests(..., '-logfile', filename) directs the output of runtests to
%   the specified log file instead of to the Command Window.
%
%   out = runtests(...) returns a logical value that is true if all the
%   tests passed.
%
%   Examples
%   --------
%   Find and run all the test cases in the current directory.
%
%       runtests
%
%   Find and run all the test cases in the current directory. Display more
%   detailed information to the Command Window as the test cases are run.
%
%       runtests -verbose
%
%   Save verbose runtests output to a log file.
%
%       runtests -verbose -logfile my_test_log.txt
%
%   Find and run all the test cases contained in the M-file myfunc.
%
%       runtests myfunc
%
%   Find and run all the test cases contained in the TestCase subclass
%   MyTestCase.
%
%       runtests MyTestCase
%
%   Run the test case named 'testFeature' contained in the M-file myfunc.
%
%       runtests myfunc:testFeature
%
%   Run all the tests in a specific directory.
%
%       runtests c:\Work\MyProject\tests
%
%   Run all the tests in two directories.
%
%       runtests c:\Work\MyProject\tests c:\Work\Book\tests

%   Steven L. Eddins
%   Copyright 2009-2010 The MathWorks, Inc.

verbose = false;
logfile = '';
if nargin < 1
    suite = TestSuite.fromPwd();
else
    [name_list, verbose, logfile] = getInputNames(varargin{:});
    if numel(name_list) == 0
        suite = TestSuite.fromPwd();
    elseif numel(name_list) == 1
        suite = TestSuite.fromName(name_list{1});
    else
        suite = TestSuite();
        for k = 1:numel(name_list)
            suite.add(TestSuite.fromName(name_list{k}));
        end
    end
end

if isempty(suite.TestComponents)
    error('xunit:runtests:noTestCasesFound', 'No test cases found.');
end

if isempty(logfile)
    logfile_handle = 1; % File handle corresponding to Command Window
else
    logfile_handle = fopen(logfile, 'w');
    if logfile_handle < 0
        error('xunit:runtests:FileOpenFailed', ...
            'Could not open "%s" for writing.', logfile);
    else
        cleanup = onCleanup(@() fclose(logfile_handle));
    end
end

fprintf(logfile_handle, 'Test suite: %s\n', suite.Name);
if ~strcmp(suite.Name, suite.Location)
    fprintf(logfile_handle, 'Test suite location: %s\n', suite.Location);
end
fprintf(logfile_handle, '%s\n\n', datestr(now));

if verbose
    monitor = VerboseTestRunDisplay(logfile_handle);
else
    monitor = TestRunDisplay(logfile_handle);
end
did_pass = suite.run(monitor);

if nargout > 0
    out = did_pass;
end

function [name_list, verbose, logfile] = getInputNames(varargin)
name_list = {};
verbose = false;
logfile = '';
k = 1;
while k <= numel(varargin)
    arg = varargin{k};
    if iscell(arg)
        name_list = [name_list; arg];
    elseif ~isempty(arg) && (arg(1) == '-')
        if strcmp(arg, '-verbose')
            verbose = true;
        elseif strcmp(arg, '-logfile')
            if k == numel(varargin)
                error('xunit:runtests:MissingLogfile', ...
                    'The option -logfile must be followed by a filename.');
            else
                logfile = varargin{k+1};
                k = k + 1;
            end
        else
            warning('runtests:unrecognizedOption', 'Unrecognized option: %s', arg);
        end
    else
        name_list{end+1} = arg;
    end
    k = k + 1;
end


function [ list_files ] = read_list_of_files( list_file_path )

list_files = {};
fid = fopen(list_file_path);
tline = fgets(fid);
c = 0;
while ischar(tline)
    c = c + 1;
    list_files{c} = tline;
%     fprintf('%s\n',list_files{c})
    tline = fgets(fid);
end
fclose(fid);
end


function N = spm_dicom_metadata(N,hdr)
% Export image metadata as side-car JSON file
% FORMAT N = spm_dicom_metadata(N,hdr)
% N(input)  - nifti object
% hdr       - a single header from spm_dicom_headers
% N(output) - unchanged nifti object (for potential future use)
%
% This function creates JSON-encoded metadata during DICOM to NIfTI
% conversion, including all acquisition parameters, and saves them as a
% JSON side-car file.
%
% See also: spm_dicom_convert
%__________________________________________________________________________
% Copyright (C) 2017-2018 Wellcome Trust Centre for Neuroimaging

% Evelyne Balteau, Cyclotron Research Centre, University of Liege, Belgium
% $Id: spm_dicom_metadata.m 7482 2018-11-12 12:18:08Z guillaume $

%-Provenance and general description of the image (hMRI toolbox)
dicom_convert_version = {sprintf('%s %s', spm_check_version, version),sprintf('spm_dicom_convert.m - %s', spm('Version'))};
metadata.history.procstep = struct('descrip','dicom to nifti import', 'version', {dicom_convert_version}, 'procpar', []);
metadata.history.input(1) = struct('filename','AnonymousFileName', 'history',[]);
if isfield(hdr,'ImageType')
    metadata.history.output = struct('imtype',hdr.ImageType, 'units','a.u.');
else
    metadata.history.output = struct('imtype','Unprocessed MR image', 'units','a.u.');
end

%-Acquisition parameters with complete DICOM header (hMRI toolbox)
hdr = reformat_spm_dicom_header(hdr);
hdr = anonymise_metadata(hdr); % default is basic anonymisation
metadata.acqpar = hdr;

%-Save metadata as side-car JSON file
spm_jsonwrite(spm_file(N.dat.fname,'ext','json'), metadata, struct('indent','\t'));

%==========================================================================
function hdr = reformat_spm_dicom_header(hdr)
% To tidy up and rearrange CSA fields in the header, including formatting
% the ASCII part into a proper MATLAB structure (Note: this is specific to
% Siemens DICOM format but could be extended to other cases where needed)
%
% FORMAT hdr = reformat_spm_dicom_header(hdr)
% where hdr is a dicom header structure as output by spm_dicom_headers.

% list of CSA-like fields:
CSAlist = {'CSAImageHeaderInfo', 'CSASeriesHeaderInfo','CSANonImageHeaderInfoVA','CSAMiscProtocolHeaderInfoVA','CSANonImageHeaderInfoVB','CSAMiscProtocolHeaderInfoVB'};
% NB: might be necessary to add cases 'Private_0029_1110' and
% 'Private_0029_1210' for spectroscopic data (see spm_dicom_essentials.m)

for ccsa = 1:length(CSAlist)
    if isfield(hdr,CSAlist{ccsa})
        tdyhdr = tidy_CSA(hdr.(CSAlist{ccsa}));
        if isfield(tdyhdr,'MrPhoenixProtocol')
            % works for Siemens VB, VD & VE DICOM format:
            tdyhdr.MrPhoenixProtocol = read_ASCII(tdyhdr.MrPhoenixProtocol);
        elseif isfield(tdyhdr,'MrProtocol')
            % works for Siemens VA DICOM format:
            tdyhdr.MrProtocol = read_ASCII(tdyhdr.MrProtocol);
        end
        hdr.(CSAlist{ccsa}) = tdyhdr;
    end
end

%==========================================================================
function hdrout = anonymise_metadata(hdr, opts)
% FORMAT hdrout = anonymise_metadata(hdr, opts)
% hdr is a DICOM header read with spm_dicom_header
% opts are anonymisation options:
%       opts.anonym = 'none': no anonymisation, all patient data kept in
%                       the metadata.
%                     'full': no patient information is kept at all
%                     'basic': patient ID (presumably not containing his
%                       name), age (years at the time of the data
%                       acquisition), sex, size and weight are kept,
%                       patient name, date of birth and DICOM filename
%                       (often containing the patient name) are removed.
%
% !!! IMPORTANT WARNING: EFFECTIVE ANONYMISATION IS NOT GUARANTEED !!!
% The anonymisation implemented here depends on the structure and content
% of the DICOM header and might not be effective in many cases.

if nargin<2
    opts.anonym = 'basic';
end
if ~iscell(hdr)
    hdrout{1} = hdr;
else
    hdrout = hdr;
end

if ~strcmp(opts.anonym,'none')
    for i=1:length(hdrout)
        if isfield(hdrout{i},'PatientName')
            hdrout{i}.PatientName = 'Anonymous';
        end
        if isfield(hdrout{i},'PatientBirthDate')
            t1 = hdrout{i}.PatientBirthDate;
            t2 = hdrout{i}.StudyDate;
            hdrout{i}.PatientAge = round((t2-t1)*10/365.25)/10;
            hdrout{i} = rmfield(hdrout{i},'PatientBirthDate');
        end
        if isfield(hdrout{i},'Filename')
            hdrout{i} = rmfield(hdrout{i},'Filename');
        end

        if strcmp(opts.anonym, 'full')
            try, hdrout{i} = rmfield(hdrout{i},'PatientID'); end
            try, hdrout{i} = rmfield(hdrout{i},'PatientSex'); end
            try, hdrout{i} = rmfield(hdrout{i},'PatientAge'); end
            try, hdrout{i} = rmfield(hdrout{i},'PatientSize'); end
            try, hdrout{i} = rmfield(hdrout{i},'PatientWeight'); end
            try, hdrout{i}.CSASeriesHeaderInfo = rmfield(hdrout{i}.CSASeriesHeaderInfo,'UsedPatientWeight'); end
        end
    end
end

%==========================================================================
function tdyhdr = tidy_CSA(csahdr)
% FORMAT tdyhdr = tidy_CSA(csahdr)
% DESCRIPTION:
% To rearrange CSAImageHeaderInfo, CSASeriesHeaderInfo, ... structures
% and make it a simplified, easier to browse structure (Siemens specific).
% USAGE:
% tdyhdr = tidy_CSA(csahdr)
% where csahdr is a structure, content of the CSA field.

tdyhdr = [];
for ccsa = 1:length(csahdr)
    val = get_numaris4_val(csahdr,csahdr(ccsa).name);
    % if val is empty, let's not waste disk space with empty fields
    if ~isempty(val)
        % for elements having a value representation corresponding to a
        % numerical value (or an array of numerical values), convert char
        % into numbers. Here is a list of VR corresponding to numerical
        % values:
        % - DS (Decimal String)
        % - FL (Floating Point Single)
        % - FD (Floating Point Double)
        % - IS (Integer String)
        % - OD (Other Double String)
        % - OF (Other Float String)
        % - SL (Signed Long)
        % - SS (Signed Short)
        % - UL (Unsigned Long)
        % - US (Unsigned Short)
        switch deblank(csahdr(ccsa).vr)
            case {'DS','FL','FD','IS','OD','OF','SL','SS','UL','US'}
                try
                    tmp = zeros(size(val,1),1);
                    for k = 1:size(val,1)
                        tmp(k) = str2num(val(k,:));
                    end
                    val = tmp;
                catch
                    fprintf('Trouble reading CSA header %s (%s) = %s\n',csahdr(ccsa).name, deblank(csahdr(ccsa).vr), val(1,:));
                    val = [];
                end
            otherwise
                val = deblank(val);
        end
        % make sure no "-" in field name (invalid otherwise)
        csahdr(ccsa).name = strrep(csahdr(ccsa).name,'-','');
        tdyhdr.(csahdr(ccsa).name) = val;
    end
end

%==========================================================================
function val = get_numaris4_val(str,name)
% copied from spm_dicom_convert
name = deblank(name);
val  = {};
for i=1:length(str)
    if strcmp(deblank(str(i).name),name)
        if isfield(str(i),'nitems')
            for j=1:str(i).nitems
                if  str(i).item(j).xx(1)
                    val = [val {str(i).item(j).val}];
                end
            end
            %         else
            %             fprintf(1,'Found empty CSA header with name %s\n',name);
        end
        break;
    end
end
val = strvcat(val{:});

%==========================================================================
function ascout = read_ASCII(ascin)
% FORMAT ascout = read_ASCII(ascin)
%
% DESCRIPTION:
% To parse the content of the ASCII part of the DICOM header (Siemens
% specific) and convert it into a matlab structure. Compatible with VA, VB,
% VD and also reading *.SR files (PhoenixProtocols).
%
% USAGE:
% ascout = read_ASCII(ascin)
% where ascin is the char content of either
% hdr.CSASeriesHeaderInfo.MrPhoenixProtocol or
% hdr.CSASeriesHeaderInfo.MrProtocol according to Siemens software version,
% and asc is a matlab structure containing the information contained in the
% ASCCONV BEGIN - ASCCONV END part of the DICOM header.
%
% NOTE: slightly different formatting from read_ascconv in spm_dicom_convert

% only for debugging purpose
ENABLE_DEBUG = false;

% extract the portion between ### ASCCONV BEGIN ### and ### ASCCONV END ###
ascin = regexprep(ascin,'^.*### ASCCONV BEGIN [^#]*###(.*)### ASCCONV END ###.*$','$1');

% split ascin into lines
% asclines = strsplit(ascin,'\n'); % ok for Matlab 2013 and later versions
% replaced by textscan (with delimiter '\n') for compatibility with
% Matlab 2012 and earlier :/...
tmp = textscan(ascin,'%s','delimiter','\n');
asclines = tmp{1};

% do a first cleaning pass over the data:
% - replace indexes [] by () + increment index +1 (C>Matlab indexing)
% - replace double "" and single " by single '
% - replace hexadecimal values (e.g. "0x01") by decimal value
% - delete lines where a field starts or ends by "_"
% - delete end of lines after "#"
asclines = regexprep(asclines,{'\[([0-9]*)\]','["]+','^([^"]*)0x([0-9a-fA-F]*)',' #.*','^.*\._.*$'},{'($1+1)','''','$1hex2dec(''$2'')','',''});

% initialise variables
ascout = [];
clinenum = 1;

while clinenum<length(asclines)

    if ENABLE_DEBUG, fprintf('Line #%d - [%s]\n', clinenum, asclines{clinenum}); end
    if ~isempty(asclines{clinenum})
        try
            [tlhrh,tmp] = regexp(asclines{clinenum}, '(?:=)+', 'split', 'match'); %#ok<*ASGLU>
            % first process the name of the parameter (hdrnam)
            % split into fields
            [hdrnam,tmp] = regexp(tlhrh{1}, '(?:\.)+', 'split', 'match');
            % check whether any field starts with a number and if so,
            % replace it by "index<number>..."
            isfirstdigit = cellfun(@(x) isstrprop(x(1),'digit'),hdrnam);
            if any(isfirstdigit)
                for cfield=1:length(hdrnam)
                    if isfirstdigit(cfield)
                        hdrnam{cfield} = ['index' hdrnam{cfield}];
                    end
                end
            end
            % concatenate back the fields
            hdrnam = sprintf('.%s', hdrnam{:});
            % now process the value (hdrval)
            hdrval = strtrim(tlhrh{2});
            % and eval the whole line
            eval(sprintf('ascout%s = %s;', hdrnam, hdrval));
            if ENABLE_DEBUG, fprintf('ascout%s = %s;\n', hdrnam, hdrval); end
        catch
            fprintf('AscConv: Error evaluating [ %s; ]\n', asclines{clinenum});
        end
    end
    clinenum = clinenum+1;
end


function varargout = DQ_Table_GUI(varargin)
% DQ_TABLE_GUI MATLAB code for DQ_Table_GUI.fig
%      DQ_TABLE_GUI, by itself, creates a new DQ_TABLE_GUI or raises the existing
%      singleton*.
%
%      H = DQ_TABLE_GUI returns the handle to a new DQ_TABLE_GUI or the handle to
%      the existing singleton*.
%
%      DQ_TABLE_GUI('CALLBACK',hObject,eventData,handles,...) calls the local
%      function named CALLBACK in DQ_TABLE_GUI.M with the given input arguments.
%
%      DQ_TABLE_GUI('Property','Value',...) creates a new DQ_TABLE_GUI or raises the
%      existing singleton*.  Starting from the left, property value pairs are
%      applied to the GUI before DQ_Table_GUI_OpeningFcn gets called.  An
%      unrecognized property name or invalid value makes property application
%      stop.  All inputs are passed to DQ_Table_GUI_OpeningFcn via varargin.
%
%      *See GUI Options on GUIDE's Tools menu.  Choose "GUI allows only one
%      instance to run (singleton)".
%
% See also: GUIDE, GUIDATA, GUIHANDLES

% Edit the above text to modify the response to pushbutton_help DQ_Table_GUI

% Last Modified by GUIDE v2.5 26-Jan-2023 16:54:38

% Begin initialization code - DO NOT EDIT
gui_Singleton = 0;
gui_State = struct('gui_Name',       mfilename, ...
    'gui_Singleton',  gui_Singleton, ...
    'gui_OpeningFcn', @DQ_Table_GUI_OpeningFcn, ...
    'gui_OutputFcn',  @DQ_Table_GUI_OutputFcn, ...
    'gui_LayoutFcn',  [] , ...
    'gui_Callback',   []);
if nargin && ischar(varargin{1})
    gui_State.gui_Callback = str2func(varargin{1});
end

if nargout
    [varargout{1:nargout}] = gui_mainfcn(gui_State, varargin{:});
else
    gui_mainfcn(gui_State, varargin{:});
end
% End initialization code - DO NOT EDIT

% --- Executes just before DQ_Table_GUI is made visible.
function DQ_Table_GUI_OpeningFcn(hObject, eventdata, handles, varargin)
% This function has no output args, see OutputFcn.
% hObject    handle to figure
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    structure with handles and user data (see GUIDATA)
% varargin   command line arguments to DQ_Table_GUI (see VARARGIN)

% Choose default command line output for DQ_Table_GUI
handles.output = [];

% check input ERPset DQ
ERP = varargin{1};
ALLERP = varargin{2};
current_ERP = varargin{3};
guiwin_num = varargin{4};

%GUI positions (for multiple DQ tables)

if guiwin_num ~= 1
    offset_GUI = 25 * randi([1 10],1); %offset by
    % %set(hObject, 'Units', 'normalized');
    figureposition = get(handles.figure1, 'Position');
    set(hObject, 'Position', ...
        [ figureposition(1)-offset_GUI figureposition(2)-offset_GUI  ...
        figureposition(3) figureposition(4)])
end

%check contents

try
    assert(exist('ERP','var')==1)
    assert(isempty(ERP)==0)
    assert(isfield(ERP,'dataquality'))
    assert(strcmp(ERP.dataquality(1).type,'empty')==0)
catch
    beep
    warning('Data Quality not present in current ERPset?')
    delete(hObject);
    return
end

% Update GUI with ERPSET info
n_erp = numel(ALLERP);

if n_erp == 0
    %for the case of showing aSME preavg
    erp_names = ERP.erpname;
    handles.text_ERPSET_title.String = 'Selected EEG Dataset:';
    handles.text_ERPSET_title.FontSize = 12;
    handles.text11.Visible = 0;
    handles.newerpwin.Visible = 0;
    handles.pushbutton_selERP.Visible = 0;
    handles.active_erpset.Style = 'text';
    if numel(erp_names) > 15
        handles.active_erpset.FontSize = 12.0;
        set(handles.active_erpset,'FontUnits', 'normalized');
    end
    %update handles
    guidata(hObject, handles);

else

    for i = 1:n_erp
        erp_names{i} = ALLERP(i).erpname;
    end

end

%ERPSET_title_str = ['ERPSET - ' erp_names(current_ERP)];
handles.active_erpset.String = erp_names;
handles.active_erpset.Value = current_ERP;

%AMS: Multiple ERPset options
handles.newerpwin.String = erp_names;
handles.newerpwin.Value = current_ERP;

n_dq = numel(ERP.dataquality);
for i=1:n_dq
    type_names{i} = ERP.dataquality(i).type;
end

handles.popupmenu_DQ_type.String = type_names;
%handles.popupmenu_DQ_type.Value = n_dq;
%default to aSME (if available) or end of list

where_aSME = strcmpi('aSME',type_names);
where_aSME_corr = strcmpi('aSME (Corrected)',type_names);

if any(where_aSME)
    indx_type = find(where_aSME);

elseif any(where_aSME_corr)
    indx_type = find(where_aSME_corr);
else
    indx_type = n_dq;
end
handles.popupmenu_DQ_type.Value = indx_type;

try
[n_elec, n_tw, n_bin] = size(ERP.dataquality(indx_type).data); %default to aSME (or last metric)
catch
[n_elec, n_tw, n_bin] = size(ERP.dataquality.data); %if only one measure
end

if n_elec == 0
    % if the dq data is of zero size, then the 3rd dim may be mis-sized.
    % So set to default of ERP.nbin instead.
    n_bin = ERP.nbin;
end

n_bin_names = numel(ERP.bindescr);
if n_bin_names == n_bin

    for i=1:n_bin
        bin_names{i} = ['BIN ' num2str(i) ' - ' ERP.bindescr{i}];
    end
else
    % if not every bin has a bin description, leave then off
    for i=1:n_bin
        bin_names{i} = ['BIN ' num2str(i)];
    end
end

handles.popupmenu_bin.String = bin_names;

% handles.popupmenu_DQ_type.Value saves the indx of the selected DQ Measure
selected_DQ_type = handles.popupmenu_DQ_type.Value;
selected_bin = handles.popupmenu_bin.Value;

table_data = ERP.dataquality(selected_DQ_type).data(:,:,selected_bin);

pSEM = 0;
if isempty(table_data) %check if potentially pointwise SEM
    if strcmp(ERP.dataquality(selected_DQ_type).type,'Point-wise SEM') && isempty(ERP.binerror) == 0
        table_data = ERP.binerror(:,:,selected_bin);
        pSEM = 1;

    elseif strcmp(ERP.dataquality(selected_DQ_type).type,'Point-wise SEM (Corrected)') && isempty(ERP.binerror) == 0
        table_data = ERP.binerror(:,:,selected_bin);
        pSEM = 1;

    end

end

handles.dq_table.Data = table_data;
handles.orig_data = table_data;

% electrode labels from ERPset, iff present and number matches
if isfield(ERP.chanlocs,'labels') && numel(ERP.chanlocs) == n_elec
    for i=1:n_elec
        elec_labels{i} = ERP.chanlocs(i).labels;
    end
else

    for i=1:ERP.nchan
        elec_labels{i} = i;
    end
end
handles.dq_table.RowName = elec_labels;
handles.orig_RowName = elec_labels;

% Time-window labels
if isfield(ERP.dataquality(selected_DQ_type),'time_window_labels') && isempty(ERP.dataquality(selected_DQ_type).time_window_labels) == 0  && handles.checkbox_text_labels.Value == 1
    tw_labels = ERP.dataquality(selected_DQ_type).time_window_labels;
elseif isempty(ERP.dataquality(selected_DQ_type).times)
    tw_labels = [];
else
    for i=1:n_tw
        tw_labels{i} = [num2str(ERP.dataquality(selected_DQ_type).times(i,1)) ' : ' num2str(ERP.dataquality(selected_DQ_type).times(i,2))];
    end
end
if n_tw == 0 %no ERP.dataquality.data?

    if pSEM == 1
        tw_labels = [];
    else
        tw_labels = 'No data here. Perhaps it was cleared?';
    end

end
handles.dq_table.ColumnName = tw_labels;

% Set font size of DQ Table
desired_fontsize = erpworkingmemory('fontsizeGUI');
handles.dq_table.FontSize = desired_fontsize;
handles.heatmap_on = 0;

%
% Prepare List of current Channels
%
nchan  = ERP.nchan; % Total number of channels
if ~isfield(ERP.chanlocs,'labels')
        for e=1:nchan
                ERP.chanlocs(e).labels = ['Ch' num2str(e)];
        end
end
listch = {''};
for ch =1:nchan
        listch{ch} = [num2str(ch) ' = ' ERP.chanlocs(ch).labels ];
end
handles.listch     = listch;

chanArray = 1:ERP.nchan; %default
handles.indxlistch = chanArray; %default
handles.orig_indxlistch = chanArray;
set(handles.chwindow, 'String', vect2colon(chanArray, 'Delimiter', 'off'));

% Set outliers text window off
set(handles.stdwindow,'Enable','Off');
set(handles.chwindow,'Enable','Off');
set(handles.chbutton,'Enable','Off');
set(handles.sdcorrection,'Enable','Off');
set(handles.sdcorrection,'Visible','Off');

%set outliers value in handles off initially
handles.outliers_on = 0;

% Update handles structure
handles.ERP = ERP;

if n_erp == 0
    %case of aSME preavg
    handles.ALLERP = ERP;
else
    handles.ALLERP = ALLERP;
end

guidata(hObject, handles);

% UIWAIT makes DQ_Table_GUI wait for user response (see UIRESUME)
% uiwait(handles.figure1);

% --- Outputs from this function are returned to the command line.
function varargout = DQ_Table_GUI_OutputFcn(hObject, eventdata, handles)
% varargout  cell array for returning output args (see VARARGOUT);
% hObject    handle to figure
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    structure with handles and user data (see GUIDATA)

% Get default command line output from handles structure
varargout{1} = [];

% --- Executes during object creation, after setting all properties.
function text_ERPSET_title_CreateFcn(hObject, eventdata, handles)
% hObject    handle to text_ERPSET_title (see GCBO)
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    empty - handles not created until after all CreateFcns called

% --- Executes on selection change in popupmenu_DQ_type.
function popupmenu_DQ_type_Callback(hObject, eventdata, handles)
% hObject    handle to popupmenu_DQ_type (see GCBO)
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    structure with handles and user data (see GUIDATA)

% Hints: contents = cellstr(get(hObject,'String')) returns popupmenu_DQ_type contents as cell array
%        contents{get(hObject,'Value')} returns selected item from popupmenu_DQ_type
% handles.popupmenu_DQ_type.Value saves the indx of the selected DQ Measure
selected_DQ_type = handles.popupmenu_DQ_type.Value;
selected_bin = handles.popupmenu_bin.Value;

% Check data exists, plot
if isempty(handles.ERP.dataquality(selected_DQ_type).data)

    % if pointwise SEM, use ERP.binerror
    if strcmp(handles.ERP.dataquality(selected_DQ_type).type,'Point-wise SEM') && isempty(handles.ERP.binerror) == 0
        table_data = handles.ERP.binerror(:,:,selected_bin);

    elseif strcmp(handles.ERP.dataquality(selected_DQ_type).type,'Point-wise SEM (Corrected)') && isempty(handles.ERP.binerror) == 0
        table_data = handles.ERP.binerror(:,:,selected_bin);
    else

        % Data empty here.
        table_data = nan(1);
        disp('DQ data not found for this DQ type and bin. Perhaps it has been cleared?');
    end

else
    % data is present and correct

    if strcmp(handles.ERP.dataquality(selected_DQ_type).type,'SD Across Trials') && isempty(handles.ERP.binerror) == 0
        table_data = handles.ERP.dataquality(selected_DQ_type).data.SD_bias(:,:,selected_bin); %SD (Divided by N-1)
        set(handles.sdcorrection,'Enable','On');
        set(handles.sdcorrection,'Visible','On');
    else
        table_data = handles.ERP.dataquality(selected_DQ_type).data(:,:,selected_bin);
        set(handles.sdcorrection,'Enable','Off');
        set(handles.sdcorrection,'Visible','Off');
    end

end

handles.dq_table.Data = table_data;
handles.orig_data = table_data;

% Time-window labels
clear tw_labels
ERP = handles.ERP;
n_dq = numel(ERP.dataquality);
if strcmp(handles.ERP.dataquality(selected_DQ_type).type,'SD Across Trials') && isempty(handles.ERP.binerror) == 0
    [n_elec, n_tw, n_bin] = size(ERP.dataquality(selected_DQ_type).data.SD_bias);
else
    [n_elec, n_tw, n_bin] = size(ERP.dataquality(selected_DQ_type).data);
end

if isfield(ERP.dataquality(selected_DQ_type),'time_window_labels') && isempty(ERP.dataquality(selected_DQ_type).time_window_labels) == 0 && handles.checkbox_text_labels.Value == 1
    tw_labels = ERP.dataquality(selected_DQ_type).time_window_labels;
elseif isempty(ERP.dataquality(selected_DQ_type).times)
    tw_labels = [];
elseif n_tw == 0  % some data problem, no tw here
    tw_labels{1} = 'No data here - measure cleared?';
else
    for i=1:n_tw
        tw_labels{i} = [num2str(ERP.dataquality(selected_DQ_type).times(i,1)) ' : ' num2str(ERP.dataquality(selected_DQ_type).times(i,2))];
    end
end
handles.dq_table.ColumnName = tw_labels;

if handles.heatmap_on
    redraw_heatmap(hObject, eventdata, handles);
end

if handles.outliers_on
   clear_heatmap(hObject, eventdata, handles);
   handles.heatmap_on = 0;

   redraw_outliers(hObject, eventdata, handles);
   set(handles.checkbox_outliers,'Value',1);
end

% Update handles structure
guidata(hObject, handles);

% --- Executes during object creation, after setting all properties.
function popupmenu_DQ_type_CreateFcn(hObject, eventdata, handles)
% hObject    handle to popupmenu_DQ_type (see GCBO)
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    empty - handles not created until after all CreateFcns called

% Hint: popupmenu controls usually have a white background on Windows.
%       See ISPC and COMPUTER.
if ispc && isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor'))
    set(hObject,'BackgroundColor','white');
end

% --- Executes on selection change in popupmenu_bin.
function popupmenu_bin_Callback(hObject, eventdata, handles)
% hObject    handle to popupmenu_bin (see GCBO)
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    structure with handles and user data (see GUIDATA)

% Hints: contents = cellstr(get(hObject,'String')) returns popupmenu_bin contents as cell array
%        contents{get(hObject,'Value')} returns selected item from popupmenu_bin
% handles.popupmenu_DQ_type.Value saves the indx of the selected DQ Measure
selected_DQ_type = handles.popupmenu_DQ_type.Value;
selected_bin = handles.popupmenu_bin.Value;

% Check data exists, plot
if isempty(handles.ERP.dataquality(selected_DQ_type).data)

    % if pointwise SEM, use ERP.binerror
    if strcmp(handles.ERP.dataquality(selected_DQ_type).type,'Point-wise SEM') && isempty(handles.ERP.binerror) == 0
        table_data = handles.ERP.binerror(:,:,selected_bin);
    else

        % Data empty here.
        table_data = nan(1);
        disp('DQ data not found for this DQ type and bin. Perhaps it has been cleared?');
    end

else
    % data is present and correct
    table_data = handles.ERP.dataquality(selected_DQ_type).data(:,:,selected_bin);
end

handles.dq_table.Data = table_data;
handles.orig_data = table_data;

%if user switches bin and both heatmap & outliers on,
%use shortcircuit to clear outliers and redraw heatmap only
if handles.heatmap_on %|| handles.outliers_on
    clear_outliers(hObject, eventdata, handles);
    redraw_heatmap(hObject, eventdata, handles);
    %turn outliers off
    handles.outliers = 0;
    set(handles.checkbox_outliers,'Value',0);

end

%if only outliers on, then clear heatmap and redraw outliers
if handles.outliers_on
   clear_heatmap(hObject, eventdata, handles);
   handles.heatmap_on = 0;

   redraw_outliers(hObject, eventdata, handles);
   set(handles.checkbox_outliers,'Value',1);
end

% Update handles structure
guidata(hObject, handles);

% --- Executes during object creation, after setting all properties.
function popupmenu_bin_CreateFcn(hObject, eventdata, handles)
% hObject    handle to popupmenu_bin (see GCBO)
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    empty - handles not created until after all CreateFcns called

% Hint: popupmenu controls usually have a white background on Windows.
%       See ISPC and COMPUTER.
if ispc && isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor'))
    set(hObject,'BackgroundColor','white');
end

% --- Executes on button press in checkbox_text_labels.
function checkbox_text_labels_Callback(hObject, eventdata, handles)
% hObject    handle to checkbox_text_labels (see GCBO)
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    structure with handles and user data (see GUIDATA)

% Hint: get(hObject,'Value') returns toggle state of checkbox_text_labels
selected_DQ_type = handles.popupmenu_DQ_type.Value;
selected_bin = handles.popupmenu_bin.Value;

% if strcmp(handles.ERP.dataquality(selected_DQ_type).type,'SD Across Trials') && isempty(handles.ERP.binerror) == 0
%     table_data = handles.ERP.dataquality(selected_DQ_type).data.SD_bias(:,:,selected_bin);
% else
%     table_data = handles.ERP.dataquality(selected_DQ_type).data(:,:,selected_bin);
%
% end
% handles.dq_table.Data = table_data;
% handles.orig_data = table_data;

% Time-window labels
clear tw_labels
ERP = handles.ERP;
n_dq = numel(ERP.dataquality);
%[n_elec, n_tw, n_bin] = size(ERP.dataquality(selected_DQ_type).data);
if strcmp(handles.ERP.dataquality(selected_DQ_type).type,'SD Across Trials') && isempty(handles.ERP.binerror) == 0
    [n_elec, n_tw, n_bin] = size(ERP.dataquality(selected_DQ_type).data.SD_bias);
else
    [n_elec, n_tw, n_bin] = size(ERP.dataquality(selected_DQ_type).data);
end
if isfield(ERP.dataquality(selected_DQ_type),'time_window_labels') && isempty(ERP.dataquality(selected_DQ_type).time_window_labels) == 0 && handles.checkbox_text_labels.Value == 1
    tw_labels = ERP.dataquality(selected_DQ_type).time_window_labels;
elseif isempty(ERP.dataquality(selected_DQ_type).times)
    tw_labels = [];
elseif n_tw == 0  % some data problem, no tw here
    tw_labels{1} = 'No data here - measure cleared?';
else
    for i=1:n_tw
        tw_labels{i} = [num2str(ERP.dataquality(selected_DQ_type).times(i,1)) ' : ' num2str(ERP.dataquality(selected_DQ_type).times(i,2))];
    end
end
handles.dq_table.ColumnName = tw_labels;

if handles.heatmap_on
    redraw_heatmap(hObject, eventdata, handles);
end

 % Update handles structure
 guidata(hObject, handles);

% --- Executes on button press in pushbutton_xls.
function pushbutton_xls_Callback(hObject, eventdata, handles)
% hObject    handle to pushbutton_xls (see GCBO)
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    structure with handles and user data (see GUIDATA)
empty_filename = [];
selected_DQ_type = handles.popupmenu_DQ_type.Value;
%sd_correction = handles.sdcorrection.Value;
save_data_quality(handles.ERP,empty_filename,'xlsx',selected_DQ_type)

% --- Executes on button press in pushbutton_mat.
function pushbutton_mat_Callback(hObject, eventdata, handles)
% hObject    handle to pushbutton_mat (see GCBO)
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    structure with handles and user data (see GUIDATA)
empty_filename = [];
selected_DQ_type = handles.popupmenu_DQ_type.Value;
save_data_quality(handles.ERP,empty_filename,'mat',selected_DQ_type)

% --- Executes on button press in pushbutton_help.
function pushbutton_help_Callback(hObject, eventdata, handles)
% hObject    handle to pushbutton_help (see GCBO)
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    structure with handles and user data (see GUIDATA)
web https://github.com/lucklab/erplab/wiki/ERPLAB-Data-Quality-Metrics -browser

% --- Executes on button press in pushbutton_done.
function pushbutton_done_Callback(hObject, eventdata, handles)
% hObject    handle to pushbutton_done (see GCBO)
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    structure with handles and user data (see GUIDATA)
%figure1_CloseRequestFcn(hObject,eventdata,handles)
delete(handles.figure1);

% --- Executes when user attempts to close figure1.
function figure1_CloseRequestFcn(hObject, eventdata, handles)
% hObject    handle to figure1 (see GCBO)
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    structure with handles and user data (see GUIDATA)

% Hint: delete(hObject) closes the figure
delete(hObject);

% --- Executes on button press in checkbox_heatmap.
function checkbox_heatmap_Callback(hObject, eventdata, handles)
% hObject    handle to checkbox_heatmap (see GCBO)
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    structure with handles and user data (see GUIDATA)

% Hint: get(hObject,'Value') returns toggle state of checkbox_heatmap
heatmap_on = get(hObject,'Value');

if heatmap_on == 1

    %if heatmap_on, then outliers not possible so clear outliers
    clear_outliers(hObject, eventdata, handles);
    handles.outliers_on = 0;
    set(handles.checkbox_outliers, 'Value', 0);

    redraw_heatmap(hObject, eventdata, handles);
    handles.heatmap_on = 1;
else
    clear_heatmap(hObject, eventdata, handles);
    handles.heatmap_on = 0;

    %if outliers is on, then keep outliers active
    if handles.outliers_on == 1
        redraw_outliers(hOBject, eventdata,handles);
    end

end
% Update handles structure
guidata(hObject, handles);

function redraw_heatmap(hObject, eventdata, handles)

color_map = viridis;
data = handles.dq_table.Data;
try
    data_min = min(data(:));
catch
    disp('Cannot show text-labels while color heatmap is activated')
    return
end
range_val = max(data(:)) - data_min;
range_colormap = size(color_map,1); % as in, 256 shades?
val_increase_per_shade = range_val / range_colormap;

% Use this @ anonymous function to make HTML tag in box in loop below
colergen = @(color,text) ['<html><table border=0 width=400 bgcolor=',color,'><TR><TD>',text,'</TD></TR> </table>'];

for cell = 1:numel(data)
    data_here = data(cell);
    shades_up_here = round((data_here - data_min) / val_increase_per_shade);
    if shades_up_here < 1
        shades_up_here = 1;
    end
    if shades_up_here > range_colormap
        shades_up_here = range_colormap;
    end
    RGB_here = color_map(shades_up_here,:);
    hex_color_here = ['#' dec2hex(round(255*RGB_here(1)),2) dec2hex(round(255*RGB_here(2)),2) dec2hex(round(255*RGB_here(3)),2)];
    data2{cell} = colergen(hex_color_here,num2str(data_here));
end

data2 = reshape(data2,size(data));

handles.dq_table.Data = data2;

function clear_heatmap(hObject, eventdata, handles)
handles.dq_table.Data = handles.orig_data;

% --- Executes when entered data in editable cell(s) in dq_table.
function dq_table_CellEditCallback(hObject, eventdata, handles)
% hObject    handle to dq_table (see GCBO)
% eventdata  structure with the following fields (see MATLAB.UI.CONTROL.TABLE)
%	Indices: row and column indices of the cell(s) edited
%	PreviousData: previous data for the cell(s) edited
%	EditData: string(s) entered by the user
%	NewData: EditData or its converted form set on the Data property. Empty if Data was not changed
%	Error: error string when failed to convert EditData to appropriate value for Data
% handles    structure with handles and user data (see GUIDATA)

% --- Executes on selection change in newerpwin.
function newerpwin_Callback(hObject, eventdata, handles)
% hObject    handle to newerpwin (see GCBO)
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    structure with handles and user data (see GUIDATA)

% Hints: contents = cellstr(get(hObject,'String')) returns newerpwin contents as cell array
%        contents{get(hObject,'Value')} returns selected item from newerpwin

% --- Executes during object creation, after setting all properties.
function newerpwin_CreateFcn(hObject, eventdata, handles)
% hObject    handle to newerpwin (see GCBO)
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    empty - handles not created until after all CreateFcns called

% Hint: popupmenu controls usually have a white background on Windows.
%       See ISPC and COMPUTER.
if ispc && isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor'))
    set(hObject,'BackgroundColor','white');
end

% --- Executes on button press in pushbutton_selERP.
function pushbutton_selERP_Callback(hObject, eventdata, handles)
% hObject    handle to pushbutton_selERP (see GCBO)
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    structure with handles and user data (see GUIDATA)
selected_erpset = handles.newerpwin.Value;
sel_ERP = handles.ALLERP(selected_erpset);
DQ_Table_GUI(sel_ERP, handles.ALLERP, selected_erpset,2);

% --- Executes on button press in checkbox_outliers.
function checkbox_outliers_Callback(hObject, eventdata, handles)
% hObject    handle to checkbox_outliers (see GCBO)
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    structure with handles and user data (see GUIDATA)

% Hint: get(hObject,'Value') returns toggle state of checkbox_outliers

% Hint: get(hObject,'Value') returns toggle state of checkbox_heatmap
outliers_on = get(hObject,'Value'); % this is not the same as handles.outliers_on

if outliers_on == 1
    set(handles.stdwindow,'Enable','On');
    set(handles.chwindow,'Enable','On');
    set(handles.chbutton,'Enable','On');
else
    set(handles.stdwindow,'Enable','Off');
    set(handles.chwindow,'Enable','Off');
    set(handles.chbutton,'Enable','Off');
end

%updated the handles object for outliers to be in sync with hObject
if outliers_on == 1
    handles.outliers_on = 1;
else
    handles.outliers_on = 0;
end

if handles.outliers_on == 1

    %if heatmap is currently on, stop it and turn its states off
    %before applying outliers fxn

    if handles.heatmap_on == 1
        clear_heatmap(hObject, eventdata, handles);
        handles.heatmap_on = 0;
        set(handles.checkbox_heatmap, 'Value', 0);

        %handles.outliers_on = 1;
        redraw_outliers(hObject, eventdata, handles);

    else
        %handles.outliers_on = 1;
        clear_outliers(hObject, eventdata, handles);
        %guidata(hObject, handles);
        redraw_outliers(hObject, eventdata, handles);

    end

else %but if outliers is off, and heatmap is wanted, you can do it

%     if handles.heatmap_on ==1
%
%         clear_outliers(hObject, eventdata, handles);
%         redraw_heatmap(hObject, eventdata, handles);
%         handles.outliers_on = 0;
%
%     else
%         clear_outliers(hObject, eventdata, handles);
%         handles.outliers_on = 0;
%
%     end

    %also, redraw initial data/channels without outliers
    clear_outliers(hObject, eventdata, handles)

end

% %if heatmap is currently on, stop it and turn its states off
% %before applying outliers fxn
% if handles.heatmap_on == 1
%
%     clear_heatmap(hObject, eventdata, handles);
%     handles.heatmap_on = 0;
%     set(handles.checkbox_heatmap, 'Value', 0);
%
%     if outliers_on == 1
%         handles.outliers_on = 1;
%         redraw_outliers(hObject, eventdata, handles);
%     else
%
%         set(handles.stdwindow,'Enable','Off');
%         set(handles.chwindow,'Enable','Off');
%         set(handles.chbutton,'Enable','Off');
%         clear_outliers(hObject, eventdata, handles);
%         handles.outliers_on = 0;
%
%     end
%
% else
%
%      if outliers_on == 1
%         handles.outliers_on = 1;
%         redraw_outliers(hObject, eventdata, handles);
%      else
%         set(handles.stdwindow,'Enable','Off');
%         set(handles.chwindow,'Enable','Off');
%         set(handles.chbutton,'Enable','Off');
%         clear_outliers(hObject, eventdata, handles);
%         handles.outliers_on = 0;
%
%      end
%
% end
% Update handles structure
guidata(hObject, handles);

function redraw_outliers(hObject, eventdata, handles)
handles = guidata(hObject); %get the most updated handles struct
data = handles.dq_table.Data;

if iscell(data)
    %in case of new std from stdwindow
    data = handles.orig_data;
end

%either default(all) or from chbutton
chans_to_use = handles.indxlistch;
data = data(chans_to_use,:);
n_elec = length(chans_to_use);
ERPtouse = handles.ERP.chanlocs;

for i=1:n_elec
    curr_chanInd = chans_to_use(i);
    elec_labels{i} = ERPtouse(curr_chanInd).labels;
end

%save new labels to handles
handles.dq_table.RowName = elec_labels;

%compute outliers as X standard deviations from mean across channels

col_means = mean(data,1);
col_std = std(data,0,1);
chosen_std = str2double((handles.stdwindow.String));

% Use this @ anonymous function to make HTML tag in box in loop below
colergen = @(color,text) ['<html><table border=0 width=400 bgcolor=',color,'><TR><TD>',text,'</TD></TR> </table>'];

for cell = 1:numel(data)

    data_here = data(cell);
    [d_row,d_col] = ind2sub(size(data),cell);

    neg_thresh = col_means(d_col) - (chosen_std*col_std(d_col));
    pos_thresh = col_means(d_col) + (chosen_std*col_std(d_col));

    if data_here < neg_thresh || data_here > pos_thresh
        hex_color_here = ['#' dec2hex(255,2) dec2hex(0,2) dec2hex(0,2)];
        data2{cell} = colergen(hex_color_here,num2str(data_here));
    else
        data2{cell} = num2str(data_here);

    end

end

data2= reshape(data2,size(data));
handles.dq_table.Data = data2;

function clear_outliers(hObject, eventdata, handles)
%handles = guidata(hObject);
handles.dq_table.Data = handles.orig_data;
handles.dq_table.RowName = handles.orig_RowName;
handles.indxlistch = handles.orig_indxlistch;

% Re-Prepare List of current Channels
chanArray = handles.orig_indxlistch; %default
%handles.indxlistch = chanArray; %default
set(handles.chwindow, 'String', vect2colon(chanArray, 'Delimiter', 'off'));
 % Update handles structure
guidata(hObject, handles);

function stdwindow_Callback(hObject, eventdata, handles)
% hObject    handle to stdwindow (see GCBO)
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    structure with handles and user data (see GUIDATA)

newstd = get(hObject,'String');
handles.stdwindow.String= newstd;
redraw_outliers(hObject, eventdata, handles);

guidata(hObject, handles);

% Hints: get(hObject,'String') returns contents of stdwindow as text
%        str2double(get(hObject,'String')) returns contents of stdwindow as a double

% --- Executes during object creation, after setting all properties.
function stdwindow_CreateFcn(hObject, eventdata, handles)
% hObject    handle to stdwindow (see GCBO)
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    empty - handles not created until after all CreateFcns called

% Hint: edit controls usually have a white background on Windows.
%       See ISPC and COMPUTER.
if ispc && isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor'))
    set(hObject,'BackgroundColor','white');
end

function chwindow_Callback(hObject, eventdata, handles)
% hObject    handle to chwindow (see GCBO)
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    structure with handles and user data (see GUIDATA)

% Hints: get(hObject,'String') returns contents of chwindow as text
%        str2double(get(hObject,'String')) returns contents of chwindow as a double

ch = get(hObject,'string');
nch = length(handles.listch);

ch_val = eval(ch);

if ~isempty(ch)

%     testch = regexpi(ch,':') ;
%
%     if isempty(testch)
%
%         try
%             ch_val = eval(ch);
%             handles.listch(ch_val); %attempt to index
%
%         catch
%             msgboxText =  'Invalid channel input: please enter channels as integers, with a space between each number, or ch1:chN array syntax';
%             title = 'ERPLAB: channel GUI input';
%             errorfound(msgboxText, title);
%
%         end
%     else
%         ch_val = eval(ch);
%
%     end
%

    tf = checkchannels(ch_val, nch, 1);

    if tf
        return
    end

%     if length(ch_val) > length(handles.orig_indxlistch)
%         msgboxText =  'Exceeded Number of Available Channels';
%         title = 'ERPLAB: channel GUI input';
%         errorfound(msgboxText, title);
%         return
%     end
%
    set(handles.chwindow, 'String', vect2colon(ch_val, 'Delimiter', 'off'));
    handles.indxlistch = ch_val;
    % Update handles structure
    guidata(hObject, handles);
    redraw_outliers(hObject, eventdata, handles);
    guidata(hObject, handles);

else
    msgboxText =  'Not valid channel';
    title = 'ERPLAB: channel GUI input';
    errorfound(msgboxText, title);
    return
end

% --- Executes during object creation, after setting all properties.
function chwindow_CreateFcn(hObject, eventdata, handles)
% hObject    handle to chwindow (see GCBO)
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    empty - handles not created until after all CreateFcns called

% Hint: edit controls usually have a white background on Windows.
%       See ISPC and COMPUTER.
if ispc && isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor'))
    set(hObject,'BackgroundColor','white');
end

% --- Executes on button press in chbutton.
function chbutton_Callback(hObject, eventdata, handles)
% hObject    handle to chbutton (see GCBO)
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    structure with handles and user data (see GUIDATA)
listch = handles.listch; %true channels list as labels
indxlistch = handles.indxlistch; %true array of channel indexs
indxlistch = indxlistch(indxlistch<=length(listch)); % true array of channel index if less than expected labels
titlename = 'Select Channel(s)';
if get(hObject, 'Value')
        if ~isempty(listch)
                ch = browsechanbinGUI(listch, indxlistch, titlename);
                if ~isempty(ch)
                        set(handles.chwindow, 'String', vect2colon(ch, 'Delimiter', 'off'));
                        handles.indxlistch = ch;
                        % Update handles structure
                        guidata(hObject, handles);
                        redraw_outliers(hObject, eventdata, handles);
                        guidata(hObject, handles);

                else
                        disp('User selected Cancel')
                        return
                end
        else
                msgboxText =  'No channel information was found';
                title = 'ERPLAB: ploterp GUI input';
                errorfound(msgboxText, title);
                return
        end
end

% --- Executes during object creation, after setting all properties.
function pushbutton_selERP_CreateFcn(hObject, eventdata, handles)
% hObject    handle to pushbutton_selERP (see GCBO)
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    empty - handles not created until after all CreateFcns called

% --- Executes on selection change in active_erpset.
function active_erpset_Callback(hObject, eventdata, handles)
% hObject    handle to active_erpset (see GCBO)
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    structure with handles and user data (see GUIDATA)

newactive = get(hObject,'Value');
ERPs = handles.ALLERP;
ERP = ERPs(newactive);

n_dq = numel(ERP.dataquality);
for i=1:n_dq
    type_names{i} = ERP.dataquality(i).type;
end

handles.popupmenu_DQ_type.String = type_names;
handles.popupmenu_DQ_type.Value = n_dq;

[n_elec, n_tw, n_bin] = size(ERP.dataquality(n_dq).data);

if n_elec == 0
    % if the dq data is of zero size, then the 3rd dim may be mis-sized.
    % So set to default of ERP.nbin instead.
    n_bin = ERP.nbin;
end

n_bin_names = numel(ERP.bindescr);
if n_bin_names == n_bin

    for i=1:n_bin
        bin_names{i} = ['BIN ' num2str(i) ' - ' ERP.bindescr{i}];
    end
else
    % if not every bin has a bin description, leave then off
    for i=1:n_bin
        bin_names{i} = ['BIN ' num2str(i)];
    end
end

handles.popupmenu_bin.String = bin_names;

% handles.popupmenu_DQ_type.Value saves the indx of the selected DQ Measure
selected_DQ_type = handles.popupmenu_DQ_type.Value;
selected_bin = handles.popupmenu_bin.Value;

table_data = ERP.dataquality(selected_DQ_type).data(:,:,selected_bin);
handles.dq_table.Data = table_data;
handles.orig_data = table_data;

% electrode labels from ERPset, iff present and number matches
if isfield(ERP.chanlocs,'labels') && numel(ERP.chanlocs) == n_elec
    for i=1:n_elec
        elec_labels{i} = ERP.chanlocs(i).labels;
    end
else

    for i=1:ERP.nchan
        elec_labels{i} = i;
    end
end
handles.dq_table.RowName = elec_labels;

% Time-window labels
if isfield(ERP.dataquality(selected_DQ_type),'time_window_labels') && isempty(ERP.dataquality(selected_DQ_type).time_window_labels) == 0  && handles.checkbox_text_labels.Value == 1
    tw_labels = ERP.dataquality(selected_DQ_type).time_window_labels;
elseif isempty(ERP.dataquality(selected_DQ_type).times)
    tw_labels = [];
else
    for i=1:n_tw
        tw_labels{i} = [num2str(ERP.dataquality(selected_DQ_type).times(i,1)) ' : ' num2str(ERP.dataquality(selected_DQ_type).times(i,2))];
    end
end
if n_tw == 0
    tw_labels = 'No data here. Perhaps it was cleared?';
end
handles.dq_table.ColumnName = tw_labels;

% Set font size of DQ Table
desired_fontsize = erpworkingmemory('fontsizeGUI');
handles.dq_table.FontSize = desired_fontsize;
handles.heatmap_on = 0;

%
% Prepare List of current Channels
%
nchan  = ERP.nchan; % Total number of channels
if ~isfield(ERP.chanlocs,'labels')
        for e=1:nchan
                ERP.chanlocs(e).labels = ['Ch' num2str(e)];
        end
end
listch = {''};
for ch =1:nchan
        listch{ch} = [num2str(ch) ' = ' ERP.chanlocs(ch).labels ];
end
handles.listch     = listch;

chanArray = 1:ERP.nchan; %default
handles.indxlistch = chanArray; %default
set(handles.chwindow, 'String', vect2colon(chanArray, 'Delimiter', 'off'));

% Set outliers text window off
set(handles.stdwindow,'Enable','Off');
set(handles.chwindow,'Enable','Off');
set(handles.chbutton,'Enable','Off');

% Update handles structure
handles.ERP = ERP;
handles.ALLERP = ERPs;

%clear all perviously set options
set(handles.checkbox_outliers,'Value',0);
set(handles.checkbox_heatmap,'Value',0);
set(handles.checkbox_text_labels,'Value',0);
set(handles.stdwindow,'Enable','Off');
set(handles.chwindow,'Enable','Off');
set(handles.chbutton,'Enable','Off');

%check to see if outliers is on prior to switching active erpset?
%checkbox_outliers_Callback(hObject, eventdata, handles)

% outliers_on = get(handles.checkbox_outliers,'Value');
%
% if outliers_on == 1
%     handles.outliers_on = 1;
%     set(handles.stdwindow,'Enable','On');
%     set(handles.chwindow,'Enable','On');
%     set(handles.chbutton,'Enable','On');
%     redraw_outliers(hObject, eventdata, handles);
%
% else
%     handles.heatmap_on = 0;
%     set(handles.stdwindow,'Enable','Off');
%     set(handles.chwindow,'Enable','Off');
%     set(handles.chbutton,'Enable','Off');
%     clear_outliers(hObject, eventdata, handles);
%
%
% end

guidata(hObject, handles);

% Hints: contents = cellstr(get(hObject,'String')) returns active_erpset contents as cell array
%        contents{get(hObject,'Value')} returns selected item from active_erpset

% --- Executes during object creation, after setting all properties.
function active_erpset_CreateFcn(hObject, eventdata, handles)
% hObject    handle to active_erpset (see GCBO)
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    empty - handles not created until after all CreateFcns called

% Hint: popupmenu controls usually have a white background on Windows.
%       See ISPC and COMPUTER.
if ispc && isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor'))
    set(hObject,'BackgroundColor','white');
end

%--------------------------------------------------------------------------
function tf = checkchannels(chx, nchan, showmsg)

if nargin<3
        showmsg = 1;
end
tf = 0; % no problem by default

if ~mod(chx, 1) == 0
    if showmsg
        msgboxText =  'Invalid channel indexing.';
        title = 'ERPLAB: basicfilterGUI() error:';
        errorfound(msgboxText, title);
    end
    tf = 1; %

end

if isempty(chx)
        if showmsg
                msgboxText =  'Invalid channel indexing.';
                title = 'ERPLAB: basicfilterGUI() error:';
                errorfound(msgboxText, title);
        end
        tf = 1; %
        return
end
if ~isempty(find(chx>nchan))
        if showmsg
                msgboxText =  ['You only have %g channels,\n'...
                        'so you cannot specify indices greater than this.'];
                title = 'ERPLAB: basicfilterGUI() error:';
                errorfound(sprintf(msgboxText, nchan), title);
        end
        tf = 1; %
        return
end
if ~isempty(find(chx<1))
        if showmsg
                msgboxText =  'You cannot use zero or a negative number as a channel indexing';
                title = 'ERPLAB: basicfilterGUI() error:';
                errorfound(msgboxText, title);
        end
        tf = 1; %
        return
end
if length(chx)>length(unique_bc2(chx))
        if showmsg
                msgboxText =  ['Repeated channels are not allowed.\n'...
                        'Therefore, ERPLAB will get rid of them.'];
                title = 'ERPLAB: basicfilterGUI() error:';
                errorfound(sprintf(msgboxText), title, [1 1 0], [0 0 0], 0)
        end
        tf = 0; %
        return
end

% --- Executes on button press in sdcorrection.
function sdcorrection_Callback(hObject, eventdata, handles)
% hObject    handle to sdcorrection (see GCBO)
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    structure with handles and user data (see GUIDATA)
selected_DQ_type = handles.popupmenu_DQ_type.Value;
selected_bin = handles.popupmenu_bin.Value;

if handles.sdcorrection.Value == 1
    table_data = handles.ERP.dataquality(selected_DQ_type).data.SD_unbias(:,:,selected_bin); %SD (Gurland & Tripathi, 1971)
else
    table_data = handles.ERP.dataquality(selected_DQ_type).data.SD_bias(:,:,selected_bin); %SD (N-1)
end
handles.dq_table.Data = table_data;
handles.orig_data = table_data;

%update handles struct
guidata(hObject, handles);

% Hint: get(hObject,'Value') returns toggle state of sdcorrection


function b = renamefields(a, old, new)

% RENAMEFIELDS renames a selection of the fields in a structure
%
% Use as
%   b = renamefields(a, old, new)
% which renames the fields with the old name to the new name. Fields that
% are specified but not present will be silently ignored.
%
% See also COPYFIELDS, KEEPFIELDS, REMOVEFIELDS

% Copyright (C) 2014, Robert Oostenveld
%
% This file is part of FieldTrip, see http://www.fieldtriptoolbox.org
% for the documentation and details.
%
%    FieldTrip is free software: you can redistribute it and/or modify
%    it under the terms of the GNU General Public License as published by
%    the Free Software Foundation, either version 3 of the License, or
%    (at your option) any later version.
%
%    FieldTrip is distributed in the hope that it will be useful,
%    but WITHOUT ANY WARRANTY; without even the implied warranty of
%    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
%    GNU General Public License for more details.
%
%    You should have received a copy of the GNU General Public License
%    along with FieldTrip. If not, see <http://www.gnu.org/licenses/>.
%
% $Id$

if isempty(a)
  % this prevents problems if a is an empty double, i.e. []
  return
end

% these should be cell-arrays
if ischar(old)
  old = {old};
end
if ischar(new)
  new = {new};
end

if length(old)~=length(new)
  ft_error('the number of field names does not match between old and new');
end

% keep the fields that were not mentioned
b = keepfields(a, setdiff(fieldnames(a), old));
% copy the fields over with their new name
for i=1:length(old)
  if isfield(a, old{i})
    for j=1:numel(b)
      b(j).(new{i}) = a(j).(old{i});
    end
  end
end