GGEventImplA.mesa
Contents: Once an event reaches the front of the slack-process queue, it is dispatched to one of the procedures in this module.
Bier, May 1, 1987 0:02:36 am PDT
Ken Pier, May 11, 1987 7:05:21 pm PDT
DIRECTORY
Ascii, Atom, AtomButtons, BasicTime, --CombinePoly,-- CubicSplines, FS, GGAlign, GGBasicTypes, GGBoundBox, GGCaret, Feedback, GGEvent, GGFont, GGFromImager, GGGravity, GGInterface, GGInterfaceTypes, GGModelTypes, GGMultiGravity, GGScene, GGOutline, GGParseOut, GGRefresh, GGSegment, GGSegmentTypes, GGSelect, GGSequence, GGShapes, GGSlice, CodeTimer, --GGTouch,-- GGTraj, GGUtility, Vectors2d, GGWindow, Imager, ImagerInterpress, ImagerTransformation, Interpress, IO, MessageWindow, --PrincOpsUtils,-- RealFns, Random, Rope, Rosary, ViewerTools;
GGEventImplA: CEDAR PROGRAM
IMPORTS Atom, AtomButtons, BasicTime, --CombinePoly,-- FS, GGAlign, GGBoundBox, GGCaret, Feedback, GGEvent, GGFont, GGFromImager, GGGravity, GGInterface, GGMultiGravity, GGScene, GGOutline, GGParseOut, GGRefresh, GGSegment, GGSelect, GGSequence, GGShapes, GGSlice, CodeTimer, --GGTouch,-- GGTraj, GGUtility, Vectors2d, GGWindow, Imager, ImagerInterpress, ImagerTransformation, Interpress, IO, MessageWindow, --PrincOpsUtils,-- Random, RealFns, Rope, ViewerTools
EXPORTS GGEvent = BEGIN
BoundBox: TYPE = GGBoundBox.BoundBox;
Slice: TYPE = GGModelTypes.Slice;
SliceGenerator: TYPE = GGModelTypes.SliceGenerator;
SliceDescriptor: TYPE = GGModelTypes.SliceDescriptor;
SliceDescriptorGenerator: TYPE = GGModelTypes.SliceDescriptorGenerator;
EntityGenerator: TYPE = GGModelTypes.EntityGenerator;
FeatureData: TYPE = GGInterfaceTypes.FeatureData;
GGData: TYPE = GGInterfaceTypes.GGData;
Joint: TYPE = GGModelTypes.Joint;
JointGenerator: TYPE = GGModelTypes.JointGenerator;
AlignBag: TYPE = GGGravity.AlignBag;
OutlineDescriptor: TYPE = GGModelTypes.OutlineDescriptor;
TriggerBag: TYPE = GGGravity.TriggerBag;
Outline: TYPE = GGModelTypes.Outline;
Point: TYPE = GGBasicTypes.Point;
Scene: TYPE = GGModelTypes.Scene;
Segment: TYPE = GGSegmentTypes.Segment;
SegAndIndex: TYPE = GGSequence.SegAndIndex;
Sequence: TYPE = GGModelTypes.Sequence;
SliceParts: TYPE = GGModelTypes.SliceParts;
Traj: TYPE = GGModelTypes.Traj;
TrajGenerator: TYPE = GGModelTypes.TrajGenerator;
SegmentGenerator: TYPE = GGModelTypes.SegmentGenerator;
SequenceGenerator: TYPE = GGModelTypes.SequenceGenerator;
Vector: TYPE = GGBasicTypes.Vector;
FontData: TYPE = GGFont.FontData;
NotYetImplemented: PUBLIC SIGNAL = CODE;
EnterEditingMode: PROC [ggData: GGData, refChar: REF CHAR] = {
IF refChar^=Ascii.BS THEN { -- try to enter editing of a single selected text slice
sliceDescGen: SliceDescriptorGenerator ← GGSelect.SelectedSlices[ggData.scene, normal];
sliceDesc: SliceDescriptor ← GGSelect.NextSliceDescriptor[sliceDescGen];
IF sliceDesc=NIL OR sliceDesc.slice.class.type#$Text OR GGSelect.NextSliceDescriptor[sliceDescGen]#NIL THEN {
Feedback.AppendHerald[ggData.feedback, "Select a single text slice for editing", oneLiner];
Feedback.Blink[ggData.feedback];
RETURN;
};
GGSelect.DeselectSlice[slice: sliceDesc.slice, parts: sliceDesc.parts, scene: ggData.scene, selectClass: normal];
ggData.refresh.textInProgress ← sliceDesc.slice; -- successfully enter editing mode
GGWindow.RestoreScreenAndInvariants[paintAction: $SelectionChanged, ggData: ggData, remake: none, backgndOK: TRUE, edited: FALSE, okToClearFeedback: TRUE];
}
ELSE {
slice: Slice ← ggData.refresh.textInProgress ← NewTextSlice[text: Rope.FromChar[refChar^], ggData: ggData]; -- start a new text slice
IF slice=NIL THEN RETURN; -- can't start a string with whitespace
ggData.refresh.startBoundBox^ ← slice.boundBox^;
ggData.refresh.addedObject ← slice;
GGWindow.RestoreScreenAndInvariants[paintAction: $ObjectAdded, ggData: ggData, remake: sceneBag, backgndOK: FALSE, edited: TRUE, okToClearFeedback: TRUE];
};
}; -- end EnterEditingMode
AddChar: PUBLIC PROC [clientData: REF ANY, event: LIST OF REF ANY] = {
ggData: GGData ← NARROW[clientData];
slice: Slice;
obBox: BoundBox;
refChar: REF CHAR;
IF NARROW[event.first, ATOM]#$AddChar THEN ERROR;
refChar ← NARROW[event.rest.first];
IF ggData.refresh.textInProgress=NIL THEN {
GGCaret.NoAttractor[ggData.caret]; -- so the feedback goes away.
EnterEditingMode[ggData, refChar];
RETURN;
}
ELSE { -- add to textInProgress
CodeTimer.StartInt[$AddChar, $Gargoyle];
slice ← ggData.refresh.textInProgress;
obBox ← GGBoundBox.CopyBoundBox[slice.boundBox]; -- remember old bound box for now
IF refChar^=Ascii.BS THEN {
GGSlice.BackspaceText[slice: slice];
ggData.refresh.startBoundBox^ ← slice.boundBox^; -- bBox was updated by GGSlice
GGBoundBox.EnlargeByBox[bBox: ggData.refresh.startBoundBox, by: obBox];
GGWindow.RestoreScreenAndInvariants[paintAction: $ObjectChangedBoundBoxProvided, ggData: ggData, remake: sceneBag, backgndOK: FALSE, edited: TRUE, okToClearFeedback: TRUE];
If we ever allow text slices to have hot joints, we will have to be more careful here.
}
ELSE {
GGSlice.AppendText[slice: slice, text: Rope.FromChar[refChar^] ];
ggData.refresh.startBoundBox^ ← slice.boundBox^; -- boundBox was updated by GGSlice
GGBoundBox.EnlargeByBox[bBox: ggData.refresh.startBoundBox, by: obBox];
ggData.refresh.addedObject ← slice;
GGWindow.RestoreScreenAndInvariants[paintAction: $ObjectAdded, ggData: ggData, remake: none, backgndOK: FALSE, edited: TRUE, okToClearFeedback: TRUE];
If we ever allow text slices to have hot joints, we will have to be more careful here.
};
CodeTimer.StopInt[$AddChar, $Gargoyle];
};
}; -- end AddChar
NewTextSlice: PRIVATE PROC [text: Rope.ROPE, ggData: GGData, selectIt: BOOLFALSE] RETURNS [slice: Slice] = {
caretPos: Point ← GGCaret.GetPoint[ggData.caret];
success: BOOLFALSE;
fontData: FontData ← GGFont.CopyFontData[ggData.defaults.font];
slice ← GGSlice.MakeTextSlice[text];
fontData.transform ← ImagerTransformation.TranslateTo[fontData.transform, caretPos];
success ← GGSlice.SetTextFontAndTransform[slice, fontData, ggData.feedback];
IF NOT success THEN ERROR;
GGScene.AddSlice[ggData.scene, slice, -1];
ggData.refresh.startBoundBox^ ← slice.boundBox^;
GGSelect.DeselectAll[ggData.scene, normal]; -- speeds up text entry
ggData.refresh.addedObject ← slice;
IF selectIt THEN GGSelect.SelectEntireSlice[slice, ggData.scene, normal];
GGWindow.RestoreScreenAndInvariants[paintAction: $ObjectAdded, ggData: ggData, remake: sceneBag, backgndOK: FALSE, edited: TRUE, okToClearFeedback: TRUE];
};
AddText: PUBLIC PROC [clientData: REF ANY, event: LIST OF REF ANY] = {
ggData: GGData ← NARROW[clientData];
text: Rope.ROPEIF event.rest = NIL THEN ViewerTools.GetSelectionContents[] ELSE NARROW[event.rest.first];
slice: Slice ← NewTextSlice[text, ggData, TRUE];
};
AmplifySpaceFromSelection: PUBLIC PROC [clientData: REF ANY, event: LIST OF REF ANY] = {
ggData: GGData ← NARROW[clientData];
amplifySpace: REALNARROW[event.rest.first, REF REAL]^;
slices: SliceDescriptorGenerator ← GGSelect.SelectedSlices[ggData.scene, normal];
ggData.refresh.startBoundBox^ ← GGBoundBox.BoundBoxOfSelected[ggData.scene, normal]^; -- remember original bound box
FOR sliceD: SliceDescriptor ← GGSelect.NextSliceDescriptor[slices], GGSelect.NextSliceDescriptor[slices] UNTIL sliceD=NIL DO
GGSlice.SetTextAmplifySpace[sliceD.slice, amplifySpace, ggData.feedback];
ENDLOOP;
GGBoundBox.EnlargeByBox[bBox: ggData.refresh.startBoundBox, by: GGBoundBox.BoundBoxOfSelected[ggData.scene, normal] ];
GGWindow.RestoreScreenAndInvariants[paintAction: $ObjectChangedBoundBoxProvided, ggData: ggData, remake: triggerBag, backgndOK: FALSE, edited: TRUE, okToClearFeedback: FALSE]; -- GGSlice.SetTextAmplifySpace can post error messages
}; -- end AmplifySpaceFromSelection
PrintAmplifySpace: PUBLIC PROC [clientData: REF ANY, event: LIST OF REF ANY] = {
ggData: GGData ← NARROW[clientData];
sliceDescGen: SliceDescriptorGenerator ← GGSelect.SelectedSlices[ggData.scene, normal];
sliceDesc: SliceDescriptor ← GGSelect.NextSliceDescriptor[sliceDescGen];
IF sliceDesc=NIL OR sliceDesc.slice.class.type#$Text OR GGSelect.NextSliceDescriptor[sliceDescGen]#NIL THEN {
Feedback.AppendHerald[ggData.feedback, "Select the single text slice whose amplify space need", oneLiner];
Feedback.Blink[ggData.feedback];
}
ELSE Feedback.PutF[ggData.feedback, oneLiner, "Amplify space: %g", [real[GGSlice.GetTextAmplifySpace[sliceDesc.slice]]]];
};
DropShadowOn: PUBLIC PROC [clientData: REF ANY, event: LIST OF REF ANY] = {
ggData: GGData ← NARROW[clientData];
offsetX: REALNARROW[event.rest.first, REF REAL]^;
offsetY: REALNARROW[event.rest.rest.first, REF REAL]^;
slices: SliceDescriptorGenerator ← GGSelect.SelectedSlices[ggData.scene, normal];
ggData.refresh.startBoundBox^ ← GGBoundBox.BoundBoxOfSelected[ggData.scene, normal]^; -- remember original bound box
FOR sliceD: SliceDescriptor ← GGSelect.NextSliceDescriptor[slices], GGSelect.NextSliceDescriptor[slices] UNTIL sliceD=NIL DO
GGSlice.DropShadowOn[sliceD.slice, [offsetX, offsetY]];
ENDLOOP;
GGBoundBox.EnlargeByBox[bBox: ggData.refresh.startBoundBox, by: GGBoundBox.BoundBoxOfSelected[ggData.scene, normal] ];
GGWindow.RestoreScreenAndInvariants[paintAction: $ObjectChangedBoundBoxProvided, ggData: ggData, remake: triggerBag, backgndOK: FALSE, edited: TRUE, okToClearFeedback: TRUE];
};
DropShadowOff: PUBLIC PROC [clientData: REF ANY, event: LIST OF REF ANY] = {
ggData: GGData ← NARROW[clientData];
slices: SliceDescriptorGenerator ← GGSelect.SelectedSlices[ggData.scene, normal];
ggData.refresh.startBoundBox^ ← GGBoundBox.BoundBoxOfSelected[ggData.scene, normal]^; -- remember original bound box
FOR sliceD: SliceDescriptor ← GGSelect.NextSliceDescriptor[slices], GGSelect.NextSliceDescriptor[slices] UNTIL sliceD=NIL DO
GGSlice.DropShadowOff[sliceD.slice];
ENDLOOP;
GGBoundBox.EnlargeByBox[bBox: ggData.refresh.startBoundBox, by: GGBoundBox.BoundBoxOfSelected[ggData.scene, normal] ];
GGWindow.RestoreScreenAndInvariants[paintAction: $ObjectChangedBoundBoxProvided, ggData: ggData, remake: triggerBag, backgndOK: FALSE, edited: TRUE, okToClearFeedback: TRUE];
};
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
In GGEventImplA.SetPrintFont
SetFontAux: PROC [clientData: REF ANY, fontData: FontData, andTransform: BOOLFALSE] = {
ggData: GGData ← NARROW[clientData];
lastSliceD: SliceDescriptor ← NIL;
slices: SliceDescriptorGenerator ← GGSelect.SelectedSlices[ggData.scene, normal];
ggData.refresh.startBoundBox^ ← GGBoundBox.BoundBoxOfSelected[ggData.scene, normal]^; -- remember original bound box
FOR sliceD: SliceDescriptor ← GGSelect.NextSliceDescriptor[slices], GGSelect.NextSliceDescriptor[slices] UNTIL sliceD=NIL DO
[] ← IF andTransform THEN GGSlice.SetTextFontAndTransform[sliceD.slice, fontData, ggData.feedback] ELSE GGSlice.SetTextFont[sliceD.slice, fontData, ggData.feedback];
lastSliceD ← sliceD;
ENDLOOP;
IF lastSliceD#NIL THEN Feedback.PutF[ggData.feedback, oneLiner, "Set to Font: %g", [rope[GGSlice.GetFontDataRope[lastSliceD.slice]]] ];
GGBoundBox.EnlargeByBox[bBox: ggData.refresh.startBoundBox, by: GGBoundBox.BoundBoxOfSelected[ggData.scene, normal] ];
GGWindow.RestoreScreenAndInvariants[paintAction: $ObjectChangedBoundBoxProvided, ggData: ggData, remake: triggerBag, backgndOK: FALSE, edited: TRUE, okToClearFeedback: FALSE];
};
SetPressFont: PUBLIC PROC [clientData: REF ANY, event: LIST OF REF ANY] = {
SetPressFont expects input of the form <FontFamily-FontFace> <scalar>. e.g. "Cream-BI 12"
errorRope: Rope.ROPE;
BEGIN
inStream: IO.STREAMIO.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[clientData, fontData];
EXITS
ParseError => {
ggData: GGData ← NARROW[clientData];
Feedback.AppendHerald[ggData.feedback, errorRope, oneLiner];
Feedback.Blink[ggData.feedback];
};
END;
};
SetPrintFont: PUBLIC PROC [clientData: REF ANY, event: LIST OF REF ANY] = {
SetPressFont expects input of the form <FontFamily-FontFace> <scalar>. e.g. "Modern-BI 12"
errorRope: Rope.ROPE;
BEGIN
inStream: IO.STREAMIO.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[clientData, fontData];
EXITS
ParseError => {
ggData: GGData ← NARROW[clientData];
Feedback.AppendHerald[ggData.feedback, errorRope, oneLiner];
Feedback.Blink[ggData.feedback];
};
END;
};
SetScreenFont: PUBLIC PROC [clientData: REF ANY, event: LIST OF REF ANY] = {
SetScreenFont expects input of the form <FontFamily-FontFace> <scalar>. e.g. "Cream12-BI 20"
errorRope: Rope.ROPE;
BEGIN
inStream: IO.STREAMIO.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[clientData, fontData];
EXITS
ParseError => {
ggData: GGData ← NARROW[clientData];
Feedback.AppendHerald[ggData.feedback, errorRope, oneLiner];
Feedback.Blink[ggData.feedback];
};
END;
};
SetFontDetailed: PUBLIC PROC [clientData: REF ANY, event: LIST OF REF ANY] = {
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.STREAMIO.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[clientData, fontData];
EXITS
ParseError => {
ggData: GGData ← NARROW[clientData];
Feedback.AppendHerald[ggData.feedback, errorRope, oneLiner];
Feedback.Blink[ggData.feedback];
};
END;
};
SetFontLiteral: PUBLIC PROC [clientData: REF ANY, event: LIST OF REF ANY] = {
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.STREAMIO.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[clientData, fontData];
EXITS
ParseError => {
ggData: GGData ← NARROW[clientData];
Feedback.AppendHerald[ggData.feedback, errorRope, oneLiner];
Feedback.Blink[ggData.feedback];
};
END;
};
ShowFontValues: PUBLIC PROC [clientData: REF ANY, event: LIST OF REF ANY] = {
Outputs string in the form <FontPrefix> <FontFamily> <FontFace> transformation preferredSize
ggData: GGData ← NARROW[clientData];
sliceDescGen: SliceDescriptorGenerator ← GGSelect.SelectedSlices[ggData.scene, normal];
sliceDesc: SliceDescriptor ← GGSelect.NextSliceDescriptor[sliceDescGen];
IF sliceDesc=NIL OR sliceDesc.slice.class.type#$Text OR GGSelect.NextSliceDescriptor[sliceDescGen]#NIL THEN {
Feedback.AppendHerald[ggData.feedback, "Select a single text slice for font printing", oneLiner];
Feedback.Blink[ggData.feedback];
}
ELSE Feedback.PutF[ggData.feedback, oneLiner, "Font Values: %g", [rope[GGSlice.GetFontDataRope[sliceDesc.slice]]] ];
};
ShowFontValues: PUBLIC PROC [clientData: REF ANY, event: LIST OF REF ANY] = {
Outputs string in the form <FontPrefix> <FontFamily> <FontFace> transformation preferredSize
fontRope: Rope.ROPENIL;
ggData: GGData ← NARROW[clientData];
sliceDescGen: SliceDescriptorGenerator ← GGSelect.SelectedSlices[ggData.scene, normal];
FOR sliceDesc: SliceDescriptor ← GGSelect.NextSliceDescriptor[sliceDescGen], GGSelect.NextSliceDescriptor[sliceDescGen] UNTIL sliceDesc=NIL DO
IF sliceDesc.slice.class.type#$Text THEN LOOP;
IF fontRope=NIL THEN fontRope ← GGSlice.GetFontDataRope[sliceDesc.slice];
IF Rope.Equal[fontRope, GGSlice.GetFontDataRope[sliceDesc.slice], FALSE] THEN LOOP;
Feedback.AppendHerald[ggData.feedback, "Multiple fonts in selected text slices", oneLiner];
Feedback.Blink[ggData.feedback];
RETURN;
ENDLOOP;
Feedback.PutF[ggData.feedback, oneLiner, "Font Values: %g", [rope[IF fontRope#NIL THEN fontRope ELSE "No text slices selected"]] ];
};
ShowFontValuesLiteral: PUBLIC PROC [clientData: REF ANY, event: LIST OF REF ANY] = {
Outputs string in the form <FontFullName> <transformation> <storedSize> <designSize>
ggData: GGData ← NARROW[clientData];
sliceDescGen: SliceDescriptorGenerator ← GGSelect.SelectedSlices[ggData.scene, normal];
sliceDesc: SliceDescriptor ← GGSelect.NextSliceDescriptor[sliceDescGen];
IF sliceDesc=NIL OR sliceDesc.slice.class.type#$Text OR GGSelect.NextSliceDescriptor[sliceDescGen]#NIL THEN {
Feedback.AppendHerald[ggData.feedback, "Select a single text slice for font printing", oneLiner];
Feedback.Blink[ggData.feedback];
}
ELSE Feedback.PutF[ggData.feedback, oneLiner, "Font Values: %g", [rope[GGSlice.GetFontLiteralDataRope[sliceDesc.slice]]] ];
};
ShowFontValuesLiteral: PUBLIC PROC [clientData: REF ANY, event: LIST OF REF ANY] = {
Outputs string in the form <FontFullName> <transformation> <storedSize> <designSize>
fontRope: Rope.ROPENIL;
ggData: GGData ← NARROW[clientData];
sliceDescGen: SliceDescriptorGenerator ← GGSelect.SelectedSlices[ggData.scene, normal];
FOR sliceDesc: SliceDescriptor ← GGSelect.NextSliceDescriptor[sliceDescGen], GGSelect.NextSliceDescriptor[sliceDescGen] UNTIL sliceDesc=NIL DO
IF sliceDesc.slice.class.type#$Text THEN LOOP;
IF fontRope=NIL THEN fontRope ← GGSlice.GetFontLiteralDataRope[sliceDesc.slice];
IF Rope.Equal[fontRope, GGSlice.GetFontLiteralDataRope[sliceDesc.slice], FALSE] THEN LOOP;
Feedback.AppendHerald[ggData.feedback, "Multiple fonts in selected text slices", oneLiner];
Feedback.Blink[ggData.feedback];
RETURN;
ENDLOOP;
Feedback.PutF[ggData.feedback, oneLiner, "Font Values: %g", [rope[IF fontRope#NIL THEN fontRope ELSE "No text slices selected"]] ];
};
CopyFont: PUBLIC PROC [clientData: REF ANY, event: LIST OF REF ANY] = {
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: GGData ← NARROW[clientData];
lastDesc: SliceDescriptor;
sliceDescGen: SliceDescriptorGenerator ← GGSelect.SelectedSlices[ggData.scene, normal];
FOR sliceD: SliceDescriptor ← GGSelect.NextSliceDescriptor[sliceDescGen], GGSelect.NextSliceDescriptor[sliceDescGen] UNTIL sliceD=NIL DO
lastDesc ← sliceD; -- find the LAST selected slice
ENDLOOP;
BEGIN
IF lastDesc=NIL OR lastDesc.slice.class.type#$Text THEN GOTO BadLastSlice
ELSE {
saveTransform: ImagerTransformation.Transformation;
oldData, copyData: FontData;
alikeRope: Rope.ROPE ← GGSlice.GetFontDataRope[lastDesc.slice];
alikeData: FontData ← GGSlice.GetFontData[lastDesc.slice];
ggData.refresh.startBoundBox^ ← GGBoundBox.BoundBoxOfSelected[ggData.scene, normal]^; -- remember original bound box
sliceDescGen ← GGSelect.SelectedSlices[ggData.scene, normal];
FOR sliceD: SliceDescriptor ← GGSelect.NextSliceDescriptor[sliceDescGen], GGSelect.NextSliceDescriptor[sliceDescGen] UNTIL sliceD=NIL DO
IF sliceD.slice.class.type#$Text THEN LOOP;
oldData ← GGSlice.GetFontData[sliceD.slice];
saveTransform ← oldData.transform; -- shouldn't need to copy transformation
copyData ← GGFont.CopyFontData[data: alikeData, oldCopy: oldData]; -- reuse storage existing inside slice. CLOBBERS oldData^ !!
copyData.transform ← saveTransform;
[] ← GGSlice.SetTextFontAndTransform[sliceD.slice, copyData, ggData.feedback];
ENDLOOP;
GGBoundBox.EnlargeByBox[bBox: ggData.refresh.startBoundBox, by: GGBoundBox.BoundBoxOfSelected[ggData.scene, normal] ];
GGWindow.RestoreScreenAndInvariants[paintAction: $ObjectChangedBoundBoxProvided, ggData: ggData, remake: triggerBag, backgndOK: FALSE, edited: TRUE, okToClearFeedback: TRUE];
Feedback.PutFHerald[ggData.feedback, oneLiner, "Text slices with font %g selected", [rope[alikeRope]] ];
};
EXITS
BadLastSlice => {
Feedback.AppendHerald[ggData.feedback, "Select a final text slice for Copy Font", oneLiner];
Feedback.Blink[ggData.feedback];
};
END;
};
CopyAll: PUBLIC PROC [clientData: REF ANY, event: LIST OF REF ANY] = {
Replicates the font data from the LAST selected text slice to all the other selected text slices
ggData: GGData ← NARROW[clientData];
lastDesc: SliceDescriptor;
sliceDescGen: SliceDescriptorGenerator ← GGSelect.SelectedSlices[ggData.scene, normal];
FOR sliceD: SliceDescriptor ← GGSelect.NextSliceDescriptor[sliceDescGen], GGSelect.NextSliceDescriptor[sliceDescGen] UNTIL sliceD=NIL DO
lastDesc ← sliceD; -- find the LAST selected slice
ENDLOOP;
BEGIN
IF lastDesc=NIL OR lastDesc.slice.class.type#$Text THEN GOTO BadLastSlice
ELSE {
oldTVec: ImagerTransformation.VEC;
oldData, copyData: FontData;
alikeRope: Rope.ROPE ← GGSlice.GetFontDataRope[lastDesc.slice];
alikeData: FontData ← GGSlice.GetFontData[lastDesc.slice];
ggData.refresh.startBoundBox^ ← GGBoundBox.BoundBoxOfSelected[ggData.scene, normal]^; -- remember original bound box
sliceDescGen ← GGSelect.SelectedSlices[ggData.scene, normal];
FOR sliceD: SliceDescriptor ← GGSelect.NextSliceDescriptor[sliceDescGen], GGSelect.NextSliceDescriptor[sliceDescGen] UNTIL sliceD=NIL DO
IF sliceD.slice.class.type#$Text THEN LOOP;
oldData ← GGSlice.GetFontData[sliceD.slice];
oldTVec ← ImagerTransformation.Factor[oldData.transform].t;
copyData ← GGFont.CopyFontData[data: alikeData, oldCopy: oldData]; -- reuse storage existing inside slice. CLOBBERS oldData^ !!
copyData.transform ← ImagerTransformation.TranslateTo[copyData.transform, oldTVec];
[] ← GGSlice.SetTextFontAndTransform[sliceD.slice, copyData, ggData.feedback];
ENDLOOP;
GGBoundBox.EnlargeByBox[bBox: ggData.refresh.startBoundBox, by: GGBoundBox.BoundBoxOfSelected[ggData.scene, normal] ];
GGWindow.RestoreScreenAndInvariants[paintAction: $ObjectChangedBoundBoxProvided, ggData: ggData, remake: triggerBag, backgndOK: FALSE, edited: TRUE, okToClearFeedback: TRUE];
Feedback.PutFHerald[ggData.feedback, oneLiner, "Text slices with font %g selected", [rope[alikeRope]] ];
};
EXITS
BadLastSlice => {
Feedback.AppendHerald[ggData.feedback, "Select a final text slice for Copy Literal", oneLiner];
Feedback.Blink[ggData.feedback];
};
END;
};
MatchAll: PUBLIC PROC [clientData: REF ANY, event: LIST OF REF ANY] = {
Checks for a single selected GG text slice, then selects all the text slices with fontData matching that slice, excluding translation.
ggData: GGData ← NARROW[clientData];
sliceDescGen: SliceDescriptorGenerator ← GGSelect.SelectedSlices[ggData.scene, normal];
sliceDesc: SliceDescriptor ← GGSelect.NextSliceDescriptor[sliceDescGen];
IF sliceDesc=NIL OR sliceDesc.slice.class.type#$Text OR GGSelect.NextSliceDescriptor[sliceDescGen]#NIL THEN {
Feedback.AppendHerald[ggData.feedback, "Select a single text slice for matching", oneLiner];
Feedback.Blink[ggData.feedback];
}
ELSE { -- select all the text slices with matching fontData
fontData: FontData ← GGSlice.GetFontData[sliceDesc.slice];
SelectMatchingData[fontData, ggData];
};
};
MatchSelectedName: PUBLIC PROC [clientData: REF ANY, event: LIST OF REF ANY] = {
selects all the text slices whose fontData.userFSF contain the Tioga selection as a SUBSTRING.
ggData: GGData ← NARROW[clientData];
matchRope: Rope.ROPENARROW[event.rest.first];
IF Rope.Equal[matchRope, ""] THEN {
Feedback.AppendHerald[ggData.feedback, "Select substring for font matching", oneLiner];
Feedback.Blink[ggData.feedback];
}
ELSE SelectMatching[matchRope, ggData, $userFSF];
};
MatchSelectedNameLiteral: PUBLIC PROC [clientData: REF ANY, event: LIST OF REF ANY] = {
selects all the text slices whose fontData.literal contain the Tioga selection as a SUBSTRING.
ggData: GGData ← NARROW[clientData];
matchRope: Rope.ROPENARROW[event.rest.first];
IF Rope.Equal[matchRope, ""] THEN {
Feedback.AppendHerald[ggData.feedback, "Select substring for literal matching", oneLiner];
Feedback.Blink[ggData.feedback];
}
ELSE SelectMatching[matchRope, ggData, $literal];
};
SetDefaultFontValues: PUBLIC PROC [clientData: REF ANY, event: LIST OF REF ANY] = {
SetDefaultFontValues checks that there is a single GG text slice selected, then copies its font information to the default font
ggData: GGData ← NARROW[clientData];
sliceDescGen: SliceDescriptorGenerator ← GGSelect.SelectedSlices[ggData.scene, normal];
sliceDesc: SliceDescriptor ← GGSelect.NextSliceDescriptor[sliceDescGen];
IF sliceDesc=NIL OR sliceDesc.slice.class.type#$Text OR GGSelect.NextSliceDescriptor[sliceDescGen]#NIL THEN {
Feedback.AppendHerald[ggData.feedback, "Select a single text slice for default font setting", oneLiner];
Feedback.Blink[ggData.feedback];
}
ELSE {
ggData.defaults.font ← GGFont.CopyFontData[GGSlice.GetFontData[sliceDesc.slice]];
Feedback.PutF[ggData.feedback, oneLiner, "New Default Font: %g", [rope[GGSlice.GetFontDataRope[sliceDesc.slice]]] ];
};
};
ShowDefaultFontValues: PUBLIC PROC [clientData: REF ANY, event: LIST OF REF ANY] = {
ggData: GGData ← NARROW[clientData];
scratch: IO.STREAMIO.ROS[];
defaultFontData: FontData ← ggData.defaults.font;
GGParseOut.WriteFactoredTransformationVEC[scratch, defaultFontData.transform];
Feedback.PutF[ggData.feedback, oneLiner, "Default Font Values: %g %g %g %g", [rope[defaultFontData.literal]], [rope[IO.RopeFromROS[scratch]]], [real[defaultFontData.storedSize]], [real[defaultFontData.designSize]] ];
};
SelectMatching: PROC [matchRope: Rope.ROPE, ggData: GGData, op: ATOM] = {
sliceGen: SliceGenerator ← GGScene.SlicesInScene[ggData.scene];
GGSelect.DeselectAll[ggData.scene, normal];
FOR slice: Slice ← GGScene.NextSlice[sliceGen], GGScene.NextSlice[sliceGen] UNTIL slice=NIL DO
nextData: FontData ← GGSlice.GetFontData[slice];
IF nextData#NIL AND Rope.Find[s1: IF op=$userFSF THEN nextData.userFSF ELSE nextData.literal, s2: matchRope, pos1: 0, case: FALSE]#-1 THEN GGSelect.SelectSlice[sliceD: slice.class.newParts[slice, NIL, topLevel], scene: ggData.scene, selectClass: normal];
ENDLOOP;
Feedback.PutFHerald[ggData.feedback, oneLiner, "Text slices with fonts matching %g selected", [rope[matchRope]] ];
GGWindow.RestoreScreenAndInvariants[paintAction: $SelectionChanged, ggData: ggData, remake: none, backgndOK: TRUE, edited: FALSE, okToClearFeedback: FALSE];
};
SelectMatchingData: PROC [fontData: FontData, ggData: GGData] = {
maxPixels: REAL ← 10000.0;
sliceGen: SliceGenerator ← GGScene.SlicesInScene[ggData.scene];
GGSelect.DeselectAll[ggData.scene, normal];
FOR slice: Slice ← GGScene.NextSlice[sliceGen], GGScene.NextSlice[sliceGen] UNTIL slice=NIL DO
nextData: FontData ← GGSlice.GetFontData[slice];
IF nextData#NIL AND Rope.Equal[fontData.literal, nextData.literal, FALSE] AND RealFns.AlmostEqual[fontData.storedSize, nextData.storedSize, -9] AND ImagerTransformation.CloseToTranslation[fontData.transform, nextData.transform, maxPixels] THEN GGSelect.SelectSlice[sliceD: slice.class.newParts[slice, NIL, topLevel], scene: ggData.scene, selectClass: normal];
ENDLOOP;
Feedback.PutFHerald[ggData.feedback, oneLiner, "Text slices with font %g selected", [rope[fontData.literal]] ];
GGWindow.RestoreScreenAndInvariants[paintAction: $SelectionChanged, ggData: ggData, remake: none, backgndOK: TRUE, edited: FALSE, okToClearFeedback: FALSE];
};
Interpress Utilities
ReadIP: PROC [ipName: Rope.ROPE, clientData: REF ANY, opName: Rope.ROPE] RETURNS [scene: Scene ← NIL] = {
ShowWarnings: Interpress.LogProc = {
Feedback.Append[ggData.feedback, explanation, oneLiner];
};
ReadMaster: PROC [context: Imager.Context] = {
Imager.ScaleT[context, 2834.646]; -- pointsPerMeter=2834.646
Interpress.DoPage[master: ipmaster, page: 1, context: context, log: ShowWarnings];
};
ggData: GGData ← NARROW[clientData];
ipmaster: Interpress.Master ← NIL;
fullName: Rope.ROPE;
startTime, endTime: BasicTime.GMT;
totalTime: INT;
success: BOOLFALSE;
[fullName, success] ← GGUtility.GetInterpressFileName[ipName, ggData.currentWDir, ggData.feedback];
IF NOT success THEN RETURN;
[ipmaster, success] ← GGUtility.OpenInterpressOrComplain[ggData.feedback, fullName];
IF NOT success THEN RETURN;
Feedback.PutF[ggData.feedback, begin, "%g %g . . . ", [rope[opName]], [rope[fullName]]];
startTime ← BasicTime.Now[];
scene ← GGFromImager.Capture[action: ReadMaster !
GGFromImager.WarningMessage => {
Feedback.Append[ggData.feedback, message, oneLiner];
RESUME;
};
];
endTime ← BasicTime.Now[];
totalTime ← BasicTime.Period[startTime, endTime];
Feedback.PutF[ggData.feedback, end, " Done in time (%r)", [integer[totalTime]]];
};
MergeIPEditable: PUBLIC PROC [clientData: REF ANY, event: LIST OF REF ANY] = {
ggData: GGData ← NARROW[clientData];
ipName: Rope.ROPENARROW[event.rest.first];
scene: Scene ← ReadIP[ipName, clientData, "MergeIPEditable"];
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, backgndOK: FALSE, edited: TRUE, okToClearFeedback: FALSE];
};
MergeIPSlice: PUBLIC PROC [clientData: REF ANY, event: LIST OF REF ANY] = {
slice: Slice;
ggData: GGData ← NARROW[clientData];
shortName, fullName: Rope.ROPE;
startTime, endTime: BasicTime.GMT;
totalTime: INT;
ipMaster: Interpress.Master;
success: BOOL;
shortName ← NARROW[event.rest.first];
[fullName, success] ← GGUtility.GetInterpressFileName[shortName, ggData.currentWDir, ggData.feedback];
IF NOT success THEN RETURN;
[ipMaster, success] ← GGUtility.OpenInterpressOrComplain[ggData.feedback, fullName];
IF NOT success THEN RETURN;
GGSelect.DeselectAll[ggData.scene, normal];
Feedback.PutF[ggData.feedback, begin, "MergeIPSlice %g . . . ", [rope[fullName]]];
startTime ← BasicTime.Now[];
slice ← GGSlice.MakeIPSliceFromMaster[ipMaster, 2834.646, fullName, ggData.feedback, NIL, NIL, FALSE];
IF slice = NIL THEN RETURN;
GGScene.AddSlice[ggData.scene, slice, -1];
endTime ← BasicTime.Now[];
totalTime ← BasicTime.Period[startTime, endTime];
Feedback.PutF[ggData.feedback, end, " Done in time (%r)", [integer[totalTime]]];
GGSelect.SelectEntireSlice[slice, ggData.scene, normal];
ggData.refresh.startBoundBox^ ← slice.boundBox^;
GGWindow.RestoreScreenAndInvariants[paintAction: $ObjectChangedBoundBoxProvided, ggData: ggData, remake: sceneBag, backgndOK: FALSE, edited: TRUE, okToClearFeedback: FALSE];
}; -- end MergeIPSlice
IncludeIPByReference: PUBLIC PROC [clientData: REF ANY, event: LIST OF REF ANY] = {
All selected IP Slices are now store-by-reference.
ggData: GGData ← NARROW[clientData];
sliceDescGen: GGSelect.SliceDescriptorGenerator;
sliceDescGen ← GGSelect.SelectedSlices[ggData.scene, normal];
FOR sliceD: GGSelect.SliceDescriptor ← GGSelect.NextSliceDescriptor[sliceDescGen], GGSelect.NextSliceDescriptor[sliceDescGen] UNTIL sliceD = NIL DO
IF sliceD.slice.class.type = $IP THEN GGSlice.SetIncludeByValue[sliceD.slice, FALSE];
ENDLOOP;
};
IncludeIPByValue: PUBLIC PROC [clientData: REF ANY, event: LIST OF REF ANY] = {
All selected IP Slices are now store-by-value.
ggData: GGData ← NARROW[clientData];
sliceDescGen: GGSelect.SliceDescriptorGenerator;
sliceDescGen ← GGSelect.SelectedSlices[ggData.scene, normal];
FOR sliceD: GGSelect.SliceDescriptor ← GGSelect.NextSliceDescriptor[sliceDescGen], GGSelect.NextSliceDescriptor[sliceDescGen] UNTIL sliceD = NIL DO
IF sliceD.slice.class.type = $IP THEN GGSlice.SetIncludeByValue[sliceD.slice, TRUE];
ENDLOOP;
};
ShowIPIncludeMode: PUBLIC PROC [clientData: REF ANY, event: LIST OF REF ANY] = {
Show whether the selected IP Slices is store-by-reference or store-by-value
ggData: GGData ← NARROW[clientData];
sliceDescGen: GGSelect.SliceDescriptorGenerator;
includeByValue: BOOL;
sliceDescGen ← GGSelect.SelectedSlices[ggData.scene, normal];
FOR sliceD: GGSelect.SliceDescriptor ← GGSelect.NextSliceDescriptor[sliceDescGen], GGSelect.NextSliceDescriptor[sliceDescGen] UNTIL sliceD = NIL DO
IF sliceD.slice.class.type = $IP THEN {
includeByValue ← GGSlice.GetIncludeByValue[sliceD.slice];
IF includeByValue THEN Feedback.Append[ggData.feedback, "include by value", oneLiner]
ELSE Feedback.Append[ggData.feedback, "include by reference", oneLiner];
RETURN;
};
ENDLOOP;
Feedback.Append[ggData.feedback, "ShowIPIncludeMode: No IP Slices selected", oneLiner];
Feedback.Blink[ggData.feedback];
};
ToIPAux: PROC [ggData: GGData, ipName: Rope.ROPE, actionAtom: ATOM, makeInterpress: PROC [dc: Imager.Context]] = {
ipRef: ImagerInterpress.Ref;
fullName: Rope.ROPE;
success: BOOL;
TIMING VARIABLES
startTime: BasicTime.GMT;
endTime: BasicTime.GMT;
totalTime: INT;
msgRope: Rope.ROPE;
[fullName, success] ← GGUtility.GetInterpressFileName[ipName, ggData.currentWDir, ggData.feedback];
IF NOT success THEN RETURN;
ipRef ← ImagerInterpress.Create[fullName];
[fullName] ← FS.FileInfo[fullName]; -- append the version number
msgRope ← IO.PutFR["%g %g . . . ", [rope[Atom.GetPName[actionAtom]]], [rope[fullName]]];
Feedback.Append[ggData.feedback, msgRope, begin];
START TIMING
startTime ← BasicTime.Now[];
ImagerInterpress.DoPage[ipRef, makeInterpress, 1.0];
ImagerInterpress.Close[ipRef];
endTime ← BasicTime.Now[];
totalTime ← BasicTime.Period[startTime, endTime];
msgRope ← IO.PutFR[" Done in time (%r)", [integer[totalTime]]];
Feedback.Append[ggData.feedback, msgRope, end];
GGEvent.SawTextFinish[ggData, NIL];
};
ToIP: PUBLIC PROC [clientData: REF ANY, event: LIST OF REF ANY] = {
DoMakeInterpress: PROC [dc: Imager.Context] = {
tempQuality: GGInterfaceTypes.QualityMode;
tempStyle: GGInterfaceTypes.DisplayStyle;
pixelsPerMeter: REAL = 0.0254/72.0;
tempQuality ← ggData.camera.quality;
ggData.camera.quality ← quality;
tempStyle ← ggData.camera.displayStyle;
ggData.camera.displayStyle ← print;
Imager.ScaleT[dc, pixelsPerMeter];
GGRefresh.InterpressEntireScene[dc, ggData];
ggData.camera.quality ← tempQuality;
ggData.camera.displayStyle ← tempStyle;
};
ggData: GGData ← NARROW[clientData];
ipName: Rope.ROPENARROW[event.rest.first];
ToIPAux[ggData, ipName, NARROW[event.first], DoMakeInterpress];
};
ToIPTestGravity: PUBLIC PROC [clientData: REF ANY, event: LIST OF REF ANY] = {
DoMakeInterpress: PROC [dc: Imager.Context] = {
tempQuality: GGInterfaceTypes.QualityMode;
tempStyle: GGInterfaceTypes.DisplayStyle;
pixelsPerMeter: REAL = 0.0254/72.0;
tempQuality ← ggData.camera.quality;
ggData.camera.quality ← quality;
tempStyle ← ggData.camera.displayStyle;
ggData.camera.displayStyle ← print;
Imager.ScaleT[dc, pixelsPerMeter];
TestGravity2[dc, ggData];
ggData.camera.quality ← tempQuality;
ggData.camera.displayStyle ← tempStyle;
};
ggData: GGData ← NARROW[clientData];
ipName: Rope.ROPENARROW[event.rest.first];
ToIPAux[ggData, ipName, NARROW[event.first], DoMakeInterpress];
};
ToIPScreen: PUBLIC PROC [clientData: REF ANY, event: LIST OF REF ANY] = {
DoMakeInterpress: PROC [dc: Imager.Context] = {
tempQuality: GGInterfaceTypes.QualityMode;
tempStyle: GGInterfaceTypes.DisplayStyle;
pixelsPerMeter: REAL = 0.0254/72.0;
tempQuality ← ggData.camera.quality;
ggData.camera.quality ← quality;
tempStyle ← ggData.camera.displayStyle;
ggData.camera.displayStyle ← screen;
Imager.ScaleT[dc, pixelsPerMeter];
GGRefresh.InterpressEntireScene[dc, ggData];
ggData.camera.quality ← tempQuality;
ggData.camera.displayStyle ← tempStyle;
};
ggData: GGData ← NARROW[clientData];
ipName: Rope.ROPENARROW[event.rest.first];
ToIPAux[ggData, ipName, NARROW[event.first], DoMakeInterpress];
};
ToIPLit: PUBLIC PROC [clientData: REF ANY, event: LIST OF REF ANY] = {
DoMakeInterpress: PROC [dc: Imager.Context] = {
switched: BOOLFALSE;
tempQuality ← ggData.camera.quality;
ggData.camera.quality ← showall;
tempStyle ← ggData.camera.displayStyle;
ggData.camera.displayStyle ← print;
IF AtomButtons.GetButtonState[ggData.refresh.showColors] = off THEN { GGEvent.ToggleShowColors[ggData, NIL];
switched ← TRUE;
};
Imager.ScaleT[dc, pixelsPerMeter];
GGRefresh.PaintEntireScene[dc, ggData, TRUE];
ggData.camera.quality ← tempQuality;
ggData.camera.displayStyle ← tempStyle;
IF switched THEN GGEvent.ToggleShowColors[ggData, NIL];
};
ggData: GGData ← NARROW[clientData];
ipRef: ImagerInterpress.Ref;
fullName: Rope.ROPE;
success: BOOL;
pixelsPerMeter: REAL = 0.0254/72.0;
TIMING VARIABLES
startTime: BasicTime.GMT;
endTime: BasicTime.GMT;
totalTime: INT;
msgRope: Rope.ROPE;
tempQuality: GGInterfaceTypes.QualityMode;
tempStyle: GGInterfaceTypes.DisplayStyle;
ipName: Rope.ROPE ← "litshot.ip";
[fullName, success] ← GGUtility.GetInterpressFileName[ipName, ggData.currentWDir, ggData.feedback];
IF NOT success THEN RETURN;
ipRef ← ImagerInterpress.Create[fullName];
msgRope ← IO.PutFR["Writing to IP file: %g . . . ", [rope[fullName]]];
Feedback.Append[ggData.feedback, msgRope, begin];
START TIMING
startTime ← BasicTime.Now[];
ImagerInterpress.DoPage[ipRef, DoMakeInterpress, 1.0];
ImagerInterpress.Close[ipRef];
endTime ← BasicTime.Now[];
totalTime ← BasicTime.Period[startTime, endTime];
msgRope ← IO.PutFR[" Done in time (%r)", [integer[totalTime]]];
Feedback.Append[ggData.feedback, msgRope, end];
GGEvent.SawTextFinish[ggData, NIL];
};
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;
uniFeature, multiFeature: FeatureData;
currentObjects: AlignBag;
sceneObjects: TriggerBag;
xRandomStream ← Random.Create[ggData.actionArea.cw];
yRandomStream ← Random.Create[ggData.actionArea.ch];
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;
[uniPoint, uniFeature] ← GGGravity.UniMap[testPoint, ggData.hitTest.tolerance, currentObjects, sceneObjects, ggData, TRUE];
[multiPoint, multiFeature] ← GGMultiGravity.Map[testPoint, ggData.hitTest.tolerance, currentObjects, sceneObjects, ggData, TRUE];
IF uniFeature = NIL AND multiFeature = NIL THEN {
PaintSpot[dc, ggData];
totalCount ← totalCount + 1;
LOOP;
};
IF uniFeature = NIL OR multiFeature = NIL OR uniFeature # multiFeature OR uniFeature.type # multiFeature.type OR uniPoint # multiPoint THEN {
ReportResultsAndPaint2[dc, testPoint, uniPoint, uniFeature, multiPoint, multiFeature, ggData];
totalCount ← totalCount + 1;
diffCount ← diffCount + 1;
IF multiFeature # NIL THEN multiHitCount ← multiHitCount + 1;
IF uniFeature # NIL THEN uniHitCount ← uniHitCount + 1;
}
ELSE {
multiHitCount ← multiHitCount + 1;
uniHitCount ← uniHitCount + 1;
totalCount ← totalCount + 1;
ggData.refresh.hitPoint ← multiPoint;
PaintHitLine[dc, ggData];
};
ENDLOOP;
Feedback.PutF[ggData.feedback, oneLiner, "Tested %g total points. %g unihits. %g multihits. %g differences", [integer[totalCount]], [integer[uniHitCount]], [integer[multiHitCount]], [integer[diffCount]]];
}; -- end TestGravity2
PaintSpot: PROC [screen: Imager.Context, ggData: GGData] = {
Imager.SetColor[screen, Imager.black];
GGShapes.DrawSpot[screen, ggData.refresh.spotPoint];
};
PaintOddHitLine: PROC [screen: Imager.Context, ggData: GGData] = {
Imager.SetColor[screen, Imager.black];
Imager.SetStrokeEnd[screen, round];
Imager.MaskVector[screen, [ggData.refresh.spotPoint.x, ggData.refresh.spotPoint.y], [ggData.refresh.hitPoint.x, ggData.refresh.hitPoint.y]];
GGShapes.DrawCP[screen, ggData.refresh.spotPoint];
};
PaintHitLine: PROC [screen: Imager.Context, ggData: GGData] = {
Imager.SetColor[screen, Imager.black];
Imager.SetStrokeEnd[screen, round];
Imager.MaskVector[screen, [ggData.refresh.spotPoint.x, ggData.refresh.spotPoint.y], [ggData.refresh.hitPoint.x, ggData.refresh.hitPoint.y]];
GGShapes.DrawFilledRect[screen, ggData.refresh.spotPoint, 3.0];
};
ReportResultsAndPaint2: PROC [dc: Imager.Context, testPoint: Point, uniPoint: Point, uniFeature: FeatureData, multiPoint: Point, multiFeature: FeatureData, ggData: GGData] = {
IF uniFeature = NIL THEN {
ggData.refresh.hitPoint ← multiPoint;
}
ELSE IF multiFeature = NIL THEN {
ggData.refresh.hitPoint ← uniPoint;
}
ELSE IF uniFeature # multiFeature THEN {
ggData.refresh.hitPoint ← multiPoint;
}
ELSE IF uniFeature.type # multiFeature.type THEN {
ggData.refresh.hitPoint ← multiPoint;
}
ELSE IF uniPoint # multiPoint THEN {
ggData.refresh.hitPoint ← uniPoint;
PaintOddHitLine[dc, ggData];
ggData.refresh.hitPoint ← multiPoint;
}
ELSE ERROR;
PaintOddHitLine[dc, ggData];
};
Hierarchy Menu
PolyType: TYPE = {positive, negative};
PolyData: TYPE = REF PolyDataObj;
PolyDataObj: TYPE = RECORD [
type: PolyType
];
HandleOverlap: PROC [currentClientdata, inputClientdata: REF] RETURNS [resultClientData: REF] = {
resultClientData ← NEW[PolyDataObj ← [positive]];
};
HandleGlue: PROC [clientData1, clientData2: REF ANY] RETURNS [BOOL] = {
RETURN[FALSE];
};
IsPositive: PROC [clientData: REF ANY] RETURNS [BOOL] = {
polyData: PolyData ← NARROW[clientData];
IF polyData = NIL THEN RETURN[FALSE];
RETURN[polyData.type = positive];
};
UnionCombine: PUBLIC PROC [clientData: REF ANY, event: LIST OF REF ANY] = {
ggData: GGData ← NARROW[clientData];
bBoxOfSelected: BoundBox ← GGBoundBox.BoundBoxOfSelected[ggData.scene, normal];
IF bBoxOfSelected=NIL THEN RETURN; -- nothing selected
IF PrincOpsUtils.IsBound[LOOPHOLE[CombinePoly.CreateDatabase]] THEN {
startTime, endTime: BasicTime.GMT;
totalTime: INT;
Feedback.PutF[ggData.feedback, begin, "Combining selected convex polygons . . . "];
startTime ← BasicTime.Now[];
UnionCombineAux[ggData];
endTime ← BasicTime.Now[];
totalTime ← BasicTime.Period[startTime, endTime];
Feedback.PutF[ggData.feedback, end, " Done in time (%r)", [integer[totalTime]]];
ggData.refresh.startBoundBox^ ← bBoxOfSelected^;
GGWindow.RestoreScreenAndInvariants[paintAction: $ObjectChangedBoundBoxProvided, ggData: ggData, remake: triggerBag, backgndOK: FALSE, edited: TRUE, okToClearFeedback: FALSE];
}
ELSE {
Feedback.Append[ggData.feedback, "Please run BasicCombiner.bcd and retry this operation", oneLiner];
Feedback.Blink[ggData.feedback];
};
Feedback.AppendHerald[ggData.feedback, "UnionCombine not implemented", oneLiner];
Feedback.Blink[ggData.feedback];
}; -- end UnionCombine
UnionCombineAux: PROC [clientData: REF ANY] = {
ggData: GGData ← NARROW[clientData];
Use Arnon's Combiner to find the union of all selected trajectories. The result of such a union may be a set of outlines with holes. For now, we assume that the result will be a set of outlines without holes (since holes are not implemented in Gargoyle yet.
dbid: CombinePoly.DBID;
polyID: CombinePoly.PolyID;
seqGen: SequenceGenerator;
regionGen: CombinePoly.RegionGenerator;
jointGen: JointGenerator;
pointGen: CombinePoly.PointGenerator;
point, firstPoint, secondPoint: Point;
polyData: REF ANY;
firstPointAndDone, previousPointAndDone: CombinePoly.PointAndDone;
success: BOOL;
traj: Traj;
seg: Segment;
outline: Outline;
jointNum: INT;
dbid ← CombinePoly.CreateDatabase[];
seqGen ← GGSelect.SelectedSequences[ggData.scene, normal];
FOR seq: Sequence ← GGSequence.NextSequence[seqGen], GGSequence.NextSequence[seqGen] UNTIL seq = NIL DO
IF NOT GGSequence.IsComplete[seq] THEN LOOP;
IF NOT seq.traj.role = fence THEN LOOP;
polyData ← NEW[PolyDataObj ← [positive]];
jointGen ← GGSequence.JointsInSequence[seq];
Create the Polygon
jointNum ← GGSequence.NextJoint[jointGen];
firstPoint ← GGTraj.FetchJointPos[seq.traj, jointNum];
jointNum ← GGSequence.NextJoint[jointGen];
secondPoint ← GGTraj.FetchJointPos[seq.traj, jointNum];
polyID ← CombinePoly.CreatePolygon[firstPoint, secondPoint, polyData];
Add the other points.
FOR jointNum: INT ← GGSequence.NextJoint[jointGen], GGSequence.NextJoint[jointGen] UNTIL jointNum = -1 DO
point ← GGTraj.FetchJointPos[seq.traj, jointNum];
CombinePoly.AddPointToPolygon[point, polyID];
ENDLOOP;
dbid ← CombinePoly.PolygonIntoDatabase[polyID, dbid, HandleOverlap, HandleGlue];
ENDLOOP;
[] ← DeleteAllSelected[ggData];
regionGen ← CombinePoly.MaximalRegions[dbid, IsPositive];
FOR region: CombinePoly.OutlineHolesDataAndDone ← CombinePoly.NextRegion[regionGen], CombinePoly.NextRegion[regionGen] UNTIL region.done DO
pointGen ← region.outline;
previousPointAndDone ← firstPointAndDone ← CombinePoly.NextPoint[pointGen];
IF previousPointAndDone.done THEN LOOP;
traj ← GGTraj.CreateTraj[previousPointAndDone.point];
FOR pointAndDone: CombinePoly.PointAndDone ← CombinePoly.NextPoint[pointGen], CombinePoly.NextPoint[pointGen] UNTIL pointAndDone.done DO
seg ← GGSegment.MakeLine[previousPointAndDone.point, pointAndDone.point, NIL];
success ← GGTraj.AddSegment[traj, hi, seg, lo];
IF NOT success THEN ERROR;
previousPointAndDone ← pointAndDone;
REPEAT
FINISHED => {
seg ← GGSegment.MakeLine[previousPointAndDone.point, firstPointAndDone.point, NIL];
GGTraj.CloseWithSegment[traj, seg, lo];
};
ENDLOOP;
outline ← GGOutline.CreateOutline[traj, round, GGScene.fillColor];
GGScene.AddOutline[ggData.scene, outline, -1];
ENDLOOP;
};
AddHoles: PUBLIC PROC [clientData: REF ANY, event: LIST OF REF ANY] = {
ggData: GGData ← NARROW[clientData];
seqGen: SequenceGenerator ← GGSelect.SelectedSequences[ggData.scene, normal];
fenceSeq, firstHoleSeq: Sequence;
fence, hole: Traj;
outline: Outline;
bBox: BoundBox ← GGBoundBox.BoundBoxOfSelected[ggData.scene, normal];
fenceSeq ← GGSequence.NextSequence[seqGen];
IF fenceSeq = NIL OR fenceSeq.traj.role#fence THEN {
Feedback.AppendHerald[ggData.feedback, "Select a closed trajectory to add holes to.", oneLiner];
GOTO Abort;
};
fence ← fenceSeq.traj;
outline ← GGOutline.OutlineOfTraj[fence];
firstHoleSeq ← GGSequence.NextSequence[seqGen];
IF firstHoleSeq = NIL THEN {
Feedback.AppendHerald[ggData.feedback, "Extend your selection to some closed trajectories to make holes.", oneLiner];
GOTO Abort;
};
FOR holeSeq: Sequence ← firstHoleSeq, GGSequence.NextSequence[seqGen] UNTIL holeSeq = NIL DO
hole ← holeSeq.traj;
IF NOT GGTraj.OnlyChild[hole] THEN LOOP;
IF holeSeq.traj.role#fence THEN {
Feedback.AppendHerald[ggData.feedback, "Select only CLOSED trajectories as holes.", oneLiner];
LOOP;
};
outline ← GGInterface.AddHole[outline, hole, ggData.scene];
ENDLOOP;
GGBoundBox.EnlargeByBox[bBox: bBox, by: outline.boundBox]; -- boundBox of old shape and new shape
ggData.refresh.startBoundBox^ ← bBox^;
GGWindow.RestoreScreenAndInvariants[paintAction: $ObjectChangedBoundBoxProvided, ggData: ggData, remake: triggerBag, backgndOK: FALSE, edited: TRUE, okToClearFeedback: FALSE];
EXITS
Abort => Feedback.Blink[NARROW[clientData, GGData].feedback];
}; -- end AddHoles
OldDeleteAllSelected: PROC [ggData: GGData] RETURNS [bBox: BoundBox] = {
sliceDescGen: SliceDescriptorGenerator;
outSeqGen: GGSelect.OutlineSequenceGenerator;
oldOutline: Outline;
fenceSeq: Sequence;
thisBox: BoundBox;
bBox ← GGBoundBox.BoundBoxOfSelected[ggData.scene, normal];
Implemented enough for non-gargoyle slices only.
sliceDescGen ← GGSelect.SelectedSlices[ggData.scene, normal];
FOR sliceD: SliceDescriptor ← GGSelect.NextSliceDescriptor[sliceDescGen], GGSelect.NextSliceDescriptor[sliceDescGen] UNTIL sliceD = NIL DO
GGSelect.DeselectEntityAllClasses[sliceD.slice, ggData.scene];
GGSlice.DeleteSlice[ggData.scene, sliceD.slice];
ENDLOOP;
Now delete selected sequences.
outSeqGen ← GGSelect.SelectedOutlineSequences[ggData.scene, normal];
WHILE outSeqGen.list # NIL DO
FOR outSeq: GGSelect.OutlineSequence ← GGSelect.NextOutlineSequences[outSeqGen], GGSelect.NextOutlineSequences[outSeqGen] UNTIL outSeq = NIL DO
fenceSeq ← outSeq.fenceSeq;
FOR holeSeq: Sequence ← GGSequence.NextSequence[outSeq.holeSeqs], GGSequence.NextSequence[outSeq.holeSeqs] UNTIL holeSeq = NIL DO
[oldOutline,----] ← GGInterface.DeleteSequence[holeSeq, ggData.scene];
thisBox ← GGTraj.GetBoundBox[holeSeq.traj];
GGBoundBox.EnlargeByBox[bBox: bBox, by: thisBox];
GOTO SelectionsHaveChanged;
ENDLOOP;
IF fenceSeq # NIL THEN {
[oldOutline,----] ← GGInterface.DeleteSequence[fenceSeq, ggData.scene];
IF fenceSeq.traj.role = open THEN {
GGSequence.UpdateBoundBox[fenceSeq];
GGBoundBox.EnlargeByBox[bBox: bBox, by: fenceSeq.boundBox];
}
ELSE {
thisBox ← GGTraj.GetBoundBox[fenceSeq.traj];
GGBoundBox.EnlargeByBox[bBox: bBox, by: thisBox];
};
GOTO SelectionsHaveChanged;
};
REPEAT
SelectionsHaveChanged => {
outSeqGen ← GGSelect.SelectedOutlineSequences[ggData.scene, normal];
};
ENDLOOP;
ENDLOOP;
};
DeleteAllSelected: PROC [ggData: GGData] RETURNS [bBox: BoundBox] = {
DeleteRun: GGSelect.RunProc = {
traj ← NIL;
};
sliceDescGen: SliceDescriptorGenerator;
thisBox: BoundBox;
bBox ← GGBoundBox.BoundBoxOfSelected[ggData.scene, normal];
Implemented enough for non-gargoyle slices only.
sliceDescGen ← GGSelect.SelectedSlices[ggData.scene, normal];
FOR sliceD: SliceDescriptor ← GGSelect.NextSliceDescriptor[sliceDescGen], GGSelect.NextSliceDescriptor[sliceDescGen] UNTIL sliceD = NIL DO
GGSelect.DeselectEntityAllClasses[sliceD.slice, ggData.scene];
GGSlice.DeleteSlice[ggData.scene, sliceD.slice];
ENDLOOP;
GGCaret.NoAttractor[ggData.caret];
Overly conservative. Just in case the caret was attracted to something that was just deleted.
thisBox ← GGSelect.ForEachOutlineRun[ggData.scene, ggData.caret, normal, DeleteRun, FALSE];
GGBoundBox.EnlargeByBox[bBox: bBox, by: thisBox];
};
Delete: PUBLIC PROC [clientData: REF ANY, event: LIST OF REF ANY] = {
ggData: GGData ← NARROW[clientData];
bBoxOfSelected: BoundBox;
CodeTimer.StartInt[$Delete, $Gargoyle];
bBoxOfSelected ← GGBoundBox.BoundBoxOfSelected[ggData.scene, normal];
IF bBoxOfSelected=NIL THEN RETURN; -- nothing selected
GGScene.PushScene[ggData.scene]; -- save for Undelete
ggData.refresh.startBoundBox^ ← GGScene.DeleteAllSelected[ggData.scene]^;
GGCaret.NoAttractor[ggData.caret];
GGCaret.SitOn[caret: ggData.caret, chair: NIL];
GGWindow.RestoreScreenAndInvariants[paintAction: $ObjectChangedBoundBoxProvided, ggData: ggData, remake: triggerBag, backgndOK: FALSE, edited: TRUE, okToClearFeedback: TRUE];
CodeTimer.StopInt[$Delete, $Gargoyle];
}; -- end Delete
Undelete: PUBLIC PROC [clientData: REF ANY, event: LIST OF REF ANY] = {
ggData: GGData ← NARROW[clientData];
IF GGScene.EmptySceneStack[ggData.scene] OR NOT MessageWindow.Confirm["UNDELETE WILL LOSE MOST EDITS performed since last Delete"] THEN RETURN;
GGSelect.DeselectAllAllClasses[ggData.scene];
GGScene.PopScene[ggData.scene];
GGWindow.RestoreScreenAndInvariants[paintAction: $PaintEntireScene, ggData: ggData, remake: triggerBag, backgndOK: FALSE, edited: TRUE, okToClearFeedback: TRUE];
};
UndeleteAutoConfirm: PUBLIC PROC [clientData: REF ANY, event: LIST OF REF ANY] = {
ggData: GGData ← NARROW[clientData];
IF GGScene.EmptySceneStack[ggData.scene] THEN RETURN;
GGSelect.DeselectAllAllClasses[ggData.scene];
GGScene.PopScene[ggData.scene];
GGWindow.RestoreScreenAndInvariants[paintAction: $PaintEntireScene, ggData: ggData, remake: triggerBag, backgndOK: FALSE, edited: TRUE, okToClearFeedback: TRUE];
};
SelectAll: PUBLIC PROC [clientData: REF ANY, event: LIST OF REF ANY] = {
ggData: GGData ← NARROW[clientData];
CodeTimer.StartInt[$SelectAll, $Gargoyle];
GGSelect.DeselectAll[ggData.scene, normal];
GGSelect.SelectAll[ggData.scene, normal];
GGEvent.SawTextFinish[ggData, NIL];
GGWindow.RestoreScreenAndInvariants[paintAction: $SelectionChanged, ggData: ggData, remake: none, backgndOK: TRUE, edited: FALSE, okToClearFeedback: TRUE];
CodeTimer.StopInt[$SelectAll, $Gargoyle];
};
Curve Menu
gScale: REAL ← 0.33;
gAngle: REAL ← 33;
MakeCurveProc: TYPE = PROC [lo, hi: Point, props: LIST OF REF ANY] RETURNS [seg: Segment];
GetPt: PROC[p0, dir: Point] RETURNS [pt: Point] = {
pt ← Vectors2d.Add[Vectors2d.Scale[Vectors2d.VectorPlusAngle[dir,gAngle], gScale*Vectors2d.Magnitude[dir] ], p0];
};
SetStraight: PUBLIC PROC [clientData: REF ANY, event: LIST OF REF ANY] = {
MakeStraightAux: MakeCurveProc = {
seg ← GGSegment.MakeLine[lo, hi, props];
};
ggData: GGData ← NARROW[clientData];
SetCurveAux[ggData, MakeStraightAux, $Line];
}; -- end SetStraight
SetArc: PUBLIC PROC [clientData: REF ANY, event: LIST OF REF ANY] = {
MakeArcAux: MakeCurveProc = {
p1: Point;
p1 ← Vectors2d.Add[Vectors2d.Scale[Vectors2d.Sub[hi,lo], 0.5], lo];
seg ← GGSegment.MakeArc[lo, p1, hi, props];
};
ggData: GGData ← NARROW[clientData];
SetCurveAux[ggData, MakeArcAux, $Arc];
}; -- end SetArc
SetSnowflake: PUBLIC PROC [clientData: REF ANY, event: LIST OF REF ANY] = {
OPEN Vectors2d;
MakeSnowflakeAux: PROC [seg: Segment, traj: Traj] RETURNS [patternTraj: Traj] = {
length: REAL;
p1, p2, p3: Point;
sideVec, dir, x: Vector;
seg1, seg2, seg3, seg4: Segment;
lo: Point ← seg.lo;
hi: Point ← seg.hi;
success: BOOL;
length ← Vectors2d.Distance[lo,hi];
dir ← Sub[hi,lo];
x ← Scale[dir, 1.0/3.0];
p1 ← Add[lo, x];
p3 ← Add[lo, Scale[dir, 2.0/3.0]];
sideVec ← Scale[VectorPlusAngle[dir, 90], length*RealFns.SqRt[3]/6.0];
p2 ← Add[lo, Vectors2d.Add[Scale[dir, 0.5], sideVec]];
patternTraj ← traj;
seg1 ← GGSegment.MakeLine[p0: lo, p1: p1, props: seg.props];
seg2 ← GGSegment.MakeLine[p0: p1, p1: p2, props: seg.props];
seg3 ← GGSegment.MakeLine[p0: p2, p1: p3, props: seg.props];
seg4 ← GGSegment.MakeLine[p0: p3, p1: hi, props: seg.props];
success ← GGTraj.AddSegment[patternTraj, hi, seg1, lo];
success ← GGTraj.AddSegment[patternTraj, hi, seg2, lo];
success ← GGTraj.AddSegment[patternTraj, hi, seg3, lo];
success ← GGTraj.AddSegment[patternTraj, hi, seg4, lo];
};
ggData: GGData ← NARROW[clientData];
SetPatternAux[ggData, MakeSnowflakeAux];
};
SetConic: PUBLIC PROC [clientData: REF ANY, event: LIST OF REF ANY] = {
MakeConicAux: MakeCurveProc = {
p1: Point;
p1 ← Vectors2d.Add[Vectors2d.Scale[Vectors2d.Sub[hi,lo], 0.5], lo];
seg ← GGSegment.MakeConic[lo, p1, hi, 0.7, props];
};
ggData: GGData ← NARROW[clientData];
SetCurveAux[ggData, MakeConicAux, $Conic];
}; -- end SetConic
SetBezier: PUBLIC PROC [clientData: REF ANY, event: LIST OF REF ANY] = {
MakeBezierAux: MakeCurveProc = {
length: REAL;
dir: Point;
p1, p2: Point;
length ← Vectors2d.Distance[lo, hi];
dir ← Vectors2d.Sub[hi,lo];
p1 ← GetPt[lo, dir];
p2 ← GetPt[hi, [-dir.x, -dir.y] ];
seg ← GGSegment.MakeBezier[lo, p1, p2, hi, props];
};
ggData: GGData ← NARROW[clientData];
SetCurveAux[ggData, MakeBezierAux, $Bezier];
}; -- end SetBezier
SegToSegMatchCp: PROC [seg: Segment, traj: Traj, makeCurve: MakeCurveProc, type: ATOM] = {
Replace segs of the old type with segs of the new type, maintaining control point data if possible
tSeg: Segment;
success: BOOL;
IF seg.class.type = type THEN -- if seg is already the right type, then we don't change it
success ← GGTraj.AddSegment[traj, hi, GGSegment.CopySegment[seg], lo]
ELSE IF type = $Arc AND seg.class.controlPointCount[seg] = 1 THEN { -- if old and new segment types have single control points, keep the control points the same
tSeg ← GGSegment.MakeArc[seg.lo, seg.class.controlPointGet[seg, 0], seg.hi, seg.props];
GGSegment.CopyLooks[seg, tSeg]; -- KAP. March 6, 1987 7:29:37 pm PST
success ← GGTraj.AddSegment[traj, hi, tSeg , lo]
}
ELSE IF type = $Conic AND seg.class.controlPointCount[seg] = 1 THEN { -- if old and new segment types have single control points, keep the control points the same
tSeg ← GGSegment.MakeConic[seg.lo, seg.class.controlPointGet[seg, 0], seg.hi, .7, seg.props];
GGSegment.CopyLooks[seg, tSeg]; -- KAP. March 6, 1987 7:29:37 pm PST
success ← GGTraj.AddSegment[traj, hi, tSeg, lo]
}
ELSE { -- make segment of new type with old endpoint info
tSeg ← makeCurve[seg.lo, seg.hi, seg.props];
GGSegment.CopyLooks[seg, tSeg]; -- KAP. March 6, 1987 7:29:37 pm PST
success ← GGTraj.AddSegment[traj, hi, tSeg, lo];
};
IF NOT success THEN ERROR;
};
SegAndCpsToSegs: PROC [seg: Segment, traj: Traj, makeCurve: MakeCurveProc, type: ATOM] = {
Replace old segments with segments of the new type, but if the old segment had control points, use these also as joints of new segments.
success: BOOL;
IF seg.class.type = type THEN -- if seg is already the right type, then we don't change it
success ← GGTraj.AddSegment[traj, hi, GGSegment.CopySegment[seg], lo]
ELSE { -- seg isn't the right type, so we convert all of its point/controlpoint spans
tSeg: Segment;
last, next: Point ← seg.lo;
FOR i:INT IN [0..seg.class.controlPointCount[seg]) DO
next ← seg.class.controlPointGet[seg, i];
tSeg ← makeCurve[last, next, seg.props];
GGSegment.CopyLooks[seg, tSeg]; -- KAP. March 6, 1987 7:29:37 pm PST
success ← GGTraj.AddSegment[traj, hi, tSeg, lo];
IF NOT success THEN ERROR;
last ← next;
ENDLOOP;
tSeg ← makeCurve[last, seg.hi, seg.props];
GGSegment.CopyLooks[seg, tSeg]; -- KAP. March 6, 1987 7:29:37 pm PST
success ← GGTraj.AddSegment[traj, hi, tSeg, lo];
IF NOT success THEN ERROR;
};
};
SetCurveAux: PROC [ggData: GGData, makeCurve: MakeCurveProc, type: ATOM] = {
Use GGSelect.ForEachOutlineRun to replace all selected runs with runs of a new type.
Strategy: Replace each selected run with a new trajectory which has a segment (of the proper type) for each joints/control point span. Note that we we copy verbatim all segs which were already of the specified type. The new segments inherit color and stroke properties from the old ones.
RunOfSegs: GGSelect.RunProc = {
[run: GGModelTypes.Sequence] RETURNS [traj: GGModelTypes.Traj]
segGen: GGSequence.SegmentGenerator ← GGSequence.SegmentsInSequence[run];
initialSeg: Segment ← GGSequence.NextSegment[segGen];
IF initialSeg = NIL THEN RETURN[NIL]; -- the run is a single vertex
traj ← GGTraj.CreateTraj[initialSeg.lo];
FOR seg: Segment ← initialSeg, GGSequence.NextSegment[segGen] UNTIL seg = NIL DO
SELECT seg.class.type FROM -- choose from old segment type
$Line, $Arc, $Conic, $Bezier => SegToSegMatchCp[seg, traj, makeCurve, type]; -- modify traj
$CubicSpline => {
IF type=$Line OR (run.traj.segCount=1 AND run.traj.role#open) THEN SegAndCpsToSegs[seg, traj, makeCurve, type] -- Splines are changed to multiple lines...(or multiple segs if spline was cyclic)
ELSE SegToSegMatchCp[seg, traj, makeCurve, type]; -- ...but single everything else
};
ENDCASE => ERROR;
ENDLOOP;
traj.strokeJoint ← run.traj.strokeJoint;
};
bBox: BoundBox;
bBox ← GGSelect.ForEachOutlineRun[ggData.scene, normal, RunOfSegs, TRUE, TRUE];
GGCaret.NoAttractor[ggData.caret];
GGCaret.SitOn[ggData.caret, NIL];
ggData.refresh.startBoundBox^ ← bBox^;
GGWindow.RestoreScreenAndInvariants[paintAction: $ObjectChangedBoundBoxProvided, ggData: ggData, remake: triggerBag, backgndOK: FALSE, edited: TRUE, okToClearFeedback: TRUE];
};
PatternProc: TYPE = PROC [seg: Segment, traj: Traj] RETURNS [patternTraj: Traj];
SetPatternAux: PROC [ggData: GGData, pattern: PatternProc] = {
Use GGSelect.ForEachOutlineRun to replace all selected runs with runs of a new type.
Strategy: Replace each selected run with a new trajectory which has a segment (of the proper type) for each joints/control point span.
RunOfSegs: GGSelect.RunProc = {
[run: GGModelTypes.Sequence] RETURNS [traj: GGModelTypes.Traj]
segGen: GGSequence.SegmentGenerator ← GGSequence.SegmentsInSequence[run];
initialSeg: Segment ← GGSequence.NextSegment[segGen];
IF initialSeg = NIL THEN RETURN[NIL]; -- the run is a single vertex
traj ← GGTraj.CreateTraj[initialSeg.lo];
FOR seg: Segment ← initialSeg, GGSequence.NextSegment[segGen] UNTIL seg = NIL DO
SELECT seg.class.type FROM -- choose from old segment type
$Line => traj ← pattern[seg, traj];
ENDCASE => NULL;
ENDLOOP;
traj.strokeJoint ← run.traj.strokeJoint;
};
bBox: BoundBox;
bBox ← GGSelect.ForEachOutlineRun[ggData.scene, normal, RunOfSegs, TRUE, TRUE];
ggData.refresh.startBoundBox^ ← bBox^;
GGCaret.NoAttractor[ggData.caret];
GGCaret.SitOn[ggData.caret, NIL];
GGWindow.RestoreScreenAndInvariants[paintAction: $ObjectChangedBoundBoxProvided, ggData: ggData, remake: triggerBag, backgndOK: FALSE, edited: TRUE, okToClearFeedback: TRUE];
};
SetNaturalSpline: PUBLIC PROC [clientData: REF ANY, event: LIST OF REF ANY] = {
ggData: GGData ← NARROW[clientData];
SetSpline[splineType: naturalAL, ggData: ggData];
};
SetBSpline: PUBLIC PROC [clientData: REF ANY, event: LIST OF REF ANY] = {
ggData: GGData ← NARROW[clientData];
SetSpline[splineType: bspline, ggData: ggData];
};
SetSpline: PROC [splineType: CubicSplines.SplineType, ggData: GGData] = {
RunToSpline: GGSelect.RunProc = {
[run: GGModelTypes.Sequence] RETURNS [traj: GGModelTypes.Traj]
X: NAT = CubicSplines.X;
Y: NAT = CubicSplines.Y;
tSeg: Segment;
controlPoint: Point;
cyclic: BOOL ← run.traj.role#open AND GGSequence.IsComplete[run];
cps: CubicSplines.KnotSequence ← NEW[CubicSplines.KnotSequenceRec[IF cyclic THEN run.jointCount+run.controlPointCount+1 ELSE run.jointCount+run.controlPointCount]];
segGen: GGSequence.SegmentGenerator ← GGSequence.OrderedSegmentsInSequence[run];
initialSeg: Segment ← GGSequence.NextSegment[segGen];
success: BOOL;
cpCount: NAT ← 0;
IF initialSeg = NIL THEN RETURN[NIL];
IF cyclic AND splineType = naturalAL THEN splineType ← cyclicAL;
traj ← GGTraj.CreateTraj[initialSeg.lo];
cps[cpCount] ← [initialSeg.lo.x, initialSeg.lo.y];
cpCount ← cpCount + 1;
FOR seg: Segment ← initialSeg, GGSequence.NextSegment[segGen] UNTIL seg = NIL DO
FOR i:INT IN [0..seg.class.controlPointCount[seg]) DO
controlPoint ← seg.class.controlPointGet[seg, i];
cps[cpCount] ← [controlPoint.x, controlPoint.y];
cpCount ← cpCount + 1;
ENDLOOP;
cps[cpCount] ← [seg.hi.x, seg.hi.y];
cpCount ← cpCount + 1;
ENDLOOP;
IF cyclic THEN cps[cps.length-1] ← cps[0]; -- cumulative error sometimes causes small differences. DJK.
tSeg ← GGSegment.MakeCubicSpline[cps, splineType, NIL];
GGSegment.CopyLooks[initialSeg, tSeg];
success ← GGTraj.AddSegment[traj, hi, tSeg, lo];
IF NOT success THEN ERROR;
traj.strokeJoint ← run.traj.strokeJoint;
};
ggData.refresh.startBoundBox^ ← GGSelect.ForEachOutlineRun[ggData.scene, normal, RunToSpline, TRUE, TRUE]^;
GGCaret.NoAttractor[ggData.caret];
GGCaret.SitOn[ggData.caret, NIL];
GGWindow.RestoreScreenAndInvariants[paintAction: $ObjectChangedBoundBoxProvided, ggData: ggData, remake: triggerBag, backgndOK: FALSE, edited: TRUE, okToClearFeedback: TRUE]
};
SelectMatchingCurve: PUBLIC PROC [clientData: REF ANY, event: LIST OF REF ANY] = {
ggData: GGData ← NARROW[clientData];
name: Rope.ROPENARROW[event.rest.first];
trajGen: TrajGenerator ← GGScene.TrajsInScene[ggData.scene];
GGSelect.DeselectAll[ggData.scene, normal];
FOR traj: Traj ← GGScene.NextTraj[trajGen], GGScene.NextTraj[trajGen] UNTIL traj = NIL DO
segGen: SegmentGenerator ← GGSequence.SegmentsInTraj[traj];
FOR segAndIndex: SegAndIndex ← GGSequence.NextSegmentAndIndex[segGen], GGSequence.NextSegmentAndIndex[segGen] UNTIL segAndIndex.seg = NIL DO
IF Rope.Equal[segAndIndex.seg.class.describe[segAndIndex.seg, TRUE, TRUE, TRUE, NIL], name, FALSE] THEN {
seq: Sequence ← GGSequence.CreateFromSegment[traj, segAndIndex.index];
GGSelect.SelectSequence[seq, ggData.scene, normal];
};
ENDLOOP;
ENDLOOP;
Feedback.PutFHerald[ggData.feedback, oneLiner, "Curves of type %g selected", [rope[name]] ];
GGWindow.RestoreScreenAndInvariants[paintAction: $SelectionChanged, ggData: ggData, remake: none, backgndOK: TRUE, edited: FALSE, okToClearFeedback: FALSE];
};
Edit Curve Menu
CloseOLDD: PUBLIC PROC [clientData: REF ANY, event: LIST OF REF ANY] = {
ggData: GGData ← NARROW[clientData];
chair: REF ANY;
traj: Traj;
outline: Outline;
firstPoint, lastPoint: Point;
seg: Segment;
success: BOOL;
chair ← GGCaret.GetChair[ggData.caret];
BEGIN
IF chair = NIL THEN GOTO NoCaretTraj;
WITH chair SELECT FROM
outlineD: OutlineDescriptor => {
[success, ----, traj] ← GGOutline.UnpackSimpleDescriptorOld[outlineD];
IF NOT success THEN ERROR;
};
sliceD: SliceDescriptor => {
IF sliceD.slice.class.type # $Outline THEN GOTO NoCaretTraj;
[success, ----, traj] ← GGOutline.UnpackSimpleDescriptor[sliceD];
IF NOT success THEN GOTO NoCaretTraj;
};
ENDCASE => ERROR;
IF traj.role = fence OR traj.role = hole THEN GOTO AlreadyClosed;
Feedback.AppendHerald[ggData.feedback, "The caret trajectory will be closed.", oneLiner];
outline ← GGOutline.OutlineOfTraj[traj];
firstPoint ← GGTraj.FetchJointPos[traj, 0];
lastPoint ← GGTraj.LastJointPos[traj];
IF firstPoint # lastPoint THEN {
lastSeg: Segment ← GGTraj.FetchSegment[traj, GGTraj.HiSegment[traj]];
seg ← GGSegment.MakeLine[lastPoint, firstPoint, NIL];
GGSegment.CopyLooks[lastSeg, seg];
GGTraj.CloseWithSegment[traj, seg, lo];
[] ← GGSelect.ReselectTraj[traj, hi, ggData.scene, TRUE];
}
ELSE {
GGOutline.SaveSelectionsInOutline[outline, ggData.scene];
GGSelect.DeselectEntityAllClasses[outline, ggData.scene];
GGTraj.CloseByDistorting[traj, hi];
GGOutline.RemakeSelectionsFromOutline[outline, ggData.scene];
};
GGSelect.SelectTraj[traj, ggData.scene, normal];
GGCaret.SitOn[ggData.caret, NIL];
GGCaret.NoAttractor[caret: ggData.caret];
outline.class.setFillColor[outline, ggData.defaults.fillColor];
ggData.refresh.startBoundBox^ ← traj.boundBox^;
GGWindow.RestoreScreenAndInvariants[paintAction: $ObjectChangedBoundBoxProvided, ggData: ggData, remake: triggerBag, backgndOK: FALSE, edited: TRUE, okToClearFeedback: 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.AppendHerald[ggData.feedback, "There is no caret trajectory to close.", oneLiner];
Feedback.Blink[ggData.feedback];
};
AlreadyClosed => {
Feedback.AppendHerald[ggData.feedback, "That trajectory is already closed.", oneLiner];
Feedback.Blink[ggData.feedback];
};
END;
}; -- end Close
Close: PUBLIC PROC [clientData: REF ANY, event: LIST OF REF ANY] = {
ggData: GGData ← NARROW[clientData];
outlineSlice: Slice;
traj: Traj;
seg: Segment;
firstPoint, lastPoint: Point;
success: BOOLFALSE;
chair: SliceDescriptor ← GGCaret.GetChair[ggData.caret];
BEGIN
IF chair = NIL OR chair.slice.class.type#$Outline THEN GOTO NoCaretTraj;
[success, ----, traj] ← GGOutline.UnpackSimpleDescriptor[chair];
IF NOT success THEN GOTO NoCaretTraj;
IF traj.role = fence OR traj.role = hole THEN GOTO AlreadyClosed;
Feedback.AppendHerald[ggData.feedback, "The caret trajectory will be closed.", oneLiner];
outlineSlice ← GGOutline.OutlineOfTraj[traj];
firstPoint ← GGTraj.FetchJointPos[traj, 0];
lastPoint ← GGTraj.LastJointPos[traj];
IF firstPoint # lastPoint THEN {
lastSeg: Segment ← GGTraj.FetchSegment[traj, GGTraj.HiSegment[traj]];
seg ← GGSegment.MakeLine[lastPoint, firstPoint, NIL];
GGSegment.CopyLooks[lastSeg, seg];
GGTraj.CloseWithSegment[traj, seg, lo];
[] ← GGSelect.ReselectTraj[traj, hi, ggData.scene, TRUE];
}
ELSE {
GGOutline.SaveSelectionsInOutline[outlineSlice, ggData.scene];
GGSelect.DeselectEntityAllClasses[outlineSlice, ggData.scene];
GGTraj.CloseByDistorting[traj, hi];
GGOutline.RemakeSelectionsFromOutline[outlineSlice, ggData.scene];
};
GGSelect.SelectTraj[traj, ggData.scene, normal];
GGCaret.SitOn[ggData.caret, NIL]; -- FIX THIS TO SIT CARET ON NEWLY CLOSED TRAJ
GGCaret.NoAttractor[caret: ggData.caret];
outlineSlice.class.setFillColor[outlineSlice, ggData.defaults.fillColor];
ggData.refresh.startBoundBox^ ← traj.boundBox^;
GGWindow.RestoreScreenAndInvariants[paintAction: $ObjectChangedBoundBoxProvided, ggData: ggData, remake: triggerBag, backgndOK: FALSE, edited: TRUE, okToClearFeedback: 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.AppendHerald[ggData.feedback, "There is no caret trajectory to close.", oneLiner];
Feedback.Blink[ggData.feedback];
};
AlreadyClosed => {
Feedback.AppendHerald[ggData.feedback, "That trajectory is already closed.", oneLiner];
Feedback.Blink[ggData.feedback];
};
END;
}; -- end Close
ShowPoints: PUBLIC PROC [clientData: REF ANY, event: LIST OF REF ANY] = {
ggData: GGData ← NARROW[clientData];
seqGen: SequenceGenerator ← GGSelect.SelectedSequences[ggData.scene, normal];
FOR seq: Sequence ← GGSequence.NextSequence[seqGen], GGSequence.NextSequence[seqGen] UNTIL seq = NIL DO
seq.traj.visibleJoints ← TRUE;
ENDLOOP;
};
HidePoints: PUBLIC PROC [clientData: REF ANY, event: LIST OF REF ANY] = {
ggData: GGData ← NARROW[clientData];
seqGen: SequenceGenerator ← GGSelect.SelectedSequences[ggData.scene, normal];
FOR seq: Sequence ← GGSequence.NextSequence[seqGen], GGSequence.NextSequence[seqGen] UNTIL seq = NIL DO
seq.traj.visibleJoints ← FALSE;
ENDLOOP;
};
View Menu
DisableRefresh: PUBLIC PROC [clientData: REF ANY, event: LIST OF REF ANY] = {
ggData: GGData ← NARROW[clientData];
ggData.refresh.suppressRefresh ← TRUE;
};
EnableRefresh: PUBLIC PROC [clientData: REF ANY, event: LIST OF REF ANY] = {
ggData: GGData ← NARROW[clientData];
ggData.refresh.suppressRefresh ← FALSE;
};
Debug menu ops moved to GGEventImplD
Init: PROC [] = {
CodeTimer.AddInt[CodeTimer.CreateInterval[$SelectAll, NIL], $Gargoyle];
CodeTimer.AddInt[CodeTimer.CreateInterval[$Delete, NIL], $Gargoyle];
};
Init[];
END.