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: BOOLTRUE;
[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: BOOLFALSE] = {
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.ROPEIO.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: BOOLFALSE;
pattern: SequenceOfReal ← NIL;
offset, length: REAL ← 0.0;
isUnique: BOOLTRUE;
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: BOOLTRUE;
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: BOOLTRUE;
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: REFNIL; -- 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 [mouseGG: 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.