GGActiveImpl.mesa
Copyright Ó 1988, 1989 by Xerox Corporation. All rights reserved.
Bier, February 19, 1992 10:52 am PST
Doug Wyatt, December 20, 1989 4:39:38 pm PST
Contents: Routines that support embedded buttons within Gargoyle.
Kenneth A. Pier, September 4, 1991 3:42 pm PDT
DIRECTORY
BiScrollers, CodeTimer, Commander, EBEditors, EBTypes, EmbeddedButtons, Feedback, FeedbackTypes, GGActive, GGAlign, GGBasicTypes, GGControlPanelTypes, GGCoreTypes, GGEmbedTypes, GGEvent, GGEventExtras, GGHistoryTypes, GGInterfaceTypes, GGModelTypes, GGMouseEvent, GGMultiGravity, GGProps, GGScene, GGSliceOps, GGState, GGUIUtility, GGUserInput, GGWindow, Imager, ImagerTransformation, InputFocus, IO, KeyGlyphs, List, MultiCursors, Rope, TIPPrivate, TIPTypes, TIPUser, TIPUserExtras, UserInput, ViewerClasses, ViewerOps;
GGActiveImpl:
CEDAR
PROGRAM
IMPORTS InputFocus, BiScrollers, CodeTimer, Commander, EBEditors, EmbeddedButtons, Feedback, GGActive, GGAlign, GGEventExtras, GGMouseEvent, GGMultiGravity, GGProps, GGScene, GGSliceOps, GGState, GGUIUtility, GGUserInput, GGWindow, ImagerTransformation, IO, List, MultiCursors, TIPPrivate, TIPUserExtras, UserInput, ViewerOps
EXPORTS EBTypes, GGActive, GGInterfaceTypes = BEGIN
ActionQueue: TYPE ~ REF ActionQueueRep;
ActionQueueRep: PUBLIC TYPE ~ UserInput.Rep;
RawAction: TYPE ~ REF RawActionRep;
RawActionRep: PUBLIC TYPE ~ UserInput.ActionBody;
RawInput: TYPE ~ GGActive.RawInput;
ActiveDoc: TYPE = EBTypes.ActiveDoc;
ActiveButton: TYPE = EBTypes.ActiveButton;
BoundBox: TYPE = GGModelTypes.BoundBox;
Color: TYPE = Imager.Color;
ControlsObj: PUBLIC TYPE = GGControlPanelTypes.ControlsObj; -- exported to GGInterfaceTypes
EmbedDataObj: PUBLIC TYPE = GGEmbedTypes.EmbedDataObj; -- exported to GGInterfaceTypes
FeatureData: TYPE = GGModelTypes.FeatureData;
GGData: TYPE = GGInterfaceTypes.GGData;
HistoryEvent: TYPE = GGHistoryTypes.HistoryEvent;
MsgRouter: TYPE = FeedbackTypes.MsgRouter;
Point: TYPE = GGBasicTypes.Point;
Scene: TYPE = GGModelTypes.Scene;
SequenceOfReal: TYPE = GGCoreTypes.SequenceOfReal;
Slice: TYPE = GGModelTypes.Slice;
SliceDescriptor: TYPE = GGModelTypes.SliceDescriptor;
StrokeEnd: TYPE = Imager.StrokeEnd;
Transformation: TYPE = ImagerTransformation.Transformation;
UserInputProc: TYPE = GGEvent.UserInputProc;
Viewer: TYPE = ViewerClasses.Viewer;
Color Palette Mini-Application
InitPaletteApplication:
PROC = {
EmbeddedButtons.RegisterApplication[$Palette, PaletteNotify];
};
PaletteNotify: EmbeddedButtons.RegisteredNotifyProc = {
PROC[events: LIST OF REF ANY, buttonInfo: ButtonInfo];
ggData: GGData ← NARROW[buttonInfo.doc.theDoc];
sliceD: SliceDescriptor ← NARROW[buttonInfo.button];
FOR list:
LIST
OF
REF
ANY ← events, list.rest
UNTIL list =
NIL
DO
SELECT list.first
FROM
$TransferFillColor => [] ← GGMouseEvent.SetFillColorRemote[ggData, sliceD];
$TransferStrokeColor => [] ← GGMouseEvent.SetStrokeColorRemote[ggData, sliceD];
$TransferBothColors => {
[] ← GGMouseEvent.SetFillColorRemote[ggData, sliceD];
[] ← GGMouseEvent.SetStrokeColorRemote[ggData, sliceD];
};
ENDCASE;
ENDLOOP;
};
ActiveDoc Class
ggActiveDocClass: EBEditors.ActiveDocClass ←
NEW[EBEditors.ActiveDocClassObj ← [
name: $Gargoyle,
getRef: GGGetRef,
setRef: GGSetRef,
mapRef: GGMapRef,
getDocName: GGGetDocName,
feedback: GGButtonFeedback, -- Feedback is the name of an interface
inButton: GGInButton
]];
LookupDoc:
PUBLIC PROC [ggData: GGData]
RETURNS [doc: ActiveDoc] ~ {
Gargoyle does us a favor here. When a new file is loaded into a viewer, ggData.behavior.activeDoc ← NIL. Thus, we can tell if we need to create a new activeDoc.
IF ggData.behavior.activeDoc =
NIL
THEN {
doc ← EBEditors.CreateActiveDoc[ggData, ggActiveDocClass];
ggData.behavior.activeDoc ← doc;
}
ELSE doc ← NARROW[ggData.behavior.activeDoc]
};
GGInButton: EBEditors.InButtonProc = {
PROC [button: ActiveButton, doc: ActiveDoc, x, y: INTEGER] RETURNS [BOOL];
pt: Imager.VEC;
feature: FeatureData;
ggData: GGData ← NARROW[doc.theDoc];
sliceD: SliceDescriptor ← NARROW[button];
sceneBag: GGAlign.TriggerBag ← GGAlign.CreateTriggerBag[];
completeD: SliceDescriptor ← GGSliceOps.NewParts[sliceD.slice, NIL, slice];
tsc: TIPTypes.TIPScreenCoords ← NEW[TIPTypes.TIPScreenCoordsRec ← [x, y, FALSE] ];
[] ← ViewerOps.MouseInViewer[tsc]; -- modifies tsc to viewer coords
pt ← GGWindow.ViewerToWorld[[tsc.mouseX, tsc.mouseY], ggData];
[] ← GGAlign.AddSliceFeature[completeD, sceneBag];
feature ← GGMultiGravity.FacesPreferred[ pt, 18.0, GGAlign.emptyAlignBag, sceneBag, ggData].feature;
IF feature = NIL THEN RETURN[FALSE]
ELSE {
scale: REAL ← GGState.GetScaleUnit[ggData];
Feedback.PutF[ggData.router, oneLiner, $DuringMouse, "Cursor on button at [%g, %g]",
[real[pt.x/scale]], [real[pt.y/scale]] ];
RETURN[TRUE];
};
};
GGGetDocName: EBEditors.GetDocNameProc ~ {
PROC [doc: ActiveDoc] RETURNS [name: ROPE];
ggData: GGData ← NARROW[doc.theDoc];
name ← GGState.GetFullName[ggData];
};
GGGetRef: EBEditors.GetRefProc = {
PROC [key: ATOM, button: ActiveButton, doc: ActiveDoc] RETURNS [ref: REF];
sliceD: SliceDescriptor ← NARROW[button];
isUnique: BOOL ← TRUE;
[ref, isUnique] ← GGProps.Get[sliceD.slice, sliceD.parts, key];
IF ref =
NIL
THEN {
-- try inheritance
ggData: GGData ← NARROW[doc.theDoc];
rootSlice: Slice ← ggData.rootSlice;
[ref, isUnique] ← GGProps.Get[rootSlice, NIL, key];
};
};
GGSetRef: EBEditors.SetRefProc = {
PROC [key: ATOM, button: ActiveButton, doc: ActiveDoc, ref: REF];
sliceD: SliceDescriptor ← NARROW[button];
GGProps.Put[sliceD.slice, sliceD.parts, key, ref];
};
GGMapRef: EBEditors.MapRefProc = {
PROC [doc: ActiveDoc, docClass: ActiveDocClass, mapProc: EachButtonProc];
ForEachButton:
PROC [leaf: Slice]
RETURNS [done:
BOOL ←
FALSE] = {
leafD: SliceDescriptor ← GGSliceOps.NewParts[leaf, NIL, slice];
buttonData: REF ← GGProps.Get[leaf, leafD.parts, $ButtonData].val;
IF buttonData = NIL THEN RETURN;
done ← mapProc[leafD, doc];
};
ggData: GGData ← NARROW[doc.theDoc];
aborted ← GGScene.WalkSlices[ggData.scene, leaf, ForEachButton];
};
GGButtonFeedback: EBEditors.FeedbackProc = {
PROC[button: ActiveButton, doc: ActiveDoc, feedback: REF] RETURNS [REF];
ggData: GGData ← NARROW[doc.theDoc];
feedbackRef: REF ANY;
WITH feedback
SELECT
FROM
rope: Rope.ROPE => feedbackRef ← GGUIUtility.ParseFeedbackRope[rope];
ENDCASE => feedbackRef ← feedback;
WITH feedbackRef
SELECT
FROM
a: ATOM => QueueButtonEvent[LIST[a], ggData, button];
l: LIST OF REF ANY => HandleList[l, ggData, button];
ENDCASE => ERROR;
RETURN[feedbackRef];
};
Property support
NoOpFileIn: GGProps.
FileinProc = {
PROC [s: STREAM] RETURNS [val: REF];
val ← IO.GetRope[s];
};
ButtonDataFileOut: GGProps.
FileoutProc = {
PROC [s: STREAM, val: REF] RETURNS [vf: ValFormat ← delimited];
rope: Rope.ROPE;
rope ← EBEditors.RopeFromButtonData[val];
s.PutRope[rope];
};
ButtonDataFileIn: GGProps.
FileinProc = {
PROC [s: STREAM] RETURNS [val: REF];
rope: Rope.ROPE ← IO.GetRope[s];
val ← EBEditors.ButtonDataFromRope[rope, FALSE];
};
ButtonDataCopy: GGProps.CopyProc = {
PROC [val: REF] RETURNS [copy: REF];
valAsRope: Rope.ROPE ← EBEditors.RopeFromButtonData[val];
copy ← EBEditors.ButtonDataFromRope[valAsRope, FALSE];
};
Gargoyle as a Button Application
An obsolete comment: However, if the combination $EBApplications $GetKeyValue $<some value name> occurs, the named value is retrieved from the active document. This allows a button to base its operation on any of the values in the document.
GargoyleHandler: EmbeddedButtons.RegisteredNotifyProc = {
PROC [events: LIST OF REF, buttonInfo: ButtonInfo];
events will be a list of Gargoyle actions, such as (LineWidth "2.3").
viewer: ViewerClasses.Viewer;
inputFocus: InputFocus.Focus;
inputFocus ← InputFocus.GetInputFocus[];
IF inputFocus #
NIL
THEN {
viewer ← inputFocus.owner;
IF viewer #
NIL
AND viewer.class.flavor = $ActionArea
THEN {
ggData: GGData ← NARROW[BiScrollers.ClientDataOfViewer[viewer]];
HandleList[events, ggData, buttonInfo.button];
};
};
};
ControlPanelButtonHandler:
PUBLIC
PROC [ggData: GGData, events:
LIST
OF
REF, buttonInfo: EBTypes.ButtonInfo] = {
Send the event to the named ggData, but extract any needed info from the named buttonInfo to prepare the event.
HandleList[events, ggData, buttonInfo.button];
};
TransferButtonDashes: UserInputProc = {
PROC [ggData: GGData, event: LIST OF REF ANY];
sliceD: SliceDescriptor ← NARROW[event.rest.first];
dashed: BOOL ← FALSE;
pattern: SequenceOfReal ← NIL;
offset, length: REAL ← 0.0;
isUnique: BOOL ← TRUE;
targetGGData: GGData ← GGState.GetGGInputFocus[];
IF targetGGData =
NIL
THEN {
Feedback.Append[ggData.router, oneLiner, $Complaint, "SetStrokeColorRemote failed: Place input focus in a Gargoyle viewer"];
RETURN;
};
[dashed, pattern, offset, length, isUnique] ← GGSliceOps.GetDashed[sliceD.slice, sliceD.parts];
IF isUnique
THEN {
GGEventExtras.SetDashed[ggData, dashed, pattern, offset, length];
}
ELSE Feedback.Append[targetGGData.router, oneLiner, $Complaint, "TransferButtonDashes failed: the button has multiple dash patterns"];
};
TransferButtonStrokeWidth: UserInputProc = {
PROC [ggData: GGData, event: LIST OF REF ANY];
sliceD: SliceDescriptor ← NARROW[event.rest.first];
strokeWidth: REAL;
isUnique: BOOL ← TRUE;
targetGGData: GGData ← GGState.GetGGInputFocus[];
IF targetGGData =
NIL
THEN {
Feedback.Append[ggData.router, oneLiner, $Complaint, "SetStrokeColorRemote failed: Place input focus in a Gargoyle viewer"];
RETURN;
};
[strokeWidth, isUnique] ← GGSliceOps.GetStrokeWidth[sliceD.slice, sliceD.parts];
IF isUnique
THEN {
GGEventExtras.SetStrokeWidth[ggData, strokeWidth];
}
ELSE Feedback.Append[targetGGData.router, oneLiner, $Complaint, "TransferButtonStrokeWidth failed: the button has multiple dash patterns"];
};
TransferButtonStrokeEnd: UserInputProc = {
PROC [ggData: GGData, event: LIST OF REF ANY];
sliceD: SliceDescriptor ← NARROW[event.rest.first];
strokeEnd: StrokeEnd;
isUnique: BOOL ← TRUE;
targetGGData: GGData ← GGState.GetGGInputFocus[];
IF targetGGData =
NIL
THEN {
Feedback.Append[ggData.router, oneLiner, $Complaint, "SetStrokeColorRemote failed: Place input focus in a Gargoyle viewer"];
RETURN;
};
[strokeEnd, isUnique] ← GGSliceOps.GetStrokeEnd[sliceD.slice, sliceD.parts];
IF isUnique
THEN {
GGEventExtras.SetStrokeEnd[ggData, strokeEnd];
}
ELSE Feedback.Append[targetGGData.router, oneLiner, $Complaint, "TransferButtonStrokeEnd failed: the button has multiple dash patterns"];
};
Notify procedures
ActiveInputHandler:
PROC [self: Viewer, ggData: GGData, input:
LIST
OF
REF
ANY] = {
IF ggData.embed.beingBorn THEN RETURN; -- don't handle input while window is being built
IF GGActive.IsRawInput[input]
THEN {
-- raw input
CodeTimer.StartInt[$RawInputNotify, $Gargoyle]; {
rawInput: GGActive.RawInput ~ GGActive.NarrowRawInput[input];
mouseAllUp: BOOL ~ MouseAllUp[rawInput];
button: REF ← NIL; -- will be set to the active button, if any
doc: ActiveDoc;
mouseAction: BOOL ← MouseAction[rawInput];
bs: BiScrollers.BiScroller ~ BiScrollers.QuaBiScroller[self];
IF GGState.GetActive[ggData]
-- activity enabled
AND ggData.behavior.rawInputMode=$None
-- not in $Gargoyle mode
AND mouseAction
-- it's some kind of mouse action
THEN {
-- test for an active button at the cursor position
GetMousePosition:
PROC [mc: TIPUser.TIPScreenCoordsRec]
RETURNS [mouse
GG: Point] ~ {
viewerToClient: Transformation ~ bs.class.style.GetTransforms[bs].viewerToClient;
mouseBiScroller: TIPUser.TIPScreenCoords ~ NEW[TIPUser.TIPScreenCoordsRec ← mc];
v: Viewer; inClient: BOOL;
[v, inClient] ← ViewerOps.MouseInViewer[mouseBiScroller];
IF NOT inClient THEN Feedback.PutF[ggData.router, oneLiner, $Debug, "GG: %g,%g; ", [integer[mouseBiScroller.mouseX]], [integer[mouseBiScroller.mouseY]] ];
mouseGG ← ImagerTransformation.Transform[m: viewerToClient, v: [mouseBiScroller.mouseX, mouseBiScroller.mouseY]];
};
mouseGG: Point ~ GetMousePosition[MouseCoords[rawInput]];
feature: FeatureData ~ GGMultiGravity.FacesPreferred[mouseGG, 18.0, GGAlign.emptyAlignBag, ggData.hitTest.sceneBag, ggData].feature;
IF feature#
NIL
THEN {
doc ← LookupDoc[ggData];
WITH feature.shape
SELECT
FROM
sliceD: SliceDescriptor => {
buttonData: REF ~ GGGetRef[$ButtonData, sliceD, doc];
IF buttonData#NIL THEN button ← sliceD; -- found an active button
};
ENDCASE;
};
};
IF button#
NIL
THEN {
EBEditors.HandleEvent[rawInput.rawAction, rawInput.actionQueue, button, doc];
}
ELSE {
IF GGState.GetReadOnly[ggData]
THEN {
Ignore this gargoyle action and display the "read-only" cursor, currently textPointer
IF MultiCursors.GetACursor[
NIL]#textPointer
THEN MultiCursors.SetACursor[textPointer, NIL];
}
ELSE {
event: LIST OF REF;
IF mouseAction
THEN {
cursorType: MultiCursors.CursorType;
cursorType ← ggData.controls.cursor;
IF cursorType#MultiCursors.GetACursor[
NIL]
THEN MultiCursors.
SetACursor[cursorType,
NIL];
The only other SetACursor call in Gargoyle is in GGWindowImpl.SetCursorLooks.
};
event ← ParseEvent[ggData.parseInfo, rawInput];
At this point, event contains viewer (not client) coordinates
IF NOT mouseAllUp THEN ggData.behavior.rawInputMode ← $Gargoyle;
IF event #
NIL
THEN {
actionArea: Viewer ← BiScrollers.QuaViewer[bs: bs, inner: TRUE];
self.class.notify[self, event]; -- self is the BiScroller
actionArea.class.notify[actionArea, event];
};
};
};
IF mouseAllUp THEN ggData.behavior.rawInputMode ← $None;
}; CodeTimer.StopInt[$RawInputNotify, $Gargoyle]; }
ELSE ERROR; -- should never be called if the input isn't from the Transparent TIP table
};
QueueButtonEvent:
PROC [event:
LIST
OF
REF
ANY, ggData: GGData, button:
REF
ANY] = {
newEvent: LIST OF REF ANY;
IF event.first = $SelectButton
OR event.first = $BeginButton
OR event.first = $TransferButtonDashes
OR event.first = $TransferButtonStrokeWidth
OR event.first = $TransferButtonStrokeEnd
THEN {
sliceD: SliceDescriptor ← NARROW[button];
newEvent ← LIST[event.first, sliceD]; -- make a copy of the eventList, because EmbeddedButtons resuses the original
}
ELSE IF event.first = $ButtonFillColorFromIntensity
OR event.first = $ButtonStrokeColorFromIntensity
THEN {
sliceD: SliceDescriptor ← NARROW[button];
newEvent ← LIST[event.first, event.rest.first, sliceD]; -- make a copy of the eventList, because EmbeddedButtons resuses the original
}
ELSE newEvent ← List.Append[event]; -- make a copy of the eventList, because EmbeddedButtons resuses the original
GGUserInput.EventNotify[ggData, newEvent];
};
HandleList:
PROC [list:
LIST
OF
REF
ANY, ggData: GGData, button:
REF
ANY] = {
WITH list.first
SELECT
FROM
l:
LIST
OF
REF
ANY => {
FOR list ← list, list.rest
UNTIL list =
NIL
DO
WITH list.first
SELECT
FROM
childList: LIST OF REF ANY => HandleList[childList, ggData, button];
ENDCASE => SIGNAL SyntaxError[msg: "Input list mixes LIST elements and ATOM elements"];
ENDLOOP;
};
ENDCASE => QueueButtonEvent[list, ggData, button];
};
SyntaxError:
PUBLIC
SIGNAL[msg: Rope.
ROPE] =
CODE;
GGActiveHandler: Commander.CommandProc = {};
User Input Handling (formerly in GGPortImpl)
IsRawInput:
PUBLIC
PROC [input:
LIST
OF
REF
ANY]
RETURNS [
BOOL] ~ {
RETURN[ISTYPE[input.first, ActionQueue]];
};
NarrowRawInput:
PUBLIC
PROC [input:
LIST
OF
REF
ANY]
RETURNS [RawInput] ~ {
actionQueue: ActionQueue ~ NARROW[input.first];
rawAction: RawAction ~ NARROW[input.rest.first];
actionQueueEB: EBTypes.ActionQueue ~ actionQueue;
rawActionEB: EBTypes.RawAction ~ rawAction;
RETURN[[actionQueue: actionQueueEB, rawAction: rawActionEB]];
};
mouseButton1: UserInput.KeySym ~ KeyGlyphs.LeftMouse;
mouseButton2: UserInput.KeySym ~ KeyGlyphs.MiddleMouse;
mouseButton3: UserInput.KeySym ~ KeyGlyphs.RightMouse;
MouseAction:
PUBLIC
PROC [input: RawInput]
RETURNS [
BOOL] ~ {
actionRef: REF UserInput.ActionBody ~ input.rawAction;
WITH a: actionRef^
SELECT
FROM
keyDown, keyStillDown, keyUp => {
keySym: UserInput.KeySym ~ UserInput.GetKeySym[input.actionQueue, a.keyCode, 0];
SELECT keySym
FROM
mouseButton1, mouseButton2, mouseButton3 => RETURN[TRUE];
ENDCASE;
};
mousePosition, fakeMouseMotion => RETURN[TRUE]; -- does fakeMouseMotion count ???
ENDCASE;
RETURN[FALSE];
};
MouseAllUp:
PUBLIC
PROC [input: RawInput]
RETURNS [
BOOL] ~ {
RETURN[
UserInput.GetKeySymState[input.actionQueue, mouseButton1]=up AND
UserInput.GetKeySymState[input.actionQueue, mouseButton2]=up AND
UserInput.GetKeySymState[input.actionQueue, mouseButton3]=up
];
};
MouseCoords:
PUBLIC
PROC [input: RawInput]
RETURNS [TIPUser.TIPScreenCoordsRec] ~ {
handle: UserInput.Handle ~ input.actionQueue;
RETURN[handle.mousePosition];
};
MapCoordinateResults:
PROC [results:
LIST
OF
REF
ANY] = {
FOR lst:
LIST
OF
REF ← results, lst.rest
UNTIL lst =
NIL
DO
SELECT lst.first
FROM
TIPPrivate.stdCoords =>
[] ← ViewerOps.MouseInViewer[TIPPrivate.stdCoords];
ENDCASE;
ENDLOOP;
};
ParseEvent:
PUBLIC
PROC [parseInfo: TIPPrivate.TIPParseInfo, input: RawInput]
RETURNS [event:
LIST
OF
REF] ~ {
actionRef: REF UserInput.ActionBody ~ input.rawAction;
parseInfo.inCreek ← input.actionQueue;
event ← TIPPrivate.MatchEvent[parseInfo, actionRef^];
MapCoordinateResults[event];
};
Initialization
ggActionAreaClass: ViewerClasses.ViewerClass ~ ViewerOps.FetchViewerClass[$ActionArea];
ggActionAreaClass.tipTable ← TIPUserExtras.TransparentTIPTable[]; -- all viewers made after this call will be able to be active
GGUserInput.RegisterRawInputHandler[ActiveInputHandler];
GGProps.Register[$ButtonData, ButtonDataFileIn, ButtonDataFileOut, ButtonDataCopy];
InitPaletteApplication[];
GGUserInput.RegisterAction[$TransferButtonDashes, TransferButtonDashes, none];
GGUserInput.RegisterAction[$TransferButtonStrokeWidth, TransferButtonStrokeWidth, none];
GGUserInput.RegisterAction[$TransferButtonStrokeEnd, TransferButtonStrokeEnd, none];
Commander.Register[key: "GGActive", proc: GGActiveHandler, doc: "Does nothing. Active Gargoyle is already loaded", clientData: NIL, interpreted: TRUE];
EmbeddedButtons.RegisterApplication[$Gargoyle, GargoyleHandler];
END.