function s = buildRSSSstructdialog( s, parent, bbox, path, initvals )
    if nargin < 4
        path = [];
    end
    if nargin < 5
        initvals = struct();
    end
    % Callbacks:
    % figure:
    %   CreateFcn, CloseRequestFcn, KeyPressFcn, KeyReleaseFcn, ResizeFcn,
    %   WindowButtonDownFcn	WindowButtonMotionFcn	WindowButtonUpFcn
    %   WindowKeyPressFcn	WindowKeyReleaseFcn	WindowScrollWheelFcn
    % All uicontrols:
    %   Callback, ButtonDownFcn, CreateFcn, DeleteFcn, KeyPressFcn
    switch s.type
        case 'figure'
            s.handle = figure( 'Visible', 'off', ...
                               'Resize', s.attribs.resize, ...
                               'MenuBar', 'none', ...
                               'Position', [0 0 1 1], ...
                               'Units', 'pixels', ...
                               'Tag', s.attribs.tag, ...
                               'Name', s.attribs.string, ...
                               'IntegerHandle', 'off', ...
                               'HandleVisibility', s.attribs.handlevisibility, ...
                               'NumberTitle', 'off', ...
                               'CreateFcn', s.attribs.createfcn, ...
                               'KeyPressFcn', 'checkTerminationKey(gcbo)', ...
                               'CloseRequestFcn', 'closeDialog(gcbo)' );
            s.attribs.interiorsize = handleInteriorSize( s.handle );
            s = buildRSSSstructdialoggroup( s, s.handle, ...
                s.attribs.interiorsize + s.attribs.outermargin([1 2 1 2]).*[1 1 -2 -2], ...
                s.attribs.innermargin, ...
                s.attribs.rows==1, ...
                [ path, 1 ], ...
                initvals );
            for i=1:length(s.menus)
                 s.menus{i} = buildRSSSstructdialog( ...
                    s.menus{i}, s.handle, [], [], initvals );
            end
            guidata( s.handle, collectRSSShandles( s ) );
        case 'panel'
            s.handle = uipanel( 'Parent', parent, 'Units', 'pixels', ...
                'Tag', s.attribs.tag, ...
                'Title', s.attribs.string, ...
                'BorderType', s.attribs.bordertype, ...
                'FontWeight', s.attribs.fontweight );
            s.attribs.interiorsize = handleInteriorSize( s.handle );
            s = buildRSSSstructdialoggroup( s, s.handle, ...
                s.attribs.interiorsize + s.attribs.outermargin([1 2 1 2]).*[1 1 -2 -2], ...
                s.attribs.innermargin, ...
                s.attribs.rows==1, ...
                [ path, 1 ], ...
                initvals );
        case 'radiogroup'
            s.handle = uibuttongroup( 'Parent', parent, 'Units', 'pixels', ...
                'Tag', s.attribs.tag, ...
                'Title', s.attribs.teststring, ...
                'BorderType', s.attribs.bordertype, ...
                'FontWeight', s.attribs.fontweight, ...
                'SelectionChangeFcn', s.attribs.SelectionChangeFcn );
            setNaturalHandleSize( s.handle, s.attribs.fontsize );
            set( s.handle, 'Title', s.attribs.string );
            s.attribs.interiorsize = handleInteriorSize( s.handle );
            s = buildRSSSstructdialoggroup( s, s.handle, ...
                s.attribs.interiorsize + s.attribs.outermargin([1 2 1 2]).*[1 1 -2 -2], ...
                s.attribs.innermargin, ...
                s.attribs.rows==1, ...
                [ path, 1 ], ...
                initvals );
        case 'group'
            s.handle = [];
            s = buildRSSSstructdialoggroup( s, parent, ...
                bbox + s.attribs.outermargin([1 2 1 2]).*[1 1 -2 -2], ...
                s.attribs.innermargin, ...
                s.attribs.rows==1, ...
                [ path, 1 ], ...
                initvals );
        case { 'popupmenu', 'listbox' }
            if strcmp( s.type, 'listbox' )
                if s.attribs.multiline
                    maxval = 2;
                else
                    maxval = 1;
                end
                lines = s.attribs.lines;
            else
                maxval = 1;
                lines = 1;
            end
            s.handle = uicontrol( 'Parent', parent, 'Units', 'pixels', 'Style', s.type, ...
                'Tag', s.attribs.tag, ...
                'Max', maxval, ...
                'String', s.attribs.teststring, ...
                'FontWeight', s.attribs.fontweight, ...
                'Callback', s.attribs.callback );
            setNaturalHandleSize( s.handle, s.attribs.fontsize, lines );
            set( s.handle, 'String', s.attribs.strings );
        case 'slider'
            if strcmp( s.attribs.direction, 'horiz' )
                s.handle = uicontrol( 'Parent', parent, 'Units', 'pixels', 'Style', 'slider', ...
                    'Min', s.attribs.min, ...
                    'Max', s.attribs.max, ...
                    'SliderStep', [s.attribs.minorstep, s.attribs.majorstep], ...
                    'Tag', s.attribs.tag, ...
                    'Callback', s.attribs.callback );
            else
                s.handle = uicontrol( 'Parent', parent, 'Units', 'pixels', 'Style', 'slider', ...
                    'Min', s.attribs.min, ...
                    'Max', s.attribs.max, ...
                    'SliderStep', [s.attribs.minorstep, s.attribs.majorstep], ...
                    'Tag', s.attribs.tag, ...
                    'Callback', s.attribs.callback, ...
                    'Position', [20 20 20 80] );
            end
            setNaturalHandleSize( s.handle, s.attribs.fontsize );
        case 'text'
            s.handle = uicontrol( 'Parent', parent, 'Units', 'pixels', 'Style', 'text', ...
                'Tag', s.attribs.tag, ...
                'String', s.attribs.teststring, ...
                'FontWeight', s.attribs.fontweight, ...
                'HorizontalAlignment', s.attribs.haligncontent, ...
                'Callback', s.attribs.callback );
            setNaturalHandleSize( s.handle, s.attribs.fontsize, s.attribs.lines );
            set( s.handle, 'String', s.attribs.string );
        case 'edit'
            if s.attribs.multiline
                maxval = 2;
            else
                maxval = 1;
            end
            s.handle = uicontrol( 'Parent', parent, 'Units', 'pixels', 'Style', 'edit', ...
                'Tag', s.attribs.tag, ...
                'String', s.attribs.teststring, ...
                'FontWeight', s.attribs.fontweight, ...
                'BackgroundColor', [1 1 1], ...
                'ForegroundColor', [0 0 0], ...
                'Max', maxval, ...
                'HorizontalAlignment', s.attribs.haligncontent, ...
                'Callback', s.attribs.callback, ...
                'CreateFcn', @edittextCreateFcn );
            setNaturalHandleSize( s.handle, s.attribs.fontsize, s.attribs.lines );
            set( s.handle, 'String', s.attribs.string );
        case 'checkbox'
            s.handle = uicontrol( 'Parent', parent, 'Units', 'pixels', 'Style', 'checkbox', ...
                'Tag', s.attribs.tag, ...
                'String', s.attribs.teststring, ...
                'FontWeight', s.attribs.fontweight, ...
                'Callback', s.attribs.callback, ...
                'Value', s.attribs.value );
            setNaturalHandleSize( s.handle, s.attribs.fontsize );
            set( s.handle, 'String', s.attribs.string );
        case 'radiobutton'
            s.handle = uicontrol( 'Parent', parent, 'Units', 'pixels', 'Style', 'radiobutton', ...
                'Tag', s.attribs.tag, ...
                'String', s.attribs.teststring, ...
                'FontWeight', s.attribs.fontweight, ...
                'Value', s.attribs.value );
            setNaturalHandleSize( s.handle, s.attribs.fontsize );
            set( s.handle, 'String', s.attribs.string );
        case 'pushbutton'
            s.handle = uicontrol( 'Parent', parent, 'Units', 'pixels', 'Style', 'pushbutton', ...
                'Tag', s.attribs.tag, ...
                'String', s.attribs.teststring, ...
                'Callback', s.attribs.callback, ...
                'FontWeight', s.attribs.fontweight );
            setNaturalHandleSize( s.handle, s.attribs.fontsize );
            set( s.handle, 'String', s.attribs.string );
        case 'togglebutton'
            s.handle = uicontrol( 'Parent', parent, 'Units', 'pixels', 'Style', 'togglebutton', ...
                'Tag', s.attribs.tag, ...
                'String', s.attribs.teststring, ...
                'Callback', s.attribs.callback, ...
                'FontWeight', s.attribs.fontweight );
            setNaturalHandleSize( s.handle, s.attribs.fontsize );
            set( s.handle, 'String', s.attribs.string );
        case 'menu'
            s.handle = uimenu( 'Parent', parent, ...
                'Tag', s.attribs.tag, ...
                'Label', s.attribs.string, ...
                'Callback', s.attribs.callback );
            for i=1:length(s.children)
                s.children{i} = buildRSSSstructdialog( ...
                    s.children{i}, s.handle, [], [], initvals );
            end
        otherwise
            fprintf( 1, 'Object type "%s" not implemented.\n', s.type );
    end
    if isfield( s.attribs, 'singlechild' ) && s.attribs.singlechild
        for i=1:length(s.children)
            if i ~= s.attribs.selectedchild
                h = s.children{i}.handle;
                if ishandle( h )
                    set( h, 'Visible', 'off' );
                end
            end
        end
    end
