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 by Xerox Corporation. All rights reserved.
Pier, May 22, 1992 4:48 pm PDT
Kurlander August 7, 1986 10:54:44 am PDT
Bier, April 28, 1992 4:26 pm PDT
Doug Wyatt, June 24, 1988 10:43:16 am 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: BOOLTRUE];
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: ATOMNARROW[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];
};
$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: ATOMNARROW[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 = $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"];
};
Caret Procs
StartCaretPos: PUBLIC 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: PUBLIC 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: PUBLIC 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.PutF[ggData.router, oneLiner, $DuringMouse, "Caret on %g at [%g, %g]. Delta [%g, %g].",
[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.PutF[ggData.router, begin, $DuringMouse, "%g caret on %g at [%g, %g]. ",
[rope[action]], [rope[GGUIUtility.DescribeFeature[feature, hitData, ggData]]],
[real[mapPoint.x/scale]], [real[mapPoint.y/scale]] ];
Feedback.PutF[ggData.router, end, $DuringMouse, "Delta [%g, %g].",
[real[(mapPoint.x-ggData.drag.startPoint.x)/scale]], [real[(mapPoint.y-ggData.drag.startPoint.y)/scale]] ];
};
};
Copy and Drag
UpdateSceneForCopy: PROC [scene: Scene, router: MsgRouter, ggData: GGData] RETURNS [newSlices: LIST OF Slice, success: BOOLTRUE] = {
newSlices ← GGScene.CopySelectedParts[fromScene: scene, toScene: scene];
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: PUBLIC 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];
};
Motion Procs
TransformObjectsAfterMove: PUBLIC PROC [scene: Scene, transform: ImagerTransformation.Transformation, editConstraints: GGModelTypes.EditConstraints, history: HistoryEvent] = {
DoTransform: PROC [sliceD: SliceDescriptor] RETURNS [done: BOOLFALSE] = {
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: BOOLTRUE, needAnchor: BOOLFALSE, backgroundOK: BOOLFALSE, newCurrent: BOOLTRUE] RETURNS [success: BOOLTRUE] = {
Check for error conditions, put moving objects on the overlay plane, record initial caret and transformation, update bags.
EnsureAnchor: PROC RETURNS [anySelections: BOOLTRUE] = {
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: BOOLTRUE;
[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];
movingBox ← GGScene.BoundBoxOfMoving[ggData.scene, ggData.drag.editConstraints, ggData.drag.bezierDrag];
IF NOT backgroundOK THEN GGRefresh.RepairBackgroundInBoundBox[ggData, movingBox];
IF NOT backgroundOK THEN {
GGRefresh.SetStartBox[ggData: ggData, dragInProgress: TRUE, movingParts: TRUE];
GGWindow.RestoreScreenAndInvariants[paintAction: $RepairBackground, ggData: ggData, remake: none, edited: FALSE, okToSkipCapture: TRUE];
};
GGCaret.NoAttractor[ggData.caret]; -- is this really needed? Bier, March 26, 1987
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
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: BOOLTRUE] = {
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: PUBLIC 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: PUBLIC 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: PUBLIC 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: PUBLIC 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: PUBLIC 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: PUBLIC 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: PUBLIC 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: PUBLIC 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: PUBLIC 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: PUBLIC MouseProc = {
The dragging is done. Update all of the drag entities with the total drag vector and repaint the entire 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: PUBLIC MouseProc = {
Derived from EndMotion. The adding is done. Update all of the drag entities with the total drag vector and repaint the entire 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];
};
Addition and Extension Procs
SafelyGetCaretTraj: PUBLIC PROC [caret: Caret] RETURNS [chair: SliceDescriptor, traj: Traj, jointNum: NAT] = {
success: BOOLFALSE;
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: NATSELECT 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: PUBLIC 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: BOOLFALSE;
caret: Caret;
oldTraj, newTraj: Slice;
ancestor, oldOutline: Slice;
trajEnd: TrajEnd;
newNormalD: SliceDescriptor;
startBox, oldSelectionsBox: BoundBox;
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];
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: BOOLTRUE] RETURNS [sliceD: SliceDescriptor] = {
box: BoundBox;
corner: GGSlice.Corner ← none;
loX: REALMIN[from.x, to.x]; loY: REALMIN[from.y, to.y];
hiX: REALMAX[from.x, to.x]; hiY: REALMAX[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: PUBLIC 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: PUBLIC 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: PUBLIC 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: PUBLIC 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[$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[$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.