GGButtonsImpl.mesa
Last edited by Bier on August 17, 1985 5:29:58 pm PDT.
Contents: General Purpose routines for use by Gargoyle.
DIRECTORY
Buttons, GGContainer, GGInterfaceTypes, GGUserInput, GGButtons, IO, Labels, Menus, Rope, TiogaButtons, ViewerClasses, ViewerOps, VFonts, ViewerTools;
GGButtonsImpl:
CEDAR
PROGRAM
IMPORTS Buttons, GGContainer, GGUserInput, IO, Labels, Menus, Rope, TiogaButtons, VFonts, ViewerOps, ViewerTools
EXPORTS GGButtons =
BEGIN
ButtonLineEntry: TYPE = GGInterfaceTypes.ButtonLineEntry;
ButtonList: TYPE = GGInterfaceTypes.ButtonList;
ButtonType: TYPE = GGInterfaceTypes.ButtonType;
DisplayStyle: TYPE = GGInterfaceTypes.DisplayStyle;
GargoyleData: TYPE = GGInterfaceTypes.GargoyleData;
GGButtonData: TYPE = REF GGButtonDataObj;
GGButtonDataObj: TYPE = GGInterfaceTypes.GGButtonDataObj;
MenuEntry: TYPE = GGInterfaceTypes.MenuEntry;
PopUpLineEntry: TYPE = GGInterfaceTypes.PopUpLineEntry;
ScalarButton: TYPE = GGInterfaceTypes.ScalarButton;
ScalarButtonClient: TYPE = REF ScalarButtonClientObj;
ScalarButtonClientObj: TYPE = GGInterfaceTypes.ScalarButtonClientObj;
StateType: TYPE = GGInterfaceTypes.StateType;
StyleChoice: TYPE = GGInterfaceTypes.StyleChoice;
TwoState: TYPE = REF TwoStateObj;
TwoStateObj: TYPE = GGInterfaceTypes.TwoStateObj;
QueuedMenuEntry: TYPE = GGInterfaceTypes.QueuedMenuEntry;
Viewer: TYPE = ViewerClasses.Viewer;
EnumTypeRef: TYPE = REF EnumTypeRec;
EnumTypeRec: TYPE = GGInterfaceTypes.EnumTypeRec;
entryHeight: CARDINAL = 15; -- height of a line of items
entryVSpace: CARDINAL = 2; -- vertical leading between lines
entryHSpace: CARDINAL = 2; -- horizontal space between items on a line
column1: CARDINAL = 200; -- horizontal space between margin and column 1;
column2: CARDINAL = 250; -- horizontal space between margin and column 2.
column3: CARDINAL = 500; -- horizontal space between margin and column 3;
Gargoyle Menu Utilities
BuildMenuLine:
PUBLIC PROC [menu: Menus.Menu, line:
NAT, clientData: REF ANY, entries:
LIST
OF MenuEntry] = {
guarded: BOOL;
FOR entryList:
LIST
OF MenuEntry ← entries, entryList.rest
UNTIL entryList =
NIL
DO
guarded ← entryList.first.confirmProc # NIL;
Menus.AppendMenuEntry[
menu: menu,
entry:
Menus.CreateEntry[
name: entryList.first.name,
proc: entryList.first.proc,
clientData: clientData,
documentation: entryList.first.confirmProc,
fork: TRUE,
guarded: guarded],
line: line
];
ENDLOOP;
};
BuildQueuedMenuLine:
PUBLIC PROC [menu: Menus.Menu, line:
NAT, clientData:
REF
ANY, entries:
LIST
OF QueuedMenuEntry] = {
guarded: BOOL;
buttonData: GGButtonData;
FOR entryList:
LIST
OF QueuedMenuEntry ← entries, entryList.rest
UNTIL entryList =
NIL
DO
guarded ← entryList.first.confirmProc # NIL;
buttonData ← NEW[GGButtonDataObj ← [clientData, entryList.first.action]];
Menus.AppendMenuEntry[
menu: menu,
entry:
Menus.CreateEntry[
name: entryList.first.name,
proc: GGUserInput.HandleMenuAction,
clientData: buttonData,
documentation: entryList.first.confirmProc,
guarded: guarded],
line: line
];
ENDLOOP;
};
Gargoyle Button Utilities
A line of regular Buttons, Labels, and TextViewers.
BuildButtonLine:
PUBLIC
PROC [container: Viewer, x, y:
NAT, clientData:
REF
ANY, entries:
LIST
OF ButtonLineEntry]
RETURNS [nextX:
INTEGER] = {
thisButton, prevButton: Buttons.Button;
buttonData: GGButtonData;
gargoyleData: GargoyleData ← NARROW[clientData];
SELECT entries.first.type FROM
button => {
buttonData ← NEW[GGButtonDataObj ← [gargoyleData, entries.first.action]];
prevButton ← Buttons.Create[
info: [name: entries.first.name, wx: x, wy: y, wh: entryHeight,
parent: container, border: entries.first.border],
proc: GGUserInput.HandleMenuAction,
clientData: buttonData,
fork: TRUE
];
};
label => {
prevButton ← Labels.Create[
info: [name: entries.first.name, wx: x, wy: y, wh: entryHeight,
parent: container, border: entries.first.border]];
};
text => {
prevButton ← ViewerTools.MakeNewTextViewer[[
parent: container,
wx: x,
wy: y,
ww: entries.first.ww,
wh: entryHeight,
data: entries.first.name,
scrollable: FALSE, border: entries.first.border]];
};
ENDCASE => ERROR;
IF entries.first.updateProc # NIL THEN entries.first.updateProc[entries.first.name, clientData, prevButton];
nextX ← prevButton.wx + prevButton.ww;
FOR entryList:
LIST
OF ButtonLineEntry ← entries.rest, entryList.rest
UNTIL entryList =
NIL
DO
SELECT entryList.first.type FROM
button => {
buttonData ← NEW[GGButtonDataObj ← [gargoyleData, entryList.first.action]];
thisButton ← Buttons.Create[
info: [name: entryList.first.name,
wx: nextX + entryHSpace,
wy: y, wh: entryHeight,
parent: container, border: entryList.first.border],
proc: GGUserInput.HandleMenuAction,
clientData: buttonData,
fork: TRUE
];
};
label => {
thisButton ← Labels.Create[
info: [name: entryList.first.name,
wx: nextX + entryHSpace,
wy: y, wh: entryHeight,
parent: container, border: entryList.first.border]];
};
text => {
thisButton ← ViewerTools.MakeNewTextViewer[[
parent: container,
wx: nextX + entryHSpace,
wy: y,
ww: entryList.first.ww,
wh: entryHeight,
data: entryList.first.name,
scrollable: FALSE, border: entryList.first.border]];
};
ENDCASE => ERROR;
IF entryList.first.updateProc # NIL THEN entryList.first.updateProc[entryList.first.name, clientData, thisButton];
prevButton ← thisButton;
nextX ← prevButton.wx + prevButton.ww;
ENDLOOP;
};
BuildLineOfPopUps:
PUBLIC
PROC [container: Viewer, x, y:
NAT, clientData:
REF
ANY, entries:
LIST
OF PopUpLineEntry]
RETURNS [nextX:
INTEGER] = {
thisButton, prevButton: Buttons.Button;
SELECT entries.first.type FROM
button => {
prevButton ← Buttons.Create[
info: [name: entries.first.name, wx: x, wy: y, wh: entryHeight,
parent: container, border: entries.first.border],
proc: entries.first.proc,
clientData: clientData,
fork: TRUE
];
};
label => {
prevButton ← Labels.Create[
info: [name: entries.first.name, wx: x, wy: y, wh: entryHeight,
parent: container, border: entries.first.border]];
};
text => {
prevButton ← ViewerTools.MakeNewTextViewer[[
parent: container,
wx: x,
wy: y,
ww: entries.first.ww,
wh: entryHeight,
data: entries.first.name,
scrollable: FALSE, border: entries.first.border]];
};
ENDCASE => ERROR;
IF entries.first.updateProc # NIL THEN entries.first.updateProc[entries.first.name, clientData, prevButton];
nextX ← prevButton.wx + prevButton.ww;
FOR entryList:
LIST
OF PopUpLineEntry ← entries.rest, entryList.rest
UNTIL entryList =
NIL
DO
SELECT entryList.first.type FROM
button => {
thisButton ← Buttons.Create[
info: [name: entryList.first.name,
wx: nextX + entryHSpace,
wy: y, wh: entryHeight,
parent: container, border: entryList.first.border],
proc: entryList.first.proc,
clientData: clientData,
fork: TRUE
];
};
label => {
thisButton ← Labels.Create[
info: [name: entryList.first.name,
wx: nextX + entryHSpace,
wy: y, wh: entryHeight,
parent: container, border: entryList.first.border]];
};
text => {
thisButton ← ViewerTools.MakeNewTextViewer[[
parent: container,
wx: nextX + entryHSpace,
wy: y,
ww: entryList.first.ww,
wh: entryHeight,
data: entryList.first.name,
scrollable: FALSE, border: entryList.first.border]];
};
ENDCASE => ERROR;
IF entryList.first.updateProc # NIL THEN entryList.first.updateProc[entryList.first.name, clientData, thisButton];
prevButton ← thisButton;
nextX ← prevButton.wx + prevButton.ww;
ENDLOOP;
};
BuildTwoStateButton:
PUBLIC
PROC [viewer: ViewerClasses.Viewer, x,y:
INTEGER, name: Rope.
ROPE, action:
LIST OF REF ANY, clientData:
REF
ANY, border:
BOOL ←
TRUE, init: StateType ← off]
RETURNS [stateInfo: TwoState, nextX:
INTEGER] = {
tempButton: Buttons.Button;
stateInfo ← NEW[TwoStateObj];
tempButton ← Buttons.Create[
info: [name: name,
wx: x,
wy: y,
default the width so that it will be computed for us
wh: entryHeight, -- specify rather than defaulting so line is uniform
parent: viewer,
border: border
],
proc: TwoSwitchProc,
clientData: stateInfo -- this will be passed to our button proc
];
stateInfo.button ← tempButton;
stateInfo.action ← action;
stateInfo.clientData ← clientData;
SetButtonState[stateInfo, init];
nextX ← tempButton.wx + tempButton.ww + 4;
};
SetButtonState:
PUBLIC
PROC [twoState: TwoState, state: StateType] = {
twoState.state ← state;
SELECT state
FROM
on => Buttons.SetDisplayStyle[button: twoState.button, style: $WhiteOnBlack];
off => Buttons.SetDisplayStyle[button: twoState.button, style: $BlackOnWhite];
ENDCASE => ERROR;
};
NextState:
PROC [state: StateType]
RETURNS [StateType] = {
IF state = LAST[StateType] THEN RETURN[FIRST[StateType]]
ELSE RETURN[SUCC[state]];
};
TwoSwitchProc: Buttons.ButtonProc = {
handle: TwoState ← NARROW[clientData];
gargoyleData: GargoyleData ← NARROW[handle.clientData];
GGUserInput.GeneralDispatch[handle.action, gargoyleData];
};
SwitchState:
PUBLIC
PROC [handle: TwoState] = {
nextState: StateType;
nextState ← NextState[handle.state];
SetButtonState[handle, nextState];
};
A line of two state buttons.
OldBuildAngleButtons: PUBLIC PROC [clientData: REF ANY, x: INTEGER ← 0, angleButtonList: LIST OF ScalarButton] RETURNS [nextX: NAT] = {
gargoyleData: GargoyleData ← NARROW[clientData];
nextX ← x;
FOR scalarButtonList: LIST OF ScalarButton ← angleButtonList, scalarButtonList.rest UNTIL scalarButtonList = NIL DO
[gargoyleData.hitTest.slopeState[gargoyleData.hitTest.slopeCount], nextX] ←
BuildTwoStateButton[viewer: gargoyleData.outer,
x: nextX,
y: gargoyleData.height,
name: scalarButtonList.first.name,
action: scalarButtonList.first.action,
clientData: gargoyleData,
border: FALSE,
init: scalarButtonList.first.init];
gargoyleData.hitTest.slopes[gargoyleData.hitTest.slopeCount] ← NARROW[scalarButtonList.first.action.rest.first, REF REAL]^;
gargoyleData.hitTest.slopeCount ← gargoyleData.hitTest.slopeCount + 1;
ENDLOOP;
};
A line of Tioga Buttons, but built for the new User Interface Architecture (see comment below).
Makes a TiogaButtons viewer and fills it with buttons. Each button names the action atoms to give to the slack process when the button is pressed. The rest is somewhat convoluted. The client must figure out which button has been pressed, update his data structures and call an appropriate procedure to change the appearance of the button, if desired. Hence, the client must have a data structure which points to the appropriate button. This procedure builds such a data structure.
MakeViewer:
PROC [gargoyleData: GargoyleData]
RETURNS [viewer: Viewer] = {
viewer ← TiogaButtons.CreateViewer[
info:
[wy: gargoyleData.height,
ww: gargoyleData.outer.ww,
wh: entryHeight,
parent: gargoyleData.outer,
border: FALSE]
];
GGContainer.ChildXBound[gargoyleData.outer, viewer];
};
HeaderButton:
PROC [v: Viewer, name: Rope.
ROPE, gargoyleData: GargoyleData]
RETURNS [button: TiogaButtons.TiogaButton] = {
button ← TiogaButtons.CreateButton[
viewer: v,
rope: name,
format: "",
looks: "",
proc: NIL,
clientData: NIL];
};
AddScalarButton:
PUBLIC PROC [prevButton: TiogaButtons.TiogaButton, value:
REAL, action:
LIST
OF
REF
ANY, on:
BOOL, gargoyleData: GargoyleData]
RETURNS [button: TiogaButtons.TiogaButton] = {
buttonData, prevButtonData: ScalarButtonClient;
name: Rope.ROPE;
buttonData ← NEW[ScalarButtonClientObj ← [NIL, value, action, on, NIL, gargoyleData]];
name ← IO.PutFR["%6.1f", [real[value]]];
IF Rope.Equal[Rope.Substr[name, Rope.Length[name]-2, 2], ".0"]
THEN
name ← Rope.Substr[name, 0, Rope.Length[name]-2];
button ← TiogaButtons.AppendToButton[
button: prevButton,
rope: Rope.Concat[name, " "],
looks: "",
proc: ToggleScalar,
clientData: buttonData,
fork: FALSE];
prevButtonData ← NARROW[prevButton.clientData];
IF prevButtonData #
NIL
THEN prevButtonData.next ← buttonData;
prevButtonData will be NIL after the Header button.
buttonData.button ← button;
};
ToggleScalar: TiogaButtons.TiogaButtonProc ~ {
button: TiogaButtons.TiogaButton ← NARROW[parent];
buttonData: ScalarButtonClient ← NARROW[clientData];
gargoyleData: GargoyleData ← buttonData.gargoyleData;
GGUserInput.GeneralDispatch[buttonData.action, gargoyleData];
};
BuildScalarButtons:
PUBLIC
PROC [clientData:
REF
ANY, header: Rope.
ROPE, scalarButtonList:
LIST
OF ScalarButton]
RETURNS [bigStructure: ScalarButtonClient] = {
gargoyleData: GargoyleData ← NARROW[clientData];
prevButton: TiogaButtons.TiogaButton;
viewer: Viewer;
viewer ← MakeViewer[gargoyleData];
prevButton ← HeaderButton[viewer, header, gargoyleData];
bigStructure ← NIL;
FOR list:
LIST
OF ScalarButton ← scalarButtonList, list.rest
UNTIL list =
NIL
DO
prevButton ← AddScalarButton[prevButton, list.first.value, list.first.action, list.first.init = on, gargoyleData];
IF list = scalarButtonList THEN bigStructure ← NARROW[prevButton.clientData];
ENDLOOP;
gargoyleData.height ← gargoyleData.height + entryHeight;
};
Enumerated Types buttons (like Choice Buttons, but built for the new User Interface Architecture (queues atoms onto the slack process queue and waits for them to return before acting. Thus, the buttons can be "pushed" from the TIP Table as well as with the mouse.
BuildEnumTypeSelection:
PUBLIC
PROC [viewer: ViewerClasses.Viewer, x, y:
CARDINAL, title:
Rope.ROPE, buttonNames: ButtonList, default:
Rope.ROPE, borderOnButtons:
BOOL, atom: ATOM, clientdata:
REF
ANY, style: StyleChoice, allInOneRow:
BOOL, maxWidth:
INTEGER]
RETURNS [EnumTypeRef] = {
foundDefault: BOOL ← FALSE;
stateInfo: EnumTypeRef ← NEW[EnumTypeRec];
DefaultFound:
PROC[currentName, defaultName:
Rope.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 ← Buttons.Create[info: [
name: eachButton.first,
wx: startX,
wy: stateInfo.nexty,
default the width so that it will be computed for us
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;
Buttons.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 {
-- create a title button
stateInfo.type ← flipThruWithTitle;
titleButton ← Buttons.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;
now set up the initial button
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
width properly (unfortunately when re-labeling a button, the width remains the same)
wh: entryHeight,
parent: viewer,
border: FALSE
]]
ELSE stateInfo.flipLabel ← Buttons.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.atom ← atom;
stateInfo.namesOfButtons ← buttonNames;
stateInfo.clientdata ← clientdata;
IF default =
NIL
THEN
-- set it to be the first name of the list of button names
default ← buttonNames.first;
SELECT style
FROM
menuSelection => BuildMenuSelection;
flipThru => BuildFlipThru;
ENDCASE => ERROR;
signal that the default didn't exist if necessary
IF ~foundDefault THEN SIGNAL DefaultDoesntExist;
stateInfo.nexty ← stateInfo.nexty + entryHeight;
RETURN[stateInfo];
};
FlipThruButtonProc: Buttons.ButtonProc = {
info: EnumTypeRef ← NARROW[clientData];
gargoyleData: GargoyleData ← NARROW[info.clientdata];
IF mouseButton=red
THEN
GGUserInput.GeneralDispatch[LIST[info.atom, $FlipForward, info], gargoyleData]
ELSE GGUserInput.GeneralDispatch[LIST[info.atom, $FlipBackward, info], gargoyleData];
};
TimeToFlipThru:
PUBLIC
PROC [event:
LIST
OF
REF
ANY] = {
info: EnumTypeRef ← NARROW[event.rest.first];
IF event.first = $FlipForward
THEN
Display the next name in succession
ViewerTools.SetContents[viewer: info.flipLabel, contents:
GetNextName[info.namesOfButtons, info.flipLabel.name]]
ELSE
IF event.first = $FlipBackward
THEN
ViewerTools.SetContents[viewer: info.flipLabel, contents:
GetPrevName[info.namesOfButtons, info.flipLabel.name]];
};
UpdateChoiceButtons:
PUBLIC
PROC [viewer: ViewerClasses.Viewer, enumTypeInfo: EnumTypeRef, newName:
Rope.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 => Buttons.ReLabel[button: enumTypeInfo.flipLabel, newName: newName];
ENDCASE => ERROR;
};
DefaultDoesntExist: PUBLIC SIGNAL = CODE;
ChoiceDoesntExist: PUBLIC SIGNAL = CODE;
ButtonsCannotBeUpdated: PUBLIC SIGNAL = CODE;
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];
};
MenuSelectionProc: Buttons.ButtonProc = {
info: EnumTypeRef ← NARROW[clientData];
viewer: ViewerClasses.Viewer ← NARROW[parent];
SwitchButtons[info.buttonOn, viewer]; -- viewer is the button which was just selected
info.buttonOn ← viewer;
Notify client that change has taken place if the client provided a proc for doing this
IF info.proc # NIL THEN info.proc[viewer.name, info.clientdata];
};
GetNextName:
PRIVATE
PROC[listOfNames: ButtonList, currentName:
Rope.ROPE]
RETURNS
[Rope.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
find the current button name in list of button names
IF eachButton.rest =
NIL
THEN
we are at the end of our list, cycle around to the beginning
RETURN [listOfNames.first]
ELSE RETURN [(eachButton.rest).first];
};
GetPrevName:
PRIVATE
PROC[listOfNames: ButtonList, currentName:
Rope.ROPE]
RETURNS
[Rope.ROPE] = {
prev: Rope.ROPE ← listOfNames.first;
eachName: ButtonList ← listOfNames.rest;
WHILE (eachName #
NIL)
AND ~Rope.Equal[eachName.first, currentName,
FALSE]
DO
prev ← eachName.first;
eachName ← eachName.rest;
ENDLOOP;
RETURN[prev];
};
on: ATOM = $WhiteOnBlack;
off: ATOM = $BlackOnWhite;
SwitchButtons:
PRIVATE
PROC[oldButton, newButton: Buttons.Button] = {
merely changes the physical appearance of the buttons
Switch off the previously selected button
Buttons.SetDisplayStyle[button: oldButton, style: off];
switch "on" the newly selected button
Buttons.SetDisplayStyle[button: newButton, style: on];
};
END.