end

function h = collectRSSShandles( s, h )
    if nargin < 2
        h = struct();
    end
    if ishandle( s.handle )
        tag = get( s.handle, 'Tag' );
        if ~isempty(tag)
            h.(tag) = s.handle;
        end
    end
    for i=1:length( s.children )
        h = collectRSSShandles( s.children{i}, h );
    end
end

function setNaturalHandleSize( h, fontsize, lines )
    if fontsize > 0
        try
            set( h, 'FontSize', fontsize );
        catch
        end
    else
        try
            fontsize = get( h, 'FontSize' );
        catch
            fontsize = 10;
        end
    end
    if nargin < 3
        lines = 1;
    end
    pos = get( h, 'Position' );
    try
        ext = get( h, 'Extent' );
    catch
        return;
    end
    t = get(h,'Type');
    if strcmp( t, 'uicontrol' )
        t = get(h,'Style');
    end
    switch t
        case 'checkbox'
            pos([3 4]) = [ ext(3)+20, ext(4) ];
        case 'radiobutton'
            pos([3 4]) = [ ext(3)+20, ext(4) ];
        case { 'pushbutton', 'togglebutton' }
            pos([3 4]) = [ ext(3)+6, max( ext(4)+3, 24 ) ];
        case 'listbox'
            pos([3 4]) = [ ext(3)+65, ext(4)+3 ];
        case 'popupmenu'
            pos([3 4]) = [ ext(3)+3*(ext(4)+3)+2*fontsize, ext(4)+3 ];
        case 'edit'
            pos([3 4]) = [ ext(3)+6, ext(4)+6 ];
        case 'text'
            pos([3 4]) = ext([3 4]);
        case 'slider'
            if pos(3) > pos(4)
                pos([3 4]) = max( pos([3 4]), [80 20] );
            else
                pos([3 4]) = max( pos([3 4]), [20 80] );
            end
        otherwise
            fprintf( 1, 'setNaturalHandleSize: uncaught case ''%s''.\n', t );
    end
    if lines > 1
        lineheight = min( fontsize*1.5, pos(4) );
        pos(4) = lineheight*lines;
    end
    set( h, 'Position', pos );
