GGEventImplC.mesa
Copyright c 1986 by Xerox Corporation. All rights reserved.
Last Edited by: Pier, May 14, 1987 4:07:07 pm PDT
Contents: Once an event reaches the front of the slack-process queue, it is dispatched to one of the procedures in this module.
Bier, April 20, 1987 6:52:01 pm PDT
DIRECTORY
AtomButtons, AtomButtonsTypes, BasicTime, CodeTimer, ColorTool, CubicSplines, Feedback, FS, GGAlign, GGBasicTypes, GGBoundBox, GGBuiltinShapes, GGCaret, GGEvent, GGInterface, GGInterfaceTypes, GGModelTypes, GGOutline, GGParseIn, GGRefresh, GGScene, GGSegment, GGSegmentTypes, GGSelect, GGSequence, GGSlice, GGState, GGTraj, GGTransform, GGUtility, GGWindow, Imager, ImagerArtwork, ImagerInterpress, ImagerTransformation, IO, List, Rope, TiogaMenuOps, TIPUser, Vectors2d, ViewerClasses, ViewerOps, ViewerTools;
GGEventImplC: CEDAR PROGRAM
IMPORTS BasicTime, CodeTimer, ColorTool, Feedback, FS, GGAlign, GGBoundBox, GGBuiltinShapes, GGCaret, GGEvent, GGInterface, GGOutline, GGParseIn, GGRefresh, GGScene, GGSegment, GGSelect, GGSequence, GGSlice, GGState, GGTraj, GGTransform, GGUtility, GGWindow, Imager, ImagerArtwork, ImagerInterpress, ImagerTransformation, IO, List, Rope, TiogaMenuOps, TIPUser, Vectors2d, ViewerOps, ViewerTools
EXPORTS GGEvent = BEGIN
BitVector: TYPE = GGModelTypes.BitVector;
BoundBox: TYPE = GGModelTypes.BoundBox;
ControlPointGenerator: TYPE = GGModelTypes.ControlPointGenerator;
EntityGenerator: TYPE = GGModelTypes.EntityGenerator;
GGData: TYPE = GGInterfaceTypes.GGData;
Joint: TYPE = GGSegmentTypes.Joint;
JointGenerator: TYPE = GGModelTypes.JointGenerator;
Outline: TYPE = GGModelTypes.Outline;
OutlineDescriptor: TYPE = GGModelTypes.OutlineDescriptor;
Point: TYPE = GGBasicTypes.Point;
ScalarButtonClient: TYPE = AtomButtons.ScalarButtonClient;
ScalarButtonHandle: TYPE = AtomButtons.ScalarButtonHandle;
Scene: TYPE = GGModelTypes.Scene;
Segment: TYPE = GGSegmentTypes.Segment;
SegmentGenerator: TYPE = GGModelTypes.SegmentGenerator;
Sequence: TYPE = GGModelTypes.Sequence;
SequenceGenerator: TYPE = GGModelTypes.SequenceGenerator;
Slice: TYPE = GGModelTypes.Slice;
SliceDescriptor: TYPE = GGModelTypes.SliceDescriptor;
SliceDescriptorGenerator: TYPE = GGModelTypes.SliceDescriptorGenerator;
SliceGenerator: TYPE = GGModelTypes.SliceGenerator;
SliceParts: TYPE = GGModelTypes.SliceParts;
Traj: TYPE = GGModelTypes.Traj;
TrajEnd: TYPE = GGModelTypes.TrajEnd;
TrajGenerator: TYPE = GGModelTypes.TrajGenerator;
TrajPartType: TYPE = GGModelTypes.TrajPartType;
TwoState: TYPE = AtomButtons.TwoState;
Vector: TYPE = GGBasicTypes.Vector;
Viewer: TYPE = ViewerClasses.Viewer;
WalkProc: TYPE = GGModelTypes.WalkProc;
Setting Gargoyle State
SetGravityExtent: PUBLIC PROC [clientData: REF ANY, event: LIST OF REF ANY] = {
ggData: GGData ← NARROW[clientData];
graphicsState: AtomButtonsTypes.GraphicsState ← ggData.hitTest.gravityExtentButton;
inches: REALNARROW[event.rest.first, REF REAL]^;
GGWindow.SetGravityExtent[ggData, inches];
};
SetShowColors: PUBLIC PROC [clientData: REF ANY, event: LIST OF REF ANY] = {
ggData: GGData ← NARROW[clientData];
boolRope: Rope.ROPENARROW[event.rest.first];
showColors: BOOL ← GGUtility.RopeToBool[boolRope];
GGState.SetShowColors[ggData, showColors];
};
SetGravity: PUBLIC PROC [clientData: REF ANY, event: LIST OF REF ANY] = {
ggData: GGData ← NARROW[clientData];
boolRope: Rope.ROPENARROW[event.rest.first];
setGravity: BOOL ← GGUtility.RopeToBool[boolRope];
GGState.SetGravity[ggData, setGravity];
};
SetMidpoints: PUBLIC PROC [clientData: REF ANY, event: LIST OF REF ANY] = {
ggData: GGData ← NARROW[clientData];
boolRope: Rope.ROPENARROW[event.rest.first];
setMidpoints: BOOL ← GGUtility.RopeToBool[boolRope];
GGState.SetMidpoints[ggData, setMidpoints];
};
SetHeuristics: PUBLIC PROC [clientData: REF ANY, event: LIST OF REF ANY] = {
ggData: GGData ← NARROW[clientData];
boolRope: Rope.ROPENARROW[event.rest.first];
setHeuristics: BOOL ← GGUtility.RopeToBool[boolRope];
GGState.SetHeuristics[ggData, setHeuristics];
};
SetDefaultFont: PUBLIC PROC [clientData: REF ANY, event: LIST OF REF ANY] = {
ggData: GGData ← NARROW[clientData];
fontRope: Rope.ROPENARROW[event.rest.first];
GGState.SetDefaultFont[ggData, fontRope];
};
Shape Menu Operations
PolygonInCircle: PUBLIC PROC [clientData: REF ANY, event: LIST OF REF ANY] = {
outline: Outline;
bBox: BoundBox;
ggData: GGData ← NARROW[clientData];
caretPoint: Point ← GGCaret.GetPoint[ggData.caret];
sideCount: INTNARROW[event.rest.first, REF INT]^;
IF sideCount=-1 THEN {
rRope: Rope.ROPE ← ViewerTools.GetSelectionContents[];
sideCount ← IO.GetInt[IO.RIS[rRope] ! IO.EndOfStream, IO.Error => sideCount ← -1];
};
IF sideCount<=0 THEN RETURN;
outline ← GGBuiltinShapes.PolygonInCircle[sideCount, caretPoint, ggData.hitTest.scaleUnit, ggData.defaults];
GGScene.AddOutline[ggData.scene, outline, -1];
GGSelect.DeselectAll[ggData.scene, normal];
GGSelect.SelectEntireSlice[outline, ggData.scene, normal];
bBox ← GGBoundBox.BoundBoxOfSelected[ggData.scene, normal];
ggData.refresh.startBoundBox^ ← bBox^;
ggData.refresh.addedObject ← outline;
GGWindow.RestoreScreenAndInvariants[paintAction: $ObjectAdded, ggData: ggData, remake: sceneBag, backgndOK: FALSE, edited: TRUE, okToClearFeedback: TRUE];
};
NewBox: PUBLIC PROC [clientData: REF ANY, event: LIST OF REF ANY] = {
slice: Slice;
sliceParts: SliceDescriptor;
bBox: BoundBox;
ggData: GGData ← NARROW[clientData];
caretPoint: Point ← GGCaret.GetPoint[ggData.caret];
sideLength: REALNARROW[event.rest.first, REF REAL]^;
slice ← GGBuiltinShapes.Box[caretPoint, sideLength*ggData.hitTest.scaleUnit, ggData.defaults];
GGScene.AddSlice[ggData.scene, slice, -1];
GGSelect.DeselectAll[ggData.scene, normal];
sliceParts ← slice.class.newParts[slice, NIL, slice];
GGSelect.SelectSlice[sliceParts, ggData.scene, normal];
bBox ← GGBoundBox.BoundBoxOfSelected[ggData.scene, normal];
ggData.refresh.startBoundBox^ ← bBox^;
ggData.refresh.addedObject ← slice;
GGWindow.RestoreScreenAndInvariants[paintAction: $ObjectAdded, ggData: ggData, remake: sceneBag, backgndOK: FALSE, edited: TRUE, okToClearFeedback: TRUE];
};
NewCircle: PUBLIC PROC [clientData: REF ANY, event: LIST OF REF ANY] = {
slice: Slice;
sliceParts: SliceDescriptor;
bBox: BoundBox;
ggData: GGData ← NARROW[clientData];
caretPoint: Point ← GGCaret.GetPoint[ggData.caret];
radius: REALNARROW[event.rest.first, REF REAL]^;
slice ← GGBuiltinShapes.Circle[caretPoint, radius*ggData.hitTest.scaleUnit, ggData.defaults];
GGScene.AddSlice[ggData.scene, slice, -1];
GGSelect.DeselectAll[ggData.scene, normal];
sliceParts ← slice.class.newParts[slice, NIL, slice];
GGSelect.SelectSlice[sliceParts, ggData.scene, normal];
bBox ← GGBoundBox.BoundBoxOfSelected[ggData.scene, normal];
ggData.refresh.startBoundBox^ ← bBox^;
ggData.refresh.addedObject ← slice;
GGWindow.RestoreScreenAndInvariants[paintAction: $ObjectAdded, ggData: ggData, remake: sceneBag, backgndOK: FALSE, edited: TRUE, okToClearFeedback: TRUE];
};
NewKnotchedLine: PUBLIC PROC [clientData: REF ANY, event: LIST OF REF ANY] = {
outline: Outline;
bBox: BoundBox;
ggData: GGData ← NARROW[clientData];
caretPoint: Point ← GGCaret.GetPoint[ggData.caret];
length: REALNARROW[event.rest.first, REF REAL]^;
segCount: INTNARROW[event.rest.rest.first, REF INT]^;
p1: Point;
p1 ← Vectors2d.Add[caretPoint, [length*ggData.hitTest.scaleUnit, 0.0]];
outline ← GGBuiltinShapes.KnotchedLine[p0: caretPoint, p1: p1, segmentCount: segCount];
GGScene.AddOutline[ggData.scene, outline, -1];
GGSelect.DeselectAll[ggData.scene, normal];
GGSelect.SelectEntireSlice[outline, ggData.scene, normal];
bBox ← GGBoundBox.BoundBoxOfSelected[ggData.scene, normal];
ggData.refresh.startBoundBox^ ← bBox^;
ggData.refresh.addedObject ← outline;
GGWindow.RestoreScreenAndInvariants[paintAction: $ObjectAdded, ggData: ggData, remake: sceneBag, backgndOK: FALSE, edited: TRUE, okToClearFeedback: TRUE];
};
NewArrow: PUBLIC PROC [clientData: REF ANY, event: LIST OF REF ANY] = {
OPEN Vectors2d;
outline: Outline;
traj: Traj;
seg: Segment;
bBox: BoundBox;
success: BOOL;
ggData: GGData ← NARROW[clientData];
shaftLength: REALNARROW[event.rest.first, REF REAL]^;
barbLength: REALNARROW[event.rest.rest.first, REF REAL]^;
shaftBottom, shaftTop, barbLeft, barbRight: Point;
shaftBottom ← GGCaret.GetPoint[ggData.caret];
shaftLength ← shaftLength * ggData.hitTest.scaleUnit; -- convert to screen dots.
barbLength ← barbLength * ggData.hitTest.scaleUnit; -- convert to screen dots.
shaftTop ← Add[shaftBottom, [0.0, shaftLength]];
barbLeft ← Add[shaftBottom, Scale[Normalize[[-1.0,1.0]], barbLength]];
barbRight ← Add[shaftBottom, Scale[Normalize[[1.0,1.0]], barbLength]];
traj ← GGTraj.CreateTraj[shaftTop];
seg ← GGSegment.MakeLine[shaftTop, shaftBottom, NIL];
success ← GGTraj.AddSegment[traj, hi, seg, lo];
IF NOT success THEN ERROR;
seg ← GGSegment.MakeLine[shaftBottom, barbLeft, NIL];
success ← GGTraj.AddSegment[traj, hi, seg, lo];
IF NOT success THEN ERROR;
seg ← GGSegment.MakeLine[barbLeft, shaftBottom, NIL];
success ← GGTraj.AddSegment[traj, hi, seg, lo];
IF NOT success THEN ERROR;
seg ← GGSegment.MakeLine[shaftBottom, barbRight, NIL];
success ← GGTraj.AddSegment[traj, hi, seg, lo];
IF NOT success THEN ERROR;
outline ← GGOutline.CreateOutline[traj: traj, fillColor: Imager.black];
GGScene.AddOutline[ggData.scene, outline, -1];
GGSelect.DeselectAll[ggData.scene, normal];
GGSelect.SelectEntireSlice[outline, ggData.scene, normal];
bBox ← GGBoundBox.BoundBoxOfSelected[ggData.scene, normal];
ggData.refresh.startBoundBox^ ← bBox^;
ggData.refresh.addedObject ← outline;
GGWindow.RestoreScreenAndInvariants[paintAction: $ObjectAdded, ggData: ggData, remake: sceneBag, backgndOK: FALSE, edited: TRUE, okToClearFeedback: TRUE];
};
Frame: PUBLIC PROC [clientData: REF ANY, event: LIST OF REF ANY] = {
add a "picture frame" of a specified size to the image, origin at [0.0, 0.0], strokeWidth=9.
ggData: GGData ← NARROW[clientData];
halfStrokeWidth: REAL = 9.0/2.0;
frameWidth: REALNARROW[event.rest.first, REF REAL]^; -- in Gargoyle units (points)
frameLength: REALNARROW[event.rest.rest.first, REF REAL]^; -- in Gargoyle units (points)
box: GGBoundBox.BoundBox ← GGBoundBox.CreateBoundBox[0.0-halfStrokeWidth, 0.0-halfStrokeWidth, frameWidth+halfStrokeWidth, frameLength+halfStrokeWidth];
sliceD: SliceDescriptor ← GGSlice.MakeBoxSlice[box, none, GGTransform.Identity[]];
[] ← sliceD.slice.class.setStrokeWidth[sliceD.slice, sliceD.parts, 9.0];
GGScene.AddSlice[ggData.scene, sliceD.slice, -1];
ggData.refresh.startBoundBox^ ← sliceD.slice.boundBox^;
ggData.refresh.addedObject ← sliceD.slice;
GGWindow.RestoreScreenAndInvariants[paintAction: $ObjectAdded, ggData: ggData, remake: sceneBag, backgndOK: FALSE, edited: TRUE, okToClearFeedback: TRUE];
};
Edit Menu Operations
ApplyAllDefaults: PUBLIC PROC [clientData: REF ANY, event: LIST OF REF ANY] = {
ggData: GGData ← NARROW[clientData];
bBox: BoundBox;
sliceDescGen: SliceDescriptorGenerator;
IF GGSelect.NoSelections[ggData.scene, normal] THEN RETURN;
bBox ← GGBoundBox.BoundBoxOfSelected[ggData.scene, normal];
sliceDescGen ← GGSelect.SelectedSlices[ggData.scene, normal];
FOR sliceD: SliceDescriptor ← GGSelect.NextSliceDescriptor[sliceDescGen], GGSelect.NextSliceDescriptor[sliceDescGen] UNTIL sliceD=NIL DO
sliceD.slice.class.setDefaults[sliceD.slice, sliceD.parts, ggData.defaults];
ENDLOOP;
GGBoundBox.EnlargeByBox[bBox: bBox, by: GGBoundBox.BoundBoxOfSelected[ggData.scene, normal]];
ggData.refresh.startBoundBox^ ← bBox^;
GGWindow.RestoreScreenAndInvariants[paintAction: $ObjectChangedBoundBoxProvided, ggData: ggData, remake: none, backgndOK: FALSE, edited: TRUE, okToClearFeedback: FALSE];
};
SetAllDefaults: PUBLIC PROC [clientData: REF ANY, event: LIST OF REF ANY] = {
GGEvent.SetDefaultStrokeValues[event, clientData];
GGEvent.SetDefaultLineColor[event, clientData];
GGEvent.SetDefaultFillColor[event, clientData];
GGEvent.SetDefaultFontValues[event, clientData];
GGEvent.ShowAllDefaults[event, clientData];
};
ShowAllDefaults: PUBLIC PROC [clientData: REF ANY, event: LIST OF REF ANY] = {
ggData: GGData ← NARROW[clientData];
Feedback.AppendTypescript[ggData.feedback, "--------------------------------------------", oneLiner];
GGEvent.ShowDefaultFontValues[clientData, event];
GGEvent.ShowDefaultLineColor[clientData, event];
GGEvent.ShowDefaultFillColor[clientData, event];
GGEvent.ShowDefaultStrokeValues[clientData, event];
Feedback.AppendTypescript[ggData.feedback, "--------------------------------------------", oneLiner];
};
StandardDefaults: PUBLIC PROC [clientData: REF ANY, event: LIST OF REF ANY] = {
ggData: GGData ← NARROW[clientData];
ggData.defaults^ ← [
strokeWidth: 2.0,
strokeJoint: round,
strokeEnd: round,
dashed: FALSE,
pattern: NIL,
offset: 0.0,
length: 0.0,
strokeColor: Imager.black,
fillColor: GGOutline.fillColor,
font: ggData.defaults.font
];
ShowAllDefaults[clientData, event];
};
Weld: PUBLIC PROC [clientData: REF ANY, event: LIST OF REF ANY] = {
ggData: GGData ← NARROW[clientData];
One or two top level trajectories are selected. If one, Close it by distorting one of its ends. If two, of the four possible pairings of endpoints, choose the one with the endpoints closest together. Translate the second trajectory to the first. Weld.
outSeqGen: GGSelect.OutlineSequenceGenerator;
firstTraj, secondTraj, newTraj: Traj;
firstOutline, secondOutline, newOutline: Outline;
firstOutSeq, secondOutSeq: GGSelect.OutlineSequence;
weldPoint: Point;
firstEnd, secondEnd: TrajEnd;
firstBox, secondBox, newBox: BoundBox;
success: BOOL;
outSeqGen ← GGSelect.SelectedOutlineSequences[ggData.scene, normal];
[firstOutSeq, secondOutSeq, firstTraj, secondTraj, success] ← GetWeldArguments[ggData, outSeqGen];
IF NOT success THEN RETURN;
IF secondOutSeq = NIL THEN {WeldToSelf[ggData, firstTraj]; RETURN;};
[firstEnd, secondEnd] ← ClosestEnds[firstTraj, secondTraj];
firstOutline ← GGOutline.OutlineOfTraj[firstTraj];
secondOutline ← GGOutline.OutlineOfTraj[secondTraj];
Replace the old outlines with the new welded one.
newTraj ← GGTraj.Concat[firstTraj, firstEnd, secondTraj, secondEnd];
newOutline ← GGOutline.CreateOutline[newTraj, firstOutline.class.getFillColor[firstOutline]];
GGInterface.DeleteOutline[firstOutline, ggData.scene];
GGInterface.DeleteOutline[secondOutline, ggData.scene];
GGScene.AddOutline[ggData.scene, newOutline, -1];
Select the new outline.
GGSelect.SelectEntireSlice[newOutline, ggData.scene, normal];
Move the caret to the weld spot
weldPoint ← GGTraj.FetchJointPos[newTraj, GGTraj.HiJoint[IF firstEnd=hi THEN firstTraj ELSE secondTraj]];
GGCaret.SetAttractor[ggData.caret, weldPoint, NIL];
GGCaret.SitOn[ggData.caret, NIL];
Compute the new bounding box.
firstBox ← GGTraj.GetBoundBox[firstTraj];
secondBox ← GGTraj.GetBoundBox[secondTraj];
newBox ← GGTraj.GetBoundBox[newTraj];
GGBoundBox.EnlargeByBox[bBox: firstBox, by: secondBox];
GGBoundBox.EnlargeByBox[bBox: firstBox, by: newBox];
ggData.refresh.startBoundBox^ ← firstBox^;
GGWindow.RestoreScreenAndInvariants[paintAction: $ObjectChangedBoundBoxProvided, ggData: ggData, remake: triggerBag, backgndOK: FALSE, edited: TRUE, okToClearFeedback: TRUE];
}; -- end of Weld
GetWeldArguments: PROC [ggData: GGData, outSeqGen: GGSelect.OutlineSequenceGenerator] RETURNS [firstOutSeq, secondOutSeq: GGSelect.OutlineSequence, firstTraj, secondTraj: Traj ← NIL, success: BOOL] = {
thirdOutSeq: GGSelect.OutlineSequence;
firstOutSeq ← GGSelect.NextOutlineSequences[outSeqGen];
secondOutSeq ← GGSelect.NextOutlineSequences[outSeqGen];
thirdOutSeq ← GGSelect.NextOutlineSequences[outSeqGen];
IF firstOutSeq = NIL OR thirdOutSeq # NIL THEN { -- 0 or more than 2 selected
Feedback.AppendHerald[ggData.feedback, "Select one or two open trajectories for a weld.", oneLiner];
Feedback.Blink[ggData.feedback];
RETURN[NIL, NIL, NIL, NIL, FALSE];
};
firstTraj ← firstOutSeq.fenceSeq.traj;
IF firstTraj.role # open THEN {
Feedback.AppendHerald[ggData.feedback, "Select one or two OPEN trajectories for a weld.", oneLiner];
Feedback.Blink[ggData.feedback];
RETURN[NIL, NIL, NIL, NIL, FALSE];
};
IF secondOutSeq = NIL THEN RETURN[firstOutSeq, NIL, firstTraj, NIL, TRUE];
secondTraj ← secondOutSeq.fenceSeq.traj;
IF secondTraj.role # open THEN {
Feedback.AppendHerald[ggData.feedback, "Select one or two OPEN trajectories for a weld.", oneLiner];
Feedback.Blink[ggData.feedback];
RETURN[NIL, NIL, NIL, NIL, FALSE];
};
success ← TRUE;
};
WeldToSelf: PROC [ggData: GGData, traj: Traj] = {
restoreBox: BoundBox;
outline: Outline ← GGOutline.OutlineOfTraj[traj];
restoreBox ← GGTraj.GetBoundBox[traj];
Modify the Traj in place to close it.
GGOutline.SaveSelectionsInOutline[outline, ggData.scene];
GGSelect.DeselectEntityAllClasses[outline, ggData.scene];
GGTraj.CloseByDistorting[traj, lo];
outline.class.setFillColor[outline, GGOutline.fillColor];
outline.class.setFillColor[outline, ggData.defaults.fillColor];
GGOutline.RemakeSelectionsFromOutline[outline, ggData.scene];
GGSelect.SelectEntireSlice[outline, ggData.scene, normal];
move the caret to the weld spot
GGCaret.SetAttractor[ggData.caret, GGTraj.FetchJointPos[traj, 0], NIL];
GGCaret.SitOn[ggData.caret, NIL]; -- this is important!
GGBoundBox.EnlargeByBox[bBox: restoreBox, by: GGTraj.GetBoundBox[traj]];
ggData.refresh.startBoundBox^ ← restoreBox^;
GGWindow.RestoreScreenAndInvariants[paintAction: $ObjectChangedBoundBoxProvided, ggData: ggData, remake: triggerBag, backgndOK: FALSE, edited: TRUE, okToClearFeedback: TRUE];
}; -- end WeldToSelf
ClosestEnds: PROC [firstTraj, secondTraj: Traj] RETURNS [firstEnd, secondEnd: TrajEnd] = {
firstLo, firstHi, secondLo, secondHi: Point;
d11, d12, d21, d22, d: REAL;
firstLo ← GGTraj.FetchJointPos[firstTraj, 0];
firstHi ← GGTraj.FetchJointPos[firstTraj, GGTraj.HiJoint[firstTraj]];
secondLo ← GGTraj.FetchJointPos[secondTraj, 0];
secondHi ← GGTraj.FetchJointPos[secondTraj, GGTraj.HiJoint[secondTraj]];
d11 ← Vectors2d.DistanceSquared[firstLo, secondLo];
d12 ← Vectors2d.DistanceSquared[firstLo, secondHi];
d21 ← Vectors2d.DistanceSquared[firstHi, secondLo];
d22 ← Vectors2d.DistanceSquared[firstHi, secondHi];
d ← MIN[d11, d12, d21, d22];
SELECT TRUE FROM
d11 = d => {firstEnd ← lo; secondEnd ← lo};
d12 = d => {firstEnd ← lo; secondEnd ← hi};
d21 = d => {firstEnd ← hi; secondEnd ← lo};
d22 = d => {firstEnd ← hi; secondEnd ← hi};
ENDCASE => ERROR;
};
SplitSegment: PUBLIC PROC [clientData: REF ANY, event: LIST OF REF ANY] = {
MakeReplacement: GGSelect.RunProc = {
RunProc: TYPE = PROC [run: Sequence] RETURNS [traj: Traj];
GetPt: PROC[p0, dir: Point] RETURNS [pt: Point] = {
gScale: REAL ← 0.33;
gAngle: REAL ← 33;
pt ← Vectors2d.Add[Vectors2d.Scale[Vectors2d.VectorPlusAngle[dir,gAngle], gScale*Vectors2d.Magnitude[dir] ], p0];
};
-- make new run of the appropriate type, having the same joints as that of the selected run, and with the caret position as an additional joint or control point.
middlePoint: Point ← GGCaret.GetPoint[ggData.caret];
runSegGen: SegmentGenerator ← GGSequence.SegmentsInSequence[run];
FOR runSeg: Segment ← GGSequence.NextSegment[runSegGen], GGSequence.NextSegment[runSegGen] UNTIL runSeg=NIL DO
firstPoint: Point ← runSeg.lo;
lastPoint: Point ← runSeg.hi;
IF traj=NIL THEN {
traj ← GGTraj.CreateTraj[firstPoint];
GGTraj.SetTrajStrokeJoint[traj, run.traj.strokeJoint];
};
SELECT runSeg.class.type FROM
$Line => {
line: Segment ← GGSegment.MakeLine[firstPoint, middlePoint, List.Append[runSeg.props, NIL]];
GGSegment.CopyLooks[runSeg, line];
IF NOT GGTraj.AddSegment[traj, hi, line, lo] THEN ERROR;
line ← GGSegment.MakeLine[middlePoint, lastPoint, List.Append[runSeg.props, NIL]];
GGSegment.CopyLooks[runSeg, line];
IF NOT GGTraj.AddSegment[traj, hi, line, lo] THEN ERROR;
};
$Arc => {
arc: Segment ← GGSegment.MakeArc[firstPoint, middlePoint, lastPoint, List.Append[runSeg.props, NIL]];
GGSegment.CopyLooks[runSeg, arc];
IF NOT GGTraj.AddSegment[traj, hi, arc, lo] THEN ERROR;
};
$Conic => {
conic: Segment ← GGSegment.MakeConic[firstPoint, middlePoint, lastPoint, 0.7, List.Append[runSeg.props, NIL]];
GGSegment.CopyLooks[runSeg, conic];
IF NOT GGTraj.AddSegment[traj, hi, conic, lo] THEN ERROR;
};
$Bezier => {
length: REAL ← Vectors2d.Distance[firstPoint, middlePoint];
dir: Point ← Vectors2d.Sub[middlePoint, firstPoint];
p1: Point ← GetPt[firstPoint, dir];
p2: Point ← GetPt[middlePoint, [-dir.x, -dir.y]];
bezier: Segment ← GGSegment.MakeBezier[firstPoint, p1, p2, middlePoint, List.Append[runSeg.props, NIL]];
GGSegment.CopyLooks[runSeg, bezier];
IF NOT GGTraj.AddSegment[traj, hi, bezier, lo] THEN ERROR; -- first Bezier
dir ← Vectors2d.Sub[lastPoint, middlePoint];
p1 ← GetPt[middlePoint, dir];
p2 ← GetPt[lastPoint, [-dir.x, -dir.y]];
bezier ← GGSegment.MakeBezier[middlePoint, p1, p2, lastPoint, List.Append[runSeg.props, NIL]];
GGSegment.CopyLooks[runSeg, bezier];
IF NOT GGTraj.AddSegment[traj, hi, bezier, lo] THEN ERROR; -- last Bezier
};
$CubicSpline => {
cSpline: Segment;
cps: CubicSplines.KnotSequence ← NEW[CubicSplines.KnotSequenceRec[3]];
cps[0] ← [firstPoint.x, firstPoint.y];
cps[1] ← [middlePoint.x, middlePoint.y];
cps[2] ← [lastPoint.x, lastPoint.y];
cSpline ← GGSegment.MakeCubicSpline[cps, naturalAL, List.Append[runSeg.props, NIL]];
GGSegment.CopyLooks[runSeg, cSpline];
IF NOT GGTraj.AddSegment[traj, hi, cSpline, lo] THEN ERROR;
};
ENDCASE => ERROR; -- unknown segment type
ENDLOOP;
};
ggData: GGData ← NARROW[clientData];
ggData.refresh.startBoundBox^ ← GGSelect.ForEachOutlineRun[ggData.scene, normal, MakeReplacement]^;
GGCaret.NoAttractor[ggData.caret];
GGCaret.SitOn[ggData.caret, NIL];
GGWindow.RestoreScreenAndInvariants[paintAction: $ObjectChangedBoundBoxProvided, ggData: ggData, remake: triggerBag, backgndOK: FALSE, edited: TRUE, okToClearFeedback: TRUE]
}; -- end SplitSegment
Splice: PUBLIC PROC [clientData: REF ANY, event: LIST OF REF ANY] = {
Replace all selected runs with line segments.
ReplaceWithLine: GGSelect.RunProc = {
RunProc: TYPE = PROC [run: Sequence] RETURNS [traj: Traj];
seg: Segment;
p0, p1: Point;
jointNum: INT;
firstRunSeg: Segment ← GGSequence.NextSegment[GGSequence.SegmentsInSequence[run]];
jointNum ← GGSequence.FirstJointNum[run];
p0 ← GGTraj.FetchJointPos[run.traj, jointNum];
jointNum ← GGSequence.LastJointNum[run, jointNum];
p1 ← GGTraj.FetchJointPos[run.traj, jointNum];
seg ← GGSegment.MakeLine[p0, p1, NIL];
GGSegment.CopyLooks[firstRunSeg, seg];
traj ← GGTraj.CreateTraj[p0];
GGTraj.SetTrajStrokeJoint[traj, run.traj.strokeJoint];
IF NOT GGTraj.AddSegment[traj, hi, seg, lo] THEN ERROR;
};
ggData: GGData ← NARROW[clientData];
bBox: BoundBox;
bBox ← GGSelect.ForEachOutlineRun[ggData.scene, normal, ReplaceWithLine];
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];
}; -- end Splice
DescribeCurve: PUBLIC PROC [clientData: REF ANY, event: LIST OF REF ANY] = {
event.first = $DescribeCurve, event.rest = $Selected or $Caret
ggData: GGData ← NARROW[clientData];
SELECT event.rest.first FROM
$Caret => GGEvent.DescribeCaretObject[clientData, event]; -- for now
$Selected => {
sliceDescGen: SliceDescriptorGenerator ← GGSelect.SelectedSlices[ggData.scene, normal];
sliceD: SliceDescriptor ← GGSelect.NextSliceDescriptor[sliceDescGen];
IF sliceD=NIL THEN {
Feedback.Append[ggData.feedback, "Select some object for description", oneLiner];
Feedback.Blink[ggData.feedback];
RETURN;
};
IF GGSelect.NextSliceDescriptor[sliceDescGen]#NIL THEN {
Feedback.Append[ggData.feedback, "Select only one object for description", oneLiner];
Feedback.Blink[ggData.feedback];
RETURN;
};
Feedback.Append[ggData.feedback, sliceD.slice.class.describe[sliceD], oneLiner];
};
ENDCASE => ERROR;
};
AddControlPointOLDD: PUBLIC PROC [clientData: REF ANY, event: LIST OF REF ANY] = {
Adds a new control point to a segment
ggData: GGData ← NARROW[clientData];
attractor: REF ANY;
seg, newSeg: Segment;
segNum: INT;
traj, newRun, newTraj: Traj;
refreshBox: BoundBox;
success: BOOL;
partType: TrajPartType;
caretPos: Point ← GGCaret.GetPoint[ggData.caret];
attractor ← GGCaret.GetAttractor[ggData.caret];
BEGIN
IF attractor = NIL THEN GOTO NotOnSpline;
WITH attractor SELECT FROM
oD: OutlineDescriptor => {
[success, partType, traj, ----, ----, ----, ----, seg, segNum] ← GGOutline.UnpackSimpleDescriptorOld[oD];
IF NOT success OR partType # segment THEN GOTO NotOnSpline;
};
sD: SliceDescriptor => {
IF sD.slice.class.type # $Outline THEN GOTO NotOnSpline;
[success, partType, traj, ----, ----, ----, ----, seg, segNum] ← GGOutline.UnpackSimpleDescriptor[sD];
IF NOT success OR partType # segment THEN GOTO NotOnSpline;
};
ENDCASE => ERROR;
IF seg.class.type # $CubicSpline THEN GOTO NotOnSpline;
Must save select data now so that segment copy (in CSControlPointAdd) will reflect selection data.
GGSelect.DeselectAll[ggData.scene, normal];
GGTraj.SaveSelection[traj, normal, ggData.scene]; -- to clear field bits
GGTraj.SaveSelection[traj, hot, ggData.scene]; -- to save hot field bits
newSeg ← GGSegment.CSControlPointAdd[seg, caretPos];
newRun ← GGTraj.CreateTraj[newSeg.lo];
GGTraj.SetTrajStrokeJoint[newRun, traj.strokeJoint];
IF NOT GGTraj.AddSegment[newRun, hi, newSeg, lo] THEN ERROR;
IF (segNum ← GGTraj.IndexOfSegment[seg, traj]) = -1 THEN ERROR; -- inconsistent
[refreshBox, newTraj] ← GGSelect.SubstituteForSegment[traj, segNum, newRun, ggData.scene];
GGCaret.NoAttractor[ggData.caret];
GGCaret.SitOn[ggData.caret, NIL];
ggData.refresh.startBoundBox^ ← refreshBox^;
GGWindow.RestoreScreenAndInvariants[paintAction: $ObjectChangedBoundBoxProvided, ggData: ggData, remake: triggerBag, backgndOK: FALSE, edited: TRUE, okToClearFeedback: TRUE];
EXITS
NotOnSpline => {
Feedback.Append[ggData.feedback, "Caret must lie on a cubic spline to add a control point", oneLiner];
Feedback.Blink[ggData.feedback];
};
END;
};
AddControlPoint: PUBLIC PROC [clientData: REF ANY, event: LIST OF REF ANY] = {
Adds a new control point to a segment
ggData: GGData ← NARROW[clientData];
attractor: SliceDescriptor;
seg, newSeg: Segment;
segNum: INT;
traj, newRun, newTraj: Traj;
refreshBox: BoundBox;
success: BOOL;
partType: TrajPartType;
caretPos: Point ← GGCaret.GetPoint[ggData.caret];
attractor ← GGCaret.GetAttractor[ggData.caret];
BEGIN
IF attractor = NIL THEN GOTO NotOnSpline;
IF attractor.slice.class.type # $Outline THEN GOTO NotOnSpline;
[success, partType, traj, ----, ----, ----, ----, seg, segNum] ← GGOutline.UnpackSimpleDescriptor[attractor];
IF NOT success OR partType # segment THEN GOTO NotOnSpline;
IF seg.class.type # $CubicSpline THEN GOTO NotOnSpline;
Must save select data now so that segment copy (in CSControlPointAdd) will reflect selection data.
GGSelect.DeselectAll[ggData.scene, normal];
GGTraj.SaveSelection[traj, normal, ggData.scene]; -- to clear field bits
GGTraj.SaveSelection[traj, hot, ggData.scene]; -- to save hot field bits
newSeg ← GGSegment.CSControlPointAdd[seg, caretPos];
newRun ← GGTraj.CreateTraj[newSeg.lo];
GGTraj.SetTrajStrokeJoint[newRun, traj.strokeJoint];
IF NOT GGTraj.AddSegment[newRun, hi, newSeg, lo] THEN ERROR;
IF (segNum ← GGTraj.IndexOfSegment[seg, traj]) = -1 THEN ERROR; -- inconsistent
[refreshBox, newTraj] ← GGSelect.SubstituteForSegment[traj, segNum, newRun, ggData.scene];
GGCaret.NoAttractor[ggData.caret];
GGCaret.SitOn[ggData.caret, NIL];
ggData.refresh.startBoundBox^ ← refreshBox^;
GGWindow.RestoreScreenAndInvariants[paintAction: $ObjectChangedBoundBoxProvided, ggData: ggData, remake: triggerBag, backgndOK: FALSE, edited: TRUE, okToClearFeedback: TRUE];
EXITS
NotOnSpline => {
Feedback.Append[ggData.feedback, "Caret must lie on a cubic spline to add a control point", oneLiner];
Feedback.Blink[ggData.feedback];
};
END;
};
DeleteControlPoint: PUBLIC PROC [clientData: REF ANY, event: LIST OF REF ANY] = {
Deletes all selected control points from segments
DeleteSelectedCPs: PROC [seq: Sequence] = {
AllFalse: PROC [bitvec: BitVector] RETURNS [BOOL] = {
FOR i: NAT IN [0..bitvec.len) DO
IF bitvec[i] = TRUE THEN RETURN[FALSE];
ENDLOOP;
RETURN[TRUE];
};
refreshBox: BoundBox;
outlineBox: BoundBox ← seq.traj.parent.class.getBoundBox[seq.traj.parent, NIL];
GGBoundBox.EnlargeByBox[ggData.refresh.startBoundBox, outlineBox]; -- refresh old outline
GGTraj.SaveSelection[seq.traj, hot, ggData.scene]; -- to be restored by SubstituteForSeg
GGTraj.SaveSelection[seq.traj, normal, ggData.scene];
FOR i:INT IN [0..seq.traj.segCount) DO
IF NOT AllFalse[seq.controlPoints[i]] AND NOT seq.segments[i] THEN {
oldSeg: Segment ← GGTraj.FetchSegment[seq.traj, i];
IF oldSeg.class.type = $CubicSpline THEN {
newSeg: Segment ← GGSegment.CSControlPointDelete[oldSeg, seq.controlPoints[i]];
newRun: Traj ← GGTraj.CreateTraj[newSeg.lo];
GGTraj.SetTrajStrokeJoint[newRun, seq.traj.strokeJoint];
IF NOT GGTraj.AddSegment[newRun, hi, newSeg, lo] THEN ERROR;
[bBox: refreshBox] ← GGSelect.SubstituteForSegment[seq.traj, i, newRun, ggData.scene];
GGCaret.SetAttractor[ggData.caret, newSeg.lo, NIL];
GGBoundBox.EnlargeByBox[ggData.refresh.startBoundBox, refreshBox];
}
ELSE {
Feedback.Append[ggData.feedback, "Only delete control points from Cubic Splines", oneLiner];
Feedback.Blink[ggData.feedback];
};
};
ENDLOOP;
};
ggData: GGData ← NARROW[clientData];
outSeqGen: GGSelect.OutlineSequenceGenerator ← GGSelect.SelectedOutlineSequences[ggData.scene, normal];
ggData.refresh.startBoundBox ← GGBoundBox.NullBoundBox[]; -- start with empty refresh box, and increase size when necessary
FOR outSeq: GGSelect.OutlineSequence ← GGSelect.NextOutlineSequences[outSeqGen], GGSelect.NextOutlineSequences[outSeqGen] UNTIL outSeq = NIL DO
IF outSeq.fenceSeq # NIL THEN DeleteSelectedCPs[outSeq.fenceSeq];
FOR hole: Sequence ← GGSequence.NextSequence[outSeq.holeSeqs], GGSequence.NextSequence[outSeq.holeSeqs] UNTIL hole = NIL DO
DeleteSelectedCPs[hole]
ENDLOOP;
ENDLOOP;
GGCaret.SitOn[ggData.caret, NIL];
GGWindow.RestoreScreenAndInvariants[paintAction: $ObjectChangedBoundBoxProvided, ggData: ggData, remake: triggerBag, backgndOK: FALSE, edited: TRUE, okToClearFeedback: FALSE];
};
AddJointOLDD: PUBLIC PROC [clientData: REF ANY, event: LIST OF REF ANY] = {
ggData: GGData ← NARROW[clientData];
attractor: REF ANY;
seg, newSeg1, newSeg2: Segment;
segNum: INT;
traj, newRun, newTraj: Traj;
refreshBox, oldBox: BoundBox;
newJoint: Joint;
success: BOOL;
partType: TrajPartType;
caretPos: Point ← GGCaret.GetPoint[ggData.caret];
attractor ← GGCaret.GetAttractor[ggData.caret];
BEGIN
IF attractor = NIL THEN GOTO NotASegment;
WITH attractor SELECT FROM
oD: OutlineDescriptor => {
[success, partType, traj, ----, ----, ----, ----, seg, segNum] ← GGOutline.UnpackSimpleDescriptorOld[oD];
IF NOT success OR partType # segment THEN GOTO NotASegment;
};
sD: SliceDescriptor => {
IF sD.slice.class.type # $Outline THEN GOTO NotASegment;
[success, partType, traj, ----, ----, ----, ----, seg, segNum] ← GGOutline.UnpackSimpleDescriptor[sD];
IF NOT success OR partType # segment THEN GOTO NotASegment;
};
ENDCASE => ERROR;
IF seg.class.type=$Conic THEN GOTO ConicsAreNotDone;
Save select data now so that segment copy (in CSControlPointAdd) will reflect selection data.
GGSelect.DeselectAll[ggData.scene, normal];
GGTraj.SaveSelection[traj, normal, ggData.scene];
GGTraj.SaveSelection[traj, hot, ggData.scene];
[Artwork node; type 'ArtworkInterpress on' to command tool]
[newSeg1, newSeg2] ← seg.class.addJoint[seg, caretPos];
newRun ← GGTraj.CreateTraj[newSeg1.lo];
IF NOT GGTraj.AddSegment[newRun, hi, newSeg1, lo] THEN ERROR;
IF NOT GGTraj.AddSegment[newRun, hi, newSeg2, lo] THEN ERROR;
newJoint ← GGTraj.FetchJoint[newRun, 1];
newJoint.TselectedInFull.normal ← TRUE;
IF (segNum ← GGTraj.IndexOfSegment[seg, traj]) = -1 THEN ERROR; -- inconsistent
oldBox ← seg.class.boundBox[seg];
[refreshBox, newTraj] ← GGSelect.SubstituteForSegment[traj, segNum, newRun, ggData.scene];
GGTraj.SetTrajStrokeJoint[newTraj, traj.strokeJoint];
GGBoundBox.EnlargeByBox[refreshBox, oldBox];
GGCaret.NoAttractor[ggData.caret];
GGCaret.SitOn[ggData.caret, NIL];
ggData.refresh.startBoundBox^ ← refreshBox^;
GGWindow.RestoreScreenAndInvariants[paintAction: $ObjectChangedBoundBoxProvided, ggData: ggData, remake: triggerBag, backgndOK: FALSE, edited: TRUE, okToClearFeedback: TRUE];
EXITS
NotASegment => {
Feedback.Append[ggData.feedback, "Caret must lie on a segment to add a joint", oneLiner];
Feedback.Blink[ggData.feedback];
};
ConicsAreNotDone => {
Feedback.Append[ggData.feedback, "Can't add joints to Conics", oneLiner];
Feedback.Blink[ggData.feedback];
};
END;
};
AddJoint: PUBLIC PROC [clientData: REF ANY, event: LIST OF REF ANY] = {
ggData: GGData ← NARROW[clientData];
attractor: SliceDescriptor;
seg, newSeg1, newSeg2: Segment;
segNum: INT;
traj, newRun, newTraj: Traj;
refreshBox, oldBox: BoundBox;
newJoint: Joint;
success: BOOL;
partType: TrajPartType;
caretPos: Point ← GGCaret.GetPoint[ggData.caret];
attractor ← GGCaret.GetAttractor[ggData.caret];
BEGIN
IF attractor = NIL THEN GOTO NotASegment;
IF attractor.slice.class.type # $Outline THEN GOTO NotASegment;
[success, partType, traj, ----, ----, ----, ----, seg, segNum] ← GGOutline.UnpackSimpleDescriptor[attractor];
IF NOT success OR partType # segment THEN GOTO NotASegment;
IF seg.class.type=$Conic THEN GOTO ConicsAreNotDone;
Save select data now so that segment copy (in CSControlPointAdd) will reflect selection data.
GGSelect.DeselectAll[ggData.scene, normal];
GGTraj.SaveSelection[traj, normal, ggData.scene];
GGTraj.SaveSelection[traj, hot, ggData.scene];
[Artwork node; type 'ArtworkInterpress on' to command tool]
[newSeg1, newSeg2] ← seg.class.addJoint[seg, caretPos];
newRun ← GGTraj.CreateTraj[newSeg1.lo];
IF NOT GGTraj.AddSegment[newRun, hi, newSeg1, lo] THEN ERROR;
IF NOT GGTraj.AddSegment[newRun, hi, newSeg2, lo] THEN ERROR;
newJoint ← GGTraj.FetchJoint[newRun, 1];
newJoint.TselectedInFull.normal ← TRUE;
IF (segNum ← GGTraj.IndexOfSegment[seg, traj]) = -1 THEN ERROR; -- inconsistent
oldBox ← seg.class.boundBox[seg];
[refreshBox, newTraj] ← GGSelect.SubstituteForSegment[traj, segNum, newRun, ggData.scene];
GGTraj.SetTrajStrokeJoint[newTraj, traj.strokeJoint];
GGBoundBox.EnlargeByBox[refreshBox, oldBox];
GGCaret.NoAttractor[ggData.caret];
GGCaret.SitOn[ggData.caret, NIL];
ggData.refresh.startBoundBox^ ← refreshBox^;
GGWindow.RestoreScreenAndInvariants[paintAction: $ObjectChangedBoundBoxProvided, ggData: ggData, remake: triggerBag, backgndOK: FALSE, edited: TRUE, okToClearFeedback: TRUE];
EXITS
NotASegment => {
Feedback.Append[ggData.feedback, "Caret must lie on a segment to add a joint", oneLiner];
Feedback.Blink[ggData.feedback];
};
ConicsAreNotDone => {
Feedback.Append[ggData.feedback, "Can't add joints to Conics", oneLiner];
Feedback.Blink[ggData.feedback];
};
END;
};
Transform Menu Operations
TransRotScale: PUBLIC PROC [clientData: REF ANY, event: LIST OF REF ANY] = {
ComplainAboutAnchor: PROC = {
Feedback.AppendHerald[ggData.feedback, "Place an anchor for transform origin", oneLiner];
Feedback.Blink[ggData.feedback];
};
ggData: GGData ← NARROW[clientData];
scalar: REAL ← GGParseIn.ReadBlankAndReal[IO.RIS[rope: NARROW[event.rest.first]] ! IO.Error => {
Feedback.AppendHerald[ggData.feedback, "Select a meaningful real number", oneLiner];
Feedback.Blink[ggData.feedback];
GOTO Abort; -- can't put RETURN in an error handler
}];
IF scalar = 0.0 THEN {  -- If either nothing or 0.0 was selected
Feedback.AppendHerald[ggData.feedback, "Select a meaningful real number", oneLiner];
Feedback.Blink[ggData.feedback];
RETURN;
};
{
anchorPoint: Point ← GGCaret.GetPoint[ggData.anchor];
transform: ImagerTransformation.Transformation;
SELECT event.first FROM
$TranslateX => transform ← ImagerTransformation.Translate[[ggData.hitTest.scaleUnit*scalar, 0.0]];
$TranslateY => transform ← ImagerTransformation.Translate[[0.0, ggData.hitTest.scaleUnit*scalar]];
$Rotate => IF GGCaret.Exists[ggData.anchor] THEN transform ← GGTransform.RotateAboutPoint[anchorPoint, scalar] ELSE {ComplainAboutAnchor[]; RETURN};
$Scale => IF GGCaret.Exists[ggData.anchor] THEN transform ← GGTransform.ScaleAboutPoint[anchorPoint, scalar] ELSE {ComplainAboutAnchor[]; RETURN};
$ScaleX => IF GGCaret.Exists[ggData.anchor] THEN transform ← GGTransform.ScaleUnevenAboutPoint[anchorPoint, scalar, 1.0] ELSE {ComplainAboutAnchor[]; RETURN};
$ScaleY => IF GGCaret.Exists[ggData.anchor] THEN transform ← GGTransform.ScaleUnevenAboutPoint[anchorPoint, 1.0, scalar] ELSE {ComplainAboutAnchor[]; RETURN};
ENDCASE => ERROR;
DoTheTransforms[ggData, transform];
}
EXITS
Abort => NULL;
}; -- end TransRotScale
SixPointTransform: PUBLIC PROC [clientData: REF ANY, event: LIST OF REF ANY] = {
The first second and third selected objects should be one-segment trajectories indicating the six-point transform. All other selected objects are to be transformed. There are obvious problems with this scheme. Suggestions welcome. -- Bier, July 28, 1986.
ggData: GGData ← NARROW[clientData];
hotGen: SequenceGenerator ← GGSelect.SelectedSequences[ggData.scene, hot];
points: ARRAY [0..5] OF Point;
transform: ImagerTransformation.Transformation;
seq: Sequence;
FOR i: NAT IN [0..1] DO
seq ← GGSequence.NextSequence[hotGen];
IF seq = NIL THEN GOTO Abort;
IF GGTraj.HiJoint[seq.traj] < 2 THEN GOTO Abort;
FOR j: NAT IN [0..2] DO
points[j + i*3] ← GGTraj.FetchJointPos[seq.traj, j];
ENDLOOP;
ENDLOOP;
transform ← GGTransform.SixPoints[points];
DoTheTransforms[ggData, transform];
EXITS
Abort => {
ggData: GGData ← NARROW[clientData];
Feedback.AppendHerald[ggData.feedback, "Not enough arguments for a six-point transform", oneLiner];
Feedback.Blink[ggData.feedback];
};
}; -- end SixPointTransform
FourPointTransform: PUBLIC PROC [clientData: REF ANY, event: LIST OF REF ANY] = {
The first and second selected objects should be one-segment trajectories representing the four-point transform. All other selected objects are to be transformed. There are obvious problems with this scheme. Suggestions welcome. -- Bier, July 28, 1986.
ggData: GGData ← NARROW[clientData];
hotGen: SequenceGenerator ← GGSelect.SelectedSequences[ggData.scene, hot];
points: ARRAY [0..3] OF Point;
transform: ImagerTransformation.Transformation;
seq: Sequence;
FOR i: NAT IN [0..1] DO
seq ← GGSequence.NextSequence[hotGen];
IF seq = NIL THEN GOTO Abort;
IF GGTraj.HiJoint[seq.traj] < 1 THEN GOTO Abort;
FOR j: NAT IN [0..1] DO
points[j + i*2] ← GGTraj.FetchJointPos[seq.traj, j];
ENDLOOP;
ENDLOOP;
transform ← GGTransform.FourPoints[points];
DoTheTransforms[ggData, transform];
EXITS
Abort => {
ggData: GGData ← NARROW[clientData];
Feedback.AppendHerald[ggData.feedback, "Not enough arguments for a four-point transform", oneLiner];
Feedback.Blink[ggData.feedback];
};
}; -- end SixPointTransform
DoTheTransforms: PROC [ggData: GGData, transform: ImagerTransformation.Transformation] = {
sliceDescGen: SliceDescriptorGenerator ← GGSelect.SelectedSlices[ggData.scene, normal];
The boundbox before anything has moved.
ggData.refresh.startBoundBox^ ← GGBoundBox.BoundBoxOfMoving[ggData.scene, normal]^;
FOR sliceD: SliceDescriptor ← GGSelect.NextSliceDescriptor[sliceDescGen], GGSelect.NextSliceDescriptor[sliceDescGen] UNTIL sliceD=NIL DO
sliceD.slice.class.transform[sliceD, transform];
ENDLOOP;
The boundbox after everything has moved.
GGBoundBox.EnlargeByBox[bBox: ggData.refresh.startBoundBox, by: GGBoundBox.BoundBoxOfMoving[ggData.scene, normal]];
GGWindow.RestoreScreenAndInvariants[paintAction: $ObjectChangedBoundBoxProvided, ggData: ggData, remake: triggerBag, backgndOK: FALSE, edited: TRUE, okToClearFeedback: TRUE];
};
Area Selection Operations
AreaSelectNew: PUBLIC PROC [clientData: REF ANY, event: LIST OF REF ANY] = {
ggData: GGData ← NARROW[clientData];
Select all objects within area(s) bounded by original selection(s) and deselect original selection
AreaSelectAux[ggData, TRUE, TRUE];
};
AreaSelectExtend: PUBLIC PROC [clientData: REF ANY, event: LIST OF REF ANY] = {
ggData: GGData ← NARROW[clientData];
Add all objects within area(s) bounded by original selection(s) to current selection
AreaSelectAux[ggData, FALSE, TRUE];
};
AreaSelectNewAndDeleteOLDD: PUBLIC PROC [clientData: REF ANY, event: LIST OF REF ANY] = {
ggData: GGData ← NARROW[clientData];
Select all objects within area(s) bounded by original selection(s) and delete original selection
IF GGSelect.NoSelections[ggData.scene, normal] THEN RETURN
ELSE { -- there were some original selections
oldSelectedGen: EntityGenerator ← GGSelect.SelectedSlices[ggData.scene, normal]; -- save original selection
startBox: BoundBox ← GGBoundBox.BoundBoxOfSelected[ggData.scene, normal];
AreaSelectAux[ggData: ggData, new: TRUE, paint: FALSE];
FOR formerSelected: REF ANY ← GGScene.NextEntity[oldSelectedGen], GGScene.NextEntity[oldSelectedGen] UNTIL formerSelected=NIL DO
WITH formerSelected SELECT FROM
outlineD: OutlineDescriptor => {
N.B.: DeleteOutline deletes too much
GGSelect.DeselectEntityAllClasses[outlineD.slice, ggData.scene];
GGScene.DeleteOutline[ggData.scene, outlineD.slice];
};
sliceD: SliceDescriptor => {
GGSelect.DeselectEntityAllClasses[sliceD.slice, ggData.scene];
GGSlice.DeleteSlice[ggData.scene, sliceD.slice];
};
ENDCASE => ERROR;
ENDLOOP;
GGCaret.NoAttractor[ggData.caret];
GGCaret.SitOn[ggData.caret, NIL];
ggData.refresh.startBoundBox^ ← startBox^;
GGWindow.RestoreScreenAndInvariants[paintAction: $ObjectChangedBoundBoxProvided, ggData: ggData, remake: triggerBag, backgndOK: TRUE, edited: FALSE, okToClearFeedback: TRUE];
};
};
AreaSelectNewAndDelete: PUBLIC PROC [clientData: REF ANY, event: LIST OF REF ANY] = {
ggData: GGData ← NARROW[clientData];
Select all objects within area(s) bounded by original selection(s) and delete original selection
IF GGSelect.NoSelections[ggData.scene, normal] THEN RETURN
ELSE { -- there were some original selections
oldSelectedGen: SliceDescriptorGenerator ← GGSelect.SelectedSlices[ggData.scene, normal]; -- save original selection
startBox: BoundBox ← GGBoundBox.BoundBoxOfSelected[ggData.scene, normal];
AreaSelectAux[ggData: ggData, new: TRUE, paint: FALSE];
FOR sliceD: SliceDescriptor ← GGSelect.NextSliceDescriptor[oldSelectedGen], GGSelect.NextSliceDescriptor[oldSelectedGen] UNTIL sliceD=NIL DO
GGSelect.DeselectEntityAllClasses[sliceD.slice, ggData.scene];
GGScene.DeleteSlice[ggData.scene, sliceD.slice];
ENDLOOP;
GGCaret.NoAttractor[ggData.caret];
GGCaret.SitOn[ggData.caret, NIL];
ggData.refresh.startBoundBox^ ← startBox^;
GGWindow.RestoreScreenAndInvariants[paintAction: $ObjectChangedBoundBoxProvided, ggData: ggData, remake: triggerBag, backgndOK: TRUE, edited: FALSE, okToClearFeedback: TRUE];
};
};
AreaSelectDegenerate: PUBLIC PROC [clientData: REF ANY, event: LIST OF REF ANY] = {
ggData: GGData ← NARROW[clientData];
Select all degenerate (i.e. segments having co-located endpoints) segments.
Select all text slices with only whitespace in them
sliceGen: SliceGenerator ← GGScene.SlicesInScene[ggData.scene];
GGSelect.DeselectAll[ggData.scene, normal];
FOR slice: Slice ← GGScene.NextSlice[sliceGen], GGScene.NextSlice[sliceGen] UNTIL slice = NIL DO -- for every slice in the scene
DegenerateProc: WalkProc = {
WalkProc: TYPE = PROC [seg: Segment] RETURNS [keep: BOOL];
RETURN [seg.lo.x=seg.hi.x AND seg.lo.y=seg.hi.y];
};
sliceD: SliceDescriptor ← GGSlice.WalkSegments[slice, DegenerateProc]; -- get a descriptor of degenerate (zero length) segments
IF sliceD#NIL THEN GGSelect.SelectSlice[sliceD, ggData.scene, normal]; -- and select it
IF GGSlice.IsWhitespace[slice] THEN GGSelect.SelectEntireSlice[slice, ggData.scene, normal];
ENDLOOP;
Feedback.PutFHerald[ggData.feedback, oneLiner, "Degenerate segments and strings selected"];
GGWindow.RestoreScreenAndInvariants[paintAction: $SelectionChanged, ggData: ggData, remake: none, backgndOK: TRUE, edited: FALSE, okToClearFeedback: TRUE];
};
AreaSelectAuxOLDD: PROC [ggData: GGData, new: BOOLTRUE, paint: BOOLTRUE] = {
Within: PROC [test, bound: GGBoundBox.BoundBox] RETURNS [BOOL] = {
RETURN [ test.hiX <= bound.hiX AND test.loX >= bound.loX AND test.hiY <= bound.hiY AND test.loY >= bound.loY ];
};
selectedGen: EntityGenerator;
sceneGen: EntityGenerator;
IF GGSelect.NoSelections[ggData.scene, normal] THEN RETURN;
selectedGen ← GGSelect.SelectedSlices[ggData.scene, normal];
IF new THEN GGSelect.DeselectAll[ggData.scene, normal]; -- get rid of old selection
outer loop on each individual selected object
FOR nextSelected: REF ANY ← GGScene.NextEntity[selectedGen], GGScene.NextEntity[selectedGen] UNTIL nextSelected=NIL DO
sBox: BoundBox;
currentEntity: REF ANY;
WITH nextSelected SELECT FROM
outlineD: OutlineDescriptor => {
sBox ← outlineD.slice.class.getBoundBox[outlineD.slice, outlineD.parts];
currentEntity ← outlineD.slice;
};
sliceD: SliceDescriptor => {
sBox ← sliceD.slice.class.getBoundBox[sliceD.slice, sliceD.parts];
currentEntity ← sliceD.slice;
};
ENDCASE => ERROR;
inner loop on each individual scene object
sceneGen ← GGScene.TopLevelEntitiesInScene[ggData.scene];
FOR nextEntity: REF ANY ← GGScene.NextEntity[sceneGen], GGScene.NextEntity[sceneGen] UNTIL nextEntity=NIL DO
IF nextEntity=currentEntity THEN LOOP;
IF NOT GGSelect.IsSelectedInFull[nextEntity, ggData.scene, normal] THEN -- don't reprocess
WITH nextEntity SELECT FROM
outline: Outline => {
IF Within[outline.class.getBoundBox[outline, NIL], sBox] THEN {
GGSelect.SelectEntireSlice[outline, ggData.scene, normal];
};
};
slice: Slice => {
IF Within[slice.class.getBoundBox[slice, NIL], sBox] THEN {
GGSelect.SelectSlice[slice.class.newParts[slice, NIL, topLevel], ggData.scene, normal ];
};
};
ENDCASE => ERROR;
ENDLOOP;
ENDLOOP;
IF paint THEN GGWindow.RestoreScreenAndInvariants[paintAction: $SelectionChanged, ggData: ggData, remake: none, backgndOK: TRUE, edited: FALSE, okToClearFeedback: TRUE];
}; -- end AreaSelectAux
AreaSelectAux: PROC [ggData: GGData, new: BOOLTRUE, paint: BOOLTRUE] = {
Within: PROC [test, bound: GGBoundBox.BoundBox] RETURNS [BOOL] = {
RETURN [ test.hiX <= bound.hiX AND test.loX >= bound.loX AND test.hiY <= bound.hiY AND test.loY >= bound.loY ];
};
selectedGen: SliceDescriptorGenerator;
sceneGen: SliceGenerator;
IF GGSelect.NoSelections[ggData.scene, normal] THEN RETURN;
selectedGen ← GGSelect.SelectedSlices[ggData.scene, normal];
IF new THEN GGSelect.DeselectAll[ggData.scene, normal]; -- get rid of old selection
outer loop on each individual selected object
FOR sliceD: SliceDescriptor ← GGSelect.NextSliceDescriptor[selectedGen], GGSelect.NextSliceDescriptor[selectedGen] UNTIL sliceD=NIL DO
sBox: BoundBox ← sliceD.slice.class.getBoundBox[sliceD.slice, sliceD.parts];
inner loop on each individual scene object
sceneGen ← GGScene.TopLevelSlicesInScene[ggData.scene];
FOR next: Slice ← GGScene.NextSlice[sceneGen], GGScene.NextSlice[sceneGen] UNTIL next=NIL DO
IF next=sliceD.slice THEN LOOP;
IF NOT GGSelect.IsSelectedInFull[next, ggData.scene, normal] AND Within[next.class.getBoundBox[next, NIL], sBox] THEN
GGSelect.SelectEntireSlice[next, ggData.scene, normal];
ENDLOOP;
ENDLOOP;
IF paint THEN GGWindow.RestoreScreenAndInvariants[paintAction: $SelectionChanged, ggData: ggData, remake: none, backgndOK: TRUE, edited: FALSE, okToClearFeedback: TRUE];
}; -- end AreaSelectAux
Stuffing
StuffIt: PUBLIC PROC [clientData: REF ANY, event: LIST OF REF ANY] = {
StuffItAux[event, clientData, print];
};
StuffItScreen: PUBLIC PROC [clientData: REF ANY, event: LIST OF REF ANY] = {
StuffItAux[event, clientData, screen];
};
StuffItAux: PROC [event: LIST OF REF ANY, clientData: REF ANY, displayStyle: GGInterfaceTypes.DisplayStyle] = {
The bound box of the Gargoyle selection will be used as a clipping/translation box. The Gargoyle scene will be clipped to the box, translated so the box lower left corner is at [0.0, 0.0], and stuffed.
DoStuff: PROC [context: Imager.Context] = {
DoIt: PROC = {
tempQuality: GGInterfaceTypes.QualityMode ← ggData.camera.quality;
tempStyle: GGInterfaceTypes.DisplayStyle ← ggData.camera.displayStyle;
ggData.camera.quality ← quality;
ggData.camera.displayStyle ← displayStyle;
context.TranslateT[t: [-bRect.x, -bRect.y]];
GGRefresh.InterpressEntireScene[context, ggData];
ggData.camera.quality ← tempQuality;
ggData.camera.displayStyle ← tempStyle;
};
Imager.DoSaveAll[context, DoIt];
};
ggData: GGData ← NARROW[clientData];
bRect: Imager.Rectangle ← GGBoundBox.RectangleFromBoundBox[GGBoundBox.BoundBoxOfSelected[ggData.scene, normal] ];
IF GGSelect.NoSelections[ggData.scene, normal] THEN {
Feedback.AppendHerald[ggData.feedback, "Select some objects for stuffing", oneLiner];
Feedback.Blink[ggData.feedback];
}
ELSE {
ImagerArtwork.PasteArtwork[action: DoStuff, bounds: [0.0, 0.0, bRect.w, bRect.h], m: ImagerArtwork.Points[], clip: TRUE];
};
};
Refresh: PUBLIC PROC [clientData: REF ANY, event: LIST OF REF ANY] = {
ggData: GGData ← NARROW[clientData];
doNotClear: BOOL ← event#NIL AND event.rest#NIL AND event.rest.first=$DoNotClearFeedback;
GGWindow.RestoreScreenAndInvariants[paintAction: $PaintEntireScene, ggData: ggData, remake: none, backgndOK: FALSE, edited: FALSE, okToClearFeedback: NOT doNotClear];
};
OpenTypescript: PUBLIC PROC [clientData: REF ANY, event: LIST OF REF ANY] = {
ggData: GGData ← NARROW[clientData];
[] ← Feedback.OpenTypescript["Gargoyle Typescript", $Gargoyle, 120];
};
Help: PUBLIC PROC [clientData: REF ANY, event: LIST OF REF ANY] = {
ggData: GGData ← NARROW[clientData];
category: ATOMNARROW[event.rest.first];
openHeight: INTEGER ← 140;
help: ViewerClasses.Viewer;
name: Rope.ROPE;
SELECT category FROM
$MouseActions => {name ← "GGHelp.Tioga"; openHeight ← 140};
$Fonts => {name ← "GGFontSampler.Tioga"; openHeight ← 210};
ENDCASE => {name ← "GargoyleDoc.Tioga"; openHeight ← 115};
IF (help ← ViewerOps.FindViewer[FS.ExpandName[name, ggData.originalWDir].fullFName])#NIL THEN { -- viewer already exists
IF help.column#right THEN ViewerOps.ChangeColumn[help, right];
}
ELSE {
help ← ViewerOps.CreateViewer[flavor: $Text, info: [iconic: TRUE, column: right, openHeight: openHeight], paint: FALSE];
TiogaMenuOps.Load[viewer: help, fileName: Rope.Concat[ggData.originalWDir, name]];
};
ViewerOps.SetOpenHeight[viewer: help, clientHeight: openHeight];
ViewerOps.OpenIcon[icon: help, bottom: FALSE, paint: FALSE]; -- must do Open before Top
ViewerOps.TopViewer[viewer: help, paint: FALSE];
ViewerOps.ComputeColumn[right]; -- repaint right column
};
Snapshots
IPSnapShot: PUBLIC PROC [clientData: REF ANY, event: LIST OF REF ANY] = {
ggData: GGData ← NARROW[clientData];
This command is executed in the middle of a dragging operation to make an interpress master of the current state of the screen. Luckily, all dragging operations use the same painting commands, namely:
GGWindow.RestoreScreenAndInvariants[paintAction: $DuringMotion, ggData: ggData, remake: none, backgndOK: TRUE, edited: FALSE, okToClearFeedback: FALSE];
We wish to have the same effect as these commands except written to an interpress master. We will create an interpress master named snapshot.ip and use GGRefresh.SnapShot to draw the scene into it.
DoMakeInterpress: PROC [dc: Imager.Context] = {
Imager.ScaleT[dc, pixelsPerMeter];
tempStyle ← ggData.camera.displayStyle;
ggData.camera.displayStyle ← print;
GGRefresh.SnapShot[dc, ggData];
ggData.camera.displayStyle ← tempStyle;
};
tempStyle: GGInterfaceTypes.DisplayStyle;
ipRef: ImagerInterpress.Ref;
fullName: Rope.ROPE;
success: BOOL;
pixelsPerMeter: REAL = 0.0254/72.0;
startTime: BasicTime.GMT;
endTime: BasicTime.GMT;
totalTime: INT;
msgRope: Rope.ROPE;
[fullName, success] ← GGUtility.GetInterpressFileName["snapshot.ip", 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];
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];
};
Miscellaneous
ReloadTipTable: PUBLIC PROC [clientData: REF ANY, event: LIST OF REF ANY] = {
ggData: GGData ← NARROW[clientData];
newTable: TIPUser.TIPTable;
actionArea: ViewerClasses.Viewer;
bad: BOOLFALSE;
tableName, msg: Rope.ROPE;
Feedback.Append[ggData.feedback, "Reloading tip table...", begin];
tableName ← Rope.Concat[ggData.originalWDir, "Gargoyle.TIP"];
newTable ← TIPUser.InstantiateNewTIPTable[tableName
! FS.Error => {
bad ← TRUE;
msg ← Rope.Concat["Cannot read TIP table file: ", tableName];
CONTINUE};
TIPUser.InvalidTable => {
bad ← TRUE;
msg ← Rope.Concat["Error(s) saved on TIP.Errors for: ", tableName];
CONTINUE}];
IF bad THEN {Feedback.Append[ggData.feedback, msg, oneLiner]; RETURN};
Feedback.Append[ggData.feedback, "Done.", end];
IF newTable = NIL THEN ERROR;
actionArea ← ggData.actionArea;
actionArea.tipTable ← newTable;
};
tryIncrementalUpdate: BOOLTRUE;
SawTextFinish: PUBLIC PROC [clientData: REF ANY, event: LIST OF REF ANY] = {
ggData: GGData ← NARROW[clientData];
slice: Slice ← ggData.refresh.textInProgress;
CodeTimer.StartInt[$SawTextFinish, $Gargoyle];
IF slice#NIL AND Rope.Length[GGSlice.GetText[slice: slice]]=0 THEN { -- backspaced to nothing
GGSelect.DeselectEntityAllClasses[slice, ggData.scene];
GGScene.DeleteSlice[ggData.scene, slice];
};
IF ggData.refresh.textInProgress#NIL THEN { -- fix up alignment triggers
IF tryIncrementalUpdate THEN GGAlign.UpdateBagsForNewSlices[LIST[slice], ggData] -- the text is not necessarily new, but I believe this will work anyway. Bier, April 20, 1987
ELSE GGWindow.RestoreScreenAndInvariants[paintAction: $NewAlignmentsSelected, ggData: ggData, remake: triggerBag, backgndOK: FALSE, edited: TRUE, okToClearFeedback: FALSE];
ggData.refresh.textInProgress ← NIL; -- terminates typed input
};
The following has nothing to do with text. We take advantage of the fact that every mouse "Start" operation calls SawTextFinish, so we implicitly complete any FollowColorTool operation here.
IF ggData.refresh.areaFollowColorTool THEN {
GGEvent.AreaColorFromColorTool[clientData, event];
ColorTool.RemoveProc[$GG, ViewerOps.FindViewer["ColorTool"]];
ggData.refresh.areaFollowColorTool ← FALSE;
};
IF ggData.refresh.lineFollowColorTool THEN {
GGEvent.LineColorFromColorTool[clientData, event];
ColorTool.RemoveProc[$GG, ViewerOps.FindViewer["ColorTool"]];
ggData.refresh.lineFollowColorTool ← FALSE;
};
CodeTimer.StopInt[$SawTextFinish, $Gargoyle];
};
PrintRope: PUBLIC PROC [clientData: REF ANY, event: LIST OF REF ANY] = {
ggData: GGData ← NARROW[clientData];
rope: Rope.ROPENARROW[event.rest.first];
Feedback.Append[feedback: ggData.feedback, msg: rope, msgType: oneLiner]
};
InitStats: PROC [] = {
interval: CodeTimer.Interval;
interval ← CodeTimer.CreateInterval[$AddChar];
CodeTimer.AddInt[interval, $Gargoyle];
interval ← CodeTimer.CreateInterval[$SawTextFinish];
CodeTimer.AddInt[interval, $Gargoyle];
};
InitStats[];
END.