WindowManagerImpl.mesa
Copyright Ó 1985, 1986, 1988, 1989, 1991 by Xerox Corporation. All rights reserved.
Doug Wyatt, July 22, 1985 2:18:21 pm PDT
Russ Atkinson (RRA) July 1, 1985 6:47:27 pm PDT
Michael Plass, September 29, 1989 4:16:48 pm PDT
Ken Pier (KAP), November 13, 1988 9:11:33 pm PST
Bier, November 7, 1991 2:49 pm PST
Christian Jacobi, March 3, 1992 2:46 pm PST
Willie-s, November 21, 1991 1:20 pm PST
DIRECTORY
Feedback,
Imager USING [ClipRectangleI, Color, Context, MaskRectangleI, SetColor, white],
ImagerBackdoor USING [MakeStipple],
InputFocus,
Menus USING [AppendMenuEntry, CreateEntry, CreateMenu, Menu, MenuEntry, MouseButton, ReplaceMenuEntry, SetGuarded],
MultiCursors,
Process,
SimpleFeedback,
TIPUser USING [TIPScreenCoords],
UserInput,
UserInputInsertActions,
ViewerClasses USING [Column, HScrollOp, NotifyProc, ScrollOp, Viewer],
ViewerLocks USING [CallUnderWriteLock, Wedged],
ViewerMenus USING [Adjust, Color, Close, Destroy, Grow, Left, Right, Top],
ViewerOps USING [MouseInViewer, PaintViewer],
ViewerPrivate,
ViewerSpecs USING [captionHeight, nColumns, scrollBarW, windowBorderSize],
ViewersWorld,
ViewersWorldInstance,
ViewersWorldRefType,
ViewersWorldTypes,
WindowManager;
WindowManagerImpl: CEDAR MONITOR
IMPORTS Imager, ImagerBackdoor, InputFocus, Menus, MultiCursors, Process, SimpleFeedback, UserInputInsertActions, ViewerLocks, ViewerMenus, ViewerOps, ViewerPrivate, ViewerSpecs, ViewersWorld, ViewersWorldInstance
EXPORTS ViewerPrivate, ViewersWorldRefType, WindowManager
SHARES Menus, ViewerClasses
~ BEGIN OPEN ViewerSpecs;
CursorType: TYPE = MultiCursors.CursorType;
Column: TYPE = ViewerClasses.Column;
HScrollOp: TYPE = ViewerClasses.HScrollOp;
NotifyProc: TYPE = ViewerClasses.NotifyProc;
ScrollOp: TYPE = ViewerClasses.ScrollOp;
Viewer: TYPE = ViewerClasses.Viewer;
ViewersWorldObj: PUBLIC TYPE = ViewersWorldTypes.ViewersWorldObj;
Zone: TYPE ~ {none, caption, menu, vscroll, hscroll};
Priority: TYPE = Process.Priority;
VMgrPriority: PUBLIC Priority ¬ 3; -- "priorityForeground" in PrincOps, "priorityUserNormal" in PCedar
CursorZone: PROC[v: Viewer, mousePos: TIPUser.TIPScreenCoords] RETURNS[Zone] ~ {
Note: mousePos is relative to lower left corner of v.
x: INTEGER ~ mousePos.mouseX;
y: INTEGER ~ mousePos.mouseY;
IF v.caption AND v.parent=NIL
AND y>=(v.wh-ViewerSpecs.captionHeight) THEN RETURN[caption];
IF v.menu#NIL AND y>=(v.cy+v.ch) THEN RETURN[menu];
IF v.scrollable AND x<v.cx AND y IN[v.cy..v.cy+v.ch) THEN RETURN[vscroll];
IF v.hscrollable AND y<v.cy AND x IN[v.cx..v.cx+v.cw) THEN RETURN[hscroll];
RETURN[none];
};
feedbackZone: Zone ¬ none;
feedbackViewer: Viewer ¬ NIL;
PostNewFeedback: PROC [viewer: Viewer, zone: Zone, device: REF] = {
This procedure is called under the notifier (and therefore needs no monitor protection) to process an input event into feedback actions. Prior to Cedar6.0 the feedback was "immediate", which tended to garbage up viewers in the process of painting. As part of the lock rationalization, feedback is required to acquire viewer locks as well. However, viewer locks must not force the input notifier to wedge, so we give the feedback chore to some other processes.
IF viewer=NIL THEN zone ¬ none;
IF zone=feedbackZone AND viewer=feedbackViewer THEN RETURN;
IF feedbackZone # none THEN {
IF feedbackZone = caption THEN ViewerPrivate.ClearMenu[windowMenu, viewer, FALSE];
AddEntry[feedbackViewer, feedbackZone, TRUE];
InputFocus.ReleaseButtons[];
};
feedbackZone ¬ zone;
feedbackViewer ¬ viewer;
IF feedbackZone#none THEN {
Add a feedback entry
SELECT feedbackZone FROM
caption, menu => {
SetC[bullseye, device];
AddEntry[feedbackViewer, feedbackZone, FALSE];
};
vscroll => IF viewer.class.scroll # NIL AND viewer.init THEN {
left, right: INTEGER;
[left, right] ¬ viewer.class.scroll[viewer, query, 0];
AddEntry[feedbackViewer, feedbackZone, FALSE, left, right];
};
hscroll => IF viewer.class.hscroll # NIL AND viewer.init THEN {
left, right: INTEGER;
[left, right] ¬ viewer.class.hscroll[viewer, query, 0];
AddEntry[feedbackViewer, feedbackZone, FALSE, left, right];
};
ENDCASE => SetC[textPointer, device]; -- no new feedback
InputFocus.CaptureButtons[proc: ProcessWindowManagerInput,
tip: ViewerPrivate.WindowManagerTIPTable, viewer: feedbackViewer];
RETURN;
};
SetC[textPointer, device]; -- no new feedback
};
scrollLow: Imager.Color ¬ ImagerBackdoor.MakeStipple[8120H];
scrollVisible: Imager.Color ¬ ImagerBackdoor.MakeStipple[5A5AH];
scrollHigh: Imager.Color ¬ ImagerBackdoor.MakeStipple[2180H];
FeedbackEchoProcess: PROC [col: Column] = {
This procedure is the base of a process that services caption, menu, and scrollbar feedback. There is a separate process for each column to allow reasonable feedback during long operations (like Save) in the other column(s). If this is not enough, a separate process could be spawned for each viewer that needs feedback. Preliminary results indicate that this is enough. We are careful to serialize all of the feedback for a given viewer properly.
There is still a minor amount of feedback that gets done by other modules that are called by this one. MenusImpl.MarkMenu & MenusImpl.HitMenu (both exported to ViewerPrivate) can invert menu entries. At some time the feedback processes in this module could take over that chore.
DO
entry: FeedbackEchoEntry ¬ RemEntry[col];
viewer: Viewer ¬ entry.viewer;
inner: PROC = {
IF viewer.iconic OR viewer.destroyed THEN RETURN;
IF entry.remove
THEN
SELECT entry.zone FROM
caption => {
ViewerPrivate.ClearMenu[windowMenu, viewer, FALSE];
ViewerOps.PaintViewer[viewer, caption];
};
menu =>
ViewerPrivate.ClearMenu[viewer.menu, viewer];
vscroll => {
removeScrollbar: PROC [context: Imager.Context] ~ {
x: INTEGER ~ IF viewer.border THEN windowBorderSize ELSE 0;
y: INTEGER ~ viewer.cy;
w: INTEGER ~ viewer.cx-x;
h: INTEGER ~ viewer.ch;
Imager.SetColor[context, Imager.white];
Imager.MaskRectangleI[context, x, y, w, h];
};
ViewerPrivate.PaintWindow[viewer, removeScrollbar];
};
hscroll => {
removeScrollbar: PROC [context: Imager.Context] ~ {
x: INTEGER ~ viewer.cx;
y: INTEGER ~ IF viewer.border THEN ViewerSpecs.windowBorderSize ELSE 0;
w: INTEGER ~ viewer.cw;
h: INTEGER ~ ViewerSpecs.scrollBarW;
Imager.SetColor[context, Imager.white];
Imager.MaskRectangleI[context, x, y, w, h];
};
ViewerPrivate.PaintWindow[viewer, removeScrollbar];
};
ENDCASE
ELSE
SELECT entry.zone FROM
caption => DrawCaptionMenu[viewer, TRUE];
menu => {};
vscroll => {
drawScrollbar: PROC [context: Imager.Context] ~ {
KAP for PCedar November 13, 1988
x: INTEGER ~ IF viewer.border THEN ViewerSpecs.windowBorderSize ELSE 0;
y: INTEGER ~ viewer.cy;
w: INTEGER ~ ViewerSpecs.scrollBarW;
h: INT ~ viewer.ch; -- for highest precision arithmetic
entry.left and entry.right are in [0..100] and represent the bottom and top percentage of the document that is visible in the viewer
d1: INTEGER ~ (entry.left*h+50)/100; -- use highest precision arithmetic
d2: INTEGER ~ (entry.right*h+50)/100; -- use highest precision arithmetic
Imager.SetColor[context, scrollLow];
Imager.MaskRectangleI[context, x, y, w, h-d2];
Imager.SetColor[context, scrollVisible];
Imager.MaskRectangleI[context, x, y+h-d2, w, d2-d1];
Imager.SetColor[context, scrollHigh];
Imager.MaskRectangleI[context, x, y+h-d1, w, d1];
};
IF entry.left IN [0..100] AND entry.right IN [0..100] THEN
ViewerPrivate.PaintWindow[viewer, drawScrollbar];
};
hscroll => {
drawScrollbar: PROC [context: Imager.Context] ~ {
KAP for PCedar November 13, 1988
x: INTEGER ~ viewer.cx;
y: INTEGER ~ IF viewer.border THEN ViewerSpecs.windowBorderSize ELSE 0;
w: INT ~ viewer.cw; -- for highest precision arithmetic
h: INTEGER ~ ViewerSpecs.scrollBarW;
entry.left and entry.right are in [0..100] and represent the left and right percentage of the document that is visible in the viewer
d1: INTEGER ~ (entry.left*w+50)/100; -- use highest precision arithmetic
d2: INTEGER ~ (entry.right*w+50)/100; -- use highest precision arithmetic
Imager.SetColor[context, scrollLow];
Imager.MaskRectangleI[context, x, y, d1, h];
Imager.SetColor[context, scrollVisible];
Imager.MaskRectangleI[context, x+d1, y, d2-d1, h];
Imager.SetColor[context, scrollHigh];
Imager.MaskRectangleI[context, x+d2, y, w-d2, h];
};
IF entry.left IN [0..100] AND entry.right IN [0..100] THEN
ViewerPrivate.PaintWindow[viewer, drawScrollbar];
};
ENDCASE;
};
IF viewer # NIL THEN
ViewerLocks.CallUnderWriteLock[inner, viewer
! ViewerLocks.Wedged, ABORTED => CONTINUE];
ENDLOOP;
};
AddEntry: ENTRY PROC [viewer: Viewer, zone: Zone,
remove: BOOL ¬ FALSE, left, right: INTEGER ¬ 0] = {
IF NOT viewer.iconic AND NOT viewer.destroyed THEN {
fbq: FeedbackEchoQueue ~ feedBackQueues[viewer.column];
new: FeedbackEchoList ~ LIST[[viewer, zone, remove, left, right]];
IF fbq.tail = NIL
THEN fbq.head ¬ new
ELSE fbq.tail.rest ¬ new;
fbq.tail ¬ new;
NOTIFY fbq.feedbackChange;
};
};
RemEntry: ENTRY PROC [col: Column] RETURNS [FeedbackEchoEntry] = {
fbq: FeedbackEchoQueue ~ feedBackQueues[col];
DO
head: FeedbackEchoList ¬ fbq.head;
IF head # NIL THEN {
IF (fbq.head ¬ head.rest) = NIL THEN fbq.tail ¬ NIL;
head.rest ¬ NIL;
RETURN [head.first];
};
WAIT fbq.feedbackChange;
ENDLOOP;
};
feedBackQueues: REF FeedBackQueuesRep ~ NEW[FeedBackQueuesRep[ViewerSpecs.nColumns]];
FeedBackQueuesRep: TYPE = RECORD [SEQUENCE n: Column OF FeedbackEchoQueue];
FeedbackEchoQueue: TYPE = REF FeedbackEchoQueueRep;
FeedbackEchoQueueRep: TYPE = RECORD [head, tail: FeedbackEchoList ¬ NIL, feedbackChange: CONDITION];
FeedbackEchoList: TYPE = LIST OF FeedbackEchoEntry;
FeedbackEchoEntry: TYPE = RECORD [
viewer: Viewer, zone: Zone,
remove: BOOL, left, right: INTEGER];
vCursor: ARRAY Menus.MouseButton OF CursorType ¬
[red: scrollUp, yellow: scrollRight, blue: scrollDown];
vScrollOp: ARRAY Menus.MouseButton OF ScrollOp ¬
[red: up, yellow: thumb, blue: down];
hCursor: ARRAY Menus.MouseButton OF CursorType ¬
[red: scrollLeft, yellow: scrollUp, blue: scrollRight];
hScrollOp: ARRAY Menus.MouseButton OF HScrollOp ¬
[red: left, yellow: thumb, blue: right];
ProcessWindowResults: PUBLIC ViewerClasses.NotifyProc = {
[self: Viewer, input: LIST OF REF ANY] RETURNS[BOOL]
SimpleFeedback.Append[$Viewers, $oneLiner, $Error, "WindowManager ProcessWindowResults called (not ProcessWindowManagerInput). Bug?"];
ProcessWindowManagerInput[self, input];
};
ProcessWindowManagerInput: PUBLIC PROC [self: Viewer, input: LIST OF REF ANY, device: REF ¬ NIL, user: REF ¬ NIL, display: REF ¬ NIL] = {
not monitored since notifier is synchronous
We have an input event to a Viewers area like a scrollbar or menu. We have passed the event through WindowManagerTIPTable (see InputFocusImpl). In this routine, we take appropriate action.
viewer: Viewer ¬ self; -- the current viewer
zone: Zone ¬ none;
mouse: TIPUser.TIPScreenCoords ¬ NIL;
shft, ctrl: BOOL ¬ FALSE;
FOR l: LIST OF REF ANY ¬ input, l.rest UNTIL l=NIL DO WITH l.first SELECT FROM
coords: TIPUser.TIPScreenCoords => {
client: BOOL ¬ FALSE;
mouse ¬ coords;
IF feedbackZone#none THEN [viewer, client] ¬ ViewerOps.MouseInViewer[mouse];
IF client OR viewer=NIL THEN zone ¬ none ELSE zone ¬ CursorZone[viewer, mouse];
};
atom: ATOM => {
button: Menus.MouseButton ¬ red;
action: {nil, move, down, slide, up} ¬ nil;
SELECT atom FROM
$M => action ¬ move;
$RD => { button ¬ red; action ¬ down };
$RM => { button ¬ red; action ¬ slide };
$RU => { button ¬ red; action ¬ up };
$YD => { button ¬ yellow; action ¬ down };
$YM => { button ¬ yellow; action ¬ slide };
$YU => { button ¬ yellow; action ¬ up };
$BD => { button ¬ blue; action ¬ down };
$BM => { button ¬ blue; action ¬ slide };
$BU => { button ¬ blue; action ¬ up };
$Control => ctrl ¬ TRUE;
$Shift => shft ¬ TRUE;
ENDCASE;
IF action=nil THEN LOOP;
PostNewFeedback[viewer, zone, device];
IF zone=caption THEN SELECT button FROM
yellow => mouse.mouseX ¬ growEntry.xPos+1;
blue => mouse.mouseX ¬ closeEntry.xPos+1;
ENDCASE;
SELECT zone FROM -- zone specific ops
caption => SELECT action FROM
down, slide => ViewerPrivate.MarkMenu[windowMenu, viewer, mouse];
up => ViewerPrivate.HitMenu[windowMenu, viewer, mouse, button, shft, ctrl];
ENDCASE;
menu => SELECT action FROM
down, slide => ViewerPrivate.MarkMenu[viewer.menu, viewer, mouse];
up => ViewerPrivate.HitMenu[viewer.menu, viewer, mouse, button, shft, ctrl];
ENDCASE;
vscroll => SELECT action FROM
move => SetC[scrollUpDown, device];
down, slide => SetC[vCursor[button], device];
up => HitVScroll[viewer, mouse.mouseY, device, vScrollOp[button], shft, ctrl];
ENDCASE;
hscroll => SELECT action FROM
move => SetC[scrollLeftRight, device];
down, slide => SetC[hCursor[button], device];
up => HitHScroll[viewer, mouse.mouseX, device, hScrollOp[button], shft, ctrl];
ENDCASE;
ENDCASE;
};
ENDCASE;
ENDLOOP;
};
HitVScroll: PROC [viewer: Viewer, wy: INTEGER, device: REF, op: ScrollOp, shift, control: BOOL] = {
KAP for PCedar November 13, 1988
IF viewer.class.scroll#NIL THEN {
h: CARD ~ viewer.ch; -- CARD for highest precision arithmetic
amount: CARD ¬ h-(wy-viewer.cy); -- from top of client area.
left, right: INTEGER;
IF op=thumb THEN amount ¬ (amount*100+h/2)/h;
AddEntry[viewer, vscroll, TRUE];
[] ¬ viewer.class.scroll[viewer, op, amount, shift, control];
[left, right] ¬ viewer.class.scroll[viewer, query, 0];
AddEntry[viewer, vscroll, FALSE, left, right];
};
SetC[scrollUpDown, device]; -- put the cursor back
};
HitHScroll: PROC [viewer: Viewer, wx: INTEGER, device: REF, op: HScrollOp, shift, control: BOOL] = {
KAP for PCedar November 13, 1988
IF viewer.class.hscroll#NIL THEN {
w: CARD ~ viewer.cw;
amount: CARD ¬ wx-viewer.cx; -- from left of client area
left, right: INTEGER;
IF op=thumb THEN amount ¬ (amount*100+w/2)/w;
AddEntry[viewer, hscroll, TRUE];
[] ¬ viewer.class.hscroll[viewer, op, amount, shift, control];
[left, right] ¬ viewer.class.hscroll[viewer, query, 0];
AddEntry[viewer, hscroll, FALSE, left, right];
};
SetC[scrollLeftRight, device]; -- put the cursor back
};
DrawCaptionMenu: PUBLIC PROC [v: Viewer, guard: BOOL] = {
drawCaption: PROC [context: Imager.Context] ~ {
wbs: INTEGER ~ IF v.border THEN windowBorderSize ELSE 0;
x: INTEGER ~ wbs;
y: INTEGER ~ v.wh-captionHeight;
w: INTEGER ~ v.ww-2*wbs;
h: INTEGER ~ captionHeight;
Imager.ClipRectangleI[context, x, y, w, h];
Imager.SetColor[context, Imager.white];
Imager.MaskRectangleI[context, x, y, w, h];
IF guard THEN
Menus.SetGuarded[destroyEntry, v.guardDestroy OR (v.newVersion AND v.link=NIL AND NOT v.saveInProgress)];
ViewerPrivate.DrawMenu[windowMenu, context, x, y+h];
};
IF v.visible AND NOT v.iconic THEN
ViewerPrivate.PaintWindow[v, drawCaption];
};
waitCount: PUBLIC INTEGER ¬ 0;
SetC: PROC [cursor: CursorType, device: REF] = INLINE {
IF waitCount=0 THEN
WITH device SELECT FROM
atom: ATOM => MultiCursors.SetACursor[cursor, atom];
ENDCASE => IF device = NIL THEN MultiCursors.SetACursor[cursor, NIL];
};
WaitCursor: PUBLIC ENTRY PROC [cursor: CursorType ¬ hourGlass] = {
ENABLE UNWIND => NULL;
vWorld: ViewersWorld.Ref ¬ ViewersWorldInstance.GetWorld[];
IF vWorld.inputEnabled THEN MultiCursors.SetACursor[cursor, NIL];
waitCount ¬ waitCount + 1;
};
UnWaitCursor: PUBLIC ENTRY PROC = {
ENABLE UNWIND => NULL;
waitCount ¬ MAX[0, waitCount - 1];
IF waitCount=0 THEN RestoreCursor[];
};
NotYetImplemented: SIGNAL = CODE;
RestoreCursor: PUBLIC PROC = TRUSTED {
vWorld: ViewersWorld.Ref ¬ ViewersWorldInstance.GetWorld[];
IF vWorld.inputEnabled THEN {
handle: UserInput.Handle ¬ ViewersWorld.GetInputHandle[vWorld];
UserInputInsertActions.InsertFakePosition[handle, 0]
};
};
colorDisplayOn: PUBLIC BOOL ¬ FALSE; -- color display status
StartColorViewers: PUBLIC PROC [screenPos: WindowManager.ScreenPos, bitsPerPixel: CARDINAL] = {
IF bitsPerPixel # CARDINAL.LAST THEN ERROR;
This is now a private call; use ColorDisplayManager instead.
IF colorDisplayOn THEN RETURN;
Menus.ReplaceMenuEntry[windowMenu, tNopEntry, tColorEntry];
[] ¬ ViewerPrivate.EnableColor[0];
colorDisplayOn ¬ TRUE;
};
StopColorViewers: PUBLIC PROC = {
IF NOT colorDisplayOn THEN RETURN;
Menus.ReplaceMenuEntry[windowMenu, tColorEntry, tNopEntry];
ViewerPrivate.DisableColor[];
colorDisplayOn ¬ FALSE;
};
destroyEntry: Menus.MenuEntry ¬ Menus.CreateEntry[name: "Destroy",
proc: ViewerMenus.Destroy, fork: FALSE, documentation: "Edits will be discarded..."];
adjustEntry: Menus.MenuEntry ¬ Menus.CreateEntry[name: "Adjust",
proc: ViewerMenus.Adjust, fork: FALSE];
topEntry: Menus.MenuEntry ¬ Menus.CreateEntry[name: "Top",
proc: ViewerMenus.Top, fork: FALSE];
leftEntry: Menus.MenuEntry ¬ Menus.CreateEntry[name: "<--",
proc: ViewerMenus.Left, fork: FALSE];
rightEntry: Menus.MenuEntry ¬ Menus.CreateEntry[name: "-->",
proc: ViewerMenus.Right, fork: FALSE];
tNopEntry: Menus.MenuEntry ¬ Menus.CreateEntry["", NIL];
tColorEntry: Menus.MenuEntry ¬ Menus.CreateEntry[name: "Color",
proc: ViewerMenus.Color, fork: FALSE];
growEntry: Menus.MenuEntry ¬ Menus.CreateEntry[name: "Grow",
proc: ViewerMenus.Grow, fork: FALSE];
closeEntry: Menus.MenuEntry ¬ Menus.CreateEntry[name: "Close",
proc: ViewerMenus.Close, fork: FALSE];
windowMenu: PUBLIC Menus.Menu ¬ Menus.CreateMenu[];
Menus.AppendMenuEntry[windowMenu, destroyEntry];
Menus.AppendMenuEntry[windowMenu, adjustEntry];
Menus.AppendMenuEntry[windowMenu, topEntry];
Menus.AppendMenuEntry[windowMenu, leftEntry];
Menus.AppendMenuEntry[windowMenu, rightEntry];
Menus.AppendMenuEntry[windowMenu, tNopEntry];
Menus.AppendMenuEntry[windowMenu, growEntry];
Menus.AppendMenuEntry[windowMenu, closeEntry];
TRUSTED {
Fork a feedback process for each column (even the "static" column)
FOR c: Column IN [Column.FIRST..feedBackQueues.n) DO
feedBackQueues[c] ¬ NEW[FeedbackEchoQueueRep];
Process.Detach[FORK FeedbackEchoProcess[c]];
ENDLOOP;
};
END.