<> <> <> DIRECTORY Buttons USING [Button, ButtonProc, Create, SetDisplayStyle], Commander USING [CommandProc, Register], Imager USING [black, SetColor, MaskBox], Labels USING [Create, SetDisplayStyle], Rope USING [Flatten, ROPE, Size], RuntimeError USING [BoundsFault], VFonts USING [defaultFont, StringWidth, FontHeight, Font], ViewerClasses USING [DestroyProc, PaintProc, Viewer, ViewerClass, ViewerClassRec], ViewerOps USING [BlinkViewer, CreateViewer, DestroyViewer, EstablishViewerPosition, PaintViewer, RegisterViewerClass], VTables USING [Border, FullBorder, NullBorder, VTable]; VTablesImpl: CEDAR MONITOR LOCKS data USING data: TableData IMPORTS Buttons, Commander, Imager, Labels, Rope, RuntimeError, VFonts, ViewerOps EXPORTS VTables = BEGIN OPEN VTables; ROPE: TYPE = Rope.ROPE; Viewer: TYPE = ViewerClasses.Viewer; TableData: TYPE = REF TableDataRep; TableDataRep: TYPE = MONITORED RECORD [ rows, columns: NAT _ 0, installed, locked, staticSize: BOOL _ FALSE, live: BOOL _ TRUE, condition: CONDITION, xRuleWidth: INTEGER _ 1, yRuleWidth: INTEGER _ 1, rowWidths: WidthSeq _ NIL, colWidths: WidthSeq _ NIL, width: INTEGER _ 0, height: INTEGER _ 0, request: INTEGER _ 0, rowSeq: RowSeq]; RowSeq: TYPE = REF RowSeqRep; RowSeqRep: TYPE = RECORD [ rowData: SEQUENCE nRows: NAT OF RowData]; RowData: TYPE = REF RowDataRep; RowDataRep: TYPE = RECORD [colData: SEQUENCE nCols: NAT OF Entry]; Entry: TYPE = RECORD [ border: Border, useMaxSize: BOOL, xoff,yoff: Offset, viewer: Viewer] _ NullEntry; NullEntry: Entry = [NullBorder, TRUE, 0, 0, NIL]; Offset: TYPE = INTEGER; textXoff: Offset _ 4; -- hack for $Text x offset textYoff: Offset _ 1; -- hack for $Text y offset wHack: INTEGER _ 12; -- hack for entry size calculated by text hHack: INTEGER _ 3; -- hack for entry size calculated by text WidthSeq: TYPE = REF WidthSeqRep; WidthSeqRep: TYPE = RECORD [widths: SEQUENCE size: NAT OF INTEGER]; AcquireLock: ENTRY PROC [data: TableData, painting: BOOL _ FALSE] RETURNS [BOOL] = { <> ENABLE UNWIND => NULL; DO IF data = NIL OR NOT data.live THEN RETURN [FALSE]; IF painting AND NOT data.installed THEN {data.request _ data.request + 1; RETURN [FALSE]}; IF NOT data.locked THEN {data.locked _ TRUE; RETURN [TRUE]}; WAIT data.condition; ENDLOOP; }; ReleaseLock: ENTRY PROC [data: TableData, install: BOOL _ FALSE] = { <> data.locked _ FALSE; IF install THEN data.installed _ TRUE; BROADCAST data.condition; }; NewRowSeq: PROC [rows,columns: NAT] RETURNS [RowSeq] = { rowSeq: RowSeq _ NEW[RowSeqRep[rows]]; FOR i: NAT IN [0..rows) DO rowSeq[i] _ NEW[RowDataRep[columns]] ENDLOOP; RETURN [rowSeq]; }; NewWidthSeq: PROC [width: NAT] RETURNS [ws: WidthSeq] = { ws _ NEW[WidthSeqRep[width]]; FOR i: NAT IN [0..width) DO ws[i] _ 0 ENDLOOP }; RecalculateWidths: PROC [data: TableData] = { rowWidths: WidthSeq _ data.rowWidths; colWidths: WidthSeq _ data.colWidths; FOR row: NAT IN [0..data.rows) DO rowWidths[row] _ 0; FOR col: NAT IN [0..data.columns) DO ent: Entry _ data.rowSeq[row][col]; v: Viewer _ ent.viewer; IF row = 0 THEN colWidths[col] _ 0; IF v # NIL THEN { w: INTEGER _ v.ww + ABS[ent.xoff]; h: INTEGER _ v.wh + ABS[ent.yoff]; IF w > colWidths[col] THEN colWidths[col] _ w; IF h > rowWidths[row] THEN rowWidths[row] _ h}; ENDLOOP; ENDLOOP; }; Create: PUBLIC PROC [columns, rows: INTEGER _ 1, name: ROPE _ NIL, parent: Viewer _ NIL, xRuleWidth: INTEGER _ 1, yRuleWidth: INTEGER _ 1, x, y, w, h: INTEGER _ 0, scrollable: BOOL _ FALSE] RETURNS [table: VTable] = TRUSTED { data: TableData _ NIL; IF columns <= 0 OR rows <= 0 THEN RETURN [NIL]; IF name = NIL THEN name _ " "; data _ NEW[TableDataRep]; data.rows _ rows; data.columns _ columns; data.rowSeq _ NewRowSeq[rows, columns]; data.rowWidths _ NewWidthSeq[rows]; data.colWidths _ NewWidthSeq[columns]; data.xRuleWidth _ xRuleWidth; data.yRuleWidth _ yRuleWidth; data.staticSize _ w > 0 OR h > 0; data.width _ w; data.height _ h; IF parent = NIL THEN parent _ ViewerOps.CreateViewer [ flavor: $Container, info: [name: name, column: right, iconic: TRUE], paint: TRUE]; table _ ViewerOps.CreateViewer [ flavor: $VTable, info: [ name: name, parent: parent, wx: x, wy: y, ww: w, wh: h, data: data, border: FALSE, scrollable: scrollable], paint: FALSE] }; SetRowsAndColumns: PUBLIC PROC [table: VTable, rows, columns: NAT _ 0] = TRUSTED { <> data: TableData _ NARROW[table.data]; IF data = NIL OR NOT AcquireLock[data] THEN RETURN; IF rows # data.rows OR columns # data.columns THEN { newRowSeq: RowSeq _ NewRowSeq[rows, columns]; data.installed _ FALSE; -- demand installation IF rows # data.rows THEN { newRowWidths: WidthSeq _ NewWidthSeq[rows]; FOR i: NAT IN [0..MIN[rows, data.rows]) DO newRowWidths[i] _ data.rowWidths[i]; ENDLOOP; data.rowWidths _ newRowWidths; }; IF columns # data.columns THEN { newColWidths: WidthSeq _ NewWidthSeq[columns]; FOR i: NAT IN [0..MIN[columns, data.columns]) DO newColWidths[i] _ data.colWidths[i]; ENDLOOP; data.colWidths _ newColWidths; }; FOR row: NAT IN [0..data.rows) DO FOR col: NAT IN [0..data.columns) DO oldEntry: Entry _ data.rowSeq[row][col]; IF row < rows AND col < columns THEN { <> newRowSeq[row][col] _ oldEntry; } ELSE { <> IF oldEntry.viewer # NIL THEN ViewerOps.DestroyViewer[oldEntry.viewer, FALSE]; }; ENDLOOP; ENDLOOP; data.rows _ rows; data.columns _ columns; data.rowSeq _ newRowSeq; -- make this the last gasp }; ReleaseLock[data]; }; Install: PUBLIC PROC [table: VTable, paint: BOOL _ TRUE] = TRUSTED { data: TableData _ NARROW[table.data]; IF data # NIL AND AcquireLock[data] THEN { ENABLE UNWIND => ReleaseLock[data, TRUE]; rows: NAT _ data.rows; columns: NAT _ data.columns; minX: INTEGER _ 0; minY: INTEGER _ 0; maxX: INTEGER _ minX; maxY: INTEGER _ minY; xRule: CARDINAL _ data.xRuleWidth; yRule: CARDINAL _ data.yRuleWidth; rowWidths: WidthSeq _ data.rowWidths; colWidths: WidthSeq _ data.colWidths; w: INTEGER _ table.ww; h: INTEGER _ table.wh; RecalculateWidths[data]; <> minY _ xRule; FOR i: NAT IN [0..rows) DO row: RowData _ data.rowSeq[i]; rowWidth: INTEGER _ rowWidths[i]; minX _ yRule; FOR j: NAT IN [0..columns) DO e: Entry _ row[j]; v: Viewer _ e.viewer; colWidth: INTEGER _ colWidths[j]; IF v # NIL THEN { vw: INTEGER _ v.ww; vh: INTEGER _ v.wh; IF e.useMaxSize THEN { vw _ colWidth-ABS[e.xoff]; vh _ rowWidth-ABS[e.yoff]; }; ViewerOps.EstablishViewerPosition[v, minX+e.xoff, minY+e.yoff, vw, vh]; }; minX _ minX + colWidth + yRule ENDLOOP; minY _ minY + rowWidth + xRule ENDLOOP; data.width _ minX; data.height _ minY; IF NOT data.staticSize THEN { w _ data.width; h _ data.height}; ViewerOps.EstablishViewerPosition[table, table.wx, table.wy, w, h]; ReleaseLock[data, TRUE]; IF paint OR data.request # 0 THEN ViewerOps.PaintViewer[table, client]; }}; GetTableEntry: PUBLIC PROC [table: VTable, row, column: NAT _ 0] RETURNS [v: Viewer] = TRUSTED { <> data: TableData _ NARROW[table.data]; v _ NIL; IF data = NIL OR NOT AcquireLock[data] THEN RETURN; IF row >= data.rows OR column >= data.columns THEN ReleaseAndFault[data]; v _ data.rowSeq[row][column].viewer; ReleaseLock[data]; }; SwapTableEntries: PUBLIC PROC [table: VTable, row1, column1, row2, column2: NAT _ 0, swapBorders: BOOL _ FALSE] = TRUSTED { <> data: TableData _ NARROW[table.data]; v: Viewer _ NIL; IF data = NIL OR NOT AcquireLock[data] THEN RETURN; IF row1 >= data.rows OR column1 >= data.columns OR row2 >= data.rows OR column2 >= data.columns THEN ReleaseAndFault[data]; { e1: Entry _ data.rowSeq[row1][column1]; e2: Entry _ data.rowSeq[row2][column2]; IF swapBorders THEN { data.rowSeq[row1][column1] _ e2; data.rowSeq[row2][column2] _ e1} ELSE { data.rowSeq[row1][column1].viewer _ e2.viewer; data.rowSeq[row2][column2].viewer _ e1.viewer}}; data.installed _ FALSE; -- demand installation ReleaseLock[data]; }; SetTableEntry: PUBLIC PROC [table: VTable, row, column: NAT _ 0, name: ROPE _ NIL, flavor: ATOM _ NIL, proc: Buttons.ButtonProc _ NIL, clientData: REF _ NIL, w, h: INTEGER _ 0, xoff, yoff: INTEGER _ 0, border: Border _ FullBorder, font: VFonts.Font _ NIL, displayStyle: ATOM _ NIL, useMaxSize: BOOL _ FALSE] = TRUSTED { v: Viewer _ NIL; parent: Viewer _ table; data: TableData _ NARROW[table.data]; flat: ROPE _ name.Flatten[]; textH, textW: INTEGER _ 0; old: Entry _ NullEntry; new: Entry _ NullEntry; IF data = NIL OR NOT AcquireLock[data] THEN RETURN; IF row >= data.rows OR column >= data.columns THEN ReleaseAndFault[data]; old _ data.rowSeq[row][column]; IF font = NIL THEN font _ VFonts.defaultFont; IF flat.Size[] # 0 THEN { textW _ VFonts.StringWidth[flat, font]; textH _ VFonts.FontHeight[font]}; data.installed _ FALSE; -- demand installation IF flavor = NIL THEN flavor _ $Label; IF proc # NIL THEN flavor _ $Button; IF name = NIL THEN name _ ""; IF textW > w THEN w _ textW + wHack; IF textH > h THEN h _ textH + hHack; new.xoff _ xoff; new.yoff _ yoff; new.useMaxSize _ useMaxSize; SELECT flavor FROM $Button => { v _ Buttons.Create [ info: [name: name, parent: parent, wx: 0, wy: 0, ww: w, wh: h, border: FALSE], proc: proc, clientData: clientData, fork: TRUE, paint: FALSE, font: font]; IF displayStyle # NIL THEN Buttons.SetDisplayStyle[v, displayStyle, FALSE]}; $Label => { v _ Labels.Create [ info: [parent: parent, name: name, wx: 0, wy: 0, ww: w, wh: h, border: FALSE], font: font, paint: FALSE]; IF displayStyle # NIL THEN Labels.SetDisplayStyle[v, displayStyle, FALSE]}; $Text => { h _ h - textYoff; -- make height of text, labels & buttons similar v _ ViewerOps.CreateViewer [ flavor: $Text, info: [ parent: parent, wx: 0, wy: 0, ww: w, wh: h, scrollable: FALSE, border: FALSE], paint: FALSE]; v.class.set[v, name, FALSE]; new.xoff _ textXoff + xoff; new.yoff _ textYoff + yoff}; $Viewer => v _ NARROW[clientData]; ENDCASE => v _ ViewerOps.CreateViewer [ flavor: flavor, info: [ name: name, parent: parent, wx: 0, wy: 0, ww: w, wh: h, data: clientData, border: FALSE]]; IF old.viewer # v AND old.viewer # NIL THEN ViewerOps.DestroyViewer[old.viewer, FALSE]; new.viewer _ v; new.border _ border; data.rowSeq[row][column] _ new; ReleaseLock[data]; }; GetEntryBorder: PUBLIC PROC [table: VTable, row, column: NAT _ 0] RETURNS [border: Border] = { <> data: TableData _ NARROW[table.data]; border _ NullBorder; IF data = NIL OR NOT AcquireLock[data] THEN RETURN; IF row >= data.rows OR column >= data.columns THEN ReleaseAndFault[data]; border _ data.rowSeq[row][column].border; ReleaseLock[data]; }; SetEntryBorder: PUBLIC PROC [table: VTable, row, column: NAT _ 0, border: Border _ FullBorder] = { <> data: TableData _ NARROW[table.data]; IF data = NIL OR NOT AcquireLock[data] THEN RETURN; IF row >= data.rows OR column >= data.columns THEN ReleaseAndFault[data]; data.installed _ FALSE; data.rowSeq[row][column].border _ border; ReleaseLock[data]; }; GetEntryOffset: PUBLIC PROC [table: VTable, row, column: NAT _ 0] RETURNS [xoff,yoff: INTEGER] = { <> data: TableData _ NARROW[table.data]; IF data = NIL OR NOT AcquireLock[data] THEN RETURN; IF row >= data.rows OR column >= data.columns THEN ReleaseAndFault[data]; xoff _ data.rowSeq[row][column].xoff; yoff _ data.rowSeq[row][column].yoff; ReleaseLock[data]; }; SetEntryOffset: PUBLIC PROC [table: VTable, row, column: NAT _ 0, xoff,yoff: INTEGER _ 0] = { <> data: TableData _ NARROW[table.data]; IF data = NIL OR NOT AcquireLock[data] THEN RETURN; IF row >= data.rows OR column >= data.columns THEN ReleaseAndFault[data]; data.installed _ FALSE; data.rowSeq[row][column].xoff _ xoff; data.rowSeq[row][column].yoff _ yoff; ReleaseLock[data]; }; GetRowsAndColumns: PUBLIC PROC [table: VTable] RETURNS [rows, columns: NAT] = { data: TableData _ NARROW[table.data]; IF data = NIL OR NOT AcquireLock[data] THEN RETURN; rows _ data.rows; columns _ data.columns; ReleaseLock[data]; }; ExchangeRows: PUBLIC PROC [table: VTable, row1,row2: NAT _ 0] = { <> data: TableData _ NARROW[table.data]; IF data = NIL OR NOT AcquireLock[data] THEN RETURN; IF row1 >= data.rows OR row2 >= data.rows THEN ReleaseAndFault[data]; data.installed _ FALSE; {-- exchange the rows temp: RowData _ data.rowSeq[row1]; data.rowSeq[row1] _ data.rowSeq[row2]; data.rowSeq[row2] _ temp}; ReleaseLock[data]; }; ExchangeColumns: PUBLIC PROC [table: VTable, column1,column2: NAT _ 0] = { <> data: TableData _ NARROW[table.data]; IF data = NIL OR NOT AcquireLock[data] THEN RETURN; IF column1 >= data.columns OR column2 >= data.columns THEN ReleaseAndFault[data]; data.installed _ FALSE; FOR row: NAT IN [0..data.rows) DO temp: Entry _ data.rowSeq[row][column1]; data.rowSeq[row][column1] _ data.rowSeq[row][column2]; data.rowSeq[row][column2] _ temp; ENDLOOP; ReleaseLock[data]; }; <> TablePainter: ViewerClasses.PaintProc = TRUSTED { <<[self: ViewerClasses.Viewer, context: Imager.Context, whatChanged: REF ANY, clear: BOOL] RETURNS [quit: BOOL _ FALSE]>> <> table: VTable _ self; data: TableData _ NARROW[self.data]; IF data = NIL OR NOT AcquireLock[data, TRUE] THEN RETURN[quit: TRUE]; { xPos: INTEGER _ 0; xLim: INTEGER _ data.width; yPos: INTEGER _ 0; yLim: INTEGER _ data.height; rows: NAT _ data.rows; columns: NAT _ data.columns; xWidth: INTEGER _ data.xRuleWidth; yWidth: INTEGER _ data.yRuleWidth; Imager.SetColor[context, Imager.black]; <<>> <> <<>> FOR row: NAT DECREASING IN [0..rows] DO yLast: INTEGER _ yPos + xWidth; xPos _ 0; FOR col: NAT IN [0..columns) DO prev: Border _ IF row = rows THEN NullBorder ELSE data.rowSeq[row][col].border; this: Border _ IF row = 0 THEN NullBorder ELSE data.rowSeq[row-1][col].border; xLast: INTEGER _ xPos + data.colWidths[col] + yWidth; IF prev.down OR this.up THEN { <> Imager.MaskBox[context, [xPos, yPos, xLast+yWidth, yLast]]}; xPos _ xLast; ENDLOOP; IF row = 0 THEN EXIT; yPos _ yLast + data.rowWidths[row-1]; ENDLOOP; <<>> <> <<>> xPos _ 0; FOR col: NAT IN [0..columns] DO xLast: INTEGER _ xPos + yWidth; yPos _ 0; FOR row: NAT DECREASING IN [0..rows) DO prev: Border _ IF col = 0 THEN NullBorder ELSE data.rowSeq[row][col-1].border; this: Border _ IF col = columns THEN NullBorder ELSE data.rowSeq[row][col].border; yLast: INTEGER _ yPos + data.rowWidths[row] + xWidth; IF prev.right OR this.left THEN { <> Imager.MaskBox[context, [xPos, yPos, xLast, yLast + xWidth]]}; yPos _ yLast; ENDLOOP; IF col = columns THEN EXIT; xPos _ xLast + data.colWidths[col]; ENDLOOP; data.request _ 0; ReleaseLock[data]; }; }; TableDestroy: ViewerClasses.DestroyProc = { <<[self: Viewer]>> data: TableData _ NARROW[self.data]; IF data = NIL OR NOT AcquireLock[data] THEN RETURN; data.live _ FALSE; self.data _ NIL; ReleaseLock[data]; }; <> MakeViewerClass: PROC = TRUSTED { vTableClass: ViewerClasses.ViewerClass _ NEW[ViewerClasses.ViewerClassRec]; vTableClass.icon _ tool; vTableClass.paint _ TablePainter; vTableClass.destroy _ TableDestroy; vTableClass.topDownCoordSys _ TRUE; ViewerOps.RegisterViewerClass[$VTable, vTableClass] }; ReleaseAndFault: PROC [data: TableData] = TRUSTED { ReleaseLock[data]; ERROR RuntimeError.BoundsFault}; DummyButtonProc: Buttons.ButtonProc = { <<[parent: REF ANY, clientData: REF ANY _ NIL, mouseButton: Menus.MouseButton _ red, shift: BOOL _ FALSE, control: BOOL _ FALSE]>> WITH parent SELECT FROM v: ViewerClasses.Viewer => ViewerOps.BlinkViewer[v]; ENDCASE; }; instance: Viewer _ NIL; TestInstance: PROC [useText: BOOL _ TRUE] = { instance _ Create[3, 3, "test"]; SetTableEntry[instance, 0, 0, "0-0 button *", $Button, DummyButtonProc]; IF useText THEN SetTableEntry[instance, 0, 1, "0-1 text **", $Text]; SetTableEntry[instance, 0, 2, "0-2 label ***"]; SetTableEntry[instance, 1, 0, "1-0 label ****"]; SetTableEntry[instance, 1, 1, "1-1 button *", $Button, DummyButtonProc]; IF useText THEN SetTableEntry[instance, 1, 2, "1-2 text **", $Text]; IF useText THEN SetTableEntry[instance, 2, 0, "2-0 text ***", $Text]; SetTableEntry[instance, 2, 1, "2-1 label ****"]; SetTableEntry[instance, 2, 2, "2-2 button ****", $Button, DummyButtonProc]; SetEntryBorder[instance, 2, 2, NullBorder]; Install[instance] }; dummyCount: INT _ 0; DummyCommandProc: Commander.CommandProc = { IF (dummyCount _ dummyCount + 1) > 1 THEN msg _ "Reusing common instance of VTablesImpl." ELSE msg _ "Started common instance of VTablesImpl." }; Commander.Register[ "VTablesImpl", DummyCommandProc, "Hack to avoid multiple instances of VTablesImpl."]; MakeViewerClass[] END.