GGEventImplF.mesa
Contents: Once an event reaches the front of the slack-process queue, it is dispatched to one of the procedures in this module.
Copyright Ó 1988, 1989 by Xerox Corporation. All rights reserved.
Bier, May 21, 1992 2:01 pm PDT
Pier, April 28, 1992 5:14 pm PDT
Doug Wyatt, December 15, 1989 8:13:30 pm PST
DIRECTORY
Ascii, Atom, BasicTime, CodeTimer, CubicSplines, Feedback, FeedbackTypes, FS, GGAlign, GGBasicTypes, GGBoundBox, GGBuiltinShapes, GGCaret, GGControlPanelTypes, GGCoreOps, GGCoreTypes, GGDescribe, GGEvent, GGEventExtras, GGFileIn, GGFileOps, GGFileOut, GGHistory, GGHistoryTypes, GGInterfaceTypes, GGModelTypes, GGMouseEvent, GGMultiGravity, GGOutline, GGParent, GGParseIn, GGParseOut, GGRefresh, GGRefreshTypes, GGScene, GGSegment, GGSegmentTypes, GGSelect, GGSequence, GGSessionLog, GGShapes, GGSlice, GGSliceOps, GGState, GGTraj, GGTransform, GGUserInput, GGUserProfile, GGUIUtility, GGUtility, GGWindow, Imager, ImagerArtwork, ImagerInterpress, ImagerTransformation, Interpress, IO, Lines2d, PBasics, Real, RealFns, Rope, SlackProcess, TiogaOps, TiogaOpsDefs, Vectors2d, ViewerClasses, ViewerTools;
GGEventImplF: CEDAR PROGRAM
IMPORTS BasicTime, CodeTimer, Feedback, FS, GGAlign, GGBoundBox, GGBuiltinShapes, GGCaret, GGEvent, GGFileIn, GGFileOps, GGFileOut, GGHistory, GGMouseEvent, GGMultiGravity, GGOutline, GGParent, GGParseIn, GGParseOut, GGRefresh, GGScene, GGSegment, GGSelect, GGSessionLog, GGShapes, GGSlice, GGSliceOps, GGState, GGTraj, GGTransform, GGUserInput, GGUIUtility, GGUtility, GGWindow, Imager, ImagerArtwork, IO, Lines2d, PBasics, Rope, SlackProcess, TiogaOps, Vectors2d, ViewerTools
EXPORTS GGEvent, GGEventExtras, GGHistoryTypes, GGInterfaceTypes = BEGIN
BoundBox: TYPE = GGModelTypes.BoundBox;
Color: TYPE = Imager.Color;
ControlsObj: PUBLIC TYPE = GGControlPanelTypes.ControlsObj; -- exported to GGInterfaceTypes
DisplayStyle: TYPE = GGModelTypes.DisplayStyle;
FeatureCycler: TYPE = GGInterfaceTypes.FeatureCycler;
FeatureData: TYPE = GGModelTypes.FeatureData;
MsgRouter: TYPE = FeedbackTypes.MsgRouter;
GGData: TYPE = GGInterfaceTypes.GGData;
HistoryEvent: TYPE = GGHistoryTypes.HistoryEvent;
HistoryTool: TYPE = REF HistoryToolObj;
HistoryToolObj: PUBLIC TYPE = GGHistory.HistoryToolObj; -- exported to GGHistoryTypes
Orientation: TYPE = GGModelTypes.Orientation;
Point: TYPE = GGBasicTypes.Point;
RefreshDataObj: PUBLIC TYPE = GGRefreshTypes.RefreshDataObj;
Scene: TYPE = GGModelTypes.Scene;
Segment: TYPE = GGSegmentTypes.Segment;
SelectionClass: TYPE = GGModelTypes.SelectionClass;
Sequence: TYPE = GGModelTypes.Sequence;
SequenceOfReal: TYPE = GGCoreTypes.SequenceOfReal;
Slice: TYPE = GGModelTypes.Slice;
SliceDescriptor: TYPE = GGModelTypes.SliceDescriptor;
StrokeEnd: TYPE = Imager.StrokeEnd;
StrokeJoint: TYPE = Imager.StrokeJoint;
Traj: TYPE = GGModelTypes.Traj;
TrajData: TYPE = GGModelTypes.TrajData;
TrajEnd: TYPE = GGModelTypes.TrajEnd;
TrajParts: TYPE = GGModelTypes.TrajParts;
Transformation: TYPE = Imager.Transformation;
UserInputProc: TYPE = GGEvent.UserInputProc;
Vector: TYPE = GGBasicTypes.Vector;
Viewer: TYPE = ViewerClasses.Viewer;
WalkProc: TYPE = GGModelTypes.WalkProc;
borderWidth: REAL ← 2.0;
reallyBigReal: REAL = 1.0e37;
Script Menu
About scripting: GGWindowImpl.CreateWindow calls SlackProcess.Create each time a window is created, passing GGSessionLog.EnterAction as a parameter. SlackProcess will start calling this proc if EnableSessionLogging is called. ggData.debug.logStream is set by GGSessionLog.OpenScript. It is an IO.STREAM representing the currently open script for this ggData (there can be only one per ggData).
About automatic scripting: GGWindowImpl.CreateWindow calls GGEventImplD.OpenAutoScript to make the first script. After that, GGEventImplD.NotNewVersion closes the last script and opens a new one by calling GGEventImplD.OpenAutoScript. The list of scripts that made the current picture is pruned back to one element by GGEventImplD.ClearAux whenever a Clear, Get, or Restore is performed. GGEventImplD.OpenAutoScript then updates this list.
ScriptAction: UserInputProc = {
atom: ATOMNARROW[event.rest.first];
SELECT atom FROM
$Open => OpenScript[ggData, event];
$Append => AppendToScript[ggData, event];
$Close => CloseScript[ggData, event];
$Playback => PlaybackScript[ggData, event];
$FastPlay => FastPlayScript[ggData, event];
ENDCASE => ERROR
};
ShowScripts: UserInputProc = {
stream: IO.STREAMIO.ROS[];
GGParseOut.WriteListOfRope[stream, ggData.debug.autoScriptNames.list];
Feedback.Append[ggData.router, oneLiner, $Show, IO.RopeFromROS[stream]];
};
OpenScript: PROC [ggData: GGData, event: LIST OF REF ANY] = {
name: Rope.ROPE ← ViewerTools.GetSelectionContents[];
[ggData.debug.writeScriptStream, ggData.debug.writeScriptName] ← GGSessionLog.OpenScript[name, ggData, ggData.debug.writeScriptStream, ggData.debug.writeScriptName];
[] ← SlackProcess.EnableSessionLogging[ggData.slackHandle];
};
AppendToScript: PROC [ggData: GGData, event: LIST OF REF ANY] = {
name: Rope.ROPE ← ViewerTools.GetSelectionContents[];
[ggData.debug.writeScriptStream, ggData.debug.writeScriptName] ← GGSessionLog.AppendScript[name, ggData, ggData.debug.writeScriptStream, ggData.debug.writeScriptName];
[] ← SlackProcess.EnableSessionLogging[ggData.slackHandle];
};
CloseScript: PROC [ggData: GGData, event: LIST OF REF ANY] = {
[] ← SlackProcess.DisableSessionLogging[ggData.slackHandle];
[ggData.debug.writeScriptStream, ggData.debug.writeScriptName] ← GGSessionLog.CloseScript[ggData.debug.writeScriptStream, ggData.debug.writeScriptName, ggData.router];
};
PlaybackScript: PROC [ggData: GGData, event: LIST OF REF ANY] = {
name: Rope.ROPE ← ViewerTools.GetSelectionContents[];
GGSessionLog.PlaybackFromFile[name, ggData];
};
FastPlayScript: PROC [ggData: GGData, event: LIST OF REF ANY] = {
name: Rope.ROPE ← ViewerTools.GetSelectionContents[];
ggData.refresh.suppressRefresh ← TRUE;
GGUserInput.EventNotify[ggData, LIST[$DisableRefresh]]; -- more kosher
GGSessionLog.PlaybackFromFile[name, ggData];
GGUserInput.EventNotify[ggData, LIST[$EnableRefresh]];
GGUserInput.EventNotify[ggData, LIST[$Refresh]];
};
Stuff Menu
MergeFromGargoyle: UserInputProc = {
Get Scene from InputFocus' Viewer
theirData: GGData ← GGState.GetGGInputFocus[];
IF theirData = NIL THEN Feedback.Append[ggData.router, oneLiner, $Complaint, "MergeFromGargoyle failed: place input focus in source Gargoyle viewer"]
ELSE {
newSlices: LIST OF Slice;
theirScene: Scene ← theirData.scene;
GGHistory.NewCapture["Merge from Gargoyle", ggData]; -- capture before update
newSlices ← GGScene.CopySelectedParts[fromScene: theirScene, toScene: ggData.scene];
IF newSlices#NIL THEN {
GGAlign.UpdateBagsForNewSlices[newSlices, ggData];
ggData.refresh.startBoundBox ← GGScene.BoundBoxOfSelected[ggData.scene, normal];
GGRefresh.SetStartBox[ggData: ggData, dragInProgress: FALSE, selectedParts: TRUE];
GGHistory.PushCurrent[ggData];
Feedback.Append[ggData.router, oneLiner, $Feedback, "Merge from Gargoyle: completed"];
GGWindow.RestoreScreenAndInvariants[paintAction: $ObjectChangedBoundBoxProvided, ggData: ggData, remake: sceneBag, edited: TRUE, okToSkipCapture: FALSE];
};
};
};
StuffIt: PUBLIC UserInputProc = {
IF GGSelect.NoSelections[ggData.scene, normal] THEN Feedback.Append[ggData.router, oneLiner, $Complaint, "Stuff failed: select some objects for stuffing"]
ELSE {
The lower left corner of the bound box of the Gargoyle selection will be used as origin . The Gargoyle scene will be translated so the box lower left corner is at [0.0, 0.0]. The selected objects will be stuffed.
op: ATOMNARROW[event.first];
fileName, fullName: Rope.ROPE;
tempQuality: GGInterfaceTypes.QualityMode ← ggData.camera.quality;
tempStyle: GGInterfaceTypes.DisplayStyle ← ggData.camera.displayStyle;
displayStyle: GGInterfaceTypes.DisplayStyle ← ggData.camera.displayStyle;
toFile, ipOnly, bordered, fit: BOOLFALSE;
success, versionSpecified, noName: BOOLFALSE;
ipOnly ← SELECT op FROM
$IPToTiogaAlt, $IPToTioga, $IPToTiogaBordered, $IPToTiogaFit, $IPToTiogaBorderedAndFit => TRUE,
ENDCASE => FALSE;
displayStyle ← SELECT op FROM
$StuffToTiogaAlt, $StuffToFileAlt, $IPToTiogaAlt => screen,
ENDCASE => print;
toFile ← SELECT op FROM
$StuffToFile, $StuffToFileAlt => TRUE,
ENDCASE => FALSE;
bordered ← SELECT op FROM
$StuffToTiogaBordered, $StuffToTiogaBorderedAndFit, $IPToTiogaBordered, $IPToTiogaBorderedAndFit => TRUE,
ENDCASE => FALSE;
fit ← SELECT op FROM
$StuffToTiogaFit, $StuffToTiogaBorderedAndFit, $IPToTiogaFit, $IPToTiogaBorderedAndFit => TRUE,
ENDCASE => FALSE;
IF toFile THEN { -- get a filename, build a scene
[fileName, fullName, success, versionSpecified, noName] ← GGEvent.FileNameFromEvent["StuffToFile", event.rest, ggData.currentWDir, ggData.router];
IF NOT success THEN GOTO Abort
ELSE {
startTime: BasicTime.GMT;
totalTime: INT;
fileScene: Scene ← GGScene.CreateScene[];
f: IO.STREAMFS.StreamOpen[fullName, $create ! FS.Error, IO.Error => GOTO Abort;];
Feedback.PutF[ggData.router, begin, $Statistics, "Stuffing to file %g", [rope[fullName]] ];
startTime ← BasicTime.Now[];
FOR slice: LIST OF Slice ← GGUtility.OrderedSelectionList[ggData, decr], slice.rest UNTIL slice=NIL DO
GGScene.AddSlice[fileScene, slice.first];
ENDLOOP;
GGFileOut.FileoutSceneAndOptions[f, ggData, fullName, fileScene];
GGUIUtility.SafeClose[f, ggData.router];
totalTime ← BasicTime.Period[startTime, BasicTime.Now[]];
Feedback.PutF[ggData.router, end, $Statistics, " Done in time (%r)", [integer[totalTime]] ];
GGEvent.SawTextFinish[ggData, NIL];
};
EXITS
Abort => Feedback.PutF[ggData.router, oneLiner, $Complaint, IF noName THEN "StuffToFile failed: no file name selected" ELSE "StuffToFile failed: could not open %g", [rope[fileName]] ];
}
ELSE {
DoStuff: PROC [context: Imager.Context] = {
ENABLE UNWIND => {
ggData.camera.quality ← tempQuality;
ggData.camera.displayStyle ← tempStyle;
};
DoStuffIt: PROC = {
ggData.camera.quality ← quality; -- the enumerated type value "quality"
ggData.camera.displayStyle ← displayStyle;
context.TranslateT[t: [-bRect.x, -bRect.y]];
FOR slice: LIST OF Slice ← GGUtility.OrderedSelectionList[ggData, decr], slice.rest UNTIL slice=NIL DO
GGSliceOps.DrawParts[slice.first, NIL, context, ggData.camera, FALSE];
ENDLOOP;
IF bordered THEN {
context.SetStrokeWidth[borderWidth];
context.SetGray[1];
GGShapes.DrawBoundBox[context, bBox];
};
ggData.camera.quality ← tempQuality;
ggData.camera.displayStyle ← tempStyle;
};
Imager.DoSave[context, DoStuffIt];
};
bBox: BoundBox ← GGScene.BoundBoxOfSelected[ggData.scene, normal, TRUE];
bRect: Imager.Rectangle ← GGBoundBox.RectangleFromBoundBox[bBox];
selViewer: Viewer;
selLevel: TiogaOps.SelectionGrain;
selStart: TiogaOpsDefs.Location;
pendingDelete: BOOLFALSE;
IF NOT PBasics.IsBound[LOOPHOLE[ImagerArtwork.PasteArtwork]] THEN {
Feedback.Append[ggData.router, oneLiner, $Complaint, "Stuff failed: install Artwork, then retry Stuff operation"];
RETURN;
};
[viewer: selViewer, start: selStart, level: selLevel, pendingDelete: pendingDelete] ← TiogaOps.GetSelection[primary];
IF selViewer=NIL OR (selLevel#node AND selLevel#branch) THEN {
Feedback.Append[ggData.router, oneLiner, $Complaint, "Stuff failed: stuffing requires a node level Tioga selection (pending delete for replace)"];
RETURN;
};
fullName ← IF GGState.GetFullName[ggData]#NIL THEN GGFileOps.GetGargoyleFileName["Stuff", GGState.GetFullName[ggData], ggData.currentWDir, ggData.router, FALSE].fullName ELSE "Gargoyle"; -- should check success here
Feedback.PutF[ggData.router, begin, $Feedback, "Stuffing from file %g. ", [rope[fullName]] ];
IF NOT pendingDelete THEN {
create a new node and immediately replace it with the Stuff
TiogaOps.Break[];
[viewer: selViewer, start: selStart] ← TiogaOps.GetSelection[primary];
TiogaOps.SetSelection[selViewer, selStart, selStart, node, TRUE, TRUE, primary];
};
ImagerArtwork.PasteArtwork[action: DoStuff, bounds: [0.0, 0.0, bRect.w, bRect.h], m: ImagerArtwork.Points[], clip: FALSE, fit: fit, caption: FALSE];
Now create a Gargoyle scene and hang it on the just created Artwork node.
IF NOT ipOnly THEN {
fileRope: Rope.ROPE;
viewer: Viewer;
stuffedLoc: TiogaOpsDefs.Location;
fileScene: Scene ← GGScene.CreateScene[];
rs: IO.STREAMIO.ROS[];
[viewer, stuffedLoc] ← TiogaOps.GetSelection[primary];
IF stuffedLoc.node#NIL THEN {
FOR slice: LIST OF Slice ← GGUtility.OrderedSelectionList[ggData, decr], slice.rest UNTIL slice=NIL DO
GGScene.AddSlice[fileScene, slice.first];
ENDLOOP;
GGFileOut.FileoutSceneAndOptions[rs, ggData, IO.PutFR["stuffed from %g at %g", [rope[GGFileOps.FNameToGName[fullName]]], IO.time[] ], fileScene ];
fileRope ← IO.RopeFromROS[self: rs, close: TRUE];
TiogaOps.PutProp[n: stuffedLoc.node, name: $GGFile, value: fileRope];
};
};
Feedback.Append[ggData.router, end, $Feedback, "Stuffing complete"];
};
};
};
Grab: PUBLIC UserInputProc = {
really Get/Grab/Merge from Tioga
ggFileProp: Rope.ROPE;
selectedLoc: TiogaOpsDefs.Location ← TiogaOps.GetSelection[primary].start;
ggFileProp ← NARROW[TiogaOps.GetProp[selectedLoc.node, $GGFile]];
IF ggFileProp#NIL THEN {
success: BOOLFALSE;
sceneName: Rope.ROPE;
f: IO.STREAM;
IF event.first=$GetFromTioga THEN GGEvent.Clear[ggData, LIST[$Clear]]; -- full clear
Feedback.Append[ggData.router, begin, $Feedback, "Grabbing node ... "];
f ← IO.RIS[ggFileProp];
[success, sceneName] ← GGFileIn.FileinSceneOnly[f, ggData.scene, TRUE, ggData.camera];
IF success THEN {
GGEvent.SawTextFinish[ggData, LIST[$SawTextFinish]];
Feedback.Append[ggData.router, end, $Feedback, sceneName];
GGWindow.RestoreScreenAndInvariants[paintAction: $PaintEntireScene, ggData: ggData, remake: triggerBag, edited: TRUE, okToSkipCapture: FALSE];
}
ELSE Feedback.Append[ggData.router, oneLiner, $Complaint, "FromTioga failed: malformed Gargoyle property on selected node"];
GGUIUtility.SafeClose[f];
}
ELSE Feedback.Append[ggData.router, oneLiner, $Complaint, "FromTioga failed: no Gargoyle property on selected Tioga node"];
};
Overlap Operations
Top: PUBLIC UserInputProc = {
IF GGSelect.NoSelections[ggData.scene, normal] THEN Feedback.Append[ggData.router, oneLiner, $Complaint, "Front failed: select some objects for moving to front"]
ELSE {
selected: LIST OF Slice;
GGHistory.NewCapture["Top", ggData]; -- capture scene BEFORE UPDATE
selected ← GGUtility.OrderedSelectionList[ggData, decr];
FOR slices: LIST OF Slice ← selected, slices.rest UNTIL slices=NIL DO
GGEvent.SelectEntireSlice[slices.first, ggData.scene, normal, ggData];
GGScene.PutAtPriority[ggData.scene, slices.first, -1];
ENDLOOP;
GGHistory.PushCurrent[ggData]; -- push captured history event onto history list
Feedback.Append[ggData.router, oneLiner, $Feedback, "Front: selected objects moved to the front"];
GGWindow.RestoreScreenAndInvariants[paintAction: $ObjectChangedInPlace, ggData: ggData, remake: none, edited: TRUE, okToSkipCapture: FALSE];
};
};
ShowPriorityValue: PUBLIC UserInputProc = {
sliceD: SliceDescriptor;
scene: Scene ← ggData.scene;
count: CARD ← GGScene.CountSelectedSlices[scene, first, normal];
IF count#1 THEN Feedback.Append[ggData.router, oneLiner, $Complaint, "ShowPriority failed: select exactly one slice to show priority"]
ELSE {
sliceD ← GGScene.FirstSelectedSlice[scene, first, normal];
Feedback.PutF[ggData.router, oneLiner, $Show, "ShowPriority: %g", [integer[GGScene.GetPriority[scene, sliceD.slice]]] ];
};
};
Bottom: PUBLIC UserInputProc = {
IF GGSelect.NoSelections[ggData.scene, normal] THEN Feedback.Append[ggData.router, oneLiner, $Complaint, "Back failed: select some objects for moving to back"]
ELSE {
selected: LIST OF Slice;
GGHistory.NewCapture["Bottom", ggData]; -- capture scene BEFORE UPDATE
selected ← GGUtility.OrderedSelectionList[ggData, incr];
FOR slices: LIST OF Slice ← selected, slices.rest UNTIL slices=NIL DO
GGEvent.SelectEntireSlice[slices.first, ggData.scene, normal, ggData];
GGScene.PutAtPriority[ggData.scene, slices.first, 0];
ENDLOOP;
GGHistory.PushCurrent[ggData]; -- push captured history event onto history list
Feedback.PutF[ggData.router, oneLiner, $Feedback, "Back: selected objects moved to the back"];
GGWindow.RestoreScreenAndInvariants[paintAction: $ObjectChangedInPlace, ggData: ggData, remake: none, edited: TRUE, okToSkipCapture: FALSE];
};
};
UpOne: PUBLIC UserInputProc = {
IF GGSelect.NoSelections[ggData.scene, normal] THEN Feedback.Append[ggData.router, oneLiner, $Complaint, "ForwardOne failed: select some objects for moving forward"]
ELSE {
selected: LIST OF Slice;
GGHistory.NewCapture["Up one", ggData]; -- capture scene BEFORE UPDATE
selected ← GGUtility.OrderedSelectionList[ggData, incr];
FOR slices: LIST OF Slice ← selected, slices.rest UNTIL slices=NIL DO
GGEvent.SelectEntireSlice[slices.first, ggData.scene, normal, ggData];
GGScene.UpOne[ggData.scene, slices.first];
ENDLOOP;
GGHistory.PushCurrent[ggData]; -- push captured history event onto history list
Feedback.PutF[ggData.router, oneLiner, $Feedback, "ForwardOne: selected objects moved one slice toward the front"];
GGWindow.RestoreScreenAndInvariants[paintAction: $ObjectChangedInPlace, ggData: ggData, remake: none, edited: TRUE, okToSkipCapture: FALSE];
};
};
DownOne: PUBLIC UserInputProc = {
IF GGSelect.NoSelections[ggData.scene, normal] THEN Feedback.Append[ggData.router, oneLiner, $Complaint, "BackOne failed: select some objects for moving backwards"]
ELSE {
selected: LIST OF Slice;
GGHistory.NewCapture["Down one", ggData]; -- capture scene BEFORE UPDATE
selected ← GGUtility.OrderedSelectionList[ggData, decr];
FOR slices: LIST OF Slice ← selected, slices.rest UNTIL slices=NIL DO
GGEvent.SelectEntireSlice[slices.first, ggData.scene, normal, ggData];
GGScene.DownOne[ggData.scene, slices.first];
ENDLOOP;
GGHistory.PushCurrent[ggData]; -- push captured history event onto history list
Feedback.PutF[ggData.router, oneLiner, $Feedback, "BackOne: selected objects moved one slice toward the back"];
GGWindow.RestoreScreenAndInvariants[paintAction: $ObjectChangedInPlace, ggData: ggData, remake: none, edited: TRUE, okToSkipCapture: FALSE];
};
};
FindPriorityFromSelection: PUBLIC UserInputProc = {
priority: INTNARROW[event.rest.first, REF INT]^;
IF priority=LAST[INT] OR priority<0 THEN Feedback.Append[ggData.router, oneLiner, $Complaint, "MatchPriority failed: select a reasonable priority number"]
ELSE {
slice: Slice ← GGScene.GetAtPriority[ggData.scene, priority];
IF slice#NIL THEN {
GGRefresh.SetStartBox[ggData: ggData, dragInProgress: FALSE, selectedCPs: TRUE];
GGSelect.DeselectAll[scene: ggData.scene, selectClass: normal];
GGEvent.SelectEntireSlice[slice, ggData.scene, normal, ggData];
Feedback.PutF[ggData.router, oneLiner, $Feedback, "MatchPriority: object at priority %g selected", [integer[slice.priority]] ];
GGWindow.RestoreScreenAndInvariants[paintAction: $SelectionChanged, ggData: ggData, remake: none, edited: FALSE, okToSkipCapture: FALSE];
};
};
};
PutInFront: PUBLIC UserInputProc = {
scene: Scene ← ggData.scene;
count: CARD ← GGScene.CountSelectedSlices[scene, first, normal];
IF count<2 THEN
Feedback.Append[ggData.router, oneLiner, $Complaint, "PutInFront failed: select at least two slices for put in front"]
ELSE {
lastDesc: SliceDescriptor ← GGScene.LastSelectedSlice[scene, first, normal];
IF lastDesc#NIL THEN {
selected: LIST OF Slice;
atSlice: Slice ← lastDesc.slice;
GGHistory.NewCapture["Put in front", ggData]; -- capture scene BEFORE UPDATE
GGSelect.DeselectEntireSlice[atSlice, scene, normal];
selected ← GGUtility.OrderedSelectionList[ggData, decr, TRUE];
[] ← GGScene.DeleteAllSelected[scene];
GGScene.PutInFront[scene: scene, slice: atSlice, slices: selected];
GGEvent.SelectEntireSlice[atSlice, scene, normal, ggData];
GGHistory.PushCurrent[ggData]; -- push captured history event onto history list
Feedback.Append[ggData.router, oneLiner, $Feedback, "PutInFront: first selected objects moved in front of last selected object"];
GGWindow.RestoreScreenAndInvariants[paintAction: $ObjectChangedInPlace, ggData: ggData, remake: none, edited: TRUE, okToSkipCapture: FALSE];
};
};
};
PutBehind: PUBLIC UserInputProc = {
scene: Scene ← ggData.scene;
count: CARD ← GGScene.CountSelectedSlices[scene, first, normal];
IF count<2 THEN
Feedback.Append[ggData.router, oneLiner, $Complaint, "PutBehind failed: select at least two slices for put behind"]
ELSE {
lastDesc: SliceDescriptor ← GGScene.LastSelectedSlice[scene, first, normal];
IF lastDesc#NIL THEN {
selected: LIST OF Slice;
atSlice: Slice ← lastDesc.slice;
GGHistory.NewCapture["Put behind", ggData]; -- capture scene BEFORE UPDATE
GGSelect.DeselectEntireSlice[atSlice, scene, normal];
selected ← GGUtility.OrderedSelectionList[ggData, decr, TRUE];
[] ← GGScene.DeleteAllSelected[scene];
GGScene.PutBehind[scene: scene, slice: atSlice, slices: selected];
GGEvent.SelectEntireSlice[atSlice, scene, normal, ggData];
GGHistory.PushCurrent[ggData]; -- push captured history event onto history list
Feedback.Append[ggData.router, oneLiner, $Feedback, "PutBehind: first selected objects moved behind last selected object"];
GGWindow.RestoreScreenAndInvariants[paintAction: $ObjectChangedInPlace, ggData: ggData, remake: none, edited: TRUE, okToSkipCapture: FALSE];
};
};
};
Exchange: PUBLIC UserInputProc = {
sliceD1, sliceD2: SliceDescriptor;
index: NAT ← 0;
aborted: BOOLFALSE;
scene: Scene ← ggData.scene;
FindFirstTwo: PROC [sliceD: SliceDescriptor] RETURNS [done: BOOLFALSE] = {
IF index = 0 THEN sliceD1 ← sliceD
ELSE IF index = 1 THEN sliceD2 ← sliceD
ELSE done ← TRUE; -- there are too many selections
index ← index + 1;
};
aborted ← GGScene.WalkSelectedSlices[scene, first, FindFirstTwo, normal];
IF index#2 THEN Feedback.Append[ggData.router, oneLiner, $Complaint, "Exchange failed: select exactly two top level slices for Exchange"]
ELSE {
priority1: INT ← GGScene.GetPriority[scene: ggData.scene, slice: sliceD1.slice];
priority2: INT ← GGScene.GetPriority[scene: ggData.scene, slice: sliceD2.slice];
GGHistory.NewCapture["Exchange priorities", ggData]; -- capture scene BEFORE UPDATE
GGEvent.SelectEntireSlice[sliceD1.slice, ggData.scene, normal, ggData];
GGEvent.SelectEntireSlice[sliceD2.slice, ggData.scene, normal, ggData];
GGScene.RemoveSlice[ggData.scene, sliceD1.slice];
GGScene.RemoveSlice[ggData.scene, sliceD2.slice];
IF priority2>=priority1 THEN {
GGScene.AddSlice[ggData.scene, sliceD2.slice, priority1]; -- first insert lower slice
GGScene.AddSlice[ggData.scene, sliceD1.slice, priority2]; -- then insert upper slice
}
ELSE {
GGScene.AddSlice[ggData.scene, sliceD1.slice, priority2]; -- first insert lower slice
GGScene.AddSlice[ggData.scene, sliceD2.slice, priority1]; -- then insert upper slice
};
GGHistory.PushCurrent[ggData]; -- push captured history event onto history list
Feedback.Append[ggData.router, oneLiner, $Feedback, "Exchange: selected objects exchanged"];
GGWindow.RestoreScreenAndInvariants[paintAction: $ObjectChangedInPlace, ggData: ggData, remake: none, edited: TRUE, okToSkipCapture: FALSE];
};
};
UpFromSelection: PUBLIC UserInputProc = {
deltaPriority: INTNARROW[event.rest.first, REF INT]^;
IF deltaPriority=LAST[INT] OR deltaPriority<=0 THEN Feedback.Append[ggData.router, oneLiner, $Complaint, "ForwardFromSelection failed: select a positive integer for moving forward"]
ELSE {
selected: LIST OF Slice ← GGUtility.OrderedSelectionList[ggData, incr];
GGHistory.NewCapture["Up from selection", ggData]; -- capture scene BEFORE UPDATE
THROUGH [0..deltaPriority) DO
FOR slices: LIST OF Slice ← selected, slices.rest UNTIL slices=NIL DO
GGEvent.SelectEntireSlice[slices.first, ggData.scene, normal, ggData];
GGScene.UpOne[ggData.scene, slices.first];
ENDLOOP;
ENDLOOP;
GGHistory.PushCurrent[ggData]; -- push captured history event onto history list
Feedback.PutF[ggData.router, oneLiner, $Feedback, "ForwardFromSelection: selected objects moved toward the front %g slice%g", [integer[deltaPriority]], [character[IF deltaPriority>1 THEN 's ELSE ' ]] ];
GGWindow.RestoreScreenAndInvariants[paintAction: $ObjectChangedInPlace, ggData: ggData, remake: none, edited: TRUE, okToSkipCapture: FALSE];
};
};
DownFromSelection: PUBLIC UserInputProc = {
deltaPriority: INTNARROW[event.rest.first, REF INT]^;
IF deltaPriority=LAST[INT] OR deltaPriority<=0 THEN Feedback.Append[ggData.router, oneLiner, $Complaint, "BackFromSelection failed: select a positive integer for moving backward"]
ELSE {
selected: LIST OF Slice ← GGUtility.OrderedSelectionList[ggData, decr];
GGHistory.NewCapture["Down from selection", ggData]; -- capture scene BEFORE UPDATE
THROUGH [0..deltaPriority) DO
FOR slices: LIST OF Slice ← selected, slices.rest UNTIL slices=NIL DO
GGEvent.SelectEntireSlice[slices.first, ggData.scene, normal, ggData];
GGScene.DownOne[ggData.scene, slices.first];
ENDLOOP;
ENDLOOP;
GGHistory.PushCurrent[ggData]; -- push captured history event onto history list
Feedback.PutF[ggData.router, oneLiner, $Feedback, "BackFromSelection: selected objects moved toward the back %g slice%g", [integer[deltaPriority]], [character[IF deltaPriority>1 THEN 's ELSE ' ]] ];
GGWindow.RestoreScreenAndInvariants[paintAction: $ObjectChangedInPlace, ggData: ggData, remake: none, edited: TRUE, okToSkipCapture: FALSE];
};
};
PutAtSelection: PUBLIC UserInputProc = {
newPriority: INTNARROW[event.rest.first, REF INT]^;
IF newPriority=LAST[INT] THEN Feedback.Append[ggData.router, oneLiner, $Complaint, "PutAtSelection failed: select an integer for PutAtSelection"]
ELSE SELECT TRUE FROM
newPriority <= -2 => Feedback.Append[ggData.router, oneLiner, $Complaint, "PutAtSelection failed: provide an integer > -2 for PutAtSelection"];
newPriority=-1 => Top[ggData, event];
newPriority=0 => Bottom[ggData, event];
ENDCASE => {
selected: LIST OF Slice ← GGUtility.OrderedSelectionList[ggData, decr, TRUE];
GGHistory.NewCapture["Put at selection", ggData]; -- capture scene BEFORE UPDATE
[] ← GGScene.DeleteAllSelected[ggData.scene];
GGScene.AddSlices[ggData.scene, selected, newPriority];
GGEvent.SelectEntireSlice[selected.first, ggData.scene, normal, ggData];
GGHistory.PushCurrent[ggData]; -- push captured history event onto history list
IF newPriority<=0 THEN Feedback.PutF[ggData.router, oneLiner, $Feedback, "PutAtSelection: selected objects moved to %g", IF newPriority=0 THEN [rope["back"]] ELSE [rope["front"]] ]
ELSE Feedback.PutF[ggData.router, oneLiner, $Feedback, "PutAtSelection: selected objects moved to priority %g", [integer[newPriority]] ];
GGWindow.RestoreScreenAndInvariants[paintAction: $ObjectChangedInPlace, ggData: ggData, remake: none, edited: TRUE, okToSkipCapture: FALSE];
};
};
Shape Menu Operations
EndNewObject: PROC [slice: Slice, ggData: GGData, object: Rope.ROPE] = {
GGHistory.NewCapture[Rope.Concat["Add new ", object], ggData]; -- capture scene BEFORE UPDATE
GGRefresh.SetStartBox[ggData: ggData, dragInProgress: FALSE, selectedCPs: TRUE];
GGScene.AddSlice[ggData.scene, slice, -1];
GGSelect.DeselectAll[ggData.scene, normal];
GGEvent.SelectEntireSlice[slice, ggData.scene, normal, ggData];
GGRefresh.EnlargeStartBox[ggData, GGScene.BoundBoxOfSelected[ggData.scene, normal], NIL];
ggData.refresh.addedObject ← slice;
GGHistory.PushCurrent[ggData]; -- push captured history event onto history list
Feedback.PutF[ggData.router, oneLiner, $Feedback, "New%g: new %g added to scene", [rope[object]], [rope[object]] ];
GGWindow.RestoreScreenAndInvariants[paintAction: $ObjectAdded, ggData: ggData, remake: sceneBag, edited: TRUE, okToSkipCapture: FALSE];
};
PolygonInCircle: UserInputProc = {
sideCount: INTNARROW[event.rest.first, REF INT]^;
exists: BOOL ← GGCaret.Exists[ggData.caret];
IF NOT exists THEN Feedback.Append[ggData.router, oneLiner, $Complaint, "NewPolygon failed: caret required for NewPolygon"]
ELSE IF sideCount<=0.0 OR sideCount=LAST[INT] THEN Feedback.Append[ggData.router, oneLiner, $Complaint, "NewPolygon failed: select a reasonable number of polygon sides"]
ELSE {
caretPoint: Point ← GGCaret.GetPoint[ggData.caret];
slice: Slice ← GGBuiltinShapes.PolygonInCircle[sideCount, caretPoint, ggData.hitTest.scaleUnit, ggData.defaults];
EndNewObject[slice, ggData, "Polygon"];
};
};
NewBox: UserInputProc = {
sideLength: REALNARROW[event.rest.first, REF REAL]^;
exists: BOOL ← GGCaret.Exists[ggData.caret];
IF NOT exists THEN Feedback.Append[ggData.router, oneLiner, $Complaint, "NewBox failed: caret required for NewBox"]
ELSE IF sideLength>reallyBigReal THEN Feedback.Append[ggData.router, oneLiner, $Complaint, "NewBox failed: select a reasonable side length for a new box"]
ELSE {
caretPoint: Point ← GGCaret.GetPoint[ggData.caret];
slice: Slice ← GGBuiltinShapes.Box[caretPoint, sideLength*ggData.hitTest.scaleUnit, ggData.defaults];
EndNewObject[slice, ggData, "Box"];
};
};
NewCircle: UserInputProc = {
radius: REALNARROW[event.rest.first, REF REAL]^;
exists: BOOL ← GGCaret.Exists[ggData.caret];
IF NOT exists THEN Feedback.Append[ggData.router, oneLiner, $Complaint, "NewCircle failed: caret required for NewCircle"]
ELSE IF radius>reallyBigReal THEN Feedback.Append[ggData.router, oneLiner, $Complaint, "NewCircle failed: select a reasonable radius for a new circle"]
ELSE {
caretPoint: Point ← GGCaret.GetPoint[ggData.caret];
slice: Slice ← GGBuiltinShapes.Circle[caretPoint, radius*ggData.hitTest.scaleUnit, ggData.defaults];
EndNewObject[slice, ggData, "Circle"];
};
};
NewKnotchedLine: UserInputProc = {
length: REALNARROW[event.rest.first, REF REAL]^;
segCount: INTNARROW[event.rest.rest.first, REF INT]^;
exists: BOOL ← GGCaret.Exists[ggData.caret];
IF NOT exists THEN Feedback.Append[ggData.router, oneLiner, $Complaint, "NewKnotchedLine failed: caret required for NewKnotchedLine"]
ELSE IF length>reallyBigReal OR segCount=LAST[INT] THEN Feedback.Append[ggData.router, oneLiner, $Complaint, "NewKnotchedLine failed: select reasonable length and count for a new knotched line"]
ELSE {
caretPoint: Point ← GGCaret.GetPoint[ggData.caret];
p1: Point ← Vectors2d.Add[caretPoint, [length*ggData.hitTest.scaleUnit, 0.0]];
slice: Slice ← GGBuiltinShapes.KnotchedLine[p0: caretPoint, p1: p1, segmentCount: segCount];
EndNewObject[slice, ggData, "Knotched line"];
};
};
NewArrow: UserInputProc = {
shaftLength: REALNARROW[event.rest.first, REF REAL]^;
barbLength: REALNARROW[event.rest.rest.first, REF REAL]^;
exists: BOOL ← GGCaret.Exists[ggData.caret];
IF NOT exists THEN Feedback.Append[ggData.router, oneLiner, $Complaint, "NewArrow failed: caret required for NewArrow"]
ELSE IF shaftLength>reallyBigReal OR barbLength>reallyBigReal THEN Feedback.Append[ggData.router, oneLiner, $Complaint, "NewArrow failed: select reasonable shaft and barb lengths for a new arrow"]
ELSE {
OPEN Vectors2d;
slice: Slice;
traj: Traj;
seg: Segment;
success: BOOLFALSE;
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;
slice ← GGOutline.CreateOutline[child: traj, fillColor: Imager.black];
EndNewObject[slice, ggData, "Arrow"];
};
};
Frame: UserInputProc = {
add a "picture frame" of a specified size to the image, origin at [frameXOffset, frameYOffset]
frameWidth, frameHeight, frameXOffset, frameYOffset: REAL ← 0.0;
box: GGBoundBox.BoundBox;
sliceD: SliceDescriptor;
eventList: LIST OF REF ← event.rest;
frameWidth ← NARROW[eventList.first, REF REAL]^; -- in Gargoyle units (points)
eventList ← eventList.rest;
frameHeight ← NARROW[eventList.first, REF REAL]^; -- in Gargoyle units (points)
eventList ← eventList.rest;
IF eventList#NIL THEN {
frameXOffset ← NARROW[eventList.first, REF REAL]^; -- in Gargoyle units (points)
eventList ← eventList.rest;
frameYOffset ← NARROW[eventList.first, REF REAL]^; -- in Gargoyle units (points)
};
IF frameWidth>reallyBigReal OR frameHeight>reallyBigReal OR frameXOffset>reallyBigReal OR frameYOffset>reallyBigReal THEN {
Feedback.Append[ggData.router, oneLiner, $Complaint, "NewFrame failed: select reasonable dimensions for frame"];
RETURN;
};
box ← GGBoundBox.CreateBoundBox[frameXOffset, frameYOffset, frameXOffset+frameWidth, frameYOffset+frameHeight];
sliceD ← GGSlice.MakeBoxSlice[box, none, GGTransform.Identity[]];
GGHistory.NewCapture["Add frame", ggData]; -- capture scene BEFORE UPDATE
GGScene.AddSlice[ggData.scene, sliceD.slice, -1];
ggData.refresh.startBoundBox^ ← GGSliceOps.GetBoundBox[sliceD.slice]^;
GGRefresh.NullStartBox[ggData];
GGRefresh.EnlargeStartBox[ggData, GGSliceOps.GetBoundBox[sliceD.slice], NIL];
ggData.refresh.addedObject ← sliceD.slice;
GGHistory.PushCurrent[ggData]; -- push captured history event onto history list
Feedback.Append[ggData.router, oneLiner, $Feedback, "NewFrame: New frame added to scene"];
GGWindow.RestoreScreenAndInvariants[paintAction: $ObjectAdded, ggData: ggData, remake: sceneBag, edited: TRUE, okToSkipCapture: FALSE];
};
SelectedBBox: UserInputProc = {
add the bounding box of the selected items to the scene
IF GGSelect.NoSelections[ggData.scene, normal] THEN Feedback.Append[ggData.router, oneLiner, $Complaint, "SelectedBBox failed: no selections"]
ELSE {
box: GGBoundBox.BoundBox ← GGScene.SelectionBoxOfSelected[ggData.scene, normal, TRUE];
sliceD: SliceDescriptor ← GGSlice.MakeBoxSlice[box, none, GGTransform.Identity[]];
GGHistory.NewCapture["Add bound box of selected objects", ggData]; -- capture scene BEFORE UPDATE
GGScene.AddSlice[ggData.scene, sliceD.slice, -1];
ggData.refresh.startBoundBox^ ← GGSliceOps.GetBoundBox[sliceD.slice]^;
GGRefresh.NullStartBox[ggData];
GGRefresh.EnlargeStartBox[ggData, GGSliceOps.GetBoundBox[sliceD.slice], NIL];
ggData.refresh.addedObject ← sliceD.slice;
GGHistory.PushCurrent[ggData]; -- push captured history event onto history list
Feedback.Append[ggData.router, oneLiner, $Feedback, "SelectedBBox: bound box of selected objects added"];
GGWindow.RestoreScreenAndInvariants[paintAction: $ObjectAdded, ggData: ggData, remake: sceneBag, edited: TRUE, okToSkipCapture: FALSE];
};
};
Style Operations
LineWidth: PUBLIC UserInputProc = {
strokeWidth: REALWITH event.rest.first SELECT FROM
real: REF REAL => real^,
int: REF INT => REAL[int^],
ENDCASE => -1.0;
SetStrokeWidth[ggData, strokeWidth];
};
SetStrokeWidth: PUBLIC PROC [ggData: GGData, strokeWidth: REAL] = {
IF GGSelect.NoSelections[ggData.scene, normal] THEN Feedback.Append[ggData.router, oneLiner, $Complaint, "SetStrokeWidth failed: select some strokes for set stroke width"]
ELSE IF strokeWidth>reallyBigReal OR strokeWidth=LAST[INT] OR strokeWidth < 0.0 THEN Feedback.Append[ggData.router, oneLiner, $Complaint, "SetStrokeWidth failed: select a reasonable positive number for stroke width"]
ELSE {
DoLineWidth: PROC [sliceD: SliceDescriptor] RETURNS [done: BOOLFALSE] = {
thisBox ← GGSliceOps.SetStrokeWidth[sliceD.slice, sliceD.parts, strokeWidth, currentEvent];
IF thisBox#NIL THEN bBoxes ← CONS[thisBox, bBoxes];
};
bBoxes: LIST OF BoundBox;
thisBox: BoundBox;
bBox: BoundBox ← GGScene.BoundBoxOfSelected[ggData.scene, normal]; -- remember box before walking slices
currentEvent: HistoryEvent ← GGHistory.NewCurrent["Set stroke width", ggData];
GGRefresh.SetStartBox[ggData: ggData, dragInProgress: FALSE, selectedParts: TRUE];
[] ← GGScene.WalkSelectedSlices[ggData.scene, leaf, DoLineWidth, normal];
ggData.refresh.startBoundBox^ ← GGBoundBox.BoundBoxOfBoxes[bBoxes]^;
GGBoundBox.EnlargeByBox[ggData.refresh.startBoundBox, bBox];
GGRefresh.EnlargeStartBox[ggData, GGBoundBox.BoundBoxOfBoxes[bBoxes], NIL];
GGHistory.PushCurrent[ggData];
Feedback.PutF[ggData.router, oneLiner, $Feedback, "SetStrokeWidth: selected objects have stroke width %g", [real[strokeWidth]]];
GGWindow.RestoreScreenAndInvariants[paintAction: $ObjectChangedBoundBoxProvided, ggData: ggData, remake: none, edited: TRUE, okToSkipCapture: FALSE];
};
};
LineEnds: PUBLIC UserInputProc = {
strokeEnd: StrokeEnd;
argRope: Rope.ROPENARROW[event.rest.first];
SELECT TRUE FROM
Rope.Equal[argRope, "square", FALSE] => strokeEnd ← square;
Rope.Equal[argRope, "butt", FALSE] => strokeEnd ← butt;
Rope.Equal[argRope, "round", FALSE] => strokeEnd ← round;
ENDCASE => {
Feedback.Append[ggData.router, oneLiner, $Complaint, "EndFromSelection failed: select square, butt, or round for set stroke ends"];
RETURN;
};
SetStrokeEnd[ggData, strokeEnd];
};
SetStrokeEnd: PUBLIC PROC [ggData: GGData, strokeEnd: Imager.StrokeEnd] = {
IF GGSelect.NoSelections[ggData.scene, normal] THEN Feedback.Append[ggData.router, oneLiner, $Complaint, "SetStrokeEnd failed: select some objects for set stroke ends"]
ELSE {
DoLineEnds: PROC [sliceD: SliceDescriptor] RETURNS [done: BOOLFALSE] = {
GGSliceOps.SetStrokeEnd[sliceD.slice, sliceD.parts, strokeEnd, currentEvent];
};
currentEvent: HistoryEvent;
bBox: BoundBox ← GGScene.BoundBoxOfSelected[ggData.scene, normal];
GGRefresh.SetStartBox[ggData: ggData, dragInProgress: FALSE, selectedParts: TRUE];
currentEvent ← GGHistory.NewCurrent["Set stroke end", ggData];
[] ← GGScene.WalkSelectedSlices[ggData.scene, leaf, DoLineEnds, normal];
GGBoundBox.EnlargeByBox[bBox: bBox, by: GGScene.BoundBoxOfSelected[ggData.scene, normal]];
ggData.refresh.startBoundBox^ ← bBox^;
GGRefresh.EnlargeStartBox[ggData, GGScene.BoundBoxOfSelected[ggData.scene, normal], NIL];
GGHistory.PushCurrent[ggData];
Feedback.PutF[ggData.router, oneLiner, $Feedback, "Selected objects have stroke end %g", [rope[GetEndRope[strokeEnd]]]];
GGWindow.RestoreScreenAndInvariants[paintAction: $ObjectChangedBoundBoxProvided, ggData: ggData, remake: none, edited: TRUE, okToSkipCapture: FALSE];
};
};
TrajJoints: PUBLIC UserInputProc = {
IF GGSelect.NoSelections[ggData.scene, normal] THEN Feedback.Append[ggData.router, oneLiner, $Complaint, "JointFromSelection failed: select some objects for set stroke joints"]
ELSE {
DoTrajJoints: PROC [sliceD: SliceDescriptor] RETURNS [done: BOOLFALSE] = {
GGSliceOps.SetStrokeJoint[sliceD.slice, sliceD.parts, strokeJoint, currentEvent];
};
currentEvent: HistoryEvent;
bBox: BoundBox;
strokeJoint: StrokeJoint;
argRope: Rope.ROPENARROW[event.rest.first];
SELECT TRUE FROM
Rope.Equal[argRope, "round", FALSE] => strokeJoint ← round;
Rope.Equal[argRope, "miter", FALSE] => strokeJoint ← miter;
Rope.Equal[argRope, "bevel", FALSE] => strokeJoint ← bevel;
ENDCASE => {
Feedback.Append[ggData.router, oneLiner, $Complaint, "JointFromSelection failed: select round, miter, or bevel for trajectory joints"];
RETURN;
};
bBox ← GGScene.BoundBoxOfSelected[ggData.scene, normal];
GGRefresh.SetStartBox[ggData: ggData, dragInProgress: FALSE, selectedParts: TRUE];
currentEvent ← GGHistory.NewCurrent["Set stroke joint", ggData];
[] ← GGScene.WalkSelectedSlices[ggData.scene, leaf, DoTrajJoints, normal];
GGBoundBox.EnlargeByBox[bBox: bBox, by: GGScene.BoundBoxOfSelected[ggData.scene, normal]];
ggData.refresh.startBoundBox^ ← bBox^;
GGRefresh.EnlargeStartBox[ggData, GGScene.BoundBoxOfSelected[ggData.scene, normal], NIL];
GGHistory.PushCurrent[ggData];
Feedback.PutF[ggData.router, oneLiner, $Feedback, "JointFromSelection: selected trajectories have stroke joints %g", [rope[argRope]]];
GGWindow.RestoreScreenAndInvariants[paintAction: $ObjectChangedBoundBoxProvided, ggData: ggData, remake: none, edited: TRUE, okToSkipCapture: FALSE];
};
};
DashesFromSelection: PUBLIC UserInputProc = {
ENABLE GGParseIn.SyntaxError => GOTO SyntaxError;
pattern: SequenceOfReal;
offset, length: REAL;
allZeroes: BOOLTRUE;
argRope: Rope.ROPENARROW[event.rest.first];
argStream: IO.STREAMIO.RIS[argRope];
pattern ← GGParseIn.ReadArrayOfReal[argStream];
offset ← GGParseIn.ReadWReal[argStream];
length ← GGParseIn.ReadWReal[argStream];
FOR i: NAT IN [0..pattern.len) DO allZeroes ← allZeroes AND pattern[i]=0.0; ENDLOOP;
IF allZeroes THEN GOTO SyntaxError;
SetDashed[ggData, TRUE, pattern, offset, length];
EXITS
SyntaxError => Feedback.Append[ggData.router, oneLiner, $Complaint, "DashesFromSelection failed: select a legal specification with non-zero pattern (e.g. [4 10] 2 0)"];
};
SetDashed: PUBLIC PROC [ggData: GGData, dashed: BOOLFALSE, pattern: SequenceOfReal ← NIL, offset: REAL ← 0.0, length: REAL ← -1.0] = {
DoDashes: PROC [sliceD: SliceDescriptor] RETURNS [done: BOOLFALSE] = {
GGSliceOps.SetDashed[sliceD.slice, sliceD.parts, dashed, pattern, offset, length, currentEvent];
};
currentEvent: HistoryEvent;
IF GGSelect.NoSelections[ggData.scene, normal] THEN Feedback.Append[ggData.router, oneLiner, $Complaint, "DashesFromSelection failed: select some objects for set dashes"]
ELSE {
currentEvent ← GGHistory.NewCurrent[IF dashed THEN "Dashes on" ELSE "Dashes off", ggData];
ggData.refresh.startBoundBox^ ← GGScene.BoundBoxOfSelected[ggData.scene, normal]^;
GGRefresh.SetStartBox[ggData: ggData, dragInProgress: FALSE, selectedParts: TRUE];
[] ← GGScene.WalkSelectedSlices[ggData.scene, leaf, DoDashes, normal];
GGHistory.PushCurrent[ggData];
Feedback.PutF[ggData.router, oneLiner, $Feedback, "Selected objects have dashes %g", [rope[GetDashesRope[dashed, pattern, offset, length]]] ];
GGWindow.RestoreScreenAndInvariants[paintAction: $ObjectChangedBoundBoxProvided, ggData: ggData, remake: none, edited: TRUE, okToSkipCapture: FALSE];
};
};
DashesOff: PUBLIC UserInputProc = {
SetDashed[ggData, FALSE];
};
GetSelectedDashPattern: PROC [ggData: GGData] RETURNS [dashed: BOOLFALSE, pattern: SequenceOfReal, offset, length: REAL ← 0.0, success: BOOLTRUE] = {
DoCheckStrokeValues: PROC [sliceD: SliceDescriptor] RETURNS [done: BOOLFALSE] = {
thisDashed: BOOLFALSE;
thisOffset, thisLength: REAL;
thisPattern: SequenceOfReal;
isUnique: BOOLFALSE;
[thisDashed, thisPattern, thisOffset, thisLength, isUnique] ← GGSliceOps.GetDashed[sliceD.slice, sliceD.parts];
IF NOT isUnique THEN {
dashed ← thisDashed; offset ← thisOffset; length ← thisLength; pattern ← thisPattern;
RETURN[TRUE];
};
IF found
THEN {
done ←
thisDashed # dashed OR (thisDashed AND
(thisOffset # offset OR
thisLength # length OR
NOT GGUtility.EquivalentPatterns[thisPattern, pattern]));
}
ELSE {
found ← TRUE;
dashed ← thisDashed;
offset ← thisOffset;
length ← thisLength;
pattern ← thisPattern;
};
};
scene: Scene ← ggData.scene;
found: BOOLFALSE;
success ← NOT GGScene.WalkSelectedSlices[scene, first, DoCheckStrokeValues, normal];
};
GetSelectedStrokeWidth: PROC [ggData: GGData] RETURNS [strokeWidth: REAL ← 17.0, success: BOOLTRUE] = {
DoCheckWidth: PROC [sliceD: SliceDescriptor] RETURNS [done: BOOLFALSE] = {
thisWidth: REAL;
isUnique: BOOLFALSE;
[thisWidth, isUnique] ← GGSliceOps.GetStrokeWidth[sliceD.slice, sliceD.parts];
IF NOT isUnique THEN {
strokeWidth ← thisWidth;
RETURN[TRUE];
};
IF found
THEN done ← thisWidth # strokeWidth
ELSE {
found ← TRUE;
strokeWidth ← thisWidth;
};
};
scene: Scene ← ggData.scene;
found: BOOLFALSE;
success ← NOT GGScene.WalkSelectedSlices[scene, first, DoCheckWidth, normal];
};
GetSelectedStrokeEnd: PROC [ggData: GGData] RETURNS [strokeEnd: StrokeEnd ← square, success: BOOLTRUE] = {
DoCheckEnd: PROC [sliceD: SliceDescriptor] RETURNS [done: BOOLFALSE] = {
thisEnd: StrokeEnd;
isUnique: BOOLFALSE;
[thisEnd, isUnique] ← GGSliceOps.GetStrokeEnd[sliceD.slice, sliceD.parts];
IF NOT isUnique THEN {
strokeEnd ← thisEnd;
RETURN[TRUE];
};
IF found
THEN done ← thisEnd # strokeEnd
ELSE {
found ← TRUE;
strokeEnd ← thisEnd;
};
};
scene: Scene ← ggData.scene;
found: BOOLFALSE;
success ← NOT GGScene.WalkSelectedSlices[scene, first, DoCheckEnd, normal];
};
GetSelectedStrokeJoint: PROC [ggData: GGData] RETURNS [strokeJoint: StrokeJoint ← bevel, success: BOOLTRUE] = {
DoCheckJoint: PROC [sliceD: SliceDescriptor] RETURNS [done: BOOLFALSE] = {
thisJoint: StrokeJoint;
isUnique: BOOLFALSE;
[thisJoint, isUnique] ← GGSliceOps.GetStrokeJoint[sliceD.slice, sliceD.parts];
IF NOT isUnique THEN {
strokeJoint ← thisJoint;
RETURN[TRUE];
};
IF found
THEN done ← thisJoint # strokeJoint
ELSE {
found ← TRUE;
strokeJoint ← thisJoint;
};
};
scene: Scene ← ggData.scene;
found: BOOLFALSE;
success ← NOT GGScene.WalkSelectedSlices[scene, first, DoCheckJoint, normal];
};
PrintStrokeValues: PUBLIC UserInputProc = {
IF GGSelect.NoSelections[ggData.scene, normal] THEN Feedback.Append[ggData.router, oneLiner, $Complaint, "ShowStrokeValues failed: select at least one stroke for ShowStrokeValues"]
ELSE {
dashed: BOOLFALSE;
pattern: SequenceOfReal;
offset, length, strokeWidth: REAL;
strokeJoint: StrokeJoint;
strokeEnd: StrokeEnd;
success: BOOLTRUE;
[strokeWidth, success] ← GetSelectedStrokeWidth[ggData];
IF NOT success THEN GOTO NotUnique;
[strokeEnd, success] ← GetSelectedStrokeEnd[ggData];
IF NOT success THEN GOTO NotUnique;
[strokeJoint, success] ← GetSelectedStrokeJoint[ggData];
IF NOT success THEN GOTO NotUnique;
[dashed, pattern, offset, length, success] ← GetSelectedDashPattern[ggData];
IF NOT success THEN GOTO NotUnique;
Feedback.PutF[ggData.router, oneLiner, $Show, "Stroke values: width: %g end: %g joint: %g dashes: %g",
[real[strokeWidth]],
[rope[GetEndRope[strokeEnd]]],
[rope[GetJointRope[strokeJoint]]],
[rope[GetDashesRope[dashed, pattern, offset, length]]] ];
EXITS
NotUnique => Feedback.Append[ggData.router, oneLiner, $Complaint, "ShowStrokeValues failed: multiple stroke values are selected"];
};
};
CopyStrokeValues: PUBLIC UserInputProc = {
This code assumes that all of the select segment stroke values are consistent. If not, the copy is not done.
scene: Scene ← ggData.scene;
lastDesc: SliceDescriptor ← GGSelect.GetLastSelection[scene];
count: INT ← GGScene.CountSelectedSlices[scene, leaf, normal];
IF count<2 OR lastDesc=NIL THEN Feedback.Append[ggData.router, oneLiner, $Complaint, "CopyStrokeValues failed: select at least one destination object, then one source stroke"]
ELSE {
DoCopyStroke: PROC [sliceD: SliceDescriptor] RETURNS [done: BOOLFALSE] = {
IF sliceD#lastDesc THEN {
[] ← GGSliceOps.SetStrokeWidth[sliceD.slice, sliceD.parts, width, currentEvent];
GGSliceOps.SetStrokeEnd[sliceD.slice, sliceD.parts, end, currentEvent];
GGSliceOps.SetStrokeJoint[sliceD.slice, sliceD.parts, joint, currentEvent];
GGSliceOps.SetDashed[slice: sliceD.slice, parts: sliceD.parts, dashed: dashed, pattern: pattern, offset: offset, length: length, history: currentEvent];
};
};
dashed: BOOLFALSE;
pattern: SequenceOfReal;
offset, length, width: REAL;
end: StrokeEnd;
joint: StrokeJoint;
success: BOOLFALSE;
currentEvent: HistoryEvent;
[width, success] ← GGSliceOps.GetStrokeWidth[lastDesc.slice, lastDesc.parts];
IF NOT success THEN GOTO NotUnique;
[end, success] ← GGSliceOps.GetStrokeEnd[lastDesc.slice, lastDesc.parts];
IF NOT success THEN GOTO NotUnique;
[joint, success] ← GGSliceOps.GetStrokeJoint[lastDesc.slice, lastDesc.parts];
IF NOT success THEN GOTO NotUnique;
[dashed, pattern, offset, length, success] ← GGSliceOps.GetDashed[lastDesc.slice, lastDesc.parts];
IF NOT success THEN GOTO NotUnique;
currentEvent ← GGHistory.NewCurrent["Copy stroke values", ggData];
ggData.refresh.startBoundBox^ ← GGScene.BoundBoxOfSelected[scene, normal]^;
GGRefresh.SetStartBox[ggData: ggData, dragInProgress: FALSE, selectedParts: TRUE];
[] ← GGScene.WalkSelectedSlices[scene, leaf, DoCopyStroke, normal];
GGBoundBox.EnlargeByBox[ggData.refresh.startBoundBox, GGScene.BoundBoxOfSelected[scene, normal]];
GGRefresh.EnlargeStartBox[ggData, GGScene.BoundBoxOfSelected[ggData.scene, normal], NIL];
GGHistory.PushCurrent[ggData];
Feedback.PutF[ggData.router, oneLiner, $Feedback, "Copied: stroke width: %g end: %g joint: %g dashes: %g",
[real[width]],
[rope[GetEndRope[end]]],
[rope[GetJointRope[joint]]],
[rope[GetDashesRope[dashed, pattern, offset, length]]] ];
GGWindow.RestoreScreenAndInvariants[paintAction: $ObjectChangedBoundBoxProvided, ggData: ggData, remake: none, edited: TRUE, okToSkipCapture: FALSE];
EXITS
NotUnique => Feedback.Append[ggData.router, oneLiner, $Complaint, "CopyStrokeValues failed: there are multiple stroke values in the source (last selected) object."];
};
};
GetDashesRope: PROC [dashed: BOOL, pattern: SequenceOfReal, offset, length: REAL] RETURNS [r: Rope.ROPE] = {
s: IO.STREAM;
IF NOT dashed THEN RETURN["Not Dashed"];
s ← IO.ROS[];
s.PutChar[ '[ ]; -- open bracket
FOR index: NAT IN [0..pattern.len) DO -- list of reals
s.PutF["%g ", [real[pattern[index]]] ];
ENDLOOP;
s.PutF["] %g %g", [real[offset]], [real[length]] ];
r ← IO.RopeFromROS[s];
};
GetEndRope: PROC [strokeEnd: StrokeEnd] RETURNS [r: Rope.ROPE] = {
r ← SELECT strokeEnd FROM
round => "round",
butt => "butt",
square => "square",
ENDCASE => "none";
};
GetJointRope: PROC [jointEnd: StrokeJoint] RETURNS [r: Rope.ROPE] = {
r ← SELECT jointEnd FROM
round => "round",
bevel => "bevel",
miter => "miter",
ENDCASE => "none";
};
SelectMatchingWidth: PUBLIC UserInputProc = {
scene: Scene ← ggData.scene;
width: REALWITH event.rest.first SELECT FROM
real: REF REAL => real^,
int: REF INT =>REAL [int^],
ENDCASE => Real.LargestNumber;
IF width>reallyBigReal OR width=LAST[INT] OR width<0.0 THEN Feedback.Append[ggData.router, oneLiner, $Complaint, "MatchSelectedWidth failed: select a real number >=0.0 for matching width"]
ELSE {
epsilon: REAL = 1.0E-3;
DoSelectMatching: PROC [slice: Slice] RETURNS [done: BOOLFALSE] = {
WidthProc: WalkProc = {
WalkProc: TYPE = PROC [seg: Segment] RETURNS [keep: BOOL];
RETURN [seg.strokeWidth=width OR ABS[seg.strokeWidth-width]<epsilon];
};
sliceD: SliceDescriptor ← GGSliceOps.WalkSegments[slice, WidthProc]; -- get a descriptor of matching parts
GGSelect.SelectSlice[sliceD, scene, normal]; -- and select it
};
GGRefresh.SetStartBox[ggData: ggData, dragInProgress: FALSE, selectedCPs: TRUE];
GGSelect.DeselectAll[scene, normal];
[] ← GGScene.WalkSlices[scene, leaf, DoSelectMatching];
Feedback.PutF[ggData.router, oneLiner, $Feedback, "MatchSelectedWidth: strokes of width %g selected", [real[width]] ];
GGWindow.RestoreScreenAndInvariants[paintAction: $SelectionChanged, ggData: ggData, remake: none, edited: FALSE, okToSkipCapture: FALSE];
};
};
SelectMatchingDashes: PUBLIC UserInputProc = {
ENABLE GGParseIn.SyntaxError => GOTO SyntaxError;
DoSelectMatching: PROC [slice: Slice] RETURNS [done: BOOLFALSE] = {
DashProc: WalkProc = {
WalkProc: TYPE = PROC [seg: Segment] RETURNS [keep: BOOL];
keep ← FALSE;
IF seg.dashed AND seg.pattern.len=pattern.len AND seg.offset=offset AND seg.length=length THEN {
FOR index: NAT IN [0..pattern.len) DO -- list of reals
IF seg.pattern[index]#pattern[index] THEN GOTO ExitLoop;
ENDLOOP;
RETURN[TRUE]; -- if you get here, you matched exactly
EXITS
ExitLoop => NULL;
};
};
sliceD: SliceDescriptor ← GGSliceOps.WalkSegments[slice, DashProc]; -- get a descriptor of matching parts
GGSelect.SelectSlice[sliceD, ggData.scene, normal]; -- and select it
};
scene: Scene ← ggData.scene;
argRope: Rope.ROPENARROW[event.rest.first];
argStream: IO.STREAMIO.RIS[argRope];
dashed: BOOLFALSE;
pattern: SequenceOfReal;
offset, length: REAL;
pattern ← GGParseIn.ReadArrayOfReal[argStream];
offset ← GGParseIn.ReadWReal[argStream];
length ← GGParseIn.ReadWReal[argStream];
GGRefresh.SetStartBox[ggData: ggData, dragInProgress: FALSE, selectedCPs: TRUE];
GGSelect.DeselectAll[ggData.scene, normal];
[] ← GGScene.WalkSlices[ggData.scene, leaf, DoSelectMatching];
Feedback.PutF[ggData.router, oneLiner, $Feedback, "MatchSelectedDashes: strokes with dashes %g selected", [rope[argRope]] ];
GGWindow.RestoreScreenAndInvariants[paintAction: $SelectionChanged, ggData: ggData, remake: none, edited: FALSE, okToSkipCapture: FALSE];
EXITS
SyntaxError => Feedback.Append[ggData.router, oneLiner, $Complaint, "MatchSelectedDashes failed: select a legal dash pattern for matching dashes"];
};
SetDefaultStrokeValues: PUBLIC UserInputProc = {
IF GGSelect.NoSelections[ggData.scene, normal] THEN Feedback.Append[ggData.router, oneLiner, $Complaint, "SetDefaultStrokeValues failed: no stroke selected for new default values"]
ELSE {
dashed: BOOLFALSE;
pattern: SequenceOfReal;
offset, length, strokeWidth: REAL;
strokeJoint: StrokeJoint;
strokeEnd: StrokeEnd;
success: BOOLFALSE;
[dashed, pattern, offset, length, success] ← GetSelectedDashPattern[ggData];
IF NOT success THEN GOTO NotUnique;
[strokeWidth, success] ← GetSelectedStrokeWidth[ggData];
IF NOT success THEN GOTO NotUnique;
[strokeJoint, success] ← GetSelectedStrokeJoint[ggData];
IF NOT success THEN GOTO NotUnique;
[strokeEnd, success] ← GetSelectedStrokeEnd[ggData];
IF NOT success THEN GOTO NotUnique;
ggData.defaults^ ← [strokeWidth: strokeWidth, strokeJoint: strokeJoint, strokeEnd: strokeEnd, dashed: dashed, pattern: GGUtility.CopyPattern[pattern], offset: offset, length: length, strokeColor: ggData.defaults.strokeColor, fillColor: ggData.defaults.fillColor, font: ggData.defaults.font];
ShowDefaultStrokeValues[ggData, event];
EXITS
NotUnique => Feedback.Append[ggData.router, oneLiner, $Complaint, "SetDefaultStrokeValues failed: there are multiple stroke values in the source (last selected) object"];
};
};
ShowDefaultStrokeValues: PUBLIC UserInputProc = {
Feedback.PutF[ggData.router, oneLiner, $Show, "Default Stroke Values: width: %g end: %g joint: %g dashes: %g",
[real[ggData.defaults.strokeWidth]],
[rope[GetEndRope[ggData.defaults.strokeEnd]]],
[rope[GetJointRope[ggData.defaults.strokeJoint]]],
[rope[GetDashesRope[ggData.defaults.dashed, ggData.defaults.pattern, ggData.defaults.offset, ggData.defaults.length]]] ];
};
Arrows: PUBLIC UserInputProc = {
IF GGSelect.NoSelections[ggData.scene, normal] THEN Feedback.Append[ggData.router, oneLiner, $Complaint, "Arrows failed: select some objects for arrows"]
ELSE {
DoArrows: PROC [sliceD: SliceDescriptor] RETURNS [done: BOOLFALSE] = {
loIsLeft ← LoIsLeft[sliceD.slice];
loIsLeft: BOOLFALSE;
loPoint: Point ← GGTraj.FetchJointPos[sliceD.slice, 0];
hiPoint: Point ← GGTraj.LastJointPos[sliceD.slice];
loIsLeft ← SELECT TRUE FROM
loPoint.x < hiPoint.x => TRUE,
loPoint.x = hiPoint.x AND loPoint.y <= hiPoint.y => TRUE,
ENDCASE => FALSE;
IF loIsLeft THEN GGSliceOps.SetArrows[sliceD.slice, NIL, leftArrows, rightArrows, currentEvent]
ELSE GGSliceOps.SetArrows[sliceD.slice, NIL, rightArrows, leftArrows, currentEvent];
};
currentEvent: HistoryEvent;
leftArrows, rightArrows: BOOLFALSE;
arrowType: INTNARROW[event.rest.first, REF INT]^;
SELECT arrowType FROM
0 => {
Feedback.Append[ggData.router, oneLiner, $Feedback, "Selected objects will have no arrows."];
leftArrows ← FALSE;
rightArrows ← FALSE;
};
1 => {
Feedback.Append[ggData.router, oneLiner, $Feedback, "Selected objects will have left/down arrows."];
leftArrows ← TRUE;
rightArrows ← FALSE;
};
2 => {
Feedback.Append[ggData.router, oneLiner, $Feedback, "Selected objects will have right/up arrows."];
leftArrows ← FALSE;
rightArrows ← TRUE;
};
3 => {
Feedback.Append[ggData.router, oneLiner, $Feedback, "Selected objects will have arrows on both ends."];
leftArrows ← TRUE;
rightArrows ← TRUE;
};
ENDCASE => {
Feedback.Append[ggData.router, oneLiner, $Feedback, "Arrows failed: illegal argument to Arrows."];
RETURN;
};
currentEvent ← GGHistory.NewCurrent["Set arrows", ggData];
[] ← GGScene.WalkSelectedSlices[ggData.scene, leaf, DoArrows, normal, $Traj];
GGHistory.PushCurrent[ggData];
Feedback.Append[ggData.router, oneLiner, $Feedback, "Arrows: completed"];
GGWindow.RestoreScreenAndInvariants[paintAction: $ObjectChangedInPlace, ggData: ggData, remake: none, edited: TRUE, okToSkipCapture: FALSE];
};
};
Area Selection Operations
SelectAll: UserInputProc = {
CodeTimer.StartInt[$SelectAll, $Gargoyle];
GGSelect.SelectAll[ggData.scene, normal];
GGEvent.SawTextFinish[ggData, NIL];
GGRefresh.NullStartBox[ggData];
GGWindow.RestoreScreenAndInvariants[paintAction: $SelectionChanged, ggData: ggData, remake: none, edited: FALSE, okToSkipCapture: FALSE];
CodeTimer.StopInt[$SelectAll, $Gargoyle];
Feedback.Append[ggData.router, oneLiner, $Feedback, "Select All: selected everything"];
};
DeselectAll: UserInputProc = {
CodeTimer.StartInt[$DeselectAll, $Gargoyle];
GGSelect.DeselectAll[ggData.scene, normal];
GGEvent.SawTextFinish[ggData, NIL];
GGRefresh.NullStartBox[ggData];
GGWindow.RestoreScreenAndInvariants[paintAction: $SelectionChanged, ggData: ggData, remake: none, edited: FALSE, okToSkipCapture: FALSE];
CodeTimer.StopInt[$DeselectAll, $Gargoyle];
Feedback.Append[ggData.router, oneLiner, $Feedback, "Deselect All: deselected everything"];
};
CycleSelection: UserInputProc = {
point: Point;
normal: Vector;
feature: FeatureData;
hitData: REF ANY;
featureCycler: FeatureCycler ← GGState.GetSelectionCycler[ggData];
direction: ATOMNARROW[event.rest.first];
IF featureCycler=NIL THEN {
Feedback.Append[ggData.router, oneLiner, $Complaint, "CycleSelection failed: no selection cycler available"];
RETURN;
};
SELECT direction FROM
$Forward => [point, normal, feature, hitData] ← GGMultiGravity.NextFeature[featureCycler];
$Backward => [point, normal, feature, hitData] ← GGMultiGravity.PreviousFeature[featureCycler];
ENDCASE => ERROR;
GGMouseEvent.SelectFromFeature[ggData, featureCycler.testPoint, point, normal, feature, hitData];
Feedback.PutF[ggData.router, oneLiner, $Feedback, "CycleSelection: selection cycled"]; -- not needed. GGMouseEvent.SelectFromFeature will print a router message.
};
MoveSelection: UserInputProc = {
action: ATOMNARROW[event.rest.first];
scene: Scene ← ggData.scene;
normalLast: SliceDescriptor ← GGSelect.GetLastSelection[scene];
newLast: SliceDescriptor;
opName: Rope.ROPESELECT action FROM
$Forward => "SelectForward",
$Backward => "SelectBackward",
$ShrinkForward => "ShrinkForward",
$ShrinkBackward => "ShrinkBackward",
$Grow => "Grow",
ENDCASE => ERROR;
BEGIN
IF normalLast = NIL THEN GOTO NoLastSelection;
newLast ← GGSliceOps.AlterParts[normalLast, action];
IF newLast=NIL THEN Feedback.Append[ggData.router, oneLiner, $Complaint, Rope.Concat[opName, " failed: can't walk any further in this direction"]]
ELSE {
GGRefresh.SetStartBox[ggData: ggData, dragInProgress: FALSE, selectedCPs: TRUE];
GGSelect.DeselectSlice[normalLast.slice, normalLast.parts, scene, normal];
GGSelect.SelectSlice[newLast, scene, normal];
Feedback.Append[ggData.router, oneLiner, $Feedback, Rope.Concat[opName, ": completed"]];
GGWindow.RestoreScreenAndInvariants[paintAction: $SelectionChanged, ggData: ggData, remake: none, edited: FALSE, okToSkipCapture: FALSE];
};
EXITS
NoLastSelection => {
Feedback.Append[ggData.router, oneLiner, $Complaint, Rope.Concat[opName, " failed: please make an intial selection"]];
};
END;
};
AreaSelectNew: UserInputProc = {
Select all objects within area(s) bounded by original selection(s) and deselect original selection
AreaSelectAux[ggData, TRUE, TRUE];
Feedback.Append[ggData.router, oneLiner, $Feedback, "AreaSelectNew: new area selected"];
};
AreaSelectNewAndDelete: PUBLIC UserInputProc = {
Select all objects within area(s) bounded by original selection(s) and delete original selection
scene: Scene ← ggData.scene;
IF GGSelect.NoSelections[scene, normal] THEN Feedback.Append[ggData.router, oneLiner, $Complaint, "AreaSelectNew failed: Select bounding objects for Area Select New And Delete"]
ELSE { -- there were some original selections
DoDelete: PROC [sliceD: SliceDescriptor] RETURNS [done: BOOLFALSE] = {
[newSelectList, ptr] ← GGUtility.AddSlice[sliceD.slice, newSelectList, ptr];
};
newSelectList, ptr: LIST OF Slice;
[newSelectList, ptr] ← GGUtility.StartSliceList[];
ggData.refresh.startBoundBox^ ← GGScene.BoundBoxOfSelected[scene, normal]^;
GGRefresh.SetStartBox[ggData: ggData, dragInProgress: FALSE, selectedParts: TRUE];
AreaSelectAux[ggData: ggData, new: TRUE, paint: FALSE];
[] ← GGScene.WalkSelectedSlices[scene, first, DoDelete, normal];
FOR list: LIST OF Slice ← newSelectList, list.rest UNTIL list = NIL DO
GGScene.DeleteSlice[ggData.scene, list.first];
ENDLOOP;
GGCaret.NoAttractor[ggData.caret];
GGCaret.SitOn[ggData.caret, NIL];
Feedback.Append[ggData.router, oneLiner, $Feedback, "AreaSelectNew: new area selected"];
GGWindow.RestoreScreenAndInvariants[paintAction: $ObjectChangedBoundBoxProvided, ggData: ggData, remake: triggerBag, edited: FALSE, okToSkipCapture: FALSE];
};
};
AreaSelectExtend: UserInputProc = {
Add all objects within area(s) bounded by original selection(s) to current selection
AreaSelectAux[ggData, FALSE, TRUE];
Feedback.Append[ggData.router, oneLiner, $Feedback, "AreaSelectExtend: new area extended"];
};
AreaSelectAux: PROC [ggData: GGData, new: BOOLTRUE, paint: BOOLTRUE] = {
For each selected object, see which scene objects are within its bounding box.
newSelectList, ptr: LIST OF Slice;
scene: Scene ← ggData.scene;
DoTestAllSlices: PROC [sliceD: SliceDescriptor] RETURNS [done: BOOLFALSE] = {
sBox: BoundBox ← GGSliceOps.GetTightBox[sliceD.slice, sliceD.parts];
DoTestSlice: PROC [next: Slice] RETURNS [done: BOOLFALSE] = {
IF next=sliceD.slice THEN RETURN;
IF NOT GGSelect.IsSelectedInFull[next, scene, normal] AND GGSliceOps.WithinBoundBox[next, sBox]
THEN [newSelectList, ptr] ← GGUtility.AddSlice[sliceD.slice, newSelectList, ptr];
};
[] ← GGScene.WalkSlices[scene, first, DoTestSlice];
};
IF GGSelect.NoSelections[scene, normal] THEN Feedback.Append[ggData.router, oneLiner, $Complaint, "Area select failed: select bounding objects for new area select"]
ELSE {
GGRefresh.SetStartBox[ggData: ggData, dragInProgress: FALSE, selectedCPs: TRUE];
[newSelectList, ptr] ← GGUtility.StartSliceList[];
[] ← GGScene.WalkSelectedSlices[scene, first, DoTestAllSlices, normal];
IF new THEN GGSelect.DeselectAll[scene, normal]; -- get rid of old selection
FOR list: LIST OF Slice ← newSelectList, list.rest UNTIL list = NIL DO
GGSelect.SelectEntireSlice[list.first, scene, normal];
ENDLOOP;
IF paint THEN GGWindow.RestoreScreenAndInvariants[paintAction: $SelectionChanged, ggData: ggData, remake: none, edited: FALSE, okToSkipCapture: FALSE];
};
}; -- end AreaSelectAux
SelectCoincident: UserInputProc = {
Select all segments having co-located endpoints.
DoCheckSegments: PROC [slice: Slice] RETURNS [done: BOOLFALSE] = {
CoincidentProc: PROC [seg: Segment, transform: ImagerTransformation.Transformation] RETURNS [keep: BOOL] = {
RETURN [seg.lo.x=seg.hi.x AND seg.lo.y=seg.hi.y]; -- what about transformation ??
};
sliceD: SliceDescriptor ← GGSliceOps.WalkSegments[slice, CoincidentProc];
IF sliceD#NIL AND NOT GGSliceOps.IsEmptyParts[sliceD] THEN {
GGSelect.SelectSlice[sliceD, ggData.scene, normal]; -- and select
count ← count + 1;
};
};
count: INT ← 0;
GGRefresh.SetStartBox[ggData: ggData, dragInProgress: FALSE, selectedCPs: TRUE];
GGSelect.DeselectAll[ggData.scene, normal];
[] ← GGScene.WalkSlices[ggData.scene, leaf, DoCheckSegments];
Feedback.PutF[ggData.router, oneLiner, $Feedback, "Coincident: %g coincident segments selected", [integer[count]] ];
GGWindow.RestoreScreenAndInvariants[paintAction: $SelectionChanged, ggData: ggData, remake: none, edited: FALSE, okToSkipCapture: FALSE];
};
SelectUnseeableSegs: UserInputProc = {
A segment is unseeable if it has stroke width=0 or stroke color=none.
DoCheckSegments: PROC [slice: Slice] RETURNS [done: BOOLFALSE] = {
type: ATOM ← GGSliceOps.GetType[slice];
IF type#$Text AND type#$IP THEN {
UnseeProc: WalkProc = {
RETURN [seg.strokeWidth<=0.0 OR seg.color=NIL];
};
sliceD: SliceDescriptor ← GGSliceOps.WalkSegments[slice, UnseeProc];
IF sliceD#NIL AND NOT GGSliceOps.IsEmptyParts[sliceD] THEN {
GGSelect.SelectSlice[sliceD, ggData.scene, normal]; -- and select
count ← count + 1;
};
};
};
count: INT ← 0;
GGRefresh.SetStartBox[ggData: ggData, dragInProgress: FALSE, selectedCPs: TRUE];
GGSelect.DeselectAll[ggData.scene, normal];
[] ← GGScene.WalkSlices[ggData.scene, leaf, DoCheckSegments];
Feedback.PutF[ggData.router, oneLiner, $Feedback, "UnSeeableSegs: %g unseeable segments selected", [integer[count]] ];
GGWindow.RestoreScreenAndInvariants[paintAction: $SelectionChanged, ggData: ggData, remake: none, edited: FALSE, okToSkipCapture: FALSE];
};
SelectUnseeableObjs: UserInputProc = {
Select all complete objects that are unseeable no matter what resolution, scale or overlap.
A segment is unseeable if it has stroke width=0 or stroke color = none.
An object is unseeable if it is whitespace-only text, if all of its segments are unseeable and it is an open object, if all of its segments are unseeable and it is a closed but unfilled object, or if all of its segments are unseeable and colinear.
DoCheckSegments: PROC [slice: Slice] RETURNS [done: BOOLFALSE] = {
UnseeProc: WalkProc = {
RETURN [seg.strokeWidth<=0.0 OR seg.color=NIL];
};
TallyUnseeableObjects: PROC [slice: Slice] RETURNS [visitChildren: BOOL, keep: BOOLFALSE, done: BOOLFALSE] = {
IsBlackHole: PROC [sliceD: SliceDescriptor] RETURNS [BOOL] = {
AllColinear: PROC [point: Point] RETURNS [done: BOOLTRUE] = {
IF p1.x=Real.LargestNumber THEN {p1 ← point; RETURN[FALSE];}; -- first point
IF p2.x=Real.LargestNumber THEN {p2 ← point; RETURN[FALSE];}; -- second point
IF line=NIL THEN line ← Lines2d.LineFromPoints[p1, p2]; -- third point
IF Lines2d.LineDistance[point, line]>epsilon THEN colinear ← FALSE
ELSE RETURN[FALSE];
};
p1, p2: Point ← [Real.LargestNumber, Real.LargestNumber];
line: Lines2d.Line ← NIL;
colinear: BOOLTRUE; -- important initialization to TRUE
GGSliceOps.WalkPointsInDescriptor[sliceD, AllColinear];
RETURN[colinear];
};
sliceD: SliceDescriptor;
IF GGParent.IsParent[slice] THEN RETURN[TRUE, FALSE, FALSE];
sliceD ← GGSliceOps.WalkSegments[slice, UnseeProc]; -- get a descriptor of unseeable segments
IF NOT GGSliceOps.IsCompleteParts[sliceD] THEN RETURN[FALSE, FALSE, FALSE];
All segments are unseeable
SELECT GGSliceOps.GetType[sliceD.slice] FROM
$Traj => {
Three cases: an open traj, a closed and unfilled traj, a closed and filled traj with all its segments colinear (a black hole)
color: Color;
success: BOOLFALSE;
[color, success] ← GGSliceOps.GetFillColor[GGParent.GetParent[sliceD.slice], NIL];
keep ← GGTraj.GetTrajRole[sliceD.slice]=open
OR (success AND color=NIL) -- evaluation order guarantees closed
OR IsBlackHole[sliceD];
};
$Box, $Circle => { -- Boxes, circles are closed
keep ← GGSliceOps.GetFillColor[sliceD.slice, NIL].color=NIL
OR IsBlackHole[sliceD];
};
$Text => {
keep ← GGSlice.IsWhitespace[sliceD.slice];
};
$IP => {
box: BoundBox ← GGSliceOps.GetTightBox[sliceD.slice];
keep ← ABS[box.hiX-box.loX]<epsilon AND ABS[box.hiY-box.loY]<epsilon;
};
ENDCASE => Feedback.Append[ggData.router, oneLiner, $Complaint, "SelectUnseeableObjects found an unknown object type"];
RETURN[FALSE, keep, FALSE];
};
tallyD: SliceDescriptor;
aborted: BOOLFALSE;
[tallyD, aborted] ← GGParent.TallyChildren[slice, TallyUnseeableObjects];
IF NOT aborted AND tallyD#NIL AND NOT GGSliceOps.IsEmptyParts[tallyD] THEN {
GGSelect.SelectSlice[tallyD, ggData.scene, normal];
count ← count + 1;
};
};
count: INT ← 0;
epsilon: REAL ← 1.0/300.0; -- one pixel on a 300 spi printer
GGRefresh.SetStartBox[ggData: ggData, dragInProgress: FALSE, selectedCPs: TRUE];
GGSelect.DeselectAll[ggData.scene, normal];
[] ← GGScene.WalkSlices[ggData.scene, all, DoCheckSegments];
Feedback.PutF[ggData.router, oneLiner, $Feedback, "UnSeeableObj: %g unseeable objects selected", [integer[count]] ];
GGWindow.RestoreScreenAndInvariants[paintAction: $SelectionChanged, ggData: ggData, remake: none, edited: FALSE, okToSkipCapture: FALSE];
};
RegisterEventProcs: PROC = {
OPEN GGUserInput;
Stroke Menu
RegisterAction[$LineWidth, LineWidth, refReal];
RegisterAction[$LineEnd, LineEnds, rope];
RegisterAction[$TrajJoints, TrajJoints, rope];
RegisterAction[$DashesFromSelection, DashesFromSelection, rope];
RegisterAction[$DashesOff, DashesOff, none];
RegisterAction[$PrintStrokeValues, PrintStrokeValues, none];
RegisterAction[$CopyStrokeValues, CopyStrokeValues, none];
RegisterAction[$SelectMatchingWidth, SelectMatchingWidth, refReal];
RegisterAction[$SelectMatchingDashes, SelectMatchingDashes, rope];
RegisterAction[$SetDefaultStrokeValues, SetDefaultStrokeValues, none];
RegisterAction[$ShowDefaultStrokeValues, ShowDefaultStrokeValues, none];
ArrowsMenu
RegisterAction[$Arrows, Arrows, none];
Shapes Menu
RegisterAction[$PolygonInCircle, PolygonInCircle, refInt];
RegisterAction[$KnotchedLine, NewKnotchedLine, none];
RegisterAction[$NewCircle, NewCircle, none];
RegisterAction[$NewBox, NewBox, none];
RegisterAction[$NewArrow, NewArrow, none];
RegisterAction[$Frame, Frame, none];
RegisterAction[$SelectedBBox, SelectedBBox, none];
Overlap Menu
RegisterAction[$Top, Top, none];
RegisterAction[$ShowPriorityValue, ShowPriorityValue, none];
RegisterAction[$Bottom, Bottom, none];
RegisterAction[$UpOne, UpOne, none];
RegisterAction[$FindPriorityFromSelection, FindPriorityFromSelection, refInt];
RegisterAction[$DownOne, DownOne, none];
RegisterAction[$PutInFront, PutInFront, none];
RegisterAction[$Exchange, Exchange, none];
RegisterAction[$PutBehind, PutBehind, none];
RegisterAction[$UpFromSelection, UpFromSelection, refInt];
RegisterAction[$PutAtSelection, PutAtSelection, refInt];
RegisterAction[$DownFromSelection, DownFromSelection, refInt];
Interpress Menu
RegisterAction[$IPToTioga, StuffIt, none];
RegisterAction[$IPToTiogaBordered, StuffIt, none];
RegisterAction[$IPToTiogaFit, StuffIt, none];
RegisterAction[$IPToTiogaBorderedAndFit, StuffIt, none];
RegisterAction[$IPToTiogaAlt, StuffIt, none];
Stuff Menu
RegisterAction[$MergeFromGargoyle, MergeFromGargoyle, none];
RegisterAction[$StuffToTioga, StuffIt, none];
RegisterAction[$StuffToTiogaAlt, StuffIt, none];
RegisterAction[$StuffToTiogaBordered, StuffIt, none];
RegisterAction[$StuffToTiogaFit, StuffIt, none];
RegisterAction[$StuffToTiogaBorderedAndFit, StuffIt, none];
RegisterAction[$StuffToFile, StuffIt, rope];
RegisterAction[$GetFromTioga, Grab, none];
RegisterAction[$GrabFromTioga, Grab, none];
RegisterAction[$MergeFromTioga, Grab, none];
Script Menu
RegisterAction[$ScriptAction, ScriptAction, none];
RegisterAction[$ShowScripts, ShowScripts, none];
Miscellaneous Keyboard only
RegisterAction[$EndOfSessionLogMessage, GGSessionLog.EndOfScriptMessage, none];
Select Menu
RegisterAction[$CycleSelection, CycleSelection, none];
RegisterAction[$MoveSelection, MoveSelection, none];
RegisterAction[$AreaSelectNew, AreaSelectNew, none];
RegisterAction[$AreaSelectNewAndDelete, AreaSelectNewAndDelete, none];
RegisterAction[$AreaSelectExtend, AreaSelectExtend, none];
RegisterAction[$SelectAll, SelectAll, none];
RegisterAction[$DeselectAll, DeselectAll, none];
RegisterAction[$AreaSelectDegenerate, AreaSelectDegenerate, none];
RegisterAction[$SelectCoincident, SelectCoincident, none];
RegisterAction[$SelectUnseeableSegs, SelectUnseeableSegs, none];
RegisterAction[$SelectUnseeableObjs, SelectUnseeableObjs, none];
};
RegisterEventProcs[];
END.