WindowManagerImpl.mesa
Copyright © 1985 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, November 26, 1985 10:33:25 am PST
DIRECTORY
Basics USING [LongDiv, LongMult],
Cursors USING [CursorType, SetCursor],
Imager USING [ClipRectangleI, Color, Context, MaskRectangleI, SetColor, white],
ImagerBackdoor USING [MakeStipple],
InputFocus USING [CaptureButtons, ReleaseButtons],
Interminal USING [TurnOffColorCursor, TurnOnColorCursor],
InterminalBackdoor USING [InsertAction],
Menus USING [AppendMenuEntry, CreateEntry, CreateMenu, Menu, MenuEntry, MouseButton, ReplaceMenuEntry, SetGuarded],
MessageWindow USING [Append, Blink],
Process USING [Detach],
TIPUser USING [TIPScreenCoords],
ViewerClasses USING [Column, HScrollOp, NotifyProc, ScrollOp, Viewer],
ViewerLocks USING [CallUnderWriteLock, Wedged],
ViewerMenus USING [Adjust, Close, Color, Destroy, Grow, Left, Right, Top],
ViewerOps USING [ChangeColumn, CloseViewer, EnumerateViewers, EnumProc, MouseInViewer, PaintViewer],
ViewerPrivate,
ViewerSpecs USING [captionHeight, scrollBarW, windowBorderSize],
WindowManager USING [ScreenPos];
WindowManagerImpl: CEDAR MONITOR
IMPORTS Basics, Cursors, Imager, ImagerBackdoor, InputFocus, Interminal, InterminalBackdoor, Menus, MessageWindow, Process, ViewerLocks, ViewerMenus, ViewerOps, ViewerPrivate, ViewerSpecs
EXPORTS ViewerPrivate, WindowManager
SHARES InputFocus, Menus, ViewerClasses
~ BEGIN OPEN Cursors, ViewerSpecs;
Column: TYPE = ViewerClasses.Column;
HScrollOp: TYPE = ViewerClasses.HScrollOp;
NotifyProc: TYPE = ViewerClasses.NotifyProc;
ScrollOp: TYPE = ViewerClasses.ScrollOp;
Viewer: TYPE = ViewerClasses.Viewer;
Zone: TYPE ~ {none, caption, menu, vscroll, hscroll};
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] = {
This procedure is called under the notifier (and therefore needs no monitor protection) to process an input event into feedback actions. Priori 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];
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]; -- no new feedback
InputFocus.CaptureButtons[proc: ProcessWindowResults,
tip: ViewerPrivate.WindowManagerTIPTable, viewer: feedbackViewer];
RETURN;
};
SetC[textPointer]; -- 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] ~ {
x: INTEGER ~ IF viewer.border THEN ViewerSpecs.windowBorderSize ELSE 0;
y: INTEGER ~ viewer.cy;
w: INTEGER ~ ViewerSpecs.scrollBarW;
h: INTEGER ~ viewer.ch;
d1: INTEGER ~ Basics.LongDiv[Basics.LongMult[entry.left, h]+50, 100];
d2: INTEGER ~ Basics.LongDiv[Basics.LongMult[entry.right, h]+50, 100];
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] ~ {
x: INTEGER ~ viewer.cx;
y: INTEGER ~ IF viewer.border THEN ViewerSpecs.windowBorderSize ELSE 0;
w: INTEGER ~ viewer.cw;
h: INTEGER ~ ViewerSpecs.scrollBarW;
d1: INTEGER ~ Basics.LongDiv[Basics.LongMult[entry.left, w]+50, 100];
d2: INTEGER ~ Basics.LongDiv[Basics.LongMult[entry.right, w]+50, 100];
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: BOOLFALSE, left, right: INTEGER ← 0] = {
IF NOT viewer.iconic AND NOT viewer.destroyed THEN {
col: Column ← viewer.column;
fbq: FeedbackEchoQueue ← feedBackQueues[col];
new: FeedbackEchoList ← LIST[[viewer, zone, remove, left, right]];
IF fbq = NIL THEN
feedBackQueues[col] ← fbq ← NEW[FeedbackEchoQueueRep ← [NIL, NIL]];
IF fbq.tail = NIL
THEN fbq.head ← new
ELSE fbq.tail.rest ← new;
fbq.tail ← new;
BROADCAST feedbackChange;
};
};
RemEntry: ENTRY PROC [col: Column] RETURNS [FeedbackEchoEntry] = {
DO
fbq: FeedbackEchoQueue ← feedBackQueues[col];
IF fbq # NIL THEN {
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 feedbackChange;
ENDLOOP;
};
feedbackChange: CONDITION;
feedBackQueues: REF FeedBackQueuesRep ← NEW[FeedBackQueuesRep ← ALL[NIL]];
FeedBackQueuesRep: TYPE = ARRAY Column OF FeedbackEchoQueue;
FeedbackEchoQueue: TYPE = REF FeedbackEchoQueueRep;
FeedbackEchoQueueRep: TYPE = RECORD [head, tail: FeedbackEchoList ← NIL];
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]
not monitored since notifier is synchronous
viewer: Viewer ← self; -- the current viewer
zone: Zone ← none;
mouse: TIPUser.TIPScreenCoords ← NIL;
shft, ctrl: BOOLFALSE;
FOR l: LIST OF REF ANY ← input, l.rest UNTIL l=NIL DO WITH l.first SELECT FROM
coords: TIPUser.TIPScreenCoords => {
client: BOOLFALSE;
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];
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];
down, slide => SetC[vCursor[button]];
up => HitVScroll[viewer, mouse.mouseY, vScrollOp[button], shft, ctrl];
ENDCASE;
hscroll => SELECT action FROM
move => SetC[scrollLeftRight];
down, slide => SetC[hCursor[button]];
up => HitHScroll[viewer, mouse.mouseX, hScrollOp[button], shft, ctrl];
ENDCASE;
ENDCASE;
};
ENDCASE;
ENDLOOP;
};
HitVScroll: PROC [viewer: Viewer, wy: INTEGER, op: ScrollOp, shift, control: BOOL] = {
IF viewer.class.scroll#NIL THEN {
h: NAT ~ viewer.ch;
y: INTEGER ~ h-(wy-viewer.cy); -- from top of client area
amount: NAT ← y;
left, right: INTEGER;
IF op=thumb THEN amount ← Basics.LongDiv[Basics.LongMult[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]; -- put the cursor back
};
HitHScroll: PROC [viewer: Viewer, wx: INTEGER, op: HScrollOp, shift, control: BOOL] = {
IF viewer.class.hscroll#NIL THEN {
w: NAT ~ viewer.cw;
x: INTEGER ~ wx-viewer.cx; -- from left of client area
amount: NAT ← x;
left, right: INTEGER;
IF op=thumb THEN amount ← Basics.LongDiv[Basics.LongMult[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]; -- 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] = INLINE { IF waitCount=0 THEN Cursors.SetCursor[cursor] };
WaitCursor: PUBLIC ENTRY PROC [cursor: Cursors.CursorType ← hourGlass] = {
ENABLE UNWIND => NULL;
IF ViewerPrivate.inputEnabled THEN SetCursor[cursor];
waitCount ← waitCount + 1;
};
UnWaitCursor: PUBLIC ENTRY PROC = {
ENABLE UNWIND => NULL;
waitCount ← MAX[0, waitCount - 1];
IF waitCount=0 THEN RestoreCursor[];
};
RestoreCursor: PUBLIC PROC = TRUSTED {
IF ViewerPrivate.inputEnabled THEN
InterminalBackdoor.InsertAction[[contents: deltaMouse[[0,0]]]]
};
colorDisplayOn: PUBLIC BOOLFALSE; -- color display status
StartColorViewers: PUBLIC PROC [
screenPos: WindowManager.ScreenPos, bitsPerPixel: CARDINAL] = {
IF colorDisplayOn THEN StopColorViewers[];
colorDisplayOn ← ViewerPrivate.EnableColor[bitsPerPixel];
IF colorDisplayOn THEN {
Interminal.TurnOnColorCursor[IF screenPos=left THEN left ELSE right];
Menus.ReplaceMenuEntry[windowMenu, tNopEntry, tColorEntry];
}
ELSE {
MessageWindow.Append["Sorry, you don't have a color display.", TRUE];
MessageWindow.Blink[];
};
};
StopColorViewers: PUBLIC PROC = {
DoColorViewer: ViewerOps.EnumProc = {
IF v.column=color THEN {
ViewerOps.CloseViewer[v];
ViewerOps.ChangeColumn[v, left];
};
};
IF NOT colorDisplayOn THEN RETURN;
Interminal.TurnOffColorCursor[];
Menus.ReplaceMenuEntry[windowMenu, tColorEntry, tNopEntry];
ViewerOps.EnumerateViewers[DoColorViewer];
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 DO
Process.Detach[FORK FeedbackEchoProcess[c]];
ENDLOOP;
};
END.