Module: duim-layouts-internals Synopsis: DUIM layouts Author: Scott McKay, Andy Armstrong Copyright: Original Code is Copyright (c) 1995-2004 Functional Objects, Inc. All rights reserved. License: Functional Objects Library Public License Version 1.0 Dual-license: GNU Lesser General Public License Warranty: Distributed WITHOUT WARRANTY OF ANY KIND /// Table panes define open abstract class (, , , ) sealed slot table-contents = #f, setter: %contents-setter; sealed constant slot table-rows :: false-or() = #f, init-keyword: rows:; sealed constant slot table-columns :: false-or() = #f, init-keyword: columns:; sealed slot %row-space-requirements :: false-or() = #f; sealed slot %cell-space-requirements :: false-or() = #f; end class ; define method initialize (table :: , #key contents, children, rows, columns) => () // Either the contents should be a sequence of sequences of sheets, // or supply children with a number of rows and columns assert(~(contents & children), "You can't supply both contents and children to a table pane"); next-method(); when (contents | (children & ~empty?(children))) // Compute size of contents array and allocate it if (children) case rows => columns := columns | ceiling/(size(children), rows); columns => rows := rows | ceiling/(size(children), columns); otherwise => error("You must supply either rows or columns for table panes"); end elseif (contents) select (contents by instance?) => rows := size(contents); columns := if (empty?(contents)) 0 // he asked for a useless table else reduce(method (v, x) max(v, size(x)) end, 0, contents) end; => rows := dimension(contents, 0); columns := dimension(contents, 1); end end; let rows :: = rows; // tighten up the types let columns :: = columns; table.%contents := make(, dimensions: list(rows, columns)); if (children) // Initialize contents from a sequence of children assert(size(children) <= rows * columns, "You are giving too many children to %=", table); fill-array!(table-contents(table), children) elseif (contents & ~empty?(contents)) select (contents by instance?) => // Initialize children nested sequence of contents for (cells in contents, // a row of panes, that is... row :: from 0) for (cell in cells, // a cell is one pane in a row column :: from 0) when (cell) add-child(table, cell, row: row, column: column) end end end; => // Initialize children from a contents array for (row :: from 0 below rows) for (column :: from 0 below columns) let cell = contents[row, column]; when (cell) add-child(table, cell, row: row, column: column) end end end end end end end method initialize; // Don't change the order of the sheets, because that will break the layout // Note that 'raise-sheet' will arrange to raise mirrors, which is OK define method do-raise-sheet (parent :: , sheet :: , #key activate? = #t) => () ignore(activate?); #f end method do-raise-sheet; define method table-contents-setter (contents :: , table :: ) assert(size(dimensions(contents)) = 2, "You are supplying a non-two dimensional array for %=", table); table.%contents := contents end method table-contents-setter; define method sheet-children-setter (children :: , table :: ) => (children :: ) let rows = table-rows(table); let columns = table-columns(table); case rows => columns := columns | ceiling/(size(children), rows); columns => rows := rows | ceiling/(size(children), columns); otherwise => error("You must supply either rows or columns for table panes"); end; next-method(); table.%contents := make(, dimensions: list(rows, columns)); fill-array!(table-contents(table), children); children end method sheet-children-setter; define method table-end-position (table :: ) => (index :: ) block (return) let contents = table-contents(table); let nrows :: = dimension(contents, 1); let ncells :: = dimension(contents, 0) * nrows; // Finds the first empty cell at the _end_ of the table for (index :: from ncells - 1 to 0 by -1) let (row, column) = floor/(index, nrows); when (contents[row, column]) return(if (index < ncells - 1) index + 1 else error("The table pane %= is full", table) end) end end; // If the table is empty, the last shall be the first 0 end end method table-end-position; define method do-add-child (table :: , child :: , #key index = #"end", row, column) => () let contents = table-contents(table); unless (row & column) let nrows = dimension(contents, 0); let index = select (index) #"start" => 0; #"end" => table-end-position(table); otherwise => index; end; let (the-column, the-row) = floor/(index, nrows); row := the-row; column := the-column; end; assert(~contents[row, column], "Attempting to replace an existing child in %= using ADD-CHILD", table); contents[row, column] := child; next-method() end method do-add-child; define method do-remove-child (table :: , child :: ) => () let contents = table-contents(table); let (row, col) = find-table-child(contents, child); when (row) contents[row, col] := #f end; next-method(); end method do-remove-child; define method do-replace-child (table :: , old-child :: , new-child :: ) => () let contents = table-contents(table); let (row, col) = find-table-child(contents, old-child); contents[row, col] := new-child; next-method() end method do-replace-child; define method find-table-child (contents :: , child) => (row, col) let nrows :: = dimension(contents, 0); let ncols :: = dimension(contents, 1); block (return) for (row :: from 0 below nrows) for (col :: from 0 below ncols) when (contents[row, col] == child) return(row, col) end end end; values(#f, #f) end end method find-table-child; // Options can be any of the pane sizing options // Note that this has no syntax define macro tabling { tabling (#rest ?options:expression) ?contents:* end } => { make(, children: vector(?contents), ?options) } contents: { } => { } { ?pane-spec:*; ... } => { ?pane-spec, ... } pane-spec: { ?pane:expression } => { ?pane } end macro tabling; define method do-compose-space (table :: , #key width, height) => (space-req :: ) let contents = table-contents(table); if (~contents | empty?(contents)) default-space-requirement(table, width: width, height: height) else let nrows :: = dimension(contents, 0); let ncols :: = dimension(contents, 1); // Overall preferred/min/max width and height let ow :: = 0; let omin-w :: = 0; let omax-w :: = 0; let oh :: = 0; let omin-h :: = 0; let omax-h :: = 0; let row-srs :: = make(); let cell-srs :: = make(); // Iterate over the rows, determining the height of each for (row :: from 0 below nrows) let height :: = 0; let min-height :: = 0; let max-height :: = 0; for (cell :: from 0 below ncols) let item = contents[row, cell]; when (item & ~sheet-withdrawn?(item)) let space-req = compose-space(item); let (w, w-, w+, h, h-, h+) = space-requirement-components(item, space-req); ignore(w, w-, w+); // Max the heights max!(height, h); max!(min-height, h-); // That max height of the row is the largest max height // for any cell in the row max!(max-height, h+) end end; add!(row-srs, make(, width: 0, height: height, min-height: min-height, max-height: max-height)); // Add the heights inc!(oh, height); inc!(omin-h, min-height); inc!(omax-h, max-height) end; table.%row-space-requirements := row-srs; // Iterate over the cells determing the widths of each for (cell :: from 0 below ncols) let width :: = 0; let min-width :: = 0; let max-width :: = 0; for (row :: from 0 below nrows) let item = contents[row, cell]; when (item & ~sheet-withdrawn?(item)) let space-req = compose-space(item); let (w, w-, w+, h, h-, h+) = space-requirement-components(item, space-req); ignore(h, h-, h+); // Max the widths max!(width, w); max!(min-width, w-); // That max width of the column is the largest max height // for any cell in the column max!(max-width, w+) end end; add!(cell-srs, make(, width: width, min-width: min-width, max-width: max-width, height: 0)); inc!(ow, width); inc!(omin-w, min-width); inc!(omax-w, max-width) end; let border*2 = layout-border(table) * 2; let total-x-spacing :: = (layout-x-spacing(table) * (ncols - 1)) + border*2; let total-y-spacing :: = (layout-y-spacing(table) * (nrows - 1)) + border*2; inc!(ow, total-x-spacing); inc!(omin-w, total-x-spacing); inc!(omax-w, total-x-spacing); inc!(oh, total-y-spacing); inc!(omin-h, total-y-spacing); inc!(omax-h, total-y-spacing); when (width) ow := max(omin-w, min(width, omax-w)) end; when (height) oh := max(omin-h, min(height, omax-h)) end; table.%cell-space-requirements := cell-srs; make(, width: ow, min-width: omin-w, max-width: omax-w, height: oh, min-height: omin-h, max-height: omax-h) end end method do-compose-space; define method do-allocate-space (table :: , width :: , height :: ) => () let contents = table-contents(table); when (contents & ~empty?(contents)) let space-req = compose-space(table, width: width, height: height); let nrows :: = dimension(contents, 0); let ncols :: = dimension(contents, 1); let x-spacing :: = layout-x-spacing(table); let y-spacing :: = layout-y-spacing(table); let border :: = layout-border(table); let total-x-spacing :: = x-spacing * (ncols - 1); let total-y-spacing :: = y-spacing * (nrows - 1); let row-heights = compose-space-for-items (table, height - total-y-spacing, space-req, table.%row-space-requirements, space-requirement-height, space-requirement-min-height, space-requirement-max-height, identity, ratios: layout-y-ratios(table)); let cell-widths = compose-space-for-items (table, width - total-x-spacing, space-req, table.%cell-space-requirements, space-requirement-width, space-requirement-min-width, space-requirement-max-width, identity, ratios: layout-x-ratios(table)); let y :: = border; for (row :: from 0 below nrows, row-height :: in row-heights) let cell-widths = cell-widths; let x :: = border; for (cell :: from 0 below ncols, cell-width :: in cell-widths) let item = contents[row, cell]; when (item & ~sheet-withdrawn?(item)) let item-space = compose-space(item, width: cell-width, height: row-height); let (w, w-, w+, h, h-, h+) = space-requirement-components(item, item-space); ignore(w, h); let item-width :: = constrain-size(cell-width, w-, w+); let item-height :: = constrain-size(row-height, h-, h+); let aligned-x :: = layout-align-sheet-x(table, item, x, x + cell-width - item-width, key: cell); let aligned-y :: = layout-align-sheet-y(table, item, y, y + row-height - item-height, key: row); set-sheet-edges(item, aligned-x, aligned-y, // Ensure end cells stay within table min(aligned-x + item-width, width), min(aligned-y + item-height, height)) end; inc!(x, cell-width + x-spacing) end; inc!(y, row-height + y-spacing) end end end method do-allocate-space; /// Default implementation define sealed class () keyword accepts-focus?: = #f; end class ; define method class-for-make-pane (framem :: , class == , #key) => (class :: , options :: false-or()) values(, #f) end method class-for-make-pane; define sealed domain make (singleton()); define sealed domain initialize (); /// Grid panes define open abstract class () sealed slot cell-space-requirement :: , required-init-keyword: cell-space-requirement:; end class ; define method do-compose-space (grid :: , #key width, height) => (space-req :: ) let contents = table-contents(grid); if (~contents | empty?(contents)) default-space-requirement(grid, width: width, height: height) else let nrows :: = dimension(contents, 0); let ncols :: = dimension(contents, 1); let border*2 = layout-border(grid) * 2; let total-x-spacing :: = (layout-x-spacing(grid) * (ncols - 1)) + border*2; let total-y-spacing :: = (layout-y-spacing(grid) * (nrows - 1)) + border*2; let (width :: , min-width :: , max-width :: , height :: , min-height :: , max-height :: ) = space-requirement-components(grid, cell-space-requirement(grid)); width := width * ncols + total-x-spacing; min-width := min-width * ncols + total-x-spacing; min-width := max-width * ncols + total-x-spacing; height := height * nrows + total-y-spacing; min-height := min-height * nrows + total-y-spacing; min-height := max-height * nrows + total-y-spacing; make(, width: width, min-width: min-width, max-width: max-width, height: height, min-height: min-height, max-height: max-height) end end method do-compose-space; define method do-allocate-space (grid :: , width :: , height :: ) => () let contents = table-contents(grid); when (contents & ~empty?(contents)) let nrows :: = dimension(contents, 0); let ncols :: = dimension(contents, 1); let border :: = layout-border(grid); let x-spacing :: = layout-x-spacing(grid); let y-spacing :: = layout-y-spacing(grid); let (cell-width :: , min-width, max-width, cell-height :: , min-height, max-height) = space-requirement-components(grid, cell-space-requirement(grid)); ignore(min-width, max-width, min-height, max-height); //--- This should give extra space to the cell, obeying min and max sizes let y :: = border; for (row :: from 0 below nrows) let x :: = border; for (cell :: from 0 below ncols) let item = contents[row, cell]; when (item & ~sheet-withdrawn?(item)) set-sheet-edges(item, x, y, x + cell-width, y + cell-height) end; inc!(x, cell-width + x-spacing) end; inc!(y, cell-height + y-spacing) end end end method do-allocate-space; /// Default implementation define sealed class () keyword accepts-focus?: = #f; end class ; define method class-for-make-pane (framem :: , class == , #key) => (class :: , options :: false-or()) values(, #f) end method class-for-make-pane; define sealed domain make (singleton()); define sealed domain initialize ();