DIRECTORY Ascii, BasicTime, CodeTimer, CubicSplines, Feedback, FeedbackTypes, FileNames, GGAlign, GGBasicTypes, GGBoundBox, GGCaret, GGControlPanelTypes, GGCoreOps, GGCoreTypes, GGEvent, GGFileOps, GGFont, GGFromImager, GGHistory, GGHistoryTypes, GGInterfaceTypes, GGModelTypes, GGMultiGravity, GGOutline, GGParent, GGParseOut, GGRefresh, GGRefreshTypes, GGScene, GGSegment, GGSegmentTypes, GGSelect, GGShapes, GGSlice, GGSliceOps, GGState, GGTraj, GGUIUtility, GGUserInput, GGUserProfile, GGUtility, GGWindow, Imager, ImagerInterpress, ImagerTransformation, Interpress, IO, IPMaster, PBasics, PFS, Random, Real, RealFns, Rope, TextNode, TiogaOps, TiogaOpsDefs, Vectors2d; GGEventImplA: CEDAR PROGRAM IMPORTS BasicTime, CodeTimer, Feedback, FileNames, GGAlign, GGBoundBox, GGCaret, GGCoreOps, GGEvent, GGFileOps, GGFont, GGFromImager, GGHistory, GGMultiGravity, GGOutline, GGParent, GGParseOut, GGRefresh, GGScene, GGSegment, GGSelect, GGShapes, GGSlice, GGSliceOps, GGState, GGTraj, GGUIUtility, GGUserInput, GGUserProfile, GGUtility, GGWindow, Imager, ImagerInterpress, ImagerTransformation, Interpress, IO, IPMaster, PBasics, PFS, Random, RealFns, Rope, TiogaOps, Vectors2d EXPORTS GGEvent, GGInterfaceTypes = BEGIN AlignBag: TYPE = GGInterfaceTypes.AlignBag; BoundBox: TYPE = GGModelTypes.BoundBox; Camera: TYPE = GGModelTypes.Camera; Caret: TYPE = GGInterfaceTypes.Caret; ControlsObj: PUBLIC TYPE = GGControlPanelTypes.ControlsObj; -- exported to GGInterfaceTypes ControlPointGenerator: TYPE = GGModelTypes.ControlPointGenerator; DisplayStyle: TYPE = GGModelTypes.DisplayStyle; MsgRouter: TYPE = FeedbackTypes.MsgRouter; FeatureData: TYPE = GGInterfaceTypes.FeatureData; FontData: TYPE = GGFont.FontData; GGData: TYPE = GGInterfaceTypes.GGData; GravityType: TYPE = GGInterfaceTypes.GravityType; HistoryEvent: TYPE = GGHistoryTypes.HistoryEvent; Joint: TYPE = GGModelTypes.Joint; JointGenerator: TYPE = GGModelTypes.JointGenerator; Orientation: TYPE = GGModelTypes.Orientation; Point: TYPE = GGBasicTypes.Point; RefreshDataObj: PUBLIC TYPE = GGRefreshTypes.RefreshDataObj; Scene: TYPE = GGModelTypes.Scene; Segment: TYPE = GGSegmentTypes.Segment; SegmentGenerator: TYPE = GGModelTypes.SegmentGenerator; Slice: TYPE = GGModelTypes.Slice; SliceDescriptor: TYPE = GGModelTypes.SliceDescriptor; SliceDescriptorGenerator: TYPE = GGModelTypes.SliceDescriptorGenerator; SliceDescriptorObj: TYPE = GGModelTypes.SliceDescriptorObj; SliceGenerator: TYPE = GGModelTypes.SliceGenerator; SliceParts: TYPE = GGModelTypes.SliceParts; StrokeEnd: TYPE = Imager.StrokeEnd; StrokeJoint: TYPE = Imager.StrokeJoint; Transformation: TYPE = Imager.Transformation; Traj: TYPE = GGModelTypes.Traj; TrajData: TYPE = GGModelTypes.TrajData; TrajEnd: TYPE = GGModelTypes.TrajEnd; TrajParts: TYPE = GGModelTypes.TrajParts; TriggerBag: TYPE = GGInterfaceTypes.TriggerBag; UserInputProc: TYPE = GGEvent.UserInputProc; Vector: TYPE = GGBasicTypes.Vector; WalkProc: TYPE = GGModelTypes.WalkProc; pointsPerIn: REAL = 72.0; cmPerInch: REAL = 2.54; metersPerPixel: REAL = 0.0254/72.0; reallyBigReal: REAL = 1.0e37; DeleteHoles: UserInputProc = { IF GGSelect.NoSelections[ggData.scene, normal] THEN Feedback.Append[ggData.router, oneLiner, $Complaint, "UnmakeHoles failed: select some composite outlines for Unmake holes"] ELSE { DoDeleteHoles: PROC [sliceD: SliceDescriptor] RETURNS [done: BOOL _ FALSE] = { IF GGSliceOps.IsCompleteParts[sliceD] THEN { DoUnmakeChild: PROC [slice: Slice] RETURNS [done: BOOL _ FALSE] = { newSlice: Slice _ IF GGSliceOps.GetType[slice]=$Traj THEN GGOutline.CreateOutline[slice, fillColor] ELSE slice; newSlice.parent _ cluster; -- reparent BEFORE reselecting newSliceList _ GGUtility.AppendSliceList[newSliceList, LIST[newSlice]]; IF GGSliceOps.GetType[newSlice]=$Box AND firstBoxChild=NIL THEN { firstBoxChild _ newSlice; GGSlice.SetBoxText[newSlice, fillText, screenStyle, NIL]; } ELSE GGSlice.SetBoxText[newSlice, [NIL, 0], screenStyle, NIL]; unmakeCount _ unmakeCount+1; }; fillText: TextNode.Location; screenStyle: BOOL _ FALSE; newSliceList: LIST OF Slice; firstBoxChild: Slice; outline: Slice _ sliceD.slice; cluster: Slice _ GGParent.GetParent[outline]; fillColor: Imager.Color _ GGSliceOps.GetFillColor[outline, NIL].color; [fillText, screenStyle] _ GGSlice.GetBoxText[outline]; -- first box child will inherit GGOutline.SaveSelectionsInOutlineAllClasses[outline]; IF GGScene.IsTopLevel[outline] THEN { -- all children become top level priority: INT _ GGScene.GetPriority[ggData.scene, outline]; GGScene.DeleteSlice[ggData.scene, outline]; -- will replace with exploded version [] _ GGParent.WalkChildren[outline, leaf, DoUnmakeChild]; GGScene.AddSlices[ggData.scene, newSliceList, priority]; reselectList _ GGUtility.AppendSliceList[reselectList, newSliceList]; } ELSE { -- child outline to be replaced by all its children in the parent cluster priority: INT _ GGParent.GetChildPriority[cluster, outline]; [] _ GGSlice.RemoveChild[cluster, outline]; -- will replace with all children [] _ GGParent.WalkChildren[outline, leaf, DoUnmakeChild]; GGSlice.AddChildrenToCluster[cluster, newSliceList, priority]; reselectList _ GGUtility.AppendSliceList[reselectList, LIST[cluster]]; }; } ELSE Feedback.Append[ggData.router, oneLiner, $Complaint, "UnmakeHoles: Ignoring partially selected outline for Unmake holes"]; }; unmakeCount: INT _ 0; reselectList: LIST OF Slice; -- needed because can't reselect while walking selections GGHistory.NewCapture["Unmake holes", ggData];-- capture scene BEFORE UPDATE GGRefresh.SetStartBox[ggData: ggData, dragInProgress: FALSE, selectedParts: TRUE]; GGCaret.NoAttractor[ggData.caret]; GGCaret.SitOn[ggData.caret, NIL]; [] _ GGScene.WalkSelectedSlices[ggData.scene, leaf, DoDeleteHoles, normal, $Outline]; FOR reselects: LIST OF Slice _ reselectList, reselects.rest UNTIL reselects=NIL DO GGSelect.ReselectSliceAllClasses[reselects.first, ggData.scene]; ENDLOOP; GGHistory.PushCurrent[ggData]; -- push captured history event onto history list IF unmakeCount#0 THEN { Feedback.Append[ggData.router, oneLiner, $Feedback, "UnmakeHoles: holes unmade"]; GGWindow.RestoreScreenAndInvariants[paintAction: $ObjectChangedBoundBoxProvided, ggData: ggData, remake: triggerBag, edited: TRUE, okToSkipCapture: FALSE]; } ELSE Feedback.Append[ggData.router, oneLiner, $Complaint, "UnmakeHoles failed: no holes were unmade"]; }; }; GetAddHolesArguments: PROC [scene: Scene, router: MsgRouter] RETURNS [distinguished: Slice, outlinesForHoles: LIST OF Slice, cluster: Slice, success: BOOL _ TRUE] = { DoFindHolesAndReverse: PROC [sliceD: SliceDescriptor] RETURNS [done: BOOL _ FALSE] = { classType: ATOM _ GGSliceOps.GetType[sliceD.slice]; IF classType = $Cluster AND sliceD # ancestorD THEN { abortedDueToCluster _ TRUE; RETURN[TRUE]; }; IF classType = $IP OR classType = $Text THEN { RETURN[TRUE]; }; outlineList _ CONS[sliceD.slice, outlineList]; -- cheap trick to reverse order. }; outlineList: LIST OF Slice; classType: ATOM; aborted: BOOL _ FALSE; abortedDueToCluster: BOOL _ FALSE; ancestorD: SliceDescriptor _ GGScene.LastSelectedSlice[scene, first, normal]; IF GGSliceOps.GetType[ancestorD.slice] = $Cluster THEN { DoFindNonCluster: PROC [childD: SliceDescriptor] RETURNS [done: BOOL _ FALSE] = { IF GGSliceOps.GetType[childD.slice] # $Cluster THEN { distinguished _ childD.slice; done _ TRUE; }; }; aborted _ GGParent.WalkIncludedChildren[ancestorD.slice, ancestorD.parts, all, DoFindNonCluster]; IF NOT aborted THEN ERROR; -- a cluster without any children? } ELSE distinguished _ ancestorD.slice; cluster _ GGParent.GetParent[distinguished]; classType _ GGSliceOps.GetType[distinguished]; IF classType = $IP OR classType = $Text THEN GOTO TextAndIPCannotBeHoles; aborted _ GGScene.WalkSelectedSlices[scene, first, DoFindHolesAndReverse, normal]; IF aborted THEN IF abortedDueToCluster THEN GOTO SomeHolesAreClusters ELSE GOTO TextAndIPCannotBeHoles; outlinesForHoles _ outlineList.rest; IF outlinesForHoles=NIL THEN GOTO NoHolesSelected; EXITS SomeHolesAreClusters => { success _ FALSE; Feedback.Append[router, oneLiner, $Complaint, "MakeHoles failed: select some UNCLUSTERED top level slices FIRST to become new holes"]; }; NoHolesSelected => { success _ FALSE; Feedback.Append[router, oneLiner, $Complaint, "MakeHoles failed: select some CLOSED top level slices FIRST to become new holes"]; }; TextAndIPCannotBeHoles => { success _ FALSE; Feedback.Append[router, oneLiner, $Complaint, "MakeHoles failed: text and IP slices cannot be nor receive holes"]; }; }; AddHoles: UserInputProc = { scene: Scene _ ggData.scene; router: MsgRouter _ ggData.router; IF GGSelect.NoSelections[scene, normal] THEN Feedback.Append[router, oneLiner, $Complaint, "MakeHoles failed: select some closed outlines for MakeHoles"] ELSE { hasFillText, screenStyle, success: BOOL _ FALSE; fillText: TextNode.Location; firstPriority: INT _ -1; -- on top outlinesForHoles, newHoles: LIST OF Slice; distinguished, ancestor, newOutline: Slice; -- will have holes added to it cluster: Slice; -- NIL if this AddHoles takes place at top level [distinguished, outlinesForHoles, cluster, success] _ GetAddHolesArguments[scene, router]; IF NOT success THEN RETURN; GGHistory.NewCapture["Make holes", ggData]; -- capture scene BEFORE UPDATE GGCaret.NoAttractor[ggData.caret]; GGCaret.SitOn[ggData.caret, NIL]; GGRefresh.SetStartBox[ggData: ggData, dragInProgress: FALSE, selectedParts: TRUE]; [fillText, screenStyle] _ GGSlice.GetBoxText[distinguished]; hasFillText _ fillText.node#NIL; ancestor _ GGParent.GetTopLevelAncestor[distinguished]; GGSelect.SaveSelectionsInSliceAllClasses[ancestor, scene]; SELECT GGSliceOps.GetType[distinguished] FROM $Box, $Circle => { -- create a one-child outline theChild: Slice _ GGSliceOps.Copy[distinguished].first; GGSlice.SetBoxText[theChild, [NIL,0], TRUE, NIL]; -- clear out textFill for child [] _ GGSliceOps.SetOrientation[theChild, NIL, ccw, NIL]; -- by convention, theChild is ccw newOutline _ GGOutline.CreateOutline[theChild, GGSliceOps.GetFillColor[theChild, NIL].color]; }; $Outline => { -- distinguished must have a closed first child for now. Mutate it. firstChild: Slice _ GGParent.FirstChild[distinguished, leaf]; IF GGSliceOps.GetType[firstChild]=$Traj THEN { IF NARROW[firstChild.data, TrajData].role#fence THEN { Feedback.Append[router, oneLiner, $Complaint, "MakeHoles failed: extend selection to a CLOSED object LAST to receive new holes"]; RETURN; }; [] _ GGSliceOps.SetOrientation[firstChild, NIL, ccw, NIL]; -- by convention, firstChild is ccw }; newOutline _ distinguished; }; $Traj => { -- uh oh Feedback.Append[router, oneLiner, $Complaint, "MakeHoles failed: encountered top level trajectory. Punting"]; RETURN; }; ENDCASE => ERROR; FOR list: LIST OF Slice _ outlinesForHoles, list.rest UNTIL list = NIL DO nextHole: Slice _ list.first; GGSelect.SaveSelectionsInSliceAllClasses[nextHole, scene]; SELECT GGSliceOps.GetType[nextHole] FROM $Outline => { -- walk outline children as holes. Only one level for now. IF NOT GGSelect.IsSelectedInFull[nextHole, scene, normal] THEN Feedback.Append[router, oneLiner, $Complaint, "MakeHoles: ignoring partially selected outline"] ELSE { DoNewHoles: PROC [slice: Slice] RETURNS [done: BOOL _ FALSE] = { IF GGSliceOps.GetType[slice]=$Traj AND NARROW[slice.data, TrajData].role=open THEN Feedback.Append[router, oneLiner, $Complaint, "MakeHoles: ignoring open hole candidate"] ELSE { [] _ GGSliceOps.SetOrientation[slice, NIL, cw, NIL]; GGSlice.SetBoxText[slice, [NIL,0], TRUE, NIL]; -- lose fillText if you become a hole newHoles _ GGUtility.AppendSliceList[newHoles, LIST[slice]]; }; }; [] _ GGParent.WalkChildren[nextHole, all, DoNewHoles]; -- all the children IF newHoles#NIL THEN GGScene.DeleteSlice[scene, nextHole]; }; }; $Box, $Circle => { [] _ GGSliceOps.SetOrientation[nextHole, NIL, cw, NIL]; GGSlice.SetBoxText[nextHole, [NIL,0], TRUE, NIL]; -- lose fillText if you become a hole newHoles _ GGUtility.AppendSliceList[newHoles, LIST[nextHole]]; GGScene.DeleteSlice[scene, nextHole]; -- get rid of old top level slice }; $Text, $IP, $Cluster => NULL; -- can't be holes $Traj => { Feedback.Append[router, oneLiner, $Complaint, "MakeHoles: ignoring top level trajectory as hole candidate"]; -- shouldn't be any topLevel trajs now }; ENDCASE => ERROR; ENDLOOP; IF newHoles#NIL THEN { IF cluster = NIL THEN { -- distinguished is top level priority: INT _ GGScene.GetPriority[scene, distinguished]; GGScene.DeleteSlice[scene, distinguished]; GGScene.AppendHoles[newOutline, newHoles]; GGScene.AddSlice[scene, newOutline, priority]; GGOutline.SetFillText[newOutline, fillText.node, screenStyle, NIL]; GGSelect.ReselectSliceAllClasses[newOutline, scene]; } ELSE { priority: INT _ GGParent.GetChildPriority[cluster, distinguished]; [] _ GGSlice.RemoveChild[cluster, distinguished]; GGScene.AppendHoles[newOutline, newHoles]; GGSlice.AddChildToCluster[cluster, newOutline, priority]; GGOutline.SetFillText[newOutline, fillText.node, screenStyle, NIL]; GGSelect.ReselectSliceAllClasses[ancestor, scene]; }; GGHistory.PushCurrent[ggData]; -- push captured history event onto history list Feedback.Append[router, oneLiner, $Feedback, "MakeHoles: holes added"]; GGWindow.RestoreScreenAndInvariants[paintAction: $ObjectChangedBoundBoxProvided, ggData: ggData, remake: triggerBag, edited: TRUE, okToSkipCapture: FALSE]; }; }; }; EnterEditingMode: PROC [ggData: GGData, refChar: REF CHAR, withFont: FontData _ NIL] = { CodeTimer.StartInt[$EnterEditingMode, $Gargoyle]; IF refChar^=Ascii.BS THEN { -- try to enter editing of a single selected text slice IF GGScene.CountSelectedSlices[ggData.scene, leaf, normal, $Text]#1 THEN Feedback.Append[ggData.router, oneLiner, $Complaint, "Edit text failed: select a single text slice for editing"] ELSE { -- BS typed when text selected. Open that text for editing sliceDesc: SliceDescriptor _ GGScene.FirstSelectedSlice[ggData.scene, leaf, normal, $Text]; slice: Slice _ sliceDesc.slice; fontData: FontData _ GGSlice.GetFontData[slice]; origin: Point _ ImagerTransformation.Transform[fontData.transform, [0.0, 0.0] ]; -- find the origin of the text string in world coordinates GGHistory.NewCapture["Editing text", ggData]; -- just like a StartAdd, but for text GGRefresh.SetStartBox[ggData: ggData, dragInProgress: FALSE, selectedCPs: TRUE, hotCPs: TRUE, caret: TRUE, attractor: TRUE]; GGCaret.SetAttractor[ggData.caret, origin, [0.0, -1.0], NIL]; -- move the caret to that point in anticipation of terminating CR for this string, but don't set the attractor to the sliceDesc to avoid feedback GGSelect.DeselectSlice[slice: slice, parts: sliceDesc.parts, scene: ggData.scene, selectClass: normal]; ggData.refresh.textInProgress _ slice; -- successfully enter editing mode ggData.refresh.addedObject _ GGParent.GetParent[slice]; IF ggData.refresh.addedObject = NIL THEN ggData.refresh.addedObject _ slice; Feedback.PutF[ggData.router, oneLiner, $Feedback, "Editing text"]; GGWindow.RestoreScreenAndInvariants[paintAction: $SelectionChanged, ggData: ggData, remake: IF GGSelect.IsSelectedInPart[sliceDesc.slice, ggData.scene, hot] THEN triggerBag ELSE sceneBag, edited: FALSE, okToSkipCapture: TRUE]; }; } ELSE { -- start a new text string at the caret IF GGCaret.Exists[ggData.caret] THEN { GGRefresh.SetStartBox[ggData: ggData, dragInProgress: FALSE, selectedCPs: TRUE, caret: TRUE, attractor: TRUE]; GGCaret.NoAttractor[ggData.caret]; -- so the feedback goes away. GGHistory.NewCapture["Editing text", ggData]; -- just like a StartAdd, but for text ggData.refresh.textInProgress _ NewTextSlice[parent: NIL, text: Rope.FromChar[refChar^], ggData: ggData, selectIt: FALSE, withFont: withFont, skipCapture: TRUE]; -- start a new text slice. NewTextSlice deselects all, adds text slice, and refreshes scene Feedback.PutF[ggData.router, oneLiner, $Feedback, "Editing text"]; } ELSE Feedback.Append[ggData.router, oneLiner, $Complaint, "New text failed: caret required for new text origin"]; }; CodeTimer.StopInt[$EnterEditingMode, $Gargoyle]; }; -- end EnterEditingMode AddChar: PUBLIC UserInputProc = { slice, parent: Slice; refChar: REF CHAR; IF NARROW[event.first, ATOM]#$AddChar THEN ERROR; refChar _ NARROW[event.rest.first]; IF ggData.refresh.textInProgress=NIL THEN EnterEditingMode[ggData, refChar] ELSE { -- add to textInProgress CodeTimer.StartInt[$AddChar, $Gargoyle]; slice _ ggData.refresh.textInProgress; parent _ IF ggData.refresh.addedObject = slice THEN NIL ELSE ggData.refresh.addedObject; SELECT refChar^ FROM Ascii.ControlW, Ascii.BS => { GGRefresh.SetStartBox[ggData: ggData, dragInProgress: FALSE, selectedCPs: TRUE, hotCPs: TRUE, caret: TRUE, attractor: TRUE]; GGRefresh.EnlargeStartBox[ggData, GGSliceOps.GetBoundBox[slice], NIL]; GGSlice.BackspaceText[slice: slice, word: refChar^=Ascii.ControlW]; GGWindow.RestoreScreenAndInvariants[paintAction: $ObjectChangedBoundBoxProvided, ggData: ggData, remake: IF GGSelect.IsSelectedInPart[slice, ggData.scene, hot] THEN triggerBag ELSE sceneBag, edited: TRUE, okToSkipCapture: TRUE]; }; Ascii.CR, Ascii.LF => { -- new line. Careful about embedded Text slice caretPos: Point _ GGCaret.GetPoint[ggData.caret]; -- assumes caret already positioned at origin of old line fontData: FontData _ GGSlice.GetFontData[slice]; newVec: Point _ ImagerTransformation.TransformVec[fontData.transform, [0.0, -GGSlice.GetTextLineSpacing[slice] ] ]; newPoint: Point _ [caretPos.x+newVec.x, caretPos.y+newVec.y]; GGRefresh.SetStartBox[ggData: ggData, dragInProgress: FALSE, caret: TRUE, attractor: TRUE]; GGCaret.SetAttractor[ggData.caret, newPoint, [0.0, -1.0], NIL]; GGRefresh.EnlargeStartBox[ggData, GGCaret.BoundBoxOfCaret[ggData.caret, ggData], NIL]; GGEvent.SawTextFinish[ggData, event]; -- terminates input, does PushCurrent for histoy, and MAY refresh screen GGHistory.NewCapture["Editing text", ggData]; -- just like a ContinueAdd, but for text ggData.refresh.textInProgress _ NewTextSlice[parent: parent, text: "", ggData: ggData, selectIt: FALSE, withFont: fontData, skipCapture: FALSE]; -- start a new EMPTY text slice, inheriting the font from the old line. NewTextSlice adds slice and refreshes scene GGSlice.SetTextLineSpacing[ggData.refresh.textInProgress, GGSlice.GetTextLineSpacing[slice], NIL]; }; ENDCASE => { -- any other char isHot: BOOL _ GGSelect.IsSelectedInPart[slice, ggData.scene, hot]; GGSlice.AppendText[slice: slice, text: Rope.FromChar[refChar^] ]; -- this can only make the slice bound box LARGER GGRefresh.NullStartBox[ggData]; GGRefresh.EnlargeStartBox[ggData, GGSliceOps.GetBoundBox[slice], NIL]; GGWindow.RestoreScreenAndInvariants[paintAction: IF isHot THEN $ObjectChangedBoundBoxProvided ELSE $ObjectAdded, ggData: ggData, remake: IF isHot THEN triggerBag ELSE none, edited: TRUE, okToSkipCapture: TRUE]; }; CodeTimer.StopInt[$AddChar, $Gargoyle]; }; }; -- end AddChar NewTextSlice: PRIVATE PROC [parent: Slice, text: Rope.ROPE, ggData: GGData, selectIt: BOOL _ FALSE, withFont: FontData _ NIL, skipCapture: BOOL _ FALSE] RETURNS [slice: Slice] = { caretPos: Point _ GGCaret.GetPoint[ggData.caret]; success: BOOL _ FALSE; camera: Camera _ ggData.camera; fontData: FontData _ GGFont.CopyFontData[IF withFont=NIL THEN GGState.GetDefaultFont[ggData] ELSE withFont]; slice _ GGSlice.MakeTextSlice[text, ggData.defaults.textColor, camera.displayStyle, 1.0, ggData.defaults.dropShadowOn, ggData.defaults.dropShadowOffset, ggData.defaults.dropShadowColor]; fontData.transform _ ImagerTransformation.TranslateTo[fontData.transform, caretPos]; success _ GGSlice.SetTextFontAndTransform[slice, fontData, ggData.router, NIL]; IF NOT success THEN ERROR; IF parent=NIL THEN { GGScene.AddSlice[ggData.scene, slice, -1]; ggData.refresh.addedObject _ slice; } ELSE { GGSlice.AddChildToCluster[parent, slice, -1]; ggData.refresh.addedObject _ parent; }; GGRefresh.EnlargeStartBox[ggData, GGSliceOps.GetBoundBox[slice], NIL]; GGSelect.DeselectAll[ggData.scene, normal]; -- speeds up text entry IF selectIt THEN GGEvent.SelectEntireSlice[slice, ggData.scene, normal, ggData]; GGWindow.RestoreScreenAndInvariants[paintAction: $ObjectAdded, ggData: ggData, remake: sceneBag, edited: TRUE, okToSkipCapture: skipCapture]; }; AddText: UserInputProc = { text: Rope.ROPE _ NARROW[event.rest.first]; IF Rope.Equal[text, NIL] THEN Feedback.Append[ggData.router, oneLiner, $Complaint, "Add text failed: select a non-empty string for AddText"] ELSE { GGHistory.NewCapture["Add text", ggData]; -- capture scene BEFORE UPDATE GGRefresh.NullStartBox[ggData]; [] _ NewTextSlice[parent: NIL, text: text, ggData: ggData, selectIt: TRUE, skipCapture: FALSE]; GGHistory.PushCurrent[ggData]; }; }; SetAmplifySpace: UserInputProc = { AmplifySpaceFromSelection[ggData, event]; }; AmplifySpaceFromSelection: UserInputProc = { amplifySpace: REAL _ NARROW[event.rest.first, REF REAL]^; count: INT _ GGScene.CountSelectedSlices[ggData.scene, leaf, normal, $Text]; IF count<1 THEN Feedback.Append[ggData.router, oneLiner, $Complaint, "AmplifySpace failed: select at least one text object to amplify space"] ELSE IF amplifySpace>reallyBigReal THEN Feedback.Append[ggData.router, oneLiner, $Complaint, "AmplifySpace failed: select a reasonable number for amplify space"] ELSE { DoAmplifySpace: PROC [sliceD: SliceDescriptor] RETURNS [done: BOOL _ FALSE] = { someD _ sliceD; GGSlice.SetTextAmplifySpace[sliceD.slice, amplifySpace, ggData.router, currentEvent]; [newSelectList, ptr] _ GGUtility.AddSlice[sliceD.slice, newSelectList, ptr]; }; currentEvent: HistoryEvent; someD: SliceDescriptor; newSelectList, ptr: LIST OF Slice; [newSelectList, ptr] _ GGUtility.StartSliceList[]; GGRefresh.SetStartBox[ggData: ggData, dragInProgress: FALSE, selectedParts: TRUE]; currentEvent _ GGHistory.NewCurrent["Amplify space", ggData]; [] _ GGScene.WalkSelectedSlices[ggData.scene, leaf, DoAmplifySpace, normal, $Text]; 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.PutF[ggData.router, oneLiner, $Feedback, "AmplifySpace: amplify space set to %g", [real[amplifySpace]] ]; GGWindow.RestoreScreenAndInvariants[paintAction: $ObjectChangedBoundBoxProvided, ggData: ggData, remake: triggerBag, edited: TRUE, okToSkipCapture: FALSE]; -- GGSlice.SetTextAmplifySpace can post error messages }; }; PrintAmplifySpace: UserInputProc = { count: INT _ GGScene.CountSelectedSlices[ggData.scene, leaf, normal, $Text]; IF count<1 THEN Feedback.Append[ggData.router, oneLiner, $Complaint, "ShowAmplifySpace failed: select at least one text object to show amplify space"] ELSE { CheckAmplifySpace: PROC [sliceD: SliceDescriptor] RETURNS [done: BOOL _ FALSE] = { IF amplifySpace = reallyBigReal THEN amplifySpace _ GGSlice.GetTextAmplifySpace[sliceD.slice]; RETURN[amplifySpace#GGSlice.GetTextAmplifySpace[sliceD.slice]]; -- abort if multiple values }; amplifySpace: REAL _ reallyBigReal; aborted: BOOL _ GGScene.WalkSelectedSlices[ggData.scene, leaf, CheckAmplifySpace, normal, $Text]; IF aborted THEN Feedback.Append[ggData.router, oneLiner, $Complaint, "ShowAmplifySpace failed: multiple amplification values selected"] ELSE Feedback.PutF[ggData.router, oneLiner, $Feedback, "ShowAmplifySpace: amplification is %g", [real[amplifySpace]]]; }; }; SetDropShadow: UserInputProc = { scale: REAL _ 10.0; -- convert fractions to offsets needed by SetDropShadow offsetX: REAL _ NARROW[event.rest.first, REF REAL]^; offsetY: REAL _ NARROW[event.rest.rest.first, REF REAL]^; count: INT _ GGScene.CountSelectedSlices[ggData.scene, leaf, normal, $Text]; IF count<1 THEN Feedback.Append[ggData.router, oneLiner, $Complaint, "SetDropShadow failed: select at least one text object to set drop shadow"] ELSE IF offsetX>reallyBigReal OR offsetY>reallyBigReal THEN Feedback.Append[ggData.router, oneLiner, $Complaint, "SetDropShadow failed: select reasonable values for drop shadow offsets"] ELSE { DoSetDropShadow: PROC [sliceD: SliceDescriptor] RETURNS [done: BOOL _ FALSE] = { someD _ sliceD; GGSlice.DropShadowOn[sliceD.slice, [offsetX, offsetY], currentEvent]; -- elements are signed and 10X, like [-5.0, -2.5] for 50% left and 25% down [newSelectList, ptr] _ GGUtility.AddSlice[sliceD.slice, newSelectList, ptr]; }; currentEvent: HistoryEvent; someD: SliceDescriptor; newSelectList, ptr: LIST OF Slice; [newSelectList, ptr] _ GGUtility.StartSliceList[]; GGRefresh.SetStartBox[ggData: ggData, dragInProgress: FALSE, selectedParts: TRUE]; currentEvent _ GGHistory.NewCurrent["Set drop shadow", ggData]; -- start new history event [] _ GGScene.WalkSelectedSlices[ggData.scene, leaf, DoSetDropShadow, normal, $Text]; 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.PutF[ggData.router, oneLiner, $Feedback, "SetDropShadow: drop shadow set to [%g, %g]", [real[offsetX/scale]], [real[offsetY/scale]] ]; GGWindow.RestoreScreenAndInvariants[paintAction: $ObjectChangedBoundBoxProvided, ggData: ggData, remake: triggerBag, edited: TRUE, okToSkipCapture: FALSE]; }; }; DropShadowFromSelection: UserInputProc = { scale: REAL _ 10.0; -- convert fractions to offsets needed by SetDropShadow offset: REAL _ NARROW[event.rest.first, REF REAL]^; -- positive fraction IF offset>reallyBigReal THEN Feedback.Append[ggData.router, oneLiner, $Complaint, "SetDropShadow failed: select reasonable values for drop shadow offsets"] ELSE SetDropShadow[ggData, LIST [event.first, NEW[REAL _ -offset*scale], NEW[REAL _ -offset*scale] ] ]; -- down and left fraction }; DropShadowTenPercent: UserInputProc = { scale: REAL _ 10.0; -- convert fractions to offsets needed by SetDropShadow offset: REAL _ 0.1; -- positive fraction SetDropShadow[ggData, LIST [event.first, NEW[REAL _ -offset*scale], NEW[REAL _ -offset*scale] ] ]; -- down and left fraction }; PrintDropShadow: UserInputProc = { count: INT _ GGScene.CountSelectedSlices[ggData.scene, leaf, normal, $Text]; IF count<1 THEN Feedback.Append[ggData.router, oneLiner, $Complaint, "ShowDropShadow failed: select at least one text object to show drop shadow"] ELSE { CheckDropShadow: PROC [sliceD: SliceDescriptor] RETURNS [done: BOOL _ FALSE] = { IF firstOffset.x=reallyBigReal THEN [firstOn, firstOffset] _ GGSlice.GetTextDropShadow[sliceD.slice]; [on, offset] _ GGSlice.GetTextDropShadow[sliceD.slice]; RETURN[firstOn#on OR firstOffset#offset]; -- abort if multiple values }; scale: REAL _ 10.0; -- convert fractions to offsets needed firstOn, on: BOOL _ FALSE; firstOffset, offset: Vector _ [reallyBigReal, reallyBigReal]; aborted: BOOL _ GGScene.WalkSelectedSlices[ggData.scene, leaf, CheckDropShadow, normal, $Text]; IF aborted THEN Feedback.Append[ggData.router, oneLiner, $Complaint, "ShowDropShadow failed: multiple drop shadow values selected"] ELSE { feedbackRope: Rope.ROPE _ SELECT TRUE FROM NOT on => "ShowDropShadows: no drop shadows", ENDCASE => IO.PutFR["ShowDropShadows: drop shadows are [%g, %g]", [real[offset.x/scale]], [real[offset.y/scale]] ]; Feedback.PutF[ggData.router, oneLiner, $Show, feedbackRope ]; }; }; }; DropShadowOff: UserInputProc = { count: INT _ GGScene.CountSelectedSlices[ggData.scene, leaf, normal, $Text]; IF count<1 THEN Feedback.Append[ggData.router, oneLiner, $Complaint, "DropShadowOff failed: select at least one text object to clear drop shadow"] ELSE { DoDropShadowOff: PROC [sliceD: SliceDescriptor] RETURNS [done: BOOL _ FALSE] = { someD _ sliceD; GGSlice.DropShadowOff[sliceD.slice, currentEvent]; [newSelectList, ptr] _ GGUtility.AddSlice[sliceD.slice, newSelectList, ptr]; }; currentEvent: HistoryEvent; someD: SliceDescriptor; newSelectList, ptr: LIST OF Slice; [newSelectList, ptr] _ GGUtility.StartSliceList[]; GGRefresh.SetStartBox[ggData: ggData, dragInProgress: FALSE, selectedParts: TRUE]; currentEvent _ GGHistory.NewCurrent["Drop shadow off", ggData]; -- start new history event [] _ GGScene.WalkSelectedSlices[ggData.scene, leaf, DoDropShadowOff, normal, $Text]; FOR list: LIST OF Slice _ newSelectList, list.rest UNTIL list = NIL DO GGSelect.SelectEntireSlice[list.first, ggData.scene, normal]; ENDLOOP; GGHistory.PushCurrent[ggData]; Feedback.PutF[ggData.router, oneLiner, $Feedback, "DropShadowOff: completed"]; GGWindow.RestoreScreenAndInvariants[paintAction: $ObjectChangedBoundBoxProvided, ggData: ggData, remake: triggerBag, edited: TRUE, okToSkipCapture: FALSE]; }; }; SetDefaultTextLooks: UserInputProc = { count: INT _ GGScene.CountSelectedSlices[ggData.scene, leaf, normal, $Text]; IF count<1 THEN Feedback.Append[ggData.router, oneLiner, $Complaint, "MakeDefaultTextLooks failed: select at least one text object to specify defaults"] ELSE { CheckTextLooks: PROC [sliceD: SliceDescriptor] RETURNS [done: BOOL _ FALSE] = { looksLike _ sliceD.slice; IF looksRope=NIL THEN looksRope _ GGSlice.GetLooksDataRope[looksLike]; RETURN[NOT Rope.Equal[looksRope, GGSlice.GetLooksDataRope[looksLike], FALSE]]; -- abort if multiple values }; looksRope: Rope.ROPE; looksLike: Slice; aborted: BOOL _ GGScene.WalkSelectedSlices[ggData.scene, leaf, CheckTextLooks, normal, $Text]; IF aborted THEN Feedback.Append[ggData.router, oneLiner, $Complaint, "MakeDefaultTextLooks failed: multiple look values selected"] ELSE { [ggData.defaults.dropShadowOn, ggData.defaults.dropShadowOffset, ggData.defaults.dropShadowColor] _ GGSlice.GetTextDropShadow[looksLike]; ggData.defaults.textColor _ GGSlice.GetTextColors[looksLike].textColor; Feedback.PutF[ggData.router, oneLiner, $Feedback, "MakeDefaultTextLooks: default text looks set to %g", [rope[GGSlice.GetLooksDataRope[looksLike]]] ]; }; }; }; ShowDefaultTextLooks: PUBLIC UserInputProc = { scale: REAL _ 10.0; -- convert fractions to offsets scratch: IO.STREAM _ IO.ROS[]; IF ggData.defaults.textColor#NIL THEN scratch.PutF["text color is %g ", [rope[GGUtility.DescribeColor[ggData.defaults.textColor]]] ] ELSE scratch.PutF["%g", [rope["no text color "]]]; IF ggData.defaults.dropShadowOn THEN { IF ggData.defaults.dropShadowColor#NIL THEN scratch.PutF["shadow color is %g ", [rope[GGUtility.DescribeColor[ggData.defaults.dropShadowColor]]] ] ELSE scratch.PutF["%g", [rope["no shadow color "]]]; scratch.PutF[" offset: [%g, %g]", [real[ggData.defaults.dropShadowOffset.x/scale]], [real[ggData.defaults.dropShadowOffset.y/scale]] ]; }; Feedback.PutF[ggData.router, oneLiner, $Show, "Default Text Looks: %g", [rope[IO.RopeFromROS[scratch]]] ]; }; SetFontAux: PROC [ggData: GGData, fontData: FontData, andTransform: BOOL _ FALSE] = { count: INT _ GGScene.CountSelectedSlices[ggData.scene, leaf, normal, $Text]; IF count<1 THEN Feedback.Append[ggData.router, oneLiner, $Complaint, "SetFont failed: select at least one text object to set font"] ELSE { DoSetFontAux: PROC [sliceD: SliceDescriptor] RETURNS [done: BOOL _ FALSE] = { success _ IF andTransform THEN GGSlice.SetTextFontAndTransform[sliceD.slice, fontData, ggData.router, currentEvent] ELSE GGSlice.SetTextFont[sliceD.slice, fontData, ggData.router, currentEvent]; IF NOT success THEN RETURN [TRUE]; -- SetTextFont will have posted an error [newSelectList, ptr] _ GGUtility.AddSlice[sliceD.slice, newSelectList, ptr]; someD _ sliceD; }; aborted: BOOL _ FALSE; currentEvent: HistoryEvent; someD: SliceDescriptor; success: BOOL _ FALSE; newSelectList, ptr: LIST OF Slice; [newSelectList, ptr] _ GGUtility.StartSliceList[]; GGRefresh.SetStartBox[ggData: ggData, dragInProgress: FALSE, selectedParts: TRUE]; currentEvent _ GGHistory.NewCurrent["Set font", ggData]; -- start new history event aborted _ GGScene.WalkSelectedSlices[ggData.scene, leaf, DoSetFontAux, normal, $Text]; 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]; -- push captured history event onto history list IF aborted AND someD#NIL THEN Feedback.Append[ggData.router, oneLiner, $Complaint, "SetFont problem: some text slices not set"]; IF NOT aborted THEN Feedback.PutF[ggData.router, oneLiner, $Feedback, "SetFont: font %g", [rope[GGSlice.GetFontDataRope[someD.slice]]] ]; GGWindow.RestoreScreenAndInvariants[paintAction: $ObjectChangedBoundBoxProvided, ggData: ggData, remake: triggerBag, edited: TRUE, okToSkipCapture: FALSE]; }; }; SetPressFont: UserInputProc = { errorRope: Rope.ROPE; BEGIN inStream: IO.STREAM _ IO.RIS[NARROW[event.rest.first]]; -- "Cream-BI 12" fontData: FontData _ GGFont.CreateFontData[]; fontData.prefix _ "xerox/pressfonts/"; -- default prefix for SetPressFont fontData _ GGFont.ParseFontData[data: fontData, inStream: inStream, familyP: TRUE, faceP: TRUE, scaleP: TRUE ! GGFont.ParseError => {errorRope _ explanation; GOTO ParseError;};]; -- family, face, scale SetFontAux[ggData, fontData]; EXITS ParseError => Feedback.Append[ggData.router, oneLiner, $Complaint, Rope.Concat["SetPressFont failed: ", errorRope]]; END; }; SetPrintFont: UserInputProc = { errorRope: Rope.ROPE; BEGIN inStream: IO.STREAM _ IO.RIS[NARROW[event.rest.first]]; -- "Modern-BI 12" fontData: FontData _ GGFont.CreateFontData[]; fontData.prefix _ "xerox/xc1-2-2/"; -- default prefix for SetPrintFont fontData _ GGFont.ParseFontData[data: fontData, inStream: inStream, familyP: TRUE, faceP: TRUE, scaleP: TRUE ! GGFont.ParseError => {errorRope _ explanation; GOTO ParseError;};]; -- family, face, scale SetFontAux[ggData, fontData]; EXITS ParseError => Feedback.Append[ggData.router, oneLiner, $Complaint, Rope.Concat["SetXCFont failed: ", errorRope]]; END; }; SetScreenFont: UserInputProc = { errorRope: Rope.ROPE; BEGIN inStream: IO.STREAM _ IO.RIS[NARROW[event.rest.first]]; -- "Cream-BI 12" fontData: FontData _ GGFont.CreateFontData[]; fontData.prefix _ "xerox/tiogafonts/"; -- default prefix for SetScreenFont fontData _ GGFont.ParseFontData[data: fontData, inStream: inStream, familyP: TRUE, faceP: TRUE, scaleP: TRUE ! GGFont.ParseError => {errorRope _ explanation; GOTO ParseError;};]; -- family, face, scale SetFontAux[ggData, fontData]; EXITS ParseError => Feedback.Append[ggData.router, oneLiner, $Complaint, Rope.Concat["SetScreenFont failed: ", errorRope]]; END; }; SetFontDetailed: UserInputProc = { errorRope: Rope.ROPE; BEGIN inStream: IO.STREAM _ IO.RIS[NARROW[event.rest.first]]; fontData: FontData _ GGFont.CreateFontData[]; fontData _ GGFont.ParseFontData[data: fontData, inStream: inStream, prefixP: TRUE, familyP: TRUE, faceP: TRUE, transformP: TRUE ! GGFont.ParseError => {errorRope _ explanation; GOTO ParseError;};]; -- prefix, family, face, transform SetFontAux[ggData, fontData]; EXITS ParseError => Feedback.Append[ggData.router, oneLiner, $Complaint, Rope.Concat["SetDetailedFont failed: ", errorRope]]; END; }; SetFontLiteral: UserInputProc = { errorRope: Rope.ROPE; BEGIN inStream: IO.STREAM _ IO.RIS[NARROW[event.rest.first]]; fontData: FontData _ GGFont.CreateFontData[]; fontData _ GGFont.ParseFontData[data: fontData, inStream: inStream, literalP: TRUE, transformP: TRUE, storedSizeP: TRUE, designSizeP: TRUE ! GGFont.ParseError => {errorRope _ explanation; GOTO ParseError;};]; -- literal name, transform, storedSize, designSize SetFontAux[ggData, fontData]; EXITS ParseError => Feedback.Append[ggData.router, oneLiner, $Complaint, Rope.Concat["SetLiteralFont failed: ", errorRope]]; END; }; ShowFontValues: UserInputProc = { success: BOOL _ FALSE; fontRope: Rope.ROPE; [fontRope, ----, success] _ GetSelectedFont[ggData, "ShowFont", GGSlice.GetFontDataRope]; IF NOT success THEN RETURN; Feedback.PutF[ggData.router, oneLiner, $Show, "ShowFont: %g", [rope[fontRope]]]; }; ShowFontValuesLiteral: UserInputProc = { success: BOOL _ FALSE; fontRope: Rope.ROPE; [fontRope, ----, success] _ GetSelectedFont[ggData, "ShowLiteralFont", GGSlice.GetFontLiteralDataRope]; IF NOT success THEN RETURN; Feedback.PutF[ggData.router, oneLiner, $Show, "ShowLiteralFont: %g", [rope[fontRope]]]; }; CopyFont: UserInputProc = { scene: Scene _ ggData.scene; lastDesc: SliceDescriptor _ GGSelect.GetLastSelection[scene]; count: INT _ GGScene.CountSelectedSlices[ggData.scene, leaf, normal, $Text]; IF count<2 OR lastDesc=NIL THEN Feedback.Append[ggData.router, oneLiner, $Complaint, "CopyFont failed: select at least one destination and then one source text object for copy font"] ELSE { BEGIN alikeData: FontData; DoCopyFont: PROC [sliceD: SliceDescriptor] RETURNS [done: BOOL _ FALSE] = { IF sliceD#lastDesc THEN { saveTransform: ImagerTransformation.Transformation; oldData, copyData: FontData; oldData _ GGSlice.GetFontData[sliceD.slice]; saveTransform _ oldData.transform; -- shouldn't need to copy transformation copyData _ GGFont.CopyFontData[data: alikeData, oldCopy: oldData]; -- clobbers oldData^ !! copyData.transform _ saveTransform; success _ GGSlice.SetTextFontAndTransform[sliceD.slice, copyData, ggData.router, currentEvent]; IF NOT success THEN RETURN; -- SetTextFont will have posted an error [newSelectList, ptr] _ GGUtility.AddSlice[sliceD.slice, newSelectList, ptr]; }; }; success: BOOL _ FALSE; alikeRope: Rope.ROPE; currentEvent: HistoryEvent; newSelectList, ptr: LIST OF Slice; [alikeRope, alikeData, success] _ GetSelectedFontInSlice[lastDesc, "CopyFont", GGSlice.GetFontDataRope, ggData.router]; IF NOT success THEN RETURN; currentEvent _ GGHistory.NewCurrent["Copy font", ggData]; [newSelectList, ptr] _ GGUtility.StartSliceList[]; GGRefresh.SetStartBox[ggData: ggData, dragInProgress: FALSE, selectedParts: TRUE]; [] _ GGScene.WalkSelectedSlices[scene: ggData.scene, level: leaf, walkProc: DoCopyFont, selectClass: normal, classType: $Text]; 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]; -- push captured history event onto history list Feedback.PutF[ggData.router, oneLiner, $Feedback, "Copied font %g to %g text objects", [rope[alikeRope]], [integer[count-1]] ]; GGWindow.RestoreScreenAndInvariants[paintAction: $ObjectChangedBoundBoxProvided, ggData: ggData, remake: triggerBag, edited: TRUE, okToSkipCapture: FALSE]; END; }; }; FontRopeProc: TYPE = PROC [slice: Slice] RETURNS [Rope.ROPE]; GetSelectedFontInSlice: PROC [sliceD: SliceDescriptor, opName: Rope.ROPE, fontRopeProc: FontRopeProc, router: MsgRouter] RETURNS [fontRope: Rope.ROPE, fontData: FontData, success: BOOL _ TRUE] = { aborted: BOOL _ FALSE; DoCheckFontValues: PROC [leafD: SliceDescriptor] RETURNS [done: BOOL _ FALSE] = { IF fontRope=NIL THEN { fontRope _ fontRopeProc[leafD.slice]; -- first font rope fontData _ GGSlice.GetFontData[leafD.slice]; } ELSE done _ NOT Rope.Equal[fontRope, fontRopeProc[leafD.slice], FALSE]; }; IF GGParent.IsParent[sliceD.slice] THEN [] _ GGParent.WalkIncludedChildren[sliceD.slice, sliceD.parts, leaf, DoCheckFontValues, $Text] ELSE { IF GGSliceOps.GetType[sliceD.slice] = $Text THEN { fontRope _ fontRopeProc[sliceD.slice]; fontData _ GGSlice.GetFontData[sliceD.slice]; }; }; IF aborted THEN { success _ FALSE; Feedback.PutF[router, oneLiner, $Complaint, "%g failed: multiple fonts are selected", [rope[opName]] ]; } ELSE IF fontRope = NIL THEN { success _ FALSE; Feedback.PutF[router, oneLiner, $Complaint, "%g failed: no fonts are selected", [rope[opName]] ]; }; }; GetSelectedFont: PROC [ggData: GGData, opName: Rope.ROPE, fontRopeProc: FontRopeProc] RETURNS [fontRope: Rope.ROPE, fontData: FontData, success: BOOL _ TRUE] = { sliceD: SliceDescriptor _ NIL; aborted: BOOL _ FALSE; scene: Scene _ ggData.scene; DoCheckFontValues: PROC [sliceD: SliceDescriptor] RETURNS [done: BOOL _ FALSE] = { IF fontRope=NIL THEN { fontRope _ fontRopeProc[sliceD.slice]; -- first font rope fontData _ GGSlice.GetFontData[sliceD.slice]; } ELSE done _ NOT Rope.Equal[fontRope, fontRopeProc[sliceD.slice], FALSE]; }; aborted _ GGScene.WalkSelectedSlices[scene, leaf, DoCheckFontValues, normal, $Text]; IF aborted THEN { success _ FALSE; Feedback.PutF[ggData.router, oneLiner, $Complaint, "%g failed: multiple fonts are selected", [rope[opName]] ]; } ELSE IF fontRope = NIL THEN { success _ FALSE; Feedback.PutF[ggData.router, oneLiner, $Complaint, "%g failed: no fonts are selected", [rope[opName]] ]; }; }; CopyAll: UserInputProc = { scene: Scene _ ggData.scene; count: INT _ GGScene.CountSelectedSlices[ggData.scene, leaf, normal, $Text]; lastDesc: SliceDescriptor _ GGSelect.GetLastSelection[scene]; IF count<2 OR lastDesc=NIL THEN Feedback.Append[ggData.router, oneLiner, $Complaint, "CopyFont failed: select at least one destination and one source text object for copy font"] ELSE { BEGIN DoCopyAll: PROC [sliceD: SliceDescriptor] RETURNS [done: BOOL _ FALSE] = { oldTVec: ImagerTransformation.VEC; oldData, copyData: FontData; oldData _ GGSlice.GetFontData[sliceD.slice]; oldTVec _ ImagerTransformation.Factor[oldData.transform].t; copyData _ GGFont.CopyFontData[data: alikeData, oldCopy: oldData]; -- clobbers oldData^ !! copyData.transform _ ImagerTransformation.TranslateTo[copyData.transform, oldTVec]; success _ GGSlice.SetTextFontAndTransform[sliceD.slice, copyData, ggData.router, currentEvent]; IF NOT success THEN RETURN; -- SetTextFont will have posted an error [newSelectList, ptr] _ GGUtility.AddSlice[sliceD.slice, newSelectList, ptr]; }; success: BOOL _ FALSE; alikeRope: Rope.ROPE; alikeData: FontData; currentEvent: HistoryEvent; newSelectList, ptr: LIST OF Slice; [alikeRope, alikeData, success] _ GetSelectedFontInSlice[lastDesc, "CopyFontAndTransform", GGSlice.GetFontDataRope, ggData.router]; IF NOT success THEN RETURN; currentEvent _ GGHistory.NewCurrent["Copy font", ggData]; [newSelectList, ptr] _ GGUtility.StartSliceList[]; GGRefresh.SetStartBox[ggData: ggData, dragInProgress: FALSE, selectedParts: TRUE]; [] _ GGScene.WalkSelectedSlices[scene, leaf, DoCopyAll, normal, $Text]; FOR list: LIST OF Slice _ newSelectList, list.rest UNTIL list = NIL DO GGSelect.SelectEntireSlice[list.first, scene, normal]; ENDLOOP; GGRefresh.EnlargeStartBox[ggData, GGScene.BoundBoxOfSelected[ggData.scene, normal], NIL]; GGHistory.PushCurrent[ggData]; -- push captured history event onto history list Feedback.PutF[ggData.router, oneLiner, $Feedback, "Copied font and transformation %g to %g text objects", [rope[alikeRope]], [integer[count-1]] ]; GGWindow.RestoreScreenAndInvariants[paintAction: $ObjectChangedBoundBoxProvided, ggData: ggData, remake: triggerBag, edited: TRUE, okToSkipCapture: FALSE]; END; }; }; MatchAll: UserInputProc = { count: INT _ GGScene.CountSelectedSlices[ggData.scene, leaf, normal, $Text]; IF count<1 THEN Feedback.Append[ggData.router, oneLiner, $Complaint, "MatchFont failed: select at least one text object to match font"] ELSE { CheckMatchAll: PROC [sliceD: SliceDescriptor] RETURNS [done: BOOL _ FALSE] = { IF fontData=NIL THEN fontData _ GGSlice.GetFontData[sliceD.slice]; RETURN[NOT IsMatching[fontData, GGSlice.GetFontData[sliceD.slice]]]; -- abort if multiple values }; maxPixels: REAL = 10000.0; fontData: FontData; aborted: BOOL _ GGScene.WalkSelectedSlices[ggData.scene, leaf, CheckMatchAll, normal, $Text]; IF aborted THEN Feedback.Append[ggData.router, oneLiner, $Complaint, "MatchFont failed: multiple fonts selected"] ELSE { CheckMatch: PROC [slice: Slice] RETURNS [done: BOOL _ FALSE] = { IF IsMatching[fontData, GGSlice.GetFontData[slice]] THEN GGEvent.SelectEntireSlice[slice, ggData.scene, normal, ggData]; }; GGRefresh.SetStartBox[ggData: ggData, dragInProgress: FALSE, selectedCPs: TRUE]; GGSelect.DeselectAll[ggData.scene, normal]; [] _ GGScene.WalkSlices[scene: ggData.scene, level: leaf, walkProc: CheckMatch, classType: $Text]; Feedback.PutF[ggData.router, oneLiner, $Feedback, "MatchFont: matched %g", [rope[fontData.literal]] ]; GGWindow.RestoreScreenAndInvariants[paintAction: $SelectionChanged, ggData: ggData, remake: none, edited: FALSE, okToSkipCapture: FALSE]; }; }; }; IsMatching: PROC [aData, bData: FontData] RETURNS [BOOL] = { maxPixels: REAL = 10000.0; RETURN[Rope.Equal[aData.literal, bData.literal, FALSE] AND RealFns.AlmostEqual[aData.storedSize, bData.storedSize, -9] AND ImagerTransformation.CloseToTranslation[aData.transform, bData.transform, maxPixels] ]; }; MatchSelectedName: UserInputProc = { matchRope: Rope.ROPE _ NARROW[event.rest.first]; IF Rope.Equal[matchRope, NIL] THEN Feedback.Append[ggData.router, oneLiner, $Complaint, "MatchFontName failed: select a substring for font matching"] ELSE SelectMatching[matchRope, ggData, $userFSF]; }; MatchSelectedNameLiteral: UserInputProc = { matchRope: Rope.ROPE _ NARROW[event.rest.first]; IF Rope.Equal[matchRope, NIL] THEN Feedback.Append[ggData.router, oneLiner, $Complaint, "MatchFontName failed: select a substring for font literal matching"] ELSE SelectMatching[matchRope, ggData, $literal]; }; SelectMatching: PROC [matchRope: Rope.ROPE, ggData: GGData, op: ATOM] = { DoSelectMatching: PROC [slice: Slice] RETURNS [done: BOOL _ FALSE] = { nextData: FontData _ GGSlice.GetFontData[slice]; IF Rope.Find[s1: IF op=$userFSF THEN nextData.userFSF ELSE nextData.literal, s2: matchRope, pos1: 0, case: FALSE]#-1 THEN GGEvent.SelectEntireSlice[slice, ggData.scene, normal, ggData]; }; GGRefresh.SetStartBox[ggData: ggData, dragInProgress: FALSE, selectedCPs: TRUE]; GGSelect.DeselectAll[ggData.scene, normal]; [] _ GGScene.WalkSlices[ggData.scene, leaf, DoSelectMatching, $Text]; Feedback.PutF[ggData.router, oneLiner, $Feedback, "MatchFontName: text slices with fonts matching %g selected", [rope[matchRope]] ]; GGWindow.RestoreScreenAndInvariants[paintAction: $SelectionChanged, ggData: ggData, remake: none, edited: FALSE, okToSkipCapture: FALSE]; }; SetDefaultFontValues: UserInputProc = { count: INT _ GGScene.CountSelectedSlices[ggData.scene, leaf, normal, $Text]; IF count<1 THEN Feedback.Append[ggData.router, oneLiner, $Complaint, "MakeDefaultFont failed: select at least one text object to specify default font"] ELSE { CheckTextFonts: PROC [sliceD: SliceDescriptor] RETURNS [done: BOOL _ FALSE] = { IF fontData=NIL THEN fontData _ GGSlice.GetFontData[sliceD.slice]; RETURN[NOT IsMatching[fontData, GGSlice.GetFontData[sliceD.slice]]]; -- abort if multiple values }; fontData: FontData; aborted: BOOL _ GGScene.WalkSelectedSlices[ggData.scene, leaf, CheckTextFonts, normal, $Text]; IF aborted THEN Feedback.Append[ggData.router, oneLiner, $Complaint, "MakeDefaultFont failed: multiple fonts selected"] ELSE { GGState.SetDefaultFont[ggData, GGFont.CopyFontData[fontData]]; Feedback.PutF[ggData.router, oneLiner, $Feedback, "MakeDefaultFont: default text font set to %g", [rope[fontData.literal]] ]; }; }; }; SetDefaultFont: UserInputProc = { errorRope: Rope.ROPE; BEGIN fontRope: Rope.ROPE _ NARROW[event.rest.first]; fontStream: IO.STREAM _ IO.RIS[fontRope]; fontData: FontData _ GGFont.ParseFontData[inStream: fontStream, literalP: TRUE, transformP: TRUE, storedSizeP: TRUE, designSizeP: TRUE ! GGFont.ParseError => {errorRope _ explanation; GOTO ParseError;};]; GGState.SetDefaultFont[ggData, fontData]; ShowDefaultFontValues[ggData, event]; EXITS ParseError => Feedback.Append[ggData.router, oneLiner, $Complaint, Rope.Concat["SetDefaultFont failed: ", errorRope]]; END; }; ShowDefaultFontValues: PUBLIC UserInputProc = { scratch: IO.STREAM _ IO.ROS[]; defaultFontData: FontData _ GGState.GetDefaultFont[ggData]; GGParseOut.WriteFactoredTransformationVEC[scratch, defaultFontData.transform]; Feedback.PutF[ggData.router, oneLiner, $Show, "Default font values: %g %g %g %g", [rope[defaultFontData.literal]], [rope[IO.RopeFromROS[scratch]]], [real[defaultFontData.storedSize]], [real[defaultFontData.designSize]] ]; }; ReadIP: PROC [ipName: Rope.ROPE, ggData: GGData, opName: Rope.ROPE, op: ATOM] RETURNS [scene: Scene _ NIL] = { ShowWarnings: Interpress.LogProc = { Feedback.PutF[ggData.router, oneLiner, $Warning, "IPImager warning: %g", [rope[explanation]] ]; }; ReadMaster: PROC [context: Imager.Context] = { Imager.ScaleT[context, 2834.646]; -- pointsPerMeter=2834.646 Imager.SetColor[context, Imager.black]; Imager.SetAmplifySpace[context, 1.0]; Imager.SetStrokeWidth[context, 0.0]; Imager.SetStrokeEnd[context, square]; Imager.SetStrokeJoint[context, miter]; Interpress.DoPage[master: ipmaster, page: 1, context: context, log: ShowWarnings]; }; ipmaster: Interpress.Master _ NIL; fullName: Rope.ROPE _ ""; startTime, endTime: BasicTime.GMT; totalTime: INT; success: BOOL _ FALSE; camera: Camera _ ggData.camera; router: MsgRouter _ ggData.router; currentWDir: Rope.ROPE _ ggData.currentWDir; BEGIN SELECT op FROM $MergeFromFile => { [fullName, success] _ GGFileOps.GetInterpressFileName["MergeFromFile", ipName, currentWDir, router]; IF NOT success THEN RETURN; [ipmaster, success] _ GGFileOps.OpenInterpressOrComplain["MergeFromFile", router, fullName]; IF NOT success THEN RETURN; }; $MergeFromTioga => { ipProp: Rope.ROPE; selectedLoc: TiogaOpsDefs.Location; selectedNode: TiogaOpsDefs.Ref; [----, selectedLoc] _ TiogaOps.GetSelection[primary]; selectedNode _ selectedLoc.node; ipProp _ NARROW[TiogaOps.GetProp[selectedNode, $Interpress]]; IF Rope.Equal[ipProp, NIL] THEN { Feedback.Append[router, oneLiner, $Complaint, "MergeFromTioga failed: no Interpress property on selected node"]; GOTO Fail; }; ipmaster _ Interpress.FromRope[ipProp, ShowWarnings ! IPMaster.Error => { Feedback.PutF[router, oneLiner, $Complaint, "MergeFromTioga failed: %g", [rope[error.explanation]] ]; GOTO Fail; };]; }; ENDCASE => { Feedback.Append[router, oneLiner, $Complaint, "IP merge: unknown IPMerge operation"]; RETURN; }; Feedback.PutF[router, begin, $Statistics, "%g: %g . . . ", [rope[opName]], [rope[fullName]]]; startTime _ BasicTime.Now[]; scene _ GGFromImager.Capture[action: ReadMaster, camera: camera ! GGFromImager.WarningMessage => { Feedback.PutF[ggData.router, oneLiner, $Warning, "GGFromImager warning: %g", [rope[message]] ]; RESUME; }; ]; endTime _ BasicTime.Now[]; totalTime _ BasicTime.Period[startTime, endTime]; Feedback.PutF[ggData.router, end, $Statistics, " Done in time (%r)", [integer[totalTime]]]; EXITS Fail => NULL; END; }; IPMergeFromTioga: UserInputProc = { scene: Scene _ ReadIP[NIL, ggData, "MergeFromTioga", $MergeFromTioga]; IF scene = NIL THEN RETURN; GGSelect.DeselectAll[ggData.scene, normal]; GGSelect.SelectAll[scene, normal]; ggData.scene _ GGScene.MergeScenes[back: ggData.scene, front: scene]; GGWindow.RestoreScreenAndInvariants[paintAction: $PaintEntireScene, ggData: ggData, remake: triggerBag, edited: TRUE, okToSkipCapture: FALSE]; }; MergeIPEditable: PUBLIC UserInputProc = { ipName: Rope.ROPE _ NARROW[event.rest.first]; scene: Scene _ ReadIP[ipName, ggData, "MergeIPEditable", $MergeFromFile]; IF scene = NIL THEN RETURN; GGSelect.DeselectAll[ggData.scene, normal]; GGSelect.SelectAll[scene, normal]; ggData.scene _ GGScene.MergeScenes[back: ggData.scene, front: scene]; GGWindow.RestoreScreenAndInvariants[paintAction: $PaintEntireScene, ggData: ggData, remake: triggerBag, edited: TRUE, okToSkipCapture: FALSE]; }; MergeIPSlice: PUBLIC UserInputProc = { slice: Slice; localBox: BoundBox _ NIL; argStream: IO.STREAM; h, w: REAL _ 0.0; pointsPerInch: REAL _ 72.0; shortName, fullName: Rope.ROPE; startTime, endTime: BasicTime.GMT; totalTime: INT; ipMaster: Interpress.Master; success: BOOL _ FALSE; includeByValue: BOOL _ GGUserProfile.GetDefaultIncludeIPByValue[]; argStream _ IO.RIS[NARROW[event.rest.first]]; -- file name plus optional two reals shortName _ IO.GetTokenRope[argStream, IO.IDProc! IO.EndOfStream, IO.Error => CONTINUE;].token; -- file name w _ IO.GetReal[argStream ! IO.EndOfStream, IO.Error => CONTINUE;]; -- first real h _ IO.GetReal[argStream ! IO.EndOfStream, IO.Error => CONTINUE;]; -- second real [fullName, success] _ GGFileOps.GetInterpressFileName["MergeIPSlice", shortName, ggData.currentWDir, ggData.router]; IF NOT success THEN RETURN; [ipMaster, success] _ GGFileOps.OpenInterpressOrComplain["MergeIPSlice", ggData.router, fullName]; IF NOT success THEN RETURN; GGRefresh.SetStartBox[ggData: ggData, dragInProgress: FALSE, selectedParts: TRUE]; GGSelect.DeselectAll[ggData.scene, normal]; Feedback.PutF[ggData.router, begin, $Statistics, "MergeIPSlice: %g . . . ", [rope[fullName]]]; startTime _ BasicTime.Now[]; IF w#0.0 AND h#0.0 THEN localBox _ GGBoundBox.CreateBoundBox[0.0, 0.0, w*pointsPerInch, h*pointsPerInch]; -- this can result in refresh problems slice _ GGSlice.MakeIPSliceFromMaster[ipMaster, 2834.646, fullName, ggData.router, NIL, localBox, localBox, includeByValue]; IF slice = NIL THEN RETURN; GGScene.AddSlice[ggData.scene, slice, -1]; endTime _ BasicTime.Now[]; totalTime _ BasicTime.Period[startTime, endTime]; GGEvent.SelectEntireSlice[slice, ggData.scene, normal, ggData]; GGRefresh.EnlargeStartBox[ggData, GGSliceOps.GetBoundBox[slice], NIL]; Feedback.PutF[ggData.router, end, $Statistics, " Done in time (%r)", [integer[totalTime]]]; GGWindow.RestoreScreenAndInvariants[paintAction: $ObjectChangedBoundBoxProvided, ggData: ggData, remake: sceneBag, edited: TRUE, okToSkipCapture: FALSE]; }; -- end MergeIPSlice IncludeIPByReference: UserInputProc = { IncludeIPBy[ggData, "IncludeIPByReference", FALSE]; }; IncludeIPByValue: UserInputProc = { IncludeIPBy[ggData, "IncludeIPByValue", TRUE]; }; IncludeIPBy: PROC [ggData: GGData, opRope: Rope.ROPE, includeBy: BOOL] = { count: INT _ GGScene.CountSelectedSlices[ggData.scene, leaf, normal, $IP]; IF count<1 THEN Feedback.PutF[ggData.router, oneLiner, $Complaint, "%g failed: select at least one IP object to specify include mode", [rope[opRope]] ] ELSE { DoIncludeBy: PROC [sliceD: SliceDescriptor] RETURNS [done: BOOL _ FALSE] = { GGSlice.SetIncludeByValue[sliceD.slice, includeBy, currentEvent]; }; currentEvent: HistoryEvent _ GGHistory.NewCurrent[opRope, ggData]; [] _ GGScene.WalkSelectedSlices[ggData.scene, leaf, DoIncludeBy, normal, $IP]; GGHistory.PushCurrent[ggData]; Feedback.PutF[ggData.router, oneLiner, $Feedback, "%g: selected IP slices included", [rope[opRope]] ]; GGWindow.RestoreScreenAndInvariants[paintAction: $None, ggData: ggData, remake: none, edited: TRUE, okToSkipCapture: FALSE]; }; }; ShowIPIncludeMode: UserInputProc = { count: INT _ GGScene.CountSelectedSlices[ggData.scene, leaf, normal, $IP]; IF count<1 THEN Feedback.Append[ggData.router, oneLiner, $Complaint, "ShowIPIncludeMode failed: select at least one IP object to show include mode"] ELSE { CheckIncludeMode: PROC [sliceD: SliceDescriptor] RETURNS [done: BOOL _ FALSE] = { IF someD=NIL THEN includeByValue _ GGSlice.GetIncludeByValue[sliceD.slice]; -- first selected someD _ sliceD; RETURN[includeByValue#GGSlice.GetIncludeByValue[sliceD.slice]]; -- abort if multiple values }; someD: SliceDescriptor; includeByValue: BOOL _ FALSE; aborted: BOOL _ GGScene.WalkSelectedSlices[ggData.scene, leaf, CheckIncludeMode, normal, $IP]; IF aborted THEN Feedback.Append[ggData.router, oneLiner, $Complaint, "ShowIPIncludeMode failed: multiple include modes selected"] ELSE Feedback.PutF[ggData.router, oneLiner, $Show, "ShowIPIncludeMode: include by %g", IF includeByValue THEN [rope["value"]] ELSE [rope["reference"]]]; }; }; ToIPAux: PROC [ggData: GGData, ipName: Rope.ROPE, actionAtom: ATOM, makeInterpress: PROC [dc: Imager.Context]] = { ipRef: ImagerInterpress.Ref; fullName: Rope.ROPE; success: BOOL _ FALSE; startTime: BasicTime.GMT; endTime: BasicTime.GMT; totalTime: INT; msgRope: Rope.ROPE; IF NOT PBasics.IsBound[LOOPHOLE[ImagerInterpress.Create]] THEN { Feedback.Append[ggData.router, oneLiner, $Complaint, "ToIP failed: please install Interpress, then retry this operation"]; RETURN; }; IF Rope.Equal[ipName, NIL] THEN IF GGState.GetFullName[ggData]=NIL THEN { Feedback.Append[ggData.router, oneLiner, $Complaint, "ToIP failed: can't default IP file name from unnamed viewer"]; RETURN; } ELSE ipName _ GGFileOps.FilenameMinusExtension[FileNames.GetShortName[GGState.GetFullName[ggData]]] ELSE ipName _ FileNames.StripVersionNumber[ipName]; -- forbid version numbers here [fullName, success] _ GGFileOps.GetInterpressFileName["ToIP", ipName, ggData.currentWDir, ggData.router]; IF NOT success THEN RETURN; ipRef _ ImagerInterpress.Create[fullName ! PFS.Error => { Feedback.PutF[ggData.router, oneLiner, $Complaint, "ToIP failed: PFS Error %g: %g while attempting to create %g", [atom[error.code]], [rope[error.explanation]], [rope[fullName]] ]; success _ FALSE; CONTINUE; }; Imager.Error => { Feedback.PutF[ggData.router, oneLiner, $Complaint, "ToIP failed: PFS Error %g: %g while attempting to create %g", [atom[error.code]], [rope[error.explanation]], [rope[fullName]] ]; success _ FALSE; CONTINUE; }; ]; IF NOT success THEN RETURN; msgRope _ IO.PutFR["%g %g . . . ", [atom[actionAtom]], [rope[fullName]]]; Feedback.PutF[ggData.router, begin, $Statistics, msgRope]; startTime _ BasicTime.Now[]; ImagerInterpress.DoPage[ipRef, makeInterpress, 1.0]; ImagerInterpress.Close[ipRef]; endTime _ BasicTime.Now[]; totalTime _ BasicTime.Period[startTime, endTime]; msgRope _ IO.PutFR[" Done in time (%r)", [integer[totalTime]]]; Feedback.PutF[ggData.router, end, $Statistics, msgRope]; GGEvent.SawTextFinish[ggData, NIL]; }; DoMakeAux: PROC [context: Imager.Context, q: GGInterfaceTypes.QualityMode, s: GGInterfaceTypes.DisplayStyle, ggData: GGData] = { tempQuality: GGInterfaceTypes.QualityMode _ ggData.camera.quality; tempStyle: GGInterfaceTypes.DisplayStyle _ ggData.camera.displayStyle; ggData.camera.quality _ q; ggData.camera.displayStyle _ s; Imager.ScaleT[context, metersPerPixel]; GGRefresh.InterpressEntireScene[context, ggData]; ggData.camera.quality _ tempQuality; ggData.camera.displayStyle _ tempStyle; }; ToIP: PUBLIC UserInputProc = { DoMakeInterpress: PROC [dc: Imager.Context] = { DoMakeAux[dc, quality, print, ggData]; -- KAP. }; ipName: Rope.ROPE _ NARROW[event.rest.first]; ToIPAux[ggData, ipName, NARROW[event.first], DoMakeInterpress]; }; ToIPLit: PUBLIC UserInputProc = { DoMakeInterpress: PROC [dc: Imager.Context] = { DoMakeAux[dc, showall, print, ggData]; -- KAP. }; ipName: Rope.ROPE _ "litshot.ip"; ToIPAux[ggData, ipName, NARROW[event.first], DoMakeInterpress]; }; ToIPScreen: PUBLIC UserInputProc = { DoMakeInterpress: PROC [dc: Imager.Context] = { DoMakeAux[dc, quality, screen, ggData]; -- KAP. }; ipName: Rope.ROPE _ NARROW[event.rest.first]; ToIPAux[ggData, ipName, NARROW[event.first], DoMakeInterpress]; }; ToIPSelected: PUBLIC UserInputProc = { DoMakeInterpress: PROC [dc: Imager.Context] = { tempQuality: GGInterfaceTypes.QualityMode; tempStyle: GGInterfaceTypes.DisplayStyle; tempQuality _ ggData.camera.quality; ggData.camera.quality _ quality; tempStyle _ ggData.camera.displayStyle; ggData.camera.displayStyle _ print; Imager.ScaleT[dc, metersPerPixel]; FOR slice: LIST OF Slice _ GGUtility.OrderedSelectionList[ggData, decr], slice.rest UNTIL slice=NIL DO GGSliceOps.DrawParts[slice.first, NIL, dc, ggData.camera, FALSE]; ENDLOOP; ggData.camera.quality _ tempQuality; ggData.camera.displayStyle _ tempStyle; }; ipName: Rope.ROPE _ NARROW[event.rest.first]; ToIPAux[ggData, ipName, NARROW[event.first], DoMakeInterpress]; }; TestGravity2: PUBLIC PROC [dc: Imager.Context, ggData: GGData] = { xRandomStream, yRandomStream: Random.RandomStream; testPoint: Point; x, y: INT; totalCount, multiHitCount, uniHitCount, diffCount: NAT _ 0; --uniPoint,-- multiPoint: Point; normal: Vector; --uniFeature,-- multiFeature: FeatureData; currentObjects: AlignBag; sceneObjects: TriggerBag; xRandomStream _ Random.Create[GGState.GetWidth[ggData]]; yRandomStream _ Random.Create[GGState.GetHeight[ggData]]; GGAlign.SetStaticBags[ggData]; ggData.aborted[gravitytest] _ FALSE; -- in case there was one left over from prior abort UNTIL totalCount > 1000 DO IF ggData.aborted[gravitytest] THEN { ggData.aborted[gravitytest] _ FALSE; EXIT; }; x _ Random.NextInt[xRandomStream]; y _ Random.NextInt[yRandomStream]; testPoint _ [x, y]; testPoint _ GGWindow.ViewerToWorld[viewerPoint: testPoint, ggData: ggData]; ggData.refresh.spotPoint _ testPoint; currentObjects _ ggData.hitTest.alignBag; sceneObjects _ ggData.hitTest.sceneBag; [multiPoint, normal, multiFeature] _ GGMultiGravity.Map[testPoint, ggData.hitTest.t, currentObjects, sceneObjects, ggData, TRUE]; IF multiFeature = NIL THEN { Imager.SetColor[dc, Imager.black]; GGShapes.DrawSpot[dc, ggData.refresh.spotPoint]; totalCount _ totalCount + 1; LOOP; }; multiHitCount _ multiHitCount + 1; totalCount _ totalCount + 1; ggData.refresh.hitPoint _ multiPoint; Imager.SetColor[dc, Imager.black]; Imager.SetStrokeEnd[dc, round]; Imager.MaskVector[dc, [ggData.refresh.spotPoint.x, ggData.refresh.spotPoint.y], [ggData.refresh.hitPoint.x, ggData.refresh.hitPoint.y]]; GGShapes.DrawFilledLoLeftSquare[dc, ggData.refresh.spotPoint, 3.0]; ENDLOOP; Feedback.PutF[ggData.router, oneLiner, $Show, "Tested %g total points. %g unihits. %g multihits. %g differences", [integer[totalCount]], [integer[uniHitCount]], [integer[multiHitCount]], [integer[diffCount]]]; }; -- end TestGravity2 Delete: UserInputProc = { aBox: BoundBox; CodeTimer.StartInt[$Delete, $Gargoyle]; IF GGSelect.NoSelections[ggData.scene, normal] THEN { Feedback.Append[ggData.router, oneLiner, $Complaint, "Delete failed: there is no selection to delete"]; RETURN; -- nothing selected }; GGHistory.NewCapture["Delete objects", ggData]; -- capture scene BEFORE UPDATE GGRefresh.SetStartBox[ggData: ggData, dragInProgress: FALSE, selectedParts: TRUE, selectedCPs: TRUE, hotCPs: TRUE, alignments: TRUE, attractor: TRUE]; -- really only need hotCPs of selected objects [] _ GGScene.DeleteAllSelected[ggData.scene]; GGCaret.NoAttractor[ggData.caret]; GGCaret.SitOn[caret: ggData.caret, chair: NIL]; GGHistory.PushCurrent[ggData]; -- push captured history event onto history list Feedback.PutF[ggData.router, oneLiner, $Feedback, "Delete: selected objects deleted"]; GGWindow.RestoreScreenAndInvariants[paintAction: $ObjectChangedBoundBoxProvided, ggData: ggData, remake: triggerBag, edited: TRUE, okToSkipCapture: FALSE]; CodeTimer.StopInt[$Delete, $Gargoyle]; }; DescribeCurve: UserInputProc = { SELECT event.rest.first FROM $Caret => GGEvent.DescribeCaretObject[ggData, event]; -- for now $Selected => { IF GGScene.CountSelectedSlices[ggData.scene, first, normal]#1 THEN Feedback.Append[ggData.router, oneLiner, $Complaint, "DescribeCurve failed: select a single object for description"] ELSE Feedback.Append[ggData.router, oneLiner, $Show, GGSliceOps.Describe[GGScene.FirstSelectedSlice[ggData.scene, all, normal]]]; }; ENDCASE => ERROR; }; CloseOrNewline: UserInputProc = { IF ggData.refresh.textInProgress=NIL THEN Close[ggData, event] ELSE { myRefChar: REF CHAR _ NEW[CHAR _ Ascii.CR ]; event _ LIST[$AddChar, myRefChar]; GGEvent.AddChar[ggData, event]; }; }; Close: UserInputProc = { outlineSlice: Slice; traj: Traj; seg: Segment; firstPoint, lastPoint: Point; success: BOOL _ FALSE; GGHistory.NewCapture["Close object", ggData]; -- capture scene BEFORE UPDATE BEGIN trajDescriptor: SliceDescriptor; trajData: TrajData; chairD: SliceDescriptor _ GGCaret.GetChair[ggData.caret]; IF NOT GGSliceOps.IsDescriptorOfEnd[chairD] THEN GOTO NoCaretTraj; [success, trajDescriptor, ----] _ GGSliceOps.UnpackJoint[chairD]; IF NOT success THEN GOTO NoCaretTraj; traj _ trajDescriptor.slice; trajData _ NARROW[traj.data]; IF trajData.role = fence OR trajData.role = hole THEN GOTO AlreadyClosed; outlineSlice _ GGParent.GetParent[traj]; firstPoint _ GGTraj.FetchJointPos[traj, 0]; lastPoint _ GGTraj.LastJointPos[traj]; IF firstPoint # lastPoint THEN { lastSeg: Segment _ GGTraj.FetchSegment[traj, GGTraj.HiSegment[traj]]; firstSeg: Segment _ GGTraj.FetchSegment[traj, 0]; cPoint1, cPoint2, oldCP: Point; addBezier: BOOL _ FALSE; IF lastSeg.class.type = $Bezier THEN { addBezier _ TRUE; oldCP _ lastSeg.class.controlPointGet[lastSeg, 1]; cPoint2 _ Vectors2d.Add[lastSeg.hi, Vectors2d.VectorFromPoints[oldCP, lastSeg.hi]]; } ELSE cPoint2 _ lastPoint; IF firstSeg.class.type = $Bezier THEN { addBezier _ TRUE; oldCP _ firstSeg.class.controlPointGet[firstSeg, 0]; cPoint1 _ Vectors2d.Add[firstSeg.lo, Vectors2d.VectorFromPoints[oldCP, firstSeg.lo]] } ELSE cPoint1 _ firstPoint; IF addBezier THEN seg _ GGSegment.MakeBezier[lastPoint, cPoint2, cPoint1, firstPoint, NIL] ELSE seg _ GGSegment.MakeLine[lastPoint, firstPoint, NIL]; GGSegment.CopyLooks[lastSeg, seg]; GGTraj.CloseWithSegment[traj, seg, lo]; [] _ GGSelect.ReselectTraj[traj, hi, ggData.scene, TRUE]; } ELSE { GGOutline.SaveSelectionsInOutlineAllClasses[outlineSlice]; GGSelect.DeselectEntityAllClasses[outlineSlice, ggData.scene]; GGTraj.CloseByDistorting[traj, hi]; GGSelect.ReselectSliceAllClasses[outlineSlice, ggData.scene]; }; GGEvent.SelectEntireSlice[traj, ggData.scene, normal, ggData]; GGCaret.SitOn[ggData.caret, NIL]; -- FIX THIS TO SIT CARET ON NEWLY CLOSED TRAJ GGCaret.NoAttractor[caret: ggData.caret]; GGRefresh.NullStartBox[ggData]; GGRefresh.EnlargeStartBox[ggData, GGSliceOps.GetBoundBox[traj], NIL]; GGHistory.PushCurrent[ggData]; -- push captured history event onto history list Feedback.PutF[ggData.router, oneLiner, $Feedback, "Close: trajectory closed."]; GGWindow.RestoreScreenAndInvariants[paintAction: $ObjectChangedBoundBoxProvided, ggData: ggData, remake: triggerBag, edited: TRUE, okToSkipCapture: FALSE]; EXITS NoCaretTraj => { Feedback.Append[ggData.router, oneLiner, $Complaint, "Close failed: there is no caret trajectory to close"]; }; AlreadyClosed => { Feedback.Append[ggData.router, oneLiner, $Complaint, "Close: that trajectory is already closed"]; }; END; }; -- end Close GravityChoiceChange: UserInputProc = { forward: BOOL _ event.rest.first = $FlipForward; GGState.CycleGravityType[ggData, forward]; }; SetGravityChoice: UserInputProc = { choiceRope: Rope.ROPE _ NARROW[event.rest.first]; gravityType: GravityType _ GGUIUtility.GravityTypeFromRope[choiceRope]; GGState.SetGravityType[ggData, gravityType]; }; GravityExtentChange: UserInputProc = { extent: REAL _ GGState.GetGravityExtent[ggData]; --RETURNS[inches]-- success: BOOL _ TRUE; SELECT event.rest.first FROM $ValueUp => { IF extent < 256.0 THEN extent _ extent*2.0 ELSE { Feedback.PutF[ggData.router, oneLiner, $Complaint, "ExtendGravity failed: can't extend gravity further than 256 inches."]; success _ FALSE; }; }; $ValueDown => extent _ extent/2.0; ENDCASE => extent _ GGUserProfile.GetDefaultGravityExtent[--RETURNS[points]--]/pointsPerIn; IF success THEN { GGState.SetGravityExtent[ggData, extent]; ShowGravityExtent[ggData, event]; }; }; SetGravityExtent: UserInputProc = { inches: REAL _ NARROW[event.rest.first, REF REAL]^; GGState.SetGravityExtent[ggData, inches]; }; ShowGravityExtent: UserInputProc = { inches: REAL _ GGState.GetGravityExtent[ggData]; Feedback.PutF[ggData.router, oneLiner, $Show, "Gravity extent is %g points = %g inches = %g centimeters", [real[inches*pointsPerIn]], [real[inches]], [real[inches*cmPerInch]] ]; }; ToggleGravity: UserInputProc = { GGState.SetGravity[ggData, NOT GGState.GetGravity[ggData]]; }; SetGravity: UserInputProc = { boolRope: Rope.ROPE _ NARROW[event.rest.first]; setGravity: BOOL _ GGCoreOps.RopeToBool[boolRope]; GGState.SetGravity[ggData, setGravity]; }; RegisterEventProcs: PROC = { OPEN GGUserInput; RegisterAction[$GravityChoiceChange, GravityChoiceChange, none, FALSE]; RegisterAction[$SetGravityChoice, SetGravityChoice, none, FALSE]; RegisterAction[$GravityExtentChange, GravityExtentChange, none, FALSE]; RegisterAction[$ToggleGravity, ToggleGravity, none, FALSE]; RegisterAction[$SetGravityExtent, SetGravityExtent, none, FALSE]; RegisterAction[$SetGravity, SetGravity, none, FALSE]; RegisterAction[$ShowGravExtent, ShowGravityExtent, none, FALSE]; RegisterAction[$AddChar, AddChar, none]; RegisterAction[$AddText, AddText, rope]; RegisterAction[$SetAmplifySpace, SetAmplifySpace, refReal]; RegisterAction[$AmplifySpaceFromSelection, AmplifySpaceFromSelection, refReal]; RegisterAction[$PrintAmplifySpace, PrintAmplifySpace, none]; RegisterAction[$SetDropShadow, SetDropShadow, none]; -- actually needs two REALS RegisterAction[$DropShadowTenPercent, DropShadowTenPercent, none]; RegisterAction[$DropShadowFromSelection, DropShadowFromSelection, refReal]; RegisterAction[$PrintDropShadow, PrintDropShadow, none]; RegisterAction[$DropShadowOff, DropShadowOff, none]; RegisterAction[$SetDefaultTextLooks, SetDefaultTextLooks, none]; RegisterAction[$ShowDefaultTextLooks, ShowDefaultTextLooks, none]; RegisterAction[$SetPressFont, SetPressFont, rope]; RegisterAction[$SetPrintFont, SetPrintFont, rope]; RegisterAction[$SetScreenFont, SetScreenFont, rope]; RegisterAction[$SetFontDetailed, SetFontDetailed, rope]; RegisterAction[$SetFontLiteral, SetFontLiteral, rope]; RegisterAction[$ShowFontValues, ShowFontValues, none]; RegisterAction[$ShowFontValuesLiteral, ShowFontValuesLiteral, none]; RegisterAction[$CopyFont, CopyFont, none]; RegisterAction[$CopyAll, CopyAll, none]; RegisterAction[$MatchAll, MatchAll, none]; RegisterAction[$MatchSelectedName, MatchSelectedName, rope]; RegisterAction[$MatchSelectedNameLiteral, MatchSelectedNameLiteral, rope]; RegisterAction[$SetDefaultFontValues, SetDefaultFontValues, none]; RegisterAction[$ShowDefaultFontValues, ShowDefaultFontValues, none]; RegisterAction[$SetDefaultFont, SetDefaultFont, none, FALSE]; RegisterAction[$IPMergeFromTioga, IPMergeFromTioga, none]; RegisterAction[$MergeIPEditable, MergeIPEditable, rope]; RegisterAction[$MergeIPSlice, MergeIPSlice, rope]; RegisterAction[$ToIP, ToIP, rope]; RegisterAction[$ToIPSelected, ToIPSelected, rope]; RegisterAction[$ToIPOnPlayback, ToIP, rope]; RegisterAction[$ToIPScreen, ToIPScreen, rope]; RegisterAction[$ToIPLit, ToIPLit, none]; RegisterAction[$IncludeIPByReference, IncludeIPByReference, none]; RegisterAction[$IncludeIPByValue, IncludeIPByValue, none]; RegisterAction[$ShowIPIncludeMode, ShowIPIncludeMode, none]; RegisterAction[$Delete, Delete, none]; RegisterAction[$DescribeCurve, DescribeCurve, none]; RegisterAction[$AddHoles, AddHoles, none]; RegisterAction[$DeleteHoles, DeleteHoles, none]; RegisterAction[$CloseOrNewline, CloseOrNewline, none]; RegisterAction[$Close, Close, none]; }; RegisterEventProcs[]; END. 1GGEventImplA.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 Ó 1988, 1989 by Xerox Corporation. All rights reserved. Bier, February 3, 1992 8:51 pm PST Pier, May 22, 1992 12:55 pm PDT Doug Wyatt, December 21, 1989 3:20:56 pm PST Make new outlines of each of the children can't ReselectSlice while walking selected slices !! GGSelect.ReselectSliceAllClasses[newSlice, ggData.scene]; ggData.refresh.startBoundBox^ _ GGScene.BoundBoxOfSelected[ggData.scene, normal, TRUE]^; For now, the distinguished slice must either be at top level or must be the only eligible (non-cluster) slice in its cluster. All other slices must be at top level. The new slice with added holes will be at the same level that the distinguished slice was at. ggData.refresh.startBoundBox^ _ GGScene.BoundBoxOfSelected[scene, normal]^; Create an outline (or extract) an outline containing the distinguished slice. If we get to here, newOutline has a $Outline in it. All of the potential holes are top-level slices The children of this outline may be closed trajectories, circles, or boxes for now. The outline must be fully selected to be included. Fully selecting an outline will break it into components and make new holes from the components. Get rid of old top level slice if it got broken into holes. If the character which causes us to enter editing mode is a backspace, then make sure that 1 and only 1 text slice is selected, make it the current textInProgress, deselect it and update the screen. For any other character, create a new text slice (which deselects everything as a side effect and updates the screen). The user has typed a character. If we are not currently in text entry mode, enter that mode. ggData.refresh.startBoundBox^ _ GGSliceOps.GetBoundBox[slice]^; -- remember old, larger bound box EnlargeStartBox to encompass new caret point ggData.refresh.startBoundBox^ _ GGSliceOps.GetBoundBox[slice]^; -- boundBox has grown ggData.refresh.addedObject _ slice; WHY WAS THIS HERE BEFORE?? ggData.refresh.startBoundBox^ _ GGSliceOps.GetBoundBox[slice]^; callers of this proc should have initialized the start box ggData.refresh.startBoundBox^ _ GGScene.BoundBoxOfSelected[ggData.scene, normal]^; -- remember original bound box GGBoundBox.EnlargeByBox[bBox: ggData.refresh.startBoundBox, by: GGScene.BoundBoxOfSelected[ggData.scene, normal] ]; ggData.refresh.startBoundBox^ _ GGScene.BoundBoxOfSelected[ggData.scene, normal]^; -- remember original bound box GGBoundBox.EnlargeByBox[bBox: ggData.refresh.startBoundBox, by: GGScene.BoundBoxOfSelected[ggData.scene, normal] ]; ggData.refresh.startBoundBox^ _ GGScene.BoundBoxOfSelected[ggData.scene, normal]^; -- remember original bound box Assert: Turning off drop shadow should always result in a smaller bound box Checks for consistent selected text looks, then copies looks to the default looks FONTS Each text string should have a single font associated with it. That font may be either a print font or a screen font. For our purposes here, a font is a collection of shapes, defined in some font coordinate system, together with a transformation to apply to a string of those shapes when the string is rendered. File Format Ignoring user interface for the moment, here is what we will store in .gargoyle files: If the font is a resolution-independent font (e.g. press font), we store: FontPrefix FontFamily Face Transformation Preferred Size Xerox/PressFonts/ Helvetica -BI M = [a, b, c, d, e, f] 1.0 Xerox/xc1-2-2/ Modern -BI M = [a, b, c, d, e, f] 1.0 The shapes of the font are assumed to be 1 unit large in font master coordinates. If the named FontPrefix/FontFamily does not obey this property, the font will be scaled to be a unit font before the Transformation is applied. Hence, if M = Scale[12], the font will appear as 12 screen dots high. Our units are 1/72.0 of an inch. Tioga uses 1/72.27 inches. Life is hard. If the font is a resolution-dependent font (e.g. screen font, like Tioga10), we store: FontPrefix FontFamily Face Transformation Preferred Size Xerox/TiogaFonts/ Tioga -BI M = [a, b, c, d, e, f] 10.0 The shapes of the font are assumed to be 1 unit large in font master coordinates. This is often not true. However, the font will be scaled to be a unit font before the Transformation is applied. Gargoyle will have to know about fonts, but can hide this knowledge from the user. For instance: CMR10 is a 1 unit font, which must be scaled by 10 to look good. Tioga10, however, is stored at 10 units high in font master coordinates. Gargoyle will scale it by 0.1 to get a unit font, but M will usually be Scale[10] so all will be well. We store both kinds of fonts in .gargoyle files using the same format with Preferred Size = 1.0 for press fonts. This is a unique value which signals the fileIn code that it is a resolution independent font. It's size is one unit, and the appearance scaling is in the transformation. User Interface for Output There will be a "look readable" mode which calculates a more readable font from the font in each text string. As we do now, we will probably cache this font in each string along with the "real font". However, the readable font will not necessarily be a Screen Font. For large strings, it may be the same as the real font, for instance. Setting Fonts Here's where we have to be flexible. We'll probably want to have several kinds of SetFont commands, some which are convenient for everyday use and some which let you be very specific. These four may make a good starting set: SetPressFont. The user selects a pair such as "Helvetica-BI 12". The <-FontFace> may be left off, denoting regular font. Gargoyle assumes FontPrefix = Xerox/PressFonts/ and lets transformation M = Scale[]. The font is made with ImagerFont.Scale[font, 1] (in all cases I know about). SetPrintFont. The user selects a pair such as "Helvetica-BI 12". The <-FontFace> may be left off, denoting regular font. Gargoyle assumes FontPrefix = Xerox/XC1-2-2/ and lets transformation M = Scale[]. The font is made with ImagerFont.Scale[font, 1] (in all cases I know about). SetScreenFont. The user selects a pair such as "CMR 10". Gargoyle assumes FontPrefix = Xerox/TiogaFonts/ and lets transformation M = Scale[]. The font is made with ImagerFont.Scale[font, 1] if the font is CMR, with ImagerFont.Scale[font, 1.0/], if the font is Helvetica, TimesRoman, Tioga. If the font is one of the traditional TiogaFonts (e.g. Helvetica, TimesRoman, Tioga, ...) Gargoyle will attempt to find the corresponding strike font. For example, SetScreenFont TimesRoman-BI 9 will find the font in file ///fonts/xerox/tiogafonts/TimesRoman9BI.ks. If the user requests a TiogaFont not in the Tioga font set (usually an odd size), Gargoyle will fail to change the font. I don't know which camp Terminal is in. At any rate, Gargoyle will have to keep track of which fonts are in which camp. SetPrintFontDetailed. The user selects a triple. Gargoyle makes no assumptions. The user must provide a factored transformation. For example: xerox/pressfonts/ gacha-bi [r1: REAL, s: VEC, r2: REAL, t: VEC] xerox/xc1-2-2/ classic-b [r1: REAL, s: VEC, r2: REAL, t: VEC] WHITESPACE IS CRITICAL !! SetScreenFontDetailed. The user selects a quadruple. Again, Gargoyle uses its information about font camps to successfully make a unit sized font. Otherwise, Gargoyle makes no assumptions. Note: For screen fonts, we may run into trouble when *(1.0/) isn't quite 1.0. We can either round within epsilon for screen fonts, or we can keep around an unscaled font when we know we should be hitting the fast case. Asking About Fonts A single function PrintFontOfSelected should say exactly what font is being used. The format of this output should be suitable for selecting and feeding to SetPrintFontDetailed or SetScreenFontDetailed. Unfortunately, the Gargoyle feedback region isn't selectable. Of course, the user could open a Typescript first. Suggestions? Fonts Menu ggData.refresh.startBoundBox^ _ GGScene.BoundBoxOfSelected[ggData.scene, normal]^; -- remember original bound box GGBoundBox.EnlargeByBox[bBox: ggData.refresh.startBoundBox, by: GGScene.BoundBoxOfSelected[ggData.scene, normal] ]; SetPressFont expects input of the form . e.g. "Cream-BI 12" SetPressFont expects input of the form . e.g. "Modern-BI 12" SetScreenFont expects input of the form . e.g. "Cream12-BI 20" SetFontDetailed expects input of the form e.g. "xerox/myfonts/fontOne-BI [1.0 2.0 3.0 4.0 5.0 6.0]. Gargoyle assumes the designSize and the storedSize can be derived from the and ; if not, then designSize and storedSize are defaulted to 1.0 SetFontLiteral expects input of the form . Gargoyle accepts this as literal information and makes no attempt at understanding the semantics of the data. Outputs string in the form Outputs string in the form Copies the fontData from the LAST selected text slice to all the other selected text slices, but preserves the other slice transformation. Effect is to change font but not transform. ggData.refresh.startBoundBox^ _ GGScene.BoundBoxOfSelected[ggData.scene, normal]^; GGBoundBox.EnlargeByBox[bBox: ggData.refresh.startBoundBox, by: GGScene.BoundBoxOfSelected[ggData.scene, normal] ]; Copies the fontData from the LAST selected text slice to all the other selected text slices ggData.refresh.startBoundBox^ _ GGScene.BoundBoxOfSelected[scene, normal]^; GGBoundBox.EnlargeByBox[bBox: ggData.refresh.startBoundBox, by: GGScene.BoundBoxOfSelected[scene, normal] ]; Checks for consistent selected text fonts, then selects all the text slices with fontData matching that font, excluding translation. selects all the text slices whose fontData.userFSF contain the Tioga selection as a SUBSTRING. selects all the text slices whose fontData.literal contain the Tioga selection as a SUBSTRING. Checks for consistent selected text fonts, then copies font to the default font Interpress Utilities The core routine to implement MergeIPEditable. Following code required to get the context state to the default assumed by Interpress.DoPage ggData.refresh.startBoundBox^ _ GGSliceOps.GetBoundBox[slice]^; All selected IP Slices are now store-by-reference. All selected IP Slices are now store-by-value. Show whether the selected IP Slices are store-by-reference or store-by-value TIMING VARIABLES START TIMING ToIPTestGravity: UserInputProc = { DoMakeInterpress: PROC [dc: Imager.Context] = { tempQuality: GGInterfaceTypes.QualityMode; tempStyle: GGInterfaceTypes.DisplayStyle; tempQuality _ ggData.camera.quality; ggData.camera.quality _ quality; tempStyle _ ggData.camera.displayStyle; ggData.camera.displayStyle _ print; Imager.ScaleT[dc, metersPerPixel]; TestGravity2[dc, ggData]; ggData.camera.quality _ tempQuality; ggData.camera.displayStyle _ tempStyle; }; ipName: Rope.ROPE _ NARROW[event.rest.first]; ToIPAux[ggData, ipName, NARROW[event.first], DoMakeInterpress]; }; Gravity Utilities Within the bounds of the viewer, randomly choose mouse positions. See if that mouse position is in range of any object. If so, draw a dot at that point. Repeat until 100 points have been drawn. PaintSpot PaintHitLine ggData.refresh.startBoundBox^ _ GGScene.DeleteAllSelected[ggData.scene]^; event.first = $DescribeCurve, event.rest = $Selected or $Caret ggData.refresh.startBoundBox^ _ GGSliceOps.GetBoundBox[traj]^; N.B. It's a shame we have to remake the triggerBag here. This is because the closed trajectory might have been hot. Its hot sequence is obsolete. Gravity Operations Gravity: Text Menu Fonts Menu Interpress Menu RegisterAction[$ToIPTestGravity, ToIPTestGravity, rope]; Hierarchy Menu RegisterAction[$Undelete, Undelete, none]; RegisterAction[$UndeleteAutoConfirm, UndeleteAutoConfirm, none]; RegisterAction[$UnionCombine , UnionCombine, none]; ÊQÒ– "cedar" style•NewlineDelimiter ˜Icode™šÏnœy™KšœB™BKšœ"™"Kšœ™K™,—K™šÏk ˜ Jšœ±žœc˜–K˜—š œžœž˜JšžœŽžœD˜ÛKšžœž˜)—˜Kšœ žœ˜+Kšœ žœ˜'Kšœžœ˜#Kšœžœ˜%Kšœ ž œ$Ïc˜[Kšœžœ&˜AKšœžœ˜/Kšœ žœ˜*Kšœ žœ ˜1Kšœ žœ˜!Kšœžœ˜'Kšœ žœ ˜1Kšœžœ˜1Kšœžœ˜!Kšœžœ˜3Kšœ žœ˜-Kšœžœ˜!Kšœžœžœ!˜Kšœ˜J˜K™—K˜Kšœ žœžœ˜Kšœžœžœ˜K˜Kšœ˜K˜-Kšœ;žœ˜FKšœ7Ÿ˜VKšœ5˜5šžœžœŸ ˜FKšœ žœ.˜;Kšœ,Ÿ%˜QKšœ*  œ˜9Kšœ8˜8KšœE˜EK˜—šžœŸI˜PKšœ žœ/˜˜>Kšœ7žœ ˜FK˜—K˜—Kšžœ{˜J˜—K˜Kšœ žœ˜KšœžœžœŸ9˜VKšœ œŸ˜KKšœQžœ™XK–![caret: GGInterfaceTypes.Caret]šœ   œ!žœžœ˜RK˜"K–![caret: GGInterfaceTypes.Caret]šœžœ˜!Kšœ2Ït  ¡œ¡˜Uš žœ žœžœ&žœ žœž˜RKšœ@˜@Kšžœ˜—Kšœ   œ Ÿ0˜Pšžœžœ˜KšœQ˜QKšœ}žœžœ˜›K˜—Kšžœb˜fK˜—K˜K˜—šœžœ#žœ*žœžœ!žœžœ˜¦J™„š œžœžœžœžœ˜VKšœ žœ$˜3šžœžœžœ˜5Kšœžœ˜Kšžœžœ˜ K˜—šžœžœžœ˜.Kšžœžœ˜ K˜—KšœžœŸ!˜PJ˜J˜—Kšœ žœžœ˜Kšœ žœ˜Kšœ žœžœ˜Jšœžœžœ˜"K–…[scene: GGModelTypes.Scene, level: GGModelTypes.WalkLevel, selectClass: GGSegmentTypes.SelectionClass, classType: ATOM _ NIL]šœM˜MK˜šžœ0žœ˜8š œžœžœžœžœ˜Qšžœ-žœ˜5Jšœ˜Jšœžœ˜ J˜—J˜—KšœM¡œ¡˜aKš žœžœ žœžœŸ"˜=K˜—Kšžœ!˜%Kšœ,˜,Kšœ.˜.Kšžœžœžœžœ˜IK˜Kšœ1¡œ¡œ¡˜Ršžœ ž˜Kšžœž œ˜5Kšžœžœž˜!—Kšœ$˜$Kšžœžœžœžœ˜2šž˜šœ˜Kšœ žœ˜Kšœ†˜†K˜—˜Kšœ žœ˜Kšœ˜K˜—šœ˜Kšœ žœ˜Kšœr˜rK˜——K˜K˜—šœ˜K–K[scene: GGModelTypes.Scene, selectClass: GGSegmentTypes.SelectionClass]˜K–K[scene: GGModelTypes.Scene, selectClass: GGSegmentTypes.SelectionClass]šœ"˜"Kšžœ%žœm˜™šžœ˜Kšœ#žœžœ˜0K˜KšœžœŸ ˜"Kšœžœžœ˜*Kšœ,Ÿ˜JKšœŸ0˜@K˜KšœZ˜ZKšžœžœ žœžœ˜Kšœ¡ œ Ÿ˜JK–![caret: GGInterfaceTypes.Caret]˜"K–![caret: GGInterfaceTypes.Caret]šœžœ˜!KšœK™KK–:[scene: GGModelTypes.Scene, slice: GGModelTypes.Slice]šœ   œ!žœžœ˜RKšœ<˜žœ˜CKšœ4˜4K˜—šžœ˜Kšœ žœ5˜BKšœ1˜1Kšœ*˜*Kšœ9˜9Kšœ>žœ˜CKšœ2˜2K˜—Kšœ Ÿ0˜PKšœG˜GKšœ}žœžœ˜›Kšœ˜—Kšœ˜—Kšœ˜—K˜š œžœžœžœžœ˜XKšœ¾™¾Kšœ1˜1šžœžœžœŸ7˜SKšžœBžœq˜¹–|[slice: GGModelTypes.Slice, gargoyleData: GGInterfaceTypes.GargoyleData, selectClass: GGInterfaceTypes.SelectionClass]šžœŸ;˜BKšœ[˜[Kšœ˜Kšœ0˜0KšœQŸ:˜‹Kšœ/Ÿ%˜TKš œ   œ!žœžœ žœ žœ žœ˜|Kšœ8žœŸ‘˜ÏKšœg˜gKšœ'Ÿ"˜IKšœ7˜7Kšžœžœžœ$˜LKšœB˜BKš œ  œ9žœ?žœ žœžœžœ˜âK˜—Kšœ˜—šžœŸ'˜.šžœžœ˜&Kš œ   œ!žœžœ žœ žœ˜nK–ß[caret: GGInterfaceTypes.Caret, point: GGBasicTypes.Point, attractor: REF ANY _ NIL, on: GGInterfaceTypes.CaretOn _ nothing, attractorJointNum: NAT _ 999, attractorCPNum: NAT _ 999, attractorSegNum: NAT _ 999]šœ#Ÿ˜@Kšœ/Ÿ%˜TKš œ   œ žœ;žœ#žœŸL¢˜ýKšœB˜BK˜—Kšžœm˜qK˜—Kšœ0˜0KšœŸ˜K˜—šœžœ˜!K™]Kšœ˜Kšœ žœžœ˜Kš žœžœžœ žœžœ˜1Kšœ žœ˜#Kšžœžœžœ"˜KšžœŸ˜Kšœ(˜(Kšœ&˜&Kš œ žœ$žœžœžœ˜Xšžœ ž˜šœžœ˜Kšœ@Ÿ!™aJš œ   œ!žœžœ žœ žœ žœ˜|Jšœ  œ(žœ˜FJšœC˜CKšœ  œ œžœ5žœ žœžœžœ˜äKšœ˜—šœžœžœŸ/˜GKšœ2Ÿ9˜kKšœ0˜0Kšœs˜sKšœ=˜=Kš œ   œ!žœ žœ žœ˜[Kšœ:žœ˜?šœ  œ8žœ˜VK™,—Kšœ&ŸH˜nKšœ/Ÿ(˜WKš œ   œ5žœ$žœŸd¢˜…Kšœ]žœ˜bK˜—šžœŸ˜Kšœžœ7˜BKšœBŸ0˜rKšœ@Ÿ™UJšœ   œ ˜Jšœ  œ(žœ˜FKšœ>™>Kšœ  œžœžœ œžœ  œžœžœ žœžœžœ˜ÒKšœ˜——Kšœ'˜'Kšœ˜—KšœŸ˜K˜—š œžœžœžœžœžœžœžœžœžœ˜³Kšœ1˜1Kšœ žœžœ˜Kšœ˜Kš œ)žœ žœžœ žœ ˜lKšœº˜ºKšœT˜TKšœJžœ˜OKšžœžœ žœžœ˜šžœžœžœ˜Kšœ*˜*Kšœ#˜#K˜—šžœ˜Kšœ-˜-Kšœ$˜$K˜—Kšœ?™?šœ  œ(žœ˜FJ™:—Jšœ   œŸ˜CKšžœ žœ@˜PKšœ  œ  œ,žœ ˜Kšœ˜K˜—šœ˜Kšœ žœžœ˜+Kšžœžœžœo˜Œšžœ˜Kšœ   œ¡œ Ÿ˜HJšœ   œ ˜Kš œ  œ žœ(žœžœ˜_Kšœ   œ ˜K˜—K˜—K˜šœ˜"Kšœ)˜)Kšœ˜K˜—šœ˜,Kš œžœžœžœžœ˜9KšœžœB˜LKšžœžœ~˜Kšžœžœžœz˜¡šžœ˜š œžœžœžœžœ˜OK˜KšœU˜UKšœL˜LJ˜—Jšœ˜Kšœ˜Kšœžœžœ˜"Kšœ2˜2K–h[gargoyleData: GGInterfaceTypes.GargoyleData, selectClass: GGInterfaceTypes.SelectionClass _ normal]šœSŸ™qKšœ   œ!žœžœ˜RKšœ=˜=KšœS˜Sš žœžœžœ"žœžœž˜FJšœ=˜=Jšžœ˜—K–<[bBox: GGModelTypes.BoundBox, by: GGModelTypes.BoundBox]šœs™sJšœ  œ;žœ˜YJšœ˜Kšœr˜rKšœ}žœžœŸ6˜ÒK˜—Kšœ˜K˜—šœ˜$KšœžœB˜LKšžœ žœ‡˜–šžœ˜š œžœžœžœžœ˜Ršžœ˜Kšžœ:˜>—Kšžœ:Ÿ˜[J˜—Jšœžœ˜#Kšœ žœT˜aKšžœžœx˜‡Kšžœr˜vK˜—K˜K˜—š œ˜ Kšœžœ Ÿ7˜KKš œ žœžœžœžœ˜4Kš œ žœžœžœžœ˜9KšœžœB˜LKšžœ žœ˜Kšžœžœžœ˜ºšžœ˜š œžœžœžœžœ˜PK˜KšœFŸL˜’KšœL˜LJ˜—Jšœ˜Kšœ˜Kšœžœžœ˜"Kšœ2˜2K–h[gargoyleData: GGInterfaceTypes.GargoyleData, selectClass: GGInterfaceTypes.SelectionClass _ normal]šœSŸ™qKšœ   œ!žœžœ˜RKšœ@Ÿ˜ZKšœT˜Tš žœžœžœ"žœžœž˜FJšœ=˜=Jšžœ˜—K–<[bBox: GGModelTypes.BoundBox, by: GGModelTypes.BoundBox]šœs™sJšœ  œ;žœ˜YKšœ˜Kšœ˜Kšœ}žœžœ˜›K˜—Kšœ˜K˜—šœ˜*Kšœžœ Ÿ7˜KKš œžœžœžœžœŸ˜HKšžœžœ˜›Kš žœžœžœžœžœžœŸ˜Kšœ˜K˜—šœ˜'Kšœžœ Ÿ7˜KKšœžœŸ˜(Kš œžœžœžœžœžœŸ˜|Kšœ˜K˜—šœ˜"KšœžœB˜LKšžœ žœƒ˜’šžœ˜š œžœžœžœžœ˜PKšžœžœB˜eKšœ7˜7Kšžœ žœŸ˜EJ˜—Kšœžœ Ÿ&˜:Kšœ žœžœ˜Kšœ=˜=Kšœ žœR˜_Kšžœžœt˜ƒšžœ˜šœžœžœžœž˜*Kšžœ*˜-Kšžœžœf˜s—Kšœ=˜=K˜—K˜—K˜K˜—š œ˜ KšœžœB˜LKšžœ žœƒ˜’šžœ˜š œžœžœžœžœ˜PK˜Kšœ2˜2KšœL˜LJ˜—Jšœ˜Kšœ˜Kšœžœžœ˜"Kšœ2˜2K–h[gargoyleData: GGInterfaceTypes.GargoyleData, selectClass: GGInterfaceTypes.SelectionClass _ normal]šœSŸ™qKšœ   œ!žœžœ˜RKšœ@Ÿ˜ZKšœT˜Tš žœžœžœ"žœžœž˜FJšœ=˜=Jšžœ˜—K–<[bBox: GGModelTypes.BoundBox, by: GGModelTypes.BoundBox]šœE™KKšœ˜KšœN˜NKšœ}žœžœ˜›Kšœ˜—Kšœ˜K˜—šœ˜&KšœQ™QKšœžœB˜LKšžœ žœ‰˜˜šžœ˜š œžœžœžœžœ˜OKšœ˜Kšžœ žœ1˜FKšžœžœ<žœŸ˜jJ˜—Jšœžœ˜J˜Kšœ žœ2œ˜^Kšžœžœs˜‚šžœ˜Kšœ‰˜‰KšœG˜GKšœ–˜–K˜—K˜—K˜K˜—šœžœ˜.Kšœžœ Ÿ˜3K–ldStream: STREAM _ NIL]š œ žœžœžœžœ˜Kšžœžœ_˜„K–[format: ROPE _ NIL, v1: IO.Value _ [null[]], v2: IO.Value _ [null[]], v3: IO.Value _ [null[]], v4: IO.Value _ [null[]], v5: IO.Value _ [null[]]]šžœ.˜2šžœžœ˜&Kšžœ!žœg˜’K–[format: ROPE _ NIL, v1: IO.Value _ [null[]], v2: IO.Value _ [null[]], v3: IO.Value _ [null[]], v4: IO.Value _ [null[]], v5: IO.Value _ [null[]]]šžœ0˜4Kšœ‡˜‡K˜—KšœNžœ˜jK˜—K˜headšž™Ibodyšœ¹™¹—šœ ™ Mšœ¡™¡J™Jšœ=™=Jšœžœ™=Jšœžœ™8J™I continuationšœö™öMšœV™VJ™Jšœ=™=Jšœžœ™;N™Nšœ©žœí™›Mšœ™—šœ™MšœÒ™Ò—šœ ™ Mšœâ™âIitemšœÅ™ÅOšœ¾žœ™ÂOšœˆžœ¢žœ½™ìšœÅ™ÅOš œ!žœžœžœžœ™@Oš œžœžœžœžœ™>Ošž œžœžœ™—Ošœ„™„Mšœè™ì—šœ™MšœÌ™Ì—M™Kšœ ™ š œžœ4žœžœ˜UKšœžœB˜LKšžœ žœt˜ƒšžœ˜š œžœžœžœžœ˜MKšœ žœžœVžœJ˜ÂKš žœžœ žœžœžœŸ(˜KKšœL˜LKšœ˜J˜—Jšœ žœžœ˜Jšœ˜Kšœ˜Kšœ žœžœ˜Kšœžœžœ˜"Kšœ2˜2K–h[gargoyleData: GGInterfaceTypes.GargoyleData, selectClass: GGInterfaceTypes.SelectionClass _ normal]šœSŸ™qKšœ   œ!žœžœ˜RKšœ9Ÿ˜SKšœV˜Vš žœžœžœ"žœžœž˜FJšœ=˜=Jšžœ˜—Kšœs™sJšœ  œ;žœ˜YKšœ Ÿ0˜PK–<[bBox: GGModelTypes.BoundBox, by: GGModelTypes.BoundBox]šžœ žœžœžœc˜€Kšžœžœ žœv˜‰Kšœ}žœžœ˜›Kšœ˜—Kšœ˜K˜—š œ˜KšœY™YK–)[rope: ROPE, oldStream: STREAM _ NIL]šœžœ˜šž˜Kš œ žœžœžœžœžœŸ˜HKšœ-˜-Kšœ'Ÿ"˜IKš œMžœ žœ žœ2žœŸ˜ÉKšœ˜šž˜Kšœt˜t—Kšžœ˜—Kšœ˜K˜—š œ˜KšœZ™ZK–)[rope: ROPE, oldStream: STREAM _ NIL]šœžœ˜–)[rope: ROPE, oldStream: STREAM _ NIL]šž˜Kš œ žœžœžœžœžœŸ˜IKšœ-˜-Kšœ$Ÿ"˜FKš œMžœ žœ žœ2žœŸ˜ÉKšœ˜šž˜Kšœq˜q—Kšžœ˜—Kšœ˜K˜—š œ˜ Kšœ\™\K–)[rope: ROPE, oldStream: STREAM _ NIL]šœžœ˜–)[rope: ROPE, oldStream: STREAM _ NIL]šž˜Kš œ žœžœžœžœžœŸ˜HKšœ-˜-Kšœ'Ÿ#˜JKš œMžœ žœ žœ2žœŸ˜ÉKšœ˜šž˜Kšœu˜u—Kšžœ˜—Kšœ˜K˜—šœ˜"KšœÈ™ÈK–)[rope: ROPE, oldStream: STREAM _ NIL]šœžœ˜šž˜Kš œ žœžœžœžœžœ˜7Kšœ-˜-Kš œMžœ žœ žœžœ2žœŸ"˜èKšœ˜šž˜Kšœw˜w—Kšžœ˜—Kšœ˜K˜—šœ˜!Kšœæ™æK–)[rope: ROPE, oldStream: STREAM _ NIL]šœžœ˜–)[rope: ROPE, oldStream: STREAM _ NIL]šž˜Kš œ žœžœžœžœžœ˜7Kšœ-˜-Kš œNžœžœžœžœ2žœŸ2˜ƒKšœ˜šž˜Kšœv˜v—Kšžœ˜—Kšœ˜K˜—šœ˜!KšœT™TKšœ žœžœ˜Kšœžœ˜KšœY˜YKšžœžœ žœžœ˜KšœP˜PKšœ˜K˜—šœ˜(KšœT™TKšœ žœžœ˜Kšœžœ˜Kšœg˜gKšžœžœ žœžœ˜KšœW˜WKšœ˜K˜—šœ˜Kšœžœ–™·K˜Kšœ=˜=KšœžœB˜LKšžœ žœ žœžœ—˜¶šžœ˜šž˜Kšœ˜š œžœžœžœžœ˜Kšžœžœ˜Kšœ3˜3Kšœ˜Kšœ,˜,Kšœ#Ÿ(˜KKšœCŸ˜ZKšœ#˜#Kšœ_˜_Kš žœžœ žœžœŸ(˜DKšœL˜LJ˜—J˜—Kšœ žœžœ˜Kšœž˜Kšœ˜Kšœžœžœ˜"Kšœw˜wKšžœžœ žœžœ˜Kšœ9˜9Kšœ2˜2K–h[gargoyleData: GGInterfaceTypes.GargoyleData, selectClass: GGInterfaceTypes.SelectionClass _ normal]šœR™RKšœ   œ!žœžœ˜RKšœL œ)˜š žœžœžœ"žœžœž˜FJšœ=˜=Jšžœ˜—Kšœs™sKšœ  œ;žœ˜YKšœ Ÿ0˜PK–Û[feedback: ViewerClasses.Viewer, msgType: GGError.MsgType, format: ROPE _ NIL, v1: IO.Value _ [null[]], v2: IO.Value _ [null[]], v3: IO.Value _ [null[]], v4: IO.Value _ [null[]], v5: IO.Value _ [null[]]]šœ˜Kšœ}žœžœ˜›Kšžœ˜—Kšœ˜—Kšœ˜K˜—Kš œžœžœžœžœ˜=šœžœ(žœ1žœžœžœžœ˜ÄKšœ žœžœ˜š œžœžœžœžœ˜Qšžœ žœžœ˜Kšœ&Ÿ˜8Kšœ,˜,K˜—Kšžœžœ1žœ˜GJ˜—šžœ!žœ˜(KšœC¡œ¡˜^—šžœ˜šžœ*žœ˜2Kšœ&˜&Kšœ-˜-K˜—K˜—šžœ žœ˜Kšœ žœ˜K–À[feedback: Feedback.FeedbackData, format: ROPE _ NIL, v1: IO.Value _ [null[]], v2: IO.Value _ [null[]], v3: IO.Value _ [null[]], v4: IO.Value _ [null[]], v5: IO.Value _ [null[]]]šœg˜gK˜—šžœžœ žœžœ˜Kšœ žœ˜K–À[feedback: Feedback.FeedbackData, format: ROPE _ NIL, v1: IO.Value _ [null[]], v2: IO.Value _ [null[]], v3: IO.Value _ [null[]], v4: IO.Value _ [null[]], v5: IO.Value _ [null[]]]šœa˜aK˜—K˜K˜—šœžœžœžœžœžœžœ˜¡Kšœžœ˜Kšœ žœžœ˜K˜š œžœžœžœžœ˜Ršžœ žœžœ˜Kšœ'Ÿ˜9Kšœ-˜-K˜—Kšžœžœ2žœ˜HJ˜—KšœC¡œ ¡˜Tšžœ žœ˜Kšœ žœ˜K–À[feedback: Feedback.FeedbackData, format: ROPE _ NIL, v1: IO.Value _ [null[]], v2: IO.Value _ [null[]], v3: IO.Value _ [null[]], v4: IO.Value _ [null[]], v5: IO.Value _ [null[]]]šœn˜nK˜—šžœžœ žœžœ˜Kšœ žœ˜K–À[feedback: Feedback.FeedbackData, format: ROPE _ NIL, v1: IO.Value _ [null[]], v2: IO.Value _ [null[]], v3: IO.Value _ [null[]], v4: IO.Value _ [null[]], v5: IO.Value _ [null[]]]šœh˜hK˜—K˜K˜—šœ˜Kšœžœ:™[K˜KšœžœB˜LKšœ=˜=Kšžœ žœ žœžœ’˜±šžœ˜šž˜š œžœžœžœžœ˜JKšœžœ˜"Kšœ˜Kšœ,˜,Kšœ;˜;KšœCŸ˜ZKšœS˜SKšœ_˜_Kš žœžœ žœžœŸ(˜DKšœL˜LJ˜—Kšœ žœžœ˜Kšœž˜Kšœ˜Kšœ˜Kšœžœžœ˜"Kšœƒ˜ƒKšžœžœ žœžœ˜Kšœ9˜9Kšœ2˜2K–h[gargoyleData: GGInterfaceTypes.GargoyleData, selectClass: GGInterfaceTypes.SelectionClass _ normal]šœK™KKšœ   œ!žœžœ˜RKšœG˜Gš žœžœžœ"žœžœž˜FJšœ6˜6Jšžœ˜—Kšœl™lKšœ  œ;žœ˜YKšœ Ÿ0˜PK–Û[feedback: ViewerClasses.Viewer, msgType: GGError.MsgType, format: ROPE _ NIL, v1: IO.Value _ [null[]], v2: IO.Value _ [null[]], v3: IO.Value _ [null[]], v4: IO.Value _ [null[]], v5: IO.Value _ [null[]]]šœ’˜’Kšœ}žœžœ˜›Kšžœ˜—Kšœ˜—Kšœ˜K˜—šœ˜Kšœ„™„KšœžœB˜LKšžœ žœx˜‡šžœ˜š œžœžœžœžœ˜NKšžœ žœžœ.˜BKšžœžœ;Ÿ˜`J˜J˜—Kšœ žœ ˜Jšœ˜Kšœ žœP˜]Kšžœžœb˜qšžœ˜š œžœžœžœžœ˜@K–-[y: REAL, x: REAL, distance: [-126..0]]šžœ1žœ@˜xJ˜J˜—Kšœ   œ!žœžœ˜PKšœ+˜+Kšœb˜bKšœf˜fKšœjžœžœ˜‰K˜—K˜—K˜K˜—š œžœžœžœ˜žœX˜ÓJšœ˜J˜—šœ˜$KšœTž œ™^Kšœžœžœ˜0Kšžœžœžœs˜•Kšžœ-˜1Kšœ˜K˜—šœ˜+KšœTž œ™^Kšœžœžœ˜0Kšžœžœžœ{˜Kšžœ-˜1Kšœ˜K˜—šœžœžœžœ˜Iš œžœžœžœžœ˜FKšœ0˜0K–-[y: REAL, x: REAL, distance: [-126..0]]š žœžœ žœžœ1žœžœ@˜¹J˜—Kšœ   œ!žœžœ˜PKšœ+˜+KšœE˜EK–Û[feedback: ViewerClasses.Viewer, msgType: GGError.MsgType, format: ROPE _ NIL, v1: IO.Value _ [null[]], v2: IO.Value _ [null[]], v3: IO.Value _ [null[]], v4: IO.Value _ [null[]], v5: IO.Value _ [null[]]]šœ„˜„Kšœjžœžœ˜‰Kšœ˜K˜—šœ˜'KšœO™OKšœžœB˜LKšžœ žœˆ˜—šžœ˜š œžœžœžœžœ˜OKšžœ žœ.˜BKšžœžœ;Ÿ˜`J˜—Jšœ˜Kšœ žœ2œ˜^Kšžœžœh˜wKšžœ˜Kšœ>˜>šœ}˜}Kšœ˜—Kšœ˜—Kšœ˜K˜—šœ˜!Kšœžœ˜šž˜Kšœžœžœ˜/Kš œ žœžœžœžœ ˜)Kš œJžœžœžœžœ2žœ˜ÌKšœ)˜)Kšœ%˜%šž˜Kšœv˜v—Kšžœ˜—K˜K˜—šœžœ˜/K–ldStream: STREAM _ NIL]š œ žœžœžœžœ˜Kšœ;˜;KšœN˜NK–Û[feedback: ViewerClasses.Viewer, msgType: GGError.MsgType, format: ROPE _ NIL, v1: IO.Value _ [null[]], v2: IO.Value _ [null[]], v3: IO.Value _ [null[]], v4: IO.Value _ [null[]], v5: IO.Value _ [null[]]]šœyžœb˜ÝKšœ˜K˜—K˜K™šœžœžœžœžœžœžœ˜nK™.š œ˜$Kšœ_˜_Kšœ˜—š œžœ˜.Jš "¢˜šžœž˜Kšœ7Ÿ ˜Ašœ˜Kšžœ;žœu˜·K–|[slice: GGModelTypes.Slice, gargoyleData: GGInterfaceTypes.GargoyleData, selectClass: GGInterfaceTypes.SelectionClass]šžœ}˜K˜—Kšžœžœ˜—K˜—K˜šœ˜!šžœžœžœžœ˜EKš œ žœžœžœžœ žœ˜,Kšœžœ˜"Kšœ˜K˜—K˜K˜—šœ˜Kšœ˜Kšœ ˜ K˜ Kšœ˜Kšœ žœžœ˜Kšœ œ¡ œ Ÿ˜Lš£˜Kšœ ˜ K˜Kšœ9˜9Kšžœžœ&žœžœ ˜BKšœŸœ#˜AKšžœžœ žœžœ ˜%Kšœ˜Kšœ žœ ˜Kšžœžœžœžœ˜IKšœ(˜(Kšœ+˜+Kšœ&˜&šžœžœ˜ KšœE˜EKšœ1˜1K˜Kšœ žœžœ˜šžœžœ˜&Kšœ žœ˜Kšœ2˜2KšœS˜SK˜—Kšžœ˜šžœžœ˜'Kšœ žœ˜Kšœ4˜4KšœT˜TK˜—Kšžœ˜Kšžœ žœEžœ˜ZKšžœ1žœ˜:Kšœ"˜"Kšœ'˜'Kšœ3žœ˜9K˜—šžœ˜Kš :˜:Kšœ>˜>Kšœ#˜#Kš =˜=K˜—K–x[traj: GGModelTypes.Traj, gargoyleData: GGInterfaceTypes.GargoyleData, selectClass: GGSegmentTypes.SelectionClass]šœ>˜>K–£[caret: GGInterfaceTypes.Caret, on: GGInterfaceTypes.CaretOn _ nothing, chair: REF ANY _ NIL, jointNum: NAT _ 999, cpNum: NAT _ 999, segNum: NAT _ 999]š £ ¢-˜OK–![caret: GGInterfaceTypes.Caret]šœ)˜)Kšœ>™>Jšœ   œ ˜Jšœ  œ'žœ˜EKšœ   œ Ÿ0˜PKšœO˜Ošœ}žœžœ˜›Kšœ”™”—šž˜˜Kšœl˜lK˜—šœ˜Kšœa˜aK˜——Kš£œ˜—KšœŸ ˜—K˜K™šœ˜&Kšœ žœ#˜0Kšœ*˜*K˜K˜—šœ˜#Kšœžœžœ˜1KšœG˜GKšœ,˜,K˜K˜—šœ˜&Kšœžœ  œ Ÿ˜DKšœ žœžœ˜šžœž˜šœ ˜ Kšžœžœ˜*šžœ˜Kšœz˜zKšœ žœ˜K˜—K˜—Kšœ"˜"Kšžœ3Ÿœ˜[—šžœ žœ˜Kšœ œ˜)Kšœ!˜!K˜—K˜K˜—šœ˜#Kš œžœžœžœžœ˜3Kšœ)˜)K˜K˜—šœ˜$Kšœžœ$˜0Kšœ²˜²K˜K˜—š œ˜ Kšœžœ˜;K˜K˜—š œ˜Kšœžœžœ˜/Kšœ žœ"˜2Kšœ'˜'K˜—K˜šœžœ˜Kšžœ ˜K˜K™Kšœ@žœ˜GKšœ:žœ˜AKšœ@žœ˜GKšœ4žœ˜;Kšœ:žœ˜AKšœ.žœ˜5Kšœ9žœ˜@K˜K™ Kšœ(˜(Kšœ(˜(Kšœ;˜;KšœO˜OKšœ<˜