ViewerOpsImpl.mesa; Written by S. McGregor
Edited by McGregor on March 4, 1983 10:52 am
Edited by Maxwell, February 7, 1983 3:41 pm
Edited by Plass, May 4, 1983 11:27 am
DIRECTORY
Atom USING [GetPropFromList, PutPropOnList],
Carets USING [ResumeCarets, SuspendCarets],
ColorDisplay USING [height, width],
Cursors USING [InvertCursor],
InputFocus USING [GetInputFocus, SetInputFocus],
List USING [Sort, CompareProc],
Menus USING [CopyMenu, Menu],
MessageWindow USING [Append, Blink, Clear],
Process USING [Detach, GetPriority, Priority, priorityForeground, SetPriority],
RefTab USING [Create, Fetch, Ref, Store],
Rope USING [Compare, ROPE],
RTOS USING [GetCurrent, RegisterCedarProcess, UnregisterCedarProcess],
SafeStorage USING [NewZone],
TIPUser USING [TIPScreenCoords],
ViewerEvents USING [EventProc, ProcessEvent, RegisterEventProc],
ViewerOps,
ViewerLocks,
ViewerClasses,
ViewerSpecs,
WindowManager USING [colorDisplayOn],
WindowManagerPrivate;
ViewerOpsImpl: CEDAR PROGRAM
IMPORTS Atom, Carets, ColorDisplay, Cursors, InputFocus, List, Menus, MessageWindow, Process, RefTab, Rope, RTOS, SafeStorage, ViewerEvents, ViewerLocks, ViewerOps, WindowManager
EXPORTS ViewerOps, ViewerSpecs, WindowManagerPrivate
SHARES Menus, ViewerClasses, ViewerEvents, ViewerOps, ViewerLocks =
BEGIN OPEN ViewerClasses, ViewerSpecs, ViewerOps, WindowManager;
vZ: ZONE ← SafeStorage.NewZone[quantized];
fatalViewersError: ERROR = CODE;
openLeftWidth: PUBLIC INTEGER ← 600;
openRightLeftX: PUBLIC INTEGER ← openLeftLeftX+openLeftWidth;
openRightWidth:
PUBLIC
INTEGER ← screenW-openLeftWidth;
openBottomY: PUBLIC INTEGER ← iconHeight+iconSpacing;
classTable: RefTab.Ref ← RefTab.Create[mod:50];
-- hold viewer classes
rootViewerTree: PUBLIC ARRAY Column OF Viewer ← ALL[NIL];
RegisterViewerClass:
PUBLIC
PROC [flavor: ViewerFlavor, class: ViewerClass] = {
class.flavor ← flavor;
[] ← RefTab.Store[classTable, flavor, class]};
let the Viewer package know about a new class of viewer.
FetchViewerClass:
PUBLIC
PROC [flavor: ViewerFlavor]
RETURNS [ViewerClass] =
{IF flavor = $TypeScript THEN flavor ← $Typescript;
RETURN[NARROW[RefTab.Fetch[classTable, flavor].val]]};
class information from an existing viewer class.
CreateViewer:
PUBLIC
PROC [flavor: ViewerFlavor, info: ViewerRec ← [], paint:
BOOL ←
TRUE]
RETURNS [new: Viewer] = BEGIN
ENABLE UNWIND => NULL;
topLevel: BOOL;
defaultPosition: BOOL;
LockedCreateViewer:
PROC = {
class: ViewerClasses.ViewerClass = FetchViewerClass[flavor];
IF class=NIL THEN ERROR fatalViewersError; -- class not implemented
defaultPosition ← (new.wx=0) AND (new.wy=0) AND (new.ww=0) AND (new.wh=0);
IF new.parent=
NIL
AND ~defaultPosition
AND new.column#static
THEN
ERROR fatalViewersError; -- illegal positioning
new.class ← class;
topLevel ← (new.parent=NIL);
new.scrollable ← new.scrollable AND (class.scroll#NIL);
IF new.tipTable=NIL THEN new.tipTable ← class.tipTable;
IF new.icon=unInit THEN new.icon ← class.icon;
IF topLevel
THEN new.iconic ← (new.iconic
AND new.column#static
AND new.column#color
AND iconSlotsFree#0) -- override if illegal or too many
ELSE
BEGIN
new.iconic ← FALSE;
new.visible ← ~new.parent.iconic;
new.column ← new.parent.column;
END;
IF topLevel THEN new.border ← TRUE;
IF new.column#static
AND new.parent=
NIL
AND new.menu=
NIL
AND new.class.menu#
NIL
THEN
new.menu ← Menus.CopyMenu[new.class.menu];
IF topLevel THEN LinkViewer[new, ~new.iconic] ELSE LinkChild[new.parent, new];
IF ~defaultPosition THEN SetViewerPosition[new, new.wx, new.wy, new.ww, new.wh];
IF new.class.init#NIL THEN new.class.init[new];
IF topLevel THEN IF new.iconic THEN PositionIcon[new]
ELSE IF defaultPosition THEN InternalComputeColumn[new.column];
new.init ← TRUE};
new ← vZ.NEW[ViewerRec ← info];
IF ViewerEvents.ProcessEvent[create, new, TRUE] THEN RETURN[NIL]; -- Gulp!!!
IF ViewerLocks.ColumnWedged[ViewerColumn[new]].wedged
THEN {
not locked, but these are not ordinary times!
column: Column ← UnWedgedColumn[];
IF column # static THEN new.column ← column};
IF new.column = static THEN { -- create a viewer in the middle of the screen
new.iconic ← FALSE;
new.wx ← 1; -- prevent call to InternalComputeColumn
LockedCreateViewer[];
new.column ← left; -- fool SetViewerPosition into leaving room for menu
SetViewerPosition[new, 300, 100, 500, 500];
new.column ← static;
ViewerOps.PaintViewer[new, all];
RETURN}};
IF new.parent=
NIL
THEN
IF new.iconic
THEN ViewerLocks.CallUnderColumnLock[LockedCreateViewer, static]
ELSE ViewerLocks.CallUnderColumnLock[LockedCreateViewer, new.column]
ELSE ViewerLocks.CallUnderWriteLock[LockedCreateViewer, new.parent];
IF paint
THEN
IF topLevel
AND defaultPosition
AND ~new.iconic
THEN InternalPaintColumn[new.column]
ELSE PaintViewer[new, all];
[] ← ViewerEvents.ProcessEvent[create, new, FALSE];
END;
DestroyViewer:
PUBLIC
PROC [viewer: Viewer, paint:
BOOL ←
TRUE] =
BEGIN
ENABLE UNWIND => NULL;
IF viewer.destroyed THEN RETURN; -- already destroyed ; nop
IF viewer=NIL THEN ERROR fatalViewersError;
InternalDestroyViewer[viewer, paint, FALSE];
END;
InternalDestroyViewer:
PROC [viewer: Viewer, paint, replace:
BOOL] =
BEGIN
paintParent, paintColumn: BOOL ← FALSE;
parent, filler: Viewer;
x,y,w,h: INTEGER;
fillerX, fillerY: INTEGER;
onlyLink: Viewer; -- set only if the only link
LockedDestroyViewer:
PROC = {
parent ← viewer.parent; -- copy parent; smashed below
x ← viewer.wx; y ← viewer.wy; w ← viewer.ww; h ← viewer.wh;
KillInputFocus[viewer];
BottomUpRecursivelyDestroyViewerAndChildren[viewer];
IF viewer.link#
NIL
THEN
BEGIN
IF viewer.link.link=viewer
THEN
BEGIN
viewer.link.link ← NIL;
IF viewer.link.parent=NIL THEN onlyLink ← viewer.link;
END
ELSE
FOR v: Viewer ← viewer.link.link, v.link
UNTIL v.link=viewer
DO
REPEAT FINISHED => v.link ← viewer.link;
ENDLOOP;
viewer.link ← NIL;
END;
IF parent#
NIL
THEN
BEGIN
IF parent.child=viewer THEN parent.child ← viewer.sibling
ELSE
IF viewer.sibling#
NIL
OR parent#viewer
THEN
FOR test: Viewer ← parent.child, test.sibling
UNTIL test=
NIL
DO
IF test.sibling#NIL AND test.sibling=viewer THEN test.sibling ← test.sibling.sibling;
ENDLOOP;
viewer.sibling ← NIL;
paintParent ← paint;
END
ELSE
IF viewer.iconic
THEN
BEGIN
GreyScreen[x, y, w, h, FALSE, TRUE]; -- erase the icon
IF replace THEN RETURN; -- used by ReplaceViewer to prevent the slot from being reused
[filler, fillerX, fillerY] ← RemoveIcon[icon: viewer, fillSlot: TRUE];
IF filler # NIL THEN GreyScreen[fillerX, fillerY, iconWidth, iconHeight, FALSE, TRUE];
END
ELSE {InternalComputeColumn[viewer.column]; paintColumn ← paint};
};
IF ViewerEvents.ProcessEvent[destroy, viewer, TRUE] THEN RETURN;
KillInputFocus[viewer];
ViewerLocks.CallUnderWriteLock[LockedDestroyViewer, viewer];
IF paintParent THEN PaintViewer[parent, all];
IF paintColumn THEN InternalPaintColumn[viewer.column];
the following are always painted because the caller isn't likely to know to repaint them
IF filler # NIL THEN ViewerOps.PaintViewer[filler, all];
IF onlyLink#
NIL
AND (onlyLink.iconic#viewer.iconic
OR onlyLink.column#viewer.column)
THEN PaintViewer[onlyLink, caption];
[] ← ViewerEvents.ProcessEvent[destroy, viewer, FALSE];
END;
BottomUpRecursivelyDestroyViewerAndChildren:
PROC [viewer: Viewer] =
BEGIN
LockedBottomUpEtc:
PROC =
BEGIN
child, next: Viewer;
viewer.visible ← FALSE;
viewer.destroyed ← TRUE;
IF viewer.class.destroy#NIL THEN viewer.class.destroy[viewer];
child ← viewer.child;
WHILE child #
NIL
DO
[] ← ViewerEvents.ProcessEvent[destroy, child, TRUE]; -- what would abort mean?
BottomUpRecursivelyDestroyViewerAndChildren[child];
[] ← ViewerEvents.ProcessEvent[destroy, child, FALSE];
next ← child.sibling;
child.sibling ← NIL;
child ← next;
ENDLOOP;
IF viewer.parent=NIL THEN UnlinkViewer[viewer];
help out the garbage collector (.sibling handled by caller and above)
viewer.parent ← NIL; viewer.name ← NIL; viewer.file ← NIL;
viewer.menu ← NIL; viewer.props ← NIL; viewer.child ← NIL;
viewer.data ← NIL;
END;
ViewerLocks.CallUnderWriteLock[LockedBottomUpEtc, viewer];
END;
EnumerateViewers:
PUBLIC
PROC [enum: EnumProc] =
BEGIN
Safe in that caller can move the enumerated viewer in the tree in which case all viewers get seen at least once
FOR c: Column
DECREASING
IN Column
DO
-- decreasing so will try static viewers last
v: Viewer ← rootViewerTree[c];
next: Viewer;
UNTIL v=
NIL
DO
next ← v.sibling;
IF ~enum[v] THEN RETURN;
v ← next;
ENDLOOP;
ENDLOOP;
END;
EnumerateChildren:
PUBLIC
PROC [viewer: Viewer, enum: EnumProc] =
BEGIN
v: Viewer ← viewer.child;
next: Viewer;
WHILE v#
NIL
DO
next ← v.sibling;
EnumerateChildren[v, enum];
IF ~enum[v] THEN RETURN;
v ← next;
ENDLOOP;
END;
CountViewers:
PROC [c: Column]
RETURNS [count:
INTEGER] =
BEGIN
count ← 0;
FOR v: Viewer ← rootViewerTree[c], v.sibling
UNTIL v=
NIL
DO
count ← count+1;
ENDLOOP;
END;
FindViewer:
PUBLIC
PROC [name: Rope.
ROPE]
RETURNS [viewer: Viewer] =
BEGIN
MatchName: EnumProc =
BEGIN
IF Rope.Compare[name, v.name,
FALSE]=equal
THEN
BEGIN
viewer ← v;
RETURN[FALSE];
END
ELSE RETURN[TRUE];
END;
EnumerateViewers[MatchName];
END;
SaveViewer:
PUBLIC
PROC [viewer: Viewer] =
BEGIN
list: LIST OF Viewer;
LockedStart:
PROC = {
FOR v: Viewer ← viewer, v.link
DO
v.saveInProgress ← TRUE;
PaintViewer[v, caption, FALSE];
list ← CONS[v, list];
IF v.link = NIL OR v.link = viewer THEN EXIT;
ENDLOOP};
LockedStop:
PROC = {
FOR saved:
LIST
OF Viewer ← list, saved.rest
UNTIL saved =
NIL
DO
v: Viewer = saved.first;
IF v.destroyed THEN LOOP;
v.newVersion ← v.newFile ← v.saveInProgress ← FALSE;
PaintViewer[v, caption, FALSE];
[] ← ViewerEvents.ProcessEvent[save, v, FALSE];
ENDLOOP};
FOR v: Viewer ← viewer, v.link
DO
IF ViewerEvents.ProcessEvent[save, v, TRUE].abort THEN RETURN;
IF v.link = NIL OR v.link = viewer THEN EXIT;
ENDLOOP;
Not called under locks so viewers can be saved when the column is locked.
LockedStart[];
IF viewer.class.save#NIL THEN viewer.class.save[viewer];
LockedStop[];
END;
RestoreViewer:
PUBLIC
PROC [viewer: Viewer] =
BEGIN
DoOne:
PROC [v: Viewer] =
INLINE
BEGIN
KillInputFocus[v];
IF v.class.init # NIL THEN v.class.init[v];
v.newVersion ← v.newFile ← FALSE;
PaintViewer[v, all];
END;
DoOne[viewer];
IF viewer.link#
NIL
THEN
FOR v: Viewer ← viewer.link, v.link UNTIL v=viewer DO DoOne[v]; ENDLOOP;
END;
SetMenu:
PUBLIC
PROC [viewer: Viewer, menu: Menus.Menu ←
NIL, paint:
BOOL ←
TRUE] =
BEGIN
sameSize:
BOOL = (menu#
NIL
AND viewer.menu#
NIL
AND
viewer.menu.linesUsed=menu.linesUsed);
IF viewer.iconic THEN paint ← FALSE;
IF viewer.parent#NIL THEN ERROR;
viewer.menu ← IF menu=NIL THEN NIL ELSE Menus.CopyMenu[menu];
IF ~sameSize
AND paint
THEN
EstablishViewerPosition[viewer, viewer.wx, viewer.wy, viewer.ww, viewer.wh];
IF paint THEN PaintViewer[viewer, IF sameSize THEN menu ELSE all];
END;
IndicateNewVersion:
PUBLIC
PROC [viewer: Viewer] =
BEGIN
link1, link2: Viewer;
LockedNewVersion:
PROC = {
FOR v: Viewer ← viewer, v.link
DO
v.newVersion ← TRUE;
PaintViewer[v, caption];
[] ← ViewerEvents.ProcessEvent[edit, v, FALSE];
IF v.link = NIL OR v.link = viewer THEN EXIT;
ENDLOOP};
FOR v: Viewer ← viewer, v.link
DO
IF ViewerEvents.ProcessEvent[edit, v, TRUE].abort THEN RETURN;
IF v.link = NIL OR v.link = viewer THEN EXIT;
ENDLOOP;
IF viewer # NIL THEN link1 ← viewer.link;
IF link1 # NIL THEN link2 ← link1.link;
ViewerLocks.CallUnderReadLocks[LockedNewVersion, viewer, link1, link2];
END;
SetNewFile:
PUBLIC
PROC [viewer: Viewer] =
BEGIN
DoOne:
PROC [v: Viewer] =
BEGIN
v.newFile ← TRUE;
PaintViewer[v, caption];
END;
DoOne[viewer];
IF viewer.link#
NIL
THEN
FOR v: Viewer ← viewer.link, v.link UNTIL v=viewer DO DoOne[v]; ENDLOOP;
END;
TopViewer:
PUBLIC
PROC [viewer: Viewer, paint:
BOOL ←
TRUE] =
BEGIN
ENABLE UNWIND => NULL;
LockedTopViewer:
PROC = {
noop: BOOL ← FALSE;
myTopY: INTEGER ~ viewer.wy+viewer.wh;
IF viewer.parent#NIL THEN RETURN WITH ERROR fatalViewersError; -- top level viewers
SELECT viewer.column
FROM
left => IF myTopY = openLeftTopY THEN noop ← TRUE;
right => IF myTopY = openRightTopY THEN noop ← TRUE;
color => IF myTopY = LOOPHOLE[ColorDisplay.height, INTEGER] THEN noop ← TRUE;
ENDCASE => RETURN WITH ERROR fatalViewersError; -- not a sequenced column
IF noop THEN {paint ← FALSE; RETURN};
UnlinkViewer[viewer];
LinkViewer[viewer, FALSE];
InternalComputeColumn[viewer.column]};
IF viewer.offDeskTop THEN ChangeColumn[viewer, left];
ViewerLocks.CallUnderColumnLock[LockedTopViewer, viewer.column];
IF paint THEN InternalPaintColumn[viewer.column];
END;
BottomViewer:
PUBLIC
PROC [viewer: Viewer, paint:
BOOL ←
TRUE] =
BEGIN
ENABLE UNWIND => NULL;
LockedBottomViewer:
PROC = {
noop: BOOL ← FALSE;
IF viewer.parent#NIL THEN RETURN WITH ERROR fatalViewersError; -- top level viewers
SELECT viewer.column
FROM
left, right => IF viewer.wy = openBottomY THEN noop ← TRUE;
color => IF viewer.wy = 0 THEN noop ← TRUE;
ENDCASE => RETURN WITH ERROR fatalViewersError; -- not a sequenced column
IF noop THEN {paint ← FALSE; RETURN};
UnlinkViewer[viewer];
LinkViewer[viewer, TRUE];
InternalComputeColumn[viewer.column]};
IF viewer.offDeskTop THEN ChangeColumn[viewer, left];
ViewerLocks.CallUnderColumnLock[LockedBottomViewer, viewer.column];
IF paint THEN InternalPaintColumn[viewer.column];
END;
MoveAboveViewer:
PUBLIC
PROC [altered, static: Viewer, paint:
BOOL ←
TRUE] =
BEGIN
LockedMoveAboveViewer:
PROC =
BEGIN
IF altered.parent#NIL OR static.parent#NIL THEN {paint ← FALSE; RETURN};
IF altered.column#static.column OR static.iconic THEN {paint ← FALSE; RETURN};
UnlinkViewer[altered];
altered.sibling ← static.sibling;
static.sibling ← altered;
InternalComputeColumn[altered.column];
END;
IF static.offDeskTop THEN ChangeColumn[static, left];
IF altered.offDeskTop THEN ChangeColumn[altered, left];
ViewerLocks.CallUnderColumnLock[LockedMoveAboveViewer, altered.column];
IF paint THEN InternalPaintColumn[altered.column];
END;
MoveBelowViewer:
PUBLIC
PROC [altered, static: Viewer, paint:
BOOL ←
TRUE] =
BEGIN
LockedMoveBelowViewer:
PROC =
BEGIN
IF altered.parent#NIL OR static.parent#NIL THEN {paint ← FALSE; RETURN};
IF altered.column#static.column OR static.iconic THEN {paint ← FALSE; RETURN};
UnlinkViewer[altered];
IF static=rootViewerTree[altered.column]
THEN
BEGIN
altered.sibling ← static;
rootViewerTree[altered.column] ← altered;
END
ELSE
BEGIN
inFront: Viewer;
FOR v: Viewer ← rootViewerTree[altered.column], v.sibling
UNTIL v.sibling=static
DO
REPEAT FINISHED => inFront ← v;
ENDLOOP;
altered.sibling ← static;
inFront.sibling ← altered;
END;
InternalComputeColumn[altered.column];
END;
IF static.offDeskTop THEN ChangeColumn[static, left];
IF altered.offDeskTop THEN ChangeColumn[altered, left];
ViewerLocks.CallUnderColumnLock[LockedMoveBelowViewer, altered.column];
IF paint THEN InternalPaintColumn[altered.column];
END;
ReplaceViewer:
PUBLIC
PROC [new, old: Viewer] =
BEGIN
LockedReplaceViewer:
PROC = {
IF old.parent#
NIL
OR new.parent#
NIL
OR new.column#old.column
THEN
RETURN WITH ERROR fatalViewersError; -- must be same level and column
KillInputFocus[old];
UnlinkViewer[new];
new.sibling ← old.sibling;
old.sibling ← new;
new.iconic ← old.iconic;
new.position ← old.position;
new.openHeight ← old.openHeight;
new.column ← old.column;
new.offDeskTop ← old.offDeskTop;
new.visible ← old.visible;
SetViewerPosition[new, old.wx, old.wy, old.ww, old.wh];
InternalDestroyViewer[old, FALSE, TRUE]; -- doesn't free the icon slot
};
KillInputFocus[old];
IF old.offDeskTop THEN ChangeColumn[old, left];
IF new.offDeskTop THEN ChangeColumn[new, left];
ViewerLocks.CallUnderColumnLocks[LockedReplaceViewer,
ViewerColumn[new], ViewerColumn[old]];
PaintViewer[new, all];
END;
OpenIcon:
PUBLIC PROC [icon: Viewer, closeOthers:
BOOL ←
FALSE, bottom:
BOOL ←
TRUE,
paint: BOOL ← TRUE] = BEGIN
filler: Viewer;
fillerX, fillerY: INTEGER;
LockedOpenIcon:
PROC =
BEGIN
ENABLE UNWIND => NULL;
SetOpen: EnumProc = {v.visible ← TRUE};
IF ~icon.iconic THEN {paint ← closeOthers ← FALSE; RETURN};
KillInputFocus[icon];
ResetPaintCache[];
closeOthers ← (closeOthers AND (CountViewers[icon.column]#0));
UnlinkViewer[icon];
icon.iconic ← FALSE;
LinkViewer[icon, bottom];
EnumerateChildren[icon, SetOpen];
[filler, fillerX, fillerY] ← RemoveIcon[icon: icon, fillSlot: ~closeOthers];
icon.position ← 0;
icon.ww ← icon.wh ← 0; -- so won't be clipped out in GreyScreen below
GreyScreen[icon.wx, icon.wy, iconWidth, iconHeight, FALSE, TRUE];
IF filler # NIL THEN GreyScreen[fillerX, fillerY, iconWidth, iconHeight, FALSE, TRUE];
InternalComputeColumn[icon.column];
IF paint THEN MessageWindow.Clear[]; -- flush old messages
END;
IF ViewerEvents.ProcessEvent[open, icon, TRUE].abort THEN RETURN;
IF ViewerLocks.ColumnWedged[icon.column].wedged
THEN {
not locked, but these are not ordinary times!
column: Column ← UnWedgedColumn[];
IF column # static THEN icon.column ← column};
IF icon.column = static THEN { -- create a window in the middle of the screen
LockedOpenIcon[];
icon.column ← left; -- to fool SetViewerPosition into leaving room for menu
SetViewerPosition[icon, 300, 100, 500, 500];
icon.column ← static;
ViewerOps.PaintViewer[icon, all];
RETURN}};
KillInputFocus[icon];
IF icon.offDeskTop THEN ChangeColumn[icon, left];
ViewerLocks.CallUnderColumnLocks[LockedOpenIcon, icon.column, static];
IF filler # NIL THEN ViewerOps.PaintViewer[filler, all];
IF closeOthers THEN GrowViewer[icon, paint]
ELSE IF paint THEN InternalPaintColumn[icon.column];
[] ← ViewerEvents.ProcessEvent[open, icon, FALSE];
END;
CloseViewer:
PUBLIC
PROC [viewer: Viewer, paint:
BOOL ←
TRUE] =
BEGIN
ENABLE UNWIND => NULL;
LockedCloseViewer:
PROC = {
SetClosed: EnumProc = {v.visible ← FALSE};
IF viewer.iconic OR viewer.column=static THEN RETURN;
IF iconSlotsFree=0
THEN
BEGIN
MessageWindow.Append["No space for another icon! Try deleting some viewers.", TRUE];
MessageWindow.Blink;
RETURN;
END;
KillInputFocus[viewer];
ResetPaintCache[];
UnlinkViewer[viewer];
viewer.iconic ← TRUE;
LinkViewer[viewer];
EnumerateChildren[viewer, SetClosed];
PositionIcon[viewer];
InternalComputeColumn[viewer.column];
};
IF ViewerEvents.ProcessEvent[close, viewer, TRUE].abort THEN RETURN;
KillInputFocus[viewer];
IF viewer.offDeskTop THEN ChangeColumn[viewer, left];
ViewerLocks.CallUnderColumnLocks[LockedCloseViewer, viewer.column, static];
IF paint
THEN {
InternalPaintColumn[viewer.column];
PaintViewer[viewer, all]};
[] ← ViewerEvents.ProcessEvent[close, viewer, FALSE];
END;
GrowViewer:
PUBLIC
PROC [viewer: Viewer, paint:
BOOL ←
TRUE] =
BEGIN
inThisColumn: LIST OF Viewer;
LockedGrowViewer:
PROC = {
FOR v: Viewer ← rootViewerTree[viewer.column], v.sibling
WHILE v #
NIL
DO
IF v # viewer THEN inThisColumn ← CONS[v, inThisColumn];
ENDLOOP};
IF viewer.iconic THEN {ViewerOps.OpenIcon[viewer, TRUE, paint]; RETURN};
IF ViewerEvents.ProcessEvent[grow, viewer, TRUE].abort THEN RETURN;
IF viewer.offDeskTop THEN ChangeColumn[viewer, left];
ViewerLocks.CallUnderColumnLocks[LockedGrowViewer, viewer.column, static];
FOR v:
LIST
OF Viewer ← inThisColumn, v.rest
UNTIL v =
NIL
DO
ViewerOps.CloseViewer[v.first, FALSE]; -- outside the lock because of KillInputFocus
ENDLOOP;
IF paint
AND inThisColumn #
NIL
THEN {
FOR v:
LIST
OF Viewer ← inThisColumn, v.rest
UNTIL v =
NIL
DO
ViewerOps.PaintViewer[v.first, all];
ENDLOOP;
ViewerOps.PaintViewer[viewer, all]};
[] ← ViewerEvents.ProcessEvent[grow, viewer, FALSE];
END;
GrowExtra: ViewerEvents.EventProc =
BEGIN
-- [viewer: Viewer, event: ViewerEvent, before: BOOL]
LockedGrowExtra:
PROC = {
closedViewers: LIST OF Viewer; -- closed with the last grow
inThisColumn: LIST OF Viewer; -- viewers currently in the column
compare: List.CompareProc = {
RETURN[
IF
NARROW[ref1, Viewer].wy >
NARROW[ref2, Viewer].wy THEN less ELSE greater]};
ListColumn: ViewerOps.EnumProc = {
IF ~v.iconic
AND v.column=viewer.column
THEN inThisColumn ← CONS[v, inThisColumn]};
IF before
THEN {
-- cons up a list of viewers currently in the column
ViewerOps.EnumerateViewers[ListColumn];
TRUSTED {inThisColumn ← LOOPHOLE[List.Sort[LOOPHOLE[inThisColumn], compare]]};
ViewerOps.AddProp[viewer, $InThisColumn, inThisColumn];
IF event = grow THEN RETURN};
closedViewers ← NARROW[ViewerOps.FetchProp[viewer, $ClosedViewers]];
inThisColumn ← NARROW[ViewerOps.FetchProp[viewer, $InThisColumn]];
ViewerOps.AddProp[viewer, $ClosedViewers, inThisColumn];
ViewerOps.AddProp[viewer, $InThisColumn, NIL];
IF inThisColumn = NIL OR inThisColumn.rest # NIL THEN RETURN; -- first grow
FOR v:
LIST
OF Viewer ← closedViewers, v.rest
WHILE v #
NIL
DO
IF v.first.destroyed THEN NULL
ELSE IF v.first = viewer THEN ViewerOps.BottomViewer[viewer: viewer, paint: FALSE]
ELSE
IF v.first.iconic
AND v.first.column = viewer.column
THEN ViewerOps.OpenIcon[icon: v.first, paint: FALSE];
ENDLOOP;
ViewerOps.ComputeColumn[viewer.column]};
IF event = destroy AND viewer.parent # NIL THEN RETURN;
IF event = destroy AND (viewer.iconic OR viewer.column = static) THEN RETURN;
ViewerLocks.CallUnderColumnLocks[LockedGrowExtra, viewer.column, static];
END;
SwapIconAndViewer:
PUBLIC
PROC [icon, openViewer: Viewer, paint:
BOOL ←
TRUE] =
BEGIN -- this isn't locked at all!
IF ~icon.iconic OR openViewer.iconic THEN RETURN WITH ERROR fatalViewersError;
KillInputFocus[icon];
KillInputFocus[openViewer];
IF icon.column#openViewer.column THEN ChangeColumn[icon, openViewer.column];
OpenIcon[icon: icon, paint: FALSE];
MoveAboveViewer[icon, openViewer, FALSE];
CloseViewer[openViewer, FALSE];
ComputeColumn[icon.column];
END;
MoveBoundary:
PUBLIC
PROC [newLeftWidth:
INTEGER ← openLeftWidth,
newBottomY: INTEGER ← openBottomY] = BEGIN
ENABLE UNWIND => NULL;
LockedMoveBoundary:
PROC = {
ResetPaintCache[];
openLeftWidth ← newLeftWidth;
openRightLeftX ← openLeftLeftX+openLeftWidth;
openRightWidth ← screenW-openLeftWidth;
openBottomY ← newBottomY;
GreyScreen[0, 0, screenW, openLeftTopY, FALSE, FALSE];
InternalComputeColumn[left];
InternalComputeColumn[right];
};
Carets.SuspendCarets[];
ViewerLocks.CallUnderViewerTreeLock[LockedMoveBoundary];
InternalPaintColumn[left];
InternalPaintColumn[right];
FOR v: Viewer ← rootViewerTree[static], v.sibling
UNTIL v=
NIL
DO
IF v.iconic THEN TRUSTED {Process.Detach[FORK PaintViewer[v, all]]};
ENDLOOP;
WaitForPaintingToFinish[];
Carets.ResumeCarets[];
END;
ChangeColumn:
PUBLIC
PROC [viewer: Viewer, newColumn: Column] =
BEGIN
ENABLE UNWIND => Carets.ResumeCarets[];
oldColumn: Column;
paintIcon: BOOL ← FALSE;
paintColumns: BOOL ← FALSE;
LockedChangeColumn:
PROC = {
SetColumn: EnumProc = {v.column ← viewer.column} ;
oldColumn ← viewer.column;
IF oldColumn = newColumn THEN RETURN;
iconic case
IF viewer.iconic
THEN
BEGIN
IF oldColumn # static
THEN {
[] ← RemoveIcon[viewer];
GreyScreen[viewer.wx, viewer.wy, viewer.ww, viewer.wh,
viewer.column=color AND ~viewer.iconic, viewer.iconic]};
IF oldColumn # static
AND newColumn # static
-- not offDeskTop
THEN newColumn ← IF viewer.column=right THEN left ELSE right;
viewer.column ← newColumn;
IF newColumn # static
THEN {
PositionIcon[viewer];
paintIcon ← TRUE};
END;
non iconic case
IF ~viewer.iconic
THEN BEGIN
UnlinkViewer[viewer];
viewer.column ← newColumn;
LinkViewer[viewer];
InternalComputeColumn[oldColumn];
InternalComputeColumn[newColumn];
paintColumns ← TRUE;
END;
hyperspace
IF oldColumn = static THEN viewer.offDeskTop ← FALSE;
IF newColumn = static
THEN {
SetViewerPosition[viewer, 2000, 2000, 10, 10];
viewer.offDeskTop ← TRUE};
EnumerateChildren[viewer, SetColumn];
};
IF ViewerEvents.ProcessEvent[changeColumn, viewer, TRUE].abort THEN RETURN;
IF ViewerLocks.ColumnWedged[newColumn].wedged
THEN {
newColumn ← UnWedgedColumn[];
IF newColumn = static THEN RETURN};
ViewerLocks.CallUnderColumnLocks[LockedChangeColumn,
ViewerColumn[viewer], newColumn];
Carets.SuspendCarets[];
ResetPaintCache[];
IF paintIcon THEN PaintViewer[viewer, all];
IF paintColumns
THEN
BEGIN
IF oldColumn # static THEN InternalPaintColumn[oldColumn];
IF newColumn # static THEN InternalPaintColumn[newColumn];
END;
Carets.ResumeCarets[];
[] ← ViewerEvents.ProcessEvent[changeColumn, viewer, FALSE];
END;
ComputeColumn:
PUBLIC
PROC [column: Column, paint:
BOOL ←
TRUE] =
BEGIN
ENABLE UNWIND => NULL;
LockedComputeColumn: PROC = {InternalComputeColumn[column]};
ViewerLocks.CallUnderColumnLock[LockedComputeColumn, column];
IF paint THEN InternalPaintColumn[column];
END;
InternalComputeColumn:
PROC [column: Column] =
BEGIN
monitored access to window chain
force: BOOL ← FALSE;
largestViewer: Viewer;
totalRequested, requests: INTEGER ← 0;
minHeight: INTEGER ~ captionHeight;
leftX, width, bottomY, nextY: INTEGER;
openViewers: INTEGER = CountViewers[column];
normalHeight, extra, default, totalSpace: INTEGER ← 0;
DesiredHeight:
PROC [v: Viewer]
RETURNS [
INTEGER] =
INLINE
{RETURN[--IF v.position#0 THEN v.position ELSE-- v.openHeight]};
AddUpRequests:
PROC [cutoff:
INTEGER] = {
largestHeight: INTEGER ← 0;
totalRequested ← requests ← 0;
FOR v: Viewer ← rootViewerTree[column], v.sibling
UNTIL v=
NIL
DO
IF DesiredHeight[v] > largestHeight
THEN {
largestHeight ← DesiredHeight[v]; largestViewer ← v};
IF DesiredHeight[v]#0
AND DesiredHeight[v] <= cutoff
THEN
BEGIN
totalRequested ← totalRequested + DesiredHeight[v];
requests ← requests + 1;
END;
ENDLOOP};
ResetPaintCache[]; -- clients are assuming that this will happen
IF column=static THEN RETURN;
IF openViewers=0 THEN RETURN;
If there are no requests, divide the column evenly among the viewers.
If total requested > column height, ignore requests for more than average height.
If total requested < column height AND requests = viewers, put extra on largest viewer.
[totalSpace, leftX, width, bottomY, nextY] ← ColumnInfo[column];
normalHeight ← totalSpace/openViewers;
AddUpRequests[totalSpace];
IF totalRequested+((openViewers-requests)*minHeight) > totalSpace
THEN {
force ← TRUE; AddUpRequests[normalHeight]};
IF openViewers=requests
THEN extra ← totalSpace-totalRequested
ELSE {default ← (totalSpace-totalRequested)/(openViewers-requests);
extra ← (totalSpace-totalRequested) MOD (openViewers-requests)};
FOR v: Viewer ← rootViewerTree[column], v.sibling
UNTIL v=
NIL
DO
height:
INTEGER ←
SELECT
TRUE
FROM
DesiredHeight[v]=0 => default,
force AND DesiredHeight[v] > normalHeight => default,
ENDCASE => DesiredHeight[v];
IF openViewers = requests
AND largestViewer #
NIL
THEN {IF v = largestViewer THEN height ← height + extra}
ELSE {IF nextY=bottomY THEN height ← height + extra};
SetViewerPosition[v, leftX, nextY, width, height];
nextY ← nextY + v.wh;
ENDLOOP;
END;
InternalPaintColumn:
PROC [column: Column] =
BEGIN
totalSpace, leftX, width, nextY: INTEGER;
Carets.SuspendCarets[];
IF rootViewerTree[column] =
NIL
AND column # static
THEN {
[totalSpace, leftX, width, , nextY] ← ColumnInfo[column];
GreyScreen[leftX, nextY, width, totalSpace, column=color, FALSE];
IF column # color
AND openBottomY < iconTopY
THEN
-- paint icons
FOR v: Viewer ← rootViewerTree[static], v.sibling
UNTIL v=
NIL
DO
IF v.iconic
AND ~v.offDeskTop
THEN
TRUSTED {Process.Detach[FORK PaintViewer[v, all]]};
ENDLOOP}
ELSE
FOR v: Viewer ← rootViewerTree[column], v.sibling
UNTIL v=
NIL
DO
IF ~v.offDeskTop THEN TRUSTED {Process.Detach[FORK PaintViewer[v, all]]};
ENDLOOP;
WaitForPaintingToFinish[];
Carets.ResumeCarets[];
END;
ColumnInfo:
PROC[column: Column]
RETURNS[totalSpace, leftX, width, bottomY, nextY:
INTEGER] =
INLINE BEGIN
SELECT column
FROM
left => {
totalSpace ← openLeftTopY-openBottomY;
bottomY ← nextY ← openBottomY;
leftX ← openLeftLeftX;
width ← openLeftWidth};
right => {
totalSpace ← openRightTopY-openBottomY;
bottomY ← nextY ← openBottomY;
leftX ← openRightLeftX;
width ← openRightWidth};
color => {
totalSpace ← ColorDisplay.height;
bottomY ← nextY ← 0;
leftX ← 0;
width ← ColorDisplay.width};
ENDCASE => ERROR;
END;
SetOpenHeight:
PUBLIC
PROC [viewer: Viewer, clientHeight:
INTEGER] =
BEGIN
overhead: INTEGER ← captionHeight + (IF viewer.border THEN windowBorderSize ELSE 0);
IF viewer.menu#
NIL
THEN overhead ←
overhead+(viewer.menu.linesUsed*menuHeight)+menuBarHeight;
viewer.openHeight ← clientHeight+overhead;
END;
iconSlots: CARDINAL = (iconRightX-iconLeftX)/(iconWidth+iconSpacing);
iconSlotsFree: CARDINAL ← iconRows*iconSlots;
iconSlotsArray:
ARRAY [0..iconRows*iconSlots)
OF
BOOL ←
ALL[
FALSE];
RemoveIcon:
PROCEDURE[icon: Viewer, fillSlot:
BOOLEAN ←
FALSE]
RETURNS[filler: Viewer, oldX, oldY: INTEGER] = BEGIN
slot: INTEGER ← 0;
FindIcon: ViewerOps.EnumProc = {
IF v.position = slot THEN {filler ← v; RETURN[FALSE]} ELSE RETURN[TRUE]};
iconSlotsFree ← iconSlotsFree + 1;
iconSlotsArray[icon.position] ← FALSE;
IF NOT fillSlot THEN RETURN;
FOR m:
INTEGER
DECREASING
IN ((icon.position/iconSlots)..iconRows)
DO
IF icon.column = right
THEN
FOR n:
CARDINAL
DECREASING
IN [0..iconSlots)
DO
slot ← m*iconSlots + n;
IF iconSlotsArray[slot] THEN EXIT;
ENDLOOP
ELSE
FOR n:
CARDINAL
IN [0..iconSlots)
DO
slot ← m*iconSlots + n;
IF iconSlotsArray[slot] THEN EXIT;
ENDLOOP;
ENDLOOP;
IF slot = 0 OR ~iconSlotsArray[slot] THEN RETURN;
ViewerOps.EnumerateViewers[FindIcon];
IF filler = NIL THEN RETURN;
oldX ← filler.wx;
oldY ← filler.wy;
iconSlotsArray[filler.position] ← FALSE;
iconSlotsArray[icon.position] ← TRUE;
filler.position ← icon.position;
SetIconPosition[filler];
END;
PositionIcon:
PROC [viewer: Viewer] =
BEGIN
FindIconSlot:
PROC [side: Column]
RETURNS [slot:
CARDINAL] =
BEGIN
FOR m:
CARDINAL
IN [0..iconRows)
DO
IF side=right
THEN
FOR n:
CARDINAL
DECREASING
IN [0..iconSlots)
DO
slot ← m*iconSlots+n;
IF ~iconSlotsArray[slot]
THEN
BEGIN
iconSlotsArray[slot] ← TRUE;
RETURN;
END;
ENDLOOP
ELSE
FOR n:
CARDINAL
IN [0..iconSlots)
DO
slot ← m*iconSlots+n;
IF ~iconSlotsArray[slot]
THEN
BEGIN
iconSlotsArray[slot] ← TRUE;
RETURN;
END;
ENDLOOP;
ENDLOOP;
END;
viewer.position ← FindIconSlot[viewer.column];
SetIconPosition[viewer];
iconSlotsFree ← iconSlotsFree-1;
END;
SetIconPosition:
PROC [icon: Viewer] =
INLINE BEGIN
SetViewerPosition[icon, (icon.position
MOD iconSlots)*(iconWidth+iconSpacing)+iconLeftX,
iconBottomY+((icon.position/iconSlots)*(iconHeight+iconSpacing)), iconWidth,
iconHeight];
END;
MoveViewer:
PUBLIC
PROC [viewer: Viewer, x, y:
INTEGER, w, h:
INTEGER,
paint: BOOL ← TRUE] = BEGIN OPEN viewer;
ENABLE UNWIND => Carets.ResumeCarets[];
LockedMoveViewer:
PROC = {
oldX, oldY, oldW, oldH: INTEGER;
oldX ← wx; oldY ← wy; oldW ← ww; oldH ← wh;
SetViewerPosition[viewer, x, y, w, h];
IF ~paint OR parent # NIL THEN RETURN;
GreyScreen[oldX, oldY, oldW, oldH, column=color AND ~iconic, iconic]};
Carets.SuspendCarets[];
ViewerLocks.CallUnderWriteLock[LockedMoveViewer, viewer];
IF paint THEN PaintViewer[IF parent=NIL THEN viewer ELSE parent, all];
Carets.ResumeCarets[];
END;
EstablishViewerPosition:
PUBLIC
PROC [viewer: Viewer, x,y,w,h:
INTEGER] =
BEGIN
LockedEstablishViewerPosition: PROC = {SetViewerPosition[viewer, x, y, w, h]};
ViewerLocks.CallUnderReadLock[LockedEstablishViewerPosition, viewer];
END;
SetViewerPosition:
PROC [viewer: Viewer, x,y,w,h:
INTEGER] =
BEGIN OPEN viewer;
vbs: INTEGER = IF border THEN windowBorderSize ELSE 0;
ResetPaintCache[viewer];
wx ← x; wy ← y; ww ← w; wh ← h;
ch ← wh - (vbs+vbs) - (IF column=static OR parent#NIL THEN 0 ELSE captionHeight);
IF menu#NIL THEN ch ← ch - (menu.linesUsed*menuHeight);
cx ← wx + vbs + (IF scrollable THEN scrollBarW ELSE 0);
cy ← wy + vbs;
cw ← ww - (cx-wx) - vbs;
END;
LinkViewer:
PROC [new: Viewer, bottom:
BOOL ←
TRUE] =
BEGIN
column: Column ~ ViewerColumn[new];
IF rootViewerTree[column]=
NIL
OR bottom
THEN
BEGIN
new.sibling ← rootViewerTree[column];
rootViewerTree[column] ← new;
END
ELSE
FOR v: Viewer ← rootViewerTree[column], v.sibling
UNTIL v.sibling=
NIL
DO
REPEAT FINISHED => v.sibling ← new;
ENDLOOP;
END;
UnlinkViewer:
PROC [old: Viewer] =
BEGIN
root: Viewer;
column: Column ~ ViewerColumn[old];
IF old=rootViewerTree[column]
THEN
{rootViewerTree[column] ← old.sibling; old.sibling ← NIL; RETURN};
root ← IF old.parent=NIL THEN rootViewerTree[column] ELSE old.parent.child;
IF old=root THEN old.parent.child ← old.sibling;
FOR v: Viewer ← root, v.sibling
UNTIL v.sibling=old
DO
REPEAT FINISHED => v.sibling ← v.sibling.sibling;
ENDLOOP;
old.sibling ← NIL;
END;
LinkChild:
PROC [parent, child: Viewer, bottom:
BOOL ←
TRUE] =
BEGIN
IF parent.child=
NIL
OR bottom
THEN
BEGIN
child.sibling ← parent.child;
parent.child ← child;
END
ELSE
FOR v: Viewer ← parent.child, v.sibling
UNTIL v.sibling=
NIL
DO
REPEAT FINISHED => v.sibling ← child;
ENDLOOP;
END;
KillInputFocus:
PROC [viewer: Viewer] =
BEGIN
focus: Viewer ← InputFocus.GetInputFocus[].owner;
WHILE focus #
NIL
DO
IF focus # viewer
THEN focus ← focus.parent
ELSE {InputFocus.SetInputFocus[]; EXIT};
ENDLOOP;
END;
UnWedgedColumn:
PROC
RETURNS[unwedged: Column] =
INLINE
BEGIN
FOR unwedged
IN Column
DO
IF unwedged = static THEN LOOP; -- save the static column for last
IF unwedged = color AND ~WindowManager.colorDisplayOn THEN LOOP;
IF ~ViewerLocks.ColumnWedged[unwedged].wedged THEN RETURN;
ENDLOOP;
RETURN[static];
SaveAllEdits:
PUBLIC
PROC =
BEGIN
poor man's crash recovery
old: Process.Priority = Process.GetPriority[];
Save: EnumProc =
BEGIN
Cursors.InvertCursor[];
IF (v.newVersion
OR v.newFile)
AND v.class.save #
NIL
THEN v.class.save[v, TRUE ! ANY => CONTINUE];
v.newVersion ← v.newFile ← FALSE;
IF v.icon=dirtyDocument THEN v.icon ← document;
IF v.link#
NIL
THEN
FOR t: Viewer ← v.link, t.link
UNTIL t=v
DO
t.newVersion ← t.newFile ← FALSE;
ENDLOOP;
Cursors.InvertCursor[];
RETURN[TRUE];
END;
IF old>Process.priorityForeground
THEN
TRUSTED BEGIN
-- called from CoPilot
Process.SetPriority[Process.priorityForeground];
RTOS.RegisterCedarProcess[RTOS.GetCurrent[]];
END;
EnumerateViewers[Save];
IF old>Process.priorityForeground
THEN
TRUSTED BEGIN
-- called from CoPilot
RTOS.UnregisterCedarProcess[RTOS.GetCurrent[]];
Process.SetPriority[old];
END;
END;
PaintEverything:
PUBLIC
PROC =
BEGIN
OPEN Process;
ResetPaintCache[];
Carets.SuspendCarets[];
GreyScreen[0, 0, 9999, 9999, FALSE, FALSE];
ComputeColumn[static];
ComputeColumn[left];
ComputeColumn[right];
IF WindowManager.colorDisplayOn THEN ComputeColumn[color];
WaitForPaintingToFinish[];
Carets.ResumeCarets[];
END;
UserToScreenCoords:
PUBLIC
PROC [self: Viewer, vx, vy:
INTEGER ← 0]
RETURNS [sx, sy: INTEGER] = BEGIN
invert: BOOL;
UNTIL self=
NIL
DO
-- compute enclosing viewer offsets
invert ←
IF self.parent=
NIL
THEN self.class.coordSys=top
ELSE self.class.coordSys#self.parent.class.coordSys;
vx ← vx + self.cx;
IF invert THEN vy ← Top2Bottom[self, vy];
vy ← vy + self.cy;
self ← self.parent;
ENDLOOP;
RETURN [vx, vy];
END;
MouseInViewer:
PUBLIC
PROC [tsc: TIPUser.TIPScreenCoords]
RETURNS [viewer: Viewer, client: BOOL] = BEGIN
ENABLE UNWIND => NULL;
x: INTEGER ← tsc.mouseX;
y: INTEGER ← tsc.mouseY;
invert: BOOL;
TopLevelHit:
PROC
RETURNS [Viewer] =
BEGIN
FOR c: Column
DECREASING
IN Column
DO
FOR v: Viewer ← rootViewerTree[c], v.sibling
UNTIL v=
NIL
DO
IF ViewerHit[v] THEN RETURN[v];
ENDLOOP;
REPEAT FINISHED => RETURN[NIL];
ENDLOOP;
END;
ViewerHit:
PROC [v: Viewer]
RETURNS [
BOOL] =
INLINE
{
RETURN[x
IN [v.wx..v.wx+v.ww)
AND y
IN [v.wy..v.wy+v.wh)
AND
(IF tsc.color THEN (v.column=color AND ~v.iconic) ELSE (v.column#color OR v.iconic))]};
Client:
PROC
RETURNS [
BOOL] =
INLINE
{
RETURN[y
IN [viewer.cy..viewer.cy+viewer.ch)
AND
x IN [viewer.cx..viewer.cx+viewer.cw)]};
CheckViewersChildren:
PROC [v: Viewer]
RETURNS [
BOOL] =
BEGIN
ex, ey: INTEGER;
EmbeddedViewerHit:
PROC [v: Viewer]
RETURNS [
BOOL] =
INLINE
{RETURN[ex IN [v.wx..v.wx+v.ww) AND ey IN [v.wy..v.wy+v.wh)]};
ex ← x - v.cx;
ey ← y - v.cy;
invert ←
IF v.parent=
NIL
THEN v.class.coordSys=top
ELSE v.class.coordSys#v.parent.class.coordSys;
IF invert THEN ey ← Bottom2Top[v, ey];
FOR v ← v.child, v.sibling
UNTIL v=
NIL
DO
IF EmbeddedViewerHit[v]
THEN
BEGIN
embedded frames are client info relative
x ← ex;
y ← ey;
viewer ← v;
RETURN[TRUE];
END;
ENDLOOP;
RETURN[FALSE];
END;
IF (viewer ← TopLevelHit[]) = NIL THEN RETURN [NIL, FALSE];
IF viewer.child#
NIL
AND ~viewer.iconic
AND Client[]
THEN
WHILE viewer.child#
NIL
AND CheckViewersChildren[viewer] DO ENDLOOP;
client ← Client[];
invert ←
IF viewer.parent=
NIL
THEN client
AND viewer.class.coordSys=top
ELSE viewer.class.coordSys#viewer.parent.class.coordSys;
overwrite screen coords record
tsc.mouseX ← x - (IF client THEN viewer.cx ELSE viewer.wx);
y ← y - (IF client THEN viewer.cy ELSE viewer.wy);
tsc.mouseY ← IF ~invert THEN y ELSE IF client THEN Top2Bottom[viewer, y] ELSE viewer.wh-y;
END;
[] ← ViewerEvents.RegisterEventProc[GrowExtra, grow, NIL, TRUE];
[] ← ViewerEvents.RegisterEventProc[GrowExtra, grow, NIL, FALSE];
[] ← ViewerEvents.RegisterEventProc[GrowExtra, destroy];
END.