WhiteboardViewersImpl.mesa
Copyright © 1984, 1986 by Xerox Corporation. All rights reserved.
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
Widom, August 24, 1984 7:21:39 pm PDT
Last Edited by: Winkler, December 18, 1984 11:00:10 am PST
Donahue, December 2, 1985 11:10:23 am PST
Rick Beach, January 29, 1986 8:24:38 am PST
DIRECTORY
DB USING [],
DBIcons USING [GetIcon],
Icons USING [DrawIcon, IconFlavor, iconH, iconW],
Imager USING [Context, DoSave, MaskFillTrajectory, MaskRectangleI, MaskVectorI, SetColor, white],
ImagerBackdoor USING [invert],
ImagerManhattan USING [Difference, Polygon],
ImagerPath USING [ArcTo, MoveTo, Trajectory],
ImagerPixelMap USING [Clip, Create, CreateFrameBuffer, DeviceRectangle, Fill, Intersect, PixelMap, ShiftMap, Transfer],
InputFocus USING [CaptureButtons, GetInputFocus, ReleaseButtons, SetInputFocus],
Interminal USING [],
InterminalBackdoor USING [terminal],
IO USING [Close, int, PutF, RopeFromROS, ROS, STREAM],
MBQueue USING [Create, CreateMenuEntry, Queue],
Menus USING [CreateMenu, FindEntry, InsertMenuEntry, Menu, MenuEntry, MenuProc, ReplaceMenuEntry],
MessageWindow USING [Append, Clear],
Process USING [Detach],
Rope USING [Concat, Find, Flatten, Length, ROPE],
Terminal USING [Current, FrameBuffer, GetBWFrameBuffer, Virtual],
TIPUser USING [InstantiateNewTIPTable, TIPScreenCoords, TIPScreenCoordsRec, TIPTable],
ViewerClasses USING [NotifyProc, PaintProc, SaveProc, Viewer, ViewerClass, ViewerClassRec, ViewerFlavor, ViewerRec],
ViewerEvents USING [EventProc, EventRegistration, RegisterEventProc, UnRegisterEventProc],
ViewerLocks USING [CallUnderWriteLock],
ViewerOps USING [AddProp, CreateViewer, EnumerateChildren, EnumProc, FetchProp, FetchViewerClass, MouseInViewer, MoveViewer, PaintViewer, RegisterViewerClass, SetMenu, UserToScreenCoords],
ViewerPrivate USING [PaintWindow, selectedIcon],
ViewerTools USING [MakeNewTextViewer, SetTiogaContents, TiogaContents],
WhiteboardDB USING [EditText, Grow, Move, Reset, Save, stopped, WBError],
WhiteboardOps USING [Notify],
WhiteboardViewers USING [];
WhiteboardViewersImpl: CEDAR MONITOR
IMPORTS
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
ROPE: TYPE = Rope.ROPE;
ViewerPair: TYPE = RECORD[from, to: ViewerClasses.Viewer];
LineList: TYPE = LIST OF ViewerPair;
Painting procedures
h: INTEGER ← 620;
w: INTEGER ← 30;
wb: PUBLIC ViewerClasses.ViewerFlavor ← $Whiteboard;
the ViewerFlavor of whiteboards
whiteboardClass: ViewerClasses.ViewerClass;
icon: PUBLIC ViewerClasses.ViewerFlavor ← $WhiteboardIcon;
the ViewerFlavor of icons appearing on whiteboards
iconClass: ViewerClasses.ViewerClass;
text: PUBLIC ViewerClasses.ViewerFlavor ← $Text;
the ViewerFlavor of text boxes appearing on whiteboards
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: ViewerClasses.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: ViewerClasses.Viewer, context: Imager.Context] = {
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;
};
PaintIconic: PUBLIC ViewerClasses.PaintProc = {
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: ViewerClasses.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]
};
Strip: PROCEDURE[name: ROPE] RETURNS[ROPE] = INLINE {
pos: INT;
IF name = NIL THEN RETURN[NIL];
IF (pos ← name.Find[":"]) > 0
THEN RETURN[name.Flatten[pos+2, name.Length[]]]
ELSE RETURN[name];
};
PaintIcon: PUBLIC ViewerClasses.PaintProc = {
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 {
this icon has been inverted; keep it that way
PaintInverted: PROC ~ {
Imager.SetColor[context, ImagerBackdoor.invert];
Imager.MaskRectangleI[context, 0, 0, Icons.iconW, Icons.iconH] };
Imager.DoSave[context, PaintInverted] }
};
InvertIcon: PUBLIC PROC[icon: ViewerClasses.Viewer, paint: BOOLTRUE] = {
icon.spare0 ← NOT icon.spare0;
IF paint THEN ViewerOps.PaintViewer[icon, all];
};
WBicon: Icons.IconFlavor ← DBIcons.GetIcon["Whiteboard"];
DirtyWBicon: Icons.IconFlavor ← DBIcons.GetIcon["DirtyWhiteboard"];
Child addition procedures
(the AddTextBox and AddIcon procedures DO NOT paint the viewers they create)
AddTextBox: PUBLIC PROC[wb: ViewerClasses.Viewer, x, y, w, h: INTEGER, contents: ViewerTools.TiogaContents] RETURNS[child: ViewerClasses.Viewer] = {
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, y, w, h, FALSE] };
registration: ViewerEvents.EventRegistration;
ViewerLocks.CallUnderWriteLock[MakeChild, wb];
wb.newVersion ← TRUE;
registration ← ViewerEvents.RegisterEventProc[SetNew, edit, child];
ViewerOps.AddProp[child, $registration, registration]
};
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: ViewerClasses.Viewer, name: ROPE, icon: Icons.IconFlavor, x, y: INTEGER] RETURNS[child: ViewerClasses.Viewer] = {
add the icon
IF name = NIL THEN RETURN; -- nothing we can do here
child ← ViewerOps.CreateViewer[flavor: $WhiteboardIcon, paint: FALSE, info: [parent: wb, wx: x, wy: y, wh: 64, ww: 64, name: name, border: FALSE, scrollable: FALSE]];
ViewerOps.AddProp[child, $IconFlavor, NEW[Icons.IconFlavor ← icon]];
ViewerOps.PaintViewer[child, all];
wb.newVersion ← TRUE;
};
GetGrid: PUBLIC PROC[ v: ViewerClasses.Viewer ] RETURNS[ grid: INT ] = {
gridSize: REF INT = NARROW[ViewerOps.FetchProp[v, $gridSize]];
RETURN[IF gridSize = NIL THEN 1 ELSE gridSize^];
};
SetGrid: PUBLIC PROC[v: ViewerClasses.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: ViewerClasses.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};
{
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[]
};
};
The Menu and Operations on Whiteboards
whiteboardMenu: PUBLIC Menus.Menu = Menus.CreateMenu[];
ActionQueue: MBQueue.Queue = MBQueue.Create[];
SetMenu: PUBLIC PROC[v: ViewerClasses.Viewer] = {
ViewerOps.SetMenu[v, whiteboardMenu];
ViewerOps.AddProp[v, $gridEntry, NIL]
};
CreateMenu: PROCEDURE = {
Build the first line of the menu
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]];
};
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: ViewerClasses.Viewer = NARROW[parent];
IF WhiteboardDB.stopped THEN RETURN;
KillInputFocus[v];
WhiteboardDB.Save[v];
};
KillInputFocus: PROC [v: ViewerClasses.Viewer] = {
focus: ViewerClasses.Viewer ← InputFocus.GetInputFocus[].owner;
WHILE focus # v AND focus # NIL DO
focus ← focus.parent
ENDLOOP;
IF focus = v THEN InputFocus.SetInputFocus[];
};
SetGridProc: Menus.MenuProc = {
v: ViewerClasses.Viewer = NARROW[parent];
oldGrid: NAT = GetGrid[v];
newGrid: NAT = IF mouseButton = red THEN MIN[oldGrid*2, 32] ELSE MAX[oldGrid/2, 1];
IF WhiteboardDB.stopped THEN RETURN;
KillInputFocus[v];
SetGrid[v, newGrid];
};
ResetProc: Menus.MenuProc = {
v: ViewerClasses.Viewer = NARROW[parent];
IF WhiteboardDB.stopped THEN RETURN;
KillInputFocus[v];
WhiteboardDB.Reset[v];
};
AddSelected: Menus.MenuProc = {
viewer: ViewerClasses.Viewer = NARROW[parent];
WhiteboardOps.Notify[viewer, LIST[$AddSelected]] };
AddCommandFile: Menus.MenuProc = {
viewer: ViewerClasses.Viewer = NARROW[parent];
WhiteboardOps.Notify[viewer, LIST[$AddCommandFile]];
};
Instructions: Menus.MenuProc = {
viewer: ViewerClasses.Viewer = NARROW[parent];
WhiteboardOps.Notify[viewer, LIST[$Instructions]];
};
Erase: Menus.MenuProc = {
viewer: ViewerClasses.Viewer = NARROW[parent];
WhiteboardOps.Notify[viewer, LIST[$Erase]];
};
Freeze: Menus.MenuProc = {
viewer: ViewerClasses.Viewer = NARROW[parent];
WhiteboardOps.Notify[viewer, LIST[$Freeze]];
};
Child manipulation procedures
MoveChild: PUBLIC ENTRY PROCEDURE[child: ViewerClasses.Viewer] = TRUSTED {
IF child # NIL THEN {
parent: ViewerClasses.Viewer ← child.parent;
moverMode ← waiting;
InputFocus.CaptureButtons[MoveNotifier, boundingBoxTIP];
Process.Detach[FORK MoverProcess[parent, child]];
};
};
MoveNotifier: ENTRY ViewerClasses.NotifyProc ~ {
uses mouseX, mouseY, moverMode, and ready CONDITION as global variables
ENABLE UNWIND => {moverMode ← abort; NOTIFY readyToMove;};
Clip: PROC [position: TIPUser.TIPScreenCoords] RETURNS [x, y: INTEGER] = {
vt: Terminal.Virtual ← Terminal.Current[];
x ← MIN[MAX[position.mouseX, 0], vt.bwWidth];
y ← MIN[MAX[position.mouseY, 0], vt.bwHeight];
};
FOR list: LIST OF REF ANY ← input, list.rest UNTIL list = NIL DO
WITH list.first SELECT FROM
x: ATOM => SELECT x FROM
$Abort => BEGIN
moverMode ← abort;
NOTIFY readyToMove;
END;
$Grow, $Move => BEGIN
moverMode ← moving;
NOTIFY readyToMove;
END;
$End => BEGIN
moverMode ← done;
NOTIFY readyToMove;
END;
ENDCASE => NULL;
z: TIPUser.TIPScreenCoords => [mouseX, mouseY] ← Clip[z];
ENDCASE => ERROR;
ENDLOOP;
};
MoverState: TYPE = {waiting, moving, done, abort};
moverMode: MoverState ← done;
mouseX, mouseY: INTEGER ← 0;
readyToMove: CONDITION;
WaitForNewCoords: ENTRY PROC [] RETURNS [x, y: INTEGER, mode: MoverState] ~ {
IF moverMode = waiting THEN WAIT readyToMove;
mode ← moverMode;
moverMode ← waiting;
RETURN [mouseX, mouseY, mode];
};
MoverProcess: PROC [parent, child: ViewerClasses.Viewer] ~ {
ENABLE UNWIND => InputFocus.ReleaseButtons[];
grid: NAT ← GetGrid[parent];
mouseX, mouseY: INTEGER;
moverMode: MoverState;
parentX, parentY: INTEGER;
screenX, screenY: INTEGER;
childX: INTEGER ← child.wx;
childY: INTEGER ← child.wy;
tackX, tackY: INTEGER; -- offsets from childX, childY of corner tacked to mouse
oldBox: ImagerManhattan.Polygon;
newBox: ImagerManhattan.Polygon;
parentBox: ImagerPixelMap.DeviceRectangle;
toRestore, toSave: ImagerManhattan.Polygon;
terminal: Terminal.Virtual ~ InterminalBackdoor.terminal;
frame: Terminal.FrameBuffer ~ Terminal.GetBWFrameBuffer[terminal]; -- b/w display
restoreMap: ImagerPixelMap.PixelMap;
saveMap: ImagerPixelMap.PixelMap ← 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]; };
[parentX, parentY] ← ViewerOps.UserToScreenCoords[parent.parent, parent.wx+parent.cx, parent.wy+parent.cy];
parentY ← restoreMap.sSize - parentY - parent.ch;
parentBox ← [parentY, parentX, 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];
SELECT corner FROM
ul => { tackX ← tackY ← 0; };
ur => { tackX ← child.ww; tackY ← 0; };
lr => { tackX ← child.ww; tackY ← child.wh; };
ll => { tackX ← 0; tackY ← child.wh; };
ENDCASE;
DO
dx, dy: INTEGER;
[mouseX, mouseY, moverMode] ← WaitForNewCoords[];
IF moverMode = abort OR moverMode = done THEN EXIT;
IF NOT MouseInViewer[mouseX, mouseY, parent] THEN LOOP;
dx ← mouseX - (screenX + tackX);
dy ← (restoreMap.sSize - mouseY) - (screenY + tackY);
gridify dx relative to childX,childY coordinate system
dx ← Gridify[childX+dx, grid] - childX;
dy ← Gridify[childY+dy, grid] - childY;
IF childX + dx < 0 THEN dx ← 0;
IF childY + dy < 0 THEN dy ← 0;
IF childX + child.ww + dx > parent.cw THEN dx ← 0;
IF childY + child.wh + dy > parent.ch THEN dy ← 0;
IF dx = 0 AND dy = 0 THEN LOOP;
screenX ← screenX + dx;
screenY ← screenY + dy;
newBox ← LIST[[screenY, screenX, child.wh, child.ww]];
toRestore ← ImagerManhattan.Difference[oldBox, newBox];
toSave ← ImagerManhattan.Difference[newBox, oldBox];
save the to-be-obscured screen bits
FOR p: LIST OF ImagerPixelMap.DeviceRectangle ← toSave, p.rest UNTIL p=NIL DO
ImagerPixelMap.Transfer[saveMap, ImagerPixelMap.Clip[restoreMap, p.first]];
ENDLOOP;
move the entity
childX ← childX + dx;
childY ← childY + dy;
ImagerPixelMap.Transfer[
ImagerPixelMap.Clip[restoreMap, ImagerPixelMap.Intersect[newBox.first, parentBox]],
ImagerPixelMap.Clip[ImagerPixelMap.ShiftMap[restoreMap, dy, dx], parentBox]];
restore uncovered bits
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;
oldBox ← newBox;
ENDLOOP;
InputFocus.ReleaseButtons[];
IF moverMode = done THEN {
ViewerOps.MoveViewer[child, childX, childY, child.ww, child.wh, FALSE];
parent.newVersion ← TRUE;
WhiteboardDB.Move[child];
};
ViewerOps.PaintViewer[parent, all]; -- repaint everything
};
MouseInViewer: PROC [mouseX, mouseY: INTEGER, v: ViewerClasses.Viewer] RETURNS [BOOL] ~ {
z: ViewerClasses.Viewer;
tsc.mouseX ← mouseX;
tsc.mouseY ← mouseY;
z ← ViewerOps.MouseInViewer[tsc].viewer;
FOR viewer: ViewerClasses.Viewer ← z, viewer.parent UNTIL viewer=NIL DO
parent: ViewerClasses.Viewer ~ viewer.parent;
IF v = viewer THEN RETURN [TRUE];
ENDLOOP;
RETURN [FALSE];
};
boundingBoxTIP: TIPUser.TIPTable ←
TIPUser.InstantiateNewTIPTable["WhiteboardMoveAndGrow.tip"];
tsc: TIPUser.TIPScreenCoords ← NEW[TIPUser.TIPScreenCoordsRec];
boxW: INTEGER = 128;
boxH: INTEGER = 32;
corner: {ll, lr, ul, ur};
growGrain: INTEGER ← 2;
GrowBox: PUBLIC PROCEDURE[wb: ViewerClasses.Viewer,
box: ViewerClasses.Viewer, x, y: INTEGER] = TRUSTED {
IF wb # NIL AND box # NIL THEN {
moverMode ← waiting;
InputFocus.CaptureButtons[MoveNotifier, boundingBoxTIP];
Process.Detach[FORK GrowerProcess[wb, box, x, y]];
};
};
GrowerProcess: PUBLIC PROCEDURE[wb: ViewerClasses.Viewer,
box: ViewerClasses.Viewer, x, y: INTEGER] = {
ENABLE UNWIND => InputFocus.ReleaseButtons[];
grid: NAT ← GetGrid[wb];
terminal: Terminal.Virtual ~ InterminalBackdoor.terminal;
frame: Terminal.FrameBuffer ~ Terminal.GetBWFrameBuffer[terminal]; -- b/w display
saveMap: ImagerPixelMap.PixelMap;
restoreMap: ImagerPixelMap.PixelMap;
oldBox: ImagerManhattan.Polygon;
newBox: ImagerManhattan.Polygon;
wbBox: ImagerPixelMap.DeviceRectangle;
toRestore, toSave: ImagerManhattan.Polygon;
mouseX, mouseY: INTEGER;
moverMode: MoverState;
screenX, screenY: INTEGER;
tackX, tackY: INTEGER;
oldX, newX: INTEGER ← box.wx;
oldY, newY: INTEGER ← box.wy;
oldW, newW: INTEGER ← box.ww;
oldH, newH: INTEGER ← box.wh;
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.wx+wb.cx, wb.wy+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];
SELECT corner FROM
ul => { tackX ← tackY ← 0; };
ur => { tackX ← box.ww; tackY ← 0; };
lr => { tackX ← box.ww; tackY ← box.wh; };
ll => { tackX ← 0; tackY ← box.wh; };
ENDCASE;
DO
dx, dy: INTEGER;
[mouseX, mouseY, moverMode] ← WaitForNewCoords[];
IF moverMode = abort OR moverMode = done THEN EXIT;
IF NOT MouseInViewer[mouseX, mouseY, wb] THEN LOOP;
dx ← mouseX - (screenX + tackX);
dy ← (restoreMap.sSize - mouseY) - (screenY + tackY);
gridify dx relative to newX,newY coordinate system
dx ← Gridify[newX+dx, grid] - newX;
dy ← Gridify[newY+dy, grid] - newY;
set limits on where it can end up
IF (corner = ll OR corner = ul) AND newX + dx < 0 THEN dx ← 0;
IF (corner = ul OR corner = ur) AND newY + dy < 0 THEN dy ← 0;
IF (corner = ur OR corner = lr) AND newX + newW + dx > wb.cw THEN dx ← 0;
IF (corner = ll OR corner = lr) AND newY + newH + dy > wb.ch THEN dy ← 0;
set limits on the minimum size
IF (corner = lr OR corner = ur) AND newW + dx < boxW THEN dx ← 0;
IF (corner = ll OR corner = lr) AND newH + dy < boxH THEN dy ← 0;
IF (corner = ll OR corner = ul) AND newW - dx < boxW THEN dx ← 0;
IF (corner = ul OR corner = ur) AND newH - dy < boxH THEN dy ← 0;
IF dx = 0 AND dy = 0 THEN LOOP;
move the corner
SELECT corner FROM
ul => {
newX ← newX + dx;
newY ← newY + dy;
newW ← newW - dx;
newH ← newH - dy;
screenX ← screenX + dx;
screenY ← screenY + dy;
};
ur => {
newY ← newY + dy;
newW ← tackX ← newW + dx;
newH ← newH - dy;
screenY ← screenY + dy;
};
ll => {
newX ← newX + dx;
newW ← newW - dx;
newH ← tackY ← newH + dy;
screenX ← screenX + dx;
};
lr => {
newW← tackX ← newW + dx;
newH ← tackY ← newH + dy;
};
ENDCASE;
newBox ← LIST[[screenY, screenX, newH, newW]];
toRestore ← ImagerManhattan.Difference[oldBox, newBox];
toSave ← ImagerManhattan.Difference[newBox, oldBox];
save the to-be-obscured screen bits
FOR p: LIST OF ImagerPixelMap.DeviceRectangle ← toSave, p.rest UNTIL p=NIL DO
ImagerPixelMap.Transfer[saveMap, ImagerPixelMap.Clip[restoreMap, p.first]];
ENDLOOP;
move the box
MoveViewer[box, newX, newY, newW, newH];
restore uncovered bits
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;
oldBox ← newBox;
ENDLOOP;
InputFocus.ReleaseButtons[];
SELECT moverMode FROM
abort => ViewerOps.MoveViewer[box, oldX, oldY, oldW, oldH, FALSE];
done => {
the box is already on the grid, update the database
wb.newVersion ← TRUE;
WhiteboardDB.Grow[box];
};
ENDCASE;
ViewerOps.PaintViewer[wb, all];
};
Gridify: PROC [p: INTEGER, grid: NAT] RETURNS [INTEGER] ~ {
RETURN [(p+grid/2)/grid*grid];
};
MoveViewer: PROCEDURE[self: ViewerClasses.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: ViewerClasses.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: ViewerClasses.Viewer, x, y: INTEGER, type: ViewerClasses.ViewerFlavor ← NIL] RETURNS[nearest: ViewerClasses.Viewer] = {
min, delta: INTEGERLAST[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: ViewerClasses.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};
if you're looking for a text box, set the corner too
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};
};
Expand: PUBLIC PROC[parent, wb: ViewerClasses.Viewer, wbList: LIST OF ROPE] = {
ENABLE UNWIND => NULL;
x: INTEGER = wb.wx + (wb.ww/2);
y: INTEGER = wb.wy + wb.wh;
child: ViewerClasses.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]
};
DontLog: PUBLIC PROC[icon: ViewerClasses.Viewer] RETURNS [BOOLEAN] = {
RETURN[ icon = NIL OR icon.spare1 ];
};
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)
SetWBName: PUBLIC PROC[wb: ViewerClasses.Viewer, name: ROPE, paint: BOOLEANTRUE] = {
wb.name ← Rope.Concat["Whiteboard: ", name];
IF paint THEN ViewerOps.PaintViewer[wb, caption];
};
AddLine: PROC[ctx: Imager.Context, fromX, fromY, toX, toY: INTEGER] = {
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]
};
Event Registration
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] ];
};
Initialization
CreateWhiteboardClass[]; CreateIconClass[]; CreateMenu[];
[] ← ViewerEvents.RegisterEventProc[proc: DeRegister, event: destroy, filter: $Whiteboard];
END.