DIRECTORY Ascii, Basics, BasicTime, CodeTimer, CubicSplines, Feedback, FeedbackTypes, FileNames, GGAlign, GGBasicTypes, GGBoundBox, GGCaret, 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, ImagerError, ImagerFont, ImagerInterpress, ImagerTransformation, InterpressInterpreter, IO, IPMaster, PFS, Random, Real, RealFns, Rope, TextNode, TiogaOps, TiogaOpsDefs, Vectors2d; GGEventImplA: CEDAR PROGRAM IMPORTS Basics, 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, ImagerError, ImagerInterpress, ImagerTransformation, InterpressInterpreter, IO, IPMaster, PFS, Random, RealFns, Rope, TiogaOps, Vectors2d EXPORTS GGEvent, GGInterfaceTypes = BEGIN AlignBag: TYPE = GGInterfaceTypes.AlignBag; BoundBox: TYPE = GGCoreTypes.BoundBox; Camera: TYPE = GGModelTypes.Camera; Caret: TYPE = GGInterfaceTypes.Caret; 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; QualityMode: TYPE = GGInterfaceTypes.QualityMode; RefreshDataObj: PUBLIC TYPE = GGRefreshTypes.RefreshDataObj; ROPE: TYPE = Rope.ROPE; 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 = ImagerTransformation.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.Append[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.Append[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.PutFL[ggData.router, oneLiner, $Feedback, "SetDropShadow: drop shadow set to [%g, %g]", LIST[[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.Append[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.Append[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.PutF1["text color is %g ", [rope[GGUtility.DescribeColor[ggData.defaults.textColor]]] ] ELSE scratch.PutF1["%g", [rope["no text color "]]]; IF ggData.defaults.dropShadowOn THEN { IF ggData.defaults.dropShadowColor#NIL THEN scratch.PutF1["shadow color is %g ", [rope[GGUtility.DescribeColor[ggData.defaults.dropShadowColor]]] ] ELSE scratch.PutF1["%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; }; SetFont: UserInputProc = { errorRope: Rope.ROPE; font: ImagerFont.Font ¬ NARROW[event.rest.first]; fontData: FontData ¬ GGFont.FontDataFromFont[font]; SetFontAux[ggData, fontData]; }; 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.PutFL[ggData.router, oneLiner, $Feedback, "Copied font %g to %g text objects", LIST[[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: Imager.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.PutFL[ggData.router, oneLiner, $Feedback, "Copied font and transformation %g to %g text objects", LIST[[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, selectionChanged: TRUE, 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.PutFL[ggData.router, oneLiner, $Show, "Default font values: %g %g %g %g", LIST[[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: InterpressInterpreter.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]; InterpressInterpreter.DoPage[master: ipmaster, page: 1, context: context, log: ShowWarnings]; }; ipmaster: InterpressInterpreter.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 ¬ InterpressInterpreter.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.PutFL[router, begin, $Statistics, "%g: %g . . . ", LIST[[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: InterpressInterpreter.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"]]]; }; }; OpenInterpress: PROC [ipName: ROPE, ggData: GGData] RETURNS [ipRef: ImagerInterpress.Ref ¬ NIL, success: BOOL ¬ TRUE, fullName: ROPE] = { 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[NIL, FALSE, NIL]; } 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.PutFL[ggData.router, oneLiner, $Complaint, "ToIP failed: PFS Error %g: %g while attempting to create %g", LIST[[atom[error.code]], [rope[error.explanation]], [rope[fullName]]] ]; success ¬ FALSE; CONTINUE; }; Imager.Error => { Feedback.PutFL[ggData.router, oneLiner, $Complaint, "ToIP failed: PFS Error %g: %g while attempting to create %g", LIST[[atom[ImagerError.AtomFromErrorCode[error.code]]], [rope[error.explanation]], [rope[fullName]]] ]; success ¬ FALSE; CONTINUE; }; ]; }; ToIPAux: PROC [ggData: GGData, ipName: ROPE, actionAtom: ATOM, makeInterpress: PROC [dc: Imager.Context]] = { ipRef: ImagerInterpress.Ref; fullName, msgRope: ROPE; success: BOOL ¬ FALSE; startTime, endTime: BasicTime.GMT; totalTime: INT; IF Basics.IsBound[ImagerInterpress.Create] THEN { [ipRef, success, fullName] ¬ OpenInterpress[ipName, ggData]; IF NOT success THEN RETURN; msgRope ¬ IO.PutFR["%g %g . . . ", [atom[actionAtom]], [rope[fullName]]]; Feedback.Append[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.PutFR1[" Done in time (%r)", [integer[totalTime]]]; Feedback.Append[ggData.router, end, $Statistics, msgRope]; GGEvent.SawTextFinish[ggData, NIL]; } ELSE Feedback.Append[ggData.router, oneLiner, $Complaint, "ToIP failed: please install Interpress, then retry this operation"]; }; pageHeight: REAL = 11.0*72.0; pageWidth: REAL = 8.5*72.0; ToIPMultipageAux: PROC [ggData: GGData, ipName: ROPE, actionAtom: ATOM, literal: BOOL ¬ FALSE] = { DoMakeInterpress: PROC [dc: Imager.Context] = { transform: Transformation ¬ ImagerTransformation.Translate[[-pageWidth*pageNum, 0]]; DoMakeAux[dc, quality, print, ggData, page, FALSE, transform]; }; ipRef: ImagerInterpress.Ref; fullName, msgRope: ROPE; success: BOOL ¬ FALSE; startTime, endTime: BasicTime.GMT; totalTime: INT; page: BoundBox ¬ GGBoundBox.CreateBoundBox[0,0,pageWidth,pageHeight]; pageNum: CARD ¬ 0; translate: Transformation = ImagerTransformation.Translate[[pageWidth, 0]]; IF Basics.IsBound[ImagerInterpress.Create] THEN { sceneBox: BoundBox ¬ GGScene.BoundBoxOfScene[ggData.scene]; [ipRef, success, fullName] ¬ OpenInterpress[ipName, ggData]; IF NOT success THEN RETURN; msgRope ¬ IO.PutFR["%g %g . . . ", [atom[actionAtom]], [rope[fullName]]]; Feedback.Append[ggData.router, begin, $Statistics, msgRope]; startTime ¬ BasicTime.Now[]; UNTIL Intersection[page, sceneBox].null DO ImagerInterpress.DoPage[ipRef, DoMakeInterpress, 1.0]; GGBoundBox.UpdateBoundBoxOfBoundBox[page, page, translate]; pageNum ¬ pageNum + 1; ENDLOOP; ImagerInterpress.Close[ipRef]; endTime ¬ BasicTime.Now[]; totalTime ¬ BasicTime.Period[startTime, endTime]; msgRope ¬ IO.PutFR1[" Done in time (%r)", [integer[totalTime]]]; Feedback.Append[ggData.router, end, $Statistics, msgRope]; GGEvent.SawTextFinish[ggData, NIL]; } ELSE Feedback.Append[ggData.router, oneLiner, $Complaint, "ToIP failed: please install Interpress, then retry this operation"]; }; nullRect: BoundBox = NEW[GGCoreTypes.BoundBoxObj _ [0,0,0,0, TRUE, FALSE]]; Intersection: PUBLIC PROC [rect1, rect2: BoundBox] RETURNS [rect: BoundBox] = { rect _ NEW[GGCoreTypes.BoundBoxObj]; IF rect1.infinite THEN rect^ ¬ rect2^ ELSE IF rect2.infinite THEN rect^ ¬ rect1^ ELSE IF rect1.null OR rect2.null THEN rect^ ¬ nullRect^ ELSE { rect1HiX, rect2HiX, rect1HiY, rect2HiY: REAL; rect1HiX ¬ rect1.hiX; IF rect1HiX < rect2.loX THEN {rect^ _ nullRect^; RETURN}; -- rect1 totally left of rect2 rect2HiX ¬ rect2.hiX; IF rect2HiX < rect1.loX THEN {rect^ _ nullRect^; RETURN}; -- rect2 totally left of rect1 rect1HiY ¬ rect1.hiY; IF rect1HiY < rect2.loY THEN {rect^ _ nullRect^; RETURN}; -- rect1 totally below rect2 rect2HiY ¬ rect2.hiY; IF rect2HiY < rect1.loY THEN {rect^ _ nullRect^; RETURN}; -- rect2 totally below rect1 rect.loX ¬ MAX[rect1.loX, rect2.loX]; rect.loY ¬ MAX[rect1.loY, rect2.loY]; rect.hiX ¬ MIN[rect1HiX, rect2HiX]; rect.hiY ¬ MIN[rect1HiY, rect2HiY]; rect.null ¬ rect.infinite ¬ FALSE; }; }; infinitePage: BoundBox = GGBoundBox.CreateBoundBox[0,0,0,0,FALSE,TRUE]; DoMakeAux: PROC [context: Imager.Context, q: QualityMode, s: DisplayStyle, ggData: GGData, bBox: BoundBox, literal: BOOL ¬ FALSE, transform: Transformation ¬ NIL] = { tempQuality: QualityMode ¬ ggData.camera.quality; tempStyle: DisplayStyle ¬ ggData.camera.displayStyle; ggData.camera.quality ¬ q; ggData.camera.displayStyle ¬ s; Imager.ScaleT[context, metersPerPixel]; IF transform # NIL THEN Imager.ConcatT[context, transform]; IF literal THEN GGRefresh.PaintEntireScene[context, ggData, FALSE, FALSE] ELSE GGRefresh.InterpressEntireScene[context, ggData, bBox]; ggData.camera.quality ¬ tempQuality; ggData.camera.displayStyle ¬ tempStyle; }; ToIP: PUBLIC UserInputProc = { DoMakeInterpress: PROC [dc: Imager.Context] = { DoMakeAux[dc, quality, print, ggData, infinitePage]; }; ipName: Rope.ROPE ¬ NARROW[event.rest.first]; ToIPAux[ggData, ipName, NARROW[event.first], DoMakeInterpress]; }; ToIPMultipage: PUBLIC UserInputProc = { ipName: Rope.ROPE ¬ NARROW[event.rest.first]; ToIPMultipageAux[ggData, ipName, NARROW[event.first], FALSE]; }; ToIPLit: PUBLIC UserInputProc = { DoMakeInterpress: PROC [dc: Imager.Context] = { DoMakeAux[dc, showall, print, ggData, infinitePage, TRUE]; }; 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, infinitePage]; }; 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]; }; 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]; }; 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.PutFL[ggData.router, oneLiner, $Show, "Tested %g total points. %g unihits. %g multihits. %g differences", LIST[[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.Append[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.Append[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.Append[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.PutFL[ggData.router, oneLiner, $Show, "Gravity extent is %g points = %g inches = %g centimeters", LIST[[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[$SetFont, SetFont, none]; 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[$ToIPMultipage, ToIPMultipage, rope]; RegisterAction[$ToIPSelected, ToIPSelected, rope]; RegisterAction[$ToIPOnPlayback, ToIP, rope]; RegisterAction[$ToIPScreen, ToIPScreen, rope]; RegisterAction[$ToIPLit, ToIPLit, none]; RegisterAction[$ToIPTestGravity, ToIPTestGravity, rope]; 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. .–GGEventImplA.mesa Copyright Ó 1988, 1989, 1991, 1992 by Xerox Corporation. All rights reserved. Contents: Once an event reaches the front of the slack-process queue, it is dispatched to one of the procedures in this module. Pier, June 23, 1993 5:37 pm PDT Bier, September 13, 1993 2:51 pm PDT Doug Wyatt, April 17, 1992 2:07 pm PDT 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 InterpressInterpreter.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 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 Hierarchy Menu RegisterAction[$Undelete, Undelete, none]; RegisterAction[$UndeleteAutoConfirm, UndeleteAutoConfirm, none]; RegisterAction[$UnionCombine , UnionCombine, none]; ÊS.•NewlineDelimiter –(cedarcode) style˜code™Kšœ ÏeœC™NKšÏnœw™K™K™$K™&K™—šÏk ˜ JšœÈŸœZ˜¤K˜—šž œŸœŸ˜JšŸœ®Ÿœ;˜òKšŸœŸ˜)—˜Kšœ Ÿœ˜+Kšœ Ÿœ˜&KšœŸœ˜#KšœŸœ˜%KšœŸœ&˜AKšœŸœ˜/Kšœ Ÿœ˜*Kšœ Ÿœ ˜1Kšœ Ÿœ˜!KšœŸœ˜'Kšœ Ÿœ ˜1KšœŸœ˜1KšœŸœ˜!KšœŸœ˜3Kšœ Ÿœ˜-KšœŸœ˜!Kšœ Ÿœ ˜1KšœŸœŸœ!˜K˜K˜K™—K˜Kšœ ŸœŸœ˜KšœŸœŸœ˜K˜K˜K˜-Kšœ;Ÿœ˜FKšœ7¡˜VKšœ5˜5šŸœŸœ¡ ˜FKšœ Ÿœ.˜;Kšœ,¡%˜QKšœ*  œ˜9Kšœ8˜8K˜EK˜—šŸœ¡I˜PKšœ Ÿœ/˜˜>Kšœ7Ÿœ ˜FK˜—K˜—KšŸœ{˜K˜—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šœŸœ¡!˜PK˜K˜—Kšœ ŸœŸœ˜Kšœ Ÿœ˜Kšœ ŸœŸœ˜KšœŸœŸœ˜"K–…[scene: GGModelTypes.Scene, level: GGModelTypes.WalkLevel, selectClass: GGSegmentTypes.SelectionClass, classType: ATOM _ NIL]˜MK˜šŸœ0Ÿœ˜8š žœŸœŸœŸœŸœ˜QšŸœ-Ÿœ˜5K˜KšœŸœ˜ K˜—K˜—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˜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˜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˜0KšœQ¡:˜‹Kšœ/¡%˜TKš œ   œ!ŸœŸœ Ÿœ Ÿœ Ÿœ˜|Kšœ8Ÿœ¡‘˜ÏKšœg˜gKšœ'¡"˜IK˜7KšŸœŸœŸœ$˜LKšœD˜DKš œ  œ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šœD˜DK˜—KšŸœm˜qK˜—Kšœ0˜0Kšœ¡˜K˜—šžœŸœ˜!K™]Kšœ˜Kšœ ŸœŸœ˜Kš ŸœŸœŸœ ŸœŸœ˜1Kšœ Ÿœ˜#KšŸœŸœŸœ"˜KšŸœ¡˜Kšœ(˜(K˜&Kš œ Ÿœ$ŸœŸœŸœ˜XšŸœ Ÿ˜šœŸœ˜Kšœ@¡!™aKš œ   œ!ŸœŸœ Ÿœ Ÿœ Ÿœ˜|Kšœ  œ(Ÿœ˜FK˜CKšœ  œ œŸœ5Ÿœ ŸœŸœŸœ˜äKšœ˜—šœŸœŸœ¡/˜GKšœ2¡9˜kK˜0K˜sK˜=Kš œ   œ!Ÿœ Ÿœ Ÿœ˜[Kšœ:Ÿœ˜?šœ  œ8Ÿœ˜VK™,—Kšœ&¡H˜nKšœ/¡(˜WKš œ   œ5Ÿœ$Ÿœ¡d£˜…Kšœ]Ÿœ˜bK˜—šŸœ¡˜KšœŸœ7˜BKšœB¡0˜rKšœ@¡™UKšœ   œ ˜Kšœ  œ(Ÿœ˜FKšœ>™>Kšœ  œŸœŸœ œŸœ  œŸœŸœ ŸœŸœŸœ˜ÒKšœ˜——Kšœ'˜'Kšœ˜—Kšœ¡˜K˜—šž œŸœŸœŸœŸœŸœŸœŸœŸœŸœ˜³K˜1Kšœ ŸœŸœ˜K˜Kš œ)Ÿœ ŸœŸœ Ÿœ ˜lK˜ºK˜TKšœJŸœ˜OKšŸœŸœ ŸœŸœ˜šŸœŸœŸœ˜Kšœ*˜*K˜#K˜—šŸœ˜Kšœ-˜-K˜$K˜—Kšœ?™?šœ  œ(Ÿœ˜FJ™:—Kšœ   œ¡˜CKšŸœ Ÿœ@˜PKšœ  œ  œ,Ÿœ ˜Kšœ˜K˜—šžœ˜Kšœ ŸœŸœ˜+KšŸœŸœŸœo˜ŒšŸœ˜Kšœ   œ¢œ ¡˜HKšœ   œ ˜Kš œ  œ Ÿœ(ŸœŸœ˜_Kšœ   œ ˜K˜—K˜—K˜šžœ˜"Kšœ)˜)Kšœ˜K˜—šžœ˜,Kš œŸœŸœŸœŸœ˜9KšœŸœB˜LKšŸœŸœ~˜KšŸœŸœŸœz˜¡šŸœ˜š žœŸœŸœŸœŸœ˜OK˜KšœU˜UK˜LK˜—Kšœ˜Kšœ˜KšœŸœŸœ˜"K˜2K–h[gargoyleData: GGInterfaceTypes.GargoyleData, selectClass: GGInterfaceTypes.SelectionClass _ normal]šœS¡™qKšœ   œ!ŸœŸœ˜RK˜=K˜Sš ŸœŸœŸœ"ŸœŸœŸ˜FKšœ=˜=KšŸœ˜—K–<[bBox: GGModelTypes.BoundBox, by: GGModelTypes.BoundBox]šœs™sKšœ  œ;Ÿœ˜YKšœ˜Kšœr˜rKšœ}ŸœŸœ¡6˜ÒK˜—Kšœ˜K˜—šžœ˜$KšœŸœB˜LKšŸœ Ÿœ‡˜–šŸœ˜š žœŸœŸœŸœŸœ˜RšŸœ˜KšŸœ:˜>—KšŸœ:¡˜[K˜—KšœŸœ˜#Kšœ ŸœT˜aKšŸœŸœx˜‡KšŸœr˜vK˜—K˜K˜—šž œ˜ KšœŸœ ¡7˜KKš œ ŸœŸœŸœŸœ˜4Kš œ ŸœŸœŸœŸœ˜9KšœŸœB˜LKšŸœ Ÿœ˜KšŸœŸœŸœ˜ºšŸœ˜š žœŸœŸœŸœŸœ˜PK˜KšœF¡L˜’K˜LK˜—Kšœ˜Kšœ˜KšœŸœŸœ˜"K˜2K–h[gargoyleData: GGInterfaceTypes.GargoyleData, selectClass: GGInterfaceTypes.SelectionClass _ normal]šœS¡™qKšœ   œ!ŸœŸœ˜RKšœ@¡˜ZK˜Tš ŸœŸœŸœ"ŸœŸœŸ˜FKšœ=˜=KšŸœ˜—K–<[bBox: GGModelTypes.BoundBox, by: GGModelTypes.BoundBox]šœs™sKšœ  œ;Ÿœ˜YKšœ˜KšœaŸœ1˜–Kšœ}ŸœŸœ˜›K˜—Kšœ˜K˜—šžœ˜*KšœŸœ ¡7˜KKš œŸœŸœŸœŸœ¡˜HKšŸœŸœ˜›Kš ŸœŸœŸœŸœŸœŸœ¡˜Kšœ˜K˜—šžœ˜'KšœŸœ ¡7˜KKšœŸœ¡˜(Kš œŸœŸœŸœŸœŸœ¡˜|Kšœ˜K˜—šžœ˜"KšœŸœB˜LKšŸœ Ÿœƒ˜’šŸœ˜š žœŸœŸœŸœŸœ˜PKšŸœŸœB˜eK˜7KšŸœ Ÿœ¡˜EK˜—KšœŸœ ¡&˜:Kšœ ŸœŸœ˜K˜=Kšœ ŸœR˜_KšŸœŸœt˜ƒšŸœ˜šœŸœŸœŸœŸ˜*KšŸœ*˜-KšŸœŸœf˜s—Kšœ?˜?K˜—K˜—K˜K˜—šž œ˜ KšœŸœB˜LKšŸœ Ÿœƒ˜’šŸœ˜š žœŸœŸœŸœŸœ˜PK˜Kšœ2˜2K˜LK˜—Kšœ˜Kšœ˜KšœŸœŸœ˜"K˜2K–h[gargoyleData: GGInterfaceTypes.GargoyleData, selectClass: GGInterfaceTypes.SelectionClass _ normal]šœS¡™qKšœ   œ!ŸœŸœ˜RKšœ@¡˜ZK˜Tš ŸœŸœŸœ"ŸœŸœŸ˜FKšœ=˜=KšŸœ˜—K–<[bBox: GGModelTypes.BoundBox, by: GGModelTypes.BoundBox]šžœE™KKšœ˜KšœP˜PKšœ}ŸœŸœ˜›Kšœ˜—Kšœ˜K˜—šžœ˜&KšœQ™QKšœŸœB˜LKšŸœ Ÿœ‰˜˜šŸœ˜š žœŸœŸœŸœŸœ˜OK˜KšŸœ Ÿœ1˜FKšŸœŸœ<Ÿœ¡˜jK˜—KšœŸœ˜K˜Kšœ Ÿœ2žœ˜^KšŸœŸœs˜‚šŸœ˜K˜‰K˜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[]]]šŸœ/˜3šŸœŸœ˜&KšŸœ!Ÿœh˜“K–[format: ROPE _ NIL, v1: IO.Value _ [null[]], v2: IO.Value _ [null[]], v3: IO.Value _ [null[]], v4: IO.Value _ [null[]], v5: IO.Value _ [null[]]]šŸœ1˜5Kšœ‡˜‡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˜LK˜K˜—Kšœ ŸœŸœ˜Kšœ˜Kšœ˜Kšœ ŸœŸœ˜KšœŸœŸœ˜"K˜2K–h[gargoyleData: GGInterfaceTypes.GargoyleData, selectClass: GGInterfaceTypes.SelectionClass _ normal]šœS¡™qKšœ   œ!ŸœŸœ˜RKšœ9¡˜SK˜Vš ŸœŸœŸœ"ŸœŸœŸ˜FKšœ=˜=KšŸœ˜—Kšœs™sKšœ  œ;Ÿœ˜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–)[rope: ROPE, oldStream: STREAM _ NIL]šœŸœ˜KšœŸœ˜1K˜3Kšœ˜Kšœ˜K˜—šžœ˜!KšœT™TKšœ ŸœŸœ˜KšœŸœ˜K˜YKšŸœŸœ ŸœŸœ˜KšœP˜PKšœ˜K˜—šžœ˜(KšœT™TKšœ ŸœŸœ˜KšœŸœ˜K˜gKšŸœŸœ ŸœŸœ˜KšœW˜WKšœ˜K˜—šžœ˜KšœŸœ–™·K˜K˜=KšœŸœB˜LKšŸœ Ÿœ ŸœŸœ—˜¶šŸœ˜šŸ˜Kšœ˜š ž œŸœŸœŸœŸœ˜KšŸœŸœ˜Kšœ3˜3Kšœ˜K˜,Kšœ#¡(˜KKšœC¡œ¡˜ZK˜#K˜_Kš ŸœŸœ ŸœŸœ¡(˜DK˜LK˜—K˜—Kšœ ŸœŸœ˜KšœŸ˜Kšœ˜KšœŸœŸœ˜"K˜wKšŸœŸœ ŸœŸœ˜K˜9K˜2K–h[gargoyleData: GGInterfaceTypes.GargoyleData, selectClass: GGInterfaceTypes.SelectionClass _ normal]šœR™RKšœ   œ!ŸœŸœ˜RKšœLž œ)˜š ŸœŸœŸœ"ŸœŸœŸ˜FKšœ=˜=KšŸœ˜—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[]]]šœXŸœ*˜†Kšœ}ŸœŸœ˜›KšŸœ˜—Kšœ˜—Kšœ˜K˜—Kš œŸœŸœŸœŸœ˜=šžœŸœ(Ÿœ1ŸœŸœŸœŸœ˜ÄKšœ ŸœŸœ˜š žœŸœŸœŸœŸœ˜QšŸœ ŸœŸœ˜Kšœ&¡˜8K˜,K˜—KšŸœŸœ1Ÿœ˜GK˜—šŸœ!Ÿœ˜(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Ÿœ˜HK˜—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˜SK˜_Kš ŸœŸœ ŸœŸœ¡(˜DK˜LK˜—Kšœ ŸœŸœ˜KšœŸ˜Kšœ˜Kšœ˜KšœŸœŸœ˜"K˜ƒKšŸœŸœ ŸœŸœ˜K˜9K˜2K–h[gargoyleData: GGInterfaceTypes.GargoyleData, selectClass: GGInterfaceTypes.SelectionClass _ normal]šœK™KKšœ   œ!ŸœŸœ˜RK˜Gš ŸœŸœŸœ"ŸœŸœŸ˜FKšœ6˜6KšŸœ˜—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šœ„™„KšœŸœB˜LKšŸœ Ÿœx˜‡šŸœ˜š ž œŸœŸœŸœŸœ˜NKšŸœ ŸœŸœ.˜BKšŸœŸœ;¡˜`K˜K˜—Kšœ Ÿœ ˜Kšœ˜Kšœ ŸœP˜]KšŸœŸœb˜qšŸœ˜š ž œŸœŸœŸœŸœ˜@K–-[y: REAL, x: REAL, distance: [-126..0]]šŸœ1Ÿœ@˜xK˜K˜—Kšœ   œ!ŸœŸœ˜PKšœ+˜+K˜bKšœf˜fKšœjŸœŸœŸœ˜¡K˜—K˜—K˜K˜—šž œŸœŸœŸœ˜ŸœX˜ÓKšœ˜K˜—šžœ˜$KšœTŸ œ™^KšœŸœŸœ˜0KšŸœŸœŸœs˜•KšŸœ-˜1Kšœ˜K˜—šžœ˜+KšœTŸ œ™^KšœŸœŸœ˜0KšŸœŸœŸœ{˜KšŸœ-˜1Kšœ˜K˜—šžœŸœŸœŸœ˜Iš žœŸœŸœŸœŸœ˜FK˜0K–-[y: REAL, x: REAL, distance: [-126..0]]š ŸœŸœ ŸœŸœ1ŸœŸœ@˜¹K˜—Kšœ   œ!ŸœŸœ˜PKšœ+˜+K˜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šŸœŸœ;¡˜`K˜—Kšœ˜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[]]]šœSŸœ(Ÿœc˜äKšœ˜K˜—K˜K™šžœŸœŸœŸœŸœŸœŸœ˜nK™.šž œ#˜/Kšœ_˜_Kšœ˜—šž œŸœ˜.Kš "£˜K˜—Kšœ˜KšœŸœ˜Kšœ ŸœŸœ˜KšœŸœ˜"Kšœ Ÿœ˜K˜EKšœ Ÿœ˜K˜KK˜šŸœ)Ÿœ˜1K˜;K˜šŸœŸ˜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šÐbk˜Kšœ ˜ K˜K˜9KšŸœŸœ&ŸœŸœ ˜BKšœ¡œ#˜AKšŸœŸœ ŸœŸœ ˜%K˜Kšœ Ÿœ ˜KšŸœŸœŸœŸœ˜IK˜(K˜+K˜&šŸœŸœ˜ K˜EK˜1K˜Kšœ ŸœŸœ˜šŸœŸœ˜&Kšœ Ÿœ˜K˜2K˜SK˜—KšŸœ˜šŸœŸœ˜'Kšœ Ÿœ˜K˜4K˜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šœ>™>Kšœ   œ ˜Kšœ  œ'Ÿœ˜EKšœ   œ ¡0˜PKšœQ˜Qšœ}ŸœŸœ˜›Kšœ”™”—šŸ˜˜Kšœl˜lK˜—šœ˜Kšœa˜aK˜——Kš¥œ˜—Kšœ¡ ˜—K˜K™šžœ˜&Kšœ Ÿœ#˜0Kšœ*˜*K˜K˜—šžœ˜#KšœŸœŸœ˜1K˜GKšœ,˜,K˜K˜—šžœ˜&KšœŸœ  œ ¡˜DKšœ ŸœŸœ˜šŸœŸ˜šœ ˜ KšŸœŸœ˜*šŸœ˜Kšœ|˜|Kšœ Ÿœ˜K˜—K˜—K˜"KšŸœ3¡œ˜[—šŸœ Ÿœ˜Kšœ œ˜)Kšœ!˜!K˜—K˜K˜—šžœ˜#Kš œŸœŸœŸœŸœ˜3Kšœ)˜)K˜K˜—šžœ˜$KšœŸœ$˜0KšœkŸœJ˜¹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šœ<˜