DIRECTORY Buttons USING [Button, ButtonProc, Create, SetDisplayStyle], Imager USING [black, MaskBox, SetColor], Labels USING [Create, SetDisplayStyle], Rope USING [Flatten, ROPE, Size], RuntimeError USING [BoundsFault], VFonts USING [defaultFont, Font, FontHeight, StringWidth], 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, 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; IF data # NIL THEN WHILE data.live DO IF painting AND NOT data.installed THEN {data.request _ data.request + 1; EXIT}; IF NOT data.locked THEN {data.locked _ TRUE; RETURN [TRUE]}; WAIT data.condition; ENDLOOP; RETURN [FALSE]; }; ReleaseLock: ENTRY PROC [data: TableData, installed: BOOL] = { data.locked _ FALSE; data.installed _ installed; BROADCAST data.condition; }; SetInstalled: ENTRY PROC [data: TableData, installed: BOOL] = { data.installed _ installed; 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 _ NIL] = TRUSTED { data: TableData _ NEW[TableDataRep]; IF columns <= 0 OR rows <= 0 THEN RETURN [NIL]; IF name = NIL THEN name _ " "; 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 AND AcquireLock[data] THEN { IF rows # data.rows OR columns # data.columns THEN { ENABLE UNWIND => ReleaseLock[data, FALSE]; newRowSeq: RowSeq _ NewRowSeq[rows, columns]; SetInstalled[data, FALSE]; 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, data.installed]; }; }; 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, FALSE]; 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 _ NIL] = TRUSTED { data: TableData _ NARROW[table.data]; IF data # NIL AND AcquireLock[data] THEN { IF row >= data.rows OR column >= data.columns THEN ReleaseAndFault[data]; v _ data.rowSeq[row][column].viewer; ReleaseLock[data, data.installed]; }; }; 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 AND AcquireLock[data] THEN { 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}}; ReleaseLock[data, FALSE]; }; }; 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 AND AcquireLock[data] THEN { 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]; }; SetInstalled[data, FALSE]; -- do this here! { ENABLE UNWIND => ReleaseLock[data, FALSE]; 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 => WITH clientData SELECT FROM vv: Viewer => v _ vv; ENDCASE; 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, FALSE]; }; }; GetEntryBorder: PUBLIC PROC [table: VTable, row, column: NAT _ 0] RETURNS [border: Border _ NullBorder] = { data: TableData _ NARROW[table.data]; IF data # NIL AND AcquireLock[data] THEN { IF row >= data.rows OR column >= data.columns THEN ReleaseAndFault[data]; border _ data.rowSeq[row][column].border; ReleaseLock[data, data.installed]; }; }; SetEntryBorder: PUBLIC PROC [table: VTable, row, column: NAT _ 0, border: Border _ FullBorder] = { data: TableData _ NARROW[table.data]; IF data # NIL AND AcquireLock[data] THEN { IF row >= data.rows OR column >= data.columns THEN ReleaseAndFault[data]; data.rowSeq[row][column].border _ border; ReleaseLock[data, FALSE]; }; }; GetEntryOffset: PUBLIC PROC [table: VTable, row, column: NAT _ 0] RETURNS [xoff,yoff: INTEGER _ 0] = { data: TableData _ NARROW[table.data]; IF data # NIL AND AcquireLock[data] THEN { 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, data.installed]; }; }; SetEntryOffset: PUBLIC PROC [table: VTable, row, column: NAT _ 0, xoff,yoff: INTEGER _ 0] = { data: TableData _ NARROW[table.data]; IF data # NIL AND AcquireLock[data] THEN { IF row >= data.rows OR column >= data.columns THEN ReleaseAndFault[data]; data.rowSeq[row][column].xoff _ xoff; data.rowSeq[row][column].yoff _ yoff; ReleaseLock[data, FALSE]; }; }; GetRowsAndColumns: PUBLIC PROC [table: VTable] RETURNS [rows, columns: NAT _ 0] = { data: TableData _ NARROW[table.data]; IF data # NIL AND AcquireLock[data] THEN { rows _ data.rows; columns _ data.columns; ReleaseLock[data, data.installed]; }; }; ExchangeRows: PUBLIC PROC [table: VTable, row1,row2: NAT _ 0] = { data: TableData _ NARROW[table.data]; IF data # NIL AND AcquireLock[data] THEN { IF row1 >= data.rows OR row2 >= data.rows THEN ReleaseAndFault[data]; {-- exchange the rows temp: RowData _ data.rowSeq[row1]; data.rowSeq[row1] _ data.rowSeq[row2]; data.rowSeq[row2] _ temp}; ReleaseLock[data, FALSE]; }; }; ExchangeColumns: PUBLIC PROC [table: VTable, column1,column2: NAT _ 0] = { data: TableData _ NARROW[table.data]; IF data # NIL AND AcquireLock[data] THEN { IF column1 >= data.columns OR column2 >= data.columns THEN ReleaseAndFault[data]; 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, FALSE]; }; }; TablePainter: ViewerClasses.PaintProc = TRUSTED { table: VTable _ self; WITH self.data SELECT FROM data: TableData => IF AcquireLock[data, TRUE] THEN { ENABLE UNWIND => ReleaseLock[data, FALSE]; 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, TRUE]; RETURN [FALSE]; }; ENDCASE; RETURN [TRUE]; -- don't try to paint the children! }; TableDestroy: ViewerClasses.DestroyProc = { WITH self.data SELECT FROM data: TableData => IF AcquireLock[data] THEN { data.live _ FALSE; self.data _ NIL; ReleaseLock[data, FALSE]; }; ENDCASE; }; 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, data.installed]; 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] }; MakeViewerClass[] END. VTablesImpl.mesa Copyright Σ 1985, 1987 by Xerox Corporation. All rights reserved. created by Russ Atkinson Doug Wyatt, January 21, 1987 11:19:49 pm PST Russ Atkinson (RRA) March 12, 1987 6:14:46 pm 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šœB™BKšœ™K™,K™1—˜šΟk ˜ Kšœœ0˜=Kšœœ˜(Kšœœ˜(Kšœœ œ˜"Kšœ œ˜"Kšœœ.˜:Kšœœ@˜SKšœ œh˜wšœœ*˜7K˜———š Οn œœœœœ˜;Kšœ?˜FKšœ˜Kšœœœ ˜K˜Kšœœœ˜Kšœœ˜$K˜Kšœ œœ˜$šœœ œœ˜(Kšœœ˜Kšœœœ˜-Kšœœœ˜Kšœ  œ˜Kšœ œ˜Kšœ œ˜Kšœœ˜Kšœœ˜Kšœœ˜Kšœœ˜Kšœ œ˜K˜K˜—Kšœœœ ˜šœ œœ˜Kšœ œœœ ˜)K˜—Kšœ œœ ˜ Kš œ œœ œœœ ˜Cšœœœ˜Kšœœ˜!K˜/—Kšœ œœ˜1Kšœœœ˜K˜KšœΟc˜1KšœŸ˜1K˜KšœœŸ)˜?KšœœŸ)˜>K˜Kšœ œœ˜"Kš œ œœ œœœœ˜DK˜šž œœœœœœœ˜TKšœrœ"™™Kšœœœ˜š œœœœ ˜%Kš œ œœœ#œ˜PKš œœ œœœœ˜Kšœv™vKšœœ˜Kšœ˜Kš œ˜K˜K˜—šž œœœœ˜?Kšœ˜Kš œ˜K˜K˜—šž œœœœ ˜8Kšœœ˜&šœœœ œ˜Kšœ œ˜$Kšœ˜—Kšœ ˜K˜K˜—šž œœ œœ˜9Kšœœ˜šœœœ œ˜K˜ Kš˜—K˜K˜—šžœœ˜-K˜%K˜%šœœœ˜!K˜šœœœ˜$K˜#K˜Kšœ œ˜#šœœœ˜Kšœœ œ ˜"Kšœœ œ ˜"Kšœœ˜.Kšœœ˜.Kšœ˜—Kšœ˜—Kšœ˜—K˜K˜—šžœœœœ œœœœœœœœœœœ˜θKšœœ˜$Kš œœ œœœ˜0Kšœœœ ˜K˜K˜K˜'K˜#K˜'K˜K˜Kšœœ˜!K˜K˜šœ œ˜šœ ˜ ˜K˜Kšœ*œ˜0Kšœœ˜———˜˜K˜˜K˜1Kšœœ˜:—Kšœœ˜ ——K˜K˜—š žœœœ œœ˜RKšœS™SKšœœ ˜%šœœœœ˜*šœœœ˜4Kšœœœ˜*Kšœ-˜-Kšœœ˜šœœ˜K˜+š œœœœ˜*K˜$Kšœ˜—K˜K˜—šœœ˜ K˜.š œœœœ˜0K˜$Kšœ˜—K˜K˜—šœœœ˜!šœœœ˜$K˜(šœ œ˜šœ˜Kšœ*™*K˜K˜—šœ˜Kšœ™šœœ˜Kšœ)œ˜0—K˜——Kšœ˜—Kšœ˜—K˜K˜KšœŸ˜4K˜—Kšœ"˜"K˜—K˜K˜—š žœœœœœœ˜DKšœœ ˜%šœœœœ˜*Kšœœœ˜*Kšœœ˜Kšœ œ˜Kšœœ˜Kšœœ˜Kšœœ ˜Kšœœ ˜Kšœœ˜#Kšœœ˜#K˜&K˜%Kšœœ ˜Kšœœ ˜K˜K˜K˜Kšœ™K˜šœœœ œ˜K˜Kšœ œ˜!K˜šœœœœ˜K˜K˜Kšœ œ˜!šœœœ˜Kšœœ˜Kšœœ˜šœœ˜Kšœœ ˜Kšœœ ˜Kšœ˜—K˜GK˜—K˜Kšœ˜ —K˜Kšœ˜ —K˜K˜šœœœ˜Kšœ"˜"—K˜DKšœœ˜Kšœœœ&˜GK˜—K˜K˜—šž œœœœœœœ˜fKšœ/™/Kšœœ ˜%šœœœœ˜*Kšœœœ˜IK˜$K˜"K˜—K˜K˜—šžœœœ/œœœœ˜{Kšœ™Kšœœ ˜%Kšœ œ˜šœœœœ˜*š œœœœ˜dK˜—˜K˜'K˜'šœ ˜šœ˜Kšœ ˜ K˜ —šœ˜Kšœ.˜.K˜0———Kšœœ˜K˜—K˜K˜—š&ž œœœœ œœ œœœœœœœ7œœœœœœ˜ΐKšœ œ˜K˜Kšœœ˜&Kšœœ˜Kšœœ˜K˜K˜šœœœœ˜*šœœ˜2K˜—K˜Kšœœœ˜-šœœ˜Kšœ'˜'K˜ K˜—KšœœŸ˜,˜Kšœœœ˜*Kšœ œœ˜&Kšœœœ˜$Kšœœœ ˜Kšœ œ˜$Kšœ œ˜$K˜K˜K˜šœœ˜˜ ˜šœ˜KšœGœ˜OK˜ K˜Kšœœ˜ Kšœœ˜ K˜ ——šœœ˜Kšœ)œ˜0—Kšœ˜—˜ ˜˜K˜?Kšœœœ˜+——šœœ˜Kšœ(œ˜/—Kšœ˜—šœ ˜ KšœŸ0˜B˜K˜Kšœ?œ œ˜UKšœœ˜—Kšœœ˜K˜K˜K˜—˜ šœ œ˜K˜Kšœ˜——šœ˜ ˜K˜KšœYœ˜b———šœœœ˜+Kšœ$œ˜,—K˜K˜K˜K˜—Kšœœ˜K˜—K˜K˜—š žœœœœœ"˜kKšœ7™7Kšœœ ˜%šœœœœ˜*Kšœœœ˜IK˜)K˜"K˜—K˜K˜—šžœœœœ&˜bKšœ7™7Kšœœ ˜%šœœœœ˜*Kšœœœ˜IK˜)Kšœœ˜K˜—K˜K˜—š žœœœœœ œ ˜fKšœ*™*Kšœœ ˜%šœœœœ˜*Kšœœœ˜IK˜%K˜%K˜"K˜—K˜K˜—š žœœœœœ ˜]KšœU™UKšœœ ˜%šœœœœ˜*Kšœœœ˜IK˜%K˜%Kšœœ˜K˜—K˜K˜—š žœœœœœ ˜SKšœœ ˜%šœœœœ˜*K˜K˜Kšœ"˜"K˜—K˜K˜—šž œœœœ ˜AKšœ7™7Kšœœ ˜%šœœœœ˜*Kšœœœ˜EšœŸ˜K˜"K˜&K˜—Kšœœ˜K˜—K˜K˜—šžœœœ"œ ˜JKšœ:™:Kšœœ ˜%šœœœœ˜*šœœ˜:K˜—šœœœ˜!K˜(K˜6K˜!Kšœ˜—Kšœœ˜K˜—K˜K˜—Kšœ™K˜•StartOfExpansiony -- [self: ViewerClasses.Viewer, context: Imager.Context, whatChanged: REF ANY, clear: BOOL] RETURNS [quit: BOOL _ FALSE]šž œœ˜1KšΠcku™uKšœU™UK˜šœ œ˜šœœœœ˜4Kšœœœ˜*Kšœœ˜Kšœœ˜Kšœœ˜Kšœœ˜Kšœœ˜Kšœ œ˜Kšœœ˜#Kšœœ˜"K˜(K™Kšœ™K™š œœ œœ ˜'Kšœœ˜K˜ šœœœ˜˜Kšœ œ œ˜@—˜Kšœ œ œ ˜?—Kšœœ'˜5šœ œ œ˜Kšœ™Kšœ<˜<—K˜ Kšœ˜—Kšœ œœ˜Kšœ%˜%Kšœ˜ —K™Kšœ™K™K˜ šœœœœ˜ Kšœœ˜K˜ š œœ œœ œ˜(˜šœ ˜Kšœ ˜K˜——˜šœ˜Kšœ ˜Kšœ˜"——Kšœœ'˜5šœ œ œ˜!Kšœ™Kšœ>˜>—K˜ Kšœ˜ —Kšœœœ˜K˜#Kšœ˜—K˜Kšœœ˜Kšœœ˜K˜—Kšœ˜—KšœœŸ#˜3K˜K˜—šž œ˜+Kšœ™šœ œ˜šœœœ˜.Kšœ œ˜Kšœ œ˜Kšœœ˜K˜—Kšœ˜—K˜K˜——šœ™K˜šžœœœ˜!Kšœ)œ˜KK˜K˜!K˜$Kšœœ˜$K˜3K˜K˜—šžœœœ˜3K˜"Kšœ˜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˜Kšœ˜——…—=ζYΙ