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, March 2, 1985 2:50:36 pm PST
Widom, August 24, 1984 7:21:39 pm PDT
Last Edited by: Winkler, December 18, 1984 11:00:10 am PST
DIRECTORY
ClassIncreek,
DB,
DBIcons USING [GetIcon],
Graphics,
Icons USING [DrawIcon, IconFlavor],
IconManager USING [selectedIcon],
ImagerFrameBuffer USING [LFDisplay],
ImagerManhattan USING [Difference, Polygon],
ImagerPixelMaps USING [Clip, Create, DeviceRectangle, Intersect, Fill, PixelMap, Transfer],
InputFocus,
Interminal,
IO,
MBQueue,
Menus,
MessageWindow,
Process USING [Detach],
Rope USING [Concat, Flatten, Find, Length, ROPE],
TIPUser,
ViewerClasses USING [PaintProc, Viewer, ViewerClass, ViewerClassRec, ViewerRec, ViewerFlavor],
ViewerEvents,
ViewerLocks USING [CallUnderWriteLock],
ViewerOps,
ViewerTools USING [MakeNewTextViewer, TiogaContents, SetTiogaContents],
WhiteboardDB,
WhiteboardViewers,
WhiteboardOps;
WhiteboardViewersImpl: CEDAR PROGRAM
IMPORTS
ClassIncreek, DBIcons, Graphics, IconManager, Icons, ImagerFrameBuffer, ImagerManhattan, ImagerPixelMaps, InputFocus, IO, MBQueue, Menus, MessageWindow, Process, Rope, TIPUser, ViewerEvents, ViewerLocks, ViewerOps, 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;
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.coordSys ← top;
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;
iconClass.coordSys ← bottom;
ViewerOps.RegisterViewerClass[icon, iconClass];
END;
PaintContainer: PaintProc;
PaintArg: TYPE = RECORD[ self: ViewerClasses.Viewer, context: Graphics.Context,
whatChanged: REF ANY, clear: BOOL ];
PaintWhiteboard: ViewerClasses.PaintProc =
BEGIN
IF self.iconic THEN { PaintIconic[self, context, whatChanged, TRUE]; RETURN };
PaintWB[NEW[PaintArg ← [self, context, whatChanged, clear]]];
END;
PaintWB: PROC[ clientData: REF ANY ] = {
data: PaintArg = NARROW[clientData, REF PaintArg]^;
self: ViewerClasses.Viewer = data.self;
context: Graphics.Context = data.context;
whatChanged: REF ANY = data.whatChanged;
clear: BOOL = data.clear;
PaintContainer[self, context, whatChanged, clear];
PaintLines[self, context, whatChanged, clear] };
PaintLines: ViewerClasses.PaintProc =
BEGIN
x1, y1, x2, y2: INTEGER;
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 ← ll.first.from.wy + ll.first.from.wh;
x2 ← ll.first.to.wx + (ll.first.to.ww/2);
y2 ← ll.first.to.wy;
-- draw line
Graphics.SetCP[context, x1, y1];
Graphics.DrawTo[context, x2, y2];
-- draw arrow
Graphics.SetCP[context, x2 - 3, y2 - 3];
Graphics.DrawRope[context, "o"] };
ENDLOOP;
END;
PaintIconic: PUBLIC ViewerClasses.PaintProc =
BEGIN
mark: Graphics.Mark = Graphics.Save[context];
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"];
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]] };
IF IconManager.selectedIcon = self THEN
{ Graphics.Restore[context, mark];
[] ← Graphics.SetPaintMode[context, invert];
Graphics.DrawBox[context, [0, 0, 64, 64]];
now set the context back to it's original value
Graphics.Restore[context, mark] }
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^;
mark: Graphics.Mark = Graphics.Save[context];
paint the icon
Icons.DrawIcon[icon, context, 0, 0, name];
IF self.spare0 THEN -- this icon has been inverted; keep it that way
{ -- reset the context (which may have been changed by DrawIcon)
Graphics.Restore[context, mark];
[] ← Graphics.SetPaintMode[context, invert];
Graphics.DrawBox[context, [0, 0, 64, 64]] };
END;
InvertIcon: PUBLIC PROC[icon: 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: 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];
add the icon
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 };
The Menu and Operations on Whiteboards
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
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]];
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]] };
Child manipulation procedures
moveGrain: INTEGER ← 1;
MoveChild: PUBLIC PROCEDURE[child: Viewer] =
TRUSTED BEGIN
parent: Viewer;
dx, dy: INTEGER;
grid: NAT;
creek: ClassIncreek.Increek ← ClassIncreek.NewStdIncreek[];
position: ClassIncreek.ViewPosition ← creek.GetPositionFrom[];
oldPosition: Interminal.MousePosition ← position.mousePosition;
actions: INTEGER ← 0;
saveMap: ImagerPixelMaps.PixelMap;
restoreMap: ImagerPixelMaps.PixelMap;
oldBox: ImagerManhattan.Polygon;
newBox: ImagerManhattan.Polygon;
parentBox: ImagerPixelMaps.DeviceRectangle;
toRestore, toSave: ImagerManhattan.Polygon;
screenX, screenY: INTEGER;
IF child # NIL THEN parent ← child.parent ELSE RETURN;
saveMap ← ImagerPixelMaps.Create[0, [0, 0, 808, 1024]];
restoreMap ← ImagerFrameBuffer.LFDisplay[];
[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, child.wy];
screenY ← restoreMap.sSize - screenY;
oldBox ← LIST[[screenY, screenX, child.wh, child.ww]];
ImagerPixelMaps.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;
only notice every moveGrain-th action
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 LOOP;
IF child.wy + dy < 0 THEN LOOP;
IF child.wx + child.ww + dx > parent.ww THEN EXIT;
IF child.wy + child.wh + dy > parent.wh 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];
save the to-be-obscured screen bits
FOR p: LIST OF ImagerPixelMaps.DeviceRectangle ← toSave, p.rest UNTIL p=NIL DO
ImagerPixelMaps.Transfer[saveMap, ImagerPixelMaps.Clip[restoreMap, p.first]];
ENDLOOP;
move the entity
MoveViewer[child, child.wx + dx, child.wy + dy, child.ww, child.wh];
restore uncovered bits
FOR p: LIST OF ImagerPixelMaps.DeviceRectangle ← toRestore, p.rest UNTIL p=NIL DO
ImagerPixelMaps.Transfer[ImagerPixelMaps.Clip[restoreMap, ImagerPixelMaps.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;
min, delta: INTEGER;
grid: NAT = GetGrid[wb];
corner: {ll, lr, ul, ur} ← ll;
creek: ClassIncreek.Increek ← ClassIncreek.NewStdIncreek[];
position: ClassIncreek.ViewPosition ← creek.GetPositionFrom[];
oldPosition: Interminal.MousePosition ← position.mousePosition;
actions: INTEGER ← 0;
saveMap: ImagerPixelMaps.PixelMap;
restoreMap: ImagerPixelMaps.PixelMap;
oldBox: ImagerManhattan.Polygon;
newBox: ImagerManhattan.Polygon;
wbBox: ImagerPixelMaps.DeviceRectangle;
toRestore, toSave: ImagerManhattan.Polygon;
screenX, screenY: INTEGER;
newX, newY, newW, newH: INTEGER;
IF box = NIL THEN RETURN;
saveMap ← ImagerPixelMaps.Create[0, [0, 0, 808, 1024]];
restoreMap ← ImagerFrameBuffer.LFDisplay[];
[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, box.wy];
screenY ← restoreMap.sSize - screenY;
oldBox ← LIST[[screenY, screenX, box.wh, box.ww]];
ImagerPixelMaps.Fill[saveMap, oldBox.first, 0];
choose the nearest corner
min ← delta ← LAST[INTEGER];
delta ← ABS[x - box.wx] + ABS[y - box.wy];
IF delta < min THEN {min ← delta; corner ← ul};
delta ← ABS[x - (box.wx + box.ww)] + ABS[y - box.wy];
IF delta < min THEN {min ← delta; corner ← ur};
delta ← ABS[x - box.wx] + ABS[y - (box.wy + box.wh)];
IF delta < min THEN {min ← delta; corner ← ll};
delta ← ABS[x - (box.wx + box.ww)] + ABS[y - (box.wy + box.wh)];
IF delta < min THEN {min ← delta; corner ← lr};
DO
a: ClassIncreek.ActionBody ← creek.GetAction[acceptance: clicksAndMotion];
WITH a SELECT FROM
keyUp => IF value = Blue THEN EXIT;
ENDCASE;
only notice every growGrain-th action
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;
set limits on where it can end up
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.ww THEN EXIT;
IF (corner = ll OR corner = lr) AND box.wy + box.wh + dy > wb.wh THEN EXIT;
set limits on the minimum size
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;
move the corner
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];
save the to-be-obscured screen bits
FOR p: LIST OF ImagerPixelMaps.DeviceRectangle ← toSave, p.rest UNTIL p=NIL DO
ImagerPixelMaps.Transfer[saveMap, ImagerPixelMaps.Clip[restoreMap, p.first]];
ENDLOOP;
move the box
MoveViewer[box, newX, newY, newW, newH];
restore uncovered bits
FOR p: LIST OF ImagerPixelMaps.DeviceRectangle ← toRestore, p.rest UNTIL p=NIL DO
ImagerPixelMaps.Transfer[ImagerPixelMaps.Clip[restoreMap, ImagerPixelMaps.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
context: Graphics.Context;
IF self = NIL THEN RETURN;
context ← ViewerOps.AcquireContext[self.parent];
Graphics.SetColor[context, Graphics.white];
Graphics.DrawBox[context, [self.wx, self.wy, self.wx + self.ww, self.wy + self.wh]];
ViewerOps.ReleaseContext[context];
END;
NearestChild: PUBLIC PROCEDURE[wb: Viewer, x, y: INTEGER, type: ViewerFlavor ← NIL]
RETURNS[nearest: Viewer] =
BEGIN
min, delta: INTEGERLAST[INTEGER];
IF wb.class.flavor = $WhiteboardIcon THEN {
IF type = NIL THEN RETURN[wb];
IF type = $WhiteboardIcon THEN RETURN[wb];
RETURN[NIL]};
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};
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};
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;
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];
AddLine[parent, x, y, child.wx + (child.ww/2), child.wy];
lines ← NARROW[ViewerOps.FetchProp[parent, $Lines], LineList];
lines ← CONS[[wb, child], lines];
ViewerOps.AddProp[parent, $Lines, lines];
expandX ← expandX + 72;
ENDLOOP;
END;
DontLog: PUBLIC PROC[icon: 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: Viewer, name: ROPE, paint: BOOLEANTRUE] = {
wb.name ← Rope.Concat["Whiteboard: ", name];
IF paint THEN ViewerOps.PaintViewer[wb, caption] };
AddLine: PROC[v: Viewer, fromX, fromY, toX, toY: INTEGER] =
BEGIN
context: Graphics.Context ← ViewerOps.AcquireContext[v];
-- draw line
Graphics.SetCP[context, fromX, fromY];
Graphics.DrawTo[context, toX, toY];
-- draw arrow
Graphics.SetCP[context, toX - 3, toY - 3];
Graphics.DrawRope[context, "o"];
ViewerOps.ReleaseContext[context];
END;
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...