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; StartProc: TYPE = GGMouseEvent.StartProc; Problem: PUBLIC SIGNAL [msg: Rope.ROPE] = Feedback.Problem; EasyAbort: PROC [input: LIST OF REF ANY, ggData: GGData, worldPt: Point] = { 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]; { 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]; }; HandleMouseless: PUBLIC UserInputProc = { 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] = { 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 = $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; 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 => { 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 = { 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 = { 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]; 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 ¬ ""] = { 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 => { 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]; GGState.SetExtendMode[ggData, topLevel]; 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 = { 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 = { 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] = { 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 { GGRefresh.NullStartBox[ggData]; GGRefresh.EnlargeStartBox[ggData, GGScene.BoundBoxOfSelected[scene: ggData.scene, selectClass: normal, sliceLevel: TRUE], NIL]; GGRefresh.RepairBackgroundInBoundBox[ggData: ggData, bBox: ggData.refresh.beforeBox]; }; 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]; 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] = { 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]; }; 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[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[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 = { 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]; CodeTimer.StopInt[$ContinueAdd, $Gargoyle]; success ¬ StartAdd[LIST[$ContinueAdd], ggData, worldPt]; }; EndMotion: MouseProc = { 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 = { 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 = { 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]; }; 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]; 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; } 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] = { jointNum: NAT ¬ SELECT trajEnd FROM lo => 0, hi => GGTraj.HiJoint[newTraj], ENDCASE => ERROR; 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]; 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 = { 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]; 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]; 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 ggData.drag.startPoint ¬ worldPt; ggData.drag.transform ¬ ImagerTransformation.Scale[1.0]; 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]; 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 = { 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]; 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 = { 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] = { 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 [] = { GGUserInput.RegisterAction[$UnGuarded, GGMouseEvent.HandleMouse, none, FALSE]; 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[$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. X 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 PROC [input: LIST OF REF ANY, ggData: GGData, worldPt: Point]; PROC [input: LIST OF REF ANY, ggData: GGData, worldPt: Point] RETURNS [success: BOOL _ TRUE]; 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]; -- can't do this. Obsolete saved selection The FSM An easy way to handle the Abort action. event will be in the format: LIST[ATOM, REF Point] or LIST[$UnGuarded, ATOM, REF Point] (mouseMode = $Circle AND state = $StartCircle) OR [] _ InputFocus.SetInputFocus[ggData.actionArea]; [] _ InputFocus.SetInputFocus[ggData.actionArea]; Caret Procs The user wishes to place the caret without changing the current selection. Only hot objects trigger alignment lines. While the caret is being placed, the normal gravity scheme applies. The only feedback is attractor feedback and the motion of the caret itself. Move caret to new position. 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. See GGMultiGravityImpl.FindIntersections to see where this was made. Copy and Drag CopySelectedParts deselects old parts and selects copied parts Set the slice to extend. 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. Expects LIST[$StartAddAndDrag, Point, Slice]. Adds Slice to scene, moves caret to Point, initiates Drag. For now, new slice is selected. Motion Procs Check for error conditions, put moving objects on the overlay plane, record initial caret and transformation, update bags. repair the background in the composite bound box of all the slices that are selected in part or in full 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]; GGCaret.NoAttractor[ggData.caret]; -- is this really needed? Bier, March 26, 1987 commented out NoAttractor. KAP. June 24, 1992 must set the StartBox AFTER the bags are filled We begin with correct static bags. Add is done. Begin the drag. must set the StartBox AFTER the bags are filled p1 _ Add[p0, VectorPlusAngle[Sub[p2, p0], 90.0]]; -- Spreitzer's Method pts[1] _ pts[4] _ Add[pts[0], VectorPlusAngle[Sub[pts[2], pts[0]], 90.0]]; -- Spreitzer's Method The dragging is done. Update the endpoint with the current transform. Then begin a new add operation. We are done with the last add and the bags are correct and static. The dragging is done. Update all of the drag entities with the total drag vector and repaint the scene. 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. 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. Addition and Extension Procs Extend the existing trajectory Create a new trajectory starting at the caret. newTraj is like oldTraj except that it has one more segment. They may be the same trajectory, modified mutably. The new outline should be hot in the same places as the old. The new outline should have just its newest joint selected. StartAdd must update the caret, the scene, the selections, and the bags. backgroundOK is TRUE even though this will leave some residue on the background, because we are adding, so the traj is open (not filled). do no painting yet Initialize the transformation and remember the starting position. Make the new slice. Move new box to overlay 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. don't need alignments: TRUE because new boxes don't generate heuristic alignment objects. Maybe they should The dragging is done. Update the box slice with the current transform. The box is cached in the ggData. This routine is called instead of EndBox. Find the box that is being added and delete it. Meta Events Mouse Events RegisterMode[$UnGuarded, guarded, NullStart, NullDuring, NullEnd, NullAbort, NIL]; Ê2·–(cedarcode) style•NewlineDelimiter ™code™KšÏnœ}™…Kšœ Ïeœ=™HK™ Kšœ%Ïk™)K™ K™&K™—šŸ ˜ Kšœé˜éK˜—šœŸœŸ˜ KšŸœÚ˜áKšŸœ"Ÿ˜.—˜KšœŸœŸœ˜6Kšœ ŸœŸœ˜3K˜Kšœ Ÿœ˜+KšœŸœ#˜7Kšœ Ÿœ˜'KšœŸœ˜%Kšœ Ÿœ˜-Kšœ Ÿœ˜-Kšœ Ÿœ˜*KšœŸœ˜'KšœŸœ˜1KšœŸœ˜!Kšœ Ÿœ˜&KšœŸœ˜!KšœŸœŸœ!˜—šœ Ÿœ˜)KšŸœ ŸœŸœŸœŸœ"Ÿœ ŸœŸœ™]——K˜KšœŸœŸœ Ÿœ˜;K˜š œŸœ ŸœŸœŸœŸœ%˜LK™Kšœ(˜(Kšœ>Ïc˜VKšœ˜K˜K˜—š œŸœ ŸœŸœŸœŸœ%˜KKšœ#Ÿœ˜(Kšœ) *™S•StartOfExpansionu[sliceD: GGModelTypes.OutlineDescriptor, scene: GGModelTypes.Scene, selectClass: GGSegmentTypes.SelectionClass]˜KšœŸœ!˜AKšŸœŸœŸœP˜bKšœ˜K˜—K˜K˜—š œŸœ ŸœŸœŸœŸœ%˜SK˜-Kšœ(˜(K˜=Kšœ˜K˜K˜—š œŸœ ŸœŸœŸœŸœ%˜KKšœ˜Kšœ˜K˜K˜—š œŸœŸœ˜-Kšœ*˜*KšœA˜AKšœpŸœŸœ˜ŽK˜K˜—š œŸœŸœ˜/K˜K˜K˜"K˜K˜—šœŸœŸœ˜5K˜K˜K˜"Kšœ*˜*K˜K˜—šœŸœŸœ˜0Kšœ%˜%Kšœ˜K˜=K˜—K™KšœŸ™šœŸœ˜)K™'KšœŸœŸœ˜3Kšœ˜K˜K˜—š œŸœ˜%Kšœ˜K˜K˜—š œŸœŸœŸœŸœ'˜_Kš œŸœŸœŸœŸœŸœ™XKšœ˜šŸœŸœ˜"Kšœ ˜ šŸœŸœ˜"K˜(K˜Kšœ˜—KšœŸœŸœ ˜-K˜ KšŸœŸœŸœŸœ#˜:šŸœ˜šŸœ#Ÿ˜)KšœGŸœ#˜k—KšŸœ^˜bK˜—K˜—šŸœ˜š œŸœ Ÿœ˜(K˜šŸœŸœ˜K˜KšŸœŸœŸœŸœ,˜CKšŸœ%˜)K˜—KšŸœ"˜&K˜—KšœŸœŸœ˜!šŸœŸ˜˜K˜$Kšœ/ \˜‹K˜—šœ˜Kšœ4˜4K˜—šœ˜Kšœ/˜/K˜—šœ˜Kšœ5˜5K˜—šœ˜Kšœ/˜/K˜—šœ˜Kšœ9˜9K˜—šœ˜Kšœ0˜0K˜—šœ˜Kšœ7˜7K˜—˜K˜6K˜—šœ˜Kšœ2˜2K˜—šœ˜Kšœ1˜1K˜—šœ˜Kšœ4˜4K˜—šœ˜Kšœ7˜7K˜—šœ˜Kšœ:˜:K˜—šœ˜Kšœ9˜9K˜—šœ˜Kšœ<˜˜>K˜—šœ˜Kšœ<˜KšŸœ¡ œ7˜F—KšŸœ˜—K˜—˜ šŸœŸ˜šœ ˜ Kš¡ œ˜#K˜#K˜—Kšœ¡ œ3˜XK˜$K˜$šœ  }˜‰Kš¡ œ/˜8K˜Kšœ;˜;K˜—KšŸœ˜—K˜—šœ ˜ šŸœŸ˜šœ ˜ Kš¡œ¡œ˜1K˜K˜K˜—Kšœ¡ œ3˜XKšŸœ˜—K˜—˜ šŸœŸ˜šœ˜Kš¡œ¡œ˜1K˜K˜K˜—Kšœ ¡ œ3˜Gšœ  E˜QšŸœŸœŸœŸœ˜7šŸœ¡ œŸœ˜.K˜K˜#K˜—KšŸœ¡ œ4˜CK˜—šŸœ˜K˜Kš¡œ¡œ˜1K˜Kšœ;˜;K˜—K˜—KšŸœ˜—K˜—šœ ˜ KšŸœŸ˜K˜;KšŸœ˜K˜—KšŸœŸœ'˜8—K˜K˜—š œŸœJŸœŸœŸœŸœ%˜“KšœŸœ˜KšœŸœ˜šŸœ ŸœŸ˜Kšœ ŸœŸœŸœ )˜FKšŸœ˜—KšœŸœ˜K˜Kš œŸœ2ŸœŸœŸœ˜`Kšœe˜eK˜K˜—šœŸœRŸœ ŸœŸœŸœŸœ%˜«šŸœŸ˜˜ šŸœŸ˜šœ ˜ Kšœ1™1KšŸœ¡ œŸœ˜>KšŸœ¡ œ2˜AKšœ˜—KšŸœ˜—K˜—˜ šŸœŸ˜Kšœ ¡ œ˜.šœ˜Kš¡œ˜ K˜K˜K˜—Kšœ ¡ œ3˜Gšœ  }˜‰Kš¡ œ/˜8K˜Kšœ;˜;K˜—KšŸœ˜—K˜—šœ ˜ šŸœŸ˜K˜;K˜=KšŸœ˜—K˜—KšŸœŸœ'˜8—K˜K˜—K™™ K˜—š œ˜K™uKšœ˜Kšœ˜Kšœ˜Kšœ ŸœŸœ˜K˜Kšœ.˜.K˜KšŸœŸœ ŸœŸœ˜1K˜8Kš œ ¡ œ!Ÿœ Ÿœ ŸœŸœ Ÿœ˜|Kšœ9¡œVŸœ˜˜KšœB ˜`KšœgŸœŸœ ˜¤Kšœ-˜-K˜K˜—šœ˜K™Kšœ˜Kšœ˜Kšœ˜Kšœ ŸœŸœ˜K˜Kšœ/˜/Kšœ9¡œVŸœ˜˜K˜Kš œ ¡ œ!Ÿœ Ÿœ Ÿœ˜[KšœB ˜`KšœhŸœŸœ ˜¥Kšœ.˜.Kšœ ˜K˜—š œ˜Kšœ˜Kšœ˜Kšœ˜Kšœ ŸœŸœ˜K˜KšœŸœ˜;KšœŸœ˜;Kšœ,˜,Kšœ9¡œBŸœ˜„Kš¡™KšœK ˜iKšœ˜KšœŸœ 9˜[Kšœ˜KšœdŸœŸœ˜‚Kšœ+˜+Kšœ ˜K˜—š œŸœRŸœŸœŸœ ˜šœG™GKšœI™IKšœ1™1—KšœŸœ˜KšŸœ ŸœŸœ6Ÿœ˜OšŸœ˜šŸœŸ˜šœ ˜ Kšœ˜KšœŸœ˜0K˜=Kšœ=˜=K˜—˜KšœD™DKšœŸœ˜3Kšœ˜K˜K˜KšŸœ ŸœŸœA˜VšŸœ˜Kšœ˜šŸœŸœ˜šŸœŸœŸ˜K˜\KšŸœŸœ˜—K˜—šŸœŸœŸœ˜"šŸœŸœŸ˜K˜\KšŸœŸœ˜—K˜—KšŸœŸœ˜ Kšœ@˜@K˜—K˜—˜ KšŸœ ŸœŸœ6Ÿœ˜OšŸœ˜Kšœ˜KšœŸœ˜3K˜.šŸœŸœŸ˜#K˜\KšŸœŸœ˜—KšœA˜AK˜—K˜—KšŸœH˜O—K˜—K˜%šŸœ ŸœŸœ˜˜aKšŸœ?˜CKšœ3˜3K˜iKšœ˜——šŸœ˜˜RKšŸœO˜SK˜6—˜CKšŸœm˜qK˜——Kšœ˜—K™™ K˜—šœŸœ3Ÿœ ŸœŸœŸœŸœ˜‡šœ¡œ#˜HK™>—K˜(Kš¡™KšŸœ ŸœŸœŸ˜šŸœ˜KšœŸœŸœ˜Kšœ˜Kšœ˜Kš ŸœŸœ ŸœŸœŸœ˜AK˜K˜,šŸœ ŸœŸœŸœ˜7Kšœ=Ÿœ ˜IKšœ)˜)K˜—Kšœ˜—˜K˜——šœ˜Kšœ÷™÷Kšœ Ÿœ˜$Kšœ ŸœŸœ˜K˜Kšœ ¡ œ ˜DKšœ 0˜HK˜KšœP +˜{KšŸœŸœ ŸœŸœ˜KšœŸœ :˜\Kšœ2˜2Kš œ ¡ œNŸœŸœŸœŸœ a˜†Kš ŸœŸœ ŸœŸœŸœ˜"Kšœ-˜-KšœjŸœŸœ˜ˆK˜K˜—šœ˜Kšœk¡™ŠK™Kšœ Ÿœ˜#K˜K˜Kšœ ŸœŸœ˜Kšœ ŸœŸœ ˜1Kšœ ŸœŸœ˜0K˜Kšœ ¡œ ˜DKšœ 0˜HK˜Kš œ=ŸœŸœ Ÿœ ŸœŸœ˜Kšœ¡œ! ˜FKšœ ¡ œ /˜[Kšœ ¡œ) ˜ZKšœ¡ œ¡ œ Ÿœ ˜XKšœŸœ 9˜[K˜2KšœO¡ œ ŸœŸœŸœŸœ ¡œ a˜œKš ŸœŸœ ŸœŸœŸœ˜"Kšœ¡ œ ˜/Kšœ2¡œ(ŸœŸœ˜ˆK˜K˜—K™™ K™—šœŸ œ‰˜¯š œŸœŸœŸœŸœ˜LKšœV˜VK˜—K˜CK˜K˜—š" œŸœŸœŸœ ŸœŸœŸœŸœŸœŸœŸœŸœŸœŸœŸœ ŸœŸœ˜ÿK™zš œŸœŸœŸœŸœ˜;šŸœ Ÿœ˜šŸœŸœ˜'K˜:K˜Kšœ ¡ œ!ŸœŸœ ŸœŸœ Ÿœ ŸœŸœ˜¡Kšœ\¢œ˜aKšœ ¡ œ  .˜NKšœ*˜*Kšœ˜Kšœ$Ÿœ˜*K˜+KšœcŸœŸœ˜K˜Kšœœ ˜=K˜—K˜™K˜—šœŸ œŸœ0Ÿœ˜nKšœ ŸœŸœ˜Kšœ˜Kšœ Ÿœ˜K˜ K˜=KšŸœŸœ ŸœŸœM˜fK˜K˜K˜K˜—šœŸœEŸœA˜¤K˜Kšœ Ÿœ˜Kšœ˜Kšœ˜Kšœ Ÿœ˜K˜K˜%Kšœ1Ÿœ˜6Kš¡™šŸœŸœ˜%K˜7K˜šœ Ÿœ Ÿ˜Kšœ˜Kšœ˜KšŸœŸœ˜—Kšœ)Ÿœ ŸœŸœ˜^Kšœ'˜'K˜:KšŸœŸœ ŸœŸœ˜K˜K˜—Kš¡.™.šŸœ˜Kšœ Ÿœ˜K˜ K˜(Kšœ#ŸœŸœ˜CK˜(K˜:KšŸœŸœ ŸœŸœ˜K˜AKšœ'˜'K˜—K˜K˜—šœŸ œ:Ÿœ)˜Kšœp™pšœ ŸœŸœ Ÿ˜#K˜Kšœ˜KšŸœŸœ˜—Kš¡<™<˜ KšŸœ ŸœŸœ0Ÿœ˜JKšŸœ=Ÿœ ˜q—KšŸœ ŸœŸœA˜UKš¡;™;KšœLŸœ˜mK˜FKšœ$˜$Kšœ/˜/K˜K˜—šœŸ œK˜iK–E[slice: GGModelTypes.Slice, childD: GGModelTypes.SliceDescriptor]˜TKšœ-Ÿœ ˜RKšœ˜K˜K˜—šœ˜K™HšŸœŸœ˜&Kšœ Ÿœ˜"Kšœ ŸœŸœ˜Kšœ ˜ Kšœ˜Kšœ˜Kšœ˜K˜Kšœ¡œ!ŸœŸœ Ÿœ ŸœŸœ l˜öK˜Kšœ)˜)Kšœ ŸœŸœ˜4Kšœ' ˜EK˜Kšœ˜K˜K˜iKš œ Ÿœ ŸœŸœŸœŸœ'˜PK˜Kšœ  œD˜UKšœ8˜8K˜GK˜šŸœ Ÿœ˜Kšœ ¡œC¡ ¢œ˜jKšœ-˜-Kšœ ¡œBŸœŸœ˜ƒK˜—šŸœ˜–Î[gargoyleData: GGInterfaceTypes.GargoyleData, opName: ROPE, bagType: ATOM, worldPt: Lines2dTypes.Point, startBox: GGBasicTypes.BoundBox _ NIL, saveState: BOOL _ TRUE, needAnchor: BOOL _ FALSE]š œ ¡ œNŸœŸœŸœŸœ¡œ˜¸KšœŸœu™‰—Kšœ-˜-Kšœ ¡œBŸœŸœ˜ƒK˜—Kš ŸœŸœ ŸœŸœŸœ˜"Kšœ(˜(Kšœ˜—KšŸœn˜rKšœ ˜K˜—š œŸœ1ŸœŸœŸœ˜wKšœ˜K˜Kš œŸœŸœŸœŸœ˜=Kš œŸœŸœŸœŸœ˜=Kš Ÿœ ŸœŸœ Ÿœ Ÿœ ˜?Kš Ÿœ ŸœŸœ Ÿœ Ÿœ ˜?K˜4K˜_KšŸœŸœ&ŸœŸœ˜UK–9[slice: GGModelTypes.Slice, color: ImagerColor.Color]š Ÿœ%Ÿœ'ŸœŸœŸœ˜gKš¡™Kšœ1˜1K˜K˜—šœ˜!šŸœŸœ˜&Kšœ˜Kšœ˜Kšœ3˜3K˜*Kšœ 0˜HKš ŸœŸœ ŸœŸœ ˜HKš¡A™AK˜!K˜8Kš¡™K–Î[gargoyleData: GGInterfaceTypes.GargoyleData, opName: ROPE, bagType: ATOM, worldPt: Lines2dTypes.Point, startBox: GGBasicTypes.BoundBox _ NIL, saveState: BOOL _ TRUE, needAnchor: BOOL _ FALSE]š œ ¡ œ!ŸœŸœ Ÿœ Ÿœ Ÿœ˜|Kšœ6Ÿœ˜=Kšœ)ŸœŸœŸœ˜>Kšœ+ŸœŸœ˜IKšœ0ŸœŸœ˜?K–][gargoyleData: GGInterfaceTypes.GargoyleData, selectClass: GGSegmentTypes.SelectionClass]˜+K–œ[slice: GGModelTypes.Slice, parts: GGModelTypes.SliceParts, gargoyleData: GGInterfaceTypes.GargoyleData, selectClass: GGSegmentTypes.SelectionClass]˜6Kš¡™Kšœ- $˜QKšœjŸœŸœ˜ˆKšœ Ÿœ˜!Kšœ2˜2Kšœ˜—KšŸœt˜xKšœ˜K˜—šœ˜Kšœ¥™¥KšœŸœ˜