<> <> <> <> <> <> <> <> <> DIRECTORY ClassIncreek, DB, DBIcons USING [GetIcon], Icons USING [DrawIcon, IconFlavor, iconW, iconH], Imager, ImagerBackdoor, ImagerManhattan USING [Difference, Polygon], ImagerPath USING [ Trajectory, ArcTo, MoveTo ], ImagerPixelMap USING [Clip, Create, DeviceRectangle, Intersect, Fill, PixelMap, Transfer, CreateFrameBuffer], InputFocus, Interminal, InterminalBackdoor, IO, MBQueue, Menus, MessageWindow, Process USING [Detach], Rope USING [Concat, Flatten, Find, Length, ROPE], Terminal, TIPUser, ViewerClasses USING [PaintProc, Viewer, ViewerClass, ViewerClassRec, ViewerRec, ViewerFlavor], ViewerEvents, ViewerLocks USING [CallUnderWriteLock], ViewerOps, ViewerPrivate, ViewerTools USING [MakeNewTextViewer, TiogaContents, SetTiogaContents], WhiteboardDB, WhiteboardViewers, WhiteboardOps; WhiteboardViewersImpl: CEDAR PROGRAM IMPORTS ClassIncreek, DBIcons, Icons, Imager, ImagerBackdoor, ImagerManhattan, ImagerPath, ImagerPixelMap, InputFocus, InterminalBackdoor, IO, MBQueue, Menus, MessageWindow, Process, Rope, Terminal, TIPUser, ViewerEvents, ViewerLocks, ViewerOps, ViewerPrivate, ViewerTools, WhiteboardDB, WhiteboardOps EXPORTS WhiteboardViewers SHARES ViewerOps = BEGIN OPEN ViewerClasses; ROPE: TYPE = Rope.ROPE; ViewerPair: TYPE = RECORD[from, to: Viewer]; LineList: TYPE = LIST OF ViewerPair; <> h: INTEGER _ 620; w: INTEGER _ 30; wb: PUBLIC ViewerClasses.ViewerFlavor _ $Whiteboard; <> whiteboardClass: ViewerClasses.ViewerClass; icon: PUBLIC ViewerClasses.ViewerFlavor _ $WhiteboardIcon; <> iconClass: ViewerClasses.ViewerClass; text: PUBLIC ViewerClasses.ViewerFlavor _ $Text; <> CreateWhiteboardClass: PROCEDURE = BEGIN tipTableName: ROPE = "Whiteboard.tip"; tipTable: TIPUser.TIPTable = TIPUser.InstantiateNewTIPTable[tipTableName]; whiteboardClass _ NEW[ViewerClasses.ViewerClassRec _ []]; whiteboardClass^ _ ViewerOps.FetchViewerClass[$Container]^; whiteboardClass.flavor _ wb; whiteboardClass.notify _ WhiteboardOps.Notify; whiteboardClass.modify _ NIL; whiteboardClass.tipTable _ tipTable; whiteboardClass.cursor _ crossHairsCircle; whiteboardClass.topDownCoordSys _ TRUE; PaintContainer _ whiteboardClass.paint; whiteboardClass.paint _ PaintWhiteboard; whiteboardClass.icon _ private; ViewerOps.RegisterViewerClass[wb, whiteboardClass]; END; CreateIconClass: PROCEDURE = BEGIN iconClass _ NEW[ViewerClasses.ViewerClassRec _ []]; iconClass^ _ ViewerOps.FetchViewerClass[wb]^; iconClass.flavor _ icon; iconClass.paint _ PaintIcon; iconClass.init _ NIL; ViewerOps.RegisterViewerClass[icon, iconClass]; END; PaintContainer: PaintProc; PaintWhiteboard: ViewerClasses.PaintProc = BEGIN IF self.iconic THEN { [] _ PaintIconic[self, context, whatChanged, TRUE]; RETURN }; [] _ PaintContainer[self, context, whatChanged, clear]; [] _ PaintLines[self, context] END; PaintLines: PROC[self: Viewer, context: Imager.Context] = BEGIN x1, y1, x2, y2: INTEGER; PaintTheLine: PROC[context: Imager.Context] = { AddLine[ctx: context, fromX: x1, fromY: y1, toX: x2, toY: y2] }; lines: LineList _ NARROW[ViewerOps.FetchProp[self, $Lines], LineList]; FOR ll: LineList _ lines, ll.rest UNTIL ll = NIL DO IF NOT ll.first.from.destroyed AND NOT ll.first.to.destroyed THEN { x1 _ ll.first.from.wx + (ll.first.from.ww/2); y1 _ self.ch - (ll.first.from.wy + ll.first.from.wh); x2 _ ll.first.to.wx + (ll.first.to.ww/2); y2 _ self.ch - ll.first.to.wy; ViewerPrivate.PaintWindow[self, PaintTheLine] } ENDLOOP; END; PaintIconic: PUBLIC ViewerClasses.PaintProc = BEGIN DrawIcon: PROC ~ { IF self.newVersion THEN { IF DirtyWBicon = unInit THEN ERROR WhiteboardDB.WBError[$ServerDown]; Icons.DrawIcon[DirtyWBicon, context, 0, 0, Strip[self.name]] } ELSE { IF WBicon = unInit THEN ERROR WhiteboardDB.WBError[$ServerDown]; Icons.DrawIcon[WBicon, context, 0, 0, Strip[self.name]] }; }; PaintInverted: PROC ~ { Imager.SetColor[context, ImagerBackdoor.invert]; Imager.MaskRectangleI[context, 0, 0, Icons.iconW, Icons.iconH] }; IF ~self.newVersion THEN FOR child: Viewer _ self.child, child.sibling WHILE child # NIL DO IF child.newVersion THEN {self.newVersion _ TRUE; EXIT}; ENDLOOP; IF DirtyWBicon = unInit THEN DirtyWBicon _ DBIcons.GetIcon["DirtyWhiteboard"]; IF WBicon = unInit THEN WBicon _ DBIcons.GetIcon["Whiteboard"]; Imager.DoSave[context, DrawIcon]; IF ViewerPrivate.selectedIcon = self THEN Imager.DoSave[context, PaintInverted] END; Strip: PROCEDURE[name: ROPE] RETURNS[ROPE] = INLINE BEGIN pos: INT; IF name = NIL THEN RETURN[NIL]; IF (pos _ name.Find[":"]) > 0 THEN RETURN[name.Flatten[pos+2, name.Length[]]] ELSE RETURN[name]; END; PaintIcon: PUBLIC ViewerClasses.PaintProc = BEGIN name: ROPE = self.name; iconRef: REF Icons.IconFlavor = NARROW[ViewerOps.FetchProp[self, $IconFlavor]]; icon: Icons.IconFlavor = IF iconRef = NIL THEN unInit ELSE iconRef^; DrawIcon: PROC ~ { Icons.DrawIcon[icon, context, 0, 0, name] }; Imager.DoSave[context, DrawIcon]; IF self.spare0 THEN { <> PaintInverted: PROC ~ { Imager.SetColor[context, ImagerBackdoor.invert]; Imager.MaskRectangleI[context, 0, 0, Icons.iconW, Icons.iconH] }; Imager.DoSave[context, PaintInverted] } END; InvertIcon: PUBLIC PROC[icon: Viewer, paint: BOOL _ TRUE] = { icon.spare0 _ NOT icon.spare0; IF paint THEN ViewerOps.PaintViewer[icon, all] }; WBicon: Icons.IconFlavor _ DBIcons.GetIcon["Whiteboard"]; DirtyWBicon: Icons.IconFlavor _ DBIcons.GetIcon["DirtyWhiteboard"]; <> <<(the AddTextBox and AddIcon procedures DO NOT paint the viewers they create)>> AddTextBox: PUBLIC PROC[wb: Viewer, x, y, w, h: INTEGER, contents: ViewerTools.TiogaContents] RETURNS[child: Viewer] = BEGIN grid: NAT = GetGrid[wb]; MakeChild: PROC[] = { child _ ViewerTools.MakeNewTextViewer[paint: FALSE, info: [parent: wb, wx: 0, wy: 0, ww: 800, wh: 800]]; IF contents # NIL THEN ViewerTools.SetTiogaContents[viewer: child, contents: contents, paint: FALSE]; ViewerOps.MoveViewer[child, x-(x MOD grid), y-(y MOD grid), w-(w MOD grid), h-(h MOD grid), FALSE] }; registration: ViewerEvents.EventRegistration; ViewerLocks.CallUnderWriteLock[MakeChild, wb]; wb.newVersion _ TRUE; registration _ ViewerEvents.RegisterEventProc[SetNew, edit, child]; ViewerOps.AddProp[child, $registration, registration] END; SetNew: ViewerEvents.EventProc = { viewer.parent.newVersion _ TRUE; WhiteboardDB.EditText[viewer]; TRUSTED { Process.Detach[FORK ViewerOps.PaintViewer[viewer: viewer.parent, hint: caption]] } }; AddIcon: PUBLIC PROC[wb: Viewer, name: ROPE, icon: Icons.IconFlavor, x, y: INTEGER] RETURNS[child: Viewer] = BEGIN OPEN ViewerOps; grid: NAT = GetGrid[wb]; <> IF name = NIL THEN RETURN; -- nothing we can do here child _ CreateViewer[flavor: $WhiteboardIcon, paint: FALSE, info: [ parent: wb, wx: x - (x MOD grid), wy: y - (y MOD grid), wh: 64, ww: 64, name: name, border: FALSE, scrollable: FALSE]]; ViewerOps.AddProp[child, $IconFlavor, NEW[Icons.IconFlavor _ icon]]; wb.newVersion _ TRUE; END; GetGrid: PUBLIC PROC[ v: Viewer ] RETURNS[ grid: INT ] = { gridSize: REF INT = NARROW[ViewerOps.FetchProp[v, $gridSize]]; RETURN[IF gridSize = NIL THEN 1 ELSE gridSize^] }; SetGrid: PUBLIC PROC[v: Viewer, grid: INT] = { gridSize: REF INT = NARROW[ViewerOps.FetchProp[v, $gridSize]]; newName: IO.STREAM = IO.ROS[]; oldEntry: Menus.MenuEntry _ NARROW[ViewerOps.FetchProp[v, $gridEntry]]; newEntry: Menus.MenuEntry; IF oldEntry = NIL THEN oldEntry _ Menus.FindEntry[v.menu, "Grid: 1"]; IO.PutF[newName, "Grid: %2g", IO.int[grid]]; IF gridSize = NIL THEN ViewerOps.AddProp[v, $gridSize, NEW[INT _ grid]] ELSE gridSize^ _ grid; newEntry _ MBQueue.CreateMenuEntry[ActionQueue, IO.RopeFromROS[newName], SetGridProc]; Menus.ReplaceMenuEntry[v.menu, oldEntry, newEntry]; ViewerOps.AddProp[v, $gridEntry, newEntry]; v.newVersion _ TRUE; ViewerOps.PaintViewer[v, all]; newName.Close[] }; ResetGrid: PUBLIC PROC[v: Viewer, grid: INT] = { gridSize: REF INT = NARROW[ViewerOps.FetchProp[v, $gridSize]]; IF gridSize = NIL THEN ViewerOps.AddProp[v, $gridSize, NEW[INT _ grid]] ELSE gridSize^ _ grid; IF grid = 1 THEN { ViewerOps.PaintViewer[v, all]; ViewerOps.AddProp[v, $gridEntry, NIL]; RETURN}; BEGIN newName: IO.STREAM = IO.ROS[]; oldEntry: Menus.MenuEntry _ NARROW[ViewerOps.FetchProp[v, $gridEntry]]; newEntry: Menus.MenuEntry; IF oldEntry = NIL THEN oldEntry _ Menus.FindEntry[v.menu, "Grid: 1"]; IO.PutF[newName, "Grid: %2g", IO.int[grid]]; newEntry _ MBQueue.CreateMenuEntry[ActionQueue, IO.RopeFromROS[newName], SetGridProc]; Menus.ReplaceMenuEntry[v.menu, oldEntry, newEntry]; ViewerOps.AddProp[v, $gridEntry, newEntry]; ViewerOps.PaintViewer[v, all]; newName.Close[] END }; <> whiteboardMenu: PUBLIC Menus.Menu = Menus.CreateMenu[]; ActionQueue: MBQueue.Queue = MBQueue.Create[]; SetMenu: PUBLIC PROC[v: Viewer] = { ViewerOps.SetMenu[v, whiteboardMenu]; ViewerOps.AddProp[v, $gridEntry, NIL] }; CreateMenu: PROCEDURE = BEGIN <> Menus.InsertMenuEntry[whiteboardMenu, MBQueue.CreateMenuEntry[ActionQueue, "Save", SaveProc]]; Menus.InsertMenuEntry[whiteboardMenu, MBQueue.CreateMenuEntry[ActionQueue, "Grid: 1", SetGridProc]]; Menus.InsertMenuEntry[whiteboardMenu, MBQueue.CreateMenuEntry[ActionQueue, "HELP", Instructions]]; Menus.InsertMenuEntry[whiteboardMenu, MBQueue.CreateMenuEntry[ActionQueue, "Erase", Erase]]; Menus.InsertMenuEntry[whiteboardMenu, MBQueue.CreateMenuEntry[ActionQueue, "AddCommandFile", AddCommandFile]]; Menus.InsertMenuEntry[whiteboardMenu, MBQueue.CreateMenuEntry[ActionQueue, "AddSelected", AddSelected]]; Menus.InsertMenuEntry[whiteboardMenu, MBQueue.CreateMenuEntry[q: ActionQueue, name: "Reset", proc: ResetProc, guarded: TRUE]]; Menus.InsertMenuEntry[whiteboardMenu, MBQueue.CreateMenuEntry[ActionQueue, "Freeze", Freeze]]; END; SaveProc: Menus.MenuProc = { ENABLE WhiteboardDB.WBError => { MessageWindow.Clear[]; SELECT reason FROM $ServerDown => { MessageWindow.Append["Didn't Save -- server unavailable; retry later"]; CONTINUE }; $TransactionAbort => { MessageWindow.Append["Didn't Save -- transaction aborted; retry later"]; CONTINUE }; $ReadOnly =>{ MessageWindow.Append["Can't Save -- database is readonly"]; CONTINUE }; $WrongVersion =>{ MessageWindow.Append["Can't Save -- whiteboard has wrong version"]; CONTINUE }; ENDCASE => REJECT }; v: Viewer = NARROW[parent]; KillInputFocus[v]; WhiteboardDB.Save[v] }; KillInputFocus: PROC [v: Viewer] = BEGIN focus: Viewer _ InputFocus.GetInputFocus[].owner; WHILE focus # v AND focus # NIL DO focus _ focus.parent ENDLOOP; IF focus = v THEN InputFocus.SetInputFocus[]; END; SetGridProc: Menus.MenuProc = { v: Viewer = NARROW[parent]; oldGrid: NAT = GetGrid[v]; newGrid: NAT = IF mouseButton = red THEN MIN[oldGrid*2, 32] ELSE MAX[oldGrid/2, 1]; KillInputFocus[v]; SetGrid[v, newGrid] }; ResetProc: Menus.MenuProc = { v: Viewer = NARROW[parent]; KillInputFocus[v]; WhiteboardDB.Reset[v] }; AddSelected: Menus.MenuProc = {viewer: Viewer = NARROW[parent]; WhiteboardOps.Notify[viewer, LIST[$AddSelected]] }; AddCommandFile: Menus.MenuProc = {viewer: Viewer = NARROW[parent]; WhiteboardOps.Notify[viewer, LIST[$AddCommandFile]] }; Instructions: Menus.MenuProc = {viewer: Viewer = NARROW[parent]; WhiteboardOps.Notify[viewer, LIST[$Instructions]] }; Erase: Menus.MenuProc = {viewer: Viewer = NARROW[parent]; WhiteboardOps.Notify[viewer, LIST[$Erase]] }; Freeze: Menus.MenuProc = {viewer: Viewer = NARROW[parent]; WhiteboardOps.Notify[viewer, LIST[$Freeze]] }; <> moveGrain: INTEGER _ 1; MoveChild: PUBLIC PROCEDURE[child: Viewer] = TRUSTED BEGIN parent: Viewer; dx, dy: INTEGER; grid: NAT; terminal: Terminal.Virtual ~ InterminalBackdoor.terminal; frame: Terminal.FrameBuffer ~ Terminal.GetBWFrameBuffer[terminal]; -- b/w display creek: ClassIncreek.Increek _ ClassIncreek.NewStdIncreek[]; position: ClassIncreek.ViewPosition _ creek.GetPositionFrom[]; oldPosition: Interminal.MousePosition _ position.mousePosition; actions: INTEGER _ 0; saveMap: ImagerPixelMap.PixelMap; restoreMap: ImagerPixelMap.PixelMap; oldBox: ImagerManhattan.Polygon; newBox: ImagerManhattan.Polygon; parentBox: ImagerPixelMap.DeviceRectangle; toRestore, toSave: ImagerManhattan.Polygon; screenX, screenY: INTEGER; IF child # NIL THEN parent _ child.parent ELSE RETURN; saveMap _ ImagerPixelMap.Create[0, [0, 0, frame.height, frame.width]]; TRUSTED { restoreMap _ ImagerPixelMap.CreateFrameBuffer[ pointer: frame.base, words: frame.vm.words, lgBitsPerPixel: 0, rast: frame.wordsPerLine, lines: frame.height, ref: frame] }; [screenX, screenY] _ ViewerOps.UserToScreenCoords[parent.parent, parent.cx, parent.cy]; screenY _ restoreMap.sSize - screenY - parent.ch; parentBox _ [screenY, screenX, parent.ch, parent.cw]; [screenX, screenY] _ ViewerOps.UserToScreenCoords[parent, child.wx, parent.ch - child.wy]; screenY _ restoreMap.sSize - screenY; oldBox _ LIST[[screenY, screenX, child.wh, child.ww]]; ImagerPixelMap.Fill[saveMap, oldBox.first, 0]; grid _ GetGrid[parent]; DO a: ClassIncreek.ActionBody _ creek.GetAction[acceptance: clicksAndMotion]; WITH a SELECT FROM keyUp => IF value = Red THEN EXIT; ENDCASE; <> IF (actions MOD moveGrain) = 0 THEN { dx _ position^.mousePosition.mouseX - oldPosition.mouseX; dy _ -(position^.mousePosition.mouseY - oldPosition.mouseY); IF dx = 0 AND dy = 0 THEN LOOP; IF child.wx + dx < 0 THEN EXIT; IF child.wy + dy < 0 THEN EXIT; IF child.wx + child.ww + dx > parent.cw THEN EXIT; IF child.wy + child.wh + dy > parent.ch THEN EXIT; screenX _ screenX + dx; screenY _ screenY + dy; newBox _ LIST[[screenY, screenX, child.wh, child.ww]]; toRestore _ ImagerManhattan.Difference[oldBox, newBox]; toSave _ ImagerManhattan.Difference[newBox, oldBox]; <> FOR p: LIST OF ImagerPixelMap.DeviceRectangle _ toSave, p.rest UNTIL p=NIL DO ImagerPixelMap.Transfer[saveMap, ImagerPixelMap.Clip[restoreMap, p.first]]; ENDLOOP; <> MoveViewer[child, child.wx + dx, child.wy + dy, child.ww, child.wh]; <> FOR p: LIST OF ImagerPixelMap.DeviceRectangle _ toRestore, p.rest UNTIL p=NIL DO ImagerPixelMap.Transfer[ImagerPixelMap.Clip[restoreMap, ImagerPixelMap.Intersect[p.first, parentBox]], saveMap]; ENDLOOP; oldPosition _ position^.mousePosition; oldBox _ newBox }; actions _ actions + 1; ENDLOOP; child.parent.newVersion _ TRUE; IF grid # 1 THEN ViewerOps.MoveViewer[child, child.wx-(child.wx MOD grid), child.wy-(child.wy MOD grid), child.ww, child.wh, FALSE]; ViewerOps.PaintViewer[parent, all]; -- repaint everything [] _ ClassIncreek.Release[creek]; WhiteboardDB.Move[child]; END; boxW: INTEGER = 128; boxH: INTEGER = 32; corner: {ll, lr, ul, ur}; growGrain: INTEGER _ 2; GrowBox: PUBLIC PROCEDURE[wb: Viewer, box: Viewer, x, y: INTEGER] = TRUSTED BEGIN dx, dy: INTEGER; grid: NAT = GetGrid[wb]; terminal: Terminal.Virtual ~ InterminalBackdoor.terminal; frame: Terminal.FrameBuffer ~ Terminal.GetBWFrameBuffer[terminal]; -- b/w display creek: ClassIncreek.Increek _ ClassIncreek.NewStdIncreek[]; position: ClassIncreek.ViewPosition _ creek.GetPositionFrom[]; oldPosition: Interminal.MousePosition _ position.mousePosition; actions: INTEGER _ 0; saveMap: ImagerPixelMap.PixelMap; restoreMap: ImagerPixelMap.PixelMap; oldBox: ImagerManhattan.Polygon; newBox: ImagerManhattan.Polygon; wbBox: ImagerPixelMap.DeviceRectangle; toRestore, toSave: ImagerManhattan.Polygon; screenX, screenY: INTEGER; newX, newY, newW, newH: INTEGER; IF box = NIL THEN RETURN; saveMap _ ImagerPixelMap.Create[0, [0, 0, frame.height, frame.width]]; TRUSTED { restoreMap _ ImagerPixelMap.CreateFrameBuffer[ pointer: frame.base, words: frame.vm.words, lgBitsPerPixel: 0, rast: frame.wordsPerLine, lines: frame.height, ref: frame] }; [screenX, screenY] _ ViewerOps.UserToScreenCoords[wb.parent, wb.cx, wb.cy]; screenY _ restoreMap.sSize - screenY - wb.ch; wbBox _ [screenY, screenX, wb.ch, wb.cw]; [screenX, screenY] _ ViewerOps.UserToScreenCoords[wb, box.wx, wb.ch - box.wy]; screenY _ restoreMap.sSize - screenY; oldBox _ LIST[[screenY, screenX, box.wh, box.ww]]; ImagerPixelMap.Fill[saveMap, oldBox.first, 0]; DO a: ClassIncreek.ActionBody _ creek.GetAction[acceptance: clicksAndMotion]; WITH a SELECT FROM keyUp => IF value = Blue THEN EXIT; ENDCASE; <> IF (actions MOD growGrain) = 0 THEN { dx _ position^.mousePosition.mouseX - oldPosition.mouseX; dy _ -(position^.mousePosition.mouseY - oldPosition.mouseY); IF dx = 0 AND dy = 0 THEN LOOP; <> IF (corner = ll OR corner = ul) AND box.wx + dx < 0 THEN LOOP; IF (corner = ul OR corner = ur) AND box.wy + dy < 0 THEN LOOP; IF (corner = ur OR corner = lr) AND box.wx + box.ww + dx > wb.cw THEN EXIT; IF (corner = ll OR corner = lr) AND box.wy + box.wh + dy > wb.ch THEN EXIT; <> IF (corner = lr OR corner = ur) AND box.ww + dx < boxW THEN LOOP; IF (corner = ll OR corner = lr) AND box.wh + dy < boxH THEN LOOP; IF (corner = ll OR corner = ul) AND box.ww - dx < boxW THEN LOOP; IF (corner = ul OR corner = ur) AND box.wh - dy < boxH THEN LOOP; <> IF dx = 0 AND dy = 0 THEN LOOP; SELECT corner FROM ul => { newX _ box.wx + dx; newY _ box.wy + dy; newW _ box.ww - dx; newH _ box.wh - dy; screenX _ screenX + dx; screenY _ screenY + dy }; ur => { newX _ box.wx; newY _ box.wy + dy; newW _ box.ww + dx; newH _ box.wh - dy; screenY _ screenY + dy }; ll => { newX _ box.wx + dx; newY _ box.wy; newW _ box.ww - dx; newH _ box.wh + dy; screenX _ screenX + dx }; lr => { newX _ box.wx; newY _ box.wy; newW _ box.ww + dx; newH _ box.wh + dy }; ENDCASE; newBox _ LIST[[screenY, screenX, newH, newW]]; toRestore _ ImagerManhattan.Difference[oldBox, newBox]; toSave _ ImagerManhattan.Difference[newBox, oldBox]; <> FOR p: LIST OF ImagerPixelMap.DeviceRectangle _ toSave, p.rest UNTIL p=NIL DO ImagerPixelMap.Transfer[saveMap, ImagerPixelMap.Clip[restoreMap, p.first]]; ENDLOOP; <> MoveViewer[box, newX, newY, newW, newH]; <> FOR p: LIST OF ImagerPixelMap.DeviceRectangle _ toRestore, p.rest UNTIL p=NIL DO ImagerPixelMap.Transfer[ImagerPixelMap.Clip[restoreMap, ImagerPixelMap.Intersect[p.first, wbBox]], saveMap]; ENDLOOP; oldPosition _ position^.mousePosition; oldBox _ newBox }; actions _ actions + 1; ENDLOOP; wb.newVersion _ TRUE; IF grid # 1 THEN ViewerOps.MoveViewer[box, box.wx-(box.wx MOD grid), box.wy-(box.wy MOD grid), box.ww-(box.ww MOD grid), box.wh-(box.wh MOD grid), FALSE]; ViewerOps.PaintViewer[wb, all]; [] _ ClassIncreek.Release[creek]; WhiteboardDB.Grow[box]; END; MoveViewer: PROCEDURE[self: Viewer, x, y, w, h: INTEGER] = BEGIN EraseViewer[self]; ViewerOps.MoveViewer[self, x, y, w, h, FALSE]; -- don't repaint ViewerOps.PaintViewer[self, all]; -- avoids repainting parent and sibling END; EraseViewer: PROCEDURE[self: Viewer] = BEGIN PaintItWhite: PROC[context: Imager.Context] = { Imager.SetColor[context, Imager.white]; Imager.MaskRectangleI[context, 0, 0, self.ww, self.wh] }; IF self = NIL THEN RETURN; ViewerPrivate.PaintWindow[self, PaintItWhite] END; NearestChild: PUBLIC PROCEDURE[wb: Viewer, x, y: INTEGER, type: ViewerFlavor _ NIL] RETURNS[nearest: Viewer] = BEGIN min, delta: INTEGER _ LAST[INTEGER]; IF wb.class.flavor = $WhiteboardIcon THEN { IF type = NIL THEN RETURN[wb]; IF type = $WhiteboardIcon THEN RETURN[wb]; RETURN[NIL]}; y _ wb.ch - y; FOR child: Viewer _ wb.child, child.sibling DO IF child = NIL THEN EXIT; IF type # NIL AND child.class.flavor # type THEN LOOP; IF x < child.wx THEN delta _ child.wx - x ELSE delta _ MAX[0, x - (child.wx + child.ww)]; IF y < child.wy THEN delta _ delta + child.wy - y ELSE delta _ delta + MAX[0, y - (child.wy + child.wh)]; IF delta > min THEN LOOP; min _ delta; nearest _ child; ENDLOOP; IF min > 40 THEN {nearest _ NIL; RETURN}; <> min _ delta _ LAST[INTEGER]; delta _ ABS[x - nearest.wx] + ABS[y - nearest.wy]; IF delta < min THEN {min _ delta; corner _ ul}; delta _ ABS[x - (nearest.wx + nearest.ww)] + ABS[y - nearest.wy]; IF delta < min THEN {min _ delta; corner _ ur}; delta _ ABS[x - nearest.wx] + ABS[y - (nearest.wy + nearest.wh)]; IF delta < min THEN {min _ delta; corner _ ll}; delta _ ABS[x - (nearest.wx + nearest.ww)] + ABS[y - (nearest.wy + nearest.wh)]; IF delta < min THEN {min _ delta; corner _ lr}; END; Expand: PUBLIC PROC[parent, wb: Viewer, wbList: LIST OF ROPE] = BEGIN ENABLE UNWIND => NULL; x: INTEGER = wb.wx + (wb.ww/2); y: INTEGER = wb.wy + wb.wh; child: Viewer; PaintTheLine: PROC[context: Imager.Context] = { AddLine[context, x, parent.ch - y, child.wx + (child.ww/2), parent.ch - child.wy] }; expandX: INTEGER _ 18; expandY: INTEGER _ y + 36; lines: LIST OF ViewerPair; FOR wbNames: LIST OF ROPE _ wbList, wbNames.rest UNTIL wbNames = NIL DO child _ AddIcon[parent, wbNames.first, WBicon, expandX, expandY]; child.spare1 _ TRUE; -- this is how we record that it was created by Expand!! ViewerOps.AddProp[ child, $EntityName, wbNames.first ]; ViewerOps.AddProp[ child, $IconType, $Whiteboard ]; ViewerOps.PaintViewer[viewer: child, hint: client]; ViewerPrivate.PaintWindow[parent, PaintTheLine]; lines _ NARROW[ViewerOps.FetchProp[parent, $Lines], LineList]; lines _ CONS[[wb, child], lines]; ViewerOps.AddProp[parent, $Lines, lines]; expandX _ expandX + 72; ENDLOOP; ViewerOps.PaintViewer[parent, caption] END; DontLog: PUBLIC PROC[icon: Viewer] RETURNS [BOOLEAN] = { RETURN[ icon = NIL OR icon.spare1 ] }; <> SetWBName: PUBLIC PROC[wb: Viewer, name: ROPE, paint: BOOLEAN _ TRUE] = { wb.name _ Rope.Concat["Whiteboard: ", name]; IF paint THEN ViewerOps.PaintViewer[wb, caption] }; AddLine: PROC[ctx: Imager.Context, fromX, fromY, toX, toY: INTEGER] = BEGIN littleCircle: ImagerPath.Trajectory _ ImagerPath.MoveTo[[toX+3, toY]]; littleCircle _ ImagerPath.ArcTo[littleCircle, [toX-3, toY], [toX+3, toY]]; Imager.MaskVectorI[ctx, fromX, fromY, toX, toY]; Imager.MaskFillTrajectory[ctx, littleCircle] END; <> DoDeregister: ViewerOps.EnumProc = { registration: ViewerEvents.EventRegistration = NARROW[ViewerOps.FetchProp[v, $registration]]; IF registration # NIL THEN ViewerEvents.UnRegisterEventProc[registration, edit] }; DeRegister: ViewerEvents.EventProc = TRUSTED { Process.Detach[ FORK ViewerOps.EnumerateChildren[viewer, DoDeregister] ] }; <> CreateWhiteboardClass[]; CreateIconClass[]; CreateMenu[]; [] _ ViewerEvents.RegisterEventProc[proc: DeRegister, event: destroy, filter: $Whiteboard]; END...