classdef uiTable < handle
%

%   Copyright 2022 The MathWorks, Inc.

    properties (Constant,Access=private)
        % Constant values used in the layout
        IconWidth = 16;
        TextRowHeight = 22;
        Pad = 4;
    end

    properties
        Grid          matlab.ui.container.GridLayout
        Fields        matlab.ui.control.EditField
        Buttons       matlab.ui.control.Image
        EditCallback
        Placeholders  string
    end

    properties(Dependent)
        Data
    end

    methods
        function hObj = uiTable(parent,nvp)
            arguments
                parent
                nvp.Placeholders
                nvp.EditCallback = @() 1; % dummy function
            end
            hObj.Placeholders = string(nvp.Placeholders);
            hObj.EditCallback = nvp.EditCallback;
            ncols = numel(hObj.Placeholders);
            hObj.Grid = uigridlayout(parent,...
                ColumnWidth = [repelem({'fit'},1,ncols),{hObj.IconWidth+2*hObj.Pad}],...
                RowHeight = repmat({hObj.TextRowHeight},1,5),...
                Padding = [0 0 0 0],...
                ColumnSpacing = 0,...
                RowSpacing = 0);
            hObj.Fields = matlab.ui.control.EditField.empty(0,ncols);
            hObj.Buttons = matlab.ui.control.Image.empty;
            hObj.addRow;
        end

        function s = get.Data(hObj)
            s = string({hObj.Fields.Value});
            s = reshape(s,size(hObj.Fields));
        end

        function set.Data(hObj,value)
            ncols = size(hObj.Fields,2);
            value = string(value);
            if isempty(value)
                delete(hObj.Fields);
                delete(hObj.Buttons);

                hObj.Fields = matlab.ui.control.EditField.empty(0,ncols);
                hObj.Buttons = matlab.ui.control.Image.empty;
                hObj.addRow;
                return;
            elseif size(value,2) ~= ncols
                return; % this function is internal only, or we'd throw an error
            end

            while size(hObj.Fields,1) > size(value,1)
                hObj.deleteRow(1)
            end
            while size(hObj.Fields,1) < size(value,1)
                hObj.addRow
            end
            for k=1:numel(hObj.Fields)
                hObj.Fields(k).Value = value(k);
            end
        end
    end

    methods(Hidden)
        function delete(hObj)
            delete(hObj.Grid);
        end

        function deleteRow(hObj,row)
            delete(hObj.Buttons(row));
            delete(hObj.Fields(row,:));
            hObj.Buttons(row) = [];
            hObj.Fields(row,:) = [];
            for r = row:numel(hObj.Buttons)
                for c = 1:size(hObj.Fields,2)
                    hObj.Fields(r,c).Layout.Row = r;
                end
                hObj.Buttons(r).Layout.Row = r;
                hObj.Buttons(r).ImageClickedFcn = @(~,~) hObj.deleteRow(r);
            end
            if isempty(hObj.Buttons)
                hObj.addRow;
            end
            hObj.Buttons(end).ImageClickedFcn = @(~,~) hObj.addRow;
            matlab.ui.control.internal.specifyIconID(hObj.Buttons(end),...
                "plusUI",16,16);
            hObj.Grid.RowHeight = repmat({hObj.TextRowHeight},1,size(hObj.Fields,1));

            hObj.fieldsEdited();
        end

        function addRow(hObj,opts)
            arguments
                hObj
                opts.DoFocus = false;
            end
            row = size(hObj.Fields,1)+1;
            hObj.Fields(row,:) = arrayfun( ...
                @(s) uieditfield(hObj.Grid,Placeholder=s, ...
                    ValueChangedFcn=@(~,~) hObj.fieldsEdited), ...
                hObj.Placeholders);

            % Remove and recreate all the buttons, to clear any event backlog.
            % Else, we may get two events both resulting in addRow or
            % resulting in addRow followed by deleteRow; e.g., the edit event
            % of a text field in the last row and the + button click next to it.
            arrayfun(@delete,hObj.Buttons);
            hObj.Buttons = arrayfun(@(k) uiimage(hObj.Grid,ScaleMethod="none", ...
                ImageClickedFcn=@(~,~) hObj.deleteRow(k)), 1:row);
            for k=1:row
                hObj.Buttons(k).Layout.Row = k;
                hObj.Buttons(k).Layout.Column = numel(hObj.Placeholders)+1;
                matlab.ui.control.internal.specifyIconID( ...
                    hObj.Buttons(k),"minusUI",16,16);
            end

            hObj.Buttons(row).ImageClickedFcn=@(~,~) hObj.addRow(DoFocus=true);
            matlab.ui.control.internal.specifyIconID(hObj.Buttons(row),...
                "plusUI",16,16);
            hObj.Grid.RowHeight = repmat({hObj.TextRowHeight},1,row);

            if opts.DoFocus
                focus(hObj.Fields(row,1));
            end
        end

        function fieldsEdited(hObj)
            % add a new row if the last one is completely filled
            if size(hObj.Fields,1) < 1 || ...
                all(cellfun(@(s) strlength(s)>0, {hObj.Fields(end,:).Value}))
                addRow(hObj,DoFocus=true);
            end
            hObj.EditCallback();
        end
    end
end
