<<>> <> <> <> <> <> <> <> <> <> 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] ~ { <> 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> 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 { <> 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] = { <> <> 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: INT ~ viewer.ch; -- for highest precision arithmetic <> 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] ~ { <> 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; <> 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] = { <> <> 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] = { <> 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] = { <> 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; <> 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 { <> FOR c: Column IN [Column.FIRST..feedBackQueues.n) DO feedBackQueues[c] ¬ NEW[FeedbackEchoQueueRep]; Process.Detach[FORK FeedbackEchoProcess[c]]; ENDLOOP; }; END.