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 { 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 = { 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 = { 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. ΜVTablesImpl.mesa Copyright c 1985 by Xerox Corporation. All rights reserved. Russ Atkinson, April 25, 1985 6:49:13 am PST acquire the lock on the data; if requireInstalled then we wait for installation as well; return the live flag; if FALSE is returned, the lock is not held release the data lock; if install, then set the installed flag as well; then notify everyone else that the world is OK sets the number of rows and columns; can be used to either grow or shrink the table copy the entry (if any) from the old world delete the old viewer determine the row spacing retrieve the viewer at the given row and column swap the given table entries get the current table entry at the given row and column get the current table entry at the given row and column get the current offsets of the given entry set the current table border at the given row and column; reinstallation is required exchange the two given rows; reinstallation is required exchange the two given columns; reinstallation is required paint and destroy procedures [self: ViewerClasses.Viewer, context: Imager.Context, whatChanged: REF ANY, clear: BOOL] RETURNS [quit: BOOL _ FALSE] Just paint the rules for the table; the children will be painted after this painting. paint the horizontals include this edge paint the verticals include this edge [self: Viewer] other procedures [parent: REF ANY, clientData: REF ANY _ NIL, mouseButton: Menus.MouseButton _ red, shift: BOOL _ FALSE, control: BOOL _ FALSE] ΚΤ˜codešœ™Kšœ Οmœ1™K˜Kšœ žœžœ˜"Kš œ žœžœ žœžœžœžœ˜DK˜šΟn œžœžœžœžœžœžœ˜TKšœ™™™Kšžœžœžœ˜šž˜Kšžœžœžœžœ žœžœžœ˜3šžœ žœžœž˜'Kšœ"žœžœ˜2—Kš žœžœ žœžœžœžœ˜˜>—K˜ Kšžœ˜ —Kšžœžœžœ˜K˜#Kšžœ˜—K˜K˜K˜—K˜K˜—˜+Kšœ™Kšœžœ ˜$Kš žœžœžœžœžœžœ˜3Kšœ žœ˜Kšœ žœ˜K˜K˜K˜—Kšœ™K˜š œžœžœ˜!Kšœ)žœ˜KK˜K˜!K˜$Kšœžœ˜$K˜3K˜K˜—š œžœžœ˜3K˜Kšžœ˜ K˜—–‚ -- [parent: REF ANY, clientData: REF ANY _ NIL, mouseButton: Menus.MouseButton _ red, shift: BOOL _ FALSE, control: BOOL _ FALSE]šœ'˜'Kš‘~™~šžœžœž˜Kšœ4˜4Kšžœ˜—K˜K˜—Kšœžœ˜š  œžœ žœžœ˜-K˜!K˜Išžœ ž˜K˜5—K˜0K˜0K˜Išžœ ž˜K˜5—šžœ ž˜K˜6—K˜0K˜KK˜,K˜K˜K˜—Kšœ žœ˜šœ+˜+šžœ"˜$Kšžœ0˜4Kšžœ0˜4—Kšœ˜—K˜šœ˜K˜UK˜—K˜K˜Kšžœ˜K˜K˜K˜——…—>”Z4