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:
BOOL ←
FALSE, 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: 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];
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
BOOL ←
FALSE;
-- 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.