end

function bbox1 = positionWithinBbox( sz, bbox, valign, halign )
    bbox1([1 3]) = positionWithinInterval( sz(1), bbox([1 3]), halign );
    bbox1([2 4]) = positionWithinInterval( sz(2), bbox([2 4]), valign );
end

function int2 = positionWithinInterval( len, interval, align )
    switch align
        case { 'top', 'right' }
            int2 = [ interval(1) + interval(2) - len, len ];
        case { 'bottom', 'left' }
            int2 = [ interval(1), len ];
        case { 'centre', 'center' }
            int2 = [ interval(1) + (interval(2)-len)/2, len ];
        otherwise
            int2 = interval;
    end
end

function s = buildRSSSstructdialoggroup( s, parent, bbox, margin, horizontal, path, initvals )
    numitems = length(s.children);
    if length(margin)==1
        margin = [margin margin];
    end
    if horizontal
        itemsize = [ (bbox(3)+margin(1))/numitems - margin(1), bbox(4) ];
        itemstep = [ itemsize(1)+margin(1), 0 ];
        childbbox = [bbox([1 2]) itemsize];
    else
        itemsize = [ bbox(3), (bbox(4)+margin(2))/numitems - margin(2) ];
        itemstep = [ 0, -itemsize(2)-margin(2) ];
        childbbox = [bbox([1 2]) - (numitems-1)*itemstep, itemsize];
    end
    
    for i=1:numitems
        s.children{i} = buildRSSSstructdialog( s.children{i}, parent, childbbox, [path i], initvals );
        childbbox = childbbox + [itemstep, 0, 0];
    end
end

