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.