EBImpl.mesa
Copyright Ó 1988, 1989, 1990, 1992 by Xerox Corporation. All rights reserved.
Goodisman, August 15, 1989 5:10:44 pm PDT
Kenneth A. Pier, October 30, 1990 5:15 pm PST
Bier, November 10, 1992 10:48 am PST
Doug Wyatt, April 21, 1992 11:25 am PDT
Contents: The implementation of EmbeddedButtons, A framework for adding standardized behaviors to any application that is willing to support a set of button objects and implement the behavior access routines.
DIRECTORY
Atom, CodeTimer, EBButtonClasses, EBConcreteTypes, EBEditors, EBEditorsExtras, EBEvent, EBLanguage, EBMesaLisp, EBTypes, EmbeddedButtons, IO, KeyMapping, KeyTypes, Process, RefTab, Rope, ScreenCoordsTypes, SimpleFeedback, SpecialKeySyms, TIPPrivate, TIPTypes, UserInput, UserInputDiscrimination, UserInputOps, UserInputOpsExtras2, ViewerClasses, ViewerOps;
EBImpl:
CEDAR
MONITOR
-- for parseInfo --
IMPORTS Atom, CodeTimer, EBLanguage, EBMesaLisp, IO, KeyMapping, RefTab, Rope, SimpleFeedback, TIPPrivate, UserInputDiscrimination, UserInputOps, UserInputOpsExtras2, ViewerOps
EXPORTS EBButtonClasses, EBEditorsExtras, EBEditors, EBTypes, EmbeddedButtons = BEGIN
ActiveButton: TYPE = EBTypes.ActiveButton;
ActiveDoc: TYPE = EBTypes.ActiveDoc;
ButtonClass: TYPE = EBButtonClasses.ButtonClass;
ButtonInfo: TYPE = EBTypes.ButtonInfo;
ButtonInfoRec: TYPE = EBTypes.ButtonInfoRec;
ButtonInfoPrivateRec: PUBLIC TYPE = EBConcreteTypes.ButtonInfoPrivateRec;
EBLanguageProc: TYPE = EBLanguage.EBLanguageProc;
ROPE: TYPE = Rope.ROPE;
Variable: TYPE = REF VariableObj;
VariableObj: TYPE = EBLanguage.VariableObj;
VariableTable: TYPE = EBConcreteTypes.VariableTable;
Viewer: TYPE = ViewerClasses.Viewer;
TargetTableEntry: TYPE = REF TargetTableEntryRep;
TargetTableEntryRep: TYPE = EBConcreteTypes.TargetTableEntryRep;
Error Reporting
Error:
PUBLIC
PROC [screenMessage:
ROPE, fileMessage:
ROPE ¬
NIL] = {
The $EmbeddedButtons is set up by ButtonApplicationsImpl's initialization.
IF fileMessage = NIL THEN fileMessage ¬ screenMessage;
SimpleFeedback.Append[$EmbeddedButtons, oneLiner, $UserError, screenMessage];
SimpleFeedback.Append[$EmbeddedButtons, oneLiner, $InternalError, fileMessage];
};
Used By Editors
ActiveDocObj: TYPE = EBTypes.ActiveDocObj;
ActiveDocClass: TYPE = EBEditors.ActiveDocClass;
ActiveDocPrivateObj: PUBLIC TYPE = EBConcreteTypes.ActiveDocPrivateObj;
Event: TYPE ~ EBEvent.Event;
EventRep: PUBLIC TYPE ~ EBEvent.EventRep; -- for EBTypes
Handle: TYPE = UserInput.Handle;
HandleRep: PUBLIC TYPE ~ UserInputPrivate.Rep; -- used only by MouseCoords routine below
CreateActiveDoc:
PUBLIC PROC [theDoc:
REF, docClass: ActiveDocClass]
RETURNS [theActiveDoc: ActiveDoc] = {
nameTable: RefTab.Ref ¬ RefTab.Create[];
targetTable: RefTab.Ref ¬ RefTab.Create[];
theActiveDoc ¬
NEW[ActiveDocObj ¬ [
theDoc: theDoc,
private:
NEW[ActiveDocPrivateObj ¬ [
class: docClass,
nameTable: nameTable,
targetTable: targetTable]]]];
};
GetHandle:
PUBLIC
PROC [event: Event]
RETURNS [UserInput.Handle] = {
RETURN[event.handle];
};
GetEvent:
PUBLIC
PROC [input:
LIST
OF
REF]
RETURNS [Event ¬
NIL] ~ {
IF input#
NIL
AND UserInputDiscrimination.IsHandle[input.first]
AND input.rest#
NIL
THEN {
WITH input.rest.first
SELECT
FROM
action: EBEvent.Action =>
IF input.rest.rest=
NIL
THEN
{
handle: Handle ← UserInputDiscrimination.NarrowHandle[input.first];
RETURN[NEW[EventRep ¬ [handle, action]]];
};
ENDCASE;
};
};
ValidEvent:
PUBLIC
PROC [event: Event]
RETURNS [
BOOL] ~ {
RETURN[event#NIL];
};
Button1: KeyTypes.KeySym ~ SpecialKeySyms.Button1;
Button2: KeyTypes.KeySym ~ SpecialKeySyms.Button2;
Button3: KeyTypes.KeySym ~ SpecialKeySyms.Button3;
MouseMotion:
PUBLIC
PROC [event: Event]
RETURNS [
BOOL ¬
FALSE] = {
SELECT event.action.kind
FROM
$IntegerPosition, $Position, $FakePosition => RETURN[TRUE];
ENDCASE;
};
MouseAction:
PUBLIC
PROC [event: Event]
RETURNS [
BOOL ¬
FALSE] ~ {
SELECT event.action.kind
FROM
$Key, $KeyStillDown => {
keySyms: KeyMapping.KeySyms ~
KeyMapping.GetKeySyms[UserInputOps.GetMapping[event.handle], event.action.keyCode];
FOR i:
BYTE
IN[0..keySyms.n)
DO
SELECT keySyms[i]
FROM
Button1, Button2, Button3 => RETURN[TRUE];
ENDCASE;
ENDLOOP;
};
$IntegerPosition, $Position, $FakePosition => RETURN[TRUE];
ENDCASE;
};
MouseAllUp:
PUBLIC
PROC [event: Event]
RETURNS [
BOOL] ~ {
RETURN[
UserInputOps.GetKeySymState[event.handle, Button1]=up AND
UserInputOps.GetKeySymState[event.handle, Button2]=up AND
UserInputOps.GetKeySymState[event.handle, Button3]=up
];
};
MouseCoords:
PUBLIC
PROC [event: Event]
RETURNS [EBEditors.MousePosition] ~ {
RETURN[event.handle.mousePosition];
RETURN[UserInputOpsExtras2.GetPosition[event.handle]];
};
HandleEvent:
PUBLIC
PROC [event: Event, button: ActiveButton, doc: ActiveDoc] ~ {
buttonInfo: ButtonInfo ~ GetButtonInfo[button, doc];
IF buttonInfo#
NIL
THEN {
class: ButtonClass ~ buttonInfo.private.class;
IF class#
NIL
AND class.handleEvent#
NIL
THEN
-- Pass event to button class
class.handleEvent[event, buttonInfo.private.instanceData, buttonInfo];
};
};
parseInfo: TIPPrivate.TIPParseInfo ~ TIPPrivate.CreateParseInfo[name: "EBImpl.ParseEvent"];
MatchEvent:
ENTRY
PROC [table: TIPTypes.TIPTable, event: Event]
RETURNS [
LIST
OF
REF] ~ {
ENABLE UNWIND => NULL;
parseInfo.inCreek ¬ event.handle;
parseInfo.tableHead ¬ table;
RETURN[TIPPrivate.WideMatchEvent[parseInfo, event.action]];
};
stdCoords: ScreenCoordsTypes.TIPScreenCoords ~ TIPPrivate.stdCoords;
ParseEvent:
PUBLIC
PROC [table: TIPTypes.TIPTable, event: Event]
RETURNS [
LIST
OF
REF] ~ {
result: LIST OF REF ~ MatchEvent[table, event];
FOR list:
LIST
OF
REF ¬ result, list.rest
UNTIL list=
NIL
DO
IF list.first=stdCoords THEN { [] ¬ ViewerOps.MouseInViewer[stdCoords]; EXIT };
ENDLOOP;
RETURN[result];
};
ButtonDataFromRope:
PUBLIC
PROC [rope:
ROPE, instantiateNow:
BOOL ¬
FALSE, button: ActiveButton ¬
NIL, doc: ActiveDoc ¬
NIL]
RETURNS [val:
REF] = {
CodeTimer.StartInt[$ButtonDataFromRope, $EmbeddedButtons];
val ¬
NEW[ButtonInfoRec ¬ [
button: button,
doc: doc,
private:
NEW [
ButtonInfoPrivateRec ¬ [
rope: rope,
changed: FALSE,
class: NIL,
nameChecked: FALSE,
name: $Unchecked,
fieldValuesOK: FALSE,
language: NIL,
languageVersion: 0,
instanceData: NIL,
hasComplained: FALSE]]]];
CodeTimer.StopInt[$ButtonDataFromRope, $EmbeddedButtons];
};
RopeFromButtonData:
PUBLIC
PROC [val:
REF]
RETURNS [rope: Rope.
ROPE ¬ ""] = {
CodeTimer.StartInt[$RopeFromButtonData, $EmbeddedButtons];
WITH val
SELECT
FROM
buttonInfo: ButtonInfo => {
IF buttonInfo.private.class = NIL OR ~buttonInfo.private.changed OR buttonInfo.private.class.unparseInstanceData = NIL THEN rope ¬ buttonInfo.private.rope
ELSE {
SELECT buttonInfo.private.language
FROM
$Poppy => {
Have button class unparse fields into the symbol table.
[] ¬ buttonInfo.private.class.unparseInstanceData[buttonInfo.private.instanceData, buttonInfo, $Poppy, 1];
Now unparse the symbol table.
rope ¬ Rope.Concat["Poppy1 ", EBLanguage.PoppyUnparse[buttonInfo.private.symbols, buttonInfo.private.order]];
};
$ButtonLisp => rope ¬ Rope.Cat[Atom.GetPName[buttonInfo.private.class.name], " ", buttonInfo.private.class.unparseInstanceData[buttonInfo.private.instanceData, buttonInfo, $ButtonLisp, 1]];
ENDCASE;
};
};
ENDCASE => Error["EmbeddedButtons: Internal error. ButtonData is wrong type (RopeFromButtonData)"];
CodeTimer.StopInt[$RopeFromButtonData, $EmbeddedButtons];
};
ParseFeedbackField: EBLanguage
.FieldParseProc = {
PROC [stream: IO.STREAM] RETURNS [val: REF]
For registration with EBLanguage.RegisterFieldParseProc.
rope: ROPE ¬ stream.GetRope[];
val ¬ ButtonFeedbackFromRope[rope];
};
UnparseFeedbackField: EBLanguage.FieldUnparseProc = {
PROC[val: REF] RETURNS [rope: ROPE];
For registration with EBLanguage.RegisterFieldUnparseProc.
WITH val
SELECT
FROM
ft: FeedbackTable => RETURN[ft.rope];
ENDCASE => RETURN[""];
};
ButtonFeedbackFromRope:
PUBLIC
PROC [rope:
ROPE, instantiateNow:
BOOL ¬
FALSE]
RETURNS[val:
REF] = {
"table" will be computed when the button is used.
val ¬
NEW[FeedbackTableRec ¬ [
rope: rope,
table: NIL]];
};
ButtonFeedbackToRope:
PUBLIC
PROC [val:
REF]
RETURNS [rope: Rope.
ROPE ←
NIL] = {
WITH val
SELECT
FROM
ft: FeedbackTable => RETURN[ft.rope];
ENDCASE => Error["EmbeddedButtons: Internal error. ButtonFeedback is wrong type (ButtonFeedbackToRope)"];
};
Used By Button Classes
RegisterButtonClass:
PUBLIC
PROC [buttonClassName:
ATOM, buttonClass: ButtonClass] = {
[] ¬ RefTab.Store[classTable, buttonClassName, buttonClass];
};
RegisterNameValuePair:
PUBLIC
PROC [name:
ATOM, value:
REF, buttonInfo: ButtonInfo] = {
[] ¬ RefTab.Store[buttonInfo.doc.private.nameTable, name, value];
Should change to include button for traceback.
};
CheckListOfATOM:
PROC [list:
LIST
OF
REF]
RETURNS [ok:
BOOL ¬
TRUE] = {
FOR l:
LIST OF REF ¬ list, l.rest
UNTIL l =
NIL
DO
IF
NOT
ISTYPE[l.first,
ATOM]
THEN {
Error["EmbeddedButtons: Message handlers (targets) must be atoms"];
RETURN[FALSE];
};
ENDLOOP;
};
UnknownLanguage:
PROC [] = {
Error["PassEventToApplication: Unknown button language"];
};
GetApplications:
PUBLIC
PROC [buttonInfo: ButtonInfo]
RETURNS [applications:
LIST
OF
ATOM] = {
SELECT buttonInfo.private.language
FROM
$Poppy => {
targetsRope: ROPE ~ EBLanguage.GetFieldRope[buttonInfo.private.symbols, $MessageHandler];
val: REF ~ EBMesaLisp.Parse[IO.RIS[targetsRope]].val;
tail: LIST OF ATOM;
IF
ISTYPE[val,
LIST OF REF]
THEN {
FOR list:
LIST
OF
REF ¬
NARROW[val], list.rest
UNTIL list =
NIL
DO
IF NOT ISTYPE[list.first, ATOM] THEN RETURN[NIL]; -- one bad apple...
[applications, tail] ¬ AddAtom[NARROW[list.first], applications, tail];
ENDLOOP;
}
ELSE {
IF
ISTYPE[val,
ATOM]
THEN applications ¬
LIST[
NARROW[val,
ATOM]]
ELSE applications ¬ NIL;
};
};
ENDCASE => {UnknownLanguage[]; RETURN[NIL]};
};
AddAtom:
PUBLIC
PROC [entity:
ATOM, entityList, ptr:
LIST
OF
ATOM]
RETURNS [newList, newPtr:
LIST
OF
ATOM] = {
IF ptr =
NIL
THEN {
IF NOT entityList = NIL THEN ERROR;
newPtr ¬ newList ¬ CONS[entity, NIL];
RETURN;
}
ELSE {
newList ¬ entityList;
ptr.rest ¬ CONS[entity, NIL];
newPtr ¬ ptr.rest;
};
};
DefaultBehavior:
PROC [class: ButtonClass, instanceData:
REF, buttonInfo: ButtonInfo] =
INLINE {
Usually, when a button is not connected to any application, it will exhibit some default behavior when clicked so that the user knows it is working. Radio Buttons, for instance, change their state when clicked, as a default behavior.
IF class #
NIL
AND class.defaultBehavior #
NIL
THEN class.defaultBehavior[instanceData, buttonInfo];
};
PassEventToApplication:
PUBLIC
PROC [event:
LIST OF REF, buttonInfo: ButtonInfo, application:
ATOM ¬ $MessageHandlers] = {
targets: LIST OF ATOM;
IF application = $MessageHandlers THEN targets ¬ GetApplications[buttonInfo]
ELSE targets ¬ LIST[application];
Process.Detach[FORK ForkedEvent[event, buttonInfo, targets]]; -- unfortunately this causes synchronization problems with Tioga (sigh), Bier February 19, 1990
ForkedEvent[event, buttonInfo, targets];
};
ForkedEvent:
PROC [event:
LIST OF REF, buttonInfo: ButtonInfo, targets:
LIST
OF
ATOM] = {
anyTargetsFound: BOOL ¬ FALSE;
target: ATOM;
found: BOOL;
class: ButtonClass;
instanceData: REF;
val, results: REF;
class ¬ buttonInfo.private.class;
instanceData ¬ buttonInfo.private.instanceData;
IF event =
NIL
OR targets =
NIL
THEN {DefaultBehavior[class, instanceData, buttonInfo]; RETURN};
FOR l:
LIST
OF
ATOM ¬ targets, l.rest
UNTIL l =
NIL
DO
-- for each application...
target ¬ l.first;
IF target = $Default
THEN {
DefaultBehavior[class, instanceData, buttonInfo];
anyTargetsFound ¬ TRUE;
LOOP;
};
[found, val] ¬ RefTab.Fetch[buttonInfo.doc.private.targetTable, target];
IF found
THEN {
-- this document is linked to a particular application window
anyTargetsFound ¬ TRUE;
WITH val
SELECT
FROM
entry: TargetTableEntry => {
IF entry.notifyProc #
NIL
THEN {
results ¬ entry.notifyProc[buttonInfo, event, entry.viewer, entry.applicationData];
ResultsFeedback[results, buttonInfo];
}
ELSE
IF entry.viewer #
NIL
THEN {
ViewerOps.NotifyViewer[entry.viewer, event];
};
};
ENDCASE => {
Error["EmbeddedButtons: Internal error. Target table entry is wrong type (PassEventToApplication)"];
RETURN;
};
}
ELSE {
-- Let class do default handling and then try globally-registered applications
IF class #
NIL
AND class.defaultBehavior #
NIL
THEN class.defaultBehavior[instanceData, buttonInfo];
[found, val] ¬ RefTab.Fetch[registeredNotifyTable, target];
IF found
THEN {
anyTargetsFound ¬ TRUE;
WITH val
SELECT
FROM
proc:
REF RegisteredNotifyProc => {
IF proc #
NIL
THEN results ¬ proc[event, buttonInfo]
ELSE results ¬ $Error;
ResultsFeedback[results, buttonInfo];
};
ENDCASE => {
Error["EmbeddedButtons: Internal error. Registered notify proc is wrong type (PassEventToApplication)"];
RETURN;
};
}
ELSE Error[Rope.Concat["This message handler (target) not found: ", Atom.GetPName[target]]];
};
ENDLOOP;
};
okEvent: LIST OF REF = LIST[$Done, $OK];
errorEvent: LIST OF REF = LIST[$Done, $Error];
warningEvent: LIST OF REF = LIST[$Done, $Warning];
ResultsFeedback:
PROC [results:
REF, buttonInfo: ButtonInfo] ~ {
feedbackEvent: LIST OF REF;
WITH results
SELECT
FROM
list: LIST OF REF => feedbackEvent ¬ list;
ENDCASE => {
IF results = NIL THEN feedbackEvent ¬ okEvent
ELSE IF results = $Error THEN feedbackEvent ¬ errorEvent
ELSE IF results = $Warning THEN feedbackEvent ¬ warningEvent
ELSE feedbackEvent ¬ LIST[results];
};
FeedbackNotify[feedbackEvent, buttonInfo];
};
FeedbackTable: TYPE = REF FeedbackTableRec;
FeedbackTableRec:
TYPE =
RECORD [
rope: ROPE,
table: REF FeedbackTableObj
];
FeedbackTableObj: TYPE = LIST OF FeedbackTableEntry;
FeedbackTableEntry: TYPE = REF FeedbackTableEntryRec;
FeedbackTableEntryRec:
TYPE =
RECORD [
event: LIST OF REF,
action: LIST OF REF
];
MatchFeedbackTableEntry:
PROC [feedbackEvent:
LIST OF REF, table: FeedbackTable]
RETURNS [bestAction:
LIST OF REF ¬
NIL] = {
bestActionMatches: INT ¬ 0;
Search the table for the most specific match to this event.
FOR l:
LIST
OF FeedbackTableEntry ¬ table.table, l.rest
UNTIL l =
NIL
DO
m: INT;
m ¬ IF Subset[l.first.event, feedbackEvent] THEN Matches[feedbackEvent, l.first.event] ELSE 0;
IF m > bestActionMatches
THEN {
bestAction ¬ l.first.action;
bestActionMatches ¬ m;
};
ENDLOOP;
};
Subset:
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];
};
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];
};
Matches:
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;
};
CheckFeedbackTable:
PROC [buttonInfo: ButtonInfo]
RETURNS [table: FeedbackTable ¬
NIL] = {
Get the feedback table
buttonFeedback: REF;
SELECT buttonInfo.private.language
FROM
$Poppy => {
buttonFeedback ¬ EBLanguage.GetFieldRef[buttonInfo.private.symbols, $Feedback];
};
ENDCASE => RETURN;
IF buttonFeedback = NIL THEN RETURN[NIL]; -- this button doesn't specify any feedback
WITH buttonFeedback
SELECT
FROM
t: FeedbackTable => {
table ¬ t;
IF table.table =
NIL
THEN {
table.table ¬ ParseFeedbackTable[table.rope];
IF table.table = NIL THEN RETURN[NIL];
};
};
ENDCASE => {
Error["EmbeddedButtons: Internal error. Button feedback table is wrong type (FeedbackNotify)"];
RETURN[NIL];
};
};
refTRUE: REF BOOL ¬ NEW[BOOL ¬ TRUE];
FeedbackNotify:
PUBLIC
PROC [feedbackEvent:
LIST OF REF, buttonInfo: ButtonInfo] = {
feedbackEvent may come from several places including
(1) Translating raw actions through ButtonClassesCommon.tip which produces the atoms Up, Down, Red, Yellow, Blue, MouseMoved and produces mouse coordinates.
(2) The SetValueProc of a given ButtonClass. For example, Multi-state buttons send ($Value, <ref>). Radio buttons send ($State, <REF BOOL>) or ($StateStill, <REF BOOL>).
table: FeedbackTable;
bestAction: LIST OF REF;
CodeTimer.StartInt[$FeedbackNotify, $EmbeddedButtons];
table ¬ CheckFeedbackTable[buttonInfo];
IF table = NIL THEN RETURN;
bestAction ¬ MatchFeedbackTableEntry[feedbackEvent, table];
IF bestAction =
NIL
OR (bestAction.first =
NIL
AND bestAction.rest =
NIL)
THEN
{
CodeTimer.StopInt[$FeedbackNotify, $EmbeddedButtons];
RETURN;
};
ActionToEditor[bestAction, buttonInfo, feedbackEvent];
CodeTimer.StopInt[$FeedbackNotify, $EmbeddedButtons];
};
Context: TYPE = EBTypes.Context;
feedbackContext: Context ¬ EBLanguage.CreateContext[];
InButton:
PUBLIC
PROC [x, y:
INTEGER, buttonInfo: ButtonInfo]
RETURNS[
BOOL] = {
IF buttonInfo.doc.private.class.inButton #
NIL
THEN
RETURN[buttonInfo.doc.private.class.inButton[buttonInfo.button, buttonInfo.doc, x, y]];
RETURN[TRUE];
};
MapButtons:
PUBLIC
PROC [doc: ActiveDoc, proc: EachButtonProc]
RETURNS [aborted:
BOOL ¬
FALSE] = {
IF doc.private.class.mapRef # NIL THEN aborted ¬ doc.private.class.mapRef[doc, proc];
};
EachButtonProc: TYPE = EmbeddedButtons.EachButtonProc;
Used By Feedback Procedures
RegisterPoppyProc:
PUBLIC
PROC [procedureName:
ATOM, proc: EBLanguageProc, interpreted:
BOOL ¬
TRUE] = {
EBLanguage.RegisterProc[procedureName, proc, interpreted];
};
FeedbackToEditor:
PUBLIC PROC [feedback:
REF, buttonInfo: ButtonInfo]
RETURNS [
REF] = {
RETURN[buttonInfo.doc.private.class.feedback[buttonInfo.button, buttonInfo.doc, feedback]];
};
ActionToEditor:
PUBLIC
PROC [action:
LIST
OF
REF, buttonInfo: ButtonInfo, feedbackEvent:
LIST
OF
REF] = {
receivers: LIST OF REF ¬ LIST[buttonInfo.doc.private.class.name];
CodeTimer.StartInt[$ActionToEditor, $EmbeddedButtons];
EBLanguage.SetSystemValue[feedbackContext, $MessageReceiver, receivers];
EBLanguage.SetSystemValue[feedbackContext, $Feedback, refTRUE];
IF
ISTYPE[action.first,
LIST
OF
REF]
THEN {
FOR l:
LIST
OF
REF ¬ action, l.rest
UNTIL l =
NIL
DO
WITH l.first
SELECT
FROM
lora:
LIST
OF
REF => {
message: REF ¬ EBLanguage.Evaluate[lora, buttonInfo, feedbackEvent, feedbackContext];
IF message # NIL THEN [] ¬ FeedbackToEditor[message, buttonInfo];
};
ENDCASE => Error["EmbeddedButtons: Feedback table actions must be lists!"];
ENDLOOP;
}
ELSE {
Otherwise, we have a list like (1 ApplyLook <TextFromValue>) or like <SetCursor bullseye>. Evaluate as needed and pass to the editor once.
message: REF ¬ EBLanguage.Evaluate[action, buttonInfo, feedbackEvent, feedbackContext];
IF message # NIL THEN [] ¬ FeedbackToEditor[message, buttonInfo];
};
CodeTimer.StopInt[$ActionToEditor, $EmbeddedButtons];
};
SetButtonValue:
PUBLIC
PROC [buttonInfo: ButtonInfo, variable:
ATOM ¬ $Value, value:
REF] = {
IF variable = $Value
THEN {
class: ButtonClass ¬ buttonInfo.private.class;
instanceData: REF ¬ buttonInfo.private.instanceData;
IF class #
NIL
AND class.setValue #
NIL
THEN {
changed: BOOL ¬ class.setValue[instanceData, value, buttonInfo];
IF changed THEN MarkButtonAsChanged[buttonInfo];
};
}
ELSE {
variableTable: VariableTable ¬ NARROW[GetFieldRef[$Variables, buttonInfo]];
IF variableTable = NIL THEN RETURN
ELSE {
realVar: Variable ¬ EBLanguage.GetVariable[variableTable, variable];
IF realVar =
NIL
THEN {
Error[IO.PutFR["Button %g has non-existent or incorrect definition for variable %g. Can't SetButtonValue", [atom[buttonInfo.private.name]], [atom[variable]] ]];
}
ELSE {
realVar.value ¬ value;
EBLanguage.SetVariable[variableTable, variable, realVar];
};
};
};
};
Used By Applications
NotifyProc: TYPE = EmbeddedButtons.NotifyProc;
RegisteredNotifyProc: TYPE = EmbeddedButtons.RegisteredNotifyProc;
LinkDocToApplication:
PUBLIC
PROC [doc: ActiveDoc, target:
ATOM, targetViewer: Viewer ¬
NIL, applicationData:
REF ¬
NIL, notifyProc: NotifyProc ¬
NIL] = {
refNotifyProc: REF NotifyProc ¬ IF notifyProc = NIL THEN NIL ELSE NEW[NotifyProc ¬ notifyProc];
tte: TargetTableEntry ¬
NEW[TargetTableEntryRep ¬ [
viewer: targetViewer,
applicationData: applicationData,
notifyProc: refNotifyProc]];
[] ¬ RefTab.Store[doc.private.targetTable, target, tte];
};
RegisterApplication:
PUBLIC PROC [target:
ATOM, notifyProc: RegisteredNotifyProc] = {
refRegisteredNotifyProc: REF RegisteredNotifyProc ¬ IF notifyProc = NIL THEN NIL ELSE NEW[RegisteredNotifyProc ¬ notifyProc];
[] ¬ RefTab.Store[registeredNotifyTable, target, refRegisteredNotifyProc];
};
GetValue:
PUBLIC
PROC [name:
ATOM, doc: ActiveDoc, variable:
ATOM ¬ $Value]
RETURNS [val:
REF
ANY] = {
Tries to find the value in the table of recently queried values. Otherwise, searches the document. In the current implementation, only value $Value is cached. Other values require searching the document.
MapGetKeyValue: EmbeddedButtons.EachButtonProc = {
PROC [button: ActiveButton, doc: ActiveDoc] RETURNS [done: BOOL ← FALSE]
class: ButtonClass;
instanceData: REF;
thisName: ATOM;
exists: BOOL ¬ FALSE;
[thisName, exists] ¬ GetName[button, doc];
IF exists
AND thisName = name
THEN {
buttonInfo: ButtonInfo ¬ GetButtonInfo[button, doc]; -- does a full parse, if needed
val ¬ GetButtonValue[buttonInfo, variable]; -- stores value in nameTable, if appropriate
};
};
found: BOOL ¬ FALSE;
IF variable = $Value
THEN {
[found, val] ¬ RefTab.Fetch[doc.private.nameTable, name];
};
IF val = NIL THEN [] ¬ doc.private.class.mapRef[doc, MapGetKeyValue];
};
GetButtonName:
PUBLIC
PROC [buttonInfo: ButtonInfo]
RETURNS [name:
ATOM] = {
exists: BOOL ¬ FALSE;
[name, exists] ¬ GetNameInternal[buttonInfo];
IF NOT exists THEN name ¬ NIL;
};
GetNameInternal:
PROC [buttonInfo: ButtonInfo]
RETURNS [name:
ATOM, exists:
BOOL ¬
FALSE] = {
IF buttonInfo #
NIL
THEN {
IF buttonInfo.private.nameChecked
THEN {
name ¬ buttonInfo.private.name;
exists ¬ TRUE;
}
ELSE {
success: BOOL ¬ ConfirmFields[buttonInfo];
IF success
THEN {
nameRope: ROPE ¬ GetFieldRope[$Name, buttonInfo];
nameVal: REF ¬ EBMesaLisp.Parse[IO.RIS[nameRope]].val;
IF nameVal #
NIL
THEN {
IF
ISTYPE[nameVal,
ATOM]
THEN {
name ¬ NARROW[nameVal];
exists ¬ TRUE;
buttonInfo.private.nameChecked ¬ TRUE;
buttonInfo.private.name ¬ name;
}
ELSE Error["GetName: Invalid Name field!"];
};
};
};
};
};
GetButtonValue:
PUBLIC
PROC [buttonInfo: ButtonInfo, variable:
ATOM ¬ $Value]
RETURNS [value:
REF ¬
NIL] = {
IF variable = $Value
THEN {
class: ButtonClass ¬ buttonInfo.private.class;
instanceData: REF ¬ buttonInfo.private.instanceData;
IF class #
NIL
AND class.getValue #
NIL
THEN
value ¬ class.getValue[instanceData, buttonInfo];
}
ELSE {
variableTable: VariableTable ¬ NARROW[GetFieldRef[$Variables, buttonInfo]];
IF variableTable = NIL THEN RETURN[NIL]
ELSE {
realVar: Variable ¬ EBLanguage.GetVariable[variableTable, variable];
IF realVar =
NIL
THEN {
value ¬ NIL;
Error[IO.PutFR["Button %g has non-existent or incorrect definition for variable %g. Can't GetButtonValue", [atom[buttonInfo.private.name]], [atom[variable]] ]];
}
ELSE value ¬ realVar.value;
};
};
};
SetValue:
PUBLIC
PROC [name:
ATOM, val:
REF, doc: ActiveDoc, variable:
ATOM ¬ $Value] = {
MapSetKeyValue: EBEditors.EachButtonProc = {
thisName: ATOM;
exists: BOOL ¬ TRUE;
[thisName, exists] ¬ GetName[button, doc];
IF exists
AND thisName = name
THEN {
buttonInfo: ButtonInfo ¬ GetButtonInfo[button, doc]; -- fully instantiates if necessary
IF buttonInfo = NIL THEN RETURN;
SetButtonValue[buttonInfo, variable, val];
};
};
[] ¬ doc.private.class.mapRef[doc, MapSetKeyValue];
};
Convenience Routines
GetRawButtonInfo:
PROC [button: ActiveButton, doc: ActiveDoc]
RETURNS [buttonInfo: ButtonInfo ¬
NIL] = {
data: REF ¬ doc.private.class.getRef[$ButtonData, button, doc];
IF data =
NIL
THEN {
Error[IO.PutFR1["Some button in doc %g has a NIL $ButtonData property (Instantiated?)", [rope[doc.private.class.getDocName[doc]]] ]];
buttonInfo ¬ NIL;
}
ELSE {
WITH data
SELECT
FROM
b: ButtonInfo => buttonInfo ¬ b;
r:
ROPE => {
buttonInfo ¬ NARROW[ButtonDataFromRope[r]];
doc.private.class.setRef[$ButtonData, button, doc, buttonInfo, FALSE];
};
ENDCASE => {
Error["Don't know what this ButtonData is!", "A button was pressed whose ButtonData was an unknown type."];
buttonInfo ¬ NIL;
};
};
};
GetButtonInfo:
PROC [button: ActiveButton, doc: ActiveDoc]
RETURNS [buttonInfo: ButtonInfo] = {
CodeTimer.StartInt[$GetButtonInfo, $EmbeddedButtons];
buttonInfo ¬ GetRawButtonInfo[button, doc];
IF buttonInfo #
NIL
THEN {
success: BOOL ¬ FALSE;
buttonInfo.button ¬ button;
buttonInfo.doc ¬ doc;
success ¬ ConfirmButtonInfo[buttonInfo];
IF NOT success THEN buttonInfo ¬ NIL;
};
CodeTimer.StopInt[$GetButtonInfo, $EmbeddedButtons];
};
GetName:
PROC [button: ActiveButton, doc: ActiveDoc]
RETURNS [name:
ATOM, exists:
BOOL ¬
FALSE] = {
buttonInfo: ButtonInfo ¬ GetRawButtonInfo[button, doc]; -- no parsing, but may already be parsed
IF buttonInfo #
NIL
THEN {
IF buttonInfo.private.nameChecked
THEN {
name ¬ buttonInfo.private.name;
exists ¬ TRUE;
}
ELSE {
success: BOOL ¬ ConfirmFields[buttonInfo];
IF success
THEN {
nameRope: ROPE ¬ GetFieldRope[$Name, buttonInfo];
nameVal: REF ¬ EBMesaLisp.Parse[IO.RIS[nameRope]].val;
IF nameVal #
NIL
THEN {
IF
ISTYPE[nameVal,
ATOM]
THEN {
name ¬ NARROW[nameVal];
exists ¬ TRUE;
buttonInfo.private.nameChecked ¬ TRUE;
buttonInfo.private.name ¬ name;
}
ELSE Error["GetName: Invalid Name field!"];
};
};
};
};
};
GetButtonClass:
PUBLIC
PROC [buttonInfo: ButtonInfo]
RETURNS [buttonClass: ButtonClass ¬
NIL] = {
buttonClass ¬ buttonInfo.private.class;
};
GetDocClassName:
PUBLIC PROC [doc: ActiveDoc]
RETURNS[name:
ATOM] = {
name ¬ doc.private.class.name;
};
GetFieldRope:
PUBLIC PROC [key:
ATOM, buttonInfo: ButtonInfo]
RETURNS [rope:
ROPE] = {
Gets the rope corresponding to the key in the specification of the button.
rope ¬ EBLanguage.GetFieldRope[buttonInfo.private.symbols, key];
};
GetFieldAtom:
PUBLIC PROC [key:
ATOM, buttonInfo: ButtonInfo]
RETURNS [atom:
ATOM] = {
Gets the atom corresponding to the key in the specification of the button.
atom ¬ EBLanguage.GetFieldAtom[buttonInfo.private.symbols, key];
};
GetFieldRef:
PUBLIC PROC [key:
ATOM, buttonInfo: ButtonInfo]
RETURNS [ref:
REF] = {
Gets the ref corresponding to the key in the specification of the button.
ref ¬EBLanguage.GetFieldRef[buttonInfo.private.symbols, key];
};
SetFieldRope:
PUBLIC PROC [key:
ATOM, rope:
ROPE, buttonInfo: ButtonInfo] = {
Gets the rope corresponding to the key in the specification of the button.
EBLanguage.SetFieldRope[buttonInfo.private.symbols, buttonInfo.private.order, key, rope];
};
SetFieldAtom:
PUBLIC PROC [key:
ATOM, atom:
ATOM, buttonInfo: ButtonInfo] = {
Gets the atom corresponding to the key in the specification of the button.
EBLanguage.SetFieldAtom[buttonInfo.private.symbols, buttonInfo.private.order, key, atom];
};
SetFieldRef:
PUBLIC PROC [key:
ATOM, ref:
REF, buttonInfo: ButtonInfo] = {
Gets the ref corresponding to the key in the specification of the button.
EBLanguage.SetFieldRef[buttonInfo.private.symbols, buttonInfo.private.order, key, ref];
};
GetAtom:
PUBLIC
PROC [key:
ATOM, buttonInfo: ButtonInfo]
RETURNS [atom:
ATOM ¬
NIL] = {
r: REF ¬ buttonInfo.doc.private.class.getRef[key, buttonInfo.button, buttonInfo.doc];
IF r = NIL THEN RETURN;
WITH r
SELECT
FROM
a: ATOM => atom ¬ a;
ENDCASE => Error["EmbeddedButtons: Value returned to GetAtom was not an ATOM."];
};
SetAtom:
PUBLIC
PROC [key:
ATOM, atom:
ATOM, buttonInfo: ButtonInfo, edited:
BOOL] = {
buttonInfo.doc.private.class.setRef[key, buttonInfo.button, buttonInfo.doc, atom, edited];
};
GetRope:
PUBLIC
PROC [key:
ATOM, buttonInfo: ButtonInfo]
RETURNS [rope:
ROPE] = {
r: REF ¬ buttonInfo.doc.private.class.getRef[key, buttonInfo.button, buttonInfo.doc];
IF r = NIL THEN RETURN;
WITH r
SELECT
FROM
a: ROPE => rope ¬ a;
ENDCASE => Error["EmbeddedButtons: Value returned to GetRope was not a ROPE."];
};
SetRope:
PUBLIC
PROC [key:
ATOM, rope:
ROPE, buttonInfo: ButtonInfo, edited:
BOOL] = {
buttonInfo.doc.private.class.setRef[key, buttonInfo.button, buttonInfo.doc, rope, edited];
};
GetRef:
PUBLIC
PROC [key:
ATOM, buttonInfo: ButtonInfo]
RETURNS [ref:
REF] = {
ref ¬ buttonInfo.doc.private.class.getRef[key, buttonInfo.button, buttonInfo.doc];
};
SetRef:
PUBLIC
PROC [key:
ATOM, ref:
REF, buttonInfo: ButtonInfo, edited:
BOOL] = {
buttonInfo.doc.private.class.setRef[key, buttonInfo.button, buttonInfo.doc, ref, edited];
};
GetText:
PUBLIC
PROC [buttonInfo: ButtonInfo]
RETURNS [text:
ROPE] = {
text ¬ buttonInfo.doc.private.class.getText[buttonInfo.button, buttonInfo.doc];
};
GetDocName:
PUBLIC
PROC [buttonInfo: ButtonInfo]
RETURNS [name:
ROPE] = {
name ¬ buttonInfo.doc.private.class.getDocName[buttonInfo.doc];
};
Internal Routines
ParseFeedbackTable:
PROC [r:
ROPE]
RETURNS [f:
REF FeedbackTableObj ¬
NIL] = {
s: IO.STREAM;
CodeTimer.StartInt[$ParseFeedbackTable, $EmbeddedButtons];
s ¬ IO.RIS[r];
f ¬ ParseCurrentFeedbackTable[s];
CodeTimer.StopInt[$ParseFeedbackTable, $EmbeddedButtons];
};
ParseCurrentFeedbackTable:
PROC [s:
IO.
STREAM]
RETURNS [f:
REF FeedbackTableObj ¬
NIL] = {
object: REF;
head: FeedbackTableObj;
tail: FeedbackTableObj;
char: CHAR;
f ¬ NEW[FeedbackTableObj ¬ NIL];
[] ¬ s.SkipWhitespace[];
char ¬ s.PeekChar[];
IF char # '(
AND char # '<
THEN {
Error["EmbeddedButtons: The value of Feedback: must be in parens or angle brackets"];
RETURN;
};
object ¬ EBMesaLisp.Parse[s].val;
IF
NOT
ISTYPE[object,
LIST OF REF]
THEN {
Error["EmbeddedButtons: The value of Feedback: must be a list"];
RETURN;
};
head ¬ LIST[NIL];
tail ¬ head;
FOR l:
LIST OF REF ¬
NARROW[object], l.rest
UNTIL l =
NIL
DO
first: LIST OF REF;
IF
NOT
ISTYPE[l.first,
LIST
OF
REF]
THEN {
Error["EmbeddedButtons: Entries in a feedback table must be event-action pairs"];
RETURN;
};
first ¬ NARROW[l.first];
IF
NOT
ISTYPE[first.first,
ATOM]
AND
NOT
ISTYPE[first.first,
LIST OF REF]
THEN {
Error["EmbeddedButtons: Invalid event in feedback table"];
RETURN;
};
IF
NOT
ISTYPE[first.rest.first,
LIST OF REF]
THEN {
Error["EmbeddedButtons: Invalid action in feedback table"];
RETURN;
};
tail.rest ¬
LIST[
NEW[FeedbackTableEntryRec ¬ [
event: IF ISTYPE[first.first, ATOM] THEN LIST[first.first] ELSE NARROW[first.first],
action: NARROW[first.rest.first]]]];
tail ¬ tail.rest;
ENDLOOP;
f ¬ head.rest;
};
ConfirmButtonInfo:
PROC [buttonInfo: ButtonInfo]
RETURNS [success:
BOOL ¬
TRUE] = {
IF buttonInfo.private.fieldValuesOK
THEN {
CodeTimer.StartInt[$ConfirmButtonInfoQuick, $EmbeddedButtons];
success ¬ TRUE;
IF buttonInfo.private.instanceData =
NIL
THEN {
buttonInfo.private.instanceData ¬
buttonInfo.private.class.instantiate[
buttonInfo: buttonInfo,
language: buttonInfo.private.language,
languageVersion: buttonInfo.private.languageVersion];
};
CodeTimer.StopInt[$ConfirmButtonInfoQuick, $EmbeddedButtons];
}
ELSE {
CodeTimer.StartInt[$ConfirmButtonInfo, $EmbeddedButtons];
success ¬ ConfirmSymbols[buttonInfo];
IF NOT success THEN {CodeTimer.StopInt[$ConfirmButtonInfo, $EmbeddedButtons]; RETURN};
IF buttonInfo.private.instanceData =
NIL
THEN {
buttonInfo.private.instanceData ¬
buttonInfo.private.class.instantiate[
buttonInfo: buttonInfo,
language: buttonInfo.private.language,
languageVersion: buttonInfo.private.languageVersion];
};
CodeTimer.StopInt[$ConfirmButtonInfo, $EmbeddedButtons];
};
};
ConfirmFields:
PROC [buttonInfo: ButtonInfo]
RETURNS [success:
BOOL ¬
TRUE] = {
Pick apart the ButtonData into fields, making an entry in "symbols" for each field. Don't parse the value section of each field.
We expect ButtonData to be in the form:
Poppy1 Class: <classname> <fieldname1>: <field1> <fieldname2>: <field2> etc.
buttonClassRope: ROPE;
buttonClassName: ATOM;
stream: IO.STREAM;
languageName: ROPE;
IF buttonInfo.private.class # NIL THEN RETURN; -- field names are OK
CodeTimer.StartInt[$ConfirmFields, $EmbeddedButtons];
stream ¬ IO.RIS[buttonInfo.private.rope];
languageName ¬ EBMesaLisp.ReadWWord[stream];
BEGIN
IF
NOT Rope.Equal[languageName, "Poppy1"]
THEN {
Error[Rope.Concat["Unknown button language: ", languageName]];
CodeTimer.StopInt[$ConfirmFields, $EmbeddedButtons];
RETURN[FALSE];
};
buttonInfo.private.language ¬ $Poppy;
buttonInfo.private.languageVersion ¬ 1;
[buttonInfo.private.symbols, buttonInfo.private.order] ¬ EBLanguage.PoppyParseFieldNames[stream];
buttonClassRope ¬ EBLanguage.GetFieldRope[buttonInfo.private.symbols, $Class];
buttonClassName ¬ Atom.MakeAtom[buttonClassRope];
buttonInfo.private.class ¬ LookupButtonClass[buttonClassName];
IF buttonInfo.private.class = NIL THEN GOTO NoSuchClass;
buttonInfo.private.fieldValuesOK ¬ FALSE;
EXITS
NoSuchClass => {
IF
NOT buttonInfo.private.hasComplained
THEN {
Error[Rope.Concat["Button class not registered: ", buttonClassRope], Rope.Concat["The button class for this button has not been registered: ", buttonClassRope]];
buttonInfo.private.hasComplained ¬ TRUE;
};
CodeTimer.StopInt[$ConfirmFields, $EmbeddedButtons];
RETURN[FALSE];
};
END;
CodeTimer.StopInt[$ConfirmFields, $EmbeddedButtons];
};
ConfirmSymbols:
PROC [buttonInfo: ButtonInfo]
RETURNS [success:
BOOL ¬
TRUE] = {
Pick apart the ButtonData into fields, making an entry in "symbols" for each field. Parse the value section of each field.
We expect ButtonData to be in the form:
Poppy1 Class: <classname> <fieldname1>: <field1> <fieldname2>: <field2> etc.
IF buttonInfo.private.fieldValuesOK THEN RETURN; -- field names and values are OK
CodeTimer.StartInt[$ConfirmSymbols, $EmbeddedButtons];
IF buttonInfo.private.class =
NIL
THEN {
-- field names and values are both needed
buttonClassRope: ROPE;
buttonClassName: ATOM;
stream: IO.STREAM ¬ IO.RIS[buttonInfo.private.rope];
languageName: ROPE ¬ EBMesaLisp.ReadWWord[stream];
BEGIN
IF
NOT Rope.Equal[languageName, "Poppy1"]
THEN {
Error[Rope.Concat["Unknown button language: ", languageName]];
CodeTimer.StopInt[$ConfirmSymbols, $EmbeddedButtons];
RETURN[FALSE];
};
buttonInfo.private.language ¬ $Poppy;
buttonInfo.private.languageVersion ¬ 1;
[buttonInfo.private.symbols, buttonInfo.private.order] ¬ EBLanguage.PoppyParse[stream];
buttonClassRope ¬ EBLanguage.GetFieldRope[buttonInfo.private.symbols, $Class];
buttonClassName ¬ Atom.MakeAtom[buttonClassRope];
buttonInfo.private.class ¬ LookupButtonClass[buttonClassName];
IF buttonInfo.private.class = NIL THEN GOTO NoSuchClass;
EXITS
NoSuchClass => {
IF
NOT buttonInfo.private.hasComplained
THEN {
Error[Rope.Concat["Button class not registered: ", buttonClassRope], Rope.Concat["The button class for this button has not been registered: ", buttonClassRope]];
buttonInfo.private.hasComplained ¬ TRUE;
};
CodeTimer.StopInt[$ConfirmSymbols, $EmbeddedButtons];
RETURN[FALSE];
};
END;
}
ELSE {
Field names are OK. Just parse the values.
EBLanguage.PoppyParseFieldValues[buttonInfo.private.symbols];
};
buttonInfo.private.fieldValuesOK ¬ TRUE;
CodeTimer.StopInt[$ConfirmSymbols, $EmbeddedButtons];
};
LookupButtonClass:
PROC [buttonClassName:
ATOM]
RETURNS [buttonClass: ButtonClass ¬
NIL] = {
found: BOOL;
b: REF ANY;
[found, b] ¬ RefTab.Fetch[classTable, buttonClassName];
IF
NOT
ISTYPE[b, ButtonClass]
THEN {
Error["EmbeddedButtons: Internal error. Button class is wrong type (LookupButtonClass).", ];
RETURN;
};
buttonClass ¬ NARROW[b];
};
MarkButtonAsChanged:
PUBLIC PROC [buttonInfo: ButtonInfo] = {
Tell the editor that the document has changed by setting the ButtonData property on the button
buttonInfo.private.changed ¬ TRUE;
buttonInfo.doc.private.class.setRef[$ButtonData, buttonInfo.button, buttonInfo.doc, buttonInfo, TRUE];
};
Initialization
classTable: RefTab.Ref ¬ RefTab.Create[];
registeredNotifyTable: RefTab.Ref ¬ RefTab.Create[];
EBLanguage.RegisterFieldParseProc[$Feedback, ParseFeedbackField];
EBLanguage.RegisterFieldUnparseProc[$Feedback, UnparseFeedbackField];
END.
$ButtonFeedback is an obsolete property. Where still used, this property is in the following format:
buttonFeedback ← "languageVersion ( feedbackTable )"
languageVersion ← 19890713
feedbackTable ← feedbackTableEntry | feedbackTableEntry feedbackTable
feedbackTableEntry ← ( event entryActions )
event ← userEvent | applicationEvent | ( qualifiersAndEvent ) | ( applicationEvents )
userEvent ← ATOM [usually $Up or $Down]
applicationEvent ← ATOM
applicationEvents ← applicationEvent | applicationEvent ApplicationEvents
qualifiersAndEvent ← qualifier
| qualifier qualifiersAndEvent
| userEvent
| userEvent qualifiersAndEvent
qualifier ← ATOM [usually $Red, $Control, $Shift, etc.]
entryActions ← entryAction | ( entryActionList )
entryActionList ← entryAction | entryAction entryActionList
entryAction ← action | handlerAction
handlerAction ← ( handler handlerParameters )
handler ← ATOM [the name of the handler]
handlerParameters ← handlerParameter | handlerParameter handlerParameters
handlerParameter ← value
value ← ATOM, BOOL, INT, REAL, or ROPE [a button value]
action ← ROPE [editor specific formatted feedback ROPE]