DIRECTORY Ascii, Atom, AtomButtonsTypes, BasicTime, CodeTimer, ColorTool, Convert, Cubic2, CubicSplines, Feedback, GGAlign, GGBasicTypes, GGBoundBox, GGCaret, GGControlPanelTypes, GGDragTypes, GGEvent, GGEventExtras, GGFileOps, GGHistory, GGHistoryTypes, GGInterfaceTypes, GGModelTypes, GGMouseEvent, GGOutline, GGParent, GGParseIn, GGProps, GGRefresh, GGRefreshTypes, GGScene, GGSceneType, GGSegment, GGSegmentTypes, GGSelect, GGSequence, GGSlice, GGSliceOps, GGState, GGTraj, GGTransform, GGUserInput, GGUserProfile, GGUtility, GGWindow, Imager, ImagerInterpress, ImagerTransformation, IO, Prop, Real, Rope, SF, SlackProcess, Vectors2d; GGEventImplC: CEDAR PROGRAM IMPORTS Atom, BasicTime, CodeTimer, ColorTool, Convert, Feedback, GGAlign, GGBoundBox, GGCaret, GGEvent, GGEventExtras, GGFileOps, GGHistory, GGMouseEvent, GGOutline, GGParent, GGParseIn, GGProps, GGRefresh, GGScene, GGSegment, GGSelect, GGSequence, GGSlice, GGSliceOps, GGState, GGTraj, GGTransform, GGUserInput, GGUserProfile, GGUtility, GGWindow, Imager, ImagerInterpress, ImagerTransformation, IO, Prop, SlackProcess, Rope, Vectors2d EXPORTS GGEvent, GGModelTypes, GGInterfaceTypes = BEGIN ControlsObj: PUBLIC TYPE = GGControlPanelTypes.ControlsObj; -- exported to GGInterfaceTypes DragDataObj: PUBLIC TYPE = GGDragTypes.DragDataObj; -- exported to GGInterfaceTypes BitVector: TYPE = GGModelTypes.BitVector; BoundBox: TYPE = GGModelTypes.BoundBox; Caret: TYPE = GGInterfaceTypes.Caret; Color: TYPE = Imager.Color; ControlPointGenerator: TYPE = GGModelTypes.ControlPointGenerator; FeatureCycler: TYPE = GGInterfaceTypes.FeatureCycler; FeatureData: TYPE = GGModelTypes.FeatureData; GGData: TYPE = GGInterfaceTypes.GGData; HistoryEvent: TYPE = GGHistoryTypes.HistoryEvent; Joint: TYPE = GGSegmentTypes.Joint; JointGenerator: TYPE = GGModelTypes.JointGenerator; Point: TYPE = GGBasicTypes.Point; RefreshDataObj: PUBLIC TYPE = GGRefreshTypes.RefreshDataObj; ROPE: TYPE = Rope.ROPE; Scene: TYPE = GGModelTypes.Scene; SceneObj: PUBLIC TYPE = GGSceneType.SceneObj; -- export of opaque type SceneRef: TYPE = REF SceneObj; Segment: TYPE = GGSegmentTypes.Segment; SegmentGenerator: TYPE = GGModelTypes.SegmentGenerator; Sequence: TYPE = GGModelTypes.Sequence; Slice: TYPE = GGModelTypes.Slice; SliceDescriptor: TYPE = GGModelTypes.SliceDescriptor; SliceDescriptorWalkProc: TYPE = GGModelTypes.SliceDescriptorWalkProc; SliceDescriptorGenerator: TYPE = GGModelTypes.SliceDescriptorGenerator; SliceGenerator: TYPE = GGModelTypes.SliceGenerator; SliceParts: TYPE = GGModelTypes.SliceParts; Traj: TYPE = GGModelTypes.Traj; TrajData: TYPE = GGModelTypes.TrajData; TrajEnd: TYPE = GGModelTypes.TrajEnd; TrajPartType: TYPE = GGModelTypes.TrajPartType; TrajParts: TYPE = GGModelTypes.TrajParts; UserInputProc: TYPE = GGEvent.UserInputProc; Vector: TYPE = GGBasicTypes.Vector; WalkProc: TYPE = GGModelTypes.WalkProc; reallyBigReal: REAL = 1.0e37; PaintActionArea: PUBLIC UserInputProc = { GGState.PaintActionArea[ggData]; }; UpdateCursor: UserInputProc = { Feedback.Append[ggData.router, oneLiner, $Error, "GGEventImplC.UpdateCursor was called. Please notify a Gargoyle implementor."]; }; ToggleActive: UserInputProc = { GGState.SetActive[ggData, NOT GGState.GetActive[ggData]]; }; SetActive: UserInputProc = { IF event.rest # NIL THEN WITH event.rest.first SELECT FROM r: ROPE => { b: BOOL _ Convert.BoolFromRope[r]; GGState.SetActive[ggData, b]; }; ENDCASE => Feedback.PutF[ggData.router, oneLiner, $Complaint, "SetActive got unexpected value %g", [refAny[event.rest.first]] ]; }; TogglePalette: UserInputProc = { GGState.SetPalette[ggData, NOT GGState.GetPalette[ggData]]; }; SaveSelections: UserInputProc = { ggData.behavior.savedSelections _ GGUtility.CopySliceDescriptorList[GGSelect.ListSelected[ggData.scene, normal]]; }; RestoreSelections: UserInputProc = { GGRefresh.SetStartBox[ggData: ggData, dragInProgress: FALSE, selectedCPs: TRUE]; GGSelect.DeselectAll[ggData.scene, normal]; GGSelect.SetSelected[ggData.scene, normal, ggData.behavior.savedSelections]; GGWindow.RestoreScreenAndInvariants[paintAction: $SelectionChanged, ggData: ggData, remake: none, edited: FALSE, okToSkipCapture: TRUE]; }; SelectButton: UserInputProc = { sliceD: SliceDescriptor _ NARROW[event.rest.first]; GGSelect.SelectSlice[sliceD, ggData.scene, normal]; }; FeedbackOn: UserInputProc = { IF ggData.router#NIL THEN Feedback.SetRouterOn[ggData.router, TRUE]; }; FeedbackOff: UserInputProc = { IF ggData.router#NIL THEN Feedback.SetRouterOn[ggData.router, FALSE]; }; BeginButton: UserInputProc = { sliceD: SliceDescriptor _ NARROW[event.rest.first]; ggData.behavior.savedSelections _ GGUtility.CopySliceDescriptorList[GGSelect.ListSelected[ggData.scene, normal]]; GGSelect.DeselectAll[ggData.scene, normal]; GGSelect.SelectSlice[sliceD, ggData.scene, normal]; IF ggData.router#NIL THEN { Feedback.SetRouterOn[ggData.router, FALSE]; }; DisableRefresh[ggData, event]; }; EndButton: UserInputProc = { GGSelect.DeselectAll[ggData.scene, normal]; GGSelect.SetSelected[ggData.scene, normal, ggData.behavior.savedSelections]; IF ggData.router#NIL THEN { Feedback.SetRouterOn[ggData.router, TRUE]; }; EnableRefresh[ggData, event]; }; ButtonFillColorFromIntensity: UserInputProc = { buttonEvent: LIST OF REF ANY _ LIST[event.first, event.rest.rest.first]; CodeTimer.StartInt[$ButtonFillColorFromIntensity, $Gargoyle]; BeginButton[ggData, buttonEvent]; GGEventExtras.SetFillColorFromIntensity[ggData, NARROW[event.rest.first, REF REAL]^]; EndButton[ggData, buttonEvent]; CodeTimer.StopInt[$ButtonFillColorFromIntensity, $Gargoyle]; }; ButtonStrokeColorFromIntensity: UserInputProc = { buttonEvent: LIST OF REF ANY _ LIST[event.first, event.rest.rest.first]; CodeTimer.StartInt[$ButtonStrokeColorFromIntensity, $Gargoyle]; BeginButton[ggData, buttonEvent]; GGEventExtras.SetStrokeColorFromIntensity[ggData, NARROW[event.rest.first, REF REAL]^]; EndButton[ggData, buttonEvent]; CodeTimer.StopInt[$ButtonStrokeColorFromIntensity, $Gargoyle]; }; SetProp: UserInputProc = { SetPropOnSlice: PROC [sliceD: SliceDescriptor] RETURNS [done: BOOL _ FALSE] = { val _ IF valRope=NIL THEN NIL ELSE GGProps.FromRope[key, valRope]; -- must do this for every slice in case, for instance, EmbeddedButtons wants to give each button a unique name GGProps.Put[sliceD.slice, sliceD.parts, key, val]; }; rope: Rope.ROPE _ NARROW[event.rest.first]; s: IO.STREAM _ IO.RIS[rope]; keyRope, valRope: Rope.ROPE; val: REF; key: ATOM; keyRope _ GGParseIn.ReadWWord[s]; IF keyRope = NIL THEN { Feedback.Append[ggData.router, oneLiner, $Complaint, "SetProp failed: Please select a key name"]; RETURN; }; GGParseIn.ReadBlank[s]; valRope _ IO.GetRope[s]; key _ Atom.MakeAtom[keyRope]; [] _ GGScene.WalkSelectedSlices[ggData.scene, leaf, SetPropOnSlice, normal]; GGWindow.RestoreScreenAndInvariants[paintAction: $None, ggData: ggData, remake: none, edited: TRUE, okToSkipCapture: TRUE]; }; GetSelectedVal: PROC [key: ATOM, ggData: GGData, opName: Rope.ROPE] RETURNS [val: REF, success: BOOL _ TRUE, sliceD: SliceDescriptor _ NIL] = { aborted: BOOL _ FALSE; scene: Scene _ ggData.scene; CheckVal: PROC [thisD: SliceDescriptor] RETURNS [done: BOOL _ FALSE] = { thisVal: REF; IF sliceD = NIL THEN { sliceD _ thisD; [val, success] _ GGProps.Get[thisD.slice, thisD.parts, key]; done _ NOT success; } ELSE { [thisVal, success] _ GGProps.Get[thisD.slice, thisD.parts, key]; IF NOT success OR thisVal # val THEN done _ TRUE }; }; aborted _ GGScene.WalkSelectedSlices[scene, first, CheckVal, normal]; IF aborted THEN { success _ FALSE; Feedback.PutF[ggData.router, oneLiner, $Complaint, "%g failed: there is more than one value for this key", [rope[opName]] ]; } ELSE IF sliceD = NIL THEN { success _ FALSE; Feedback.PutF[ggData.router, oneLiner, $Complaint, "%g failed: no objects are selected", [rope[opName]] ]; }; }; GetProp: UserInputProc = { keyRope: Rope.ROPE _ NARROW[event.rest.first]; key: ATOM _ Atom.MakeAtom[keyRope]; val: REF; success: BOOL _ FALSE; sliceD: SliceDescriptor; [val, success, sliceD] _ GetSelectedVal[key, ggData, "GetProp"]; IF NOT success THEN RETURN; IF val#NIL THEN { valRope: Rope.ROPE; valRope _ GGProps.ToRope[key, val].r; IF valRope = NIL THEN Feedback.PutF[ggData.router, oneLiner, $Show, "%g ", [rope[keyRope]] ] ELSE Feedback.PutF[ggData.router, oneLiner, $Show, "%g %g", [rope[keyRope]], [rope[valRope]]] } ELSE Feedback.PutF[ggData.router, oneLiner, $Show, "No value for key %g.", [rope[keyRope]]]; }; GetSelectedValExternal: PROC [key: ATOM, ggData: GGData, opName: Rope.ROPE] RETURNS [val: REF, success: BOOL _ TRUE, error: Rope.ROPE _ NIL] = { aborted: BOOL _ FALSE; scene: Scene _ ggData.scene; sliceD: SliceDescriptor; CheckVal: PROC [thisD: SliceDescriptor] RETURNS [done: BOOL _ FALSE] = { thisVal: REF; IF sliceD = NIL THEN { sliceD _ thisD; [val, success] _ GGProps.Get[thisD.slice, thisD.parts, key]; done _ NOT success; } ELSE { [thisVal, success] _ GGProps.Get[thisD.slice, thisD.parts, key]; IF NOT success OR thisVal # val THEN done _ TRUE }; }; aborted _ GGScene.WalkSelectedSlices[scene, first, CheckVal, normal]; IF aborted THEN { success _ FALSE; error _ IO.PutFR["%g failed: there is more than one value for this key", [rope[opName]] ]; Feedback.PutF[ggData.router, oneLiner, $Complaint, "%g failed: there is more than one value for this key", [rope[opName]] ]; } ELSE IF sliceD = NIL THEN { success _ FALSE; error _ IO.PutFR["%g failed: no objects are targeted", [rope[opName]] ]; Feedback.PutF[ggData.router, oneLiner, $Complaint, "%g failed: no objects are selected", [rope[opName]] ]; }; }; GetPropExternal: UserInputProc = { returnList: LIST OF Rope.ROPE; errorRope, valRope: Rope.ROPE; extRef: GGUserInput.External _ NARROW[event.rest.first]; key: ATOM _ NARROW[extRef.results]; keyRope: Rope.ROPE _ Atom.GetPName[key]; val: REF; success: BOOL _ FALSE; [val, success, errorRope] _ GetSelectedValExternal[key, ggData, "GetProp"]; IF success THEN { IF val#NIL THEN { valRope _ GGProps.ToRope[key, val].r; IF valRope = NIL THEN { Feedback.PutF[ggData.router, oneLiner, $Show, "%g ", [rope[keyRope]] ]; errorRope _ IO.PutFR["%g ", [rope[keyRope]] ]; } ELSE Feedback.PutF[ggData.router, oneLiner, $Show, "%g %g", [rope[keyRope]], [rope[valRope]]] } ELSE { Feedback.PutF[ggData.router, oneLiner, $Show, "No value for key %g.", [rope[keyRope]]]; valRope _ IO.PutFR["No value for key %g.", [rope[keyRope]]]; }; }; returnList _ LIST[valRope, errorRope]; extRef.results _ returnList; extRef.valid _ TRUE; GGUserInput.BroadcastExternal[extRef]; }; RemoveProp: UserInputProc = { RemPropOnSlice: PROC [sliceD: SliceDescriptor] RETURNS [done: BOOL _ FALSE] = { [] _ GGProps.Rem[sliceD.slice, sliceD.parts, key]; }; keyRope: Rope.ROPE _ NARROW[event.rest.first]; key: ATOM _ Atom.MakeAtom[keyRope]; IF keyRope = NIL THEN { Feedback.Append[ggData.router, oneLiner, $Complaint, "RemoveProp failed: Please select a key name"]; RETURN; }; [] _ GGScene.WalkSelectedSlices[ggData.scene, first, RemPropOnSlice, normal]; Feedback.PutF[ggData.router, oneLiner, $Feedback, "Key %g removed from selected slices.", [rope[keyRope]] ]; }; ListProps: UserInputProc = { success: BOOL _ FALSE; propsList: Prop.PropList; first: BOOL _ TRUE; PrintKey: PROC [key: REF, val: REF] RETURNS [quit: BOOL _ FALSE] = { atom: ATOM _ NARROW[key]; keyRope: Rope.ROPE _ Atom.GetPName[atom]; IF first THEN { Feedback.PutF[ggData.router, begin, $Show, "%g", [rope[keyRope]] ]; first _ FALSE; } ELSE Feedback.PutF[ggData.router, middle, $Show, " %g", [rope[keyRope]] ]; }; [propsList, success] _ GetSelectedPropsList[ggData, "ListProps"]; IF NOT success THEN RETURN; IF propsList#NIL THEN { [] _ Prop.Map[propsList, PrintKey]; Feedback.PutF[ggData.router, end, $Show, ""]; } ELSE Feedback.PutF[ggData.router, oneLiner, $Show, "No properties."]; }; ListPropsExternal: UserInputProc = { extRef: GGUserInput.External _ NARROW[event.rest.first]; returnList: LIST OF ATOM; success: BOOL _ FALSE; propsList: Prop.PropList; first: BOOL _ TRUE; GetSelectedPropsExt: PROC [ggData: GGData, opName: Rope.ROPE] RETURNS [propsList: Prop.PropList, success: BOOL _ TRUE, error: Rope.ROPE] = { sliceD: SliceDescriptor _ NIL; aborted: BOOL _ FALSE; scene: Scene _ ggData.scene; CheckPropsList: PROC [thisD: SliceDescriptor] RETURNS [done: BOOL _ FALSE] = { thisList: Prop.PropList; IF sliceD = NIL THEN { sliceD _ thisD; propsList _ sliceD.slice.props; } ELSE { thisList _ thisD.slice.props; IF thisList # propsList THEN done _ TRUE }; }; aborted _ GGScene.WalkSelectedSlices[scene, leaf, CheckPropsList, normal]; IF aborted THEN { success _ FALSE; Feedback.PutF[ggData.router, oneLiner, $Complaint, "%g failed: there is more than one properties list selected", [rope[opName]] ]; } ELSE IF sliceD = NIL THEN { success _ FALSE; Feedback.PutF[ggData.router, oneLiner, $Complaint, "%g failed: no objects are selected", [rope[opName]] ]; }; }; PrintKey: PROC [key: REF, val: REF] RETURNS [quit: BOOL _ FALSE] = { atom: ATOM _ NARROW[key]; keyRope: Rope.ROPE _ Atom.GetPName[atom]; IF first THEN { Feedback.PutF[ggData.router, begin, $Show, "%g", [rope[keyRope]] ]; first _ FALSE; } ELSE Feedback.PutF[ggData.router, middle, $Show, " %g", [rope[keyRope]] ]; returnList _ CONS[atom, returnList]; }; [propsList, success] _ GetSelectedPropsExt[ggData, "ListProps"]; IF success THEN { IF propsList#NIL THEN { [] _ Prop.Map[propsList, PrintKey]; Feedback.PutF[ggData.router, end, $Show, ""]; } ELSE Feedback.PutF[ggData.router, oneLiner, $Show, "No properties."]; }; extRef.results _ returnList; extRef.valid _ TRUE; GGUserInput.BroadcastExternal[extRef]; }; CopyProps: UserInputProc = { CopyAndSetProps: PROC [sliceD: SliceDescriptor] RETURNS [done: BOOL _ FALSE] = { IF sliceD.slice#lastSliceD.slice THEN GGProps.CopyAll[lastSliceD.slice, sliceD.slice, lastSliceD.parts, sliceD.parts]; }; lastSliceD: SliceDescriptor _ GGScene.LastSelectedSlice[ggData.scene, leaf, normal]; [] _ GGScene.WalkSelectedSlices[ggData.scene, leaf, CopyAndSetProps, normal]; }; SetRootProp: PUBLIC UserInputProc = { rope: Rope.ROPE _ NARROW[event.rest.first]; s: IO.STREAM _ IO.RIS[rope]; keyRope, valRope: Rope.ROPE; val: REF; key: ATOM; keyRope _ GGParseIn.ReadWWord[s]; IF keyRope = NIL THEN { Feedback.Append[ggData.router, oneLiner, $Complaint, "SetRootProp failed: Please select a key name"]; RETURN; }; GGParseIn.ReadBlank[s]; valRope _ IO.GetRope[s]; key _ Atom.MakeAtom[keyRope]; val _ IF valRope=NIL THEN NIL ELSE GGProps.FromRope[key, valRope]; GGProps.Put[ggData.rootSlice, NIL, key, val]; GGWindow.RestoreScreenAndInvariants[paintAction: $None, ggData: ggData, remake: none, edited: TRUE, okToSkipCapture: TRUE]; }; GetRootProp: PUBLIC UserInputProc = { keyRope: Rope.ROPE _ NARROW[event.rest.first]; key: ATOM _ Atom.MakeAtom[keyRope]; val: REF; success: BOOL _ FALSE; [val, success] _ GGProps.Get[ggData.rootSlice, NIL, key]; IF NOT success THEN RETURN; IF val#NIL THEN { valRope: Rope.ROPE; valRope _ GGProps.ToRope[key, val].r; IF valRope = NIL THEN Feedback.PutF[ggData.router, oneLiner, $Show, "%g ", [rope[keyRope]] ] ELSE Feedback.PutF[ggData.router, oneLiner, $Show, "%g %g", [rope[keyRope]], [rope[valRope]]] } ELSE Feedback.PutF[ggData.router, oneLiner, $Show, "No value for key %g.", [rope[keyRope]]]; }; RemoveRootProp: PUBLIC UserInputProc = { keyRope: Rope.ROPE _ NARROW[event.rest.first]; key: ATOM _ Atom.MakeAtom[keyRope]; IF keyRope = NIL THEN { Feedback.Append[ggData.router, oneLiner, $Complaint, "RemoveRootProp failed: Please select a key name"]; RETURN; }; [] _ GGProps.Rem[ggData.rootSlice, NIL, key]; Feedback.PutF[ggData.router, oneLiner, $Feedback, "Key %g removed from root slice.", [rope[keyRope]] ]; }; ListRootProps: PUBLIC UserInputProc = { success: BOOL _ FALSE; propsList: Prop.PropList; first: BOOL _ TRUE; PrintKey: PROC [key: REF, val: REF] RETURNS [quit: BOOL _ FALSE] = { atom: ATOM _ NARROW[key]; keyRope: Rope.ROPE _ Atom.GetPName[atom]; IF first THEN { Feedback.PutF[ggData.router, begin, $Show, "%g", [rope[keyRope]] ]; first _ FALSE; } ELSE Feedback.PutF[ggData.router, middle, $Show, " %g", [rope[keyRope]] ]; }; propsList _ ggData.rootSlice.props; IF propsList#NIL THEN { [] _ Prop.Map[propsList, PrintKey]; Feedback.PutF[ggData.router, end, $Show, ""]; } ELSE Feedback.PutF[ggData.router, oneLiner, $Show, "No properties."]; }; GetSelectedPropsList: PROC [ggData: GGData, opName: Rope.ROPE] RETURNS [propsList: Prop.PropList, success: BOOL _ TRUE] = { sliceD: SliceDescriptor _ NIL; aborted: BOOL _ FALSE; scene: Scene _ ggData.scene; CheckPropsList: PROC [thisD: SliceDescriptor] RETURNS [done: BOOL _ FALSE] = { thisList: Prop.PropList; IF sliceD = NIL THEN { sliceD _ thisD; propsList _ sliceD.slice.props; } ELSE { thisList _ thisD.slice.props; IF thisList # propsList THEN done _ TRUE }; }; aborted _ GGScene.WalkSelectedSlices[scene, leaf, CheckPropsList, normal]; IF aborted THEN { success _ FALSE; Feedback.PutF[ggData.router, oneLiner, $Complaint, "%g failed: there is more than one properties list selected", [rope[opName]] ]; } ELSE IF sliceD = NIL THEN { success _ FALSE; Feedback.PutF[ggData.router, oneLiner, $Complaint, "%g failed: no objects are selected", [rope[opName]] ]; }; }; ApplyAllDefaults: UserInputProc = { newSelectList, ptr: LIST OF Slice; currentEvent: HistoryEvent; scene: Scene _ ggData.scene; DoApplyAllDefaults: PROC [sliceD: SliceDescriptor] RETURNS [done: BOOL _ FALSE] = { GGSliceOps.SetDefaults[sliceD.slice, sliceD.parts, ggData.defaults, currentEvent]; [newSelectList, ptr] _ GGUtility.AddSlice[sliceD.slice, newSelectList, ptr]; }; IF GGSelect.NoSelections[ggData.scene, normal] THEN { Feedback.Append[ggData.router, oneLiner, $Complaint, "ApplyAllDefaults failed: select some objects for ApplyAllDefaults"]; RETURN; }; [newSelectList, ptr] _ GGUtility.StartSliceList[]; GGRefresh.SetStartBox[ggData: ggData, dragInProgress: FALSE, selectedParts: TRUE]; currentEvent _ GGHistory.NewCurrent["Apply default looks", ggData]; -- start new history event [] _ GGScene.WalkSelectedSlices[scene, first, DoApplyAllDefaults, normal]; FOR list: LIST OF Slice _ newSelectList, list.rest UNTIL list = NIL DO GGSelect.SelectEntireSlice[list.first, ggData.scene, normal]; ENDLOOP; GGRefresh.EnlargeStartBox[ggData, GGScene.BoundBoxOfSelected[ggData.scene, normal], NIL]; GGHistory.PushCurrent[ggData]; Feedback.Append[ggData.router, oneLiner, $Feedback, "ApplyAllDefaults: all defaults applied"]; GGWindow.RestoreScreenAndInvariants[paintAction: $ObjectChangedBoundBoxProvided, ggData: ggData, remake: none, edited: TRUE, okToSkipCapture: FALSE]; }; SetAllDefaults: UserInputProc = { }; ShowAllDefaults: UserInputProc = { Feedback.Append[ggData.router, oneLiner, $Typescript, "--------------------------------------------"]; GGEvent.ShowDefaultFontValues[ggData, event]; GGEvent.ShowDefaultLineColor[ggData, event]; GGEvent.ShowDefaultFillColor[ggData, event]; GGEvent.ShowDefaultStrokeValues[ggData, event]; GGEvent.ShowDefaultTextLooks[ggData, event]; Feedback.Append[ggData.router, oneLiner, $Typescript, "--------------------------------------------"]; }; StandardDefaults: UserInputProc = { ggData.defaults^ _ [ strokeWidth: 2.0, strokeJoint: round, strokeEnd: round, dashed: FALSE, pattern: NIL, offset: 0.0, length: 0.0, strokeColor: Imager.black, fillColor: GGOutline.fillColor, font: GGUserProfile.GetDefaultDefaultFont[], textColor: Imager.black, dropShadowOn: FALSE, dropShadowOffset: [-0.1, -0.1], dropShadowColor: Imager.black ]; ShowAllDefaults[ggData, event]; }; WeldOrBackword: UserInputProc = { IF ggData.refresh.textInProgress=NIL THEN Weld[ggData, event] ELSE { myRefChar: REF CHAR _ NEW[CHAR _ Ascii.ControlW ]; event _ LIST[$AddChar, myRefChar]; GGEvent.AddChar[ggData, event]; }; }; Weld: UserInputProc = { firstTraj, secondTraj, newTraj: Traj; firstOutline, secondOutline, newOutline: Slice; weldPoint: Point; firstEnd, secondEnd: TrajEnd; success: BOOL; firstPriority: INT _ 0; cluster: Slice; scene: Scene _ ggData.scene; whichClustered: OfTwoArgs _ none; GGHistory.NewCapture["Weld", ggData]; -- capture scene BEFORE UPDATE [firstTraj, secondTraj, cluster, whichClustered, success] _ GetWeldArguments[ggData]; IF NOT success THEN RETURN; IF secondTraj = NIL THEN success _ WeldToSelf[ggData, firstTraj] ELSE { ancestor: Slice; IF cluster # NIL THEN { ancestor _ GGParent.GetTopLevelAncestor[cluster]; GGSelect.SaveSelectionsInSliceAllClasses[ancestor, scene]; GGSelect.DeselectEntityAllClasses[ancestor, scene]; }; [firstEnd, secondEnd] _ ClosestEnds[firstTraj, secondTraj]; firstOutline _ GGParent.GetParent[firstTraj]; secondOutline _ GGParent.GetParent[secondTraj]; newTraj _ GGTraj.Concat[firstTraj, firstEnd, secondTraj, secondEnd]; newOutline _ GGOutline.CreateOutline[newTraj, GGSliceOps.GetFillColor[firstOutline, NIL].color]; IF cluster = NIL THEN { -- both are top level firstPriority _ GGScene.GetPriority[scene, firstOutline]; GGScene.DeleteSlice[scene, firstOutline]; GGScene.DeleteSlice[scene, secondOutline]; GGScene.AddSlice[scene, newOutline, firstPriority]; } ELSE { -- one or both are in a cluster firstPriority _ GGParent.GetChildPriority[cluster, firstOutline]; GGSelect.DeselectEntityAllClasses[firstOutline, scene]; GGSelect.DeselectEntityAllClasses[secondOutline, scene]; IF whichClustered = first OR whichClustered = both THEN [] _ GGSlice.RemoveChild[cluster, firstOutline] ELSE GGScene.DeleteSlice[scene, firstOutline]; IF whichClustered = second OR whichClustered = both THEN [] _ GGSlice.RemoveChild[cluster, secondOutline] ELSE GGScene.DeleteSlice[scene, secondOutline]; GGSlice.AddChildToCluster[cluster, newOutline, firstPriority]; }; IF cluster # NIL THEN GGSelect.ReselectSliceAllClasses[ancestor, ggData.scene]; GGEvent.SelectEntireSlice[newOutline, scene, normal, ggData]; weldPoint _ GGTraj.FetchJointPos[newTraj, GGTraj.HiJoint[IF firstEnd=hi THEN firstTraj ELSE secondTraj]]; GGCaret.SetAttractor[ggData.caret, weldPoint, [0,-1], NIL]; GGCaret.SitOn[ggData.caret, NIL]; GGRefresh.NullStartBox[ggData]; GGRefresh.EnlargeStartBox[ggData, GGSliceOps.GetBoundBox[firstTraj], GGSliceOps.GetBoundBox[secondTraj]]; GGRefresh.EnlargeStartBox[ggData, GGSliceOps.GetBoundBox[newTraj], NIL]; }; IF success THEN { GGHistory.PushCurrent[ggData]; -- push captured history event onto history list Feedback.Append[ggData.router, oneLiner, $Feedback, "Weld: completed"]; GGWindow.RestoreScreenAndInvariants[paintAction: $ObjectChangedBoundBoxProvided, ggData: ggData, remake: triggerBag, edited: TRUE, okToSkipCapture: FALSE]; }; }; OfTwoArgs: TYPE = {none, first, second, both}; GetWeldArguments: PROC [ggData: GGData] RETURNS [firstTraj, secondTraj: Traj, cluster: Slice, whichClustered: OfTwoArgs _ none, success: BOOL _ FALSE] = { firstOutlineD, secondOutlineD: SliceDescriptor; scene: Scene _ ggData.scene; index: NAT _ 0; otherCluster: Slice; FirstAndSecond: PROC [sliceD: SliceDescriptor] RETURNS [done: BOOL _ FALSE] = { IF index = 0 THEN { firstOutlineD _ sliceD; index _ index + 1; } ELSE IF index = 1 THEN { secondOutlineD _ sliceD; done _ TRUE; }; }; argCount: NAT _ GGScene.CountSelectedSlices[ggData.scene, leaf, normal, $Outline]; IF argCount = 0 OR argCount > 2 THEN GOTO Abort; -- 0 or more than 2 selected [] _ GGScene.WalkSelectedSlices[scene, leaf, FirstAndSecond, normal, $Outline]; IF GGOutline.HasHoles[firstOutlineD.slice] OR (secondOutlineD#NIL AND GGOutline.HasHoles[secondOutlineD.slice]) THEN GOTO Abort; -- not simple outlines IF (firstTraj _ GGParent.FirstChild[firstOutlineD.slice, first])=NIL OR GGSliceOps.GetType[firstTraj]#$Traj OR NARROW[firstTraj.data, TrajData].role#open THEN GOTO Abort; IF secondOutlineD=NIL THEN { cluster _ GGParent.GetParent[firstOutlineD.slice]; whichClustered _ IF cluster = NIL THEN none ELSE first; RETURN[firstTraj, NIL, cluster, whichClustered, TRUE]; }; IF (secondTraj _ GGParent.FirstChild[secondOutlineD.slice, first])=NIL OR GGSliceOps.GetType[secondTraj]#$Traj OR NARROW[secondTraj.data, TrajData].role#open THEN GOTO Abort; cluster _ GGParent.GetParent[firstOutlineD.slice]; otherCluster _ GGParent.GetParent[secondOutlineD.slice]; IF cluster = NIL THEN { whichClustered _ IF otherCluster = NIL THEN none ELSE second; RETURN[firstTraj, secondTraj, otherCluster, whichClustered, TRUE]; }; IF otherCluster = NIL THEN { RETURN[firstTraj, secondTraj, cluster, first, TRUE]; }; IF cluster # otherCluster THEN { Feedback.Append[ggData.router, oneLiner, $Complaint, "Weld failed: can't weld two CLUSTERED trajectories"]; RETURN[NIL, NIL, NIL, none, FALSE]; }; success _ TRUE; whichClustered _ both; EXITS Abort => { Feedback.Append[ggData.router, oneLiner, $Complaint, "Weld failed: select one or two OPEN trajectories for a weld."]; RETURN[NIL, NIL, NIL, none, FALSE]; }; }; WeldToSelf: PROC [ggData: GGData, traj: Traj] RETURNS [success: BOOL _ FALSE] = { outline: Slice _ GGParent.GetParent[traj]; ancestor: Slice _ GGParent.GetTopLevelAncestor[traj]; GGRefresh.NullStartBox[ggData]; GGRefresh.EnlargeStartBox[ggData, GGSliceOps.GetBoundBox[traj], NIL]; IF GGTraj.HiSegment[traj]=0 AND GGTraj.FetchSegment[traj,0].class.type=$Line THEN { Feedback.Append[ggData.router, oneLiner, $Complaint, "Weld failed: can't weld a single straight line segment to itself"]; RETURN; }; GGSelect.SaveSelectionsInSliceAllClasses[ancestor, ggData.scene]; GGSelect.DeselectEntityAllClasses[ancestor, ggData.scene]; GGTraj.CloseByDistorting[traj, lo]; GGSelect.ReselectSliceAllClasses[ancestor, ggData.scene]; GGEvent.SelectEntireSlice[outline, ggData.scene, normal, ggData]; GGCaret.SetAttractor[ggData.caret, GGTraj.FetchJointPos[traj, 0], [0,-1], NIL]; GGCaret.SitOn[ggData.caret, NIL]; -- this is important! GGRefresh.EnlargeStartBox[ggData, GGSliceOps.GetBoundBox[traj], NIL]; success _ TRUE; }; ClosestEnds: PROC [firstTraj, secondTraj: Traj] RETURNS [firstEnd, secondEnd: TrajEnd] = { firstLo, firstHi, secondLo, secondHi: Point; d11, d12, d21, d22, d: REAL; firstLo _ GGTraj.FetchJointPos[firstTraj, 0]; firstHi _ GGTraj.FetchJointPos[firstTraj, GGTraj.HiJoint[firstTraj]]; secondLo _ GGTraj.FetchJointPos[secondTraj, 0]; secondHi _ GGTraj.FetchJointPos[secondTraj, GGTraj.HiJoint[secondTraj]]; d11 _ Vectors2d.DistanceSquared[firstLo, secondLo]; d12 _ Vectors2d.DistanceSquared[firstLo, secondHi]; d21 _ Vectors2d.DistanceSquared[firstHi, secondLo]; d22 _ Vectors2d.DistanceSquared[firstHi, secondHi]; d _ MIN[d11, d12, d21, d22]; SELECT TRUE FROM d11 = d => {firstEnd _ lo; secondEnd _ lo}; d12 = d => {firstEnd _ lo; secondEnd _ hi}; d21 = d => {firstEnd _ hi; secondEnd _ lo}; d22 = d => {firstEnd _ hi; secondEnd _ hi}; ENDCASE => ERROR; }; < { line: Segment _ GGSegment.MakeLine[firstPoint, middlePoint, List.Append[runSeg.props, NIL]]; GGSegment.CopyLooks[runSeg, line]; IF NOT GGTraj.AddSegment[traj, hi, line, lo] THEN ERROR; line _ GGSegment.MakeLine[middlePoint, lastPoint, List.Append[runSeg.props, NIL]]; GGSegment.CopyLooks[runSeg, line]; IF NOT GGTraj.AddSegment[traj, hi, line, lo] THEN ERROR; }; $Arc => { arc: Segment _ GGSegment.MakeArc[firstPoint, middlePoint, lastPoint, List.Append[runSeg.props, NIL]]; GGSegment.CopyLooks[runSeg, arc]; IF NOT GGTraj.AddSegment[traj, hi, arc, lo] THEN ERROR; }; $Conic => { conic: Segment _ GGSegment.MakeConic[firstPoint, middlePoint, lastPoint, 0.7, List.Append[runSeg.props, NIL]]; GGSegment.CopyLooks[runSeg, conic]; IF NOT GGTraj.AddSegment[traj, hi, conic, lo] THEN ERROR; }; $Bezier => { length: REAL _ Vectors2d.Distance[firstPoint, middlePoint]; dir: Point _ Vectors2d.Sub[middlePoint, firstPoint]; p1: Point _ GetPt[firstPoint, dir]; p2: Point _ GetPt[middlePoint, [-dir.x, -dir.y]]; bezier: Segment _ GGSegment.MakeBezier[firstPoint, p1, p2, middlePoint, List.Append[runSeg.props, NIL]]; GGSegment.CopyLooks[runSeg, bezier]; IF NOT GGTraj.AddSegment[traj, hi, bezier, lo] THEN ERROR; -- first Bezier dir _ Vectors2d.Sub[lastPoint, middlePoint]; p1 _ GetPt[middlePoint, dir]; p2 _ GetPt[lastPoint, [-dir.x, -dir.y]]; bezier _ GGSegment.MakeBezier[middlePoint, p1, p2, lastPoint, List.Append[runSeg.props, NIL]]; GGSegment.CopyLooks[runSeg, bezier]; IF NOT GGTraj.AddSegment[traj, hi, bezier, lo] THEN ERROR; -- last Bezier }; $CubicSpline => { cSpline: Segment; cps: CubicSplines.KnotSequence _ NEW[CubicSplines.KnotSequenceRec[3]]; cps[0] _ [firstPoint.x, firstPoint.y]; cps[1] _ [middlePoint.x, middlePoint.y]; cps[2] _ [lastPoint.x, lastPoint.y]; cSpline _ GGSegment.MakeCubicSpline[cps, naturalAL, List.Append[runSeg.props, NIL]]; GGSegment.CopyLooks[runSeg, cSpline]; IF NOT GGTraj.AddSegment[traj, hi, cSpline, lo] THEN ERROR; }; ENDCASE => ERROR; -- unknown segment type ENDLOOP; }; GGHistory.NewCapture["Split segments", ggData]; -- capture scene BEFORE UPDATE ggData.refresh.startBoundBox^ _ GGSelect.ForEachOutlineRun[ggData.scene, normal, MakeReplacement, TRUE, FALSE]^; GGCaret.NoAttractor[ggData.caret]; GGCaret.SitOn[ggData.caret, NIL]; GGHistory.PushCurrent[ggData]; -- push captured history event onto history list Feedback.Append[ggData.router, "Segements split", oneLiner]; GGWindow.RestoreScreenAndInvariants[paintAction: $ObjectChangedBoundBoxProvided, ggData: ggData, remake: triggerBag, edited: TRUE, okToSkipCapture: FALSE] }; >> AddControlPoint: UserInputProc = { seg, newSeg: Segment; segNum: INT; traj, newRun, newOutline, ancestor, oldOutline: Slice; refreshBox: BoundBox; success: BOOL; partType: TrajPartType; caretPos: Point; attractor: REF ANY; sliceD: SliceDescriptor; scene: Scene _ ggData.scene; caretPos _ GGCaret.GetPoint[ggData.caret]; attractor _ GGCaret.GetAttractor[ggData.caret]; IF attractor = NIL THEN GOTO NotOnSpline; IF NOT ISTYPE[attractor, SliceDescriptor] THEN GOTO NotOnSpline; sliceD _ NARROW[attractor]; [success, partType, traj, seg, segNum] _ GGSliceOps.UnpackSegment[sliceD]; IF NOT success OR partType#segment OR seg.class.type#$CubicSpline THEN GOTO NotOnSpline; ancestor _ GGParent.GetTopLevelAncestor[sliceD.slice]; GGHistory.NewCapture["Add control point", ggData]; -- capture scene BEFORE UPDATE GGSelect.DeselectAll[scene, normal]; GGSelect.SaveSelectionsInSliceAllClasses[ancestor, scene]; GGRefresh.NullStartBox[ggData]; GGRefresh.EnlargeStartBox[ggData, seg.class.boundBox[seg], NIL]; newSeg _ GGSegment.CSControlPointAdd[seg, caretPos]; newRun _ GGTraj.CreateTraj[newSeg.lo]; GGSliceOps.SetStrokeJoint[newRun, NIL, NARROW[traj.data, TrajData].strokeJoint, NIL]; refreshBox _ newSeg.class.boundBox[newSeg]; GGRefresh.EnlargeStartBox[ggData, refreshBox, NIL]; IF NOT GGTraj.AddSegment[newRun, hi, newSeg, lo] THEN ERROR; IF (segNum _ GGTraj.IndexOfSegment[seg, traj]) = -1 THEN ERROR; -- inconsistent newOutline _ SubstituteForSegment[traj, segNum, newRun].newOutline; oldOutline _ GGParent.GetParent[traj]; IF GGScene.IsTopLevel[oldOutline] THEN { priority: INT _ GGScene.GetPriority[scene, ancestor]; GGScene.DeleteSlice[scene, oldOutline]; GGScene.AddSlice[scene, newOutline, priority]; GGSelect.ReselectSliceAllClasses[newOutline, scene]; } ELSE { cluster: Slice _ GGParent.GetParent[oldOutline]; priority: INT _ GGParent.GetChildPriority[cluster, oldOutline]; [] _ GGSlice.RemoveChild[cluster, oldOutline]; GGSlice.AddChildToCluster[cluster, newOutline, priority]; GGSelect.ReselectSliceAllClasses[GGParent.GetTopLevelAncestor[cluster], scene]; }; GGCaret.NoAttractor[ggData.caret]; GGCaret.SitOn[ggData.caret, NIL]; GGHistory.PushCurrent[ggData]; -- push captured history event onto history list Feedback.Append[ggData.router, oneLiner, $Feedback, "Add CP: control point added"]; GGWindow.RestoreScreenAndInvariants[paintAction: $ObjectChangedBoundBoxProvided, ggData: ggData, remake: triggerBag, edited: TRUE, okToSkipCapture: FALSE]; EXITS NotOnSpline => { Feedback.Append[ggData.router, oneLiner, $Complaint, "Add CP failed: caret must lie on a cubic spline to add a control point"]; }; }; DeleteControlPoint: UserInputProc = { scene: Scene _ ggData.scene; topLevelList: LIST OF Slice; DoSaveSelections: PROC [sliceD: SliceDescriptor] RETURNS [done: BOOL _ FALSE] = { GGSelect.SaveSelectionsInSliceAllClasses[sliceD.slice, scene]; topLevelList _ CONS[sliceD.slice, topLevelList]; }; DoDeleteCP: PROC [nextD: SliceDescriptor] RETURNS [done: BOOL _ FALSE] = { refreshBox: BoundBox _ GGOutline.DeleteControlPoints[nextD, scene]; GGRefresh.EnlargeStartBox[ggData, refreshBox, NIL]; }; IF GGSelect.NoSelections[scene, normal] THEN Feedback.Append[ggData.router, oneLiner, $Complaint, "Delete CP failed: no selected control points"] ELSE { GGHistory.NewCapture["Delete control point", ggData]; GGRefresh.NullStartBox[ggData]; [] _ GGScene.WalkSelectedSlices[scene, first, DoSaveSelections, normal]; [] _ GGScene.WalkSelectedSlices[scene, leaf, DoDeleteCP, normal, $Outline]; FOR list: LIST OF Slice _ topLevelList, list.rest UNTIL list = NIL DO GGSelect.ReselectSliceAllClasses[list.first, scene]; ENDLOOP; GGSelect.DeselectAll[scene, normal]; GGCaret.SitOn[ggData.caret, NIL]; GGCaret.NoAttractor[ggData.caret]; GGHistory.PushCurrent[ggData]; -- push captured history event onto history list Feedback.Append[ggData.router, oneLiner, $Feedback, "Delete CP: selected control points deleted"]; GGWindow.RestoreScreenAndInvariants[paintAction: $ObjectChangedBoundBoxProvided, ggData: ggData, remake: triggerBag, edited: TRUE, okToSkipCapture: FALSE]; }; }; AddJoint: UserInputProc = { sliceD: SliceDescriptor; seg, newSeg1, newSeg2: Segment; segNum: INT; traj, newRun, newTraj, newOutline: Traj; newJoint: Joint; success: BOOL; partType: TrajPartType; caretPos: Point; attractor: REF ANY; scene: Scene _ ggData.scene; ancestor, outline: Slice; caretPos _ GGCaret.GetPoint[ggData.caret]; attractor _ GGCaret.GetAttractor[ggData.caret]; IF attractor = NIL THEN GOTO NotASegment; IF NOT ISTYPE[attractor, SliceDescriptor] THEN GOTO NotASegment; sliceD _ NARROW[attractor]; [success, partType, traj, seg, segNum] _ GGSliceOps.UnpackSegment[sliceD]; IF NOT success OR partType#segment THEN GOTO NotASegment; IF seg.class.type=$Conic THEN GOTO ConicsAreNotDone; ancestor _ GGParent.GetTopLevelAncestor[sliceD.slice]; GGHistory.NewCapture["Add joint", ggData]; -- capture scene BEFORE UPDATE GGSelect.DeselectAll[scene, normal]; GGSelect.SaveSelectionsInSliceAllClasses[ancestor, scene]; [newSeg1, newSeg2] _ seg.class.addJoint[seg, caretPos]; newRun _ GGTraj.CreateTraj[newSeg1.lo]; IF NOT GGTraj.AddSegment[newRun, hi, newSeg1, lo] THEN ERROR; IF NOT GGTraj.AddSegment[newRun, hi, newSeg2, lo] THEN ERROR; newJoint _ GGTraj.FetchJoint[newRun, 1]; newJoint.TselectedInFull.normal _ TRUE; IF (segNum _ GGTraj.IndexOfSegment[seg, traj]) = -1 THEN ERROR; -- inconsistent GGRefresh.NullStartBox[ggData]; GGRefresh.EnlargeStartBox[ggData, seg.class.boundBox[seg], NIL]; [newTraj, newOutline] _ SubstituteForSegment[traj, segNum, newRun]; GGSliceOps.SetStrokeJoint[newTraj, NIL, NARROW[traj.data, TrajData].strokeJoint, NIL]; GGRefresh.EnlargeStartBox[ggData, GGSliceOps.GetBoundBox[newOutline], NIL]; outline _ GGParent.GetParent[traj]; IF GGScene.IsTopLevel[outline] THEN { priority: INT _ GGScene.GetPriority[scene, ancestor]; GGScene.DeleteSlice[scene, ancestor]; GGScene.AddSlice[scene, newOutline, priority]; GGSelect.ReselectSliceAllClasses[newOutline, scene]; } ELSE { cluster: Slice _ GGParent.GetParent[outline]; priority: INT _ GGParent.GetChildPriority[cluster, outline]; [] _ GGSlice.RemoveChild[cluster, outline]; GGSlice.AddChildToCluster[cluster, newOutline, priority]; GGSelect.ReselectSliceAllClasses[GGParent.GetTopLevelAncestor[cluster], scene]; }; GGCaret.NoAttractor[ggData.caret]; GGCaret.SitOn[ggData.caret, NIL]; GGHistory.PushCurrent[ggData]; -- push captured history event onto history list Feedback.Append[ggData.router, oneLiner, $Feedback, "Add Joint: joint added"]; GGWindow.RestoreScreenAndInvariants[paintAction: $ObjectChangedBoundBoxProvided, ggData: ggData, remake: triggerBag, edited: TRUE, okToSkipCapture: FALSE]; EXITS NotASegment => { Feedback.Append[ggData.router, oneLiner, $Complaint, "Add Joint failed: caret must lie on a segment to add a joint"]; }; ConicsAreNotDone => { Feedback.Append[ggData.router, oneLiner, $Complaint, "Add Joint failed: can't add joints to Conics"]; }; }; SubstituteForSegment: PROC [traj: Slice, segNum: NAT, newRun: Slice] RETURNS [newTraj, newOutline: Slice] = { trajData: TrajData _ NARROW[traj.data]; runParts: TrajParts _ GGSequence.CreateFromSegment[trajData, segNum]; run: SliceDescriptor _ GGSlice.DescriptorFromParts[traj, runParts]; oldOutline: Slice _ GGParent.GetParent[traj]; newTraj _ GGTraj.SpliceIn[run, newRun]; newOutline _ GGParent.GetParent[newTraj]; }; GetAnchorPoint: PROC [ggData: GGData] RETURNS [success: BOOL _ TRUE, anchorPoint: Point] = { IF GGCaret.Exists[ggData.anchor] THEN anchorPoint _ GGCaret.GetPoint[ggData.anchor] ELSE { tightBox: BoundBox _ GGScene.TightBoxOfSelected[ggData.scene, normal]; [anchorPoint, success] _ GGBoundBox.Centroid[tightBox]; }; }; TransRotScale: UserInputProc = { argStream: IO.STREAM _ IO.RIS[NARROW[event.rest.first]]; scalar1, scalar2: REAL _ 0.0; SELECT event.first FROM $Scale, $ScaleX, $ScaleY => { success: BOOL _ TRUE; scalar1 _ GGParseIn.ReadWReal[argStream ! IO.Error, IO.EndOfStream, GGParseIn.SyntaxError => {success _ FALSE; CONTINUE}]; IF NOT success THEN scalar1 _ 2.0; }; ENDCASE => scalar1 _ GGParseIn.ReadWReal[argStream ! IO.Error, IO.EndOfStream, GGParseIn.SyntaxError => GOTO NoNumber]; IF scalar1 = Real.LargestNumber OR scalar1 = 0.0 THEN GOTO NoNumber ELSE { GetSecondScalar: PROC [] RETURNS [success: BOOL _ TRUE] ~ { scalar2 _ GGParseIn.ReadWReal[argStream ! IO.Error, IO.EndOfStream, GGParseIn.SyntaxError => {success _ FALSE; CONTINUE;}; ]; IF scalar2 = Real.LargestNumber OR scalar2 = 0.0 THEN success _ FALSE; }; transform: ImagerTransformation.Transformation; anchorPoint: Point; success: BOOL _ FALSE; [success, anchorPoint] _ GetAnchorPoint[ggData]; IF NOT success OR GGSelect.NoSelections[ggData.scene, normal] THEN GOTO NoSelections; SELECT event.first FROM $Scale => transform _ GGTransform.ScaleAboutPoint[anchorPoint, scalar1]; $ScaleXY => { IF NOT GetSecondScalar[] THEN GOTO NoNumberPair; transform _ GGTransform.ScaleUnevenAboutPoint[anchorPoint, scalar1, scalar2]; }; $UnScale => transform _ GGTransform.ScaleAboutPoint[anchorPoint, 1.0/scalar1]; $Rotate => transform _ GGTransform.RotateAboutPoint[anchorPoint, scalar1]; $UnRotate => transform _ GGTransform.RotateAboutPoint[anchorPoint, -1.0*scalar1]; $TranslateX => transform _ ImagerTransformation.Translate[[ggData.hitTest.scaleUnit*scalar1, 0.0]]; $TranslateXY => { IF NOT GetSecondScalar[] THEN GOTO NoNumberPair; transform _ ImagerTransformation.Translate[[ggData.hitTest.scaleUnit*scalar1, ggData.hitTest.scaleUnit*scalar2]]; }; $TranslateToXY => { IF NOT GetSecondScalar[] THEN GOTO NoNumberPair; transform _ ImagerTransformation.Translate[Vectors2d.Sub[[ggData.hitTest.scaleUnit*scalar1, ggData.hitTest.scaleUnit*scalar2], anchorPoint]]; }; $TranslateY => transform _ ImagerTransformation.Translate[[0.0, ggData.hitTest.scaleUnit*scalar1]]; $ScaleX => transform _ GGTransform.ScaleUnevenAboutPoint[anchorPoint, scalar1, 1.0]; $ScaleY => transform _ GGTransform.ScaleUnevenAboutPoint[anchorPoint, 1.0, scalar1]; $UnScaleX => transform _ GGTransform.ScaleUnevenAboutPoint[anchorPoint, 1.0/scalar1, 1.0]; $UnScaleY => transform _ GGTransform.ScaleUnevenAboutPoint[anchorPoint, 1.0, 1.0/scalar1]; ENDCASE => ERROR; DoTheTransforms[ggData, transform]; } EXITS NoNumber => { Feedback.PutF[ggData.router, oneLiner, $Complaint, "%g failed: select a non-zero real number", [rope[Atom.GetPName[NARROW[event.first]]]] ]; }; NoNumberPair => { Feedback.PutF[ggData.router, oneLiner, $Complaint, "%g failed: select two non-zero real numbers", [rope[Atom.GetPName[NARROW[event.first]]]] ]; }; NoSelections => { Feedback.PutF[ggData.router, oneLiner, $Complaint, "%g failed: insufficient object selection for", [rope[Atom.GetPName[NARROW[event.first]]]] ]; }; }; SixPointTransform: UserInputProc = { points: ARRAY [0..5] OF Point _ ALL[[0.0, 0.0]]; transform: ImagerTransformation.Transformation; aborted: BOOL _ FALSE; scene: Scene _ ggData.scene; DoGetSixPoints: PROC [hotSlice: SliceDescriptor] RETURNS [done: BOOL _ FALSE] = { IF GGTraj.HiJoint[hotSlice.slice] < 2 THEN RETURN[TRUE]; FOR j: NAT IN [0..2] DO points[j + i*3] _ GGTraj.FetchJointPos[hotSlice.slice, j]; ENDLOOP; i _ 1; }; i: NAT _ 0; argCount: CARD _ GGScene.CountSelectedSlices[scene, leaf, hot, $Traj]; IF argCount # 2 THEN GOTO Abort; aborted _ GGScene.WalkSelectedSlices[scene, leaf, DoGetSixPoints, hot, $Traj]; IF aborted THEN GOTO Abort; transform _ GGTransform.SixPoints[points]; DoTheTransforms[ggData, transform]; EXITS Abort => Feedback.Append[ggData.router, oneLiner, $Complaint, "Six Point failed: not enough arguments for a six-point transform"]; }; FourPointTransform: UserInputProc = { points: ARRAY [0..3] OF Point _ ALL[[0.0, 0.0]]; transform: ImagerTransformation.Transformation; i: NAT _ 0; scene: Scene _ ggData.scene; aborted: BOOL _ FALSE; DoGetFourPoints: PROC [hotSlice: SliceDescriptor] RETURNS [done: BOOL _ FALSE] = { IF GGTraj.HiJoint[hotSlice.slice] < 1 THEN RETURN[TRUE]; FOR j: NAT IN [0..1] DO points[j + i*2] _ GGTraj.FetchJointPos[hotSlice.slice, j]; ENDLOOP; i _ 1; }; argCount: CARD _ GGScene.CountSelectedSlices[scene, leaf, hot, $Traj]; IF argCount # 2 THEN GOTO Abort; aborted _ GGScene.WalkSelectedSlices[scene, leaf, DoGetFourPoints, hot, $Traj]; IF aborted THEN GOTO Abort; transform _ GGTransform.FourPoints[points]; DoTheTransforms[ggData, transform]; EXITS Abort => Feedback.Append[ggData.router, oneLiner, $Complaint, "Four Point failed: not enough arguments for a four-point transform"]; }; DoTheTransforms: PROC [ggData: GGData, transform: ImagerTransformation.Transformation] = { IF GGSelect.NoSelections[ggData.scene, normal] THEN Feedback.Append[ggData.router, oneLiner, $Complaint, "Transform failed: no selection to transform"] ELSE { currentEvent: HistoryEvent; scene: Scene _ ggData.scene; DoTransform: PROC [sliceD: SliceDescriptor] RETURNS [done: BOOL _ FALSE] = { GGSliceOps.Transform[sliceD.slice, sliceD.parts, transform, ggData.drag.editConstraints, currentEvent]; }; ggData.drag.transform _ GGTransform.Identity[]; GGRefresh.SetStartBox[ggData: ggData, dragInProgress: FALSE, selectedCPs: TRUE, hotCPs: TRUE, movingParts: TRUE]; currentEvent _ GGHistory.NewCurrent["Transform", ggData]; -- start new history event [] _ GGScene.WalkSelectedSlices[scene, first, DoTransform, normal]; GGRefresh.EnlargeStartBox[ggData, GGScene.BoundBoxOfMoving[scene, ggData.drag.editConstraints, ggData.drag.bezierDrag, normal], NIL]; GGHistory.PushCurrent[ggData]; Feedback.Append[ggData.router, oneLiner, $Feedback, "Transform completed"]; GGWindow.RestoreScreenAndInvariants[paintAction: $ObjectChangedBoundBoxProvided, ggData: ggData, remake: triggerBag, edited: TRUE, okToSkipCapture: FALSE]; }; }; Refresh: UserInputProc = { startTime, endTime: BasicTime.GMT; totalTime: INT; Feedback.PutF[ggData.router, begin, $Statistics, "Refreshing . . . "]; startTime _ BasicTime.Now[]; GGWindow.RestoreScreenAndInvariants[paintAction: $PaintSceneNoBuffer, ggData: ggData, remake: none, edited: FALSE, okToSkipCapture: TRUE]; endTime _ BasicTime.Now[]; totalTime _ BasicTime.Period[startTime, endTime]; Feedback.PutF[ggData.router, end, $Statistics, " Done in time (%r)", [integer[totalTime]]]; }; Revive: UserInputProc = { scene: SceneRef _ NARROW[ggData.scene]; GGMouseEvent.ResetMouseMachinery[ggData]; CodeTimer.ResetTable[CodeTimer.GetTable[$Gargoyle]]; SlackProcess.Restart[ggData.slackHandle]; ggData.camera.quality _ fast; ggData.refresh.suppressRefresh _ FALSE; -- doesn't call thru GGRefresh ggData.refresh.suppressScreen _ FALSE; ggData.refresh.areaFollowColorTool _ ggData.refresh.lineFollowColorTool _ FALSE; scene.ptrValid _ scene.prioritiesValid _ FALSE; ggData.aborted _ ALL[FALSE]; GGHistory.KillAdvanceCapture[ggData]; IF event.rest # NIL AND event.rest.first = $Repaint THEN GGWindow.RestoreScreenAndInvariants[paintAction: $PaintEntireScene, ggData: ggData, remake: triggerBag, edited: FALSE, okToSkipCapture: TRUE]; }; DisableRefresh: UserInputProc = { -- this belongs in GGState GGRefresh.DisableRefresh[ggData]; }; EnableRefresh: UserInputProc = { -- this belongs in GGState GGRefresh.EnableRefresh[ggData]; }; DisableScreen: UserInputProc = { ggData.refresh.suppressScreen _ TRUE; }; EnableScreen: UserInputProc = { ggData.refresh.suppressScreen _ FALSE; }; ToggleBuffer: UserInputProc = { GGState.SetDoubleBuffer[ggData, NOT GGState.GetDoubleBuffer[ggData]]; }; Help: UserInputProc = { category: ATOM _ NARROW[event.rest.first]; GGState.ShowHelp[ggData, category]; }; IPSnapShot: UserInputProc = { DoMakeInterpress: PROC [dc: Imager.Context] = { ENABLE UNWIND => { ggData.camera.displayStyle _ tempStyle; ggData.camera.quality _ tempQuality; }; DoItInterpress: PROC = { Imager.ScaleT[dc, metersPerPixel]; ggData.camera.displayStyle _ print; ggData.camera.quality _ showall; GGRefresh.SnapShot[dc, ggData]; ggData.camera.displayStyle _ tempStyle; ggData.camera.quality _ tempQuality; }; Imager.DoSave[dc, DoItInterpress]; }; ipRef: ImagerInterpress.Ref; fullName: Rope.ROPE; success: BOOL; metersPerPixel: REAL = 0.0254/72.0; startTime: BasicTime.GMT; endTime: BasicTime.GMT; totalTime: INT; msgRope: Rope.ROPE; tempStyle: GGInterfaceTypes.DisplayStyle _ ggData.camera.displayStyle; tempQuality: GGInterfaceTypes.QualityMode _ ggData.camera.quality; [fullName, success] _ GGFileOps.GetInterpressFileName["IPSnapshot", "snapshot.ip", ggData.currentWDir, ggData.router]; IF NOT success THEN RETURN; ipRef _ ImagerInterpress.Create[fullName]; msgRope _ IO.PutFR["IPSnapshot: writing to IP file: %g . . . ", [rope[fullName]]]; Feedback.Append[ggData.router, begin, $Statistics, msgRope]; startTime _ BasicTime.Now[]; ImagerInterpress.DoPage[ipRef, DoMakeInterpress, 1.0]; ImagerInterpress.Close[ipRef]; endTime _ BasicTime.Now[]; totalTime _ BasicTime.Period[startTime, endTime]; msgRope _ IO.PutFR[" Done in time (%r)", [integer[totalTime]]]; Feedback.Append[ggData.router, end, $Statistics, msgRope]; }; ReloadTipTable: UserInputProc = { [] _ GGState.ReloadTipTable[ggData]; }; ToggleEditable: UserInputProc = { readOnly: BOOL _ GGState.GetReadOnly[ggData]; GGState.SetReadOnly[ggData, NOT readOnly]; }; SawTextFinish: PUBLIC UserInputProc = { slice: Slice _ ggData.refresh.textInProgress; CodeTimer.StartInt[$SawTextFinish, $Gargoyle]; IF slice#NIL AND Rope.Length[GGSlice.GetText[slice]]=0 THEN { -- backspaced to nothing isHot: BOOL _ GGSelect.IsSelectedInPart[slice, ggData.scene, hot]; GGScene.DeleteSlice[ggData.scene, slice]; ggData.refresh.textInProgress _ NIL; -- terminates typed input GGHistory.PushCurrent[ggData: ggData]; GGWindow.RestoreScreenAndInvariants[paintAction: IF isHot THEN $NewAlignmentsSelected ELSE $None, ggData: ggData, remake: triggerBag, edited: FALSE, okToSkipCapture: FALSE]; } ELSE IF slice#NIL THEN { -- fix up alignment triggers GGHistory.PushCurrent[ggData: ggData]; IF tryIncrementalUpdate THEN GGAlign.UpdateBagsForNewSlices[LIST[slice], ggData] -- the text is not necessarily new, but I believe this will work anyway. Bier, April 20, 1987 ELSE GGWindow.RestoreScreenAndInvariants[paintAction: $NewAlignmentsSelected, ggData: ggData, remake: triggerBag, edited: TRUE, okToSkipCapture: FALSE]; ggData.refresh.textInProgress _ NIL; -- terminates typed input }; IF ggData.refresh.areaFollowColorTool THEN { GGEvent.AreaColorFromColorTool[ggData, event ! ColorTool.NoColorToolViewer => CONTINUE;]; ColorTool.RemoveProc[$GG, NIL ! ColorTool.NoColorToolViewer => CONTINUE;]; ggData.refresh.areaFollowColorTool _ FALSE; }; IF ggData.refresh.lineFollowColorTool THEN { GGEvent.LineColorFromColorTool[ggData, event ! ColorTool.NoColorToolViewer => CONTINUE;]; ColorTool.RemoveProc[$GG, NIL ! ColorTool.NoColorToolViewer => CONTINUE;]; ggData.refresh.lineFollowColorTool _ FALSE; }; CodeTimer.StopInt[$SawTextFinish, $Gargoyle]; }; PrintRope: UserInputProc = { rope: Rope.ROPE _ NARROW[event.rest.first]; Feedback.Append[ggData.router, oneLiner, $Statistics, rope] }; SetCaretPosition: UserInputProc = { point: Point _ NARROW[event.rest.first, REF Point]^; caret: Caret _ ggData.caret; caret.point _ point; }; SetCaretNormal: UserInputProc = { normal: Vector _ NARROW[event.rest.first, REF Vector]^; caret: Caret _ ggData.caret; caret.normal _ normal; }; NoOp: UserInputProc = {}; RegisterAction: PROC [atom: ATOM, eventProc: GGUserInput.UserInputProc, argType: GGUserInput.ArgumentType, causeMouseEventsToComplete: BOOL _ TRUE, ensureUnique: BOOL _ TRUE] = GGUserInput.RegisterAction; RegisterEventProcs: PROC = { RegisterAction[$PaintActionArea, PaintActionArea, none]; RegisterAction[$ToggleActive, ToggleActive, none, TRUE]; RegisterAction[$SetActive, SetActive, none, TRUE]; RegisterAction[$TogglePalette, TogglePalette, none, TRUE]; RegisterAction[$SaveSelections, SaveSelections, none]; RegisterAction[$RestoreSelections, RestoreSelections, none]; RegisterAction[$SelectButton, SelectButton, none, TRUE]; -- actually takes a SliceDescriptor arg RegisterAction[$FeedbackOn, FeedbackOn, none]; RegisterAction[$FeedbackOff, FeedbackOff, none]; RegisterAction[$BeginButton, BeginButton, none]; RegisterAction[$EndButton, EndButton, none]; RegisterAction[$ButtonFillColorFromIntensity, ButtonFillColorFromIntensity, refReal]; RegisterAction[$ButtonStrokeColorFromIntensity, ButtonStrokeColorFromIntensity, refReal]; RegisterAction[$SetProp, SetProp, rope]; RegisterAction[$GetProp, GetProp, rope]; RegisterAction[$GetPropExternal, GetPropExternal, refExt]; RegisterAction[$RemoveProp, RemoveProp, rope]; RegisterAction[$ListProps, ListProps, none]; RegisterAction[$ListPropsExternal, ListPropsExternal, refExt]; RegisterAction[$CopyProps, CopyProps, none]; RegisterAction[$SetRootProp, SetRootProp, rope]; RegisterAction[$GetRootProp, GetRootProp, rope]; RegisterAction[$RemoveRootProp, RemoveRootProp, rope]; RegisterAction[$ListRootProps, ListRootProps, none]; RegisterAction[$Abort, GGMouseEvent.HandleMouseless, none, FALSE]; RegisterAction[$NoOp, NoOp, none]; RegisterAction[$Again, NoOp, none, FALSE]; -- added July 24, 1987. KAP. RegisterAction[$ApplyAllDefaults, ApplyAllDefaults, none]; RegisterAction[$SetAllDefaults, SetAllDefaults, none]; RegisterAction[$ShowAllDefaults, ShowAllDefaults, none]; RegisterAction[$StandardDefaults, StandardDefaults, none]; RegisterAction[$Weld, Weld, none]; RegisterAction[$AddControlPoint, AddControlPoint, none]; RegisterAction[$DeleteControlPoint, DeleteControlPoint, none]; RegisterAction[$AddJoint, AddJoint, none]; RegisterAction[$Scale, TransRotScale, rope]; RegisterAction[$ScaleXY, TransRotScale, rope]; RegisterAction[$UnScale, TransRotScale, rope]; RegisterAction[$Rotate, TransRotScale, rope]; RegisterAction[$UnRotate, TransRotScale, rope]; RegisterAction[$TranslateX, TransRotScale, rope]; RegisterAction[$TranslateXY, TransRotScale, rope]; RegisterAction[$TranslateToXY, TransRotScale, rope]; RegisterAction[$TranslateY, TransRotScale, rope]; RegisterAction[$ScaleX, TransRotScale, rope]; RegisterAction[$SixPointTransform, SixPointTransform, none]; RegisterAction[$ScaleY, TransRotScale, rope]; RegisterAction[$UnScaleX, TransRotScale, rope]; RegisterAction[$FourPointTransform, FourPointTransform, none]; RegisterAction[$UnScaleY, TransRotScale, rope]; RegisterAction[$Refresh, Refresh, none]; RegisterAction[$Revive, Revive, none]; RegisterAction[$DisableRefresh, DisableRefresh, none]; RegisterAction[$EnableRefresh, EnableRefresh, none]; RegisterAction[$ToggleBuffer, ToggleBuffer, none]; RegisterAction[$EnableScreen, EnableScreen, none]; RegisterAction[$DisableScreen, DisableScreen, none]; RegisterAction[$Help, Help, none]; RegisterAction[$ReloadTipTable, ReloadTipTable, none]; RegisterAction[$IPSnapShot, IPSnapShot, none, FALSE]; RegisterAction[$PrintRope, PrintRope, none, FALSE]; RegisterAction[$SetCaretPosition, SetCaretPosition, none]; RegisterAction[$SetCaretNormal, SetCaretNormal, none]; RegisterAction[$ToggleEditable, ToggleEditable, none]; RegisterAction[$SawTextFinish, SawTextFinish, none, FALSE]; RegisterAction[$SawMouseFinish, GGMouseEvent.HandleMouseless, none, FALSE]; RegisterAction[$WeldOrBackword, WeldOrBackword, none]; }; tryIncrementalUpdate: BOOL _ TRUE; RegisterEventProcs[]; END. ¤GGEventImplC.mesa Contents: Once an event reaches the front of the slack-process queue, it is dispatched to one of the procedures in this module. Copyright Ó 1986, 1987, 1988, 1989 by Xerox Corporation. All rights reserved. Bier, March 19, 1992 12:15 pm PST Kurlander, July 21, 1987 10:52:27 am PDT Eisenman, August 11, 1987 5:36:57 pm PDT Pier, June 9, 1992 5:13 pm PDT Maureen Stone, October 5, 1987 11:15:23 am PDT Doug Wyatt, December 15, 1989 7:28:14 pm PST Active Document Elements PROC [ggData: GGData, event: LIST OF REF ANY]; This routine should never actually be called, because InputNotify treats it specially. It exists just to make sure Gargoyle is notified of all mouse motion (even when none of the buttons are down) PROC [ggData: GGData, event: LIST OF REF ANY]; PROC [ggData: GGData, event: LIST OF REF ANY]; PROC [ggData: GGData, event: LIST OF REF ANY]; SaveSelections DeselectAll SelectButton FeedbackOff Turn off Screen Refresh until all button operations are completed DisableScreen[ggData, event]; -- wrong call RestoreSelections FeedbackOn Turn screen refresh back on and update the screen to reflect the current state EnableScreen[ggData, event]; -- wrong call PROC [ggData: GGData, event: LIST OF REF ANY]; PROC [ggData: GGData, event: LIST OF REF ANY]; Props Menu In GGEventImplC.SetProp Parse the Tioga selection, which is in the form "key value". Add the key-value pair to the selected Gargoyle objects. IF all selected slices have the same list of properties, then print out a list of the keys of those properties. IF all selected slices have the same list of properties, then return a list of the keys of those properties. Copy the propList of the last selected slice to all other selected slices Edit Menu Operations ggData.refresh.startBoundBox^ _ GGScene.BoundBoxOfSelected[ggData.scene, normal]^; GGBoundBox.EnlargeByBox[bBox: ggData.refresh.startBoundBox, by: GGScene.BoundBoxOfSelected[ggData.scene, normal]]; GGEvent.SetDefaultStrokeValues[event, clientData]; GGEvent.SetDefaultLineColor[event, clientData]; GGEvent.SetDefaultFillColor[event, clientData]; GGEvent.SetDefaultFontValues[event, clientData]; GGEvent.ShowAllDefaults[event, clientData]; One or two top level trajectories are selected. If one, Close it by distorting one of its ends. If two, of the four possible pairings of endpoints, choose the one with the endpoints closest together. Translate the second trajectory to the first. Weld. Select the new outline. Move the caret to the weld spot Compute the new bounding box. ggData.refresh.startBoundBox^ _ GGSliceOps.GetBoundBox[firstTraj]^; GGBoundBox.EnlargeByBox[bBox: ggData.refresh.startBoundBox, by: GGSliceOps.GetBoundBox[secondTraj]]; GGBoundBox.EnlargeByBox[bBox: ggData.refresh.startBoundBox, by: GGSliceOps.GetBoundBox[newTraj]]; If either outline is at top level, weld its parts into the other cluster, if any. ggData.refresh.startBoundBox^ _ GGSliceOps.GetBoundBox[traj]^; Disallow special case of single segment straight line traj Modify the Traj in place to close it. move the caret to the weld spot GGBoundBox.EnlargeByBox[bBox: ggData.refresh.startBoundBox, by: GGSliceOps.GetBoundBox[traj]]; Curve Modification RunProc: TYPE = PROC [run: Sequence] RETURNS [traj: Traj]; -- make new run of the appropriate type, having the same joints as that of the selected run, and with the caret position as an additional joint or control point. Adds a new control point to a segment ggData.refresh.startBoundBox^ _ seg.class.boundBox[seg]^; GGBoundBox.EnlargeByBox[ggData.refresh.startBoundBox, refreshBox]; Deletes all selected control points from segments GGBoundBox.EnlargeByBox[bBox: ggData.refresh.startBoundBox, by: refreshBox]; ggData.refresh.startBoundBox^ _ GGBoundBox.emptyBoundBox^; [Artwork node; type 'ArtworkInterpress on' to command tool] ggData.refresh.startBoundBox^ _ seg.class.boundBox[seg]^; GGBoundBox.EnlargeByBox[bBox: ggData.refresh.startBoundBox, by: GGSliceOps.GetBoundBox[newOutline, NIL]]; Replaces a segment with a new run. If newRun is not NIL, then only one trajectory is modified, and the new trajectory is returned. Also returned is the bbox of modified Outlines. IF NOT success THEN ERROR; -- should only happen if their are no selections Transform Menu Operations The first second and third selected objects should be one-segment trajectories indicating the six-point transform. All other selected objects are to be transformed. There are obvious problems with this scheme. Suggestions welcome. -- Bier, July 28, 1986. The first and second selected objects should be one-segment trajectories representing the four-point transform. All other selected objects are to be transformed. There are obvious problems with this scheme. Suggestions welcome. -- Bier, July 28, 1986. The boundbox before anything has moved. ggData.refresh.startBoundBox^ _ GGScene.BoundBoxOfMoving[ggData.scene, ggData.drag.editConstraints, ggData.drag.bezierDrag, normal]^; because ggData.drag.transform is used to compute movingParts bound box and we want the untransformed bound box to start with The boundbox after everything has moved. GGBoundBox.EnlargeByBox[bBox: ggData.refresh.startBoundBox, by: GGScene.BoundBoxOfMoving[scene, ggData.drag.editConstraints, ggData.drag.bezierDrag, normal]]; Stand alone menus PROC [ggData: GGData, event: LIST OF REF ANY]; Must not be queued, since the queue may be stuck. Copied from GGMenuImplC. ggData.refresh.suppressRefresh _ TRUE; ggData.refresh.suppressRefresh _ FALSE; GGRefresh.DisableRefresh[ggData]; -- just plain wrong GGRefresh.EnableRefresh[ggData]; -- just plain wrong Snapshots This command is executed in the middle of a dragging operation to make an interpress master of the current state of the screen. Luckily, all dragging operations use the same painting commands, namely: GGWindow.RestoreScreenAndInvariants[paintAction: $DuringMotion, ggData: ggData, remake: none, edited: FALSE, okToSkipCapture: FALSE]; We wish to have the same effect as these commands except written to an interpress master. We will create an interpress master named snapshot.ip and use GGRefresh.SnapShot to draw the scene into it. Miscellaneous The following has nothing to do with text. We take advantage of the fact that every mouse "Start" operation calls SawTextFinish, so we implicitly complete any FollowColorTool operation here. Active Button Props Menus Special Edit Menu RegisterAction[$SplitSegment, SplitSegment, none]; Transform Menu Refresh related operations Help Menu Miscellaneous Keyboard only Internal Atoms Ê=œ– "cedar" style•NewlineDelimiter ˜Icodešœ™šÏnœx™€KšœN™NKšœ!™!Kšœ%Ïk™(Kšœ%ž™(Kšœ™Kšœ.™.K™,—K™šž ˜ JšœÃžœžœ˜õ—K˜š œžœž˜Jšžœ‡žœ&˜¶Kšžœ+ž˜7—˜Kšœ žœžœ$Ïc˜[Kšœ žœžœŸ˜SK˜Kšœ žœ˜)Kšœ žœ˜'Kšœžœ˜%Kšœžœ˜Kšœžœ&˜AKšœžœ"˜5Kšœ žœ˜-Kšœžœ˜'Kšœžœ˜1Kšœžœ˜#Kšœžœ˜3Kšœžœ˜!Kšœžœžœ!˜˜>K˜—˜K˜—™ Kšœ™—šœ˜K™vš œžœžœžœžœ˜OKš œžœ žœžœžœžœ!Ÿn˜±Kšœ2˜2K˜—Kšœ žœžœ˜+Kšœžœžœžœ˜Kšœžœ˜Kšœžœ˜ Kšœžœ˜ K˜K˜!šžœ žœžœ˜Kšœa˜aKšžœ˜K˜—Kšœ˜Kšœ˜Kšœ˜K˜KšœL˜LKšœ^žœžœ˜{K˜K˜—šœžœžœžœžœžœ žœžœžœ˜Kšœ žœžœ˜K˜–=[rgb: RGB, calibration: ImagerColor.RGBCalibration _ NIL]š œžœžœžœžœ˜HJšœ žœ˜ šžœ žœžœ˜Jšœ˜Jšœ<˜˜HKšœj˜jK˜—K˜K˜—š œ˜"Kšœ žœžœžœ˜Kšœžœ˜Kšœžœ˜8Kšœžœžœ˜#Kšœžœ˜(Kšœžœ˜ Kšœ žœžœ˜K˜KšœK˜Kšžœ žœ˜šžœžœžœ˜Kšœ%˜%šžœ žœžœ˜KšœT˜TKšœ žœ-˜;K˜—KšžœY˜]K˜—šžœ˜KšœW˜WKšœ žœ0˜˜>K˜—Kš ™Kšžœ žœžœ:˜OKšœ=˜=Kš ™K–![caret: GGInterfaceTypes.Caret]šœ9žœ žœ žœ˜iKšœ6žœ˜;Kšœžœ˜!Kš ™KšœC™CKšœd™dKšœa™aK˜Jšœ   œ ˜Jšœ  œP˜iJšœ  œ*žœ˜HK˜—šžœžœ˜Kšœ   œ Ÿ0˜PJšœG˜GKšœ}žœžœ˜›Jšœ˜—Jšœ˜J˜—Kšœ žœ˜.š œžœžœZžœžœ˜šKšœ/˜/K˜Kšœžœ˜Kšœ˜š œžœžœžœžœ˜Ošžœ žœ˜Jšœ˜Jšœ˜J˜—šžœžœ žœ˜Jšœ˜Jšœžœ˜ J˜—J˜—Kšœ žœE˜RKš žœžœžœžœŸ˜MKšœO˜OKš žœ)žœžœžœ+žœžœŸ˜—Jšžœ œžœžœ%žœžœ%žœžœ˜ªšžœžœžœž˜Kšœ2˜2Kš œžœ žœžœžœ˜7Kšžœ žœžœ˜6K˜—Jšžœ œžœžœ&žœžœ&žœžœ˜®Kšœ2˜2Kšœ8˜8Kš Q™Qšžœ žœžœž˜Kš œžœžœžœžœ˜=Kšžœ6žœ˜BKšœ˜—šžœžœžœž˜Kšžœ(žœ˜4K˜—šžœžœž˜ Kšœk˜kKš žœžœžœžœžœ˜#K˜—Kšœ žœ˜Kšœ˜šž˜˜ Kšœu˜uKš žœžœžœžœžœ˜#K˜——K˜K˜—š œžœžœ žœžœ˜QKšœ*˜*Kšœ5˜5K–[bBox: GGCoreTypes.BoundBox]šœ>™>K™Jšœ   œ ˜Jšœ  œ'žœ˜E˜Kš :™:—šžœžœ.žœ˜SK–0[feedback: Feedback.FeedbackData, msg: ROPE]šœy˜yKšžœ˜K˜Kš %™%—KšœA˜AKšœ:˜:Kšœ#˜#Kšœ9˜9šœA˜AKš ™—K–)[traj: GGModelTypes.Traj, index: NAT]šœJžœ˜OKšœžœŸ˜7Kšœ^™^Kšœ  œ'žœ˜EKšœ žœ˜Kšœ˜K˜—š œžœžœ#˜ZJšœ,˜,Jšœžœ˜Kšœ-˜-KšœE˜EKšœ/˜/KšœH˜HKšœ3˜3Kšœ3˜3Kšœ3˜3Kšœ3˜3Kšœžœ˜šžœžœž˜Kšœ+˜+Kšœ+˜+Kšœ+˜+Kšœ+˜+Kšžœžœ˜—K˜—K˜K™šœ˜!šœ˜%Kšœ žœžœžœ™:šœžœžœ˜3Kšœžœ˜Kšœžœ˜Kšœq˜qKšœ˜—KšŸ¡™¡K™K˜4Kšœžœ˜+Kšœžœ ˜(KšœZ˜ZšžœXžœžœž˜nKšœ˜Kšœ˜šžœžœžœ˜Kšœ%˜%Kšœ žœžœ˜?K˜—šžœž˜˜ KšœVžœ˜\Kšœ"˜"Kšžœžœ'žœžœ˜8KšœLžœ˜RKšœ"˜"Kšžœžœ'žœžœ˜8K˜—˜ Kšœ_žœ˜eKšœ!˜!Kšžœžœ&žœžœ˜7K˜—˜ Kšœhžœ˜nKšœ#˜#Kšžœžœ(žœžœ˜9K˜—˜ Kšœžœ/˜;K˜4K˜#K˜1Kšœbžœ˜hKšœ$˜$Kš žœžœ)žœžœŸ˜JK˜,K˜K˜(KšœXžœ˜^Kšœ$˜$Kš žœžœ)žœžœŸ˜IK˜—˜K˜Kšœ!žœ"˜FK˜&K˜(K˜$KšœNžœ˜TKšœ%˜%Kšžœžœ*žœžœ˜;K˜—KšžœžœŸ˜)—Kšžœ˜—K˜K˜—Kšœ œ¢œ Ÿ˜NKšœbžœžœ˜pK˜"Kšœžœ˜!Kšœ   œ Ÿ0˜PJšœ<˜˜>Jšœžœ˜0J˜—š œžœžœžœžœ˜JJšœC˜CJšœL™LJšœ  œžœ˜3Jšœ˜—Kšžœ&žœe˜‘šžœ˜Kšœ   œ¢œ ˜5Jšœ:™:Kšœ   œ ˜K˜Kšœ,¢œ¢œ¢˜HKšœ+¢  ¢œ¢˜Kš žœžœžœ!žœžœž˜EJšœ4˜4Jšžœ˜—J˜$Jšœžœ˜!Jšœ"˜"Kšœ   œ Ÿ0˜PJšœb˜bKšœ}žœžœ˜›J˜—J˜J˜—šœ˜I artworkFigure–G41.62778 mm topLeading 41.62778 mm topIndent 1.411111 mm bottomLeading •Bounds:0.0 mm xmin 0.0 mm ymin 126.6472 mm xmax 38.80556 mm ymax •Artwork Interpress• InterpressõInterpress/Xerox/3.0  f j k j¡¥“ÄWB ¤ ¨  ¡£€S ¢ ¨ r j ¡ ¢ ¨¡¡¨ÄWB ¤ ¨ r j¡¥“ÄWB ¤ ¨¡¡¨¢¯“¡¡¨¢·“¢°“ÑS™Û3—P÷—v —˜£¯“¡¡¨¢·“¢°“ÄA¶—Ä1?r™ÄC{—Ä4Ïr—ÄE@—Ä1?r—˜¢¯“¡¡¨¢·“¢°“-™Ä×ßÄb#ÄVoÊÄT!*A¡’Ä#iGĨnÄO;ŽÄwá¼>@¡’˜ r jHÄŽæ ¢ ¨ÅXeroxÅ TiogaFontsÅ Helvetica10£¡ “ •  —¡¡¨  Š¡²“Á attractor– k é r jÇÄ, a ¢ ¨ÅXeroxÅ TiogaFontsÅ Helvetica10£¡ “ •  —¡¡¨  Š¡²“ÁnewSeg1– k é r jÄ+[ ¢ ¨ÅXeroxÅ TiogaFontsÅ Helvetica10£¡ “ •  —¡¡¨  Š¡²“ÁnewSeg2– k é¢¯“¡¡¨¢·“¢°“-™ÄA ­Ä¥W—-—ÄvéÄKЇ—˜¢¯“¡¡¨¢·“¢°“¤3™÷—˜ r j½ý ¢ ¨ÅXeroxÅ TiogaFontsÅ Helvetica10£¡ “ •  —¡¡¨  Š¡²“ÁnewRun– k é r j °“¢¯“¡¡¨ÄbKQÄLî­ÄbKQÄ'ýS¡¹¢¯“¡¡¨ÄbKQÄ'ýSÄMé Ä'ýS¡¹¢¯“¡¡¨ÄMé Ä'ýSÄMé ÄLî­¡¹¢¯“¡¡¨ÄMé ÄLî­ÄbKQÄLî­¡¹ k é r jèÄE[ ¢ ¨ÅXeroxÅ TiogaFontsÅ Helvetica10£¡ “ •  —¡¡¨  Š¡²“ÁnewJoint– k é k é k é k gšœ=™=Jšœ˜J˜Jšœžœ˜ J˜(J˜Jšœ žœ˜Jšœ˜J˜Jšœ žœžœ˜J˜J˜J˜J˜*J–![caret: GGInterfaceTypes.Caret]šœ/˜/Jšžœ žœžœžœ ˜)Jš žœžœžœžœžœ ˜@Jšœ žœ ˜JšœJ˜JJš žœžœ žœžœžœ ˜9Jšžœžœžœ˜4Jšœ6˜6J˜Kšœ œ¢ œ Ÿ˜IJšœ$˜$Jšœ:˜:J˜7J˜'Jšžœžœ,žœžœ˜=Jšžœžœ,žœžœ˜=Jšœ(˜(Jšœ"žœ˜'Jšžœ2žœžœŸ˜OJšœ9™9Jšœ   œ ˜Jšœ  œ"žœ˜@Jšœ œ˜CJšœ#žœžœ#žœ˜VJšœcžœ™iJšœ  œ-žœ˜KJšœ#˜#šžœžœ˜%Jšœ žœ(˜5Jšœ%˜%Jšœ.˜.Jšœ4˜4J˜—šžœ˜Jšœ-˜-Jšœ žœ/˜K–#[ggData: GGInterfaceTypes.GGData]šœ   œ˜&Kš œ1žœžœžœ4žœžœ˜­K˜—šžœžœžœŸ˜5K–#[ggData: GGInterfaceTypes.GGData]šœ   œ˜&Kšžœžœ žœŸ^˜¯Kšžœvžœžœ˜˜Kšœ žœŸ˜>K˜—Kšœs  œ?™¿šžœ$žœ˜,KšœNžœ˜YKšœžœ"žœ˜JKšœ%žœ˜+K˜—šžœ$žœ˜,KšœNžœ˜YKšœžœ"žœ˜JKšœ%žœ˜+K˜—Kšœ-˜-K˜K˜—š œ˜Kšœ žœžœ˜+K–M[feedback: Feedback.FeedbackData, msg: ROPE, msgType: Feedback.MsgType]šœ;˜;K˜K˜—šœ˜#Kšœžœžœ ˜4K˜Kšœ˜K˜K˜—šœ˜!Kšœžœžœ ˜7K˜Kšœ˜K˜—K˜Kšœ˜K˜K˜Kšœžœžœgžœžœžœžœ˜ÌK˜šœžœ˜Kšœ8˜8K˜K™ Kšœ2žœ˜8Kšœ,žœ˜2Kšœ4žœ˜:Kšœ6˜6Kšœ<˜˜>Kšœ,˜,Kšœ0˜0Kšœ0˜0Kšœ6˜6Kšœ4˜4K˜K™Kšœ;žœ˜BKšœ"˜"Kšœ#žœŸ˜GK™K™ Kšœ:˜:Kšœ6˜6Kšœ8˜8Kšœ:˜:Kšœ"˜"Kšœ8˜8Kšœ>˜>Kšœ*˜*Kšœ2™2K™K™Kšœ,˜,Kšœ.˜.Kšœ.˜.Kšœ-˜-Kšœ/˜/Kšœ1˜1Kšœ2˜2Kšœ4˜4Kšœ1˜1Kšœ-˜-Kšœ<˜˜>Kšœ/˜/K˜K˜K™Kšœ(˜(Kšœ&˜&Kšœ6˜6Kšœ4˜4Kšœ2˜2Kšœ2˜2Kšœ4˜4K˜K™ Kšœ"˜"K˜K™Kšœ6˜6Kšœ.žœ˜5Kšœ,žœ˜3Kšœ:˜:Kšœ6˜6Kšœ6˜6K˜K™Kšœ4žœ˜;KšœDžœ˜KKšœ6˜6K™K˜—K˜Kšœžœžœ˜"K˜K˜J˜Kšžœ˜—…—ÏŒ&Ì