DIRECTORY GGAlign, GGBasicTypes, GGBoundBox, GGBoxCluster, GGCaret, GGDeselectEvent, GGEvent, GGError, GGGravity, GGInterfaceTypes, GGModelTypes, GGMouseEvent, GGObjects, GGRefresh, GGSegment, GGSelect, GGSequence, GGSlackProcess, GGTouch, GGTransform, GGVector, GGWindow, Imager, ImagerTransformation, InputFocus, IO, Menus, Rope, Rosary; GGMouseEventImpl: CEDAR PROGRAM IMPORTS GGAlign, GGBoundBox, GGBoxCluster, GGCaret, GGDeselectEvent, GGError, GGEvent, GGGravity, GGObjects, GGRefresh, GGSegment, GGSelect, GGSequence, GGSlackProcess, GGTouch, GGTransform, GGVector, GGWindow, Imager, ImagerTransformation, InputFocus, IO, Rosary EXPORTS GGMouseEvent = BEGIN BoundBox: TYPE = GGModelTypes.BoundBox; Caret: TYPE = GGInterfaceTypes.Caret; Cluster: TYPE = GGModelTypes.Cluster; ClusterParts: TYPE = GGModelTypes.ClusterParts; ClusterGenerator: TYPE = GGModelTypes.ClusterGenerator; ClusterDescriptor: TYPE = GGModelTypes.ClusterDescriptor; EntityGenerator: TYPE = GGModelTypes.EntityGenerator; FeatureData: TYPE = GGGravity.FeatureData; Joint: TYPE = GGModelTypes.Joint; GargoyleData: TYPE = GGInterfaceTypes.GargoyleData; MouseButton: TYPE = Menus.MouseButton; ObjectBag: TYPE = GGGravity.ObjectBag; Outline: TYPE = GGModelTypes.Outline; Point: TYPE = GGBasicTypes.Point; Scene: TYPE = GGModelTypes.Scene; Segment: TYPE = GGModelTypes.Segment; Sequence: TYPE = GGModelTypes.Sequence; TouchGroup: TYPE = GGModelTypes.TouchGroup; TouchItem: TYPE = GGModelTypes.TouchItem; TouchItemGenerator: TYPE = GGTouch.TouchItemGenerator; Traj: TYPE = GGModelTypes.Traj; TrajEnd: TYPE = GGModelTypes.TrajEnd; TrajGenerator: TYPE = GGObjects.TrajGenerator; TriggerBag: TYPE = GGAlign.TriggerBag; Vector: TYPE = GGBasicTypes.Vector; MouseProc: TYPE = GGMouseEvent.MouseProc; StartProc: TYPE = GGMouseEvent.StartProc; UnexpectedType: PUBLIC ERROR = CODE; NotYetImplemented: PUBLIC SIGNAL = CODE; Problem: PUBLIC SIGNAL [msg: Rope.ROPE] = CODE; EasyAbort: PROC [input: LIST OF REF ANY, gargoyleData: GargoyleData, worldPt: Point] = { normalSelectedList: LIST OF REF ANY _ gargoyleData.drag.savedSelected.normal; GGSelect.DeselectAll[gargoyleData, normal]; -- get rid of any transient selections FOR list: LIST OF REF ANY _ normalSelectedList, list.rest UNTIL list = NIL DO WITH list.first SELECT FROM cD: ClusterDescriptor => GGSelect.SelectCluster[cD.cluster, cD.parts, gargoyleData, normal]; ENDCASE => GGSelect.SelectEntity[list.first, gargoyleData, normal]; ENDLOOP; GGCaret.Copy[from: gargoyleData.drag.savedCaret, to: gargoyleData.caret]; --restore original caret RestoreInvariants[gargoyleData]; GGError.AppendHerald[". . . Aborted.", FALSE]; GGWindow.Painter[$PaintEntireScene, gargoyleData]; -- only way to accurately restore picture }; AbortAdd: PROC [input: LIST OF REF ANY, gargoyleData: GargoyleData, worldPt: Point] = { FixupAbortedAdd[gargoyleData]; RestoreInvariants[gargoyleData]; GGError.AppendHerald[". . . Aborted.", TRUE]; }; AbortBox: PROC [input: LIST OF REF ANY, gargoyleData: GargoyleData, worldPt: Point] = { FixupAbortedBox[gargoyleData]; RestoreInvariants[gargoyleData]; GGError.AppendHerald[". . . Aborted.", TRUE]; }; RestoreInvariants: PROC [gargoyleData: GargoyleData] = { GGRefresh.MoveOverlayToBackground[gargoyleData]; }; ResetMouseMachinery: PUBLIC PROC [gargoyleData: GargoyleData] = { gargoyleData.mouseMode _ $None; gargoyleData.state _ $None; RestoreInvariants[gargoyleData]; }; SaveSavedState: PROC [gargoyleData: GargoyleData] = { gargoyleData.drag.savedSelected _ gargoyleData.selected; GGCaret.Copy[from: gargoyleData.caret, to: gargoyleData.drag.savedCaret]; }; HandleMouse: PUBLIC PROC [input: LIST OF REF ANY, gargoyleData: GargoyleData, worldPt: Point] = { atom: ATOM; SELECT gargoyleData.mouseMode FROM $CaretPos => HandleGuarded[StartCaretPos, DuringCaretPos, EndCaretPos, EasyAbort, input, gargoyleData, worldPt]; $Add => HandleGuarded[StartAdd, DuringAdd, EndAdd, AbortAdd, input, gargoyleData, worldPt]; $Box => HandleGuarded[StartBox, DuringBox, EndBox, AbortBox, input, gargoyleData, worldPt]; $Drag => HandleGuarded[StartDrag, DuringDrag, EndDrag, EasyAbort, input, gargoyleData, worldPt]; $CopyAndDrag => HandleGuarded[CopySelected, DuringDrag, EndDrag, EasyAbort, input, gargoyleData, worldPt]; $Rotate => HandleGuarded[StartRotate, DuringRotate, EndRotate, EasyAbort, input, gargoyleData, worldPt]; $Scale => HandleGuarded[StartScale, DuringScale, EndScale, EasyAbort, input, gargoyleData, worldPt]; $SelectJoint => HandleUnGuarded[StartSelectJoint, DuringSelect, EndSelect, EasyAbort, input, gargoyleData, worldPt]; $SelectSegment => HandleUnGuarded[StartSelectSegment, DuringSelect, EndSelect, EasyAbort, input, gargoyleData, worldPt]; $SelectTrajectory => HandleUnGuarded[StartSelectTrajectory, DuringSelect, EndSelect, EasyAbort, input, gargoyleData, worldPt]; $SelectTopLevel => HandleUnGuarded[StartSelectTopLevel, DuringSelect, EndSelect, EasyAbort, input, gargoyleData, worldPt]; $Extend => HandleUnGuarded[GGDeselectEvent.StartExtendSelection, GGDeselectEvent.DuringExtendSelection, GGDeselectEvent.EndExtendSelection, EasyAbort, input, gargoyleData, worldPt]; $DeselectJoint => HandleGuarded[GGDeselectEvent.StartDeselectJoint, GGDeselectEvent.DuringDeselect, GGDeselectEvent.EndDeselect, EasyAbort, input, gargoyleData, worldPt]; $DeselectSegment => HandleGuarded[GGDeselectEvent.StartDeselectSegment, GGDeselectEvent.DuringDeselect, GGDeselectEvent.EndDeselect, EasyAbort, input, gargoyleData, worldPt]; $DeselectTrajectory => HandleGuarded[GGDeselectEvent.StartDeselectTrajectory, GGDeselectEvent.DuringDeselect, GGDeselectEvent.EndDeselect, EasyAbort, input, gargoyleData, worldPt]; $DeselectTopLevel => HandleGuarded[GGDeselectEvent.StartDeselectTopLevel, GGDeselectEvent.DuringDeselect, GGDeselectEvent.EndDeselect, EasyAbort, input, gargoyleData, worldPt]; $None => { WITH input.first SELECT FROM refChar: REF CHAR => { GGSlackProcess.QueueInputActionNoPoint[GGEvent.AddChar, input, gargoyleData]; RETURN; }; ENDCASE; atom _ NARROW[input.first]; SELECT atom FROM $StartCaretPos => { gargoyleData.mouseMode _ $CaretPos; HandleMouse[input, gargoyleData, worldPt]; }; $StartAdd => { gargoyleData.mouseMode _ $Add; HandleMouse[input, gargoyleData, worldPt]; }; $StartBox => { gargoyleData.mouseMode _ $Box; HandleMouse[input, gargoyleData, worldPt]; }; $StartDrag => { gargoyleData.mouseMode _ $Drag; HandleMouse[input, gargoyleData, worldPt]; }; $StartCopyAndDrag => { gargoyleData.mouseMode _ $CopyAndDrag; HandleMouse[input, gargoyleData, worldPt]; }; $StartRotate => { gargoyleData.mouseMode _ $Rotate; HandleMouse[input, gargoyleData, worldPt]; }; $StartScale => { gargoyleData.mouseMode _ $Scale; HandleMouse[input, gargoyleData, worldPt]; }; $StartSelectJoint => { gargoyleData.mouseMode _ $SelectJoint; HandleMouse[input, gargoyleData, worldPt]; }; $StartSelectSegment => { gargoyleData.mouseMode _ $SelectSegment; HandleMouse[input, gargoyleData, worldPt]; }; $StartSelectTrajectory => { gargoyleData.mouseMode _ $SelectTrajectory; HandleMouse[input, gargoyleData, worldPt]; }; $StartSelectTopLevel => { gargoyleData.mouseMode _ $SelectTopLevel; HandleMouse[input, gargoyleData, worldPt]; }; $StartExtendSelection => { gargoyleData.mouseMode _ $Extend; HandleMouse[input, gargoyleData, worldPt]; }; $StartDeselectJoint => { gargoyleData.mouseMode _ $DeselectJoint; HandleMouse[input, gargoyleData, worldPt]; }; $StartDeselectSegment => { gargoyleData.mouseMode _ $DeselectSegment; HandleMouse[input, gargoyleData, worldPt]; }; $StartDeselectTrajectory => { gargoyleData.mouseMode _ $DeselectTrajectory; HandleMouse[input, gargoyleData, worldPt]; }; $StartDeselectTopLevel => { gargoyleData.mouseMode _ $DeselectTopLevel; HandleMouse[input, gargoyleData, worldPt]; }; ENDCASE; -- ignore other actions }; ENDCASE => SIGNAL Problem[msg: "Unimplemented Mode"]; }; HandleGuarded: PROC [startProc: StartProc, duringProc, endProc, abortProc: MouseProc, input: LIST OF REF ANY, gargoyleData: GargoyleData, worldPt: Point] = { genericAction, atom: ATOM; WITH input.first SELECT FROM refChar: REF CHAR => RETURN; -- ignore characters during mouse actions ENDCASE; atom _ NARROW[input.first]; SELECT atom FROM $StartCaretPos, $StartAdd, $StartBox, $StartDrag, $StartCopyAndDrag, $StartRotate, $StartScale, $StartSelectJoint, $StartSelectSegment, $StartSelectTrajectory, $StartSelectTopLevel, $StartExtendSelection, $StartDeselectJoint, $StartDeselectSegment, $StartDeselectTrajectory, $StartDeselectTopLevel => genericAction _ $Start; ENDCASE => genericAction _ atom; HandleGuardedAux[startProc, duringProc, endProc, abortProc, genericAction, input, gargoyleData, worldPt]; }; HandleUnGuarded: PROC [startProc: StartProc, duringProc, endProc, abortProc: MouseProc, input: LIST OF REF ANY, gargoyleData: GargoyleData, worldPt: Point] = { genericAction, atom: ATOM; WITH input.first SELECT FROM refChar: REF CHAR => RETURN; -- ignore characters during mouse actions ENDCASE; atom _ NARROW[input.first]; SELECT atom FROM $StartCaretPos, $StartAdd, $StartDrag, $StartCopyAndDrag, $StartRotate, $StartScale, $StartSelectJoint, $StartSelectSegment, $StartSelectTrajectory, $StartSelectTopLevel, $StartExtendSelection => genericAction _ $Start; ENDCASE => genericAction _ atom; HandleUnGuardedAux[startProc, duringProc, endProc, abortProc, genericAction, input, gargoyleData, worldPt]; }; HandleGuardedAux: PROC [startProc: StartProc, duringProc, endProc, abortProc: MouseProc, genericAction: ATOM, input: LIST OF REF ANY, gargoyleData: GargoyleData, worldPt: Point] = { SELECT gargoyleData.state FROM $None => { SELECT genericAction FROM $Start => {IF startProc[input, gargoyleData, worldPt] THEN gargoyleData.state _ $Main ELSE {abortProc[input, gargoyleData, worldPt]; gargoyleData.state _ $Aborted;}; }; ENDCASE; }; $Main => { SELECT genericAction FROM $During => duringProc[input, gargoyleData, worldPt]; $Abort => {abortProc[input, gargoyleData, worldPt]; gargoyleData.state _ $Aborted}; $GuardUp => gargoyleData.state _ $GuardUp; $MouseUp => gargoyleData.state _ $MouseUp; ENDCASE; }; $GuardUp => { SELECT genericAction FROM $AllUp => { endProc[input, gargoyleData, gargoyleData.drag.currentPoint]; gargoyleData.state _ $None; gargoyleData.mouseMode _ $None; }; $Abort => {abortProc[input, gargoyleData, worldPt]; gargoyleData.state _ $Aborted}; ENDCASE; }; $MouseUp => { SELECT genericAction FROM $AllUp => { endProc[input, gargoyleData, gargoyleData.drag.currentPoint]; gargoyleData.state _ $None; gargoyleData.mouseMode _ $None; }; $Abort => {abortProc[input, gargoyleData, worldPt]; gargoyleData.state _ $Aborted}; $Start => { -- we may be starting another action of this mode or some other mode. endProc[input, gargoyleData, gargoyleData.drag.currentPoint]; gargoyleData.state _ $None; gargoyleData.mouseMode _ $None; HandleMouse[input, gargoyleData, worldPt]; }; ENDCASE; }; $Aborted => { SELECT genericAction FROM $AllUp => {gargoyleData.state _ $None; gargoyleData.mouseMode _ $None}; ENDCASE; }; ENDCASE => SIGNAL Problem[msg: "Unknown generic state"]; }; HandleUnGuardedAux: PROC [startProc: StartProc, duringProc, endProc, abortProc: MouseProc, genericAction: ATOM, input: LIST OF REF ANY, gargoyleData: GargoyleData, worldPt: Point] = { SELECT gargoyleData.state FROM $None => { SELECT genericAction FROM $Start => {IF startProc[input, gargoyleData, worldPt] THEN gargoyleData.state _ $Main ELSE {abortProc[input, gargoyleData, worldPt]; gargoyleData.state _ $Aborted;}; }; ENDCASE; }; $Main => { SELECT genericAction FROM $During => duringProc[input, gargoyleData, worldPt]; $MouseUp => { endProc[input, gargoyleData, worldPt]; gargoyleData.state _ $None; gargoyleData.mouseMode _ $None; }; $AllUp => { endProc[input, gargoyleData, worldPt]; gargoyleData.state _ $None; gargoyleData.mouseMode _ $None; }; $Abort => {abortProc[input, gargoyleData, worldPt]; gargoyleData.state _ $Aborted}; ENDCASE; }; $Aborted => { SELECT genericAction FROM $AllUp => {gargoyleData.state _ $None; gargoyleData.mouseMode _ $None}; $MouseUp => {gargoyleData.state _ $None; gargoyleData.mouseMode _ $None}; ENDCASE; }; ENDCASE => SIGNAL Problem[msg: "Unknown generic state"]; }; StartCaretPos: PUBLIC StartProc = { gargoyleData.drag.currentPoint _ worldPt; GGAlign.SetBagsForAction[gargoyleData, $CaretPos]; StartSelectAux[gargoyleData, worldPt, NIL]; DuringCaretPos[NIL, gargoyleData, worldPt]; }; DuringCaretPos: PUBLIC MouseProc = { resultPoint: Point; feature: FeatureData; activeObjects: ObjectBag; sensitiveObjects: TriggerBag; activeObjects _ NARROW[gargoyleData.hitTest.activeObjectBag]; sensitiveObjects _ NARROW[gargoyleData.hitTest.sensitiveTriggerBag]; gargoyleData.drag.currentPoint _ worldPt; [resultPoint, feature] _ GGGravity.Map[worldPt, gargoyleData.hitTest.criticalR, activeObjects, sensitiveObjects, gargoyleData]; GGWindow.Painter[$BitcopyBackground, gargoyleData]; -- show all but caret SetCaretAttractor[gargoyleData, resultPoint, feature]; -- move caret to feature point GGWindow.Painter[$PaintDragOverlay, gargoyleData]; -- show caret in new position }; -- end of DuringCaretPos EndCaretPos: PUBLIC MouseProc = { resultPoint: Point; feature: FeatureData; activeObjects: ObjectBag; sensitiveObjects: TriggerBag; activeObjects _ NARROW[gargoyleData.hitTest.activeObjectBag]; sensitiveObjects _ NARROW[gargoyleData.hitTest.sensitiveTriggerBag]; [resultPoint, feature] _ GGGravity.Map[worldPt, gargoyleData.hitTest.criticalR, activeObjects, sensitiveObjects, gargoyleData]; SetCaretAttractor[gargoyleData, resultPoint, feature]; -- move caret to feature point GGCaret.DoNotSit[gargoyleData.caret]; GGError.PutF[oneLiner, "Final caret on %g.", [rope[DescribeFeature[feature, gargoyleData]]]]; GGRefresh.MoveOverlayToBackground[gargoyleData]; -- caret is back in background GGWindow.Painter[$MergeCaret, gargoyleData]; -- caret in proper place. }; SitOnFeature: PROC [caret: Caret, feature: FeatureData] = { IF feature = NIL THEN { GGCaret.DoNotSit[caret]; RETURN; }; SELECT feature.resultType FROM joint => GGCaret.SitOnJoint[caret, feature.tseq.traj, feature.jointNum]; segment => GGCaret.SitOnSegment[caret, feature.tseq.traj, feature.segNum]; ENDCASE => GGCaret.DoNotSit[caret]; }; SetCaretAttractorEndpoint: PROC [gargoyleData: GargoyleData, mapPoint: Point, feature: FeatureData] = { IF feature = NIL THEN GGCaret.SetAttractor[gargoyleData, mapPoint, NIL] ELSE SELECT feature.resultType FROM joint => GGCaret.SetAttractor[gargoyleData, mapPoint, feature.tseq.traj, TRUE, feature.jointNum]; segment => { jointNum: NAT; jointPos: Point; jointNum _ NearestJoint[feature, mapPoint]; jointPos _ GGObjects.FetchJointPos[feature.tseq.traj, jointNum]; GGCaret.SetAttractor[gargoyleData, jointPos, feature.tseq.traj, TRUE, jointNum]; }; ENDCASE => GGCaret.SetAttractor[gargoyleData, mapPoint, NIL]; GGError.PutF[oneLiner, "Caret on %g.", [rope[DescribeFeature[feature, gargoyleData]]]]; }; SetCaretAttractor: PROC [gargoyleData: GargoyleData, mapPoint: Point, feature: FeatureData] = { IF feature = NIL THEN GGCaret.SetAttractor[gargoyleData, mapPoint, NIL] ELSE SELECT feature.resultType FROM joint => GGCaret.SetAttractor[gargoyleData, mapPoint, feature.tseq.traj, TRUE, feature.jointNum]; segment => GGCaret.SetAttractor[gargoyleData, mapPoint, feature.tseq.traj, FALSE,,feature.segNum]; ENDCASE => GGCaret.SetAttractor[gargoyleData, mapPoint, NIL]; GGError.PutF[oneLiner, "Caret on %g.", [rope[DescribeFeature[feature, gargoyleData]]]]; }; CopySelected: PUBLIC StartProc = { outSeqGen: GGSelect.OutlineSequenceGenerator; newTraj: Traj; newOutline, outline: Outline; IF NOT GGRefresh.EmptyOverlay[gargoyleData] THEN ERROR; -- nothing on overlay [] _ InputFocus.SetInputFocus[gargoyleData.actionArea]; GGWindow.SaveCaretPos[gargoyleData]; SaveSavedState[gargoyleData]; -- must do this before any possible aborts occur outSeqGen _ GGSelect.SelectedOutlineSequences[gargoyleData, normal]; FOR outSeq: GGSelect.OutlineSequence _ GGSelect.NextOutlineSequences[outSeqGen], GGSelect.NextOutlineSequences[outSeqGen] UNTIL outSeq = NIL DO IF outSeq.fenceSeq # NIL THEN { IF GGSequence.NextSegment[GGSequence.SegmentsInSequence[outSeq.fenceSeq]]=NIL THEN { GGError.Append[". . . Cannot Copy Joints", oneLiner]; GGError.Blink[]; RETURN[FALSE]; }; outline _ GGObjects.OutlineOfTraj[outSeq.fenceSeq.traj]; IF GGSelect.IsSelectedInFull[outline, gargoyleData, normal] THEN { newOutline _ GGObjects.CopyOutline[outline]; GGObjects.AddOutline[gargoyleData.scene, newOutline, -1]; LOOP; } ELSE { newTraj _ GGObjects.CopyTrajFromSequence[outSeq.fenceSeq]; newOutline _ GGObjects.CreateOutline[traj: newTraj, lineEnds: outline.lineEnds, fillColor: outline.fillColor]; GGObjects.AddOutline[gargoyleData.scene, newOutline, -1]; }; }; FOR holeSeq: Sequence _ GGSequence.NextSequence[outSeq.holeSeqs], GGSequence.NextSequence[outSeq.holeSeqs] UNTIL holeSeq = NIL DO outline _ GGObjects.OutlineOfTraj[holeSeq.traj]; newTraj _ GGObjects.CopyTrajFromSequence[holeSeq]; newOutline _ GGObjects.CreateOutline[traj: newTraj, lineEnds: outline.lineEnds, fillColor: outline.fillColor]; GGObjects.AddOutline[gargoyleData.scene, newOutline, -1]; ENDLOOP; ENDLOOP; [] _ StartDrag[NIL, gargoyleData, worldPt]; }; StartSelectJoint: PUBLIC StartProc = { gargoyleData.drag.selectState _ joint; GGAlign.SetBagsForAction[gargoyleData, $Select]; StartSelectAux[gargoyleData, worldPt, NIL]; DuringSelect[NIL, gargoyleData, worldPt]; }; StartSelectSegment: PUBLIC StartProc = { gargoyleData.drag.selectState _ segment; GGAlign.SetBagsForAction[gargoyleData, $Select]; StartSelectAux[gargoyleData, worldPt, NIL]; DuringSelect[NIL, gargoyleData, worldPt]; }; StartSelectTrajectory: PUBLIC StartProc = { gargoyleData.drag.selectState _ traj; GGAlign.SetBagsForAction[gargoyleData, $Select]; StartSelectAux[gargoyleData, worldPt, NIL]; DuringSelect[NIL, gargoyleData, worldPt]; }; StartSelectTopLevel: PUBLIC StartProc = { gargoyleData.drag.selectState _ topLevel; GGAlign.SetBagsForAction[gargoyleData, $SelectTopLevel]; StartSelectAux[gargoyleData, worldPt, NIL]; DuringSelect[NIL, gargoyleData, worldPt]; }; StartSelectAux: PROC [gargoyleData: GargoyleData, worldPt: Point, startBox: BoundBox] = { IF NOT GGRefresh.EmptyOverlay[gargoyleData] THEN ERROR; -- nothing on overlay [] _ InputFocus.SetInputFocus[gargoyleData.actionArea]; GGWindow.SaveCaretPos[gargoyleData]; SaveSavedState[gargoyleData]; GGRefresh.MoveToOverlay[gargoyleData.caret, gargoyleData]; -- caret on overlay GGRefresh.StoreBackground[gargoyleData, GGBoundBox.emptyBoundBox]; --all but caret is bkgnd }; DuringSelect: PUBLIC MouseProc = { resultPoint: Point; feature: FeatureData; activeObjects: ObjectBag; sensitiveObjects: TriggerBag; GGWindow.Painter[$BitcopyBackground, gargoyleData]; -- show all but caret activeObjects _ NARROW[gargoyleData.hitTest.activeObjectBag]; sensitiveObjects _ NARROW[gargoyleData.hitTest.sensitiveTriggerBag]; gargoyleData.drag.currentPoint _ worldPt; IF gargoyleData.drag.selectState = joint THEN [resultPoint, feature] _ GGGravity.InnerCircle[worldPt, gargoyleData.hitTest.criticalR, gargoyleData.hitTest.innerR, activeObjects, sensitiveObjects] ELSE [resultPoint, feature] _ GGGravity.StrictDistance[worldPt, gargoyleData.hitTest.criticalR, activeObjects, sensitiveObjects]; SetCaretAttractorEndpoint[gargoyleData, resultPoint, feature]; GGSelect.DeselectAll[gargoyleData, normal]; IF feature = NIL THEN { -- no near trajectories, caret in free space } ELSE { SELECT gargoyleData.drag.selectState FROM joint => DuringSelectJointFeedback[feature: feature, caretPt: resultPoint, gargoyleData: gargoyleData]; segment => DuringSelectSegmentFeedback[feature: feature, caretPt: resultPoint, gargoyleData: gargoyleData]; traj => DuringSelectTrajFeedback[feature: feature, caretPt: resultPoint, gargoyleData: gargoyleData]; topLevel => DuringSelectTopLevelFeedback[feature: feature, caretPt: resultPoint, gargoyleData: gargoyleData]; ENDCASE => ERROR; }; GGWindow.Painter[$PaintSelectionFeedback, gargoyleData]; -- show caret in new position }; DuringSelectJointFeedback: PROC [feature: FeatureData, caretPt: Point, gargoyleData: GargoyleData] = { SELECT feature.resultType FROM joint => { jointSeq: Sequence _ GGSequence.CreateJointToJoint[feature.tseq.traj, feature.jointNum, feature.jointNum]; GGSelect.SelectSequence[jointSeq, gargoyleData, normal]; }; controlPoint => { controlPointSeq: Sequence _ GGSequence.CreateFromControlPoint[feature.tseq.traj, feature.segNum, feature.controlPointNum]; GGSelect.SelectSequence[controlPointSeq, gargoyleData, normal]; }; segment => { -- we are in the middle of a segment jointSeq: Sequence; jointNum: NAT; jointNum _ NearestJoint[feature, caretPt]; jointSeq _ GGSequence.CreateJointToJoint[feature.tseq.traj, jointNum, jointNum]; GGSelect.SelectSequence[jointSeq, gargoyleData, normal]; }; cluster => { parts: ClusterParts _ feature.cluster.class.endSelect[feature.cluster, joint]; GGSelect.SelectCluster[feature.cluster, parts, gargoyleData, normal]; }; slopeLine, distanceLine, intersectionPoint, midpoint, radiiCircle => ERROR; ENDCASE => ERROR NotYetImplemented; }; -- end of DuringSelectJointFeedback DuringSelectSegmentFeedback: PROC [feature: FeatureData, caretPt: Point, gargoyleData: GargoyleData] = { SELECT feature.resultType FROM joint => { -- joints are ambiguous if they are not end joints. Do nothing. }; segment, controlPoint => { -- we are in the middle of a segment. Select it. seq: Sequence _ GGSequence.CreateJointToJoint[feature.tseq.traj, feature.segNum, GGObjects.FollowingJoint[feature.tseq.traj, feature.segNum]]; GGSelect.SelectSequence[seq, gargoyleData, normal]; }; cluster => { parts: ClusterParts _ feature.cluster.class.endSelect[feature.cluster, segment]; GGSelect.SelectCluster[feature.cluster, parts, gargoyleData, normal]; }; slopeLine, distanceLine, intersectionPoint, midpoint, radiiCircle => ERROR; ENDCASE => ERROR NotYetImplemented; }; -- end of DuringSelectSegmentFeedback DuringSelectTrajFeedback: PROC [feature: FeatureData, caretPt: Point, gargoyleData: GargoyleData] = { SELECT feature.resultType FROM joint, controlPoint, segment => { -- we are in the middle of a segment. Select its traj. seq: Sequence _ GGSequence.CreateComplete[feature.tseq.traj]; GGSelect.SelectSequence[seq, gargoyleData, normal]; }; cluster => { parts: ClusterParts _ feature.cluster.class.endSelect[feature.cluster, traj]; GGSelect.SelectCluster[feature.cluster, parts, gargoyleData, normal]; }; slopeLine, distanceLine, intersectionPoint, midpoint, radiiCircle => ERROR; ENDCASE => ERROR NotYetImplemented; }; DuringSelectTopLevelFeedback: PROC [feature: FeatureData, caretPt: Point, gargoyleData: GargoyleData] = { SELECT feature.resultType FROM joint, controlPoint, segment => { GGSelect.SelectOutline[feature.tseq.traj.parent, gargoyleData, normal]; }; cluster => { parts: ClusterParts _ feature.cluster.class.endSelect[feature.cluster, topLevel]; GGSelect.SelectCluster[feature.cluster, parts, gargoyleData, normal]; }; slopeLine, distanceLine, intersectionPoint, midpoint, radiiCircle => ERROR; ENDCASE => ERROR NotYetImplemented; }; EndSelect: PUBLIC MouseProc = { resultPoint: Point; feature: FeatureData; activeObjects: ObjectBag; sensitiveObjects: TriggerBag; activeObjects _ NARROW[gargoyleData.hitTest.activeObjectBag]; sensitiveObjects _ NARROW[gargoyleData.hitTest.sensitiveTriggerBag]; IF gargoyleData.drag.selectState = joint THEN [resultPoint, feature] _ GGGravity.InnerCircle[worldPt, gargoyleData.hitTest.criticalR, gargoyleData.hitTest.innerR, activeObjects, sensitiveObjects] ELSE [resultPoint, feature] _ GGGravity.StrictDistance[worldPt, gargoyleData.hitTest.criticalR, activeObjects, sensitiveObjects]; GGRefresh.MoveOverlayToBackground[gargoyleData]; GGSelect.DeselectAll[gargoyleData, normal]; SELECT gargoyleData.drag.selectState FROM joint => EndSelectJoint[gargoyleData, resultPoint, feature]; segment => EndSelectSegment[gargoyleData, resultPoint, feature]; traj => EndSelectTrajectory[gargoyleData, resultPoint, feature]; topLevel => EndSelectTopLevel[gargoyleData, resultPoint, feature]; ENDCASE => ERROR; }; EndSelectJoint: PROC [gargoyleData: GargoyleData, resultPoint: Point, feature: FeatureData] = { jointSeq, controlPointSeq: Sequence; jointNum: NAT; IF feature # NIL AND (feature.resultType = joint OR feature.resultType = segment) THEN { jointNum _ NearestJoint[feature, resultPoint]; GGCaret.SitOnJoint[gargoyleData.caret, feature.tseq.traj, jointNum]; } ELSE GGCaret.DoNotSit[gargoyleData.caret]; IF feature = NIL THEN { -- no preparation needed GGError.Append["No near joint found.", oneLiner]; } ELSE { SELECT feature.resultType FROM joint, segment => { jointSeq _ GGSequence.CreateJointToJoint[feature.tseq.traj, jointNum, jointNum]; GGSelect.SelectSequence[jointSeq, gargoyleData, normal]; IF GGObjects.IsEndJoint[feature.tseq.traj, feature.jointNum] THEN GGError.Append["End ", begin]; -- an end joint GGError.PutF[end, "Joint %g selected", [integer[feature.jointNum]] ]; gargoyleData.drag.extendMode _ joint; gargoyleData.drag.trajToExtend _ feature.tseq.traj; }; controlPoint => { controlPointSeq _ GGSequence.CreateFromControlPoint[feature.tseq.traj, feature.segNum, feature.controlPointNum]; GGSelect.SelectSequence[controlPointSeq, gargoyleData, normal]; GGError.PutF[oneLiner, "Control point %g selected", [integer[feature.controlPointNum]] ]; gargoyleData.drag.extendMode _ controlPoint; gargoyleData.drag.trajToExtend _ feature.tseq.traj; }; cluster => { parts: ClusterParts _ feature.cluster.class.endSelect[feature.cluster, joint]; GGSelect.SelectCluster[feature.cluster, parts, gargoyleData, normal]; GGError.Append["Cluster selected", oneLiner]; gargoyleData.drag.extendMode _ joint; }; ENDCASE => ERROR UnexpectedType; }; GGWindow.Painter[$MergeBkgndAndSelected, gargoyleData]; }; EndSelectSegment: PROC [gargoyleData: GargoyleData, resultPoint: Point, feature: FeatureData] = { jointNum: NAT; seq: Sequence; IF feature # NIL AND (feature.resultType = joint OR feature.resultType = segment) THEN { jointNum _ NearestJoint[feature, resultPoint]; GGCaret.SitOnJoint[gargoyleData.caret, feature.tseq.traj, jointNum]; } ELSE GGCaret.DoNotSit[gargoyleData.caret]; IF feature = NIL THEN { -- no preparation needed GGError.Append["No near segment found.", oneLiner]; } ELSE { SELECT feature.resultType FROM joint => {}; -- actually if it is the last joint of a trajectory, this isn't ambiguous, but for now do nothing. segment, controlPoint => { seq _ GGSequence.CreateFromSegment[feature.tseq.traj, feature.segNum]; GGSelect.SelectSequence[seq, gargoyleData, normal]; GGError.PutF[oneLiner, "Segment %g selected", [integer[feature.segNum]] ]; gargoyleData.drag.extendMode _ segment; gargoyleData.drag.trajToExtend _ feature.tseq.traj; gargoyleData.drag.segToExtendNum _ feature.segNum; }; cluster => { parts: ClusterParts _ feature.cluster.class.endSelect[feature.cluster, segment]; GGSelect.SelectCluster[feature.cluster, parts, gargoyleData, normal]; GGError.Append["Cluster selected", oneLiner]; gargoyleData.drag.extendMode _ segment; }; ENDCASE => ERROR UnexpectedType; -- nothing else should be in the object bag }; GGWindow.Painter[$MergeBkgndAndSelected, gargoyleData]; }; EndSelectTrajectory: PROC [gargoyleData: GargoyleData, resultPoint: Point, feature: FeatureData] = { GGCaret.DoNotSit[gargoyleData.caret]; IF feature = NIL THEN { GGError.Append["No near trajectory found.", oneLiner]; } ELSE { SELECT feature.type FROM sequence => { jointNum: NAT _ NearestJoint[feature, resultPoint]; GGCaret.SitOnJoint[gargoyleData.caret, feature.tseq.traj, jointNum]; GGSelect.SelectTraj[feature.tseq.traj, gargoyleData, normal]; GGError.Append["Trajectory Selected", oneLiner]; gargoyleData.drag.extendMode _ traj; }; cluster => { parts: ClusterParts _ feature.cluster.class.endSelect[feature.cluster, traj]; GGSelect.SelectCluster[feature.cluster, parts, gargoyleData, normal]; GGError.Append["Cluster selected", oneLiner]; gargoyleData.drag.extendMode _ traj; }; ENDCASE => ERROR UnexpectedType; }; GGWindow.Painter[$MergeBkgndAndSelected, gargoyleData]; }; -- end SelectTrajectory EndSelectTopLevel: PROC [gargoyleData: GargoyleData, resultPoint: Point, feature: FeatureData] = { GGCaret.DoNotSit[gargoyleData.caret]; -- Don't prepare for a subsequent Add operation. IF feature = NIL THEN { -- no selection GGError.Append["No near object found.", oneLiner]; } ELSE { SELECT feature.resultType FROM joint, segment, controlPoint => { GGSelect.SelectOutline[feature.tseq.traj.parent, gargoyleData, normal]; GGError.Append["Top level trajectory selected.", oneLiner]; gargoyleData.drag.extendMode _ topLevel; }; cluster => { parts: ClusterParts _ feature.cluster.class.endSelect[feature.cluster, topLevel]; GGSelect.SelectCluster[feature.cluster, parts, gargoyleData, normal]; GGError.Append["Cluster selected", oneLiner]; gargoyleData.drag.extendMode _ topLevel; }; ENDCASE => ERROR; -- nothing else should be in the object bag }; GGWindow.Painter[$MergeBkgndAndSelected, gargoyleData]; }; -- end SelectTopLevel UpdateSelectedAfterMove: PROC [gargoyleData: GargoyleData] = { entityGen: EntityGenerator; GGTouch.InitializeTouching[gargoyleData]; entityGen _ GGSelect.SelectedEntities[gargoyleData, normal]; FOR entity: REF ANY _ GGObjects.NextEntity[entityGen], GGObjects.NextEntity[entityGen] UNTIL entity = NIL DO WITH entity SELECT FROM selSeq: Sequence => { GGObjects.TransformSequence[selSeq, gargoyleData.drag.transform]; GGTouch.SequenceMoved[selSeq, gargoyleData]; }; outline: Outline => { FOR trajs: LIST OF Traj _ outline.children, trajs.rest UNTIL trajs = NIL DO GGObjects.TransformTraj[trajs.first, gargoyleData.drag.transform]; GGTouch.TrajMoved[trajs.first, gargoyleData]; ENDLOOP; }; clusD: ClusterDescriptor => { clusD.cluster.class.transform[clusD.cluster, gargoyleData.drag.transform]; }; ENDCASE => ERROR NotYetImplemented; ENDLOOP; }; StartDrag: PUBLIC StartProc = { restoreBox: BoundBox; mapPoint: Point; feature: FeatureData; activeObjects: ObjectBag; sensitiveObjects: TriggerBag; IF NOT GGRefresh.EmptyOverlay[gargoyleData] THEN ERROR; -- nothing on overlay [] _ InputFocus.SetInputFocus[gargoyleData.actionArea]; GGWindow.SaveCaretPos[gargoyleData]; SaveSavedState[gargoyleData]; -- must do this before any possible aborts occur IF GGSelect.NoSelections[gargoyleData, normal] THEN { GGError.Append["Select some objects to drag", oneLiner]; GGError.Blink[]; RETURN[FALSE]; }; restoreBox _ GGCaret.BoundBoxOfCaret[caret: gargoyleData.caret, gargoyleData: gargoyleData]; -- must remember OLD caret position to give to StoreBackground below gargoyleData.drag.currentPoint _ worldPt; GGRefresh.MoveToOverlay[gargoyleData.caret, gargoyleData]; -- caret on overlay GGRefresh.MoveAllSelectedToOverlay[gargoyleData, normal]; -- selected objects on overlay GGAlign.SetBagsForAction[gargoyleData, $DragStartUp]; activeObjects _ NARROW[gargoyleData.hitTest.activeObjectBag]; sensitiveObjects _ NARROW[gargoyleData.hitTest.sensitiveTriggerBag]; [mapPoint, feature] _ GGGravity.Map[worldPt, gargoyleData.hitTest.criticalR, activeObjects, sensitiveObjects, gargoyleData]; IF feature = NIL THEN gargoyleData.drag.startPoint _ worldPt ELSE gargoyleData.drag.startPoint _ mapPoint; GGCaret.SetAttractor[gargoyleData, gargoyleData.drag.startPoint, NIL]; SitOnFeature[gargoyleData.caret, feature]; GGAlign.SetBagsForAction[gargoyleData, $Drag]; gargoyleData.drag.transform _ ImagerTransformation.Scale[1.0]; GGBoundBox.EnlargeByBox[restoreBox, GGBoundBox.BoundBoxOfMoving[gargoyleData] ]; gargoyleData.refresh.startBoundBox^ _ restoreBox^; GGRefresh.StoreBackground[gargoyleData, restoreBox ]; -- all but selected and caret are on the background DuringDrag[NIL, gargoyleData, worldPt]; }; DuringDrag: PUBLIC MouseProc = { totalDragVector: Vector; mapPoint: Point; feature: FeatureData; activeObjects: ObjectBag _ NARROW[gargoyleData.hitTest.activeObjectBag]; sensitiveObjects: TriggerBag _ NARROW[gargoyleData.hitTest.sensitiveTriggerBag]; gargoyleData.drag.currentPoint _ worldPt; GGWindow.Painter[$BitcopyBackground, gargoyleData]; -- Draw the background [mapPoint, feature] _ GGGravity.Map[worldPt, gargoyleData.hitTest.criticalR, activeObjects, sensitiveObjects, gargoyleData]; SetCaretAttractor[gargoyleData, mapPoint, feature]; totalDragVector _ GGVector.Sub[mapPoint, gargoyleData.drag.startPoint]; gargoyleData.drag.transform _ ImagerTransformation.Translate[[totalDragVector[1], totalDragVector[2]]]; GGWindow.Painter[$PaintDragOverlay, gargoyleData]; }; EndDrag: PUBLIC MouseProc = { UpdateSelectedAfterMove[gargoyleData]; GGCaret.MakeChairTouchAttractor[gargoyleData.caret, gargoyleData]; GGRefresh.MoveOverlayToBackground[gargoyleData]; GGWindow.Painter[$MergeAfterDragging, gargoyleData]; GGSlackProcess.EventNotify[ LIST[gargoyleData, $Edited] ]; -- tell viewer to be edited }; StartRotate: PUBLIC StartProc = { restoreBox: BoundBox; mapPoint: Point; feature: FeatureData; activeObjects: ObjectBag; sensitiveObjects: TriggerBag; IF NOT GGRefresh.EmptyOverlay[gargoyleData] THEN ERROR; -- nothing on overlay [] _ InputFocus.SetInputFocus[gargoyleData.actionArea]; GGWindow.SaveCaretPos[gargoyleData]; SaveSavedState[gargoyleData]; -- must do this before any possible aborts occur IF GGSelect.NoSelections[gargoyleData, normal] THEN { GGError.Append["Select some objects to rotate", oneLiner]; GGError.Blink[]; RETURN[FALSE]; }; IF NOT GGCaret.Exists[gargoyleData.anchor] THEN { GGError.Append["Place an anchor to rotate around", oneLiner]; GGError.Blink[]; RETURN[FALSE]; }; restoreBox _ GGCaret.BoundBoxOfCaret[caret: gargoyleData.caret, gargoyleData: gargoyleData]; -- must remember OLD caret position to give to StoreBackground below gargoyleData.drag.currentPoint _ worldPt; GGRefresh.MoveToOverlay[gargoyleData.caret, gargoyleData]; -- caret on overlay GGRefresh.MoveAllSelectedToOverlay[gargoyleData, normal]; -- selected objects on overlay GGAlign.SetBagsForAction[gargoyleData, $DragStartUp]; activeObjects _ NARROW[gargoyleData.hitTest.activeObjectBag]; sensitiveObjects _ NARROW[gargoyleData.hitTest.sensitiveTriggerBag]; [mapPoint, feature] _ GGGravity.Map[worldPt, gargoyleData.hitTest.criticalR, activeObjects, sensitiveObjects, gargoyleData]; IF feature = NIL THEN gargoyleData.drag.startPoint _ worldPt ELSE gargoyleData.drag.startPoint _ mapPoint; GGAlign.SetBagsForAction[gargoyleData, $Drag]; GGCaret.SetAttractor[gargoyleData, gargoyleData.drag.startPoint, NIL]; gargoyleData.drag.transform _ ImagerTransformation.Scale[1.0]; GGBoundBox.EnlargeByBox[restoreBox, GGBoundBox.BoundBoxOfMoving[gargoyleData] ]; gargoyleData.refresh.startBoundBox^ _ restoreBox^; GGRefresh.StoreBackground[gargoyleData, restoreBox ]; -- all but selected and caret are on the background DuringRotate[NIL, gargoyleData, worldPt]; }; DuringRotate: PUBLIC MouseProc = { originalVector, newVector: Vector; mapPoint: Point; feature: FeatureData; activeObjects: ObjectBag _ NARROW[gargoyleData.hitTest.activeObjectBag]; sensitiveObjects: TriggerBag _ NARROW[gargoyleData.hitTest.sensitiveTriggerBag]; degrees: REAL; anchorPoint: Point; anchorPoint _ GGCaret.GetPoint[gargoyleData.anchor]; GGWindow.Painter[$BitcopyBackground, gargoyleData]; gargoyleData.drag.currentPoint _ worldPt; [mapPoint, feature] _ GGGravity.Map[worldPt, gargoyleData.hitTest.criticalR, activeObjects, sensitiveObjects, gargoyleData]; SetCaretAttractor[gargoyleData, mapPoint, feature]; originalVector _ GGVector.Sub[gargoyleData.drag.startPoint, anchorPoint]; newVector _ GGVector.Sub[mapPoint, anchorPoint]; degrees _ GGVector.AngleCCWBetweenVectors[originalVector, newVector]; gargoyleData.drag.transform _ GGTransform.RotateAboutPoint[anchorPoint, degrees]; GGWindow.Painter[$PaintDragOverlay, gargoyleData]; }; EndRotate: PUBLIC MouseProc = { UpdateSelectedAfterMove[gargoyleData]; GGRefresh.MoveOverlayToBackground[gargoyleData]; GGWindow.Painter[$MergeAfterDragging, gargoyleData]; GGSlackProcess.EventNotify[ LIST[gargoyleData, $Edited] ]; -- tell viewer to be edited }; StartScale: PUBLIC StartProc = { restoreBox: BoundBox; mapPoint: Point; feature: FeatureData; activeObjects: ObjectBag; sensitiveObjects: TriggerBag; IF NOT GGRefresh.EmptyOverlay[gargoyleData] THEN ERROR; -- nothing on overlay [] _ InputFocus.SetInputFocus[gargoyleData.actionArea]; GGWindow.SaveCaretPos[gargoyleData]; SaveSavedState[gargoyleData]; -- must do this before any possible aborts occur IF GGSelect.NoSelections[gargoyleData, normal] THEN { GGError.Append["Select some objects to scale", oneLiner]; GGError.Blink[]; RETURN[FALSE]; }; IF NOT GGCaret.Exists[gargoyleData.anchor] THEN { GGError.Append["Place an anchor to scale around", oneLiner]; GGError.Blink[]; RETURN[FALSE]; }; restoreBox _ GGCaret.BoundBoxOfCaret[caret: gargoyleData.caret, gargoyleData: gargoyleData]; -- must remember OLD caret position to give to StoreBackground below gargoyleData.drag.currentPoint _ worldPt; GGRefresh.MoveToOverlay[gargoyleData.caret, gargoyleData]; -- caret on overlay GGRefresh.MoveAllSelectedToOverlay[gargoyleData, normal]; -- selected objects on overlay GGAlign.SetBagsForAction[gargoyleData, $DragStartUp]; activeObjects _ NARROW[gargoyleData.hitTest.activeObjectBag]; sensitiveObjects _ NARROW[gargoyleData.hitTest.sensitiveTriggerBag]; [mapPoint, feature] _ GGGravity.Map[worldPt, gargoyleData.hitTest.criticalR, activeObjects, sensitiveObjects, gargoyleData]; IF feature = NIL THEN gargoyleData.drag.startPoint _ worldPt ELSE gargoyleData.drag.startPoint _ mapPoint; GGAlign.SetBagsForAction[gargoyleData, $Drag]; GGCaret.SetAttractor[gargoyleData, gargoyleData.drag.startPoint, NIL]; gargoyleData.drag.transform _ ImagerTransformation.Scale[1.0]; GGBoundBox.EnlargeByBox[restoreBox, GGBoundBox.BoundBoxOfMoving[gargoyleData] ]; gargoyleData.refresh.startBoundBox^ _ restoreBox^; GGRefresh.StoreBackground[gargoyleData, restoreBox ]; -- all but selected and caret are on the background DuringScale[NIL, gargoyleData, worldPt]; }; DuringScale: PUBLIC MouseProc = { epsilon: REAL = 1.0e-3; originalVector, newVector: Vector; mapPoint: Point; feature: FeatureData; activeObjects: ObjectBag _ NARROW[gargoyleData.hitTest.activeObjectBag]; sensitiveObjects: TriggerBag _ NARROW[gargoyleData.hitTest.sensitiveTriggerBag]; ratio: REAL; anchorPoint: Point; anchorPoint _ GGCaret.GetPoint[gargoyleData.anchor]; GGWindow.Painter[$BitcopyBackground, gargoyleData]; gargoyleData.drag.currentPoint _ worldPt; [mapPoint, feature] _ GGGravity.Map[worldPt, gargoyleData.hitTest.criticalR, activeObjects, sensitiveObjects, gargoyleData]; SetCaretAttractor[gargoyleData, mapPoint, feature]; originalVector _ GGVector.Sub[gargoyleData.drag.startPoint, anchorPoint]; newVector _ GGVector.Sub[mapPoint, anchorPoint]; ratio _ GGVector.Magnitude[newVector]/GGVector.Magnitude[originalVector]; gargoyleData.drag.transform _ GGTransform.ScaleAboutPoint[anchorPoint, ratio]; GGWindow.Painter[$PaintDragOverlay, gargoyleData]; }; EndScale: PUBLIC MouseProc = { UpdateSelectedAfterMove[gargoyleData]; GGRefresh.MoveOverlayToBackground[gargoyleData]; GGWindow.Painter[$MergeAfterDragging, gargoyleData]; GGSlackProcess.EventNotify[ LIST[gargoyleData, $Edited] ]; -- tell viewer to be edited }; ExtendTrajToMouse: PRIVATE PROC [scene: Scene, worldPt: Point, gargoyleData: GargoyleData] RETURNS [traj: Traj, success: BOOL] = { caret: Caret _ gargoyleData.caret; caretPoint: Point; jointNum: NAT; newLine: Segment; caretPoint _ GGCaret.GetPoint[caret]; IF GGCaret.SittingOnEnd[caret] THEN { -- extend the existing trajectory [traj, ----, ----, jointNum, ----] _ GGCaret.GetChair[caret]; IF jointNum = 0 THEN { -- add to the low end of the trajectory newLine _ GGSegment.MakeLine[worldPt, caretPoint]; success _ GGObjects.AddSegment[traj, lo, newLine, hi]; IF NOT success THEN RETURN; GGSelect.ReselectTraj[traj, lo, gargoyleData, TRUE]; GGCaret.SetAttractor[gargoyleData, worldPt, NIL]; GGCaret.SitOnJoint[gargoyleData.caret, traj, 0]; } ELSE IF jointNum = GGObjects.HiJoint[traj] THEN { -- add to the high end of the trajectory newLine _ GGSegment.MakeLine[caretPoint, worldPt]; success _ GGObjects.AddSegment[traj, hi, newLine, lo]; IF NOT success THEN RETURN; GGSelect.ReselectTraj[traj, hi, gargoyleData, TRUE]; GGCaret.SetAttractor[gargoyleData, worldPt, NIL]; GGCaret.SitOnJoint[gargoyleData.caret, traj, jointNum + 1]; } ELSE ERROR; } ELSE { -- Create a new trajectory starting at the caret (making touching constraints, if any). newOutline: Outline; newLine _ GGSegment.MakeLine[caretPoint, worldPt]; traj _ GGObjects.CreateTraj[caretPoint]; success _ GGObjects.AddSegment[traj, hi, newLine, lo]; IF NOT success THEN RETURN; newOutline _ GGObjects.CreateOutline[traj]; GGObjects.AddOutline[scene, newOutline, -1]; GGCaret.MakeChairTouchTrajJoint[gargoyleData.caret, gargoyleData, traj, 0]; GGCaret.SetAttractor[gargoyleData, worldPt, NIL]; GGCaret.SitOnJoint[gargoyleData.caret, traj, 1]; }; }; StartAdd: PUBLIC StartProc = { restoreBox: BoundBox; jointSeq: Sequence; trajUnderCaret: Traj; jointNum: NAT; [] _ InputFocus.SetInputFocus[gargoyleData.actionArea]; GGWindow.SaveCaretPos[gargoyleData]; SaveSavedState[gargoyleData]; -- must do this before any possible aborts occur gargoyleData.drag.currentPoint _ worldPt; IF NOT GGRefresh.EmptyOverlay[gargoyleData] THEN ERROR; -- nothing on overlay restoreBox _ GGCaret.BoundBoxOfCaret[caret: gargoyleData.caret, gargoyleData: gargoyleData]; -- must remember OLD caret position to give to StoreBackground below [trajUnderCaret, success] _ ExtendTrajToMouse[gargoyleData.scene, worldPt, gargoyleData]; IF NOT success THEN RETURN[FALSE]; GGAlign.SetBagsForAction[gargoyleData, $Add]; [----, ----, ----, jointNum, ----] _ GGCaret.GetChair[gargoyleData.caret]; jointSeq _ GGSequence.CreateJointToJoint[trajUnderCaret, jointNum, jointNum]; GGCaret.SetSequence[gargoyleData.caret, jointSeq]; GGRefresh.MoveToOverlay[jointSeq, gargoyleData]; -- traj on overlay (why = joint). GGRefresh.MoveToOverlay[gargoyleData.caret, gargoyleData]; -- caret on overlay gargoyleData.drag.startPoint _ worldPt; gargoyleData.drag.transform _ ImagerTransformation.Scale[1.0]; GGRefresh.StoreBackground[gargoyleData, restoreBox]; -- all but caret and traj on background DuringAdd[NIL, gargoyleData, worldPt]; -- Bier February 10, 1986 }; DuringAdd: PUBLIC MouseProc = { totalDragVector: Vector; mapPoint: Point; feature: FeatureData; activeObjects: ObjectBag _ NARROW[gargoyleData.hitTest.activeObjectBag]; sensitiveObjects: TriggerBag _ NARROW[gargoyleData.hitTest.sensitiveTriggerBag]; GGWindow.Painter[$BitcopyBackground, gargoyleData]; -- Draw the background gargoyleData.drag.currentPoint _ worldPt; [mapPoint, feature] _ GGGravity.Map[worldPt, gargoyleData.hitTest.criticalR, activeObjects, sensitiveObjects, gargoyleData]; SetCaretAttractor[gargoyleData, mapPoint, feature]; totalDragVector _ GGVector.Sub[mapPoint, gargoyleData.drag.startPoint]; gargoyleData.drag.transform _ ImagerTransformation.Translate[[totalDragVector[1], totalDragVector[2]]]; GGWindow.Painter[$PaintDragOverlay, gargoyleData]; }; EndAdd: PUBLIC MouseProc = { selSeq: Sequence; selSeq _ GGCaret.GetSequence[gargoyleData.caret]; GGObjects.TransformSequence[selSeq, gargoyleData.drag.transform]; GGCaret.MakeChairTouchAttractor[gargoyleData.caret, gargoyleData]; GGRefresh.MoveOverlayToBackground[gargoyleData]; gargoyleData.refresh.startBoundBox^ _ selSeq.traj.boundBox^; GGWindow.Painter[$MergeBoundBox, gargoyleData]; GGSlackProcess.EventNotify[ LIST[gargoyleData, $Edited] ]; -- tell viewer to be edited }; FixupAbortedAdd: PROC [gargoyleData: GargoyleData] = { GGEvent.DeleteCaretSegment[NIL, gargoyleData]; }; AddNewBoxCluster: PROC [from, to: Point, gargoyleData: GargoyleData] RETURNS [clus: Cluster] = { box: BoundBox; corner: GGBoxCluster.Corner _ none; loX: REAL _ MIN[from[1], to[1] ]; loY: REAL _ MIN[from[2], to[2] ]; hiX: REAL _ MAX[from[1], to[1] ]; hiY: REAL _ MAX[from[2], to[2] ]; IF to[1]=loX THEN IF to[2]=loY THEN corner _ ll ELSE corner _ ul; IF to[1]=hiX THEN IF to[2]=loY THEN corner _ lr ELSE corner _ ur; box _ GGBoundBox.CreateBoundBox[loX, loY, hiX, hiY]; clus _ GGBoxCluster.MakeBoxCluster[box, corner]; GGObjects.AddCluster[gargoyleData.scene, clus, -1]; GGSlackProcess.EventNotify[ LIST[gargoyleData, $Edited] ]; -- tell viewer to be edited }; StartBox: PUBLIC StartProc = { restoreBox: BoundBox; clus: Cluster; caretPos: Point _ GGCaret.GetPoint[gargoyleData.caret]; [] _ InputFocus.SetInputFocus[gargoyleData.actionArea]; GGWindow.SaveCaretPos[gargoyleData]; SaveSavedState[gargoyleData]; -- must do this before any possible aborts occur gargoyleData.drag.currentPoint _ worldPt; IF NOT GGRefresh.EmptyOverlay[gargoyleData] THEN ERROR; -- nothing on overlay restoreBox _ GGCaret.BoundBoxOfCaret[caret: gargoyleData.caret, gargoyleData: gargoyleData]; -- must remember OLD caret position to give to StoreBackground below clus _ AddNewBoxCluster[caretPos, worldPt, gargoyleData]; GGAlign.SetBagsForAction[gargoyleData, $Drag]; -- same alignment as for Drag GGRefresh.MoveToOverlay[clus, gargoyleData]; -- clus on overlay (why ?? ). GGRefresh.MoveToOverlay[gargoyleData.caret, gargoyleData]; -- caret on overlay gargoyleData.drag.startPoint _ worldPt; gargoyleData.drag.transform _ ImagerTransformation.Scale[1.0]; GGRefresh.StoreBackground[gargoyleData, restoreBox]; -- all but caret and box on bkgnd DuringBox[NIL, gargoyleData, worldPt]; }; DuringBox: PUBLIC MouseProc = { DuringAdd[input, gargoyleData, worldPt]; }; EndBox: PUBLIC MouseProc = { cluster: Cluster; foundFirst: BOOL _ FALSE; clusGen: ClusterGenerator _ GGObjects.ClustersInScene[gargoyleData.scene]; FOR clus: Cluster _ GGObjects.NextCluster[clusGen], GGObjects.NextCluster[clusGen] UNTIL clus=NIL DO IF clus.onOverlay THEN { IF NOT foundFirst THEN { clus.class.transform[clus, gargoyleData.drag.transform]; -- update the cluster foundFirst _ TRUE; cluster _ clus; } ELSE ERROR; -- for now }; ENDLOOP; GGRefresh.MoveOverlayToBackground[gargoyleData]; gargoyleData.refresh.startBoundBox^ _ cluster.boundBox^; --newly updated by cluster.class.transform GGWindow.Painter[$MergeBoundBox, gargoyleData]; GGSlackProcess.EventNotify[ LIST[gargoyleData, $Edited] ]; -- tell viewer to be edited }; FixupAbortedBox: PROC [gargoyleData: GargoyleData] = { repaintBox: BoundBox _ GGCaret.BoundBoxOfCaret[gargoyleData.caret, gargoyleData]; -- start with caret foundFirst: BOOL _ FALSE; clusGen: ClusterGenerator _ GGObjects.ClustersInScene[gargoyleData.scene]; FOR clus: Cluster _ GGObjects.NextCluster[clusGen], GGObjects.NextCluster[clusGen] UNTIL clus=NIL DO IF clus.onOverlay THEN { GGObjects.DeleteCluster[gargoyleData.scene, clus]; GGBoundBox.EnlargeByBox[repaintBox, clus.boundBox]; -- repaint deleted box cluster EXIT; }; ENDLOOP; gargoyleData.refresh.startBoundBox^ _ repaintBox^; GGWindow.Painter[$PaintBoundBox, gargoyleData]; }; NearestJoint: PROC [feature: FeatureData, caretPt: Point] RETURNS [jointNum: NAT] = { nextNum: NAT; traj: Traj; p1, p2: Point; d1, d2: REAL; traj _ feature.tseq.traj; SELECT feature.resultType FROM joint => { jointNum _ feature.jointNum; }; segment, controlPoint => { nextNum _ GGObjects.FollowingJoint[traj, feature.segNum]; p1 _ GGObjects.FetchJointPos[traj, feature.segNum]; p2 _ GGObjects.FetchJointPos[traj, nextNum]; d1 _ GGVector.DistanceSquared[p1, caretPt]; d2 _ GGVector.DistanceSquared[p2, caretPt]; IF d1 <= d2 THEN jointNum _ feature.segNum ELSE jointNum _ nextNum; }; ENDCASE => ERROR NotYetImplemented; }; -- end of NearestJoint NearestTrajectory: PROC [scene: Scene, worldPt: Point, gargoyleData: GargoyleData] RETURNS [traj: Traj] = { feature: FeatureData; resultPoint: Point; activeObjects: ObjectBag; sensitiveObjects: TriggerBag; GGAlign.SetBagsForAction[gargoyleData, $Select]; activeObjects _ NARROW[gargoyleData.hitTest.activeObjectBag]; sensitiveObjects _ NARROW[gargoyleData.hitTest.sensitiveTriggerBag]; [resultPoint, feature] _ GGGravity.StrictDistance[worldPt, gargoyleData.hitTest.criticalR, activeObjects, sensitiveObjects]; RETURN[IF feature=NIL OR feature.type=cluster THEN NIL ELSE feature.tseq.traj]; }; NearestSegment: PROC [scene: Scene, worldPt: Point, gargoyleData: GargoyleData] RETURNS [traj: Traj, segNum: NAT] = { feature: FeatureData; resultPoint: Point; activeObjects: ObjectBag; sensitiveObjects: TriggerBag; GGAlign.SetBagsForAction[gargoyleData, $Select]; activeObjects _ NARROW[gargoyleData.hitTest.activeObjectBag]; sensitiveObjects _ NARROW[gargoyleData.hitTest.sensitiveTriggerBag]; [resultPoint, feature] _ GGGravity.StrictDistance[worldPt, gargoyleData.hitTest.criticalR, activeObjects, sensitiveObjects]; IF (feature=NIL OR feature.type=cluster) THEN RETURN[NIL, 0] ELSE RETURN[feature.tseq.traj, feature.segNum]; }; NearestTopLevel: PROC [scene: Scene, worldPt: Point, gargoyleData: GargoyleData] RETURNS [topLevel: REF ANY] = { feature: FeatureData; resultPoint: Point; activeObjects: ObjectBag; sensitiveObjects: TriggerBag; GGAlign.SetBagsForAction[gargoyleData, $SelectTopLevel]; activeObjects _ NARROW[gargoyleData.hitTest.activeObjectBag]; sensitiveObjects _ NARROW[gargoyleData.hitTest.sensitiveTriggerBag]; [resultPoint, feature] _ GGGravity.StrictDistance[worldPt, gargoyleData.hitTest.criticalR, activeObjects, sensitiveObjects]; IF feature = NIL THEN topLevel _ NIL ELSE { SELECT feature.resultType FROM joint, segment, controlPoint => { topLevel _ feature.tseq.traj.parent; }; cluster => { topLevel _ feature.cluster; }; ENDCASE => ERROR; }; }; SelectAllTouchingFeature: PROC [feature: FeatureData, gargoyleData: GargoyleData] = { item: TouchItem; jointSeq, segSeq: Sequence; SELECT feature.resultType FROM joint => { item _ TouchItemOfJoint[feature.tseq.traj, feature.jointNum]; IF item = NIL THEN { jointSeq _ GGSequence.CreateJointToJoint[feature.tseq.traj, feature.jointNum, feature.jointNum]; GGSelect.SelectSequence[jointSeq, gargoyleData, normal]; IF GGObjects.IsEndJoint[feature.tseq.traj, feature.jointNum] THEN GGError.Append["End ", begin]; -- an end joint GGError.PutF[end, "Joint %g deselected", [integer[feature.jointNum]] ]; } ELSE { group: TouchGroup _ GGTouch.TouchGroupOfItem[item]; jointNum, segNum: NAT; itemGen: TouchItemGenerator; itemGen _ GGTouch.AllTouchItems[group]; FOR thisItem: TouchItem _ GGTouch.NextTouchItem[itemGen], GGTouch.NextTouchItem[itemGen] UNTIL thisItem = NIL DO SELECT thisItem.touchingPartType FROM joint => { jointNum _ GGObjects.IndexOfJoint[thisItem.joint, thisItem.traj]; jointSeq _ GGSequence.CreateFromJoint[thisItem.traj, jointNum]; GGSelect.SelectSequence[jointSeq, gargoyleData, normal]; }; segment => { segNum _ GGObjects.IndexOfSegment[thisItem.seg, thisItem.traj]; segSeq _ GGSequence.CreateFromSegment[thisItem.traj, segNum]; GGSelect.SelectSequence[segSeq, gargoyleData, normal]; }; ENDCASE; ENDLOOP; GGError.Append["Multiple entities selected", oneLiner]; }; }; ENDCASE => ERROR NotYetImplemented; }; TouchItemOfJoint: PROC [traj: Traj, jointNum: NAT] RETURNS [item: TouchItem] = { joint: Joint _ NARROW[Rosary.Fetch[traj.joints, jointNum]]; item _ joint.touchItem; }; DescribeFeature: PROC [feature: FeatureData, gargoyleData: GargoyleData] RETURNS [rope: Rope.ROPE] = { IF feature = NIL THEN RETURN[" nothing"] ELSE { SELECT feature.resultType FROM joint => rope _ "joint"; controlPoint => rope _ "control point"; segment => { rope _ IO.PutFR["segment %g", [integer[feature.segNum]]]; }; cluster => rope _ "cluster"; distanceLine => rope _ "distance line"; slopeLine => rope _ "slope line"; symmetryLine => rope _ "symmetry line"; radiiCircle => rope _ "compass circle"; intersectionPoint => { firstObj, secondObj: Rope.ROPE; firstObj _ IF feature.line1Feature # NIL THEN DescribeSourceFeature[feature.line1Feature, gargoyleData] ELSE "unknown"; secondObj _ IF feature.line2Feature # NIL THEN DescribeSourceFeature[feature.line2Feature, gargoyleData] ELSE "unknown"; rope _ IO.PutFR["a %g/%g intersection point", [rope[firstObj]], [rope[secondObj]] ]; }; midpoint => rope _ IO.PutFR["midpoint of segment %g", [integer[feature.segNum]]]; ENDCASE => ERROR; }; }; DescribeSourceFeature: PROC [feature: FeatureData, gargoyleData: GargoyleData] RETURNS [rope: Rope.ROPE] = { IF feature = NIL THEN RETURN[" nothing"] ELSE { SELECT feature.type FROM sequence => rope _ "sequence"; cluster => rope _ "cluster"; distanceLine => rope _ "distance line"; slopeLine => rope _ "slope line"; symmetryLine => rope _ "symmetry line"; radiiCircle => rope _ "compass circle"; intersectionPoint => { firstObj, secondObj: Rope.ROPE; firstObj _ IF feature.line1Feature # NIL THEN DescribeSourceFeature[feature.line1Feature, gargoyleData] ELSE "unknown"; secondObj _ IF feature.line2Feature # NIL THEN DescribeSourceFeature[feature.line2Feature, gargoyleData] ELSE "unknown"; rope _ IO.PutFR["a %g/%g intersection point", [rope[firstObj]], [rope[secondObj]] ]; }; midpoint => rope _ IO.PutFR["midpoint of segment %g", [integer[feature.segNum]]]; ENDCASE => ERROR; }; }; END. > GGMouseEventImpl.mesa Last edited by Bier on March 18, 1986 4:27:27 pm PST Contents: Once a mouse event reaches the front of the slack-process queue, it is dispatched to one of the procedures in this module. Stone, August 5, 1985 4:14:05 pm PDT Pier, April 24, 1986 4:09:47 pm PST MouseProc: TYPE = PROC [input: LIST OF REF ANY, gargoyleData: GargoyleData, worldPt: Point]; StartProc: TYPE = PROC [input: LIST OF REF ANY, gargoyleData: GargoyleData, 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. FOR list: LIST OF REF ANY _ normalSelectedList, list.rest UNTIL list = NIL DO GGSelect.SelectEntity[list.first, gargoyleData, normal]; -- restore original selections ENDLOOP; NoOpAbort: PROC [input: LIST OF REF ANY, gargoyleData: GargoyleData, worldPt: Point] = { No abort action is provided, yet. rope: Rope.ROPE; rope _ Atom.GetPName[gargoyleData.mouseMode]; GGError.PutF[oneLiner, "%g cannot be aborted yet.", [rope[rope]]]; GGError.Blink[]; }; NoOpDuring: PROC [input: LIST OF REF ANY, gargoyleData: GargoyleData, worldPt: Point] = { }; NoOpEnd: PROC [input: LIST OF REF ANY, gargoyleData: GargoyleData, worldPt: Point] = { }; gargoyleData.hitTest.responsibleFor _ NIL; -- NO LONGER USED gargoyleData.drag.state _ $None; The FSM $MouseUp, $AllUp => { -- CODE REMOVED DUE TO COMPILER BUG endProc[input, gargoyleData, worldPt]; gargoyleData.state _ $None; gargoyleData.mouseMode _ $None; }; Caret Procs The user wishes to place the caret without changing the current selection. Only hot objects trigger alignment lines. Build the alignment lines bag. Common Select Operations. While the caret is being placed, the normal gravity scheme applies. The only feedback is the appearance of text strings and the position of the caret itself. Don't Paint Background. Use User's choice of gravity. Move caret to new position. The caret doesn't know what it is sitting on. For now, this is our way of ensuring that a subsequent $Add operation will start a NEW trajectory. Later, we should remember the chair to be used to construct touching information. Called by DuringSelectFeedback, EndSelect, DuringDrag, DuringRotate, and DuringScale. Moves the caret and records the name of the attractor. Selection Procs Make copies of all selected objects. Then drags the originals. Then pretend that we are doing a normal drag operation. clusGen: ClusterGenerator; FOR clus: Cluster _ GGObjects.NextCluster[clusGen], GGObjects.NextCluster[clusGen] UNTIL clus = NIL DO newClus _ GGObjects.CopyCluster[clus]; GGObjects.AddCluster[scene, newClus, -1]; ENDLOOP; -- no segments, only joints selected Use the StrictDistance gravity function. Find the nearest segment to the cursor. Snap the caret to the nearest endpoint of that segment. Feedback during the operation will be to select hightlight the joint which the caret is snapped to. The final result is to deselect all previous selections and make a single joint selection. If the final position of the cursor is too far from any line, deselect all. Refresh strategy: The currently selected objects will need to be redrawn to get rid of selection feedback. The newly selected joint will have to be drawn. gargoyleData.drag.currentPoint _ worldPt; Build the alignment lines bag. Common Select Operations. Use the StrictDistance gravity function. Find the nearest segment to the cursor. Snap the caret to the nearest endpoint of that segment. Feedback during the operation will be to select hightlight the endjoints of the segment which the caret is snapped to. The final result is to deselect all previous selections and make a single segment selection. If the final position of the cursor is too far from any line, deselect all. Refresh strategy: The currently selected objects will need to be redrawn to get rid of selection feedback. The newly selected joint will have to be drawn. gargoyleData.drag.currentPoint _ worldPt; Build the alignment lines bag. Common Select Operations. Use the StrictDistance gravity function. Find the nearest trajectory to the cursor. Snap the caret to the nearest endjoint of that trajectory. Feedback during the operation will be to select hightlight the endjoints of the trajectory which the caret is snapped to. The final result is to deselect all previous selections and make a single trajectory selection. If the final position of the cursor is too far from any line, deselect all. Refresh strategy: The currently selected objects will need to be redrawn to get rid of selection feedback. The newly selected trajectory will have to be drawn. gargoyleData.drag.currentPoint _ worldPt; Build the alignment lines bag. Common Select Operations. Use the StrictDistance gravity function. Find the nearest top level object to the cursor. Snap the caret to the nearest point of that object. Feedback during the operation is cluster dependent. The final result is to deselect all previous selections and make a single top level object selected. If the final position of the cursor is too far from any object, deselect all. Refresh strategy: The currently selected objects will need to be redrawn to get rid of selection feedback. The newly selected object will have to be drawn with selection feedback. gargoyleData.drag.currentPoint _ worldPt; Build the alignment lines bag. Common Select Operations. Check overlay invariant and grab input focus. Measure caret coordinates. Lift caret. Store the whole scene in the background plane. While a joint, segment, traj, or top level object is being selected, gravity is forced to be StrictDistance. The object bag should consist only of trajectories and clusters. The caret is moved to the segment endpoint of the nearest segment or traj as appropriate or tracks the cursor if none are nearby. Feedback is in the form of highlighted joints. Paint Background. Use StrictDistance gravity except for SelectJoint. Put Caret on a joint (if any). Deselect all. GGRefresh.MoveOverlayToBackground[gargoyleData]; GGRefresh.MoveToOverlay[gargoyleData.caret, gargoyleData]; -- caret back on overlay Find out which joint is being selected and highlight it. GGRefresh.MoveJointsToOverlay[feature.tseq.traj, gargoyleData]; GGRefresh.MoveJointsToOverlay[feature.tseq.traj, gargoyleData]; GGRefresh.MoveJointsToOverlay[feature.tseq.traj, gargoyleData]; There shouldn't be any alignment lines in the object bag. Find out which segment is being selected and highlight it. GGRefresh.MoveJointsToOverlay[feature.tseq.traj, gargoyleData]; There shouldn't be any alignment lines in the object bag. Find out which traj is being selected and highlight it. GGRefresh.MoveJointsToOverlay[feature.tseq.traj, gargoyleData]; There shouldn't be any alignment lines in the object bag. Find out which traj is being selected and highlight it. GGRefresh.MoveJointsToOverlay[feature.tseq.traj, gargoyleData]; There shouldn't be any alignment lines in the object bag. Don't Paint Background. Deselect all. Dispatch to the proper EndSelect handler for final selection. Prepare for a subsequent Add operation. Perform the final selection. Prepare for a subsequent Extend operation. GGSelect.SelectEntity[feature.cluster, gargoyleData, normal]; Prepare for a subsequent Add operation. Prepare for a subsequent extend operation. Prepare for a subsequent Add or extend operation. Perform the selection. Motion Procs Use gravity to find a nearby point on the selected objects. Move the selected objects so that this point coincides with the cursor. Then we can use the same gravity paradigm that we used for Select: The caret is drawn toward interesting lines. We should also keep track of which object point the caret is on so we can create fragile touching constraints. The caret and the selected objects will be on the overlay plane. gargoyleData.drag.state _ $Drag; Build the alignment lines bag. Time to move the nearby object (if any) to the cursor. Build the alignment lines bag. We initialize the transform to the identity. Later, it will describe how far the mappoint has moved from the startpoint. We had to wait until AFTER the duringBag was built before calling StoreBackground restore the OLD caret bitmap and the selected items bitmap We keep track of the total vector of motion since the drag began (i.e. the difference between the initial mouse position and the current mouse position. We transform all of the dragged objects before redrawing them. This style should be better than changing the actual data since there is less accumulated error, and the undo transformation is easily derived. IF gargoyleData.hitTest.linesAlwaysOn.state = off THEN -- Bier February 3, 1986 GGGravity.AdjustVisibilityPoint[worldPt, gargoyleData.hitTest.tolerance, environ]; The dragging is done. Update all of the drag entities with the total drag vector and repaint the entire scene. Use gravity to find a nearby point on the selected objects. Move the selected objects so that this point coincides with the cursor. Then we can use the same gravity paradigm that we used for Select: The caret is drawn toward interesting lines. We should also keep track of which object point the caret is on so we can create fragile touching constraints. The caret and the selected objects will be on the overlay plane. Build the alignment lines bag. Time to move the nearby object (if any) to the cursor. Build the alignment lines bag. We initialize the transform to the identity. Later, it will describe how far the mappoint has moved from the startpoint. We had to wait until AFTER the duringBag was built before calling StoreBackground restore the OLD caret bitmap and the selected items bitmap We keep track of the total vector of motion since the drag began (i.e. the difference between the initial mouse position and the current mouse position. We transform all of the dragged objects before redrawing them. This style should be better than changing the actual data since there is less accumulated error, and the undo transformation is easily derived. IF gargoyleData.hitTest.linesAlwaysOn.state = off THEN -- Bier February 3, 1986 7:59:50 pm PST GGGravity.AdjustVisibilityPoint[worldPt, gargoyleData.hitTest.tolerance, environ]; The rotating is done. Update all of the drag entities with the full rotation and repaint the entire scene. Use gravity to find a nearby point on the selected objects. Move the selected objects so that this point coincides with the cursor. Then we can use the same gravity paradigm that we used for Select: The caret is drawn toward interesting lines. We should also keep track of which object point the caret is on so we can create fragile touching constraints. The caret and the selected objects will be on the overlay plane. Build the alignment lines bag. Time to move the nearby object (if any) to the cursor. Build the alignment lines bag. We initialize the transform to the identity. Later, it will describe how far the mappoint has moved from the startpoint. We had to wait until AFTER the duringBag was built before calling StoreBackground restore the OLD caret bitmap and the selected items bitmap We keep track of the total vector of motion since the drag began (i.e. the difference between the initial mouse position and the current mouse position. We transform all of the dragged objects before redrawing them. This style should be better than changing the actual data since there is less accumulated error, and the undo transformation is easily derived. ratioX, ratioY: REAL; IF gargoyleData.hitTest.linesAlwaysOn.state = off THEN -- Bier February 3, 1986 8:00:00 pm PST GGGravity.AdjustVisibilityPoint[worldPt, gargoyleData.hitTest.tolerance, environ]; IF ABS[originalVector[1]] > epsilon THEN ratioX _ ABS[newVector[1]/originalVector[1]] ELSE ratioX _ 1.0; IF ABS[originalVector[2]] > epsilon THEN ratioY _ ABS[newVector[2]/originalVector[2]] ELSE ratioY _ 1.0; IF ratioX > epsilon AND ratioY > epsilon THEN gargoyleData.drag.transform _ GGTransform.ScaleUnevenAboutPoint[anchorPoint, ratioX, ratioY]; The scaling is done. Update all of the drag entities with the full rotation and repaint the entire scene. Addition and Extension Procs new _ FALSE; The trajectory is about to change size. Any sequences stored on the selection lists are obsolete. Update them. The trajectory is about to change size. Any sequences stored on the selection lists are obsolete. Update them. new _ TRUE; If the caret is on the end of a trajectory, then extend the trajectory from that end. Otherwise, create a new simple outline with a segment from the caret to the current mouse position. In either case, move the trajectory to the overlay plane so we can rubberband the new line. This is a little strange, since the new line is not selected. The caret sticks to the new endpoint. Add a new segment. Choose appropriate alignment lines. GGGravity.AdjustVisibilityPoint[worldPt, gargoyleData.hitTest.tolerance, NARROW[gargoyleData.hitTest.activeObjectBag]]; Move the trajectory's outline to the overlay plane. Initialize the transformation and remember the starting position. gargoyleData.drag.state _ $Add; As long as we are making the user pay for the alignment lines, we might as well draw them once. Map the endpoint and the caret and reposition them. GGGravity.AdjustVisibilityPoint[worldPt, gargoyleData.hitTest.tolerance, activeObjects]; The dragging is done. Update the endpoint with the current transform. gargoyleData.drag.newOutline _ FALSE; -- no longer used do no painting yet Choose appropriate alignment lines. Move new box to overlay Initialize the transformation and remember the starting position. gargoyleData.drag.state _ $Box; not sure this does the right thing with the caret, but it's close The dragging is done. Update the box cluster with the current transform. Unfortunately, we've lost track of the box cluster. Find it for now by looking on the overlay plane for the first (hopefully ONLY) box cluster on it. What is going on: basically, this routine is called instead of EndBox. Sooo, we find the box that is being added and delete it. Find it for now by looking on the overlay plane for the first (hopefully ONLY) box cluster on it. Nearest Procs traj _ IF feature = NIL THEN NIL ELSE feature.tseq.traj; The following doesn't seem to take care of all the cases properly ??. KAP. March 24, 1986 traj _ IF feature = NIL THEN NIL ELSE feature.tseq.traj; IF feature # NIL THEN segNum _ feature.segNum; The following doesn't seem to take care of all the cases properly ??. KAP. March 24, 1986 NearestCluster: PROC [scene: Scene, worldPt: Point, gargoyleData: GargoyleData] RETURNS [cluster: Cluster] = { feature: FeatureData; resultPoint: Point; activeObjects: ObjectBag; sensitiveObjects: TriggerBag; GGAlign.SetBagsForAction[gargoyleData, $SelectCluster]; activeObjects _ NARROW[gargoyleData.hitTest.activeObjectBag]; sensitiveObjects _ NARROW[gargoyleData.hitTest.sensitiveTriggerBag]; [resultPoint, feature] _ GGGravity.StrictDistance[worldPt, gargoyleData.hitTest.criticalR, activeObjects, sensitiveObjects]; cluster _ IF feature = NIL THEN NIL ELSE feature.cluster; }; Touching Procs IF GGObjects.IsEndJoint[feature.tseq.traj, feature.jointNum] THEN { -- an end joint GGError.Append["End Joint selected", oneLiner]; } ELSE { -- a middle joint After this time, a Splice will create a new trajectory. GGError.Append["Middle Joint selected", oneLiner]; }; Feature description Procs Pier, November 27, 1985 2:44:20 pm PST changes to: ReportFeature was sending CR to typescript for null reports. Not sure what was intended, so took it out. -- The idea was to clear the MessageWindow when the caret wasn't being attracted to anything anymore. Pier, January 7, 1986 8:45:29 pm PST changes to: EndAdd, EndSelectPoint, EndDrag, EndRotate, EndScale: implement MergeAfterDragging Pier, January 20, 1986 11:20:08 am PST changes to many to implement faster painting. Added $PaintCopySelectedRegion Ê0Ò˜Icode™K™4™…K™$K™#—K™šÏk ˜ Kšœ±œ˜ÉK˜—šœœ˜Kšœöœ˜‡Kšœ˜—K˜Kšœ œ˜'Kšœœ˜%Kšœ œ˜%Kšœœ˜/Kšœœ!˜7Kšœœ"˜9Kšœœ ˜5Kšœ œ˜*Kšœœ˜!Kšœœ!˜3Kšœ œ˜&Kšœ œ˜&Kšœ œ˜%Kšœœ˜!Kšœœ˜!Kšœ œ˜%Kšœ œ˜'Kšœ œ˜+Kšœ œ˜)Kšœœ˜6Kšœœ˜Kšœ œ˜%Kšœœ˜.Kšœ œ˜&Kšœœ˜#KšÏn œœž œ˜)Kšž œœœ œœœœ.™\Kšž œœž œ˜)Kšž œœœ œœœœ.œ œ™{K˜Kšœœœœ˜$Kšœœœœ˜(Kš œ œœ œœ˜/˜K˜—š ž œœ œœœœ1˜XK™Kš œœœœœ*˜MKšœ-Ïc&˜Sšœœœœœ!œœ™MJšœ9Ÿ™WJšœ™—šœœœœœ!œœ˜Mšœ œ˜Kšœ\˜\Kšœ<˜C—Jšœ˜—KšœJŸœ˜bKšœ ˜ Kšœ'œ˜.Kšœ3Ÿ)˜\K˜K˜—š žœœ œœœœ1˜WKšœ˜Kšœ ˜ Kšœ'œ˜-K˜K˜—š žœœ œœœœ1˜WKšœ˜Kšœ ˜ Kšœ'œ˜-K˜K˜—š ž œœ œœœœ1™XK™!Kšœ œ™Kšœ-™-KšœB™BK™K™K™—š ž œœ œœœœ1™YK™K™—š žœœ œœœœ1™VK™K˜—šžœœ!˜8Kšœ&œ™˜>Kš  ™ —Kšœ  œ™0Kšœ   œ˜+šœ œœŸ-˜EK˜—šœ˜šœ˜)Kšœg˜gKšœk˜kKšœe˜eKšœm˜mKšœœ˜—K˜—Kšœ   œ%Ÿ™TKšœžœŸ˜WK˜K˜—šžœœG˜fK™8šœ˜šœ ˜ Kšœj˜jKšœ8˜8Kšœ?™?K˜—˜Kšœz˜zKšœ?˜?Kšœ?™?K˜—šœ Ÿ$˜1Kšœ˜Kšœ œ˜Kšœ*˜*KšœP˜PKšœ8˜8Kšœ?™?K˜—šœ ˜ KšœN˜NKšœE˜EKšœ˜—šœEœ˜KK™9—Kšœœ˜#—KšœŸ ž˜&K˜—šžœœG˜hK™:šœ˜šœ Ÿ?˜JK˜—šœŸ1˜LKšœŽ˜ŽKšœ3˜3Kšœ?™?K˜—šœ ˜ KšœP˜PKšœE˜EKšœ˜—šœEœ˜KK™9—Kšœœ˜#—KšœŸ%˜(K˜—šžœœG˜eK™7šœ˜šœ"Ÿ7˜YKšœ=˜=Kšœ3˜3Kšœ?™?K˜—šœ ˜ KšœM˜MKšœE˜EKšœ˜—šœEœ˜KK™9—Kšœœ˜#—Kšœ˜K˜—šžœœG˜iK™7šœ˜šœ!˜!KšœG˜GKšœ?™?K˜—šœ ˜ KšœQ˜QKšœE˜EK˜—šœEœ˜KK™9—Kšœœ˜#—Kšœ˜K˜—šž œœ˜Kšœ˜Kšœ˜K˜˜Kš ™—Kšœœ'˜=Kšœœ+˜Dšœ'˜-Kšœ#  œg˜•—šœ$ œK˜Kš  ™ —Kšœ  œ˜0šœ   œ˜+Kš =™=—šœ˜)Kšœ<˜K˜Kšœ œ˜)Kšœ<˜<š œ œœDœ œ˜lšœœ˜šœ˜KšœA˜AKšœ  œ˜,K˜—˜š œœœ%œ œ˜KKšœB˜BKšœ  œ˜-—Kšœ˜K˜—šœ˜KšœJ˜JK˜—Kšœœ˜#——Kšœ˜K˜K˜—K˜K™ šž œœ˜K™öK™nK™@Kšœ˜K˜Kšœ˜K˜K˜Kš œœ&œœŸ˜NKšœ7˜7Kšœ$˜$KšœŸ0˜Nšœ-œ˜5Kšœ8˜8K˜Kšœœ˜K˜—K–![caret: GGInterfaceTypes.Caret]šœ]ŸD˜¡Kšœ ™ Kšœ)˜)Kšœ<Ÿ˜Ošœ;Ÿ˜YK™Kš ™—šœ5˜5K™6—Kšœœ'˜=Kšœœ+˜DKšœ|˜|Kšœ œœ'˜˜>K™yK™—KšœBŸ™QK™:KšœP˜PK˜2Kšœ6Ÿ3˜iKšœ œ˜'K˜K˜—šž œœ˜ K™éK˜Kšœ˜Kšœ˜Kšœœ'˜HKšœœ+˜PKšœ)˜)Kšœ5Ÿ˜KKšœ  œY˜|Kš œ"˜3Kšœ0œl™¢KšœG˜GKšœg˜gKšœ2˜2K˜K˜—šžœœ˜K™oKšœ&˜&KšœB˜BKšœ  œ˜0Kšœ4˜4KšœœŸ˜VK˜—K˜šž œœ˜!K™öK™nK™@Kšœ˜K˜Kšœ˜K˜K˜Kš œœ   œœœŸ˜NKšœ7˜7Kšœ$˜$KšœŸ0˜Nšœ-œ˜5Kšœ:˜:K˜Kšœœ˜K˜—šœœ%œ˜1Kšœ=˜=K˜Kšœœ˜K˜—K–![caret: GGInterfaceTypes.Caret]šœ]ŸD˜¡Kšœ)˜)Kšœ<Ÿ˜Ošœ;Ÿ˜YK™Kš ™—šœ5˜5K™K™6—Kšœœ'˜=Kšœœ+˜DKšœ|˜|Kšœ œœ'˜˜>K™yK™—KšœBŸ™QK™:KšœP˜PK˜2Kšœ6Ÿ3˜iKšœ œ˜)K˜K˜—šž œœ˜"K™éK˜"Kšœ˜Kšœ˜Kšœœ'˜HKšœœ+˜PKšœ œ˜K˜Kšœ4˜4Kšœ3˜3Kšœ)˜)Kšœ  œY˜|Kšœ3˜3Kšœ0œ{™±KšœI˜IKšœ0˜0KšœE˜EKšœ* œ˜QKšœ2˜2K˜K˜—šž œœ˜K™kKšœ&˜&Kšœ  œ˜0Kšœ4˜4KšœœŸ˜VK˜K˜—K˜šž œœ˜ K™öK™nK™@Kšœ˜K˜Kšœ˜J˜J˜K˜Kš œœ   œœœŸ˜NKšœ7˜7Kšœ$˜$KšœŸ0˜Nšœ-œ˜5Kšœ9˜9K˜Kšœœ˜K˜—šœœ%œ˜1Kšœ<˜˜>K™yK™—KšœBŸ™QK™:KšœP˜PK˜2Kšœ6Ÿ3˜iKšœ œ˜(K˜K˜—šž œœ˜!K™éKšœ œ ˜K˜"Kšœ˜Kšœ˜Kšœœ'˜HKšœœ+˜PKšœœ˜ Kšœœ™K˜Kšœ4˜4Kšœ3˜3Kšœ)˜)Kšœ  œY˜|Kšœ3˜3Kšœ0œ{™±KšœI˜IKšœ0˜0KšœI˜IKšœ* œ˜NKšœœœ œ ™UKšœ™Kšœœœ œ ™UKšœ™šœœ™-Kšœ* œ™]—Kšœ2˜2K˜K˜—šžœœ˜K™jKšœ&˜&Kšœ  œ˜0Kšœ4˜4KšœœŸ˜VK˜K˜—K™K™š žœœœ<œœ˜‚Kšœ"˜"K˜Kšœ œ˜K˜Kšœ%˜%šœœŸ!˜GKšœœ™ KšœŸœŸœ Ÿœ˜=šœœŸ'˜>Kšœ2˜2Kšœ6˜6Kšœœ œœ˜šœ œœ˜4K™p—Kšœ,œ˜1Kšœ0˜0K˜—šœœ$œŸ(˜ZKšœ2˜2Kšœ6˜6Kšœœ œœ˜šœ œœ˜4K™p—Kšœ,œ˜1Kšœ;˜;K˜—Kšœœ˜ K˜—šœŸW˜^K˜Kšœœ™ Kšœ2˜2Kšœ(˜(Kšœ6˜6Kšœœ œœ˜Kšœ+˜+Kšœ,˜,KšœK˜KKšœ,œ˜1Kšœ0˜0K˜—K˜K˜—šžœœ˜K™ýKšœ˜K˜Kšœ˜Kšœ œ˜Kšœ7˜7Kšœ$˜$KšœŸ0˜NKšœ)˜)Kš œœ&œœŸ˜N–![caret: GGInterfaceTypes.Caret]šœ]ŸD˜¡K™—Kšœ œ,˜Yš œœ œœœ˜"Kš #™#—K˜Kšœ-˜-K˜šœIœ(™wKšœ3™3—Kš œŸœŸœŸœ Ÿœ)˜JKšœM˜MKšœ2˜2Kšœ2Ÿ!˜Sšœ<Ÿ˜OK™A—Kšœ'˜'Kšœ>˜>Kšœ™Kšœ6Ÿ'˜]šœ œŸ˜@K™_—K˜K˜—šž œœ˜K™3K˜Kšœ˜Kšœ˜Kšœœ'˜HKšœœ+˜PKšœ5Ÿ˜KKšœ)˜)Kšœ|˜|Kšœ3˜3KšœX™XKšœG˜GKšœg˜gKšœ2˜2K˜K˜—šžœœ˜K™FKšœ˜Kšœ2˜2KšœA˜AKšœB˜BKšœ  œ˜0Kšœ<˜˜>Kšœ™Kšœ6Ÿ!˜WKšœ œ˜&K˜K˜—šž œœ ˜J™AJ˜(K˜K˜—šžœœ˜K™ßKšœ˜Kšœ œœ˜KšœJ˜JšœPœœ˜dšœœ˜šœœ œ˜Kšœ9Ÿ˜NKšœ œ˜K˜K˜—KšœœŸ ˜K˜—Kšœ˜—Kšœ  œ˜0Kšœ9Ÿ*˜cKšœ/˜/KšœœŸ˜VK˜K˜—šžœœ!˜6Kšœá™áKšœRŸ˜eKšœ œœ˜KšœJ˜JšœPœœ˜dšœœ˜Kšœ2˜2Kšœ4Ÿ˜RKšœ˜K˜—Kšœ˜—Kšœ2˜2Kšœ/˜/K˜—K˜K™ šž œœ(œ œ˜UKšœ œ˜ Kšœ ˜ K˜Kšœœ˜ Kšœ˜šœ˜šœ ˜ Kšœ˜K˜—šœ˜Kšœ9˜9Kšœ3˜3Kšœ,˜,Kšœ+˜+Kšœ+˜+Kšœ œ˜*Kšœ˜K˜—Kšœœ˜#—KšœŸ˜K˜—šžœœ<œ˜kKšœ˜Kšœ˜J˜J˜Kšœ0˜0Jšœœ'˜=Jšœœ+˜DKšœ|˜|Kš œœ œœœœ™8K™YKšœœ œœœœœ˜OK˜K˜—šžœœ<œœ˜uKšœ˜Kšœ˜J˜J˜K˜Kšœ0˜0Jšœœ'˜=Jšœœ+˜DKšœ|˜|Kš œœ œœœœ™8Kšœ œœ™.K™YKšœ œœœœœœœ$˜lK˜K˜—šžœœ<œ™nKšœ™Kšœ™J™J™Kšœ7™7Jšœœ'™=Jšœœ+™DKšœ|™|Kš œ œ œœœœ™9K™K™—š žœœ<œ œœ˜pKšœ˜Kšœ˜J˜J˜Kšœ8˜8Jšœœ'˜=Jšœœ+˜DKšœ|˜|Kšœ œœ ˜$šœ˜šœ˜šœ!˜!Kšœ$˜$K˜—˜ Kšœ˜K˜—Kšœœ˜—K˜—K˜K˜—K˜K™šžœœ7˜UK˜Kšœ˜šœ˜šœ ˜ Kšœ=˜=šœœœ˜Kšœa˜aKšœ8˜8Kšœ;œ Ÿ˜pKšœG˜Gšœ;œŸ™TKšœ/™/K™—šœŸ™Kšœ7™7Kšœ2™2K™—K˜—šœ˜Kšœ3˜3Kšœœ˜K˜Kšœ'˜'šœVœ œ˜pšœ˜%˜ KšœA˜AKšœ?˜?Kšœ8˜8K˜—˜ Kšœ?˜?Kšœ=˜=Kšœ6˜6K˜—Kšœ˜——Kšœ˜Kšœ7˜7K˜—K˜—Kšœœ˜#—K˜K˜—šžœœœœ˜PKšœœ&˜;K˜K˜—K˜K™šžœœ4œ œ˜fKšœ œœœ ˜(šœ˜šœ˜Kšœ˜Kšœ'˜'šœ ˜ Kšœœ0˜9Kšœ˜—Kšœ˜Kšœ'˜'Kšœ!˜!Kšœ'˜'Kšœ'˜'šœ˜Kšœœ˜šœ œœœ:˜gKšœ ˜—šœ œœœ:˜hKšœ ˜—KšœœK˜TK˜—Kšœœ<˜QKšœœ˜—K˜—K˜K˜—šžœœ4œ œ˜lKšœ œœœ ˜(šœ˜šœ˜Kšœ˜Kšœ˜Kšœ'˜'Kšœ!˜!Kšœ'˜'Kšœ'˜'šœ˜Kšœœ˜šœ œœœ:˜gKšœ ˜—šœ œœœ:˜hKšœ ˜—KšœœK˜TK˜—Kšœœ<˜QKšœœ˜—K˜—K˜K˜—Kšœ˜™&Kšœ Ïr œÁ™Ú—™$Kšœ £4œ™^—™&KšœL™L—K™—…—β>$