GGUserImpl.mesa
Copyright Ó 1986, 1989, 1991, 1992 by Xerox Corporation. All rights reserved.
Contents: Routines for reading the Gargoyle entries in the user profile and setting Gargoyle options. Also, procedures which handle user actions (menu buttons and mouse events).
Kurlander August 24, 1986 1:00:26 pm PDT
Goodisman, August 11, 1989 4:59:38 pm PDT
Pier, November 4, 1992 3:36 pm PST
Bier, August 20, 1993 7:12 pm PDT
Doug Wyatt, April 20, 1992 12:24 pm PDT
DIRECTORY
Atom, BasicTime, BiScrollers, CodeTimer, Convert, Feedback, GGActive, GGBasicTypes, GGControlPanelTypes, GGCoreOps, GGCoreTypes, GGEmbedTypes, GGEvent, GGFont, GGInterfaceTypes, GGModelTypes, GGScrollMonitor, GGSlice, GGState, GGTransform, GGUserInput, GGUserProfile, GGViewerOps, GGWindow, GGWorld, Imager, ImagerTransformation, InputFocus, IO, List, Menus, Process, Real, RealFns, RefTab, Rope, ScreenCoordsTypes, SlackProcess, TIPUser, UserProfile, Vector2, ViewerClasses;
GGUserImpl: CEDAR MONITOR
IMPORTS Atom, BasicTime, BiScrollers, CodeTimer, Convert, Feedback, GGActive, GGCoreOps, GGEvent, GGFont, GGScrollMonitor, GGSlice, GGState, GGTransform, GGUserInput, GGViewerOps, GGWindow, ImagerTransformation, InputFocus, IO, List, Process, RealFns, RefTab, Rope, SlackProcess, UserProfile
EXPORTS GGInterfaceTypes, GGUserInput, GGUserProfile = BEGIN
Camera: TYPE = GGModelTypes.Camera;
ControlsObj: PUBLIC TYPE = GGControlPanelTypes.ControlsObj; -- exported to GGInterfaceTypes
EmbedDataObj: PUBLIC TYPE = GGEmbedTypes.EmbedDataObj; -- exported to GGInterfaceTypes
Event: TYPE = GGCoreTypes.Event;
EventListt: TYPE = GGCoreTypes.EventListt;
External: TYPE = GGUserInput.External;
ExternalRec: TYPE = GGUserInput.ExternalRec;
FeatureData: TYPE = GGModelTypes.FeatureData;
FontData: TYPE = GGModelTypes.FontData;
GGData: TYPE = GGInterfaceTypes.GGData;
Point: TYPE = GGBasicTypes.Point;
RawInputHandlerProc: TYPE = GGUserInput.RawInputHandlerProc;
Scene: TYPE = GGModelTypes.Scene;
SlackHandle: TYPE = SlackProcess.SlackHandle;
Slice: TYPE = GGModelTypes.Slice;
SliceDescriptor: TYPE = GGModelTypes.SliceDescriptor;
Transformation: TYPE = ImagerTransformation.Transformation;
UserInputProc: TYPE = GGUserInput.UserInputProc;
VEC: TYPE = Imager.VEC;
XVEC: TYPE = RECORD [x, y: REAL];
Viewer: TYPE = ViewerClasses.Viewer;
Problem: PUBLIC SIGNAL [msg: Rope.ROPE] = Feedback.Problem;
externalCV: CONDITION;
ExternalRec is used by applications external to Gargoyle to return results to those applications. Clients should call EventNotify with a registered external proc name and a REF to an ExternalRec on a two-element event list. Client should then call WaitExternal in a loop checking their particular "valid" bit. Gargoyle completes the desired action, puts results in "results", and sets "valid" to TRUE. Gargoyle external procs will call BroadcastExternal when actions complete.
WaitExternal: PUBLIC ENTRY PROC [External] = {
WAIT externalCV;
};
BroadcastExternal: PUBLIC ENTRY PROC [External] = {
BROADCAST externalCV;
};
NotifyExternal: PUBLIC ENTRY PROC [External] = {
NOTIFY externalCV;
};
Before the Queue (done by InputNotifier, Menu Process or Playback Process)
From Buttons and Menus
EventNotify: PUBLIC PROC [clientData: REF ANY, event: LIST OF REF ANY] = {
ggData: GGData ¬ NARROW[clientData];
Used by several menu classes as the procedure to call when a menu click occurs.
ProcessAndQueueEvent[event, ggData];
};
UnQueuedEventNotify: PUBLIC PROC [clientData: REF ANY, event: LIST OF REF ANY] = {
Used by several menu classes as the procedure to call when a menu click occurs.
ggData: GGData ~ NARROW[clientData];
atom: ATOM ~ NARROW[event.first];
regEvent: RegisteredEvent ~ FetchAction[atom];
IF regEvent=NIL THEN {
NotYetImplementedMessage[atom, ggData];
}
ELSE {
event ¬ GetAnyArguments[event, regEvent, ggData];
TRUSTED {Process.Detach[FORK regEvent.eventProc[ggData, event] ]; };
};
};
From Playback Scripts
PlayAction: PUBLIC PROC [clientData: REF ANY, event: LIST OF REF ANY] = {
ggData: GGData ¬ NARROW[clientData];
IF
event.first = $Version OR
event.first = $Store OR
event.first = $Save OR
event.first = $ToIP OR
event.first = $ToIPScreen OR
event.first = $ToIPLit OR
event.first = $IPToTioga OR
event.first = $IPToTiogaBordered OR
event.first = $IPToTiogaFit OR
event.first = $IPToTiogaBorderedAndFit OR
event.first = $IPToTiogaAlt OR
event.first = $StuffToTioga OR
event.first = $StuffToFile OR
event.first = $MergeFromTioga OR
event.first = $StuffToTiogaBordered OR
event.first = $StuffToFileAlt OR
event.first = $GrabFromTioga OR
event.first = $StuffToTiogaFit OR
event.first = $GetFromTioga OR
event.first = $StuffToTiogaBorderedAndFit OR
event.first = $MergeFromGargoyle OR
event.first = $StuffToTiogaAlt
THEN RETURN;
ProcessAndQueueEvent[event, ggData];
};
Regular Input Notification
gRawInputHandler: GGUserInput.RawInputHandlerProc ¬ GGActive.ActiveInputHandler;
InputNotify: PUBLIC ViewerClasses.NotifyProc = {
Called by the TIP table machinery when an action is received from mouse or keyboard. Self is an ActionArea.
ggData: GGData ~ NARROW[BiScrollers.ClientDataOfViewer[self]];
notify: PROC [input: LIST OF REF] ~ {
InputFocus.SetInputFocus[ggData.controls.actionArea];
Problem: If a gargoyle scene changes from active to inactive due to a queued action, the length of the queue will determine when Gargoyle gets the input focus.
ProcessAndQueueEvent[input, ggData];
};
gRawInputHandler[self, ggData, input, notify];
};
BiScrollerInputNotify: PUBLIC PROC [ggData: GGData, event: LIST OF REF] = {
An action has been received from BiScroller (e.g. from the ScrollBar).
BiScrollerQueue: PROC [event: LIST OF REF ANY, ggData: GGData] = {
first: ATOM ¬ NARROW[event.first];
IF first=$Shift THEN {
vec: REF VECNARROW[event.rest.first]; -- Shift VECTOR
xVec: REF XVEC ¬ NEW[XVEC ¬ [vec.x, vec.y] ]; -- put it in a record different from type VEC
event ¬ LIST[$Shift, xVec];
};
ProcessAndQueueEvent[event, ggData];
};
IF ISTYPE[event.first, LIST OF REF] THEN {
FOR list: LIST OF REF ← event, list.rest UNTIL list = NIL DO
sublist: REF ¬ list.first;
IF ISTYPE[sublist, LIST OF REF] THEN BiScrollerQueue[NARROW[sublist], ggData];
ENDLOOP;
}
ELSE BiScrollerQueue[event, ggData];
};
finishText: LIST OF REF ANY ~ LIST[$SawTextFinish];
finishMouse: LIST OF REF ANY ~ LIST[$SawMouseFinish];
ProcessAndQueueEvent: PROC [event: LIST OF REF ANY, ggData: GGData] = {
The event will be in one of two forms:
1) LIST[REF CHAR].
2) LIST[ATOM, ... ].
IF ggData.embed.beingBorn THEN RETURN; -- don't handle events while the window is being created
WITH event.first SELECT FROM
refChar: REF CHAR => {
Need our own copy of the ref and char, because the system reuses the storage.
myRefChar: REF CHAR ~ NEW[CHAR ¬ refChar­];
event ¬ LIST[$AddChar, myRefChar];
};
atom: ATOM => {
regEvent: RegisteredEvent ~ FetchAction[atom];
IF regEvent=NIL THEN NotYetImplementedMessage[atom, ggData]
ELSE {
atomName: Rope.ROPE ~ Atom.GetPName[atom];
startAction: BOOL ~ Rope.Equal[Rope.Substr[atomName, 0, 5], "Start", TRUE];
DKW: Yecch! Maybe the RegisteredEvent could contain this information.
IF startAction THEN
QueueInput[ggData, finishText]; -- need this before selection changes
IF regEvent.causeMouseEventsToComplete THEN
QueueInput[ggData, finishMouse];
event ¬ GetAnyArguments[event, regEvent, ggData];
IF atom = $OneScroll THEN {
IF event.rest # NIL THEN
BEGIN
x, y: INTEGER ¬ 0;
WITH event.rest.first SELECT FROM
change: REF Imager.VEC => {
GGScrollMonitor.ConcatToDue[ggData, ImagerTransformation.Translate[[change.x, change.y]] ];
GOTO EasyCase;
};
i: REF INTEGER => x ¬ i­;
ENDCASE;
IF event.rest.rest # NIL THEN {
WITH event.rest.rest.first SELECT FROM
i: REF INTEGER => y ¬ i­;
ENDCASE;
};
GGScrollMonitor.ConcatToDue[ggData, ImagerTransformation.Translate[[x, y]] ];
EXITS
EasyCase => NULL;
END;
}
ELSE IF atom = $OneZoom THEN {
originViewer: Point;
x: INTEGER ¬ 0;
fx, scalar: REAL;
viewport: Imager.Rectangle ¬ GGState.GetViewport[ggData];
originViewer ¬ [viewport.x+(viewport.w/2.0), viewport.y+(viewport.h/2.0)];
IF event.rest # NIL THEN {
WITH event.rest.first SELECT FROM
i: REF INTEGER => x ¬ i­;
ENDCASE;
};
fx ¬ x; -- convert to float
scalar ¬ RealFns.Power[base: 2.0, exponent: fx/50.0]; -- fifty clicks of the track ball gives a factor of two
Feedback.PutFL[ggData.router, oneLiner, $Typescript, "[%g %g: %g] ", LIST[ [real[originViewer.x]], [real[originViewer.y]], [real[scalar]] ]];
GGScrollMonitor.ConcatToDue[ggData, GGTransform.ScaleAboutPoint[originViewer, scalar] ];
};
};
};
ENDCASE => ERROR;
QueueInput[ggData, event, NIL];
};
Timestamp: TYPE = REF TimestampObj;
TimestampObj: TYPE = RECORD [
startTime: CARD32
];
timeQueue: BOOL ¬ FALSE;
QueueInput: PROC [ggData: GGData, event: LIST OF REF ANY, optimizeHint: REF ANY ¬ NIL] ~ {
handle: SlackHandle ~ ggData.slackHandle;
GGEvent.PrintAllInput[ggData, event]; -- KAP
IF timeQueue THEN event ¬ List.Nconc1[event, NEW[TimestampObj ¬ [NowInMilliseconds[]]] ]; -- for timing how much time is spent on the queue
SlackProcess.QueueAction[handle, Dispatch, event, ggData, optimizeHint];
};
GetAnyArguments: PROC [event: LIST OF REF ANY, regEvent: RegisteredEvent, ggData: GGData] RETURNS [newEvent: LIST OF REF ANY] = {
atom: ATOM ¬ NARROW[event.first];
SELECT regEvent.argType FROM
none => newEvent ¬ event;
DKW: REF VEC values come from Biscrollers, which treats them as immutable, so the following is unnecessary. Maybe this code is left over from pre-Biscrollers days; TIPScreenCoords values straight from TIP are mutable, and would have to be copied.
none => {
tail: LIST OF REF ANY;
Copy all of the VECs since the TIP table reuses the storage.
tail ← newEvent ← CONS[event.first, NIL];
FOR list: LIST OF REF ANY ← event.rest, list.rest UNTIL list = NIL DO
IF ISTYPE[list.first, REF VEC] THEN {
mousePlace: REF VECNARROW[list.first];
tail.rest ← CONS[NEW[VEC ← [mousePlace.x, mousePlace.y]], NIL];
}
ELSE tail.rest ← CONS[list.first, NIL];
tail ← tail.rest;
ENDLOOP;
};
rope => newEvent ¬ CheckForSelectedRope[atom, event];
rope2 => newEvent ¬ CheckForSelectedRope2[atom, event];
refReal => newEvent ¬ CheckForSelectedReal[atom, event];
refInt => newEvent ¬ CheckForSelectedInt[atom, event];
refCard => newEvent ¬ CheckForSelectedCard[atom, event];
refExt => newEvent ¬ CheckForActualExt[atom, event];
ENDCASE => ERROR;
};
RegisterRawInputHandler: PUBLIC PROC [rawInputHandler: RawInputHandlerProc] = {
gRawInputHandler ¬ rawInputHandler;
};
After the Queue (Done by SlackProcess)
NowInMilliseconds: PROC RETURNS [CARD32] = {
RETURN[(BasicTime.PulsesToMicroseconds[BasicTime.GetClockPulses[]]+500)/1000];
};
PrintUserTrace: PROC [ggData: GGInterfaceTypes.GGData, eventProc: GGUserInput.UserInputProc, event: LIST OF REF ANY] ~ {
procTV: AMTypes.TV ← AMBridge.TVForProc[eventProc];
moduleTV: AMTypes.TV ← AMTypes.GlobalParent[procTV];
procName: Rope.ROPE ← AMTypes.TVToName[procTV];
moduleName: Rope.ROPE ← AMTypes.TVToName[moduleTV];
procName: Rope.ROPE ¬ "?";
moduleName: Rope.ROPE ¬ "?";
thisArgName: Rope.ROPE;
allArgs: Rope.ROPE;
FOR list: LIST OF REF ANY ¬ event, list.rest UNTIL list = NIL DO
thisArgName ¬ IO.PutFR1["%g", [refAny[list.first]] ];
IF allArgs = NIL THEN allArgs ¬ thisArgName
ELSE allArgs ¬ Rope.Cat[allArgs, ", ", thisArgName];
ENDLOOP;
Feedback.PutFL[ggData.router, oneLiner, $Typescript, "%g.%g[%g]", LIST[[rope[moduleName]], [rope[procName]], [rope[allArgs]]] ];
};
Dispatch: PROC [clientData: REF ANY, inputAction: REF] = {
ggData: GGData ~ NARROW[clientData];
atom: ATOM;
startTime, endTime: CARD32;
startTimeRef: REF;
regEvent: RegisteredEvent;
event: LIST OF REF ¬ NARROW[inputAction];
UpdateCoords: PROC [input: LIST OF REF] RETURNS [o: LIST OF REF] = {
i, l: LIST OF REF;
viewerToClient: Transformation ← GGState.GetBiScrollersTransforms[ggData].viewerToClient;
IF input.first = $AlignFracs OR (input.rest # NIL AND input.rest.first = $AlignFracs) THEN RETURN[input]; -- because BiScrollers uses REF Vector2.VEC for the shift amount
FOR i ¬ input, i.rest WHILE i # NIL DO
l ¬ IF l = NIL THEN o ¬ CONS[i.first, NIL] ELSE l.rest ¬ CONS[i.first, NIL];
WITH l.first SELECT FROM
z: TIPUser.TIPScreenCoords => {
l.first ¬ NEW [Vector2.VEC ¬ viewerToClient.Transform[[z.mouseX, z.mouseY]]];
};
v: REF Vector2.VEC => {
l.first ← NEW [Vector2.VEC ← viewerToClient.Transform[v^]];
};
z: REF XVEC => {
l.first ← NEW [Vector2.VEC ← [z.x, z.y] ];
};
ENDCASE;
ENDLOOP;
};
event ¬ UpdateCoords[event]; -- perform the BiScrollers transform
IF timeQueue THEN {
startTimeRef ¬ List.NthElement[event, -1];
WITH startTimeRef SELECT FROM
timeStamp: Timestamp => {
startTime ¬ timeStamp.startTime;
event ¬ List.DRemove[startTimeRef, event];
endTime ¬ NowInMilliseconds[];
CodeTimer.SetIntMilliseconds[$TimeOnQueue, startTime, endTime, $Gargoyle];
};
ENDCASE;
};
atom ¬ NARROW[event.first];
regEvent ¬ FetchAction[atom];
IF regEvent=NIL THEN NotYetImplementedMessage[atom, ggData]
ELSE {
IF event.first=$Again THEN {
IF NOT GGCoreOps.NoEvents[ggData.lastEvents] THEN {
FOR list: LIST OF Event ¬ ggData.lastEvents.list, list.rest UNTIL list = NIL DO
thisEvent: LIST OF REF ANY ~ list.first;
thisAtom: ATOM ~ NARROW[thisEvent.first];
thisRegEvent: RegisteredEvent ~ FetchAction[thisAtom];
IF thisRegEvent=NIL
THEN NotYetImplementedMessage[thisAtom, ggData]
ELSE thisRegEvent.eventProc[ggData, thisEvent];
ENDLOOP;
}
ELSE {}; -- there is nothing to do again
}
ELSE {
SELECT GetEventClass[atom] FROM
select => ggData.justSawSelect ¬ TRUE;
action => {
IF ggData.justSawSelect THEN {
GGCoreOps.FlushEventListt[ggData.lastEvents];
ggData.justSawSelect ¬ FALSE;
};
GGCoreOps.AppendEvent[event, ggData.lastEvents];
};
suppress => {}; -- $During. Ignore it.
neutral => {
IF ggData.justSawSelect THEN {} -- this is the during or end of a select operation
ELSE GGCoreOps.AppendEvent[event, ggData.lastEvents];
};
ENDCASE => ERROR;
IF GGUserInput.GetUserTraceOn[] THEN
PrintUserTrace[ggData, regEvent.eventProc, event];
regEvent.eventProc[ggData, event];
};
};
};
EventClass: TYPE = {select, neutral, action, suppress};
GetEventClass: PROC [atom: ATOM] RETURNS [eventClass: EventClass] = {
IF ( -- oops. You can't suppress all of the Durings. You must not suppress the last one for a given operation. Yuk.
atom = $During
) THEN RETURN [suppress];
IF (
atom = $During OR
atom = $GuardUp OR
atom = $MouseUp OR
atom = $AllUp OR
atom = $SawTextFinish OR
atom = $SawMouseFinish
) THEN RETURN [neutral];
IF (
atom = $StartSelectWithBox OR
atom = $StartSelectJoint OR
atom = $StartExtSelectJoint OR
atom = $StartSelectSegment OR
atom = $StartExtSelectSegment OR
atom = $StartSelectTrajectory OR
atom = $StartExtSelectTrajectory OR
atom = $StartSelectTopLevel OR
atom = $StartExtSelectTopLevel OR
atom = $StartExtendSelection OR
atom = $StartDeselectJoint OR
atom = $StartDeselectSegment OR
atom = $StartDeselectTrajectory OR
atom = $StartDeselectTopLevel OR
atom = $CycleSelection OR
atom = $AreaSelectNew OR
atom = $AreaSelectNewAndDelete OR
atom = $SelectAll OR
atom = $AreaSelectDegenerate OR
atom = $SelectCoincident OR
atom = $SelectUnseeableSegs OR
atom = $SelectUnseeableObjs
) THEN RETURN [select];
IF (
atom = $StartCaretPos OR
atom = $StartAdd OR
atom = $StartBox OR
atom = $StartDrag OR
atom = $StartCopyAndDrag OR
atom = $StartAddAndDrag OR
atom = $StartRotate OR
atom = $StartScale OR
atom = $StartSixPoint
) THEN RETURN [action];
IF (
atom = $OneZoom OR
atom = $OneScroll
) THEN RETURN [suppress]; -- added November 4, 1992. KAP.
RETURN [action];
};
Utility Routines
NotYetImplementedMessage: PROC [atom: ATOM, ggData: GGData] = {
Feedback.Append[ggData.router, begin, $Warning, "User event "];
Feedback.Append[ggData.router, middle, $Warning, Atom.GetPName[atom]];
Feedback.Append[ggData.router, end, $Warning, " is not yet implemented"];
};
CheckForSelectedRope: PROC [atom: ATOM, event: LIST OF REF ANY] RETURNS [newAction: LIST OF REF ANY] = {
IF event.rest = NIL THEN { -- interactive call
r: Rope.ROPE ¬ GGViewerOps.GetSelectionContents[];
newAction ¬ LIST[atom, r];
}
ELSE IF ISTYPE[event.rest.first, REF TEXT] THEN { -- TIP table call
newAction ¬ CONS[atom, CONS[Rope.FromRefText[NARROW[event.rest.first]], event.rest.rest]];
}
ELSE newAction ¬ event; -- SessionLog call
};
CheckForSelectedRope2: PROC [atom: ATOM, event: LIST OF REF ANY] RETURNS [newAction: LIST OF REF ANY] = {
Expect a rope as the second argument.
IF event.rest = NIL THEN ERROR;
IF event.rest.rest = NIL THEN { -- interactive call
r: Rope.ROPE ¬ GGViewerOps.GetSelectionContents[];
newAction ¬ LIST[atom, event.rest.first, r];
}
ELSE IF ISTYPE[event.rest.rest.first, REF TEXT] THEN { -- TIP table call
newAction ¬ LIST[atom, event.rest.first, Rope.FromRefText[NARROW[event.rest.rest.first]], event.rest.rest.rest];
}
ELSE newAction ¬ event; -- SessionLog call
};
CheckForSelectedReal: PROC [atom: ATOM, event: LIST OF REF ANY] RETURNS [newAction: LIST OF REF ANY] = {
rope: Rope.ROPE;
real: REAL;
IF event.rest = NIL THEN { -- interactive call
rope ¬ GGViewerOps.GetSelectionContents[];
real ¬ Convert.RealFromRope[rope ! Convert.Error => {real ¬ Real.LargestNumber; CONTINUE}];
newAction ¬ LIST[atom, NEW[REAL ¬ real]];
}
ELSE IF ISTYPE[event.rest.first, REF TEXT] THEN { -- TIP table call
rope ¬ Rope.FromRefText[NARROW[event.rest.first]];
real ¬ Convert.RealFromRope[rope ! Convert.Error => {real ¬ Real.LargestNumber; CONTINUE}];
newAction ¬ LIST[atom, NEW[REAL ¬ real]];
}
ELSE newAction ¬ event; -- SessionLog call
};
CheckForSelectedCard: PROC [atom: ATOM, event: LIST OF REF ANY] RETURNS [newAction: LIST OF REF ANY] = {
rope: Rope.ROPE;
card: CARD;
IF event.rest = NIL THEN { -- interactive call
rope ¬ GGViewerOps.GetSelectionContents[];
card ¬ IO.GetCard[IO.RIS[rope] ! IO.EndOfStream, IO.Error => {card ¬ LAST[CARD]; CONTINUE}];
newAction ¬ LIST[atom, NEW[CARD ¬ card]];
}
ELSE IF ISTYPE[event.rest.first, REF CARD] THEN { -- TIP table call or SessionLog call
newAction ¬ event;
}
ELSE ERROR;
};
CheckForSelectedInt: PROC [atom: ATOM, event: LIST OF REF ANY] RETURNS [newAction: LIST OF REF ANY] = {
rope: Rope.ROPE;
int: INT;
IF event.rest = NIL THEN { -- interactive call
rope ¬ GGViewerOps.GetSelectionContents[];
int ¬ IO.GetInt[IO.RIS[rope] ! IO.EndOfStream, IO.Error => {int ¬ LAST[INT]; CONTINUE}];
newAction ¬ LIST[atom, NEW[INT ¬ int]];
}
ELSE {
WITH event.rest.first SELECT FROM
int: REF INT => newAction ¬ event; -- TIP table call or SessionLog call
card: REF CARD => newAction ¬ List.Append[LIST[atom, NEW[INT ¬ card­]], event.rest.rest]; -- type coersion
ENDCASE => ERROR;
};
};
CheckForActualExt: PROC [atom: ATOM, event: LIST OF REF ANY] RETURNS [newAction: LIST OF REF ANY] = {
IF event.rest=NIL OR NOT ISTYPE[event.rest.first, REF GGUserInput.ExternalRec] THEN ERROR;
newAction ¬ event;
};
RegisteredEvent: TYPE = REF RegisteredEventObj;
RegisteredEventObj: TYPE = RECORD [
eventProc: UserInputProc,
argType: GGUserInput.ArgumentType,
causeMouseEventsToComplete: BOOL
];
RegisterAction: PUBLIC PROC [atom: ATOM, eventProc: UserInputProc, argType: GGUserInput.ArgumentType, causeMouseEventsToComplete: BOOL ¬ TRUE, ensureUnique: BOOL ¬ TRUE] = {
If the event has already been registered and ensureUnique is true, an error will be signalled. If the event has been registered and ensureUnique is FALSE, the action will be reregistered.
regEvent: RegisteredEvent ¬ NEW[RegisteredEventObj ¬ [eventProc, argType, causeMouseEventsToComplete]];
justInserted: BOOL ¬ RefTab.Insert[eventTable, atom, regEvent];
IF NOT justInserted THEN
IF ensureUnique THEN SIGNAL Problem[msg: IO.PutFR1["Event %g was already registered in Gargoyle's event table.", [rope[Atom.GetPName[atom]]]]]
ELSE [] ¬ RefTab.Replace[eventTable, atom, regEvent]; -- register again
[] ¬ RefTab.Replace[eventTable, atom, regEvent];
};
FetchAction: PROC [atom: ATOM] RETURNS [RegisteredEvent] ~ {
WITH RefTab.Fetch[eventTable, atom].val SELECT FROM
regEvent: RegisteredEvent => RETURN[regEvent];
ENDCASE => RETURN[NIL];
};
GGUserProfile
LookAtProfile: PUBLIC UserProfile.ProfileChangedProc = {
[reason: UserProfile.ProfileChangeReason]
gravExtent: REAL; -- in inches
defaultHistorySize: INT;
heuristics: BOOL ¬ UserProfile.Boolean[key: "Gargoyle.Heuristics", default: FALSE];
quickClickMode: BOOL ¬ UserProfile.Boolean[key: "Gargoyle.QuickClickEnable", default: FALSE];
useLatestIPVersion: BOOL ¬ UserProfile.Boolean[key: "Gargoyle.UseLatestIPVersion", default: FALSE];
autoOpenTypescript: BOOL ¬ UserProfile.Boolean[key: "Gargoyle.AutoOpenTypescript", default: TRUE];
autoOpenHistory: BOOL ¬ UserProfile.Boolean[key: "Gargoyle.AutoOpenHistory", default: TRUE];
autoScriptingOn: BOOL ¬ UserProfile.Boolean[key: "Gargoyle.AutoScriptingOn", default: FALSE];
separateControlPanel: BOOL ¬ UserProfile.Boolean[key: "Gargoyle.SeparateControlPanel", default: FALSE];
defaultIncludeIPBy: Rope.ROPE ¬ UserProfile.Token[key: "Gargoyle.DefaultIncludeIPBy", default: "Reference"];
holdThatTiger: BOOL ¬ UserProfile.Boolean[key: "Gargoyle.HoldThatTiger", default: FALSE];
newBoxesUnfilled: BOOL ¬ UserProfile.Boolean[key: "Gargoyle.NewBoxesUnfilled", default: FALSE];
defaultHistorySize ¬ Convert.IntFromRope[UserProfile.Token[key: "Gargoyle.DefaultHistorySize", default: "40"]
! Convert.Error => IF reason = syntax THEN {defaultHistorySize ¬ -1; CONTINUE;} ELSE REJECT];
gravExtent ¬ Convert.RealFromRope[UserProfile.Token[key: "Gargoyle.GravityExtent", default: "-1.0"]
! Convert.Error => IF reason = syntax THEN {gravExtent ¬ -1.0; CONTINUE;} ELSE REJECT];
IF gravExtent=-1.0 THEN gravExtent ¬ 25.0/72.0;
IF defaultHistorySize<1 THEN defaultHistorySize ¬ IF defaultHistorySize=0 THEN 1 ELSE 40;
SetDefaultHeuristics[on: heuristics];
SetDefaultGravityExtent[inches: gravExtent];
GGState.SetQuickClickMode[quickClickMode];
SetDefaultUseLatestIPVersion[useLatestIPVersion];
SetAutoOpenTypescript[autoOpenTypescript];
SetAutoOpenHistory[autoOpenHistory];
SetDefaultHistorySize[defaultHistorySize];
SetAutoScriptingOn[autoScriptingOn];
SetSeparateControlPanel[separateControlPanel];
SetHoldThatTiger[holdThatTiger];
SetNewBoxesUnfilled[newBoxesUnfilled];
SetDefaultIncludeIPByValue[Rope.Equal[defaultIncludeIPBy, "Value", FALSE]];
BEGIN -- set the default default font
description: Rope.ROPE ¬ UserProfile.Token[key: "Gargoyle.DefaultDefaultFont", default: NIL];
descriptionStream: IO.STREAM;
defaultFont: FontData;
IF description = NIL THEN
description ¬ "xerox/xc1-2-2/helvetica [r1: 0.0 s: [10.0 10.0] r2: 0.0] 1.0 1.0";
formerly "xerox/pressfonts/helvetica-mrr [r1: 0.0 s: [10.0 10.0] r2: 0.0] 1.0 1.0"
descriptionStream ¬ IO.RIS[description];
defaultFont ¬ GGFont.ParseFontData[inStream: descriptionStream, literalP: TRUE, transformP: TRUE, storedSizeP: TRUE, designSizeP: TRUE];
SetDefaultDefaultFont[defaultFont];
END;
IF reason#firstTime THEN GGWindow.InitIcons[]; -- don't do this on first registering this proc
};
No matter how many Gargoyle viewers are present, there is only one MasterData.
MasterData: TYPE = REF MasterDataObj;
MasterDataObj: TYPE = RECORD [
defaultGravityExtent: REAL ¬ 25.0, -- in screen dots
defaultHeuristics: BOOL ¬ TRUE,
objectsBeingCopied: LIST OF REF ANY, -- for copying objects from viewer to viewer,
defaultDefaultFont: FontData,
autoOpenTypescript: BOOL ¬ TRUE,
autoOpenHistory: BOOL ¬ TRUE,
defaultHistorySize: INT ¬ 40,
autoScriptingOn: BOOL ¬ TRUE,
separateControlPanel: BOOL ¬ FALSE,
defaultIncludeIPByValue: BOOL ¬ FALSE,
holdThatTiger: BOOL ¬ FALSE,
newBoxesUnfilled: BOOL ¬ FALSE,
turboOn: BOOL ¬ FALSE,
userTraceOn: BOOL ¬ FALSE
];
SetUserTraceOn: PUBLIC PROC [on: BOOL] = {
masterData.userTraceOn ¬ on;
};
GetUserTraceOn: PUBLIC PROC RETURNS [on: BOOL] = {
on ¬ masterData.userTraceOn;
};
SetDefaultHeuristics: PUBLIC PROC [on: BOOL] = {
When a new viewer is created, should heuristics be turned on?
masterData.defaultHeuristics ¬ on;
};
GetDefaultHeuristics: PUBLIC PROC RETURNS [on: BOOL] = {
When a new viewer is created, should heuristics be turned on?
on ¬ masterData.defaultHeuristics;
};
SetDefaultGravityExtent: PUBLIC PROC [inches: REAL] = {
When a new viewer is created, how strong should its gravity be?
masterData.defaultGravityExtent ¬ inches*72.0;
};
GetDefaultGravityExtent: PUBLIC PROC RETURNS [screenDots: REAL] = {
When a new viewer is created, how strong should its gravity be?
screenDots ¬ masterData.defaultGravityExtent;
};
SetDefaultUseLatestIPVersion: PUBLIC PROC [useLatestIPVersion: BOOL] = {
GGSlice.SetDefaultUseLatestIPVersion[useLatestIPVersion];
};
GetDefaultUseLatestIPVersion: PUBLIC PROC [] RETURNS [useLatestIPVersion: BOOL] = {
RETURN[GGSlice.GetDefaultUseLatestIPVersion[]];
};
SetDefaultDefaultFont: PUBLIC PROC [font: FontData] = {
masterData.defaultDefaultFont ¬ font;
};
GetDefaultDefaultFont: PUBLIC PROC RETURNS [font: FontData] = {
font ¬ masterData.defaultDefaultFont;
};
SetAutoOpenTypescript: PUBLIC PROC [autoOpenTypescript: BOOL] = {
masterData.autoOpenTypescript ¬ autoOpenTypescript;
};
GetAutoOpenTypescript: PUBLIC PROC [] RETURNS [autoOpenTypescript: BOOL] = {
autoOpenTypescript ¬ masterData.autoOpenTypescript;
};
SetAutoOpenHistory: PUBLIC PROC [autoOpenHistory: BOOL] = {
masterData.autoOpenHistory ¬ autoOpenHistory;
};
GetAutoOpenHistory: PUBLIC PROC [] RETURNS [autoOpenHistory: BOOL] = {
autoOpenHistory ¬ masterData.autoOpenHistory;
};
SetDefaultHistorySize: PUBLIC PROC [defaultHistorySize: INT] = {
masterData.defaultHistorySize ¬ defaultHistorySize;
};
GetDefaultHistorySize: PUBLIC PROC RETURNS [defaultHistorySize: INT] = {
defaultHistorySize ¬ masterData.defaultHistorySize;
};
SetAutoScriptingOn: PUBLIC PROC [autoScriptingOn: BOOL] = {
masterData.autoScriptingOn ¬ autoScriptingOn;
};
GetAutoScriptingOn: PUBLIC PROC [] RETURNS [autoScriptingOn: BOOL] = {
autoScriptingOn ¬ masterData.autoScriptingOn;
};
SetSeparateControlPanel: PUBLIC PROC [separateControlPanel: BOOL] = {
masterData.separateControlPanel ¬ separateControlPanel;
};
GetSeparateControlPanel: PUBLIC PROC [] RETURNS [separateControlPanel: BOOL] = {
separateControlPanel ¬ masterData.separateControlPanel;
};
SetDefaultIncludeIPByValue: PUBLIC PROC [defaultIncludeIPByValue: BOOL] = {
masterData.defaultIncludeIPByValue ¬ defaultIncludeIPByValue;
};
GetDefaultIncludeIPByValue: PUBLIC PROC [] RETURNS [defaultIncludeIPByValue: BOOL] = {
defaultIncludeIPByValue ¬ masterData.defaultIncludeIPByValue;
};
SetHoldThatTiger: PUBLIC PROC [holdThatTiger: BOOL] = {
masterData.holdThatTiger ¬ holdThatTiger;
};
GetHoldThatTiger: PUBLIC PROC [] RETURNS [holdThatTiger: BOOL] = {
holdThatTiger ¬ masterData.holdThatTiger;
};
SetNewBoxesUnfilled: PUBLIC PROC [newBoxesUnfilled: BOOL] = {
masterData.newBoxesUnfilled ¬ newBoxesUnfilled;
};
GetNewBoxesUnfilled: PUBLIC PROC [] RETURNS [newBoxesUnfilled: BOOL] = {
newBoxesUnfilled ¬ masterData.newBoxesUnfilled;
};
masterData: MasterData;
eventTable: RefTab.Ref;
masterData ¬ NEW[MasterDataObj];
UserProfile.CallWhenProfileChanges[LookAtProfile];
eventTable ¬ RefTab.Create[255];
interval ← CodeTimer.CreateInterval[$UserInput];
CodeTimer.AddInt[interval, $Gargoyle];
END.