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]; }; $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 = $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: PUBLIC 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: PUBLIC 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: 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]; 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.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]] ]; }; }; 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: PUBLIC 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]; }; 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] 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.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]; 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: 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[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[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 = { 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: PUBLIC 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: PUBLIC 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]; }; 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: PUBLIC 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; 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]; 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: 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 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: PUBLIC 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: 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]; 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 = { 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[$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[$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. ’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 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 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. Motion Procs Check for error conditions, put moving objects on the overlay plane, record initial caret and transformation, update bags. movingBox _ GGScene.BoundBoxOfMoving[ggData.scene, ggData.drag.editConstraints, ggData.drag.bezierDrag]; IF NOT backgroundOK THEN GGRefresh.RepairBackgroundInBoundBox[ggData, movingBox]; 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 entire scene. 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. 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. startBox, oldSelectionsBox: BoundBox; 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]; Κ2p˜Icode™šΟnœ}™…K™BKšœ™Kšœ%Οk™(K™ K™)—K™šž ˜ Jšœι˜ιK˜—šœžœž˜ JšžœΪ˜α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šœ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šœ8˜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šœ%˜%šžœ žœžœ˜šœ`˜`Kšœ>˜>Kšœ3˜3Kšœh˜hKšœ˜——šžœ˜šœQ˜QKšœN˜NKšœ5˜5—šœB˜BKšœk˜kK˜——Kšœ˜—K™™ K˜—šœžœ3žœ žœžœžœžœ˜‡Kšœ œ#˜HK˜(Kš ™Kšžœ žœžœž˜šžœ˜Kšœžœžœ˜Kšœ˜Kšœ˜Kš žœžœ žœžœžœ˜AKšœ˜Kšœ,˜,šžœ žœžœžœ˜7Kšœ=žœ ˜IKšœ)˜)K˜—Kšœ˜—˜J˜——šœžœ˜&Kšœχ™χKšœ žœ˜$Kšœ žœžœ˜K˜Kšœ   œŸ˜DKšœŸ0˜HK˜JšœPŸ+˜{Jšžœžœ žœžœ˜JšœžœŸ:˜\Jšœ2˜2Kš œ   œNžœžœžœžœŸa˜†Kš žœžœ žœžœžœ˜"Kšœ-˜-Kšœjžœžœ˜ˆJ˜K˜—K™™ K™—šœž œ‰˜―š  œžœžœžœžœ˜LKšœV˜VJ˜—KšœC˜CK˜K˜—š  œžœžœžœ žœžœžœžœžœžœžœžœžœžœ žœžœ˜εK™zš  œžœžœžœžœ˜;šžœ žœ˜šžœžœ˜'Kšœ:˜: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šœžœ˜