ChoiceButtonsImpl.mesa
Written by Linda Gass on August 30, 1982 1:21 pm
Last Edit by McGregor July 21, 1983 4:03 pm
Last Edited by: Maxwell, January 3, 1983 12:35 pm
DIRECTORY
Buttons USING [Button, ButtonProc, Create, ReLabel, SetDisplayStyle],
ChoiceButtons,
Containers USING [ChildXBound],
Labels USING [Create, Label, Set],
Rope USING [ROPE, Equal],
ViewerClasses USING [Viewer],
ViewerOps USING [AddProp, EnumProc, EnumerateChildren, FetchProp],
ViewerTools USING [MakeNewTextViewer, SetContents, SetSelection],
VFonts USING [CharWidth, Font, FontHeight, RopeWidth, defaultFont];
ChoiceButtonsImpl: CEDAR PROGRAM
IMPORTS Buttons, Containers, Labels, Rope, ViewerOps, ViewerTools, VFonts
EXPORTS ChoiceButtons =
BEGIN OPEN ChoiceButtons;
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
-- their corresponding labels
on: ATOM = $WhiteOnBlack;
off: ATOM = $BlackOnWhite;
DefaultDoesntExist: PUBLIC SIGNAL = CODE;
ChoiceDoesntExist: PUBLIC SIGNAL = CODE;
ButtonsCannotBeUpdated: PUBLIC SIGNAL = CODE;
---- ---- ---- ---- ---- ---- ---- ----
BuildEnumTypeSelection:
PUBLIC
PROC [viewer: ViewerClasses.Viewer, x, y:
CARDINAL, title: Rope.
ROPE, buttonNames: ButtonList, default: Rope.
ROPE, borderOnButtons:
BOOLEAN, notifyClientProc: SelectionNotifierProc, permit: EnumPermissionProc, clientdata:
REF
ANY, style: StyleChoice, allInOneRow:
BOOLEAN, maxWidth:
INTEGER]
RETURNS [EnumTypeRef] =
BEGIN
foundDefault: BOOLEAN ← FALSE;
stateInfo: EnumTypeRef ← NEW[EnumTypeRec];
DefaultFound:
PROC[currentName, defaultName: Rope.
ROPE]
RETURNS [
BOOLEAN] =
BEGIN
IF Rope.Equal[currentName, defaultName, FALSE] THEN RETURN [foundDefault ← TRUE]
ELSE RETURN [FALSE];
END;
BuildMenuSelection: PROC = BEGIN
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.RopeWidth[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;
END;
BuildFlipThru: PROCEDURE = BEGIN
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;
END;
stateInfo.nextx ← x;
stateInfo.nexty ← y;
stateInfo.proc ← notifyClientProc;
stateInfo.namesOfButtons ← buttonNames;
stateInfo.clientdata ← clientdata;
stateInfo.permission ← permit;
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];
END;
MenuSelectionProc: Buttons.ButtonProc =
BEGIN
info: EnumTypeRef ← NARROW[clientData];
viewer: ViewerClasses.Viewer ← NARROW[parent];
IF info.permission # NIL THEN {
IF info.permission[info] THEN { -- don't do anything without permission first
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];
}}
ELSE {
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];
};
END;
SwitchButtons:
PRIVATE
PROCEDURE[oldButton, newButton: Buttons.Button] =
BEGIN
-- 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;
FlipThruButtonProc: Buttons.ButtonProc =
BEGIN
info: EnumTypeRef ← NARROW[clientData];
IF info.permission # NIL THEN {
IF info.permission[info] THEN { -- don't do anything without permission first
IF mouseButton=red THEN
-- Display the next name in succession
ViewerTools.SetContents[viewer: info.flipLabel, contents:
GetNextName[info.namesOfButtons, info.flipLabel.name]]
ELSE -- Display the previous name
ViewerTools.SetContents[viewer: info.flipLabel, contents:
GetPrevName[info.namesOfButtons, info.flipLabel.name]];
-- Notify client that change has taken place if the client provided a proc for doing this
IF info.proc # NIL THEN info.proc[info.flipLabel.name, info.clientdata];
}}
ELSE {
IF mouseButton=red THEN
-- Display the next name in succession
ViewerTools.SetContents[viewer: info.flipLabel, contents:
GetNextName[info.namesOfButtons, info.flipLabel.name]]
ELSE -- display the previous name
ViewerTools.SetContents[viewer: info.flipLabel, contents:
GetPrevName[info.namesOfButtons, info.flipLabel.name]];
-- Notify client that change has taken place if the client provided a procedure for doing this
IF info.proc # NIL THEN info.proc[info.flipLabel.name, info.clientdata];
};
END;
UpdateChoiceButtons:
PUBLIC
PROCEDURE [viewer: ViewerClasses.Viewer, enumTypeInfo: EnumTypeRef, newName: Rope.
ROPE] =
BEGIN
newButton: Buttons.Button ← NIL;
GetDesiredButton: ViewerOps.EnumProc = BEGIN
IF (Rope.Equal[v.name, newName, FALSE]) AND
(ViewerOps.FetchProp[v, $ChoiceButtons] = enumTypeInfo) THEN {
newButton ← v;
RETURN [FALSE];
};
END;
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;
END;
GetSelectedButton:
PUBLIC
PROC [handle: EnumTypeRef]
RETURNS [Rope.
ROPE] = {
IF handle.type = menuStyle THEN {
IF handle.buttonOn = NIL THEN ERROR
ELSE RETURN [handle.buttonOn.name]
}
ELSE {
IF handle.flipLabel = NIL THEN ERROR
ELSE RETURN[handle.flipLabel.name]
};
};
GetNextName:
PRIVATE
PROC[listOfNames: ButtonList, currentName: Rope.
ROPE]
RETURNS
[Rope.
ROPE] =
BEGIN
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];
END;
GetPrevName:
PRIVATE
PROC[listOfNames: ButtonList, currentName: Rope.
ROPE]
RETURNS
[Rope.
ROPE] =
BEGIN
prev: Rope.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];
END;
MaxNameWidth:
PRIVATE
PROC[listOfNames: ButtonList]
RETURNS [
CARDINAL] =
BEGIN
eachButton: ButtonList ← listOfNames;
maxWidth: CARDINAL;
IF eachButton # NIL THEN {
maxWidth ← VFonts.RopeWidth[eachButton.first, VFonts.defaultFont];
eachButton ← eachButton.rest }
ELSE RETURN [0];
WHILE (eachButton # NIL) DO
IF VFonts.RopeWidth[eachButton.first, VFonts.defaultFont] > LOOPHOLE[maxWidth, INTEGER] THEN
maxWidth ← VFonts.RopeWidth[eachButton.first, VFonts.defaultFont];
eachButton ← eachButton.rest;
ENDLOOP;
maxWidth ← maxWidth + VFonts.CharWidth['M]; -- add on a little extra just to be sure
RETURN[maxWidth];
END;
BuildTriStateButton:
PUBLIC
PROCEDURE [viewer: ViewerClasses.Viewer, x,y:
INTEGER, name: Rope.
ROPE]
RETURNS [stateInfo: ThreeStateRef, nextX:
INTEGER] = {
tempButton: Buttons.Button;
stateInfo ← NEW[ThreeState];
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: 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] =
BEGIN
SELECT state FROM
on => Buttons.SetDisplayStyle[button: buttonSelected, style: $WhiteOnBlack];
off => Buttons.SetDisplayStyle[button: buttonSelected, style: $BlackOnWhite];
default => Buttons.SetDisplayStyle[button: buttonSelected, style: $BlackOnGrey];
ENDCASE => ERROR;
END;
NextState:
PROC [state: StateType]
RETURNS [StateType] =
BEGIN
IF state = LAST[StateType] THEN RETURN[FIRST[StateType]]
ELSE RETURN[SUCC[state]];
END;
TriSwitchProc: Buttons.ButtonProc =
BEGIN
handle: ThreeStateRef ← NARROW[clientData];
handle.state ← NextState[handle.state];
-- Indicate which state this button is in.
SetButtonState[handle.button, handle.state];
END;
BuildTextPrompt:
PUBLIC
PROCEDURE [viewer: ViewerClasses.Viewer, x,y:
CARDINAL, title: Rope.
ROPE, default: Rope.
ROPE, font: VFonts.Font, textViewerWidth:
INTEGER, clientdata:
REF
ANY, notify: SelectionNotifierProc, permit: PromptPermissionProc]
RETURNS [promptData: PromptDataRef] =
BEGIN
height: INTEGER ← VFonts.FontHeight[font] + 6;
promptData ← NEW[PromptDataRec];
promptData.promptButton ← Buttons.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;
END;
PromptProc: Buttons.ButtonProc =
BEGIN
force the selection into the user input field (the text viewer). Depending on which mouse button this viewer button was selected with, do different things
handle: PromptDataRef ← NARROW[clientData];
viewer: ViewerClasses.Viewer ← NARROW[parent];
IF handle.permission # NIL THEN {
IF handle.permission[handle] THEN { -- dont do anything without permission
IF mouseButton=red THEN -- make pending delete
ViewerTools.SetSelection[handle.textViewer]
ELSE -- notify the client if proc provided.
IF handle.notify # NIL THEN handle.notify[viewer.name, handle.clientdata];
}}
ELSE {
IF mouseButton=red THEN -- make pending delete
ViewerTools.SetSelection[handle.textViewer]
ELSE -- notify the client if proc provided.
IF handle.notify # NIL THEN handle.notify[viewer.name, handle.clientdata];
}
END;
END.