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
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] = {
Make new outlines of each of the children
newSlice: Slice ¬ IF GGSliceOps.GetType[slice]=$Traj THEN GGOutline.CreateOutline[slice, fillColor] ELSE slice;
newSlice.parent ¬ cluster; -- reparent BEFORE reselecting
can't ReselectSlice while walking selected slices !!
GGSelect.ReselectSliceAllClasses[newSlice, ggData.scene];
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
ggData.refresh.startBoundBox^ ← GGScene.BoundBoxOfSelected[ggData.scene, normal, TRUE]^;
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] = {
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.
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];
ggData.refresh.startBoundBox^ ← GGScene.BoundBoxOfSelected[scene, normal]^;
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];
Create an outline (or extract) an outline containing the distinguished slice.
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;
If we get to here, newOutline has a $Outline in it.
All of the potential holes are top-level slices
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.
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.
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];
Get rid of old top level slice if it got broken into holes.
};
};
$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] = {
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).
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 = {
The user has typed a character. If we are not currently in text entry mode, enter that mode.
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 => {
ggData.refresh.startBoundBox^ ← GGSliceOps.GetBoundBox[slice]^; -- remember old, larger bound box
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];
EnlargeStartBox to encompass new caret point
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
ggData.refresh.startBoundBox^ ← GGSliceOps.GetBoundBox[slice]^; -- boundBox has grown
GGRefresh.NullStartBox[ggData];
GGRefresh.EnlargeStartBox[ggData, GGSliceOps.GetBoundBox[slice], NIL];
ggData.refresh.addedObject ← slice; WHY WAS THIS HERE BEFORE??
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;
};
ggData.refresh.startBoundBox^ ← GGSliceOps.GetBoundBox[slice]^;
GGRefresh.EnlargeStartBox[ggData, GGSliceOps.GetBoundBox[slice], NIL];
callers of this proc should have initialized the start box
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[];
ggData.refresh.startBoundBox^ ← GGScene.BoundBoxOfSelected[ggData.scene, normal]^; -- remember original bound box
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;
GGBoundBox.EnlargeByBox[bBox: ggData.refresh.startBoundBox, by: GGScene.BoundBoxOfSelected[ggData.scene, normal] ];
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[];
ggData.refresh.startBoundBox^ ← GGScene.BoundBoxOfSelected[ggData.scene, normal]^; -- remember original bound box
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;
GGBoundBox.EnlargeByBox[bBox: ggData.refresh.startBoundBox, by: GGScene.BoundBoxOfSelected[ggData.scene, normal] ];
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[];
ggData.refresh.startBoundBox^ ← GGScene.BoundBoxOfSelected[ggData.scene, normal]^; -- remember original bound box
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;
Assert: Turning off drop shadow should always result in a smaller bound box
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 = {
Checks for consistent selected text looks, then copies looks to the default looks
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]]] ];
};
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 <FontFamily-FontFace> <scalar> 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[<scalar>]. The font is made with ImagerFont.Scale[font, 1] (in all cases I know about).
SetPrintFont. The user selects a <FontFamily-FontFace> <scalar> 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[<scalar>]. The font is made with ImagerFont.Scale[font, 1] (in all cases I know about).
SetScreenFont. The user selects a <FontFamily-FontFace> <preferred size> pair such as "CMR 10". Gargoyle assumes FontPrefix = Xerox/TiogaFonts/ and lets transformation M = Scale[<preferred size>]. The font is made with ImagerFont.Scale[font, 1] if the font is CMR, with ImagerFont.Scale[font, 1.0/<preferred size>], 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 <FontPrefix> <FontFamily-FontFace> <Transformation> 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 <FontPrefix> <FontFamily-FontFace> <Transformation> <Preferred Size> 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 <scalar>*(1.0/<scalar>) 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
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[];
ggData.refresh.startBoundBox^ ← GGScene.BoundBoxOfSelected[ggData.scene, normal]^; -- remember original bound box
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;
GGBoundBox.EnlargeByBox[bBox: ggData.refresh.startBoundBox, by: GGScene.BoundBoxOfSelected[ggData.scene, normal] ];
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 = {
SetPressFont expects input of the form <FontFamily-FontFace> <scalar>. e.g. "Cream-BI 12"
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 = {
SetPressFont expects input of the form <FontFamily-FontFace> <scalar>. e.g. "Modern-BI 12"
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 = {
SetScreenFont expects input of the form <FontFamily-FontFace> <scalar>. e.g. "Cream12-BI 20"
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 = {
SetFontDetailed expects input of the form <FontPrefix> <FontFamily-FontFace> <Transformation> 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 <FontPrefix> and <FontFamily-FontFace>; if not, then designSize and storedSize are defaulted to 1.0
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 = {
SetFontLiteral expects input of the form <FontPrefix> <FontFamily-FontFace> <Transformation> <storedSize> <design size>. Gargoyle accepts this as literal information and makes no attempt at understanding the semantics of the data.
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 = {
Outputs string in the form <FontFullName> <transformation> <storedSize> <designSize>
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 = {
Outputs string in the form <FontFullName> <transformation> <storedSize> <designSize>
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 = {
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.
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[];
ggData.refresh.startBoundBox^ ← GGScene.BoundBoxOfSelected[ggData.scene, normal]^;
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;
GGBoundBox.EnlargeByBox[bBox: ggData.refresh.startBoundBox, by: GGScene.BoundBoxOfSelected[ggData.scene, normal] ];
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 = {
Copies the fontData from the LAST selected text slice to all the other selected text slices
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[];
ggData.refresh.startBoundBox^ ← GGScene.BoundBoxOfSelected[scene, normal]^;
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;
GGBoundBox.EnlargeByBox[bBox: ggData.refresh.startBoundBox, by: GGScene.BoundBoxOfSelected[scene, normal] ];
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 = {
Checks for consistent selected text fonts, then selects all the text slices with fontData matching that font, excluding translation.
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 = {
selects all the text slices whose fontData.userFSF contain the Tioga selection as a SUBSTRING.
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 = {
selects all the text slices whose fontData.literal contain the Tioga selection as a SUBSTRING.
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 = {
Checks for consistent selected text fonts, then copies font to the default font
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]]] ];
};
Interpress Utilities
ReadIP: PROC [ipName: Rope.ROPE, ggData: GGData, opName: Rope.ROPE, op: ATOM] RETURNS [scene: Scene ¬ NIL] = {
The core routine to implement MergeIPEditable.
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
Following code required to get the context state to the default assumed by InterpressInterpreter.DoPage
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];
ggData.refresh.startBoundBox^ ← GGSliceOps.GetBoundBox[slice]^;
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 = {
All selected IP Slices are now store-by-reference.
IncludeIPBy[ggData, "IncludeIPByReference", FALSE];
};
IncludeIPByValue: UserInputProc = {
All selected IP Slices are now store-by-value.
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 = {
Show whether the selected IP Slices are store-by-reference or store-by-value
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.ROPENARROW[event.rest.first];
ToIPAux[ggData, ipName, NARROW[event.first], DoMakeInterpress];
};
Gravity Utilities
TestGravity2: PUBLIC PROC [dc: Imager.Context, ggData: GGData] = {
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.
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 {
PaintSpot
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;
PaintHitLine
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
ggData.refresh.startBoundBox^ ← GGScene.DeleteAllSelected[ggData.scene]^;
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 = {
event.first = $DescribeCurve, event.rest = $Selected or $Caret
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];
ggData.refresh.startBoundBox^ ← GGSliceOps.GetBoundBox[traj]^;
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];
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.
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
Gravity Operations
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;
Gravity:
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];
Text Menu
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];
Fonts Menu
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];
Interpress Menu
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];
Hierarchy Menu
RegisterAction[$Delete, Delete, none];
RegisterAction[$Undelete, Undelete, none];
RegisterAction[$UndeleteAutoConfirm, UndeleteAutoConfirm, none];
RegisterAction[$UnionCombine , UnionCombine, none];
RegisterAction[$DescribeCurve, DescribeCurve, none];
RegisterAction[$AddHoles, AddHoles, none];
RegisterAction[$DeleteHoles, DeleteHoles, none];
RegisterAction[$CloseOrNewline, CloseOrNewline, none];
RegisterAction[$Close, Close, none];
};
RegisterEventProcs[];
END.