ButtonClassesImpl.mesa
Copyright Ó 1989, 1990, 1992 by Xerox Corporation. All rights reserved.
Contents: EmbeddedButtons button classes
Last edited by Goodisman, August 18, 1989 1:57:51 pm PDT
Kenneth A. Pier, August 27, 1990 4:18 pm PDT
Bier, July 24, 1992 6:26 pm PDT
Doug Wyatt, April 16, 1992 11:12 am PDT
DIRECTORY
Atom, AtomButtons, AtomButtonsTypes, BasicTime, ButtonClassesCommon, CedarProcess, CodeTimer, Convert, EBButtonClasses, EBEvent, EBLanguage, EBMesaLisp, EBTypes, EmbeddedButtons, Imager, InputFocus, IO, List, PopUpButtons, Process, RefTab, Rope, RuntimeError, ScreenCoordsTypes, SimpleFeedback, TIPPrivate, TIPTypes, TIPUser, UserInput, UserInputLookahead, UserInputOps, ViewerClasses;
ButtonClassesImpl: CEDAR MONITOR
IMPORTS Atom, AtomButtons, BasicTime, ButtonClassesCommon, CedarProcess, CodeTimer, Convert, EBButtonClasses, EBLanguage, EBMesaLisp, EmbeddedButtons, Imager, InputFocus, IO, PopUpButtons, Process, Rope, RuntimeError, SimpleFeedback, UserInputLookahead, UserInputOps, TIPPrivate, TIPUser
EXPORTS ButtonClassesCommon, EBTypes =
BEGIN
ButtonInfo: TYPE = EBTypes.ButtonInfo;
Context: TYPE = EBTypes.Context;
EventRep: PUBLIC TYPE = EBEvent.EventRep; -- for EBTypes
HandleRep: PUBLIC TYPE = UserInputPrivate.Rep; -- for UserInput
ROPE: TYPE = Rope.ROPE;
Variable: TYPE = EBLanguage.Variable;
VariableObj: TYPE = EBLanguage.VariableObj;
VariableType: TYPE = EBLanguage.VariableType;
VariableTypeObj: TYPE = EBLanguage.VariableTypeObj;
VariableTable: TYPE = EBLanguage.VariableTable;
TIPScreenCoords: TYPE = ScreenCoordsTypes.TIPScreenCoords;
ActiveClientData: TYPE = REF ActiveClientDataRec;
ActiveClientDataRec: TYPE = RECORD[
buttonInfo: ButtonInfo,
instanceData: REF];
Used for packing information away to be passed through other package's clientData fields.
Pop-up State Button Class
Behavior:
Pop-up state buttons have both a pop-up menu and a state. The menu lists the possible states of the button. By selecting a given menu item (either by slow-clicking or fast-clicking; see the PopUp class below) the user moves the button to the new state.
Feedback:
Pop-up state buttons always succeed. Either they change state if a selection was made or they don't. The Feedback section of these buttons usually describes how to display each possible state value.
Example: PopUpStateButton
Pop-up state button class record definition
popUpStateClass: EBButtonClasses.ButtonClass ¬ NEW[EBButtonClasses.ButtonClassObj ¬ [
name: $PopUpStateButton,
instantiate: PopUpStateInstantiate,
unparseInstanceData: PopUpStateUnparse,
handleEvent: PopUpStateHandle,
getValue: PopUpStateGet,
setValue: PopUpStateSet,
defaultBehavior: PopUpStateDefaultBehavior]];
PopUpStateData: TYPE = REF PopUpStateDataObj;
PopUpStateDataObj: TYPE = RECORD[
popUp: REF,
multi: Multi,
newValue: REF ¬ NIL
];
bigFont: Imager.Font ¬ Imager.FindFontScaled["xerox/tiogafonts/helvetica10", 1.0];
PopUpStateInstantiate: EBButtonClasses.InstantiateProc = {
PROC [buttonInfo: ButtonInfo, language: ATOM, languageVersion: NAT, initFeedback: BOOLFALSE] RETURNS [instance: REF];
menuName: ROPE;
choices: AtomButtons.PopUpChoices;
self: PopUpStateData;
quickClickEnabled: BOOL ¬ TRUE;
IF language # $Poppy THEN RETURN[NIL];
self ¬ NEW[PopUpStateDataObj];
self.multi ¬ NEW[MultiRec ¬ [
name: NIL,
nameExists: FALSE,
value: NEW[VariableObj ¬ [
type: NEW[VariableTypeObj[3]],
value: NEW[INT ¬ 1]]],
message: NIL,
mouseInside: FALSE,
defaultDirection: FALSE -- down
]];
self.multi.value.type.class ¬ enumerated;
FOR i: INT IN [1..3] DO
self.multi.value.type.enumeration[i-1] ¬ NEW[INT ¬ i];
ENDLOOP;
MultiInstantiateAux[buttonInfo, language, languageVersion, self.multi, initFeedback];
BEGIN
menuRope: ROPE ¬ EmbeddedButtons.GetFieldRope[$Menu, buttonInfo];
IF menuRope = NIL THEN {
choices ¬ PopUpStateMakeMenu[self.multi];
menuName ¬ "Values";
}
ELSE {
ropeStream: IO.STREAM ¬ IO.RIS[menuRope];
object: REF;
quickClickRope: ROPE ¬ EmbeddedButtons.GetFieldRope[$QuickClickEnabled, buttonInfo];
quickClickEnabled ¬ IF quickClickRope = NIL THEN TRUE
ELSE Convert.BoolFromRope[quickClickRope ! Convert.Error => {quickClickEnabled ¬ TRUE; CONTINUE}];
object ¬ EBMesaLisp.Parse[ropeStream].val;
IF object = NIL THEN {
menuName ¬ "DummyName";
choices ¬ LIST[[action: LIST[$Dummy], actionImage: "No Action", doc: "Embedded Buttons couldn't parse the Menu entry for this button"]];
}
ELSE [menuName, choices] ¬ PopUpStateParseMenu[object, menuRope];
};
END;
BEGIN
entry: AtomButtons.ButtonLineEntry ¬ [popUpButton[
name: menuName,
choices: choices,
help: PopUpButtons.noHelp, -- providing this field saves lots of compute time in PCedar
font: bigFont,
disableDecoding: NOT quickClickEnabled
]];
self.popUp ¬ AtomButtons.BuildPopUp[
clientData: self,
handleProc: PopUpStatePassEventToApplication,
paint: PopUpPaint,
inButton: PopUpInButton,
entry: entry,
clientPackageName: "ButtonClasses"];
END;
RETURN[self];
};
PopUpStateMenuAction: TYPE = REF PopUpStateMenuActionObj;
PopUpStateMenuActionObj: TYPE = RECORD [
count: NAT,
message: LIST OF REF,
value: REF
];
GetMenuListAndName: PROC [obj: REF, instanceData: ROPE] RETURNS [o: LIST OF REF, menuName: ROPE ¬ NIL, success: BOOL ¬ TRUE] = {
Make sure that the object is a list, since a menu must be.
IF NOT ISTYPE[obj, LIST OF REF] THEN {
EmbeddedButtons.Error["Pop-up button: Invalid ButtonData!", Rope.Concat["The ButtonData for this pop-up button is invalid:\n", instanceData]];
RETURN[NIL, NIL, FALSE];
};
o ¬ NARROW[obj];
Get the menu name if there is one. If o.first is NIL, we assume that it is a blank first entry rather than a blank menu name.
IF o.first # NIL AND ISTYPE[o.first, ROPE] THEN {
menuName ¬ NARROW[o.first];
o ¬ o.rest;
};
};
GetMessageAsList: PROC [entry: LIST OF REF] RETURNS [event: LIST OF REF] = {
IF entry.first = NIL THEN event ¬ NIL
ELSE {
WITH entry.first SELECT FROM
lor: LIST OF REF => event ¬ lor;
ENDCASE => event ¬ LIST[entry.first];
};
};
GetMenuEntry: PROC [entry: LIST OF REF] RETURNS [menuEntry: ROPE, success: BOOL ¬ TRUE] = {
IF entry.rest = NIL THEN RETURN[NIL, FALSE];
IF NOT ISTYPE[entry.rest.first, ROPE] THEN RETURN[NIL, FALSE];
menuEntry ¬ NARROW[entry.rest.first];
};
GetDocString: PROC [entry: LIST OF REF] RETURNS [docString: ROPE ¬ NIL, success: BOOL ¬ TRUE] = {
IF entry.rest.rest = NIL THEN docString ¬ ""
ELSE {
IF NOT ISTYPE[entry.rest.rest.first, ROPE] THEN RETURN[NIL, FALSE];
docString ¬ NARROW[entry.rest.rest.first];
};
};
PopUpStateParseMenu: PROC [obj: REF, instanceData: ROPE] RETURNS [menuName: ROPE ¬ NIL, choices: AtomButtons.PopUpChoices ¬ NIL] = {
o: LIST OF REF;
success: BOOL ¬ TRUE;
choice: AtomButtons.PopUpChoice;
i: NAT ¬ 0;
[o, menuName, success] ¬ GetMenuListAndName[obj, instanceData];
IF NOT success THEN RETURN;
Pull out the menu fields from the list, building data structures
FOR list: LIST OF REF ¬ o, list.rest UNTIL list = NIL DO -- for each entry...
entry: LIST OF REF;
event: LIST OF REF;
menuEntry: ROPE;
docString: ROPE;
BEGIN
action: PopUpStateMenuAction;
IF NOT ISTYPE[list.first, LIST OF REF] THEN GOTO FieldTypeError;
entry ¬ NARROW[list.first];
IF entry # NIL THEN { -- get the application message, entry name, and doc string
event ¬ GetMessageAsList[entry];
[menuEntry, success] ¬ GetMenuEntry[entry];
IF NOT success THEN GOTO FieldTypeError;
[docString, success] ¬ GetDocString[entry];
IF NOT success THEN GOTO FieldTypeError;
}
ELSE {event ¬ NIL; menuEntry ¬ NIL; docString ¬ NIL};
action ¬ NEW[PopUpStateMenuActionObj ¬ [
count: i,
message: event,
value: NIL
]];
choice ¬ [action: LIST[action], actionImage: menuEntry, doc: docString, font: bigFont];
choices ¬ PopUpAppend[choices, LIST[choice]];
EXITS
FieldTypeError => {
EmbeddedButtons.Error["Pop-up buttons: Invalid field in menu!", Rope.Concat["The menu description for this pop-up button contains an invalid field:\n", instanceData]];
};
END;
i ¬ i + 1;
ENDLOOP;
};
PopUpStateMakeMenu: PROC [multi: Multi] RETURNS [choices: AtomButtons.PopUpChoices ¬ NIL] = {
AddChoice: PROC [action: ATOM, entry: ROPE, doc: ROPE, count: NAT] = {
choice: AtomButtons.PopUpChoice;
pusmAction: PopUpStateMenuAction;
pusmAction ¬ NEW[PopUpStateMenuActionObj ¬ [
count: count,
message: LIST[action],
value: NIL
]];
choice ¬ [action: LIST[pusmAction], actionImage: entry, doc: doc, font: bigFont];
choices ¬ PopUpAppend[choices, LIST[choice]];
};
SELECT multi.value.type.class FROM
enumerated => {
FOR i: NAT IN [0..multi.value.type.count) DO
docString, menuEntry: ROPE;
choice: AtomButtons.PopUpChoice;
action: PopUpStateMenuAction;
WITH multi.value.type[i] SELECT FROM
rope: Rope.ROPE => menuEntry ¬ rope;
atom: ATOM => menuEntry ¬ Atom.GetPName[atom];
ENDCASE => menuEntry ¬ "no value";
docString ¬ IO.PutFR1["Set to value %g", [rope[menuEntry]] ];
action ¬ NEW[PopUpStateMenuActionObj ¬ [
count: i,
message: LIST[$ButtonSetValue, menuEntry],
value: multi.value.type[i]
]];
choice ¬ [action: LIST[action], actionImage: menuEntry, doc: docString, font: bigFont];
choices ¬ PopUpAppend[choices, LIST[choice]];
ENDLOOP;
};
integer => {
AddChoice[$ButtonValueUp, "Up", "increase value by 1", 0];
AddChoice[$ButtonValueUp, "Up", "increase value by 1", 1];
AddChoice[$ButtonValueDown, "Down", "decrease value by 1", 2];
};
real => {
AddChoice[$ButtonValueUp, "Up", "multiply value by 2", 0];
AddChoice[$ButtonValueUp, "Up", "multiply value by 2", 1];
AddChoice[$ButtonValueDown, "Down", "divide value by 2", 2];
};
ENDCASE => NULL;
};
PassEvent: PROC [msg: REF, buttonInfo: ButtonInfo] = {
msgAsList: LIST OF REF;
WITH msg SELECT FROM
list: LIST OF REF => msgAsList ¬ list;
ENDCASE => msgAsList ¬ LIST[msg];
EmbeddedButtons.PassEventToApplication[msgAsList, buttonInfo];
};
PopUpStatePassEventToApplication: AtomButtons.HandlePopUpProc = {
[button: REF, clientData: REF, event: LIST OF REF]
If the event is one of ours, then update the click direction and extract the real application message before passing on the event. clientData is a PopUpStateData
PreprocessEvent: PROC [event: LIST OF REF] RETURNS [newEvent: LIST OF REF] = {
IF event = NIL THEN RETURN[event];
IF event.first = NIL THEN RETURN[event];
WITH event.first SELECT FROM
ours: PopUpStateMenuAction => {
self: PopUpStateData ¬ NARROW[clientData];
newEvent ¬ ours.message;
self.newValue ¬ ours.value;
IF self.newValue = NIL THEN {
IF (ours.count MOD 3 = 2) THEN self.multi.defaultDirection ¬ TRUE
ELSE self.multi.defaultDirection ¬ FALSE;
};
};
ENDCASE => newEvent ¬ event;
};
WITH button SELECT FROM
buttonInfo: ButtonInfo => {
message: REF;
event ¬ PreprocessEvent[event];
EBLanguage.SetSystemValue[clickContext, $MessageReceiver, EmbeddedButtons.GetApplications[buttonInfo]];
message ¬ EBLanguage.Evaluate[event, buttonInfo, clientData, clickContext];
Evaluate any angle bracket "<>" expressions in "event"
IF message # NIL THEN PassEvent[message, buttonInfo];
};
ENDCASE => {
EmbeddedButtons.Error["Pop-up buttons: button is wrong type (PopUpStatePassEventToApplication)"];
};
};
PopUpStateUnparse: EBButtonClasses.UnparseInstanceDataProc = {
PROC [instanceData: REF, buttonInfo: ButtonInfo ← NIL, language: ATOMNIL, languageVersion: NAT ← 0] RETURNS [ROPENIL];
If language is Poppy, unparses fields into symbol table of buttonInfo. Tries very hard not to unparse something if it has a default value so that a button specified with Name: Gravity will get unparsed with only Name: Gravity unless it is necessary to give more information.
self: PopUpStateData;
rope: ROPE;
ppListPoppy: LIST OF REF;
multi: Multi;
Get self.
IF instanceData = NIL THEN RETURN[""];
IF NOT ISTYPE[instanceData, PopUpStateData] THEN {
EmbeddedButtons.Error["PopUpState buttons: Internal Error, button is wrong type (PopUpStateUnparse)"];
RETURN[""];
};
self ¬ NARROW[instanceData];
multi ¬ self.multi;
Set pretty print lists
ppListPoppy ¬ LIST[
"", refFALSE,
"\n ", refFALSE];
Unparse name
IF multi.name # NIL AND NOT ButtonClassesCommon.IsGeneratedRope[Atom.GetPName[multi.name]]
THEN EmbeddedButtons.SetFieldRope[$Name, EBMesaLisp.Unparse[multi.name], buttonInfo];
Unparse value
SELECT language FROM
$Poppy => {
tableRef: REF;
table: VariableTable;
type: VariableType;
tableRef ¬ EmbeddedButtons.GetFieldRef[$Variables, buttonInfo];
IF ISTYPE[tableRef, VariableTable] AND tableRef # NIL THEN
table ¬ NARROW[tableRef]
ELSE {
table ¬ EBLanguage.CreateVariableTable[];
EmbeddedButtons.SetFieldRef[$Variables, table, buttonInfo];
};
IF multi.value.type = booleanEnumeratedType THEN {
type ¬ NEW[VariableTypeObj[0]];
type.class ¬ boolean;
EBLanguage.SetVariable[table, $Value, NEW[VariableObj ¬ [
value: multi.value.value,
type: type]]];
}
ELSE
EBLanguage.SetVariable[table, $Value, multi.value];
};
ENDCASE;
Unparse transitions
SELECT language FROM
$Poppy =>
IF multi.message # NIL THEN
EmbeddedButtons.SetFieldRope[$UpClickMessage, EBMesaLisp.Unparse[multi.message, ppListPoppy], buttonInfo];
ENDCASE;
Generate rope
SELECT language FROM
$Poppy => rope ¬ NIL; -- A button class doesn't know enough to unparse any further. Embedded Buttons will.
ENDCASE;
RETURN[rope];
};
PopUpStateHandle: EBButtonClasses.HandleEventProc ~ {
ENABLE UNWIND => {
SimpleFeedback.Append[$EmbeddedButtons, oneLiner, $Error, "Pressing that PopUpButton caused an internal error."];
};
self: PopUpStateData ¬ NARROW[instanceData];
Produce any feedback associated with this mouse or keyboard action
ProduceStandardFeedback[event, buttonInfo];
Let pop-up buttons handle the rest
IF instanceData = NIL OR self.popUp = NIL THEN GOTO NoPop
ELSE {
maybeInstance: PopUpButtons.MaybeInstance ¬ PopUpButtons.QuaInstance[self.popUp];
IF NOT maybeInstance.is THEN GOTO NoPop;
PopUpButtons.RawNotify[buttonInfo, maybeInstance.inst, event.handle, event.action];
Will call PopUpStatePassEventToApplication with the menu selection
};
EXITS
NoPop => EmbeddedButtons.Error["This PopUpStateButton is broken (syntax problem?)"];
};
PopUpStateGet: EBButtonClasses.GetValueProc = {
PROC[instanceData: REF, buttonInfo: ButtonInfo] RETURNS [value: REF];
self: PopUpStateData;
multi: Multi;
Get self
IF instanceData = NIL THEN RETURN[NIL];
IF NOT ISTYPE[instanceData, PopUpStateData] THEN {
EmbeddedButtons.Error["PopUpState buttons: Internal error. Button is wrong type (PopUpStateGet)."];
RETURN[NIL];
};
self ¬ NARROW[instanceData];
multi ¬ self.multi;
name ← multi.name;
value ¬ multi.value.value;
EmbeddedButtons.RegisterNameValuePair[multi.name, value, buttonInfo];
};
PopUpStateSet: EBButtonClasses.SetValueProc = {
PROC[instanceData: REF, value: REF, buttonInfo: ButtonInfo] RETURNS[changed: BOOLFALSE];
self: PopUpStateData;
multi: Multi;
IF instanceData = NIL THEN RETURN;
IF NOT ISTYPE[instanceData, PopUpStateData] THEN {EmbeddedButtons.Error["PopUpState buttons: Internal error. Button is wrong type (PopUpStateSet)."]; RETURN};
self ¬ NARROW[instanceData];
multi ¬ self.multi;
IF multi.value.type.class = integer THEN {
WITH value SELECT FROM
i: REF INT => {
multi.value.value ¬ value;
changed ¬ TRUE;
EmbeddedButtons.FeedbackNotify[LIST[$Value, value], buttonInfo];
};
ENDCASE => EmbeddedButtons.Error[IO.PutFR["Attempt to set button %g (type INT) to %g", [rope[Atom.GetPName[multi.name]]], [rope[EBMesaLisp.Unparse[value]]] ]];
}
ELSE IF multi.value.type.class = real THEN {
WITH value SELECT FROM
i: REF REAL => {
multi.value.value ¬ value;
changed ¬ TRUE;
EmbeddedButtons.FeedbackNotify[LIST[$Value, value], buttonInfo];
};
ENDCASE => EmbeddedButtons.Error[IO.PutFR["Attempt to set button %g (type REAL) to %g", [rope[Atom.GetPName[multi.name]]], [rope[EBMesaLisp.Unparse[value]]] ]];
}
ELSE {
FOR i: NAT IN [0..multi.value.type.count) DO
IF EBLanguage.Equal[value, multi.value.type.enumeration[i]] THEN {
multi.value.value ¬ value;
changed ¬ TRUE;
EmbeddedButtons.FeedbackNotify[LIST[$Value, value], buttonInfo];
EXIT;
};
ENDLOOP;
};
};
PopUpStateDefaultBehavior: EBButtonClasses.DefaultBehaviorProc = {
PROC[instanceData: REF, buttonInfo: ButtonInfo];
self: PopUpStateData;
multi: Multi;
state: INT ¬ 0;
newValue: REF;
IF instanceData = NIL THEN RETURN;
IF NOT ISTYPE[instanceData, PopUpStateData] THEN {
EmbeddedButtons.Error["PopUpState: Internal error. Button is wrong type (PopUpStateDefaultBehavior)."];
RETURN;
};
self ¬ NARROW[instanceData];
multi ¬ self.multi;
IF self.newValue # NIL THEN {
newValue ¬ self.newValue;
}
ELSE {
IF multi.value.type.class = integer THEN {
oldValue: REF INT ¬ NARROW[multi.value.value];
IF NOT multi.defaultDirection THEN newValue ¬ NEW[INT ¬ oldValue­ +1]
ELSE newValue ¬ NEW[INT ¬ oldValue­ -1];
}
ELSE IF multi.value.type.class = real THEN {
oldValue: REF REAL ¬ NARROW[multi.value.value];
IF NOT multi.defaultDirection THEN newValue ¬ NEW[REAL ¬ oldValue­ *2.0]
ELSE newValue ¬ NEW[REAL ¬ oldValue­ /2.0];
}
ELSE {
FOR i: NAT IN [0..multi.value.type.count) DO
IF EBLanguage.Equal[multi.value.value, multi.value.type.enumeration[i]]
THEN { state ¬ i; EXIT };
ENDLOOP;
IF NOT multi.defaultDirection THEN {
state ¬ state + 1;
IF state >= multi.value.type.count THEN state ¬ 0; -- Greater than equal for safety?
}
ELSE {
state ¬ state - 1;
IF state < 0 THEN state ¬ multi.value.type.count-1;
};
newValue ¬ multi.value.type.enumeration[state];
};
};
IF multi.nameExists THEN
EmbeddedButtons.SetValue[multi.name, newValue, buttonInfo.doc]
ELSE {
changed: BOOL ¬ PopUpStateSet[instanceData, newValue, buttonInfo];
IF changed THEN EBButtonClasses.MarkButtonAsChanged[buttonInfo];
};
};
Guarded Button Class
Behavior:
Guarded buttons act much like the buttons implemented in Buttons.mesa. They perform a single action when clicked. If they are guarded, they must be clicked twice to trigger the action. The first click removes a guard, often displayed as a strike-through line. The second click causes the action to be performed.
Feedback:
Multi-state buttons generate the following events for feedback:
The standard feedback events (Down, Up, Enter, Exit) and
$Highlight -- when a mouse button goes down over the button
$UnHighlight -- when a mouse button goes up over the button
$Guard $Off -- when a mouse button goes up over the button for the first time
$Guard $On -- when the guard returns to the button after 5 seconds
$Pressed -- when a mouse button goes up over the button and it is unguarded
$Done -- when the button has finished performing its action
buttonClass: EBButtonClasses.ButtonClass ¬ NEW[EBButtonClasses.ButtonClassObj ¬ [
name: $GuardedButton,
instantiate: ButtonInstantiate,
unparseInstanceData: NIL,
handleEvent: ButtonHandle,
getValue: NIL,
setValue: NIL,
defaultBehavior: NIL]];
ObjectAsEvents: PROC [object: REF] RETURNS [events: LIST OF LIST OF REF] = {
WITH object SELECT FROM
es: LIST OF LIST OF REF => RETURN[es];
e: LIST OF REF => RETURN[LIST[e]];
ENDCASE => RETURN[LIST[LIST[object]]];
};
ButtonInstantiate: EBButtonClasses.InstantiateProc = {
PROC [buttonInfo: ButtonInfo, language: ATOM, languageVersion: NAT, initFeedback: BOOLFALSE] RETURNS [instance: REF];
Instantiate and return the button. The input rope is a EBLanguage expression describing the behavior of the button. See ButtonClasses.form for the form of this expression.
menuName: ROPE;
events: LIST OF AtomButtons.Event;
guarded: BOOL ¬ FALSE;
CodeTimer.StartInt[$GuardedInstantiate, $EmbeddedButtons];
SELECT language FROM
$Poppy => {
object: REF;
actionRope: ROPE ¬ EmbeddedButtons.GetFieldRope[$Message, buttonInfo];
guardedRope: ROPE ¬ EmbeddedButtons.GetFieldRope[$GuardMessage, buttonInfo];
ropeStream: IO.STREAM ¬ IO.RIS[actionRope];
object ¬ EBMesaLisp.Parse[ropeStream].val;
events ¬ ObjectAsEvents[object];
guarded ¬ guardedRope # NIL;
};
ENDCASE => {
CodeTimer.StopInt[$GuardedInstantiate, $EmbeddedButtons];
RETURN[NIL];
};
Build the button
BEGIN
entry: AtomButtons.ButtonLineEntry ¬ [button[
name: "",
events: events,
font: bigFont,
confirmProc: NIL -- overridden by the confirmProc argument below
]];
instance ¬ BuildButtonHandler[
clientData: NIL,
handleProc: ButtonPassEventToApplication,
confirmProc: IF guarded THEN ButtonConfirm ELSE NIL,
entry: entry,
clientPackageName: "ButtonClasses"];
END;
CodeTimer.StopInt[$GuardedInstantiate, $EmbeddedButtons];
RETURN[instance];
};
ButtonConfirm: AtomButtons.HandlePopUpProc = {
[button: REF, clientData: REF, event: LIST OF REF]
ENABLE RuntimeError.Uncaught => {
GOTO Error;
};
message: REF;
buttonInfo: ButtonInfo;
WITH button SELECT FROM
buttonInfo: ButtonInfo => {
confirmRope: ROPE ¬ EmbeddedButtons.GetFieldRope[$GuardMessage, buttonInfo];
ropeStream: IO.STREAM ¬ IO.RIS[confirmRope];
object: REF;
event: LIST OF REF;
EBLanguage.SetSystemValue[clickContext, $MessageReceiver, EmbeddedButtons.GetApplications[buttonInfo]];
object ¬ EBMesaLisp.Parse[ropeStream].val;
message ¬ EBLanguage.Evaluate[object, buttonInfo, clientData, clickContext];
IF message # NIL THEN PassEvent[message, buttonInfo];
};
ENDCASE =>
SimpleFeedback.Append[$EmbeddedButtons, oneLiner, $Error, "Pressing that guarded button caused an internal error."];
EXITS
Error => {
SimpleFeedback.Append[$EmbeddedButtons, oneLiner, $Error, "ButtonConfirm received an uncaught error."];
};
};
ButtonPassEventToApplication: AtomButtons.HandlePopUpProc = {
[button: REF, clientData: REF, event: LIST OF REF]
message: REF;
WITH button SELECT FROM
buttonInfo: ButtonInfo => {
EBLanguage.SetSystemValue[clickContext, $MessageReceiver, EmbeddedButtons.GetApplications[buttonInfo]];
message ¬ EBLanguage.Evaluate[event, buttonInfo, clientData, clickContext];
Evaluate any angle bracket "<>" expressions in "event"
IF message # NIL THEN PassEvent[message, buttonInfo];
};
ENDCASE => {
EmbeddedButtons.Error["Button is wrong type (ButtonPassEventToApplication)"];
};
};
ButtonHandle: EBButtonClasses.HandleEventProc = {
errorMsg: ROPE;
BEGIN
ENABLE BEGIN
RuntimeError.NarrowRefFault => {errorMsg ¬ "NarrowRefFault"; GOTO InternalError};
RuntimeError.NarrowFault => {errorMsg ¬ "NarrowFault"; GOTO InternalError};
RuntimeError.NilFault => {errorMsg ¬ "NilFault"; GOTO InternalError};
RuntimeError.BoundsFault => {errorMsg ¬ "BoundsFault"; GOTO InternalError};
RuntimeError.Uncaught => {errorMsg ¬ "Uncaught error"; GOTO InternalError};
RuntimeError.UnnamedError => {errorMsg ¬ "UnnamedError"; GOTO InternalError};
RuntimeError.UnnamedSignal => {errorMsg ¬ "UnnamedSignal"; GOTO InternalError};
UNWIND => {errorMsg ¬ "internal error"; GOTO InternalError};
END;
buttonData: ButtonData ¬ NARROW[instanceData];
ProduceStandardFeedback[event, buttonInfo];
Let AtomButtons handle the rest
IF instanceData = NIL THEN GOTO NoButton
ELSE {
success: BOOL ¬ PushButtonHandler[buttonInfo, buttonData, event];
IF NOT success THEN GOTO NoButton;
};
EXITS
NoButton => {
SimpleFeedback.Append[$EmbeddedButtons, oneLiner, $Error, "A button was pressed that has no button handler"];
};
InternalError => {
SimpleFeedback.Append[$EmbeddedButtons, oneLiner, $Error, Rope.Concat["Pressing that GuardedButton caused ", errorMsg]];
};
END;
};
BuildButtonHandler: PROC [clientData: REF, handleProc: HandleButtonProc, entry: ButtonLineEntry, confirmProc: HandleButtonProc, clientPackageName: Rope.ROPE] RETURNS [instance: REF] = {
WITH entry SELECT FROM
b: ButtonLineEntry.button => instance ¬ BuildButtonHandlerAux[clientData, handleProc, b.name, b.events, b.font, confirmProc, b.initProc, clientPackageName];
ENDCASE => ERROR;
};
ButtonLineEntry: TYPE = AtomButtons.ButtonLineEntry;
Event: TYPE = AtomButtons.Event;
InitButtonProc: TYPE = AtomButtonsTypes.InitButtonProc;
ButtonData: TYPE = REF ButtonDataObj;
ButtonDataObj: TYPE = RECORD [
clientData: REF,
events: LIST OF Event,
handleProc: HandleButtonProc,
name: Rope.ROPE,
font: Imager.Font,
confirmProc: HandleButtonProc,
initProc: InitButtonProc,
clientPackageName: Rope.ROPE,
inverted: BOOL ¬ FALSE,
state: GuardState,
guarded: BOOL ¬ FALSE,
greyCount: INTEGER ¬ 0,
buttonInfo: ButtonInfo ¬ NIL,
viewer: ViewerClasses.Viewer ¬ NIL,
displayStyle: DisplayStyle ¬ blackOnWhite
];
DisplayStyle: TYPE = {blackOnWhite, whiteOnBlack, blackOnGrey};
GuardState: TYPE = { guarded, arming, armed };
BuildButtonHandlerAux: PROC [clientData: REF, handleProc: HandleButtonProc, name: Rope.ROPE, events: LIST OF Event, font: Imager.Font, confirmProc: HandleButtonProc, initProc: InitButtonProc, clientPackageName: Rope.ROPE] RETURNS [instanceData: ButtonData] = {
choiceList, ptr: LIST OF PopUpButtons.Choice;
image: PopUpButtons.Image;
fakeViewer: ViewerClasses.Viewer;
fakeViewer ¬ NEW[ViewerClasses.ViewerRec];
instanceData ¬ NEW[ButtonDataObj ¬ [
clientData: clientData,
handleProc: handleProc,
name: name,
events: events,
font: font,
confirmProc: confirmProc,
guarded: confirmProc # NIL,
initProc: initProc,
clientPackageName: clientPackageName,
state: IF confirmProc = NIL THEN armed ELSE guarded,
buttonInfo: NIL, -- for now
viewer: fakeViewer
]];
fakeViewer.data ¬ instanceData;
};
PushButtonHandler: PROC [buttonInfo: ButtonInfo, instanceData: ButtonData,
event: EBEvent.Event] RETURNS [success: BOOL ¬ TRUE] = {
userEvent, buttonEvent: LIST OF REF;
Run the input action through TIP
tipParseInfo.inCreek ¬ event.handle;
userEvent ¬ TIPPrivate.WideMatchEvent[tipParseInfo, event.action­];
IF instanceData.handleProc # NIL THEN {
ButtonHandlerEvent[buttonInfo, instanceData, userEvent];
};
};
ButtonNotify: ViewerClasses.NotifyProc = {
PROC [self: Viewer, input: LIST OF REF];
data: ButtonData ¬ NARROW[self.data];
ButtonHandlerEvent[data.buttonInfo, data, input];
};
HandleButtonProc: TYPE = PROC [button: ButtonInfo, clientData: REF, event: LIST OF REF];
ButtonHandlerEvent: ENTRY PROC [self: ButtonInfo, data: ButtonData, input: LIST OF REF] = {
ENABLE BEGIN
UNWIND => InputFocus.ReleaseButtons[];
RuntimeError.Uncaught => GOTO Uncaught;
RuntimeError.NarrowRefFault => GOTO NarrowRefFault;
END;
button: ViewerClasses.MouseButton ¬ red;
shift, control: BOOL ¬ FALSE;
mouse: TIPScreenCoords;
IF data = NIL THEN RETURN;
data.buttonInfo ¬ self;
FOR list: LIST OF REF ¬ input, list.rest UNTIL list = NIL DO
WITH list.first SELECT FROM
x: ATOM => SELECT x FROM
$Blue => button ¬ blue;
$Control => control ¬ TRUE;
$Up => IF data.inverted THEN SELECT data.state FROM
guarded => {
data.state¬arming;
ViewerOps.PaintViewer[self, client, FALSE, $Invert];
EmbeddedButtons.FeedbackNotify[LIST[$Unhighlight], self];
data.inverted ¬ FALSE;
InputFocus.ReleaseButtons[];
TRUSTED {Process.Detach[FORK ArmButtonProc[data, self]]};
IF data.confirmProc # NIL THEN data.confirmProc[self, data.clientData, input];
};
arming=> NULL; -- no action
armed=> {
InputFocus.ReleaseButtons[];
ViewerOps.PaintViewer[self, client, FALSE, $Invert];
EmbeddedButtons.FeedbackNotify[LIST[$Unhighlight], self];
data.inverted ¬ FALSE;
IF data.guarded THEN {
data.state ¬ guarded;
ViewerOps.PaintViewer[self, client];
EmbeddedButtons.FeedbackNotify[LIST[$Guard, $On], self];
};
IF data.fork THEN TRUSTED {Process.Detach[FORK ButtonPusher[self,
data, data.handleProc, data.clientData, button, shift, control, TRUE]]}
ELSE
ButtonPusher[self, data, data.handleProc, data.clientData, input, FALSE];
};
ENDCASE;
$Down => {
IF ~data.inverted THEN {
InputFocus.CaptureButtons[ButtonNotify, tipParseInfo.tableHead, data.viewer];
ViewerOps.PaintViewer[self, client, FALSE, $Invert];
EmbeddedButtons.FeedbackNotify[LIST[$Highlight], self];
data.inverted ¬ TRUE;
}
ELSE {
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: TIPScreenCoords => mouse ¬ z;
ENDCASE => ERROR;
ENDLOOP;
EXITS
Uncaught => {
SimpleFeedback.Append[$EmbeddedButtons, oneLiner, $Error, "ButtonHandlerEvent received an uncaught error."];
};
NarrowRefFault => {
SimpleFeedback.Append[$EmbeddedButtons, oneLiner, $Error, "ButtonHandlerEvent received a NarrowRefFault."];
};
};
armingTime: Process.Milliseconds ¬ 100; -- cover removal time.
armedTime: Process.Milliseconds ¬ 5000; -- unguarded interval.
ArmButtonProc: ENTRY PROC [data: ButtonData, button: ButtonInfo] = {
assert: state=arming
IF data = NIL THEN RETURN;
ButtonWait[data, armingTime];
IF data.state = arming THEN {
data.state ¬ armed;
ViewerOps.PaintViewer[button, client];
EmbeddedButtons.FeedbackNotify[LIST[$Guard, $Off], button];
ButtonWait[data, armedTime];
};
IF data.state#guarded THEN {
data.state ¬ guarded;
ViewerOps.PaintViewer[button, client];
EmbeddedButtons.FeedbackNotify[LIST[$Guard, $On], button];
};
};
ButtonWait: INTERNAL PROC[data: ButtonData, msec: Process.Milliseconds] = TRUSTED {
buttonWaitCondition: CONDITION;
Process.SetTimeout[LONG[@buttonWaitCondition], Process.MsecToTicks[msec]];
WAIT buttonWaitCondition;
};
DoPush: PROC [button: ButtonInfo, instanceData: ButtonData, userEvent: LIST OF REF] = {
buttonEvent: LIST OF REF;
buttonEvent ¬ IF ButtonClassesCommon.CountMatches[LIST[$Red, $Up], userEvent] = 2 THEN instanceData.events.first
ELSE IF ButtonClassesCommon.CountMatches[LIST[$Yellow, $Up], userEvent] = 2 THEN IF instanceData.events.rest = NIL THEN instanceData.events.first ELSE instanceData.events.rest.first
ELSE IF ButtonClassesCommon.CountMatches[LIST[$Blue, $Up], userEvent] = 2 THEN IF instanceData.events.rest = NIL OR instanceData.events.rest.rest = NIL THEN instanceData.events.first ELSE instanceData.events.rest.rest.first
ELSE NIL;
IF instanceData.handleProc # NIL AND buttonEvent # NIL
THEN instanceData.handleProc[button, instanceData.clientData, buttonEvent];
};
ButtonPusher: PROC [button: ButtonInfo, myData: ButtonData, proc: HandleButtonProc, clientData: REF, userEvent: LIST OF REF, normalPriority: BOOL] = {
IF myData = NIL THEN RETURN;
myData.greyCount ¬ myData.greyCount + 1;
IF myData.displayStyle#blackOnGrey
THEN ViewerOps.PaintViewer[button, client];
THEN EmbeddedButtons.FeedbackNotify[LIST[$Pressed], button];
IF normalPriority THEN CedarProcess.SetPriority[normal];
DoPush[button, myData, userEvent ! ABORTED => CONTINUE];
myData.greyCount ¬ MAX[myData.greyCount - 1, 0];
IF myData.displayStyle#blackOnGrey
THEN ViewerOps.PaintViewer[button, client];
THEN EmbeddedButtons.FeedbackNotify[LIST[$Done], button];
};
Pop-up Button Class
Behavior:
Pop-up buttons have no state. When a pop-up button is quick-clicked, it sends an application message corresponding to the mouse button that was clicked and any modifier keys that were pressed (Shift, Control, etc.). If a mouse button is clicked and held on a pop-up button, the button "pops up" a two dimensional menu of the button's functions (from which the user can choose) and displays a documentation string for the choice at which the cursor is pointing.
Feedback:
Pop-up buttons generate no special feedback. Like all buttons, the generate $Done, $Warning, or $Error to indicate the success of the button action (see EBImpl).
Pop-up button class record definition
popUpClass: EBButtonClasses.ButtonClass ¬ NEW[EBButtonClasses.ButtonClassObj ¬ [
name: $PopUpButton,
instantiate: PopUpInstantiate,
unparseInstanceData: NIL,
handleEvent: PopUpHandle,
getValue: NIL,
setValue: NIL,
defaultBehavior: NIL]];
PopUpInstantiate: EBButtonClasses.InstantiateProc = {
PROC [buttonInfo: ButtonInfo, language: ATOM, languageVersion: NAT, initFeedback: BOOLFALSE] RETURNS [instance: REF];
Instantiate and return the button. The input rope is a EBLanguage expression describing the behavior of the pop-up button. See ButtonClasses.form for the form of this expression.
menuName: ROPE;
choices: AtomButtons.PopUpChoices;
quickClickEnabled: BOOL ¬ TRUE;
CodeTimer.StartInt[$PopUpInstantiate, $EmbeddedButtons];
CodeTimer.StartInt[$PopUpInstantiateParse, $EmbeddedButtons];
Get the menu name and the menu
SELECT language FROM
$Poppy => {
menuRope: ROPE ¬ EmbeddedButtons.GetFieldRope[$Menu, buttonInfo];
ropeStream: IO.STREAM ¬ IO.RIS[menuRope];
object: REF;
quickClickRope: ROPE ¬ EmbeddedButtons.GetFieldRope[$QuickClickEnabled, buttonInfo];
quickClickEnabled ¬ IF quickClickRope = NIL THEN TRUE
ELSE Convert.BoolFromRope[quickClickRope ! Convert.Error => {quickClickEnabled ¬ TRUE; CONTINUE}];
object ¬ EBMesaLisp.Parse[ropeStream].val;
IF object = NIL THEN {
menuName ¬ "DummyName";
choices ¬ LIST[[action: LIST[$Dummy], actionImage: "No Action", doc: "Embedded Buttons couldn't parse the Menu entry for this button"]];
}
ELSE [menuName, choices] ¬ PopUpParseMenu[object, menuRope];
};
ENDCASE => {
CodeTimer.StopInt[$PopUpInstantiateParse, $EmbeddedButtons];
CodeTimer.StopInt[$PopUpInstantiate, $EmbeddedButtons];
RETURN[NIL];
};
CodeTimer.StopInt[$PopUpInstantiateParse, $EmbeddedButtons];
Build the button
BEGIN
entry: AtomButtons.ButtonLineEntry ¬ [popUpButton[
name: menuName,
choices: choices,
help: PopUpButtons.noHelp, -- providing this field saves lots of compute time in PCedar
font: bigFont,
disableDecoding: NOT quickClickEnabled
]];
instance ¬ AtomButtons.BuildPopUp[
clientData: NIL,
handleProc: PopUpPassEventToApplication,
paint: PopUpPaint,
inButton: PopUpInButton,
entry: entry,
clientPackageName: "ButtonClasses"];
END;
CodeTimer.StopInt[$PopUpInstantiate, $EmbeddedButtons];
RETURN[instance];
};
PopUpParseMenu: PROC[obj: REF, instanceData: ROPE] RETURNS [menuName: ROPE ¬ "", choices: AtomButtons.PopUpChoices ¬ NIL] = {
o: LIST OF REF;
success: BOOL ¬ TRUE;
CodeTimer.StartInt[$PopUpParseMenu, $EmbeddedButtons];
[o, menuName, success] ¬ GetMenuListAndName[obj, instanceData];
IF NOT success THEN RETURN;
Now go through the list and pull out the menu fields, building the data structures that PopUpButtons needs.
FOR list: LIST OF REF ¬ o, list.rest UNTIL list = NIL DO -- for each entry...
entry: LIST OF REF;
event: LIST OF REF;
menuEntry: ROPE;
docString: ROPE;
choice: AtomButtons.PopUpChoice;
BEGIN
IF NOT ISTYPE[list.first, LIST OF REF] THEN GOTO FieldTypeError;
entry ¬ NARROW[list.first];
IF entry # NIL THEN { -- get the application message, entry name, and doc string
event ¬ GetMessageAsList[entry];
[menuEntry, success] ¬ GetMenuEntry[entry];
IF NOT success THEN GOTO FieldTypeError;
[docString, success] ¬ GetDocString[entry];
IF NOT success THEN GOTO FieldTypeError;
}
ELSE {event ¬ NIL; menuEntry ¬ ""; docString ¬ ""};
choice ¬ [action: event, actionImage: menuEntry, doc: docString, font: bigFont];
choices ¬ PopUpAppend[choices, LIST[choice]];
EXITS
FieldTypeError => {
EmbeddedButtons.Error["Pop-up buttons: Invalid field in menu!", Rope.Concat["The menu description for this pop-up button contains an invalid field:\n", instanceData]];
};
END;
ENDLOOP;
CodeTimer.StopInt[$PopUpParseMenu, $EmbeddedButtons];
};
PopUpPassEventToApplication: AtomButtons.HandlePopUpProc = {
[button: REF, clientData: REF, event: LIST OF REF]
message: REF;
WITH button SELECT FROM
buttonInfo: ButtonInfo => {
EBLanguage.SetSystemValue[clickContext, $MessageReceiver, EmbeddedButtons.GetApplications[buttonInfo]];
message ¬ EBLanguage.Evaluate[event, buttonInfo, clientData, clickContext];
Evaluate any angle bracket "<>" expressions in "event"
IF message # NIL THEN PassEvent[message, buttonInfo];
};
ENDCASE => {
EmbeddedButtons.Error["Pop-up buttons: button is wrong type (PopUpPassEventToApplication)"];
};
};
PopUpHandle: EBButtonClasses.HandleEventProc = {
ENABLE UNWIND => {GOTO InternalError};
CodeTimer.StartInt[$PopUpHandle, $EmbeddedButtons];
ProduceStandardFeedback[event, buttonInfo];
Let pop-up buttons handle the rest
IF instanceData = NIL THEN GOTO NoPop
ELSE {
maybeInstance: PopUpButtons.MaybeInstance ¬ PopUpButtons.QuaInstance[instanceData];
IF NOT maybeInstance.is THEN GOTO NoPop
ELSE PopUpButtons.RawNotify[buttonInfo, maybeInstance.inst, event.handle, event.action];
Will call PopUpPassEventToApplication with the menu selection
};
CodeTimer.StopInt[$PopUpHandle, $EmbeddedButtons];
EXITS
NoPop => {
EmbeddedButtons.Error["This PopUpButton is broken (syntax problem?)"];
CodeTimer.StopInt[$PopUpHandle, $EmbeddedButtons];
};
InternalError => {
SimpleFeedback.Append[$EmbeddedButtons, oneLiner, $Error, "Pressing that PopUpButton caused an internal error."];
CodeTimer.StopInt[$PopUpHandle, $EmbeddedButtons];
};
};
clickContext: Context ¬ EBLanguage.CreateContext[];
PopUpPaint: PopUpButtons.PaintProc = {
};
PopUpInButton: PopUpButtons.InTestProc = {
PROC [view: View, coords: TIPScreenCoords] RETURNS [in: BOOL];
buttonInfo: ButtonInfo;
Get the button info
IF NOT ISTYPE[view, ButtonInfo] THEN {
EmbeddedButtons.Error["Pop-up buttons: Internal Error, button is wrong type (PopUpInButton)"];
RETURN[TRUE];
};
buttonInfo ¬ NARROW[view];
Ask EmbeddedButtons if this position is in this button.
RETURN[EmbeddedButtons.InButton[coords.mouseX, coords.mouseY, buttonInfo]];
};
PopUpAppend: PROC [l1: AtomButtons.PopUpChoices, l2: AtomButtons.PopUpChoices ¬ NIL] RETURNS[val: AtomButtons.PopUpChoices] = {
z: AtomButtons.PopUpChoices ¬ NIL;
val ¬ l2;
IF l1 = NIL THEN RETURN[val];
val ¬ CONS[l1.first, val];
z ¬ val;
UNTIL (l1 ¬ l1.rest) = NIL DO
z.rest ¬ CONS[l1.first, z.rest];
z ¬ z.rest;
ENDLOOP;
RETURN[val];
};
Multi-State Buttons
Behavior:
When a multi-state button is clicked, it does not automatically change state. Instead, it sends the message corresponding to its current state to the application to which it is linked. It is the responsibility of that application to call EBApplications.SetKeyValuePair to cause the actual change, unless the button's MessageHandler includes Default, in which case EmbeddedButtons will cause the default behavior. The default behavior of a multi-state button is to change to the next state on a mouse up, wrapping around to the first state when reaching the last state.
A multi-state button has a value associated with each of its states. (If no values are specified when the button is created, they default to the integers 1 through n, where n is the number of states.) A button's state is set by calling EBApplications.SetKeyValuePair with the value of the state to which the button is to change (not the number of the state, unless the two are the same).
Feedback:
Multi-state buttons generate the following events for feedback:
The standard feedback events (Down, Up, etc.) and
If the mouse enters the button with a mouse button down, the button generates
$Enter
Then the button generates one of:
$Exit    the mouse left the button without an up-click
$Value value   the button's value has changed to value.
The $Value value events are generated when the actual change of state is made, and not when the request for state change is sent to the application.
Multi-state button class record definition
multiClass: EBButtonClasses.ButtonClass ¬ NEW[EBButtonClasses.ButtonClassObj ¬ [
name: $MultiStateButton,
instantiate: MultiInstantiate,
unparseInstanceData: MultiUnparse,
handleEvent: MultiHandle,
getValue: MultiGet,
setValue: MultiSet,
defaultBehavior: MultiDefaultBehavior]];
The internal representation of a multi-state button
Multi: TYPE = REF MultiRec;
MultiRec: TYPE = RECORD[
nameExists: BOOL ¬ FALSE,
name: ATOM,
value: Variable,
message: REF,
mouseInside: BOOL,
defaultDirection: BOOL -- direction to change state by default FALSE => up, TRUE => down
];
MultiInstantiate: EBButtonClasses.InstantiateProc = {
PROC [buttonInfo: ButtonInfo, language: ATOM, languageVersion: NAT, initFeedback: BOOLFALSE] RETURNS [instance: REF];
self: Multi;
CodeTimer.StartInt[$MultiInstantiate, $EmbeddedButtons];
Set default values
self ¬ NEW[MultiRec ¬ [
name: NIL,
nameExists: FALSE,
value: NEW[VariableObj ¬ [
type: NEW[VariableTypeObj[3]],
value: NEW[INT ¬ 1]]],
message: NIL,
mouseInside: FALSE,
defaultDirection: FALSE -- down
]];
self.value.type.class ¬ enumerated;
FOR i: INT IN [1..3] DO
self.value.type.enumeration[i-1] ¬ NEW[INT ¬ i];
ENDLOOP;
MultiInstantiateAux[buttonInfo, language, languageVersion, self, initFeedback];
CodeTimer.StopInt[$MultiInstantiate, $EmbeddedButtons];
RETURN[self];
};
MultiInstantiateAux: PROC [buttonInfo: ButtonInfo, language: ATOM, languageVersion: NAT, self: Multi, initFeedback: BOOL ¬ FALSE] = {
fieldError, fieldValue: ROPE;
val: REF;
success: BOOL ¬ FALSE;
varTable: REF;
found: BOOL ¬ FALSE;
self.name ¬ EmbeddedButtons.GetButtonName[buttonInfo];
self.nameExists ¬ self.name # NIL;
varTable ¬ EmbeddedButtons.GetFieldRef[$Variables, buttonInfo];
IF varTable # NIL THEN {
v: Variable ¬ EBLanguage.GetVariable[NARROW[varTable, VariableTable], $Value];
IF v # NIL THEN {
SELECT v.type.class FROM
boolean => {
self.value.type ¬ booleanEnumeratedType;
self.value.value ¬ v.value;
};
integer => {
self.value.type ¬ v.type;
self.value.value ¬ v.value;
};
real => {
self.value.type ¬ v.type;
self.value.value ¬ v.value;
};
enumerated => self.value ¬ v;
ENDCASE =>
EmbeddedButtons.Error["Multi-state buttons: Invalid Value variable. Must be a boolean, integer, real or enumerated type."];
};
};
IF self.value.value = NIL AND self.name # NIL THEN { -- get value from like-named button?
val ¬ EmbeddedButtons.GetValue[self.name, buttonInfo.doc];
IF val # NIL THEN {
fieldError ¬ EBMesaLisp.Unparse[val];
IF self.value.type.class = integer THEN found ¬ ISTYPE[val, REF INT]
ELSE IF self.value.type.class = real THEN found ¬ ISTYPE[val, REF REAL]
ELSE { -- Search through the values list.
found ¬ FALSE;
FOR i: NAT IN [1 .. self.value.type.count) DO
IF EBLanguage.Equal[val, self.value.type.enumeration[i]] THEN
{self.value.value ¬ val; found ¬ TRUE};
ENDLOOP;
};
IF NOT found THEN
EmbeddedButtons.Error[IO.PutFR["Two multi-state buttons of name %g have incompatible values, including %g", [rope[Atom.GetPName[self.name]]], [rope[fieldError]] ]];
};
};
IF self.value.value = NIL -- button still has no value. Give it a default
THEN self.value.value ¬ self.value.type.enumeration[0];
Transition message:
fieldValue ¬ EmbeddedButtons.GetFieldRope[$UpClickMessage, buttonInfo];
self.message ¬ EBMesaLisp.Parse[IO.RIS[fieldValue]].val;
Cause the feedback events corresponding to this button changing to its current state.
IF initFeedback THEN {
EmbeddedButtons.FeedbackNotify[
feedbackEvent: LIST[$Value, self.value.value],
buttonInfo: buttonInfo];
};
};
MultiUnparse: EBButtonClasses.UnparseInstanceDataProc = {
PROC [instanceData: REF, buttonInfo: ButtonInfo ← NIL, language: ATOMNIL, languageVersion: NAT ← 0] RETURNS [ROPENIL];
If language is Poppy, unparses fields into symbol table of buttonInfo. Tries very hard not to unparse something if it has a default value so that a button specified with Name: Gravity will get unparsed with only Name: Gravity unless it is necessary to give more information.
self: Multi;
rope: ROPE;
ppListPoppy: LIST OF REF;
Get self.
IF instanceData = NIL THEN RETURN[""];
IF NOT ISTYPE[instanceData, Multi] THEN {
EmbeddedButtons.Error["Multiple-state buttons: Internal Error, button is wrong type (MultiUnparse)"];
RETURN[""];
};
self ¬ NARROW[instanceData];
Set pretty print lists
ppListPoppy ¬ LIST[
"", refFALSE,
"\n ", refFALSE];
Unparse name
IF self.name # NIL AND NOT ButtonClassesCommon.IsGeneratedRope[Atom.GetPName[self.name]]
THEN EmbeddedButtons.SetFieldRope[$Name, EBMesaLisp.Unparse[self.name], buttonInfo];
Unparse value
SELECT language FROM
$Poppy => {
tableRef: REF;
table: VariableTable;
type: VariableType;
tableRef ¬ EmbeddedButtons.GetFieldRef[$Variables, buttonInfo];
IF ISTYPE[tableRef, VariableTable] AND tableRef # NIL THEN
table ¬ NARROW[tableRef]
ELSE {
table ¬ EBLanguage.CreateVariableTable[];
EmbeddedButtons.SetFieldRef[$Variables, table, buttonInfo];
};
IF self.value.type = booleanEnumeratedType THEN {
type ¬ NEW[VariableTypeObj[0]];
type.class ¬ boolean;
EBLanguage.SetVariable[table, $Value, NEW[VariableObj ¬ [
value: self.value.value,
type: type]]];
}
ELSE
EBLanguage.SetVariable[table, $Value, self.value];
};
ENDCASE;
Unparse transitions
SELECT language FROM
$Poppy =>
IF self.message # NIL THEN
EmbeddedButtons.SetFieldRope[$UpClickMessage, EBMesaLisp.Unparse[self.message, ppListPoppy], buttonInfo];
ENDCASE;
Generate rope
SELECT language FROM
$Poppy => rope ¬ NIL; -- A button class doesn't know enough to unparse any further. Embedded Buttons will.
ENDCASE;
RETURN[rope];
};
MultiHandle: EBButtonClasses.HandleEventProc = {
userEvent: LIST OF REF;
Produce standard feedback
ProduceStandardFeedback[event, buttonInfo];
Run the input action through TIP
tipParseInfo.inCreek ¬ event.handle;
userEvent ¬ TIPPrivate.WideMatchEvent[tipParseInfo, event.action­];
MultiEvent[instanceData, userEvent, buttonInfo];
};
gClientData: REF;
MultiCaptureNotify: InputFocus.NotifyProc = {
PROC [self: Viewer, input: LIST OF REF]
acd: ActiveClientData;
Unpack the client data
IF NOT ISTYPE[gClientData, ActiveClientData] THEN {
EmbeddedButtons.Error["Multiple-state buttons: Internal error. Client data is wrong type (MultiCaptureNotify)."];
RETURN;
};
acd ¬ NARROW[gClientData];
MultiEvent[acd.instanceData, input, acd.buttonInfo];
};
MultiEvent: PROC [instanceData: REF, userEvent: LIST OF REF, buttonInfo: ButtonInfo] = {
self: Multi;
mousePos: TIPScreenCoords ¬ NIL;
IF userEvent = NIL OR instanceData = NIL THEN RETURN;
IF NOT ISTYPE[instanceData, Multi] THEN {EmbeddedButtons.Error["Multiple-state buttons: Internal error. Button is wrong type (MultiEvent)."]; RETURN};
self ¬ NARROW[instanceData];
IF ButtonClassesCommon.CountMatches[userEvent, LIST[$Up]] > 0 THEN { -- up event
message: REF ¬ self.message;
self.defaultDirection ¬ ButtonClassesCommon.CountMatches[userEvent, LIST[$Right]] > 0;
IF self.mouseInside THEN {self.mouseInside ¬ FALSE; InputFocus.ReleaseButtons[]};
EBLanguage.SetSystemValue[clickContext, $MessageReceiver, EmbeddedButtons.GetApplications[buttonInfo]];
message ¬ EBLanguage.Evaluate[message, buttonInfo, NIL, clickContext];
IF ISTYPE[message, LIST OF REF]
THEN EmbeddedButtons.PassEventToApplication[NARROW[message], buttonInfo]
ELSE EmbeddedButtons.PassEventToApplication[LIST[message], buttonInfo];
}
ELSE { -- not a button up event. Generate Enter or Exit actions
IF NOT self.mouseInside THEN { -- Enter action
self.mouseInside ¬ TRUE;
EmbeddedButtons.FeedbackNotify[LIST[$Enter], buttonInfo];
RETURN;
};
IF ButtonClassesCommon.CountMatches[userEvent, LIST[$MouseMoved]] = 0 THEN RETURN; -- can't deal with this event. Ignoring it.
Get mouse position
FOR l: LIST OF REF ¬ userEvent, l.rest UNTIL l = NIL DO
IF ISTYPE[l.first, TIPScreenCoords] THEN mousePos ¬ NARROW[userEvent.rest.first];
ENDLOOP;
IF mousePos = NIL THEN {
EmbeddedButtons.Error["Multiple-state buttons: Internal error, NIL mouse position (MultiEvent)."];
RETURN;
};
IF NOT EmbeddedButtons.InButton[mousePos.mouseX, mousePos.mouseY, buttonInfo] THEN {
self.mouseInside ¬ FALSE;
InputFocus.ReleaseButtons[];
EmbeddedButtons.FeedbackNotify[LIST[$Exit], buttonInfo];
};
};
};
MultiGet: EBButtonClasses.GetValueProc = {
PROC[instanceData: REF, buttonInfo: ButtonInfo] RETURNS [value: REF];
self: Multi;
Get self
IF instanceData = NIL THEN RETURN[NIL];
IF NOT ISTYPE[instanceData, Multi] THEN {
EmbeddedButtons.Error["Multiple-state buttons: Internal error. Button is wrong type (MultiGet)."];
RETURN[NIL];
};
self ¬ NARROW[instanceData];
name ← self.name;
value ¬ self.value.value;
EmbeddedButtons.RegisterNameValuePair[self.name, value, buttonInfo];
};
MultiSet: EBButtonClasses.SetValueProc = {
PROC[instanceData: REF, value: REF, buttonInfo: ButtonInfo] RETURNS[changed: BOOLFALSE];
self: Multi;
IF instanceData = NIL THEN RETURN;
IF NOT ISTYPE[instanceData, Multi] THEN {EmbeddedButtons.Error["Multiple-state buttons: Internal error. Button is wrong type (MultiSet)."]; RETURN};
self ¬ NARROW[instanceData];
IF self.value.type.class = integer THEN {
WITH value SELECT FROM
i: REF INT => {
self.value.value ¬ value;
changed ¬ TRUE;
EmbeddedButtons.RegisterNameValuePair[self.name, value, buttonInfo];
EmbeddedButtons.FeedbackNotify[LIST[$Value, value], buttonInfo];
};
ENDCASE => EmbeddedButtons.Error[IO.PutFR["Attempt to set button %g (type INT) to %g", [rope[Atom.GetPName[self.name]]], [rope[EBMesaLisp.Unparse[value]]] ]];
}
ELSE IF self.value.type.class = real THEN {
WITH value SELECT FROM
r: REF REAL => {
self.value.value ¬ value;
changed ¬ TRUE;
EmbeddedButtons.RegisterNameValuePair[self.name, value, buttonInfo];
EmbeddedButtons.FeedbackNotify[LIST[$Value, value], buttonInfo];
};
ENDCASE => EmbeddedButtons.Error[IO.PutFR["Attempt to set button %g (type REAL) to %g", [rope[Atom.GetPName[self.name]]], [rope[EBMesaLisp.Unparse[value]]] ]];
}
ELSE {
FOR i: NAT IN [0..self.value.type.count) DO
IF EBLanguage.Equal[value, self.value.type.enumeration[i]] THEN {
self.value.value ¬ value;
changed ¬ TRUE;
EmbeddedButtons.RegisterNameValuePair[self.name, value, buttonInfo];
EmbeddedButtons.FeedbackNotify[LIST[$Value, value], buttonInfo];
EXIT;
};
ENDLOOP;
};
};
MultiDefaultBehavior: EBButtonClasses.DefaultBehaviorProc = {
PROC[instanceData: REF, buttonInfo: ButtonInfo];
self: Multi;
state: INT ¬ 0;
newValue: REF;
IF instanceData = NIL THEN RETURN;
IF NOT ISTYPE[instanceData, Multi] THEN {
EmbeddedButtons.Error["Multiple-state buttons: Internal error. Button is wrong type (MultiDefaultNotify)."];
RETURN;
};
self ¬ NARROW[instanceData];
IF self.value.type.class = integer THEN {
oldValue: REF INT ¬ NARROW[self.value.value];
IF NOT self.defaultDirection THEN newValue ¬ NEW[INT ¬ oldValue­ +1]
ELSE newValue ¬ NEW[INT ¬ oldValue­ -1];
}
ELSE IF self.value.type.class = real THEN {
oldValue: REF REAL ¬ NARROW[self.value.value];
IF NOT self.defaultDirection THEN newValue ¬ NEW[REAL ¬ oldValue­ *2.0]
ELSE newValue ¬ NEW[REAL ¬ oldValue­ /2.0];
}
ELSE {
FOR i: NAT IN [0..self.value.type.count) DO
IF EBLanguage.Equal[self.value.value, self.value.type.enumeration[i]]
THEN { state ¬ i; EXIT };
ENDLOOP;
IF NOT self.defaultDirection THEN {
state ¬ state + 1;
IF state >= self.value.type.count THEN state ¬ 0; -- Greater than equal for safety?
}
ELSE {
state ¬ state - 1;
IF state < 0 THEN state ¬ self.value.type.count-1;
};
newValue ¬ self.value.type.enumeration[state];
};
IF self.nameExists THEN
EmbeddedButtons.SetValue[self.name, newValue, buttonInfo.doc]
ELSE {
changed: BOOL ¬ MultiSet[instanceData, newValue, buttonInfo];
IF changed THEN EBButtonClasses.MarkButtonAsChanged[buttonInfo];
};
};
Two-state button class
Behavior:
Two-stae buttons are exactly like multi-state buttons, except that they have only two states and default values of FALSE and TRUE. In fact, two-state buttons are implemented with mostly multi-state buttons code.
Feedback:
Two-state buttons generate feedback like any other multi-state buttons.
Two-state button class record definition
twoStateClass: EBButtonClasses.ButtonClass ¬ NEW[EBButtonClasses.ButtonClassObj ¬ [
name: $TwoStateButton,
instantiate: TwoStateInstantiate,
unparseInstanceData: MultiUnparse,
handleEvent: MultiHandle,
getValue: MultiGet,
setValue: MultiSet,
defaultBehavior: MultiDefaultBehavior]];
TwoStateInstantiate: EBButtonClasses.InstantiateProc = {
PROC [buttonInfo: ButtonInfo, language: ATOM, languageVersion: NAT, initFeedback: BOOLFALSE] RETURNS [instance: REF];
Set default values
self: Multi ¬ NEW[MultiRec ¬ [
name: NIL,
value: NEW[VariableObj ¬ [
type: booleanEnumeratedType,
value: refFALSE]],
message: NIL,
mouseInside: FALSE,
defaultDirection: FALSE -- up
]];
MultiInstantiateAux[buttonInfo, language, languageVersion, self, initFeedback];
RETURN[self];
};
Radio Button Class
Behavior:
Radio buttons are sets of buttons. In each set, only one button may be "on" at any given time. Clicking on a radio button turns that button on and all the other buttons in the set off. A set of radio buttons is defined as all radio buttons in a document which have the same Name. Each radio button has a value and the value of the radio button which is currently on is associated with the Name of the set of buttons. When a radio button is clicked, it sends its request message to the application to which it is linked. It is the responsibility of the application to call EBApplications.SetNameValuePair with the new value for the radio button set, unless the radio buttons have their default behavior.
Radio buttons look at a boolean variable (in the Poppy variables field) called "State." This variable specifies whether a particular Radio button is on or not.
Feedback:
Radio buttons generate the following events for feedback:
If the mouse enters the button with a mouse button down, the button generates:
$Enter
Then the button generates one of:
$Exit   -- the mouse left the button without an up-click
($State TRUE)  -- change to the "on" appearance
($State FALSE) -- change to the "off" appearance
Radio button class record definition
radioClass: EBButtonClasses.ButtonClass ¬ NEW[EBButtonClasses.ButtonClassObj ¬ [
name: $RadioButton,
instantiate: RadioInstantiate,
unparseInstanceData: RadioUnparse,
handleEvent: RadioHandle,
getValue: RadioGet,
setValue: RadioSet,
defaultBehavior: RadioDefaultBehavior]];
The internal representation of a radio button
Radio: TYPE = REF RadioRec;
RadioRec: TYPE = RECORD[
nameExists: BOOL ¬ FALSE,
name: ATOM,
value: REF,
on: BOOL,
mouseInside: BOOL,
message: LIST OF REF];
RadioInstantiate: EBButtonClasses.InstantiateProc = {
PROC [buttonInfo: ButtonInfo, language: ATOM, languageVersion: NAT, initFeedback: BOOLFALSE] RETURNS [instance: REF];
In Poppy, then the following fields are used:
Name: name     -- (an ATOM)
RadioButtonValue: value
Variables: State    -- (FALSE, TRUE, Off, or On)
Message: message
self: Radio;
fieldError, fieldValue: ROPE;
success: BOOL;
val, varTable: REF;
CodeTimer.StartInt[$RadioInstantiate, $EmbeddedButtons];
self ¬ NEW[RadioRec ¬ [nameExists: FALSE, name: NIL, value: NIL, on: FALSE, mouseInside: FALSE, message: NIL]];
self.name ¬ EmbeddedButtons.GetButtonName[buttonInfo];
self.nameExists ¬ self.name # NIL;
IF self.name = NIL THEN {
EmbeddedButtons.Error["Every radio button must have a Name: field."];
CodeTimer.StopInt[$PopUpInstantiate, $EmbeddedButtons];
RETURN[NIL];
};
fieldValue ¬ EmbeddedButtons.GetFieldRope[$RadioButtonValue, buttonInfo];
fieldError ¬ fieldValue;
self.value ¬ EBMesaLisp.Parse[IO.RIS[fieldValue]].val;
IF self.value = NIL THEN -- Create a value if none provided
self.value ¬ ButtonClassesCommon.GenerateUniqueRope[];
varTable ¬ EmbeddedButtons.GetFieldRef[$Variables, buttonInfo];
success ¬ FALSE;
IF varTable # NIL THEN {
v: Variable ¬ EBLanguage.GetVariable[NARROW[varTable, VariableTable], $State];
IF v # NIL THEN {
IF v.type.class = boolean THEN {
success ¬ TRUE;
self.on ¬ NARROW[v.value, REF BOOL]­;
}
ELSE EmbeddedButtons.Error["Radio buttons: Invalid State variable. Must be a boolean."];
};
};
IF NOT success AND self.name # NIL THEN { -- get State from another like-named button?
val ¬ EmbeddedButtons.GetValue[self.name, buttonInfo.doc];
IF val # NIL AND EBLanguage.Equal[val, self.value] THEN self.on ¬ TRUE;
};
fieldValue ¬ EmbeddedButtons.GetFieldRope[$UpClickMessage, buttonInfo];
fieldError ¬ fieldValue;
val ¬ EBMesaLisp.Parse[IO.RIS[fieldValue]].val;
IF val # NIL THEN {
IF ISTYPE[val, LIST OF REF] THEN self.message ¬ NARROW[val]
ELSE EmbeddedButtons.Error[IO.PutFR1["Radio button: invalid message field: %g", [rope[fieldError]] ]];
};
CodeTimer.StopInt[$PopUpInstantiate, $EmbeddedButtons];
RETURN[self];
};
RadioUnparse: EBButtonClasses.UnparseInstanceDataProc = {
PROC [instanceData: REF, buttonInfo: ButtonInfo ← NIL, language: ATOMNIL, languageVersion: NAT ← 0] RETURNS [ROPENIL];
self: Radio;
Get self.
IF instanceData = NIL THEN RETURN[""];
IF NOT ISTYPE[instanceData, Radio] THEN {
EmbeddedButtons.Error["Radio buttons: Internal Error, button is wrong type (RadioUnparse)"];
RETURN[""];
};
self ¬ NARROW[instanceData];
SELECT language FROM
$Poppy => {
Unparse each field.
IF NOT ISTYPE[self.value, ROPE] OR NOT ButtonClassesCommon.IsGeneratedRope[NARROW[self.value, ROPE]] THEN
EmbeddedButtons.SetFieldRope[$RadioButtonValue, EBMesaLisp.Unparse[self.value], buttonInfo];
EmbeddedButtons.SetFieldRope[$Name, EBMesaLisp.Unparse[self.name], buttonInfo];
EmbeddedButtons.SetFieldRope[$UpClickMessage, EBMesaLisp.Unparse[self.message], buttonInfo];
BEGIN
tableRef: REF;
table: VariableTable;
type: VariableType;
tableRef ¬ EmbeddedButtons.GetFieldRef[$Variables, buttonInfo];
IF ISTYPE[tableRef, VariableTable] AND tableRef # NIL THEN
table ¬ NARROW[tableRef]
ELSE {
table ¬ EBLanguage.CreateVariableTable[];
EmbeddedButtons.SetFieldRef[$Variables, table, buttonInfo];
};
type ¬ NEW[VariableTypeObj[0]];
type.class ¬ boolean;
EBLanguage.SetVariable[table, $State, NEW[VariableObj ¬ [
type: type,
value: IF self.on THEN refTRUE ELSE refFALSE
]]];
END;
RETURN;
};
ENDCASE;
};
RadioHandle: EBButtonClasses.HandleEventProc = {
userEvent: LIST OF REF;
Produce standard feedback
ProduceStandardFeedback[event, buttonInfo];
Run the input action through TIP
tipParseInfo.inCreek ¬ event.handle;
userEvent ¬ TIPPrivate.WideMatchEvent[tipParseInfo, event.action­];
RadioEvent[instanceData, userEvent, buttonInfo];
};
RadioCaptureNotify: InputFocus.NotifyProc = {
PROC [self: Viewer, clientData: REF, input: LIST OF REF]
acd: ActiveClientData;
Extract client data.
IF NOT ISTYPE[gClientData, ActiveClientData] THEN {
EmbeddedButtons.Error["Radio buttons: Internal error. Client data is wrong type (RadioCaptureNotify).", "Radio buttons: Internal error. Client data is wrong type (RadioCaptureNotify)."];
RETURN;
};
acd ¬ NARROW[gClientData];
RadioEvent[acd.instanceData, input, acd.buttonInfo];
};
RadioEvent: PROC [instanceData: REF, userEvent: LIST OF REF, buttonInfo: ButtonInfo] = {
self: Radio;
mousePos: TIPScreenCoords ¬ NIL;
message: REF;
IF userEvent = NIL OR instanceData = NIL THEN RETURN;
IF NOT ISTYPE[instanceData, Radio] THEN {
EmbeddedButtons.Error["Radio buttons: Internal error. Button is incorrect type (RadioHandle)."];
RETURN;
};
self ¬ NARROW[instanceData];
On an upclick, send an application message.
IF ButtonClassesCommon.CountMatches[userEvent, LIST[$Up]] > 0 THEN {
Release mouse keys if necessary
IF self.mouseInside THEN {
self.mouseInside ¬ FALSE;
InputFocus.ReleaseButtons[];
};
Evaluate message and pass it to application.
EBLanguage.SetSystemValue[clickContext, $MessageReceiver, EmbeddedButtons.GetApplications[buttonInfo]];
message ¬ EBLanguage.Evaluate[self.message, buttonInfo, NIL, clickContext];
IF ISTYPE[message, LIST OF REF] THEN
EmbeddedButtons.PassEventToApplication[NARROW[message], buttonInfo]
ELSE
EmbeddedButtons.PassEventToApplication[LIST[message], buttonInfo];
RETURN;
};
If the mouse was not previously in the button, then generate feedback and capture buttons
IF NOT self.mouseInside THEN {
self.mouseInside ¬ TRUE;
EmbeddedButtons.FeedbackNotify[LIST[$Enter], buttonInfo];
RETURN;
};
If the mouse was previously inside the button, check if it still is and generate feedback if not, releasing buttons.
IF ButtonClassesCommon.CountMatches[userEvent, LIST[$MouseMoved]] = 0 THEN RETURN; -- can't deal with this event. Ignoring it.
Get mouse position
FOR l: LIST OF REF ¬ userEvent, l.rest UNTIL l = NIL DO
IF ISTYPE[l.first, TIPScreenCoords] THEN mousePos ¬ NARROW[userEvent.rest.first];
ENDLOOP;
IF mousePos = NIL THEN {
EmbeddedButtons.Error["Radio buttons: Internal error, TIP table probably mashed (RadioEvent)."];
RETURN;
};
IF NOT EmbeddedButtons.InButton[mousePos.mouseX, mousePos.mouseY, buttonInfo] THEN {
self.mouseInside ¬ FALSE;
InputFocus.ReleaseButtons[];
EmbeddedButtons.FeedbackNotify[LIST[$Exit], buttonInfo];
};
};
RadioGet: EBButtonClasses.GetValueProc = {
PROC[instanceData: REF, buttonInfo: ButtonInfo] RETURNS [value: REF];
self: Radio;
Get self
IF NOT ISTYPE[instanceData, Radio] THEN {
EmbeddedButtons.Error["Radio buttons: Internal error. Button is incorrect type (RadioHandle)."];
RETURN[NIL];
};
self ¬ NARROW[instanceData];
IF instanceData = NIL THEN RETURN[NIL];
name ← self.name;
value ¬ self.value;
IF self.on THEN {
EmbeddedButtons.RegisterNameValuePair[self.name, value, buttonInfo];
RETURN[value];
};
RETURN[NIL];
};
RadioSet: EBButtonClasses.SetValueProc = {
PROC[instanceData: REF, value: REF, buttonInfo: ButtonInfo] RETURNS[changed: BOOLFALSE];
self: Radio;
IF instanceData = NIL THEN RETURN;
IF NOT ISTYPE[instanceData, Radio] THEN {EmbeddedButtons.Error["RadioSet: This radio button is not a radio button."]; RETURN};
self ¬ NARROW[instanceData];
If this button has been pressed, turn it on (if not already on)
IF EBLanguage.Equal[value, self.value] THEN {
IF NOT self.on THEN {
self.on ¬ TRUE;
changed ¬ TRUE;
IF self.nameExists THEN
EmbeddedButtons.RegisterNameValuePair[self.name, self.value, buttonInfo];
EmbeddedButtons.FeedbackNotify[LIST[$State, refTRUE], buttonInfo];
}
ELSE EmbeddedButtons.FeedbackNotify[LIST[$StateStill, refTRUE], buttonInfo];
}
ELSE { -- someone else has been pressed. Turn off, (if you're not already off)
IF self.on THEN {
self.on ¬ FALSE;
changed ¬ TRUE;
EmbeddedButtons.FeedbackNotify[LIST[$State, refFALSE], buttonInfo];
};
};
};
RadioDefaultBehavior: EBButtonClasses.DefaultBehaviorProc = {
PROC[instanceData: REF, buttonInfo: ButtonInfo];
self: Radio;
Get self
IF instanceData = NIL THEN RETURN;
IF NOT ISTYPE[instanceData, Radio] THEN {
EmbeddedButtons.Error["Radio buttons: Internal error. Button is wrong type (RadioDefaultBehavior)."];
RETURN;
};
self ¬ NARROW[instanceData];
EmbeddedButtons.SetValue[self.name, self.value, buttonInfo.doc];
};
Routines Shared By Several Button Classes
CountMatches: PUBLIC PROC [a, b: LIST OF REF] RETURNS [result: INT ¬ 0] = {
Returns the number of elements of the first list that also occur in the second list with the following provisos: $Red matches $Left, $Yellow matches $Middle, $Blue matches $Right.
FOR c: LIST OF REF ¬ a, c.rest UNTIL c = NIL DO
FOR d: LIST OF REF ¬ b, d.rest UNTIL d = NIL DO
IF Match[c.first, d.first] THEN result ¬ result + 1;
ENDLOOP;
ENDLOOP;
};
Match: PROC [a, b: REF] RETURNS [BOOL] = {
IF a = b THEN RETURN[TRUE]
ELSE IF EBLanguage.Equal[a, b] THEN RETURN[TRUE]
ELSE IF a = $Red AND b = $Left THEN RETURN[TRUE]
ELSE IF a = $Yellow AND b = $Middle THEN RETURN[TRUE]
ELSE IF a = $Blue AND b = $Right THEN RETURN[TRUE]
ELSE IF a = $Left AND b = $Red THEN RETURN[TRUE]
ELSE IF a = $Middle AND b = $Yellow THEN RETURN[TRUE]
ELSE IF a = $Right AND b = $Blue THEN RETURN[TRUE]
ELSE RETURN[FALSE];
};
ExactMatch: PUBLIC PROC [a, b: LIST OF REF] RETURNS [BOOL] = {
Returns TRUE iff a and b contain the same REFS in the same order. The atom $Mouse matches any of $Red, $Yellow, $Blue, $Left, $Middle, or $Right. The atom $Highlight matches ($Mouse $Down) or $Enter, where $Mouse matches all the patterns just mentioned. The atom $UnHighlight matches ($Mouse $Up) or $Exit.
blist: LIST OF REF ¬ b;
FOR alist: LIST OF REF ¬ a, alist.rest UNTIL alist = NIL DO
IF NOT Match[alist.first, blist.first] THEN RETURN[FALSE];
blist ¬ blist.rest;
ENDLOOP;
RETURN[TRUE];
};
Subset: PUBLIC PROC [a, b: LIST OF REF] RETURNS [BOOL] = {
Included: PROC [a: REF, b: LIST OF REF] RETURNS [BOOL] = {
FOR list: LIST OF REF ¬ b, list.rest UNTIL list = NIL DO
IF Match[a, list.first] THEN RETURN[TRUE];
ENDLOOP;
RETURN[FALSE];
};
Returns TRUE iff all REFs of a (or their equivalents, see ExactMatch) are also in b.
FOR alist: LIST OF REF ¬ a, alist.rest UNTIL alist = NIL DO
IF NOT Included[alist.first, b] THEN RETURN[FALSE];
ENDLOOP;
RETURN[TRUE];
};
KeyValueExtract: PUBLIC PROC [object: LIST OF REF, key: ATOM, screenError, fileError: ROPE] RETURNS [val: REF ¬ NIL, success: BOOL ¬ FALSE] = {
FOR list: LIST OF REF ¬ object, list.rest UNTIL list = NIL DO
first: LIST OF REF;
keyword: ATOM;
IF ~ISTYPE[list.first, LIST OF REF] OR list.first = NIL THEN {
EmbeddedButtons.Error[screenError, fileError];
LOOP;
};
first ¬ NARROW[list.first];
IF ~ISTYPE[first.first, ATOM] OR first.first = NIL THEN {
EmbeddedButtons.Error[screenError, fileError];
LOOP;
};
keyword ¬ NARROW[first.first];
IF keyword = key THEN {
val ¬ first.rest.first;
success ¬ TRUE;
RETURN;
};
ENDLOOP;
};
ProduceStandardFeedback: PROC [event: EBEvent.Event, buttonInfo: ButtonInfo] = {
Runs a copy of the action and queue through a standard tip table and produces standard feedback events such as Down, Up, etc.
userEvents: LIST OF REF;
CodeTimer.StartInt[$ProduceStandardFeedback, $EmbeddedButtons];
Copy queue into tipParseInfo since TIPPrivate.WideMatchEvent may eat actions off the queue.
TRUSTED {
UserInputLookahead.SaveState[saved: standardFeedbackTIPParseInfo.inCreek, handle: event.handle];
};
userEvents ¬ TIPPrivate.WideMatchEvent[standardFeedbackTIPParseInfo, event.action­];
IF userEvents # NIL THEN {
EmbeddedButtons.FeedbackNotify[userEvents, buttonInfo]; -- do the feedback
};
UserInputOps.Close[standardFeedbackTIPParseInfo.inCreek]; -- so garbage collection can proceed
CodeTimer.StopInt[$ProduceStandardFeedback, $EmbeddedButtons];
};
uniqueHeader: ROPE ¬ "EBButtonString";
GenerateUniqueRope: PUBLIC PROC [] RETURNS [rope: ROPE] = {
rope ¬ uniqueHeader;
rope ¬ Rope.Concat[rope, Convert.RopeFromCard[BasicTime.ToNSTime[BasicTime.Now[]]]];
rope ¬ Rope.Concat[rope, Convert.RopeFromCard[BasicTime.PulsesToMicroseconds[BasicTime.GetClockPulses[]]]];
};
IsGeneratedRope: PUBLIC PROC[rope: ROPE] RETURNS [BOOL] = {
RETURN[Rope.Equal[uniqueHeader, Rope.Substr[rope, 0, Rope.Length[uniqueHeader]]]];
};
Load-time Processing
Create the TIPParseInfo for event decoding
tipParseInfo: TIPPrivate.TIPParseInfo;
standardFeedbackTIPParseInfo: TIPPrivate.TIPParseInfo;
booleanEnumeratedType: VariableType ¬ NEW[VariableTypeObj[2]];
refFALSE: REF BOOL ¬ NEW[BOOL ¬ FALSE];
refTRUE: REF BOOL ¬ NEW[BOOL ¬ TRUE];
tipParseInfo ¬ TIPPrivate.CreateParseInfo[name: "ButtonClasses"];
tipParseInfo.tableHead ¬ TIPUser.InstantiateNewTIPTable["ButtonClasses.tip"];
standardFeedbackTIPParseInfo ¬ TIPPrivate.CreateParseInfo[name: "ButtonClassesFeedback"];
standardFeedbackTIPParseInfo.tableHead ¬ TIPUser.InstantiateNewTIPTable["ButtonClassesCommon.tip"];
booleanEnumeratedType.class ¬ enumerated;
booleanEnumeratedType.enumeration[0] ¬ refFALSE;
booleanEnumeratedType.enumeration[1] ¬ refTRUE;
Register the button classes.
EBButtonClasses.RegisterButtonClass[$TwoStateButton, twoStateClass]; -- for backwards compat.
EBButtonClasses.RegisterButtonClass[$MultiStateButton, multiClass];
EBButtonClasses.RegisterButtonClass[$PopUpButton, popUpClass];
EBButtonClasses.RegisterButtonClass[$RadioButton, radioClass];
EBButtonClasses.RegisterButtonClass[$PopUpStateButton, popUpStateClass]; -- experimental
EBButtonClasses.RegisterButtonClass[$GuardedButton, buttonClass];
END.