<> <> <> <> <> DIRECTORY Buttons USING [Button, ButtonProc], CedarProcess USING [SetPriority], ChoiceButtons, Containers USING [ChildXBound], Imager USING [black, Color, Font, MaskRectangleI, SetColor, SetFont, SetXYI, ShowRope, white], ImagerBackdoor USING [invert, MakeStipple], InputFocus USING [CaptureButtons, ReleaseButtons], Labels USING [Create, Label, Set], Menus USING [MouseButton], Process USING [Detach, Milliseconds, MsecToTicks, SetTimeout], Rope USING [ROPE, Equal], TIPUser USING [InstantiateNewTIPTable, TIPScreenCoords], VFonts USING [CharWidth, defaultFont, DefaultFont, Font, FontHeight, StringWidth], ViewerClasses USING [GetProc, NotifyProc, PaintProc, PaintRectangle, SetProc, Viewer, ViewerClass, ViewerClassRec, ViewerRec], ViewerOps USING [AddProp, CreateViewer, DestroyViewer, EnumerateChildren, EnumProc, FetchProp, MouseInViewer, MoveViewer, PaintViewer, RegisterViewerClass], ViewerPrivate USING [Document, messageWindow], ViewerTools USING [MakeNewTextViewer, SetContents, SetSelection]; ButtonsImpl: CEDAR MONITOR LOCKS data USING data: ButtonData IMPORTS CedarProcess, Containers, Imager, ImagerBackdoor, InputFocus, Labels, Process, Rope, TIPUser, VFonts, ViewerOps, ViewerPrivate, ViewerTools EXPORTS Buttons, ChoiceButtons = BEGIN OPEN Buttons, ChoiceButtons, ViewerClasses; <> ButtonData: TYPE = REF ButtonDataRec; ButtonDataRec: TYPE = MONITORED RECORD [ proc: ButtonProc, font: Imager.Font, clientData: REF ANY, documentation: REF ANY, greyCount: INTEGER _ 0, displayStyle: DisplayStyle _ blackOnWhite, inverted: BOOL _ FALSE, fork: BOOL, guarded: BOOL, state: GuardState ]; GuardState: TYPE = { guarded, arming, armed }; DisplayStyle: TYPE = {blackOnWhite, whiteOnBlack, blackOnGrey}; bottomOffset: INTEGER = 2; guardOffset: INTEGER = 3; sideMargin: INTEGER = 3; extraHeight: INTEGER = 3; entryHeight: CARDINAL = 15; -- how tall to make each line of items entryHSpace: CARDINAL = 10; -- horizontal space between title and selections fudge: CARDINAL = 5; -- fudge factor so that the baselines of text viewers are aligned with <> on: ATOM = $WhiteOnBlack; off: ATOM = $BlackOnWhite; <> armingTime: Process.Milliseconds _ 100; -- cover removal time. armedTime: Process.Milliseconds _ 5000; -- unguarded interval. <> Create: PUBLIC PROC [info: ViewerRec _ [], proc: ButtonProc, clientData: REF ANY _ NIL, fork: BOOL _ FALSE, font: Imager.Font _ NIL, documentation: REF ANY _ NIL, guarded: BOOL _ FALSE, paint: BOOL _ TRUE] RETURNS [button: Button] = { data: ButtonData _ NEW[ButtonDataRec _ [proc: proc, font: VFonts.DefaultFont[font], clientData: clientData, documentation: documentation, fork: fork, guarded: guarded, state: IF guarded THEN guarded ELSE armed]]; IF info.ww=0 THEN info.ww _ VFonts.StringWidth[info.name, data.font]+sideMargin*2; IF info.wh=0 THEN info.wh _ VFonts.FontHeight[data.font]+extraHeight; IF info.parent=NIL AND info.wx=0 AND info.wy=0 THEN { m: Viewer ~ ViewerPrivate.messageWindow; ViewerOps.MoveViewer[m, m.wx, m.wy, m.ww-info.ww, m.wh, FALSE]; info.wx _ m.wx + m.ww; info.wy _ m.wy; info.wh _ m.wh; info.column _ static; }; info.data _ data; IF documentation#NIL THEN WITH documentation SELECT FROM doc: REF TEXT => NULL; doc: REF ButtonProc => NULL; doc: Rope.ROPE => NULL; ENDCASE => ERROR; -- not valid documentation RETURN[ViewerOps.CreateViewer[$Button, info, paint]]; }; buttonGrey: Imager.Color ~ ImagerBackdoor.MakeStipple[stipple: 001010B]; ButtonPaint: PaintProc = { data: ButtonData ~ NARROW[self.data]; margin: INTEGER ~ 1; x: INTEGER ~ margin-self.cx; y: INTEGER ~ margin-self.cy; w: INTEGER ~ self.ww-2*margin; h: INTEGER ~ self.wh-2*margin; <> IF data = NIL THEN RETURN; IF ISTYPE[whatChanged, PaintRectangle] THEN whatChanged _ NIL; IF whatChanged=NIL THEN { <> background: Imager.Color _ Imager.white; IF data.greyCount#0 THEN background _ buttonGrey ELSE SELECT data.displayStyle FROM blackOnWhite => background _ Imager.white; blackOnGrey => background _ buttonGrey; whiteOnBlack => background _ Imager.black; ENDCASE; IF clear AND background=Imager.white THEN NULL ELSE { Imager.SetColor[context, background]; Imager.MaskRectangleI[context, x, y, w, h]; }; Imager.SetColor[context, IF data.displayStyle=whiteOnBlack THEN Imager.white ELSE Imager.black]; Imager.SetXYI[context, x+sideMargin, y+bottomOffset]; Imager.SetFont[context, data.font]; Imager.ShowRope[context, self.name]; IF data.guarded AND data.state#armed THEN { Imager.MaskRectangleI[context, x, y+bottomOffset+guardOffset, w, 1]; }; }; IF whatChanged=$Invert OR data.inverted THEN { -- invert to indicate highlighting Imager.SetColor[context, ImagerBackdoor.invert]; Imager.MaskRectangleI[context, x, y, w, h]; IF whatChanged=$Invert THEN data.inverted _ ~data.inverted; }; }; ButtonNotify: NotifyProc = { data: ButtonData _ NARROW[self.data]; EntryButtonNotify[self, input, data]; }; EntryButtonNotify: ENTRY PROC [self: Viewer, input: LIST OF REF ANY, data: ButtonData] = { ENABLE UNWIND => InputFocus.ReleaseButtons[]; button: Menus.MouseButton _ red; shift, control: BOOL _ FALSE; mouse: TIPUser.TIPScreenCoords; IF data = NIL THEN RETURN; FOR list: LIST OF REF ANY _ input, list.rest UNTIL list = NIL DO WITH list.first SELECT FROM x: ATOM => SELECT x FROM $Blue => button _ blue; $Control => control _ TRUE; $Documentation => IF data.documentation#NIL THEN ViewerPrivate.Document[data.documentation, self, data.clientData, button, shift, control]; $Hit => IF data.inverted THEN SELECT data.state FROM guarded => { data.state_arming; ViewerOps.PaintViewer[self, client, FALSE, $Invert]; InputFocus.ReleaseButtons[]; TRUSTED {Process.Detach[FORK ArmButtonProc[data, self]]}; IF data.documentation#NIL THEN -- post documentation ViewerPrivate.Document[data.documentation, self, data.clientData, button, shift, control]; }; arming=> NULL; -- no action armed=> { InputFocus.ReleaseButtons[]; ViewerOps.PaintViewer[self, client, FALSE, $Invert]; IF data.guarded THEN { data.state_guarded; ViewerOps.PaintViewer[self, client]; }; IF data.fork THEN TRUSTED {Process.Detach[FORK ButtonPusher[self, data, data.proc, data.clientData, button, shift, control, TRUE]]} ELSE ButtonPusher[self, data, data.proc, data.clientData, button, shift, control, FALSE]; }; ENDCASE; $Mark => IF ~data.inverted THEN { InputFocus.CaptureButtons[ButtonNotify, buttonClass.tipTable, self]; ViewerOps.PaintViewer[self, client, FALSE, $Invert]; } ELSE { v: Viewer; c: BOOL; [v, c] _ ViewerOps.MouseInViewer[mouse]; IF v=self AND c THEN RETURN; ViewerOps.PaintViewer[self, client, FALSE, $Invert]; InputFocus.ReleaseButtons[]; }; $Red => button _ red; $Shift => shift _ TRUE; $Yellow => button _ yellow; ENDCASE => NULL; z: TIPUser.TIPScreenCoords => mouse _ z; ENDCASE => ERROR; ENDLOOP; }; ArmButtonProc: ENTRY PROC [data: ButtonData, button: Buttons.Button] = { <> IF data = NIL THEN RETURN; ButtonWait[data, armingTime]; IF data.state = arming THEN { data.state_armed; ViewerOps.PaintViewer[button, client]; ButtonWait[data, armedTime]; }; IF data.state#guarded THEN { data.state_guarded; ViewerOps.PaintViewer[button, client]; }; }; ButtonWait: INTERNAL PROC[data: ButtonData, ticks: Process.Milliseconds] = TRUSTED { buttonWaitCondition: CONDITION; Process.SetTimeout[LONG[@buttonWaitCondition], Process.MsecToTicks[ticks]]; WAIT buttonWaitCondition; }; ButtonPusher: PROC [button: Button, myData: ButtonData, proc: ButtonProc, data: REF ANY, mouseButton: Menus.MouseButton, shift, control, normalPriority: BOOL] = { IF myData = NIL THEN RETURN; myData.greyCount _ myData.greyCount + 1; IF myData.displayStyle#blackOnGrey THEN ViewerOps.PaintViewer[button, client]; IF normalPriority THEN CedarProcess.SetPriority[normal]; proc[button, data, mouseButton, shift, control ! ABORTED => CONTINUE]; myData.greyCount _ MAX[myData.greyCount - 1, 0]; IF myData.displayStyle#blackOnGrey THEN ViewerOps.PaintViewer[button, client]; }; ButtonGet: PRIVATE GetProc = { RETURN[self.name]; }; ButtonSet: PRIVATE SetProc = { IF op=NIL THEN self.name _ NARROW[data] ELSE WITH self.data SELECT FROM myData: ButtonData => SELECT data FROM $BlackOnWhite => { myData.greyCount _ MAX[myData.greyCount - 1, 0]; IF myData.greyCount <= 0 THEN myData.displayStyle _ blackOnWhite}; $WhiteOnBlack => { myData.greyCount _ MAX[myData.greyCount - 1, 0]; IF myData.greyCount <= 0 THEN myData.displayStyle _ whiteOnBlack}; $BlackOnGrey, $BlackOnGray => { myData.greyCount _ myData.greyCount + 1; myData.displayStyle _ blackOnWhite}; ENDCASE => ERROR; ENDCASE => RETURN; IF finalise THEN ViewerOps.PaintViewer[self, all]; }; Destroy: PUBLIC PROC [button: Button] = { ViewerOps.DestroyViewer[button]; }; ReLabel: PUBLIC PROC [button: Button, newName: Rope.ROPE, paint: BOOL _ TRUE] = { button.class.set[button, newName, paint]; }; SetDisplayStyle: PUBLIC PROC [button: Button, style: ATOM, paint: BOOL _ TRUE] = { button.class.set[button, style, paint, $DisplayStyle] }; <> <<>> DefaultDoesntExist: PUBLIC SIGNAL = CODE; ChoiceDoesntExist: PUBLIC SIGNAL = CODE; ButtonsCannotBeUpdated: PUBLIC SIGNAL = CODE; BuildEnumTypeSelection: PUBLIC PROC [viewer: ViewerClasses.Viewer, x, y: CARDINAL, title: ROPE, buttonNames: ButtonList, default: ROPE, borderOnButtons: BOOL, notifyClientProc: SelectionNotifierProc, permit: EnumPermissionProc, clientdata: REF ANY, style: StyleChoice, allInOneRow: BOOL, maxWidth: INTEGER] RETURNS [EnumTypeRef] = { foundDefault: BOOL _ FALSE; stateInfo: EnumTypeRef _ NEW[EnumTypeRec]; DefaultFound: PROC[currentName, defaultName: ROPE] RETURNS [BOOL] = { IF Rope.Equal[currentName, defaultName, FALSE] THEN RETURN [foundDefault _ TRUE] ELSE RETURN [FALSE]; }; BuildMenuSelection: PROC = { border: INTEGER _ 10; startButtons: CARDINAL; startX: INTEGER _ 0; -- keeps a running tally of where the next x position is tempButton: Buttons.Button; eachButton: ButtonList; stateInfo.type _ menuStyle; IF title # NIL THEN { titleLabel: Labels.Label_ Labels.Create[[ name: title, parent: viewer, wx: x, wy: y, wh: entryHeight, border: FALSE ]]; startButtons _ titleLabel.wx + titleLabel.ww + entryHSpace; } ELSE startButtons _ x; startX _ startButtons; FOR eachButton _ buttonNames, eachButton.rest UNTIL eachButton = NIL DO IF ~allInOneRow THEN { IF startX + VFonts.StringWidth[eachButton.first, VFonts.defaultFont] + border >= maxWidth THEN { startX _ startButtons; stateInfo.nexty _ stateInfo.nexty + entryHeight; }; }; tempButton _ Create[info: [ name: eachButton.first, wx: startX, wy: stateInfo.nexty, <> wh: entryHeight, -- specify rather than defaulting so line is uniform parent: viewer, border: borderOnButtons ], proc: MenuSelectionProc, clientData: stateInfo ]; ViewerOps.AddProp[tempButton, $ChoiceButtons, stateInfo]; IF DefaultFound[eachButton.first, default] THEN { stateInfo.buttonOn _ tempButton; SetDisplayStyle[button: tempButton, style: on]; }; startX _ tempButton.wx + tempButton.ww; ENDLOOP; stateInfo.nextx _ startX; }; BuildFlipThru: PROC = { eachButton: ButtonList; titleButton: Buttons.Button; nextx: INTEGER _ x; IF title # NIL THEN { <> stateInfo.type _ flipThruWithTitle; titleButton _ Create[ info: [name: title, parent: viewer, wx: nextx, wy: y, wh: entryHeight, border: TRUE ], proc: FlipThruButtonProc, clientData: stateInfo ]; nextx _ titleButton.wx + titleButton.ww + 3; } ELSE stateInfo.type _ flipThruNoTitle; FOR eachButton _ buttonNames, eachButton.rest UNTIL (eachButton = NIL) OR DefaultFound[eachButton.first, default] DO -- do nothing ENDLOOP; <> IF title # NIL THEN stateInfo.flipLabel _ Labels.Create[[ name: eachButton.first, wx: nextx, wy: y, ww: MaxNameWidth[buttonNames], -- first find the longest name so we can set up the <> wh: entryHeight, parent: viewer, border: FALSE ]] ELSE stateInfo.flipLabel _ Create[ info: [name: eachButton.first, wx: nextx, wy: y, ww: MaxNameWidth[buttonNames], wh: entryHeight, parent: viewer, border: borderOnButtons ], proc: FlipThruButtonProc, clientData: stateInfo ]; stateInfo.nextx _ stateInfo.flipLabel.wx + stateInfo.flipLabel.ww; }; stateInfo.nextx _ x; stateInfo.nexty _ y; stateInfo.proc _ notifyClientProc; stateInfo.namesOfButtons _ buttonNames; stateInfo.clientdata _ clientdata; stateInfo.permission _ permit; IF default = NIL THEN <> default _ buttonNames.first; SELECT style FROM menuSelection => BuildMenuSelection; flipThru => BuildFlipThru; ENDCASE => ERROR; <> IF ~foundDefault THEN SIGNAL DefaultDoesntExist; stateInfo.nexty _ stateInfo.nexty + entryHeight; RETURN[stateInfo]; }; MenuSelectionProc: Buttons.ButtonProc = { viewer: ViewerClasses.Viewer _ NARROW[parent]; WITH clientData SELECT FROM info: EnumTypeRef => IF info.permission = NIL OR info.permission[info] THEN { SwitchButtons[info.buttonOn, viewer]; info.buttonOn _ viewer; <> IF info.proc # NIL THEN info.proc[viewer.name, info.clientdata]; }; ENDCASE; }; SwitchButtons: PRIVATE PROC[oldButton, newButton: Buttons.Button] = { <> <> SetDisplayStyle[button: oldButton, style: off]; <> SetDisplayStyle[button: newButton, style: on]; }; FlipThruButtonProc: Buttons.ButtonProc = { WITH clientData SELECT FROM info: EnumTypeRef => IF info.permission = NIL OR info.permission[info] THEN { IF mouseButton=red THEN <> ViewerTools.SetContents[viewer: info.flipLabel, contents: GetNextName[info.namesOfButtons, info.flipLabel.name]] ELSE <> ViewerTools.SetContents[viewer: info.flipLabel, contents: GetPrevName[info.namesOfButtons, info.flipLabel.name]]; <> IF info.proc # NIL THEN info.proc[info.flipLabel.name, info.clientdata]; }; ENDCASE; }; UpdateChoiceButtons: PUBLIC PROC [viewer: ViewerClasses.Viewer, enumTypeInfo: EnumTypeRef, newName: ROPE] = { newButton: Buttons.Button _ NIL; GetDesiredButton: ViewerOps.EnumProc = { IF (Rope.Equal[v.name, newName, FALSE]) AND (ViewerOps.FetchProp[v, $ChoiceButtons] = enumTypeInfo) THEN { newButton _ v; RETURN [FALSE]; }; }; IF enumTypeInfo = NIL THEN SIGNAL ButtonsCannotBeUpdated; SELECT enumTypeInfo.type FROM menuStyle => { ViewerOps.EnumerateChildren[viewer, GetDesiredButton]; IF newButton # NIL THEN { SwitchButtons[enumTypeInfo.buttonOn, newButton]; enumTypeInfo.buttonOn _ newButton; } ELSE SIGNAL ChoiceDoesntExist; }; flipThruWithTitle => Labels.Set[label: enumTypeInfo.flipLabel, value: newName]; flipThruNoTitle => ReLabel[button: enumTypeInfo.flipLabel, newName: newName]; ENDCASE => ERROR; }; GetSelectedButton: PUBLIC PROC [handle: EnumTypeRef] RETURNS [ROPE] = { IF handle.type = menuStyle THEN { IF handle.buttonOn = NIL THEN ERROR; RETURN [handle.buttonOn.name] } ELSE { IF handle.flipLabel = NIL THEN ERROR; RETURN[handle.flipLabel.name] }; }; GetNextName: PRIVATE PROC[listOfNames: ButtonList, currentName: ROPE] RETURNS [ROPE] = { eachButton: ButtonList _ listOfNames; WHILE (eachButton # NIL) AND (eachButton.first # currentName) DO eachButton _ eachButton.rest; ENDLOOP; IF eachButton = NIL THEN ERROR; -- This shouldn't happen; we should always be able to <> IF eachButton.rest = NIL THEN <> RETURN [listOfNames.first] ELSE RETURN [(eachButton.rest).first]; }; GetPrevName: PRIVATE PROC [listOfNames: ButtonList, currentName: ROPE] RETURNS [ROPE] = { prev: ROPE _ listOfNames.first; eachName: ChoiceButtons.ButtonList _ listOfNames.rest; WHILE (eachName # NIL) AND ~Rope.Equal[eachName.first, currentName, FALSE] DO prev _ eachName.first; eachName _ eachName.rest; ENDLOOP; RETURN[prev]; }; MaxNameWidth: PRIVATE PROC[listOfNames: ButtonList] RETURNS [CARDINAL] = { eachButton: ButtonList _ listOfNames; maxWidth: CARDINAL; IF eachButton # NIL THEN { maxWidth _ VFonts.StringWidth[string: eachButton.first, font: VFonts.defaultFont]; eachButton _ eachButton.rest } ELSE RETURN [0]; WHILE (eachButton # NIL) DO IF VFonts.StringWidth[string: eachButton.first, font: VFonts.defaultFont] > maxWidth THEN maxWidth _ VFonts.StringWidth[string: eachButton.first, font: VFonts.defaultFont]; eachButton _ eachButton.rest; ENDLOOP; maxWidth _ maxWidth + VFonts.CharWidth['M]; -- add on a little extra just to be sure RETURN[maxWidth]; }; BuildTriStateButton: PUBLIC PROC [viewer: ViewerClasses.Viewer, x,y: INTEGER, name: ROPE] RETURNS [stateInfo: ThreeStateRef, nextX: INTEGER] = { tempButton: Buttons.Button; stateInfo _ NEW[ThreeState]; tempButton _ Create[ info: [name: name, wx: x, wy: y, <> wh: entryHeight, -- specify rather than defaulting so line is uniform parent: viewer, border: TRUE ], proc: TriSwitchProc, clientData: stateInfo -- this will be passed to our button proc ]; stateInfo.button _ tempButton; nextX _ tempButton.wx + tempButton.ww + 4; }; SetButtonState: PUBLIC PROC [buttonSelected: Buttons.Button, state: StateType] = { SELECT state FROM on => SetDisplayStyle[button: buttonSelected, style: $WhiteOnBlack]; off => SetDisplayStyle[button: buttonSelected, style: $BlackOnWhite]; default => SetDisplayStyle[button: buttonSelected, style: $BlackOnGrey]; ENDCASE => ERROR; }; TriSwitchProc: Buttons.ButtonProc = { handle: ThreeStateRef _ NARROW[clientData]; state: StateType _ handle.state; handle.state _ state _ IF state = LAST[StateType] THEN FIRST[StateType] ELSE SUCC[state]; <> SetButtonState[handle.button, state]; }; BuildTextPrompt: PUBLIC PROC [viewer: ViewerClasses.Viewer, x,y: CARDINAL, title: ROPE, default: ROPE, font: VFonts.Font, textViewerWidth: INTEGER, clientdata: REF ANY, notify: SelectionNotifierProc, permit: PromptPermissionProc] RETURNS [promptData: PromptDataRef] = { height: INTEGER _ VFonts.FontHeight[font] + 6; promptData _ NEW[PromptDataRec]; promptData.promptButton _ Create[info: [name: title, wx: x, wy: y, wh: height, parent: viewer, border: FALSE], font: font, proc: PromptProc, clientData: promptData]; promptData.textViewer _ ViewerTools.MakeNewTextViewer [[ parent: viewer, wx: promptData.promptButton.wx + promptData.promptButton.ww + entryHSpace, wy: y + fudge, ww: textViewerWidth, wh: height - fudge, name: title, -- give it the name of the prompt associated with it data: default, scrollable: FALSE, border: FALSE ]]; IF textViewerWidth = LAST[INTEGER] THEN { Containers.ChildXBound[viewer, promptData.textViewer]; promptData.newx _ LAST[INTEGER]; } ELSE promptData.newx _ promptData.textViewer.wx + promptData.textViewer.ww; IF default # NIL THEN ViewerTools.SetContents[promptData.textViewer, default]; promptData.clientdata _ clientdata; promptData.notify _ notify; promptData.permission _ permit; promptData.newy _ y + height; }; PromptProc: Buttons.ButtonProc = { viewer: ViewerClasses.Viewer _ NARROW[parent]; WITH clientData SELECT FROM handle: PromptDataRef => IF handle.permission = NIL OR handle.permission[handle] THEN SELECT TRUE FROM mouseButton=red => <> ViewerTools.SetSelection[handle.textViewer]; handle.notify # NIL => <> handle.notify[viewer.name, handle.clientdata]; ENDCASE; ENDCASE; }; <<>> <> buttonClass: ViewerClasses.ViewerClass _ NEW[ViewerClasses.ViewerClassRec _ [ paint: ButtonPaint, get: ButtonGet, set: ButtonSet, notify: ButtonNotify, tipTable: TIPUser.InstantiateNewTIPTable["Button.tip"], cursor: bullseye ]]; ViewerOps.RegisterViewerClass[$Button, buttonClass]; -- plug in to Viewers END.