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: BOOLTRUE, 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: BOOLFALSE;
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.