GGMouseEventImplA.mesa
Contents: Once a mouse event reaches the front of the slack-process queue, it is dispatched to one of the procedures in this module.
Copyright Ó 1987, 1988, 1992 by Xerox Corporation. All rights reserved.
Pier, July 15, 1993 11:17 am PDT
Kurlander August 7, 1986 10:54:44 am PDT
Bier, March 9, 1992 10:40 am PST
Doug Wyatt, April 16, 1992 4:38 pm PDT
DIRECTORY
Atom, CodeTimer, Feedback, FeedbackTypes, GGAlign, GGBasicTypes, GGBoundBox, GGCaret, GGDragTypes, GGEmbedTypes, GGEvent, GGHistory, GGHistoryTypes, GGInterfaceTypes, GGModelTypes, GGMouseEvent, GGMultiGravity, GGOutline, GGParent, GGRefresh, GGRefreshTypes, GGScene, GGSegment, GGSegmentTypes, GGSelect, GGSequence, GGSlice, GGSliceOps, GGState, GGTraj, GGTransform, GGUIUtility, GGUserInput, GGUserProfile, GGWindow, Imager, ImagerTransformation, Menus, RealFns, RefTab, Rope, Vectors2d;
GGMouseEventImplA:
CEDAR
PROGRAM
IMPORTS Atom, CodeTimer, Feedback, GGAlign, GGBoundBox, GGCaret, GGEvent, GGHistory, GGMouseEvent, GGMultiGravity, GGOutline, GGParent, GGRefresh, GGScene, GGSegment, GGSelect, GGSequence, GGSlice, GGSliceOps, GGState, GGTraj, GGTransform, GGUIUtility, GGUserInput, GGUserProfile, GGWindow, Imager, ImagerTransformation, RealFns, RefTab, Rope, Vectors2d
EXPORTS GGMouseEvent, GGInterfaceTypes = BEGIN
EmbedDataObj: PUBLIC TYPE = GGEmbedTypes.EmbedDataObj;
DragDataObj: PUBLIC TYPE = GGDragTypes.DragDataObj;
AlignBag: TYPE = GGInterfaceTypes.AlignBag;
AlignmentPoint: TYPE = GGInterfaceTypes.AlignmentPoint;
BoundBox: TYPE = GGModelTypes.BoundBox;
Caret: TYPE = GGInterfaceTypes.Caret;
DefaultData: TYPE = GGModelTypes.DefaultData;
FeatureData: TYPE = GGModelTypes.FeatureData;
MsgRouter: TYPE = FeedbackTypes.MsgRouter;
GGData: TYPE = GGInterfaceTypes.GGData;
HistoryEvent: TYPE = GGHistoryTypes.HistoryEvent;
Joint: TYPE = GGModelTypes.Joint;
MouseButton: TYPE = Menus.MouseButton;
Point: TYPE = GGBasicTypes.Point;
RefreshDataObj: PUBLIC TYPE = GGRefreshTypes.RefreshDataObj;
Scene: TYPE = GGModelTypes.Scene;
Segment: TYPE = GGSegmentTypes.Segment;
Sequence: TYPE = GGModelTypes.Sequence;
Slice: TYPE = GGModelTypes.Slice;
SliceDescriptor: TYPE = GGModelTypes.SliceDescriptor;
SliceDescriptorGenerator: TYPE = GGModelTypes.SliceDescriptorGenerator;
SliceDescriptorObj: TYPE = GGModelTypes.SliceDescriptorObj;
SliceGenerator: TYPE = GGModelTypes.SliceGenerator;
SliceParts: TYPE = GGModelTypes.SliceParts;
Traj: TYPE = GGModelTypes.Traj;
TrajEnd: TYPE = GGModelTypes.TrajEnd;
TrajParts: TYPE = GGModelTypes.TrajParts;
TrajPartType: TYPE = GGModelTypes.TrajPartType;
TriggerBag: TYPE = GGAlign.TriggerBag;
UserInputProc: TYPE = GGUserInput.UserInputProc;
Vector: TYPE = GGBasicTypes.Vector;
MouseProc:
TYPE = GGMouseEvent.MouseProc;
PROC [input: LIST OF REF ANY, ggData: GGData, worldPt: Point];
StartProc:
TYPE = GGMouseEvent.StartProc;
PROC [input: LIST OF REF ANY, ggData: GGData, worldPt: Point] RETURNS [success: BOOL ← TRUE];
Problem: PUBLIC SIGNAL [msg: Rope.ROPE] = Feedback.Problem;
EasyAbort:
PROC [input:
LIST
OF
REF
ANY, ggData: GGData, worldPt: Point] = {
Many actions don't change the scene at all until the End proc is called. For these cases, we just restore the selections and caret position.
GGScene.RestoreSelections[ggData.scene];
GGCaret.Copy[from: ggData.drag.savedCaret, to: ggData.caret]; --restore original caret
FinishAbort[ggData];
};
AbortAdd:
PROC [input:
LIST
OF
REF
ANY, ggData: GGData, worldPt: Point] = {
GGEvent.DeleteCaretSegment[ggData, NIL];
GGScene.RestoreSelections[ggData.scene]; -- can't do this. Obsolete saved selection
{
sliceD: SliceDescriptor ¬ NARROW[GGCaret.GetChair[ggData.caret]];
IF sliceD#NIL THEN GGSelect.SelectSlice[sliceD: sliceD, scene: ggData.scene, selectClass: normal];
FinishAbort[ggData];
};
};
AbortCopyAndDrag:
PROC [input:
LIST
OF
REF
ANY, ggData: GGData, worldPt: Point] = {
[] ¬ GGScene.DeleteAllSelected[ggData.scene];
GGScene.RestoreSelections[ggData.scene];
GGCaret.Copy[from: ggData.drag.savedCaret, to: ggData.caret];
FinishAbort[ggData];
};
AbortBox:
PROC [input:
LIST
OF
REF
ANY, ggData: GGData, worldPt: Point] = {
FixupAbortedBox[ggData];
FinishAbort[ggData];
};
FinishAbort:
PUBLIC
PROC [ggData: GGData] = {
GGRefresh.MoveOverlayToBackground[ggData];
Feedback.Append[ggData.router, end, $Feedback, ". . . Aborted."];
GGWindow.RestoreScreenAndInvariants[paintAction: $PaintEntireScene, ggData: ggData, remake: triggerBag, edited: FALSE, okToSkipCapture: TRUE];
};
InitializeFSM:
PUBLIC
PROC [ggData: GGData] = {
ggData.state ¬ $None;
ggData.mouseMode ¬ $None;
ggData.drag.guardedMode ¬ default;
};
ResetMouseMachinery:
PUBLIC
PROC [ggData: GGData] = {
ggData.mouseMode ¬ $None;
ggData.state ¬ $None;
ggData.drag.guardedMode ¬ default;
GGRefresh.MoveOverlayToBackground[ggData];
};
SaveSavedState:
PUBLIC
PROC [ggData: GGData] = {
GGScene.SaveSelections[ggData.scene];
GGWindow.SaveCaretPos[ggData];
GGCaret.Copy[from: ggData.caret, to: ggData.drag.savedCaret];
};
The FSM
HandleMouseless:
PUBLIC UserInputProc = {
An easy way to handle the Abort action.
event ¬ LIST[event.first, NEW[Point ¬ [0.0, 0.0]]];
HandleMouse[ggData, event];
};
HandleMouse:
PUBLIC UserInputProc = {
HandleMouseAux[ggData, event];
};
HandleMouseAux:
PROC [ggData: GGData, event:
LIST
OF
REF, guardP: GuardPredicate ¬ default] = {
event will be in the format: LIST[ATOM, REF Point] or LIST[$UnGuarded, ATOM, REF Point]
mE: ModeEntry;
IF ggData.mouseMode # $None
THEN {
point: Point;
IF event.first = $UnGuarded
THEN {
ggData.drag.nextGuardedMode ¬ unguarded;
event ¬ event.rest;
};
point ¬ NARROW[event.rest.first, REF Point];
mE ¬ FindMode[ggData.mouseMode];
IF mE = NIL THEN SIGNAL Problem[msg: "Unimplemented Mode"]
ELSE {
IF ggData.drag.guardedMode = guarded
THEN
HandleGuarded[mE.startProc, mE.duringProc, mE.endProc, mE.abortProc, mE.continueProc, event, ggData, point]
ELSE HandleUnGuarded[mE.startProc, mE.duringProc, mE.endProc, mE.abortProc, event, ggData, point];
}
}
ELSE {
SetMouseMode:
PROC [mouseMode:
ATOM] = {
ggData.mouseMode ¬ mouseMode;
IF guardP = default
THEN {
mE ¬ FindMode[mouseMode];
IF mE = NIL THEN SIGNAL Problem[msg: "Starting unimplemented mode"]
ELSE ggData.drag.guardedMode ¬ mE.guardP;
}
ELSE ggData.drag.guardedMode ¬ guardP;
};
atom: ATOM ¬ NARROW[event.first];
SELECT atom
FROM
$UnGuarded => {
ggData.drag.guardedMode ¬ unguarded;
HandleMouseAux[ggData, event.rest, unguarded]; -- strip UnGuarded off the front. execute the actual command, setting guardP to "unguarded"
};
$StartCaretPos => {
SetMouseMode[$CaretPos]; HandleMouse[ggData, event];
};
$StartAdd => {
SetMouseMode[$Add]; HandleMouse[ggData, event];
};
$StartBezier => {
SetMouseMode[$AddBezier]; HandleMouse[ggData, event];
};
$StartBox => {
SetMouseMode[$Box]; HandleMouse[ggData, event];
};
$StartSelectWithBox => {
SetMouseMode[$SelectWithBox]; HandleMouse[ggData, event];
};
$StartDrag => {
SetMouseMode[$Drag]; HandleMouse[ggData, event];
};
$StartCopyAndDrag => {
SetMouseMode[$CopyAndDrag]; HandleMouse[ggData, event];
};
$StartAddAndDrag => {
SetMouseMode[$AddAndDrag]; HandleMouse[ggData, event];
};
$StartRotate => {
SetMouseMode[$Rotate]; HandleMouse[ggData, event];
};
$StartScale => {
SetMouseMode[$Scale]; HandleMouse[ggData, event];
};
$StartSixPoint => {
SetMouseMode[$SixPoint]; HandleMouse[ggData, event];
};
$StartSelectJoint => {
SetMouseMode[$SelectJoint]; HandleMouse[ggData, event];
};
$StartExtSelectJoint => {
SetMouseMode[$ExtSelectJoint]; HandleMouse[ggData, event];
};
$StartSelectSegment => {
SetMouseMode[$SelectSegment]; HandleMouse[ggData, event];
};
$StartExtSelectSegment => {
SetMouseMode[$ExtSelectSegment]; HandleMouse[ggData, event];
};
$StartSelectTrajectory => {
SetMouseMode[$SelectTrajectory]; HandleMouse[ggData, event];
};
$StartExtSelectTrajectory => {
SetMouseMode[$ExtSelectTrajectory]; HandleMouse[ggData, event];
};
$StartSelectTopLevel => {
SetMouseMode[$SelectTopLevel]; HandleMouse[ggData, event];
};
$StartExtSelectTopLevel => {
SetMouseMode[$ExtSelectTopLevel]; HandleMouse[ggData, event];
};
$StartExtendSelection => {
SetMouseMode[$ExtendSelection]; HandleMouse[ggData, event];
};
$StartDeselectJoint => {
SetMouseMode[$DeselectJoint]; HandleMouse[ggData, event];
};
$StartDeselectSegment => {
SetMouseMode[$DeselectSegment]; HandleMouse[ggData, event];
};
$StartDeselectTrajectory => {
SetMouseMode[$DeselectTrajectory]; HandleMouse[ggData, event];
};
$StartDeselectTopLevel => {
SetMouseMode[$DeselectTopLevel]; HandleMouse[ggData, event];
};
ENDCASE => RETURN; -- ignore other actions
};
};
Restart:
PROC [input:
LIST
OF
REF
ANY, ggData: GGData]
RETURNS [
BOOL] = {
mouseMode: ATOM ¬ ggData.mouseMode;
state: ATOM ¬ NARROW[input.first];
RETURN[
(mouseMode = $CaretPos AND state = $StartCaretPos) OR
(mouseMode = $Add AND state = $StartAdd) OR
(mouseMode = $AddBezier AND state = $StartBezier) OR
(mouseMode = $Box AND state = $StartBox) OR
(mouseMode = $Circle AND state = $StartCircle) OR
(mouseMode = $Drag AND state = $StartDrag) OR
(mouseMode = $CopyAndDrag AND state = $StartCopyAndDrag) OR
(mouseMode = $AddAndDrag AND state = $StartAddAndDrag) OR
(mouseMode = $Rotate AND state = $StartRotate) OR
(mouseMode = $Scale AND state = $StartScale) OR
(mouseMode = $SelectJoint AND state = $StartSelectJoint) OR
(mouseMode = $SelectSegment AND state = $StartSelectSegment) OR
(mouseMode = $SelectTrajectory AND state = $StartSelectTrajectory) OR
(mouseMode = $SelectTopLevel AND state = $StartSelectTopLevel) OR
(mouseMode = $ExtendSelection AND state = $StartExtendSelection) OR
(mouseMode = $DeselectJoint AND state = $StartDeselectJoint) OR
(mouseMode = $DeselectSegment AND state = $StartDeselectSegment) OR
(mouseMode = $DeselectTrajectory AND state = $StartDeselectTrajectory) OR
(mouseMode = $DeselectTopLevel AND state = $StartDeselectTopLevel)];
HandleGuarded:
PROC [startProc: StartProc, duringProc, endProc, abortProc: MouseProc, continueProc: StartProc, input:
LIST
OF
REF
ANY, ggData: GGData, worldPt: Point] = {
genericAction, atom: ATOM;
atomName: Rope.ROPE;
CodeTimer.StartInt[$HandleGuarded, $Gargoyle];
WITH input.first
SELECT
FROM
refChar: REF CHAR => RETURN; -- ignore characters during mouse actions
ENDCASE;
atom ¬ NARROW[input.first];
atomName ¬ Atom.GetPName[atom];
genericAction ¬ IF Rope.Equal[Rope.Substr[atomName, 0, 5], "Start", TRUE] THEN $Start ELSE atom;
HandleGuardedAux[startProc, duringProc, endProc, abortProc, continueProc, genericAction, input, ggData, worldPt];
CodeTimer.StopInt[$HandleGuarded, $Gargoyle];
};
HandleGuardedAux:
PROC [startProc: StartProc, duringProc, endProc, abortProc: MouseProc, continueProc: StartProc, genericAction:
ATOM, input:
LIST
OF
REF
ANY, ggData: GGData, worldPt: Point] = {
SELECT ggData.state
FROM
$None => {
SELECT genericAction
FROM
$Start => {
ggData.drag.currentPoint ¬ worldPt;
[] ← InputFocus.SetInputFocus[ggData.actionArea];
IF startProc[input, ggData, worldPt] THEN ggData.state ¬ $Main
ELSE {abortProc[input, ggData, worldPt]; ggData.state ¬ $Aborted;}; };
ENDCASE;
};
$Main => {
SELECT genericAction
FROM
$During => {
duringProc[input, ggData, worldPt];
ggData.drag.currentPoint ¬ worldPt;
};
$SawMouseFinish, $Abort => {abortProc[input, ggData, worldPt]; ggData.state ¬ $Aborted};
$GuardUp => ggData.state ¬ $GuardUp;
$MouseUp => ggData.state ¬ $MouseUp;
$Start => {
-- the mouse must have gone out of the window while the mouse button was done. Abort the current action and start a new one.
abortProc[input, ggData, worldPt]; ggData.state ¬ $None;
ggData.mouseMode ¬ $None;
HandleMouseAux[ggData, input, ggData.drag.nextGuardedMode];
};
ENDCASE;
};
$GuardUp => {
SELECT genericAction
FROM
$AllUp => {
endProc[input, ggData, ggData.drag.currentPoint];
ggData.mouseMode ¬ $None;
ggData.state ¬ $None;
};
$SawMouseFinish, $Abort => {abortProc[input, ggData, worldPt]; ggData.state ¬ $Aborted};
ENDCASE;
};
$MouseUp => {
SELECT genericAction
FROM
$SawMouseFinish, $AllUp => {
endProc[input, ggData, ggData.drag.currentPoint];
ggData.mouseMode ¬ $None;
ggData.state ¬ $None;
};
$Abort => {abortProc[input, ggData, worldPt]; ggData.state ¬ $Aborted};
$Start => {
-- we may be starting another action of this mode or some other mode.
IF Restart[input, ggData]
AND continueProc #
NIL
THEN {
IF
continueProc[input, ggData, worldPt]
THEN {
ggData.state ¬ $Main;
ggData.drag.currentPoint ¬ worldPt;
}
ELSE {abortProc[input, ggData, worldPt]; ggData.state ¬ $Aborted;};
}
ELSE {
ggData.mouseMode ¬ $None;
endProc[input, ggData, ggData.drag.currentPoint];
ggData.state ¬ $None;
HandleMouseAux[ggData, input, ggData.drag.nextGuardedMode];
};
};
ENDCASE;
};
$Aborted => {
SELECT genericAction FROM
$AllUp => {ggData.state ¬ $None; ggData.mouseMode ¬ $None};
ENDCASE;
};
ENDCASE => SIGNAL Problem[msg: "Unknown generic state"];
};
HandleUnGuarded:
PROC [startProc: StartProc, duringProc, endProc, abortProc: MouseProc, input:
LIST
OF
REF
ANY, ggData: GGData, worldPt: Point] = {
genericAction, atom: ATOM;
atomName: Rope.ROPE;
WITH input.first
SELECT
FROM
refChar: REF CHAR => RETURN; -- ignore characters during mouse actions
ENDCASE;
atom ¬ NARROW[input.first];
atomName ¬ Atom.GetPName[atom];
genericAction ¬ IF Rope.Equal[Rope.Substr[atomName, 0, 5], "Start", TRUE] THEN $Start ELSE atom;
HandleUnGuardedAux[startProc, duringProc, endProc, abortProc, genericAction, input, ggData, worldPt];
};
HandleUnGuardedAux:
PROC [startProc: StartProc, duringProc, endProc, abortProc: MouseProc, genericAction:
ATOM, input:
LIST
OF
REF
ANY, ggData: GGData, worldPt: Point] = {
SELECT ggData.state
FROM
$None => {
SELECT genericAction
FROM
$Start => {
[] ← InputFocus.SetInputFocus[ggData.actionArea];
IF startProc[input, ggData, worldPt] THEN ggData.state ¬ $Main
ELSE {abortProc[input, ggData, worldPt]; ggData.state ¬ $Aborted}
};
ENDCASE;
};
$Main => {
SELECT genericAction
FROM
$During => duringProc[input, ggData, worldPt];
$MouseUp, $AllUp => {
endProc[input, ggData, worldPt];
ggData.state ¬ $None;
ggData.mouseMode ¬ $None;
};
$Abort => {abortProc[input, ggData, worldPt]; ggData.state ¬ $Aborted};
$Start => {
-- the mouse must have gone out of the window while the mouse button was done. Abort the current action and start a new one.
abortProc[input, ggData, worldPt]; ggData.state ¬ $None;
ggData.mouseMode ¬ $None;
HandleMouseAux[ggData, input, ggData.drag.nextGuardedMode];
};
ENDCASE;
};
$Aborted => {
SELECT genericAction
FROM
$AllUp => {ggData.state ¬ $None; ggData.mouseMode ¬ $None};
$MouseUp => {ggData.state ¬ $None; ggData.mouseMode ¬ $None};
ENDCASE;
};
ENDCASE => SIGNAL Problem[msg: "Unknown generic state"];
};
StartCaretPos: StartProc = {
The user wishes to place the caret without changing the current selection. Only hot objects trigger alignment lines.
resultPoint: Point;
normal: Vector;
feature: FeatureData;
hitData: REF ANY;
CodeTimer.StartInt[$StartCaretPos, $Gargoyle];
SaveSavedState[ggData];
IF NOT GGRefresh.EmptyOverlay[ggData] THEN ERROR;
ggData.drag.startPoint ¬ GGCaret.GetPoint[ggData.caret];
GGRefresh.SetStartBox[ggData: ggData, dragInProgress: FALSE, caret: TRUE, attractor: TRUE, selectedCPs: TRUE, hotCPs: TRUE];
[resultPoint, normal, feature, hitData] ¬ GGMultiGravity.Map[worldPt, ggData.hitTest.t, ggData.hitTest.alignBag, ggData.hitTest.sceneBag, ggData, TRUE];
SetCaretAttractor[ggData, resultPoint, normal, feature, hitData]; -- move caret to feature point
GGWindow.RestoreScreenAndInvariants[paintAction: $StartCaretPos, ggData: ggData, remake: none, edited: FALSE, okToSkipCapture: TRUE]; -- show caret in new position
CodeTimer.StopInt[$StartCaretPos, $Gargoyle];
};
DuringCaretPos: MouseProc = {
While the caret is being placed, the normal gravity scheme applies. The only feedback is attractor feedback and the motion of the caret itself.
resultPoint: Point;
normal: Vector;
feature: FeatureData;
hitData: REF ANY;
CodeTimer.StartInt[$DuringCaretPos, $Gargoyle];
[resultPoint, normal, feature, hitData] ¬ GGMultiGravity.Map[worldPt, ggData.hitTest.t, ggData.hitTest.alignBag, ggData.hitTest.sceneBag, ggData, TRUE];
GGRefresh.SetStartBox[ggData: ggData, dragInProgress: FALSE, caret: TRUE, attractor: TRUE];
SetCaretAttractor[ggData, resultPoint, normal, feature, hitData]; -- move caret to feature point
GGWindow.RestoreScreenAndInvariants[paintAction: $DuringCaretPos, ggData: ggData, remake: none, edited: FALSE, okToSkipCapture: TRUE]; -- show caret in new position
CodeTimer.StopInt[$DuringCaretPos, $Gargoyle];
}; -- end of DuringCaretPos
EndCaretPos: MouseProc = {
resultPoint: Point;
normal: Vector;
feature: FeatureData;
hitData: REF ANY;
currentObjects: AlignBag ¬ NARROW[ggData.hitTest.alignBag];
sceneObjects: TriggerBag ¬ NARROW[ggData.hitTest.sceneBag];
CodeTimer.StartInt[$EndCaretPos, $Gargoyle];
[resultPoint, normal, feature, hitData] ¬ GGMultiGravity.Map[worldPt, ggData.hitTest.t, currentObjects, sceneObjects, ggData, TRUE];
Move caret to new position.
SetCaretAttractor[ggData, resultPoint, normal, feature, hitData, "Final"]; -- move caret to feature point
GGWindow.NewCaretPos[ggData];
GGCaret.SitOn[ggData.caret, NIL]; -- subsequent Add operations will start a NEW trajectory.
GGRefresh.NullStartBox[ggData];
GGWindow.RestoreScreenAndInvariants[paintAction: $CaretMoved, ggData: ggData, remake: none, edited: FALSE, okToSkipCapture: TRUE];
CodeTimer.StopInt[$EndCaretPos, $Gargoyle];
}; -- end EndCaretPos
SetCaretAttractor:
PROC [ggData: GGData, mapPoint: Point, normal: Vector, feature: FeatureData, hitData:
REF
ANY, action: Rope.
ROPE ¬ ""] = {
Moves the caret and records the attractor which is a simple descriptor.
This code very similar to SetCaretAttractorEndpoint in GGMouseEventImplB.
Called by EndCaretPos and all the "During" procs.
scale: REAL ¬ 1.0;
IF feature = NIL THEN GGCaret.SetAttractor[ggData.caret, mapPoint, normal, NIL]
ELSE {
SELECT feature.type
FROM
slice => {
partsD: SliceDescriptor;
slideD: SliceDescriptor ¬ NARROW[feature.shape];
partsD ¬ GGSliceOps.NewParts[slideD.slice, hitData, literal];
GGCaret.SetAttractor[ggData.caret, mapPoint, normal, partsD];
};
intersectionPoint => {
See GGMultiGravityImpl.FindIntersections to see where this was made.
alignPoint: AlignmentPoint ¬ NARROW[feature.shape];
curve1, curve2: FeatureData;
curve1 ¬ alignPoint.curve1;
curve2 ¬ alignPoint.curve2;
IF hitData = NIL THEN GGCaret.SetAttractor[ggData.caret, mapPoint, normal, alignPoint]
ELSE {
attractorD: SliceDescriptor;
IF curve1.type = slice
THEN {
WITH curve1.shape
SELECT
FROM
sliceD: SliceDescriptor => attractorD ¬ GGSliceOps.NewParts[sliceD.slice, hitData, literal];
ENDCASE => ERROR;
}
ELSE
IF curve2.type = slice
THEN {
WITH curve2.shape
SELECT
FROM
sliceD: SliceDescriptor => attractorD ¬ GGSliceOps.NewParts[sliceD.slice, hitData, literal];
ENDCASE => ERROR;
}
ELSE ERROR;
GGCaret.SetAttractor[ggData.caret, mapPoint, normal, attractorD]
};
};
midpoint => {
IF hitData = NIL THEN GGCaret.SetAttractor[ggData.caret, mapPoint, normal, NIL]
ELSE {
attractorD: SliceDescriptor;
alignPoint: AlignmentPoint ¬ NARROW[feature.shape];
curveFeature: FeatureData ¬ alignPoint.curve1;
WITH curveFeature.shape
SELECT
FROM
sliceD: SliceDescriptor => attractorD ¬ GGSliceOps.NewParts[sliceD.slice, hitData, literal];
ENDCASE => ERROR;
GGCaret.SetAttractor[ggData.caret, mapPoint, normal, attractorD];
};
};
ENDCASE => GGCaret.SetAttractor[ggData.caret, mapPoint, normal, feature.shape];
};
scale ¬ GGState.GetScaleUnit[ggData];
IF action =
NIL
THEN
Feedback.PutFL[ggData.router, oneLiner, $DuringMouse, "Caret on %g at [%g, %g]. Delta [%g, %g].",
LIST[[rope[GGUIUtility.DescribeFeature[feature, hitData, ggData]]],
[real[mapPoint.x/scale]], [real[mapPoint.y/scale]],
[real[(mapPoint.x-ggData.drag.startPoint.x)/scale]], [real[(mapPoint.y-ggData.drag.startPoint.y)/scale]]]
]
ELSE {
Feedback.PutFL[ggData.router, begin, $DuringMouse, "%g caret on %g at [%g, %g]. ",
LIST[[rope[action]], [rope[GGUIUtility.DescribeFeature[feature, hitData, ggData]]],
[real[mapPoint.x/scale]], [real[mapPoint.y/scale]]] ];
Feedback.PutFL[ggData.router, end, $DuringMouse, "Delta [%g, %g].",
LIST[[real[(mapPoint.x-ggData.drag.startPoint.x)/scale]], [real[(mapPoint.y-ggData.drag.startPoint.y)/scale]]] ];
};
};
UpdateSceneForCopy:
PROC [scene: Scene, router: MsgRouter, ggData: GGData]
RETURNS [newSlices:
LIST
OF Slice, success:
BOOL ¬
TRUE] = {
newSlices ¬ GGScene.
CopySelectedParts[fromScene: scene, toScene: scene];
CopySelectedParts deselects old parts and selects copied parts
GGState.SetExtendMode[ggData, topLevel];
Set the slice to extend.
IF newSlices = NIL THEN RETURN
ELSE {
list: LIST OF Slice;
sliceToExtend: Slice;
extender: SliceDescriptor;
FOR list ¬ newSlices, list.rest UNTIL list.rest = NIL DO ENDLOOP;
sliceToExtend ¬ list.first;
extender ¬ GGState.GetSliceToExtend[ggData];
IF extender#
NIL
AND extender.slice#sliceToExtend
THEN {
sliceD: SliceDescriptor ¬ GGSliceOps.NewParts[sliceToExtend, NIL, slice];
GGState.SetSliceToExtend[ggData, sliceD];
};
};
StartCopyAndDrag: StartProc = {
Create copies of all selected objects. The selected objects can be part of trajectories, trajectories, outlines, or whole slices. Sort the resulting slices by the priority ordering of the objects they came from. Add the new objects on top. This can be done in two ways: Sort the selections by priority order to begin with, or sort the new objects after they are created.
opName: Rope.ROPE = "Copy and drag";
sliceList: LIST OF Slice;
GGHistory.NewCapture[opName, ggData]; -- capture scene BEFORE UPDATE
SaveSavedState[ggData]; -- must do this before any possible aborts occur
[sliceList, success] ¬ UpdateSceneForCopy[ggData.scene, ggData.router, ggData]; -- adds new shapes and sets slice to extend
IF NOT success THEN RETURN;
GGCaret.SitOn[ggData.caret, NIL]; -- avoids extending old outline if Add follows CopyAndDrag
GGAlign.UpdateBagsForNewSlices[sliceList, ggData];
success ¬ StartMotion[ggData: ggData, opName: opName, bagType: $Drag, worldPt: worldPt, saveState: FALSE, needAnchor: FALSE, backgroundOK: TRUE, newCurrent: FALSE]; -- put moving objects on the overlay plane. backgroundOK is TRUE because we are strictly adding.
IF NOT success THEN RETURN[FALSE];
DragUpdateCaretAndTransform[worldPt, ggData];
GGWindow.RestoreScreenAndInvariants[paintAction: $StartCopyAndDrag, ggData: ggData, remake: none, edited: FALSE, okToSkipCapture: TRUE];
};
StartAddAndDrag: StartProc = {
Expects LIST[$StartAddAndDrag, Point, Slice]. Adds Slice to scene, moves caret to Point, initiates Drag. For now, new slice is selected.
opName: Rope.ROPE = "Add and drag";
thisPoint: Point;
startBox: BoundBox;
sliceList: LIST OF Slice;
thisPoint ¬ NARROW[input.rest.first, REF Point];
sliceList ¬ LIST[NARROW[input.rest.rest.first]];
GGHistory.NewCapture[opName, ggData]; -- capture scene BEFORE UPDATE
SaveSavedState[ggData]; -- must do this before any possible aborts occur
startBox ← GGRefresh.GetABox[ggData: ggData, dragInProgress: TRUE, selectedCPs: TRUE, attractor: TRUE, caret: TRUE, into: NIL];
GGScene.AddSlice[ggData.scene, sliceList.first]; -- add slice to scene
GGSelect.DeselectAll[ggData.scene, normal]; -- clear existing selections so they don't drag
GGSelect.SelectEntireSlice[sliceList.first, ggData.scene, normal]; -- and select new slice
GGCaret.SetAttractor[ggData.caret, thisPoint, [0,-1], NIL]; -- move caret to click point
GGCaret.SitOn[ggData.caret, NIL]; -- avoids extending old outline if Add follows AddAndDrag
GGAlign.UpdateBagsForNewSlices[sliceList, ggData];
success ¬ StartMotion[ggData: ggData, opName: opName, bagType: $Drag, worldPt: thisPoint, saveState: FALSE, needAnchor: FALSE, backgroundOK: TRUE, newCurrent: FALSE, startBox: startBox]; -- put moving objects on the overlay plane. backgroundOK is TRUE because we are strictly adding.
IF NOT success THEN RETURN[FALSE];
DragUpdateCaretAndTransform[thisPoint, ggData];
GGWindow.RestoreScreenAndInvariants[paintAction: $StartCopyAndDrag, ggData: ggData, remake: none, edited: FALSE, okToSkipCapture: TRUE];
};
TransformObjectsAfterMove:
PUBLIC PROC [scene: Scene, transform: ImagerTransformation.Transformation, editConstraints: GGModelTypes.EditConstraints, history: HistoryEvent] = {
DoTransform:
PROC [sliceD: SliceDescriptor]
RETURNS [done:
BOOL ¬
FALSE] = {
GGSliceOps.Transform[sliceD.slice, sliceD.parts, transform, editConstraints, history];
};
[] ¬ GGScene.WalkSelectedSlices[scene, first, DoTransform, normal];
};
StartMotion:
PUBLIC
PROC [ggData: GGData, opName: Rope.
ROPE, bagType:
ATOM, worldPt: Point, saveState:
BOOL ¬
TRUE, needAnchor:
BOOL ¬
FALSE, backgroundOK:
BOOL ¬
FALSE, newCurrent:
BOOL ¬
TRUE, startBox: BoundBox ←
NIL]
RETURNS [success:
BOOL ¬
TRUE] = {
Check for error conditions, put moving objects on the overlay plane, record initial caret and transformation, update bags.
EnsureAnchor:
PROC
RETURNS [anySelections:
BOOL ¬
TRUE] = {
IF needAnchor
THEN {
IF GGCaret.Exists[ggData.anchor]
THEN {
ggData.drag.anchorPoint ¬ GGCaret.GetPoint[ggData.anchor];
ggData.drag.anchorNormal ¬ GGCaret.GetNormal[ggData.anchor];
}
ELSE {
tightBox: BoundBox ¬ GGScene.TightBoxOfSelected[ggData.scene, normal];
success: BOOL ¬ TRUE;
[ggData.drag.anchorPoint, success] ¬ GGBoundBox.Centroid[tightBox];
IF NOT success THEN RETURN[FALSE];
ggData.drag.anchorNormal ¬ [0, -1];
};
};
};
CodeTimer.StartInt[$StartMotion, $Gargoyle];
IF NOT GGRefresh.EmptyOverlay[ggData] THEN ERROR; -- nothing on overlay
IF saveState THEN SaveSavedState[ggData]; -- must do this before any possible aborts occur
BEGIN
IF GGSelect.NoSelections[ggData.scene, normal] OR NOT EnsureAnchor[] THEN GOTO NoSelections;
IF NOT GGCaret.Exists[ggData.caret] THEN GOTO NoCaret;
IF newCurrent THEN [] ¬ GGHistory.NewCurrent[Rope.Concat["Motion: ", opName], ggData];
GGRefresh.MoveAllSelectedToOverlay[ggData, normal];
ggData.drag.startPoint ¬ GGCaret.GetPoint[ggData.caret];
ggData.drag.transform ¬ ImagerTransformation.Scale[1.0];
ggData.embed.scaleCaretActive ¬ FALSE;
ggData.embed.scaleCaretCurrent ¬ [0,0];
IF
NOT backgroundOK
THEN {
repair the background in the composite bound box of all the slices that are selected in part or in full
GGRefresh.NullStartBox[ggData];
GGRefresh.EnlargeStartBox[ggData, GGScene.BoundBoxOfSelected[scene: ggData.scene, selectClass: normal, sliceLevel: TRUE], NIL];
Changed to directly call RepairBackgroundInBox to try to solve MMM repaint problem.
July 15, 1993 11:13:41 am PDT. KAP.
GGWindow.RestoreScreenAndInvariants[paintAction: $RepairBackground, ggData: ggData, remake: none, edited: FALSE, okToSkipCapture: TRUE];
GGRefresh.RepairBackgroundInBoundBox[ggData: ggData, bBox: ggData.refresh.beforeBox];
};
GGCaret.NoAttractor[ggData.caret]; -- is this really needed? Bier, March 26, 1987
commented out NoAttractor. KAP. June 24, 1992
GGAlign.StaticToDynamicBags[ggData, TRUE]; -- bags and foreground bitmap
GGRefresh.
SetStartBox[ggData: ggData, dragInProgress:
TRUE, movingParts:
TRUE, selectedCPs:
TRUE, hotCPs:
TRUE, alignments:
TRUE, attractor:
TRUE, caret:
TRUE];
must set the StartBox AFTER the bags are filled
IF startBox#NIL THEN GGRefresh.EnlargeStartBox[ggData, startBox, NIL];
EXITS
NoSelections => {
Feedback.PutF[ggData.router, oneLiner, $Complaint, "Motion failed: select some objects to %g", [rope[opName]]];
success ¬ FALSE;
};
NoCaret => {
Feedback.PutF[ggData.router, oneLiner, $Complaint, "Motion failed: Caret needed to %g", [rope[opName]]];
success ¬ FALSE;
};
END;
CodeTimer.StopInt[$StartMotion, $Gargoyle];
}; -- end StartMotion
ContinueMotion:
PUBLIC PROC [ggData: GGData, opName: Rope.
ROPE, bagType:
ATOM, worldPt: Point, startBox: BoundBox ¬
NIL]
RETURNS [success:
BOOL ¬
TRUE] = {
We begin with correct static bags. Add is done. Begin the drag.
movingBox: BoundBox;
normal: Vector;
IF
NOT GGRefresh.EmptyOverlay[ggData]
THEN
ERROR;
-- nothing on overlay
GGRefresh.MoveAllSelectedToOverlay[ggData, normal];
ggData.drag.startPoint ¬ GGCaret.GetPoint[ggData.caret];
normal ¬ GGCaret.GetNormal[ggData.caret];
GGCaret.SetAttractor[ggData.caret, ggData.drag.startPoint, normal, NIL]; -- place caret. NIL attractor
ggData.drag.transform ¬ ImagerTransformation.Scale[1.0];
GGAlign.StaticToDynamicBags[ggData, TRUE];
GGRefresh.UpdateForeground[ggData]; -- augmenting is always adequate
GGRefresh.
SetStartBox[ggData: ggData, dragInProgress:
TRUE, movingParts:
TRUE, selectedCPs:
TRUE, hotCPs:
TRUE, alignments:
TRUE, attractor:
TRUE, caret:
TRUE];
must set the StartBox AFTER the bags are filled
};
StartDrag: StartProc = {
startSuccess: BOOL;
CodeTimer.StartInt[$StartDrag, $Gargoyle];
startSuccess ¬ StartMotion[ggData, "drag", $Drag, worldPt];
IF NOT startSuccess THEN RETURN[FALSE];
CodeTimer.StopInt[$StartDrag, $Gargoyle];
DragUpdateCaretAndTransform[worldPt, ggData];
GGWindow.RestoreScreenAndInvariants[paintAction: $StartMotion, ggData: ggData, remake: none, edited: FALSE, okToSkipCapture: TRUE];
};
StartRotate: StartProc = {
startSuccess: BOOL;
startSuccess ¬ StartMotion[ggData, "rotate", $Drag, worldPt, TRUE, TRUE];
IF NOT startSuccess THEN RETURN[FALSE];
RotateUpdateCaretAndTransform[worldPt, ggData];
GGWindow.RestoreScreenAndInvariants[paintAction: $StartMotion, ggData: ggData, remake: none, edited: FALSE, okToSkipCapture: TRUE];
};
StartScale: StartProc = {
originalVector: Vector;
startSuccess: BOOL;
startSuccess ¬ StartMotion[ggData, "scale", $Drag, worldPt, TRUE, TRUE];
IF NOT startSuccess THEN RETURN[FALSE];
originalVector ¬ Vectors2d.Sub[ggData.drag.startPoint, ggData.drag.anchorPoint];
IF originalVector = [0.0, 0.0]
THEN {
Feedback.Append[ggData.router, oneLiner, $Complaint, "Scale failed: move caret away from anchor before scaling"];
RETURN[FALSE];
};
ScaleUpdateCaretAndTransform[worldPt, ggData];
GGWindow.RestoreScreenAndInvariants[paintAction: $StartMotion, ggData: ggData, remake: none, edited: FALSE, okToSkipCapture: TRUE];
};
StartSixPoint: StartProc = {
OPEN Vectors2d;
epsilon: REAL = 1.0e-3;
p0, p1, p2: Point;
crossProduct: REAL;
anchor: Caret ¬ ggData.anchor;
startSuccess: BOOL;
startSuccess ¬ StartMotion[ggData, "six point", $Drag, worldPt, TRUE, TRUE];
IF NOT startSuccess THEN RETURN[FALSE];
p0 ¬ GGCaret.GetPoint[anchor];
p2 ¬ ggData.drag.startPoint;
p1 ← Add[p0, VectorPlusAngle[Sub[p2, p0], 90.0]]; -- Spreitzer's Method
p1 ¬ Add[p0, VectorPlusAngle[GGCaret.GetNormal[anchor], 90.0]]; -- oriented anchor method
crossProduct ¬ CrossProductScalar[Sub[p1,p0], Sub[p2, p0]];
IF
ABS[crossProduct] < epsilon
THEN {
Feedback.Append[ggData.router, oneLiner, $Complaint, "Sixpoint failed: move caret away from anchor before six point"];
RETURN[FALSE];
};
SixPointUpdateCaretAndTransform[worldPt, ggData];
GGWindow.RestoreScreenAndInvariants[paintAction: $StartMotion, ggData: ggData, remake: none, edited: FALSE, okToSkipCapture: TRUE];
};
DragTheCaret:
PUBLIC PROC [worldPt: Point, ggData: GGData, opName: Rope.
ROPE]
RETURNS [mapPoint: Point] = {
feature: FeatureData;
normal: Vector;
hitData: REF ANY;
[mapPoint, normal, feature, hitData] ¬ GGMultiGravity.Map[worldPt, ggData.hitTest.t, ggData.hitTest.alignBag, ggData.hitTest.sceneBag, ggData, TRUE];
SetCaretAttractor[ggData, mapPoint, normal, feature, hitData, opName];
};
DuringDrag: MouseProc = {
CodeTimer.StartInt[$DuringDrag, $Gargoyle];
GGRefresh.SetStartBox[ggData: ggData, dragInProgress: TRUE, movingParts: TRUE, caret: TRUE, attractor: TRUE];
DragUpdateCaretAndTransform[worldPt, ggData];
GGWindow.RestoreScreenAndInvariants[paintAction: $DuringDrag, ggData: ggData, remake: none, edited: FALSE, okToSkipCapture: TRUE];
CodeTimer.StopInt[$DuringDrag, $Gargoyle];
}; -- end DuringDrag
DragUpdateCaretAndTransform:
PROC [worldPt: Point, ggData: GGData] = {
mapPoint: Point ¬ DragTheCaret[worldPt, ggData, "Dragging:"];
totalDragVector: Vector ¬ Vectors2d.Sub[mapPoint, ggData.drag.startPoint];
ggData.drag.transform ¬ ImagerTransformation.Translate[[totalDragVector.x, totalDragVector.y]];
IF ggData.embed.scaleCaretActive
THEN {
epsilon: REAL = 1.0e-3;
ratio: REAL;
scaleCaretStart: Vector ¬ ggData.embed.scaleCaretStart;
scaleCaretCurrent: Vector ¬ ggData.embed.scaleCaretCurrent;
scaleT: ImagerTransformation.Transformation;
IF RealFns.AlmostZero[scaleCaretCurrent.x, -10] AND RealFns.AlmostZero[scaleCaretCurrent.y, -10] THEN RETURN; -- can't scale to zero
IF RealFns.AlmostZero[scaleCaretStart.x, -10] AND RealFns.AlmostZero[scaleCaretStart.y, -10] THEN RETURN; -- can't scale by infinity
ratio ¬ Vectors2d.Magnitude[scaleCaretCurrent]/Vectors2d.Magnitude[scaleCaretStart];
scaleT ¬ GGTransform.ScaleAboutPoint[mapPoint, ratio];
ggData.drag.transform ¬ ImagerTransformation.Concat[ggData.drag.transform, scaleT];
};
};
DuringRotate: MouseProc = {
GGRefresh.SetStartBox[ggData: ggData, dragInProgress: TRUE, movingParts: TRUE, caret: TRUE, attractor: TRUE];
RotateUpdateCaretAndTransform[worldPt, ggData];
GGWindow.RestoreScreenAndInvariants[paintAction: $DuringDrag, ggData: ggData, remake: none, edited: FALSE, okToSkipCapture: TRUE];
};
RotateUpdateCaretAndTransform:
PROC [worldPt: Point, ggData: GGData] = {
anchorPoint: Point ¬ ggData.drag.anchorPoint;
mapPoint: Point ¬ DragTheCaret[worldPt, ggData, "Rotating:"];
originalVector: Vector ¬ Vectors2d.Sub[ggData.drag.startPoint, anchorPoint];
newVector: Vector ¬ Vectors2d.Sub[mapPoint, anchorPoint];
degrees: REAL ¬ Vectors2d.AngleCCWBetweenVectors[originalVector, newVector];
ggData.drag.transform ¬ GGTransform.RotateAboutPoint[anchorPoint, degrees];
};
DuringScale: MouseProc = {
GGRefresh.SetStartBox[ggData: ggData, dragInProgress: TRUE, movingParts: TRUE, caret: TRUE, attractor: TRUE];
ScaleUpdateCaretAndTransform[worldPt, ggData];
GGWindow.RestoreScreenAndInvariants[paintAction: $DuringDrag, ggData: ggData, remake: none, edited: FALSE, okToSkipCapture: TRUE];
};
ScaleUpdateCaretAndTransform:
PROC [worldPt: Point, ggData: GGData] = {
anchorPoint: Point ¬ ggData.drag.anchorPoint;
epsilon: REAL = 1.0e-3;
mapPoint: Point ¬ DragTheCaret[worldPt, ggData, "Scaling:"];
originalVector: Vector ¬ Vectors2d.Sub[ggData.drag.startPoint, anchorPoint];
newVector: Vector ¬ Vectors2d.Sub[mapPoint, anchorPoint];
ratio: REAL;
IF RealFns.AlmostZero[newVector.x, -10] AND RealFns.AlmostZero[newVector.y, -10] THEN RETURN; -- can't scale to zero
ratio ¬ Vectors2d.Magnitude[newVector]/Vectors2d.Magnitude[originalVector];
ggData.drag.transform ¬ GGTransform.ScaleAboutPoint[anchorPoint, ratio];
};
DuringSixPoint: MouseProc = {
GGRefresh.SetStartBox[ggData: ggData, dragInProgress: TRUE, movingParts: TRUE, caret: TRUE, attractor: TRUE];
SixPointUpdateCaretAndTransform[worldPt, ggData];
GGWindow.RestoreScreenAndInvariants[paintAction: $DuringDrag, ggData: ggData, remake: none, edited: FALSE, okToSkipCapture: TRUE];
};
SixPointUpdateCaretAndTransform:
PROC [worldPt: Point, ggData: GGData] = {
OPEN Vectors2d;
epsilon: REAL = 1.0e-3;
pts: ARRAY [0..5] OF Point;
crossProduct: REAL;
mapPoint: Point;
pts[0] ¬ pts[3] ¬ ggData.drag.anchorPoint;
pts[2] ¬ ggData.drag.startPoint;
pts[1] ← pts[4] ← Add[pts[0], VectorPlusAngle[Sub[pts[2], pts[0]], 90.0]]; -- Spreitzer's Method
pts[1] ¬ pts[4] ¬ Add[pts[0], VectorPlusAngle[ggData.drag.anchorNormal, 90.0]];
mapPoint ¬ DragTheCaret[worldPt, ggData, "Six Point Transform:"];
pts[5] ¬ mapPoint;
crossProduct ¬ CrossProductScalar[Sub[pts[4],pts[3]], Sub[pts[5],pts[3]]];
IF ABS[crossProduct] < epsilon THEN RETURN; -- illegal six point transform
ggData.drag.transform ¬ GGTransform.SixPoints[pts];
};
ContinueAdd: StartProc = {
The dragging is done. Update the endpoint with the current transform. Then begin a new add operation.
CodeTimer.StartInt[$ContinueAdd, $Gargoyle];
TransformObjectsAfterMove[ggData.scene, ggData.drag.transform, ggData.drag.editConstraints, NIL]; -- don't try to record undoable transforms here. A capture event is the current event.
GGHistory.PushCurrent[ggData]; -- push captured history event onto history list
GGRefresh.MoveOverlayToBackground[ggData];
GGWindow.NewCaretPos[ggData];
[] ¬ GGAlign.DynamicToStaticBags[ggData];
We are done with the last add and the bags are correct and static.
CodeTimer.StopInt[$ContinueAdd, $Gargoyle];
success ¬ StartAdd[LIST[$ContinueAdd], ggData, worldPt];
};
EndMotion: MouseProc = {
The dragging is done. Update all of the drag entities with the total drag vector and repaint the scene.
CodeTimer.StartInt[$EndMotion, $Gargoyle];
GGRefresh.SetStartBox[ggData: ggData, dragInProgress: FALSE, selectedCPs: TRUE, hotCPs: TRUE, movingParts: TRUE, caret: TRUE, attractor: TRUE, alignments: TRUE];
TransformObjectsAfterMove[ggData.scene, ggData.drag.transform, ggData.drag.editConstraints, GGHistory.GetCurrent[ggData].event];
GGHistory.PushCurrent[ggData]; -- put new transforms on history list
GGRefresh.MoveOverlayToBackground[ggData];
GGWindow.NewCaretPos[ggData];
GGAlign.DynamicToStaticBags[ggData, TRUE];
GGWindow.RestoreScreenAndInvariants[paintAction: $EndMotion, ggData: ggData, remake: none, edited: TRUE, okToSkipCapture: FALSE];
CodeTimer.StopInt[$EndMotion, $Gargoyle];
};
EndMotionNoHistory: MouseProc = {
Derived from EndMotion. The adding is done. Update all of the drag entities with the total drag vector and repaint the scene. Don't pass along the current history event. Start code has already captured the scene.
CodeTimer.StartInt[$EndMotionNoHistory, $Gargoyle];
GGRefresh.SetStartBox[ggData: ggData, dragInProgress: FALSE, selectedCPs: TRUE, hotCPs: TRUE, movingParts: TRUE, caret: TRUE, attractor: TRUE, alignments: TRUE];
TransformObjectsAfterMove[ggData.scene, ggData.drag.transform, ggData.drag.editConstraints, NIL];
GGHistory.PushCurrent[ggData]; -- put scene captured by Start code on history
GGRefresh.MoveOverlayToBackground[ggData];
GGWindow.NewCaretPos[ggData];
GGAlign.DynamicToStaticBags[ggData, TRUE];
GGWindow.RestoreScreenAndInvariants[paintAction: $EndMotion, ggData: ggData, remake: none, edited: TRUE, okToSkipCapture: FALSE];
CodeTimer.StopInt[$EndMotionNoHistory, $Gargoyle];
};
EndMotionNoHistoryAndDeselect: MouseProc = {
Derived from EndMotion. The adding is done. Update all of the drag entities with the total drag vector and repaint the scene. Don't pass along the current history event. Start code has already captured the scene. DeselectAll before painting.
CodeTimer.StartInt[$EndMotionNoHistoryAndDeselect, $Gargoyle];
GGRefresh.SetStartBox[ggData: ggData, dragInProgress: FALSE, selectedCPs: TRUE, hotCPs: TRUE, movingParts: TRUE, caret: TRUE, attractor: TRUE, alignments: TRUE];
TransformObjectsAfterMove[ggData.scene, ggData.drag.transform, ggData.drag.editConstraints, NIL];
GGHistory.PushCurrent[ggData]; -- put scene captured by Start code on history
GGRefresh.MoveOverlayToBackground[ggData];
GGWindow.NewCaretPos[ggData];
GGAlign.DynamicToStaticBags[ggData, TRUE];
GGSelect.DeselectAll[ggData.scene, normal];
GGWindow.RestoreScreenAndInvariants[paintAction: $EndMotion, ggData: ggData, remake: none, edited: TRUE, okToSkipCapture: FALSE];
CodeTimer.StopInt[$EndMotionNoHistoryAndDeselect, $Gargoyle];
};
Addition and Extension Procs
SafelyGetCaretTraj:
PUBLIC PROC [caret: Caret]
RETURNS [chair: SliceDescriptor, traj: Traj, jointNum:
NAT] = {
success: BOOL ¬ FALSE;
chairD: SliceDescriptor;
tJointNum: INT;
chair ¬ GGCaret.GetChair[caret];
[success, chairD, tJointNum] ¬ GGSliceOps.UnpackJoint[chair];
IF NOT success THEN ERROR Problem[msg: "Attempt to extend a trajectory without the caret on its end"];
jointNum ¬ tJointNum;
traj ¬ chairD.slice;
};
UpdateSceneForAdd:
PROC [scene: Scene, worldPt: Point, caret: Caret, defaults: DefaultData]
RETURNS [oldTraj, newTraj: Traj, trajEnd: TrajEnd, newParent: Slice] = {
caretPoint: Point;
jointNum: NAT;
newSeg, extendSeg: Segment;
chair: SliceDescriptor;
success: BOOL;
caretPoint ¬ GGCaret.GetPoint[caret];
newSeg ¬ GGSegment.MakeLine[worldPt, caretPoint, NIL];
Extend the existing trajectory
IF GGCaret.SittingOnEnd[caret]
THEN {
[chair, newTraj, jointNum] ¬ SafelyGetCaretTraj[caret];
oldTraj ¬ newTraj;
trajEnd ¬
SELECT jointNum
FROM
0 => lo,
GGTraj.HiJoint[newTraj] => hi,
ENDCASE => ERROR;
extendSeg ¬ GGTraj.FetchSegment[newTraj, IF trajEnd=lo THEN 0 ELSE GGTraj.HiSegment[newTraj]];
GGSegment.CopyLooks[extendSeg, newSeg];
success ¬ GGTraj.AddSegment[newTraj, trajEnd, newSeg, hi];
IF NOT success THEN RETURN;
newParent ¬ chair.slice;
}
Create a new trajectory starting at the caret.
ELSE {
oldTraj ¬ NIL;
trajEnd ¬ hi;
newTraj ¬ GGTraj.CreateTraj[caretPoint];
GGSliceOps.SetStrokeJoint[newTraj, NIL, defaults.strokeJoint, NIL];
GGSegment.SetDefaults[newSeg, defaults];
success ¬ GGTraj.AddSegment[newTraj, trajEnd, newSeg, hi];
IF NOT success THEN RETURN;
newParent ¬ GGOutline.CreateOutline[newTraj, defaults.fillColor];
GGScene.AddSlice[scene, newParent, -1];
};
};
UpdateSelectionsForAdd:
PUBLIC PROC [scene: Scene, oldTraj, newTraj: Traj, trajEnd: TrajEnd]
RETURNS [newNormal, newHot: SliceDescriptor] = {
newTraj is like oldTraj except that it has one more segment. They may be the same trajectory, modified mutably.
jointNum:
NAT ¬
SELECT trajEnd
FROM
lo => 0,
hi => GGTraj.HiJoint[newTraj],
ENDCASE => ERROR;
The new outline should be hot in the same places as the old.
newHot ¬
IF oldTraj # NIL THEN GGSelect.ReselectTraj[newTraj, trajEnd, scene, TRUE]
ELSE GGSlice.DescriptorFromParts[newTraj, GGSequence.CreateEmpty[NARROW[newTraj.data]]]; -- creating from scratch
IF newHot # NIL THEN newHot ¬ GGParent.TopLevelDescriptorFromChildDescriptor[newHot];
The new outline should have just its newest joint selected.
newNormal ¬ GGSlice.DescriptorFromParts[newTraj, GGSequence.CreateFromJoint[NARROW[newTraj.data], jointNum]];
newNormal ¬ GGParent.TopLevelDescriptorFromChildDescriptor[newNormal];
GGSelect.DeselectAll[scene, normal];
GGSelect.SelectSlice[newNormal, scene, normal];
};
UpdateCaretForAdd:
PUBLIC PROC [caret: Caret, newOutline: Slice, newNormal: Sequence, worldPt: Point] = {
jointD: SliceDescriptor ¬ GGParent.TopLevelDescriptorFromChildDescriptor[newNormal];
GGCaret.SetAttractor[caret, worldPt, [0,-1], NIL]; -- Can have better orientation.
GGCaret.SitOn[caret, jointD];
};
StartAdd: StartProc = {
StartAdd must update the caret, the scene, the selections, and the bags.
IF GGCaret.Exists[ggData.caret]
THEN {
opName: Rope.ROPE = "Add line to";
continue: BOOL ¬ FALSE;
caret: Caret;
oldTraj, newTraj: Slice;
ancestor, oldOutline: Slice;
trajEnd: TrajEnd;
newNormalD: SliceDescriptor;
startBox: BoundBox ← GGRefresh.GetABox[ggData: ggData, dragInProgress: TRUE, selectedCPs: TRUE, caret: TRUE, attractor: TRUE, into: NIL]; -- because UpdateSelectionsForAdd will DeselectAll and UpdateCaretForAdd will change the caret and attractor
CodeTimer.StartInt[$StartAdd, $Gargoyle];
continue ¬ NARROW[input.first, ATOM] = $ContinueAdd;
GGHistory.NewCapture[opName, ggData ]; -- capture scene BEFORE UPDATE
caret ¬ ggData.caret;
SaveSavedState[ggData];
[oldTraj, newTraj, trajEnd, ancestor] ¬ UpdateSceneForAdd[ggData.scene, worldPt, caret, ggData.defaults];
oldOutline ¬ IF oldTraj=NIL THEN NIL ELSE GGParent.GetTopLevelAncestor[oldTraj];
[newNormalD, ----] ¬ UpdateSelectionsForAdd[ggData.scene, oldTraj, newTraj, trajEnd];
UpdateCaretForAdd[caret, ancestor, newNormalD, worldPt];
[] ¬ GGAlign.UpdateBagsForAdd[oldOutline, newNormalD, trajEnd, ggData];
IF continue
THEN {
success ¬ ContinueMotion[ggData: ggData, opName: opName, bagType: $Drag, worldPt: worldPt, startBox: NIL];
DragUpdateCaretAndTransform[worldPt, ggData];
GGWindow.RestoreScreenAndInvariants[paintAction: $StartMotion, ggData: ggData, remake: none, edited: FALSE, okToSkipCapture: TRUE];
}
ELSE {
success ¬
StartMotion[ggData: ggData, opName: opName, bagType: $Drag, worldPt: worldPt, saveState:
FALSE, needAnchor:
FALSE, backgroundOK:
TRUE, newCurrent:
FALSE,
startBox: startBox];
backgroundOK is TRUE even though this will leave some residue on the background, because we are adding, so the traj is open (not filled).
DragUpdateCaretAndTransform[worldPt, ggData];
GGWindow.RestoreScreenAndInvariants[paintAction: $StartMotion, ggData: ggData, remake: none, edited: FALSE, okToSkipCapture: TRUE];
};
IF NOT success THEN RETURN[FALSE];
CodeTimer.StopInt[$StartAdd, $Gargoyle];
}
ELSE Feedback.Append[ggData.router, oneLiner, $Complaint, "Start add failed: caret required for adding new line"];
}; -- end StartAdd
AddNewBoxSlice:
PROC [from, to: Point, ggData: GGData, withDefaults:
BOOL ¬
TRUE]
RETURNS [sliceD: SliceDescriptor] = {
box: BoundBox;
corner: GGSlice.Corner ¬ none;
loX: REAL ¬ MIN[from.x, to.x]; loY: REAL ¬ MIN[from.y, to.y];
hiX: REAL ¬ MAX[from.x, to.x]; hiY: REAL ¬ MAX[from.y, to.y];
IF to.x=loX THEN IF to.y=loY THEN corner ¬ ll ELSE corner ¬ ul;
IF to.x=hiX THEN IF to.y=loY THEN corner ¬ lr ELSE corner ¬ ur;
box ¬ GGBoundBox.CreateBoundBox[loX, loY, hiX, hiY];
sliceD ¬ ggData.drag.boxInProgress ¬ GGSlice.MakeBoxSlice[box, corner, GGTransform.Identity[]];
IF withDefaults THEN GGSliceOps.SetDefaults[sliceD.slice, NIL, ggData.defaults, NIL];
IF GGUserProfile.GetNewBoxesUnfilled[] THEN GGSliceOps.SetFillColor[sliceD.slice, NIL, NIL, $Set, NIL];
do no painting yet
GGScene.AddSlice[ggData.scene, sliceD.slice, -1];
};
StartSelectWithBox: StartProc = {
IF GGCaret.Exists[ggData.caret]
THEN {
boxSlideD: SliceDescriptor;
caretPos: Point;
CodeTimer.StartInt[$StartSelectWithBox, $Gargoyle];
caretPos ¬ GGCaret.GetPoint[ggData.caret];
SaveSavedState[ggData]; -- must do this before any possible aborts occur
IF NOT GGRefresh.EmptyOverlay[ggData] THEN ERROR; -- nothing on overlay
Initialize the transformation and remember the starting position.
ggData.drag.startPoint ¬ worldPt;
ggData.drag.transform ¬ ImagerTransformation.Scale[1.0];
Make the new slice.
GGRefresh.SetStartBox[ggData: ggData, dragInProgress: FALSE, selectedCPs: TRUE, hotCPs: TRUE, caret: TRUE, attractor: TRUE];
boxSlideD ¬ AddNewBoxSlice[caretPos, worldPt, ggData, FALSE];
GGSliceOps.SetFillColor[boxSlideD.slice, NIL, NIL, $Set, NIL];
GGSliceOps.SetStrokeColor[boxSlideD.slice, NIL, Imager.black, $Set, NIL];
[] ¬ GGSliceOps.SetStrokeWidth[boxSlideD.slice, NIL, 1.0, NIL];
GGSelect.DeselectAll[ggData.scene, normal];
GGSelect.SelectSlice[boxSlideD, ggData.scene, normal];
Move new box to overlay
GGRefresh.MoveToOverlay[boxSlideD, ggData]; -- box on overlay to be rubberbanded
GGWindow.RestoreScreenAndInvariants[paintAction: $SelectionChanged, ggData: ggData, remake: none, edited: FALSE, okToSkipCapture: TRUE];
DuringDrag[NIL, ggData, worldPt];
CodeTimer.StopInt[$StartSelectWithBox, $Gargoyle];
}
ELSE Feedback.Append[ggData.router, oneLiner, $Complaint, "Select with box failed: caret required for select with box"];
};
EndSelectWithBox: MouseProc = {
The dragging is done. Update the box slice with the current transform. The box is cached in the ggData. Then, select the entire box and call AreaSelectNewAndDelete.
sliceD: SliceDescriptor ¬ NARROW[ggData.drag.boxInProgress];
slice: Slice ¬ sliceD.slice;
box: BoundBox;
CodeTimer.StartInt[$EndSelectWithBox, $Gargoyle];
GGRefresh.SetStartBox[ggData: ggData, dragInProgress: FALSE, movingParts: TRUE, hotCPs: TRUE, caret: TRUE, attractor: TRUE];
GGSliceOps.Transform[sliceD.slice, sliceD.parts, ggData.drag.transform, ggData.drag.editConstraints, NIL];
GGRefresh.MoveOverlayToBackground[ggData]; -- doesn't actually draw box on background
box ¬ GGSliceOps.GetBoundBox[slice];
GGScene.DeleteSlice[ggData.scene, slice];
GGScene.SelectInBox[ggData.scene, box, normal];
GGCaret.SetAttractor[ggData.caret, GGCaret.GetPoint[ggData.drag.savedCaret], GGCaret.GetNormal[ggData.drag.savedCaret], NIL]; -- put the caret back where it was before this selection operation, Bier, May 22, 1991
GGWindow.RestoreScreenAndInvariants[paintAction: $SelectionAndCaretChanged, ggData: ggData, remake: none, edited: FALSE, okToSkipCapture: FALSE];
CodeTimer.StopInt[$EndSelectWithBox, $Gargoyle];
};
StartBox: StartProc = {
IF GGCaret.Exists[ggData.caret]
THEN {
sliceD: SliceDescriptor;
caretPos: Point;
CodeTimer.StartInt[$StartBox, $Gargoyle];
IF NOT GGRefresh.EmptyOverlay[ggData] THEN ERROR; -- nothing on overlay
GGHistory.NewCapture["Add box", ggData]; -- capture scene BEFORE UPDATE
caretPos ¬ GGCaret.GetPoint[ggData.caret];
SaveSavedState[ggData]; -- must do this before any possible aborts occur
GGRefresh.
SetStartBox[ggData: ggData, dragInProgress:
TRUE, selectedCPs:
TRUE, hotCPs:
TRUE, caret:
TRUE, attractor:
TRUE];
don't need alignments: TRUE because new boxes don't generate heuristic alignment objects. Maybe they should
sliceD ¬ AddNewBoxSlice[caretPos, worldPt, ggData, TRUE];
GGSelect.DeselectAll[ggData.scene, normal];
GGSelect.SelectSlice[sliceD: sliceD, scene: ggData.scene, selectClass: normal];
GGRefresh.MoveToOverlay[sliceD, ggData]; -- put new box on overlay to be rubberbanded
ggData.drag.startPoint ¬ worldPt;
ggData.drag.transform ¬ ImagerTransformation.Scale[1.0];
GGWindow.RestoreScreenAndInvariants[paintAction: $None, ggData: ggData, remake: bitMap, edited: FALSE, okToSkipCapture: TRUE];
DragUpdateCaretAndTransform[worldPt, ggData];
GGWindow.RestoreScreenAndInvariants[paintAction: $StartCopyAndDrag, ggData: ggData, remake: none, edited: FALSE, okToSkipCapture: TRUE];
CodeTimer.StopInt[$StartBox, $Gargoyle];
}
ELSE Feedback.Append[ggData.router, oneLiner, $Complaint, "Start box failed: caret required for adding new box"];
};
EndBox: MouseProc = {
The dragging is done. Update the box slice with the current transform. The box is cached in the ggData.
sliceD: SliceDescriptor ¬ NARROW[ggData.drag.boxInProgress];
slice: Slice ¬ sliceD.slice;
GGSliceOps.Transform[sliceD.slice, sliceD.parts, ggData.drag.transform, ggData.drag.editConstraints, NIL]; -- update the slice. No history event, because this is an Add and StartBox has already captured the original scene.
GGHistory.PushCurrent[ggData]; -- push captured history event onto history list
GGRefresh.MoveOverlayToBackground[ggData];
GGRefresh.SetStartBox[ggData: ggData, dragInProgress: TRUE, movingParts: TRUE, selectedCPs: TRUE, hotCPs: TRUE, caret: TRUE, attractor: TRUE];
ggData.refresh.addedObject ¬ slice;
GGWindow.RestoreScreenAndInvariants[paintAction: $FinishedAdding, ggData: ggData, remake: triggerBag, edited: TRUE, okToSkipCapture: FALSE];
};
FixupAbortedBox:
PROC [ggData: GGData] = {
This routine is called instead of EndBox. Find the box that is being added and delete it.
sliceD: SliceDescriptor ¬ NARROW[ggData.drag.boxInProgress];
slice: Slice ¬ sliceD.slice;
GGRefresh.SetStartBox[ggData: ggData, dragInProgress: FALSE, caret: TRUE];
GGRefresh.EnlargeStartBox[ggData, GGSliceOps.GetBoundBox[slice], NIL ];
GGScene.DeleteSlice[ggData.scene, slice];
GGWindow.RestoreScreenAndInvariants[paintAction: $ObjectChangedBoundBoxProvided, ggData: ggData, remake: triggerBag, edited: FALSE, okToSkipCapture: FALSE];
};
RegisterMouseActions:
PROC [] = {
Meta Events
GGUserInput.RegisterAction[$UnGuarded, GGMouseEvent.HandleMouse, none, FALSE];
Mouse Events
GGUserInput.RegisterAction[$StartCaretPos, GGMouseEvent.HandleMouse, none, FALSE];
GGUserInput.RegisterAction[$StartAdd, GGMouseEvent.HandleMouse, none, FALSE];
GGUserInput.RegisterAction[$StartBox, GGMouseEvent.HandleMouse, none, FALSE];
GGUserInput.RegisterAction[$StartBezier, GGMouseEvent.HandleMouse, none, FALSE];
GGUserInput.RegisterAction[$StartSelectWithBox, GGMouseEvent.HandleMouse, none, FALSE];
GGUserInput.RegisterAction[$StartDrag, GGMouseEvent.HandleMouse, none, FALSE];
GGUserInput.RegisterAction[$StartCopyAndDrag, GGMouseEvent.HandleMouse, none, FALSE];
GGUserInput.RegisterAction[$StartAddAndDrag, GGMouseEvent.HandleMouse, none, FALSE];
GGUserInput.RegisterAction[$StartRotate, GGMouseEvent.HandleMouse, none, FALSE];
GGUserInput.RegisterAction[$StartScale, GGMouseEvent.HandleMouse, none, FALSE];
GGUserInput.RegisterAction[$StartSixPoint, GGMouseEvent.HandleMouse, none, FALSE];
GGUserInput.RegisterAction[$StartSelectJoint, GGMouseEvent.HandleMouse, none, FALSE];
GGUserInput.RegisterAction[$StartExtSelectJoint, GGMouseEvent.HandleMouse, none, FALSE];
GGUserInput.RegisterAction[$StartSelectSegment, GGMouseEvent.HandleMouse, none, FALSE];
GGUserInput.RegisterAction[$StartExtSelectSegment, GGMouseEvent.HandleMouse, none, FALSE];
GGUserInput.RegisterAction[$StartSelectTrajectory, GGMouseEvent.HandleMouse, none, FALSE];
GGUserInput.RegisterAction[$StartExtSelectTrajectory, GGMouseEvent.HandleMouse, none, FALSE];
GGUserInput.RegisterAction[$StartSelectTopLevel, GGMouseEvent.HandleMouse, none, FALSE];
GGUserInput.RegisterAction[$StartExtSelectTopLevel, GGMouseEvent.HandleMouse, none, FALSE];
GGUserInput.RegisterAction[$StartExtendSelection, GGMouseEvent.HandleMouse, none, FALSE];
GGUserInput.RegisterAction[$StartDeselectJoint, GGMouseEvent.HandleMouse, none, FALSE];
GGUserInput.RegisterAction[$StartDeselectSegment, GGMouseEvent.HandleMouse, none, FALSE];
GGUserInput.RegisterAction[$StartDeselectTrajectory, GGMouseEvent.HandleMouse, none, FALSE];
GGUserInput.RegisterAction[$StartDeselectTopLevel, GGMouseEvent.HandleMouse, none, FALSE];
GGUserInput.RegisterAction[$During, GGMouseEvent.HandleMouse, none, FALSE];
GGUserInput.RegisterAction[$AllUp, GGMouseEvent.HandleMouse, none, FALSE];
GGUserInput.RegisterAction[$GuardUp, GGMouseEvent.HandleMouse, none, FALSE];
GGUserInput.RegisterAction[$MouseUp, GGMouseEvent.HandleMouse, none, FALSE];
};
GuardPredicate: TYPE = GGDragTypes.GuardPredicate;
modeTable: RefTab.Ref;
ModeEntry: TYPE = REF ModeEntryObj;
ModeEntryObj:
TYPE =
RECORD [
guardP: GuardPredicate,
startProc: StartProc,
duringProc, endProc, abortProc: MouseProc,
continueProc: StartProc
];
RegisterMode:
PROC [modeName:
ATOM, guardP: GuardPredicate, startProc: StartProc, duringProc, endProc, abortProc: MouseProc, continueProc: StartProc] = {
modeEntry: ModeEntry ¬
NEW[ModeEntryObj ¬ [
guardP: guardP,
startProc: startProc,
duringProc: duringProc,
endProc: endProc,
abortProc: abortProc,
continueProc: continueProc
]];
[] ¬ RefTab.Store[modeTable, modeName, modeEntry];
};
FindMode:
PROC [modeName:
ATOM]
RETURNS [modeEntry: ModeEntry ¬
NIL] = {
found: BOOL;
val: REF;
[found, val] ¬ RefTab.Fetch[modeTable, modeName];
IF found THEN modeEntry ¬ NARROW[val];
};
RegisterModes:
PROC [] = {
modeTable ¬ RefTab.Create[29];
RegisterMode[$UnGuarded, guarded, NullStart, NullDuring, NullEnd, NullAbort, NIL];
RegisterMode[$CaretPos, guarded, StartCaretPos, DuringCaretPos, EndCaretPos, EasyAbort, NIL];
RegisterMode[$Box, guarded, StartBox, DuringDrag, EndBox, AbortBox, NIL];
RegisterMode[$Add, guarded, StartAdd, DuringDrag, EndMotionNoHistory, AbortAdd, ContinueAdd];
RegisterMode[$AddBezier, guarded, GGMouseEvent.StartAddBezier, GGMouseEvent.DuringBezierDrag, GGMouseEvent.EndBezierAdd, GGMouseEvent.AbortBezierAdd, GGMouseEvent.ContinueBezierAdd];
RegisterMode[$Drag, guarded, StartDrag, DuringDrag, EndMotion, EasyAbort, NIL];
RegisterMode[$CopyAndDrag, guarded, StartCopyAndDrag, DuringDrag, EndMotionNoHistory, AbortCopyAndDrag, NIL];
RegisterMode[$AddAndDrag, unguarded, StartAddAndDrag, DuringDrag, EndMotionNoHistoryAndDeselect, AbortCopyAndDrag, NIL];
RegisterMode[$Rotate, guarded, StartRotate, DuringRotate, EndMotion, EasyAbort, NIL];
RegisterMode[$Scale, guarded, StartScale, DuringScale, EndMotion, EasyAbort, NIL];
RegisterMode[$SixPoint, guarded, StartSixPoint, DuringSixPoint, EndMotion, EasyAbort, NIL];
RegisterMode[$SelectWithBox, unguarded, StartSelectWithBox, DuringDrag, EndSelectWithBox, AbortBox, NIL];
RegisterMode[$SelectJoint, unguarded, GGMouseEvent.StartSelect, GGMouseEvent.DuringSelect, GGMouseEvent.EndSelect, EasyAbort, NIL];
RegisterMode[$ExtSelectJoint, guarded, GGMouseEvent.StartExtendSelect, GGMouseEvent.DuringExtendSelection, GGMouseEvent.EndExtendSelection, EasyAbort, NIL];
RegisterMode[$SelectSegment, unguarded, GGMouseEvent.StartSelect, GGMouseEvent.DuringSelect, GGMouseEvent.EndSelect, EasyAbort, NIL];
RegisterMode[$ExtSelectSegment, guarded, GGMouseEvent.StartExtendSelect, GGMouseEvent.DuringExtendSelection, GGMouseEvent.EndExtendSelection, EasyAbort, NIL];
RegisterMode[$SelectTrajectory, unguarded, GGMouseEvent.StartSelect, GGMouseEvent.DuringSelect, GGMouseEvent.EndSelect, EasyAbort, NIL];
RegisterMode[$ExtSelectTrajectory, guarded, GGMouseEvent.StartExtendSelect, GGMouseEvent.DuringExtendSelection, GGMouseEvent.EndExtendSelection, EasyAbort, NIL];
RegisterMode[$SelectTopLevel, unguarded, GGMouseEvent.StartSelect, GGMouseEvent.DuringSelect, GGMouseEvent.EndSelect, EasyAbort, NIL];
RegisterMode[$ExtSelectTopLevel, guarded, GGMouseEvent.StartExtendSelect, GGMouseEvent.DuringExtendSelection, GGMouseEvent.EndExtendSelection, EasyAbort, NIL];
RegisterMode[$ExtendSelection, unguarded, GGMouseEvent.StartExtendSelection, GGMouseEvent.DuringExtendSelection, GGMouseEvent.EndExtendSelection, EasyAbort, NIL];
RegisterMode[$DeselectJoint, guarded, GGMouseEvent.StartDeselect, GGMouseEvent.DuringDeselect, GGMouseEvent.EndDeselect, EasyAbort, NIL];
RegisterMode[$DeselectSegment, guarded, GGMouseEvent.StartDeselect, GGMouseEvent.DuringDeselect, GGMouseEvent.EndDeselect, EasyAbort, NIL];
RegisterMode[$DeselectTrajectory, guarded, GGMouseEvent.StartDeselect, GGMouseEvent.DuringDeselect, GGMouseEvent.EndDeselect, EasyAbort, NIL];
RegisterMode[$DeselectTopLevel, guarded, GGMouseEvent.StartDeselect, GGMouseEvent.DuringDeselect, GGMouseEvent.EndDeselect, EasyAbort, NIL];
};
RegisterMouseActions[];
RegisterModes[];
END.