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"]; 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... ¦WhiteboardViewersImpl.mesa Copyright (C) 1984 by Xerox Corporation. All rights reserved. last edited by: John Maxwell on: September 22, 1982 12:19 pm Willie-Sue on: February 22, 1983 4:19 pm Cattell on: April 21, 1983 4:02 pm Donahue, May 22, 1985 9:16:26 am PDT Widom, August 24, 1984 7:21:39 pm PDT Last Edited by: Winkler, December 18, 1984 11:00:10 am PST Painting procedures the ViewerFlavor of whiteboards the ViewerFlavor of icons appearing on whiteboards the ViewerFlavor of text boxes appearing on whiteboards this icon has been inverted; keep it that way Child addition procedures (the AddTextBox and AddIcon procedures DO NOT paint the viewers they create) add the icon The Menu and Operations on Whiteboards Build the first line of the menu Child manipulation procedures only notice every moveGrain-th action save the to-be-obscured screen bits move the entity restore uncovered bits only notice every growGrain-th action set limits on where it can end up set limits on the minimum size move the corner save the to-be-obscured screen bits move the box restore uncovered bits if you're looking for a text box, set the corner too If this icon was added to the whiteboard as the result of an Expand operation, then DontLog will return TRUE -- the idea is that such expansions are not to be logged as permanent changes to the containing whiteboard (they reflect only the current state of affairs) Event Registration Initialization ʘJšœ™Jšœ=™=Jšœ™Jšœ,™,Jšœ(™(Jšœ"™"Jšœ$™$Jšœ%™%J™:J˜šÏk ˜ J˜ Jšœ˜Jšœœ ˜Jšœœ&˜1J˜Jšœ˜Jšœœ˜,Jšœ/˜/JšœœY˜mJ˜ J˜ J˜J˜J˜J˜J˜Jšœœ ˜Jšœœ!œ˜1J˜ J˜JšœœK˜^J˜ Jšœ œ˜'J˜ J˜Jšœ œ6˜GJ˜ Jšœ˜Jšœ˜J˜—šœœœ˜%šœ˜Jšœ¦˜¦—Jšœ˜Jšœ ˜Jš˜Jšœ˜J˜—Jšœœœ˜J˜Jšœ œœ˜,J˜Jšœ œœœ ˜$head1šœ™Jšœœ˜šœœ˜J˜—šœœ*˜4Jšœ™—J˜J˜+J˜šœœ.˜:Jšœ2™2—J˜J˜%J˜šœœ$˜0Jšœ7™7—J˜šÏnœ œ˜"š˜Jšœœ˜&J˜JJšœœ$˜9Jšœ;˜;Jšœ˜Jšœ.˜.Jšœœ˜Jšœ$˜$Jšœ*˜*Jšœ'˜'Jšœ'˜'Jšœ(˜(Jšœ˜Jšœ3˜3Jšœ˜——J˜šžœ œ˜š˜Jšœ œ$˜3Jšœ-˜-Jšœ˜Jšœ˜Jšœœ˜Jšœ/˜/Jšœ˜——J˜Jšžœ ˜J˜šžœ˜*š˜Jšœ œ0œœ˜SJ˜7Jšœ˜Jšœ˜——J˜šž œœ)˜9š˜Jšœœ˜šž œœ˜/J˜@—Jšœœ.˜Fšœœœ˜3š œœœœœ˜CJ˜-J˜5J˜)J˜Jšœ/˜/—Jšœ˜—Jšœ˜——J˜šœ œ˜-š˜šžœœ˜šœœ˜Jšœœœ#˜EJ˜>—šœ˜Jšœœœ#˜@Jšœ:˜:—J˜—šž œœ˜Jšœ0˜0JšœA˜A—šœ˜šœ+œ œ˜BJšœœœœ˜8Jšœ˜——Jšœœ2˜NJšœœ(˜?Jšœ!˜!Jšœ#žœ%˜OJšœ˜——J˜š žœ œœœœ˜-šœ˜ Jšœœ˜ Jš œœœœœ˜šœ˜šœœ%˜0Jšœœ˜——Jšœ˜—J˜—J˜šž œœ˜+š˜Jšœœ ˜Jšœ œœ)˜OJš œœ œœœ ˜DJšžœœ1˜?J˜!šœ œ˜Jšœ-™-šž œœ˜Jšœ0˜0JšœA˜A—J˜'—Jšœ˜——J˜š ž œœœœœ˜;Jšœœœœ$˜R—J˜J˜9J˜J˜C—™JšœL™LJ˜š ž œœœœ'œ˜vJš˜Jšœœ˜šž œœ˜Jšœ-œ6˜hšœ œ˜JšœGœ˜N—Jš œ!œ œ œ œœ˜e—J˜-J˜.Jšœœ˜J˜CJ˜5Jšœ˜J˜—J˜šžœ˜ šœœ˜"J˜Jš˜JšœœB˜WJ˜——J˜š žœœœœ œœ˜lš˜Jšœ ˜Jšœœ˜Jšœ ™ Jš œœœœÏc˜4šœ5œ˜<˜J˜ Jšœ œœ˜,J˜J˜ Jšœœ˜Jšœ œ˜——Jšœ&œ˜DJšœœ˜Jšœ˜J˜——J˜š žœœœœœ˜:Jšœ œœœ$˜>Jš œœ œœœ˜2J˜—J˜šžœœœœ˜.Jšœ œœœ$˜>Jš œ œœœœ˜Jšœœ%˜GJšœ˜Jšœ œœ0˜FJšœœ ˜,Jš œ œœ!œœ ˜GJšœ˜Jšœ0œ$˜VJšœ3˜3Jšœ+˜+Jšœœ˜J˜Jšœ˜—J˜šž œœœœ˜0Jšœ œœœ$˜>Jš œ œœ!œœ ˜GJšœ˜šœ œ˜Jšœ˜Jšœ!œ˜&Jšœ˜—š˜Jš œ œœœœ˜Jšœœ%˜GJšœ˜Jšœ œœ0˜FJšœœ ˜,Jšœ0œ$˜VJšœ3˜3Jšœ+˜+J˜Jšœ˜Jšœ˜———™&Jšœœ!˜7J˜J˜.J˜šžœœœ˜#J˜%Jšœ!œ˜%J˜—J˜šž œ œ˜š˜Jšœ ™ Jšœ^˜^Jšœe˜eJšœb˜bJšœ\˜\Jšœn˜nJšœh˜hJšœwœ˜~Jšœ^˜^Jšœ˜——J˜šœ˜šœ˜ Jšœ˜Jšœ˜šœ˜JšœHœ˜S—šœ˜JšœIœ˜T—šœ˜Jšœ<œ˜G—šœ˜JšœDœ˜O—Jšœ˜Jšœ˜—Jšœ œ ˜Jšœ˜Jšœ˜J˜—šžœœ˜#š˜Jšœ1˜1šœ œ œ˜"Jšœ˜Jšœ˜—Jšœ œ˜-—Jšœ˜—J˜šœ˜Jšœ œ ˜Jšœ œ˜Jš œ œœœœœœ˜SJšœ˜Jšœ˜J˜—šœ˜šœœ ˜Jšœ˜Jšœ˜—J˜—šœ˜Jšœœ'œ˜UJ˜—šœ ˜ Jšœœ'œ˜X—J˜šœ˜Jšœœ'œ˜VJ˜—šœ˜Jšœœ'œ ˜OJ˜—šœ˜Jšœœ'œ ˜P—J˜—šœ™Jšœ œ˜J˜šž œœ œ˜,Jšœ˜ J˜Jšœœ˜Jšœœ˜ J˜9JšœCŸ˜QJ˜;J˜>J˜?Jšœ œ˜J˜!J˜$J˜ J˜ J˜*J˜+Jšœœ˜Jš œ œœœœ˜6J˜FJšœ¯˜¶J˜WJ˜1J˜5J˜ZJ˜%Jšœ œ)˜6J˜.J˜šœ˜J˜Jšœœ˜Jšœ œ œœ˜"Jšœ˜ —Jšœ%™%šœ œœ˜%J˜9J˜J˜?Jšœ œ˜J˜!J˜$J˜ J˜ J˜&J˜+Jšœœ˜Jšœœ˜ Jšœœœœ˜J˜FJšœ¯˜¶J˜KJ˜-J˜)J˜NJ˜%Jšœ œ%˜2J˜.š˜J˜Jšœœ˜Jšœ œœœ˜#Jšœ˜ —Jšœ%™%šœ œœ˜%J˜9J˜Jš œœœœœ˜>Jš œœœœœ˜KJš œœœœœ˜KJšœ™Jš œœœœœ˜AJš œœœœœ˜AJš œœœœœ˜AJš œœœœœ˜AJšœ™Jšœœœœ˜Jšœ˜˜J˜J˜J˜J˜J˜J˜—˜J˜J˜J˜J˜J˜—˜J˜J˜J˜J˜J˜—˜J˜J˜J˜J˜—Jšœ˜Jšœ œ!˜.J˜7J˜6Jšœ#™#š œœœ1œœ˜MJ˜KJšœ˜—Jšœ ™ J˜(Jšœ™š œœœ4œœ˜PJ˜l—Jšœ˜J˜&J˜—J˜Jšœ˜Jšœœ˜šœ ˜šœ)œœ˜MJšœœœ˜3Jšœ˜——J˜J˜!J˜Jšœ˜——J˜šž œ œœ˜:š˜J˜Jšœ'œŸ˜?Jšœ"Ÿ'˜IJšœ˜——J˜šž œ œ˜&š˜šž œœ˜/J˜'J˜9—Jšœœœœ˜J˜-Jšœ˜——J˜š ž œœ œœœ˜TJšœ˜š˜Jšœ œœœ˜$šœ#œ˜+Jšœœœœ˜Jšœœœ˜*Jšœœ˜ —J˜šœ)˜.Jšœ œœœ˜Jš œœœœœ˜6šœ˜Jšœ˜Jšœ œ˜/—šœ ˜Jšœ˜!Jšœœ˜7—šœ œœ˜J˜ —J˜Jšœ˜—Jšœ œ œœ˜)Jšœ4™4Jšœœœ˜Jšœœœ˜2Jšœ œ˜/Jšœœ"œ˜AJšœ œ˜/Jšœœœ ˜AJšœ œ˜/Jšœœ"œ ˜PJšœ œ˜/Jšœ˜——J˜š žœœœœœœ˜?š˜Jšœœœ˜Jšœœ˜Jšœœ˜J˜šž œœ˜/J˜T—Jšœ œ˜Jšœ œ ˜Jšœœœ ˜š œ œœœœ œ˜GJšœA˜AJšœœŸ8˜MJšœ7˜7J˜3J˜3J˜0Jšœœ0˜>Jšœœ˜!Jšœ)˜)Jšœ˜Jšœ˜—J˜&Jšœ˜——J˜š žœœœœœ˜6Jšœœ œœ˜(J™ˆ—J˜š ž œœœœ œœ˜IJšœ,˜,Jšœœ&˜3—J˜šžœœ.œ˜Eš˜JšœF˜FJ˜JJšœ0˜0J˜,—Jšœ˜——™šž œ˜$šœ/œ(˜]Jšœœœ8˜RJ˜——J˜šž œ˜$Jšœœ7˜U——K™˜J˜9J˜˜[J˜—Jšœ˜—J˜—…—U‚q¸