MatchViewerImpl.mesa
Contents: Builds the MatchTool viewer and implements the user interface functions for toggling buttons. Maintains the MatchTool user interface state. BuildControlPanel, BuildRightControlPanel, and RegisterMatchActions are interesting places to look.
Copyright Ó 1987 by Xerox Corporation. All rights reserved.
Kurlander, September 5, 1987 4:36:00 pm PDT
Bier, May 27, 1992 11:24 am PDT
Pier, April 17, 1990 1:17 pm PDT
Spreitze, May 7, 1990 12:01 pm PDT
DIRECTORY
AtomButtons, AtomButtonsTypes, BiScrollers, Buttons, Commander, Containers, Feedback, FeedbackOps, Geom2D, GGAlign, GGBasicTypes, GGBoundBox, GGCaret, GGControlPanelTypes, GGCoreOps, GGDragTypes, GGEmbedTypes, GGInterfaceTypes, GGModelTypes, GGMouseEvent, GGMultiGravity, GGOutline, GGRefresh, GGRefreshTypes, GGScene, GGSessionLog, GGShapes, GGSlice, GGState, GGStateExtras, GGStateTypes, GGUserInput, GGUserProfile, GGWindow, GList, GraphicsButton, Icons, Imager, ImagerTransformation, InputFocus, IO, Labels, Match, MatchGrep, MatchViewer, MJSContainers, Rope, SlackProcess, TIPUser, ViewerClasses, ViewerOps;
MatchViewerImpl: CEDAR PROGRAM
IMPORTS AtomButtons, BiScrollers, Buttons, Commander, Containers, Feedback, FeedbackOps, Geom2D, GGAlign, GGBoundBox, GGCaret, GGCoreOps, GGMouseEvent, GGMultiGravity, GGOutline, GGRefresh, GGScene, GGSessionLog, GGShapes, GGSlice, GGState, GGStateExtras, GGUserInput, GGUserProfile, GGWindow, GList, GraphicsButton, Icons, Imager, ImagerTransformation, InputFocus, IO, Labels, Match, MatchGrep, MJSContainers, Rope, SlackProcess, TIPUser, ViewerOps
EXPORTS MatchViewer, GGInterfaceTypes = BEGIN
ControlsObj: PUBLIC TYPE = GGControlPanelTypes.ControlsObj; -- exported to GGInterfaceTypes
EmbedDataObj: PUBLIC TYPE = GGEmbedTypes.EmbedDataObj; -- exported to GGInterfaceTypes
RefreshDataObj: PUBLIC TYPE = GGRefreshTypes.RefreshDataObj;
StateDataObj: PUBLIC TYPE = GGStateTypes.StateDataObj; -- exported to GGInterfaceTypes
DragDataObj: PUBLIC TYPE = GGDragTypes.DragDataObj;
CameraObj: TYPE = GGModelTypes.CameraObj;
Caret: TYPE = REF CaretObj;
CaretObj: TYPE = GGInterfaceTypes.CaretObj;
ChoiceDataObj: TYPE = MatchViewer.ChoiceDataObj;
Controls: TYPE = GGInterfaceTypes.Controls;
FiltersObj: TYPE = GGInterfaceTypes.FiltersObj;
GGData: TYPE = GGInterfaceTypes.GGData;
GGDataObj: TYPE = GGInterfaceTypes.GGDataObj;
MatchData: TYPE = MatchViewer.MatchData;
MatchDataObj: TYPE = MatchViewer.MatchDataObj;
Point: TYPE = GGBasicTypes.Point;
Sequence: TYPE = GGModelTypes.Sequence;
Scene: TYPE = GGModelTypes.Scene;
SearchState: TYPE = Match.SearchState;
Slice: TYPE = GGModelTypes.Slice;
SliceDescriptor: TYPE = GGModelTypes.SliceDescriptor;
SliceDescriptorGenerator: TYPE = GGModelTypes.SliceDescriptorGenerator;
Traj: TYPE = GGModelTypes.Traj;
Viewer: TYPE = ViewerClasses.Viewer;
minMatchTol: REAL ← .02;
initialTolerance: REAL = 0.2;
ToleranceData: TYPE = REF ToleranceDataObj;
ToleranceDataObj: TYPE = RECORD [
current: REAL ← initialTolerance
];
actionAreaHeight: CARDINAL = 200; -- height of action area biscroller viewer
actionAreaMargin: CARDINAL = 3; -- distance between two action areas
actionAreaStartY: CARDINAL = entryHeight;
buttonAlign: CARDINAL = 2; -- vertical alignment differential for the 2 kinds of buttons used
entryHeight: CARDINAL = 15; -- height of a line of items
entryHSpace: CARDINAL = 2; -- space between ButtonLines on same line
entryVSpace: CARDINAL = 2; -- vertical leading between lines
column0: CARD = 0; -- x coordinate of Characteristics column
column1: CARD = 135; -- x coordinate of From column
column2: CARD = 195; -- x coordinate of To column
column3: CARD = 245; -- x coordinate of Search button line
upperMargin: CARD = 8; -- Vertical space between actionAreas and Characteristics line
lowerMargin: CARD = 5; -- Extra space between Characteristics description line and FromTo entries
fromBoxOffset: CARD = 14; -- column1 + fromBoxOffset = x location of From boxes
toBoxOffset: CARD = 4; -- column2 + toBoxOffset = x location of To boxes
actionAreaClass: BiScrollers.BiScrollerClass ← NIL; -- filled in by MatchInit
Global MatchTool Data. At the moment, there is only one MatchTool in the world.
globalTwoState: AtomButtons.TwoState;
matchData: MatchData; -- global one of a kind matchData
matchToolIcon: Icons.IconFlavor; -- MatchTool icon
matchContainerClass: ViewerClasses.ViewerClass;
fromViewer, toViewer: Viewer;
fromLine, toLine: Viewer;
fromData, toData, grepData: GGData;
Building a MatchTool Viewer (like GGWindow)
ErrorFeedback: PUBLIC PROC [message: Rope.ROPE] = {
ggData: GGData ← GetToData[];
Feedback.PutF[ggData.router, oneLiner, $Error, message];
Feedback.Blink[ggData.router, $Error];
};
CreateTool: PROC [] = {
[] ← ViewerOps.CreateViewer [flavor: $MatchContainer,
info: [name: "MatchTool",
menu: NIL,
iconic: TRUE,
column: right,
scrollable: FALSE
],
paint: TRUE]; -- MatchContainerInit will be called by Viewers as an effect of CreateViewer
};
MatchContainerInit: ViewerClasses.InitProc = {
fromData ← BuildMatchGGData[self]; -- From viewer GG Data
toData ← BuildMatchGGData[self]; -- To viewer GG Data
grepData ← BuildMatchGGData[self]; -- this data is used to search scenes during grep
CreateFeedbackLine[toData, fromData, grepData];
fromViewer ← BuildActionArea[self, fromData]; -- positioned by AdjustProc
toViewer ← BuildActionArea[self, toData]; -- positioned by AdjustProc
fromLine ← BuildActionAreaLine[fromData, 0]; -- positioned by AdjustProc
toLine ← BuildActionAreaLine[toData, 0]; -- positioned by AdjustProc
BuildControlPanel[self, actionAreaStartY + actionAreaHeight + entryHeight + upperMargin];
fromData.embed.beingBorn ← FALSE; -- start input handling
toData.embed.beingBorn ← FALSE;
grepData.embed.beingBorn ← FALSE;
};
NormalPaint: GGRefresh.PaintProc = {
PROC [ggData: GGData, request: REFNIL, bounds: BoundBoxObj ← infiniteRect, clientData: REFNIL];
GGInMMMImpl.Paint can be used in place of this routine when Gargoyle is embedded in MMM.
ViewerOps.PaintViewer[viewer: ggData.controls.actionArea, hint: client, whatChanged: ggData, clearClient: FALSE];
};
BuildMatchGGData: PROC [outer: Viewer] RETURNS [ggData: GGData] = {
Builds the GGData for the MatchTool From and To viewers. outer is the MatchTool Container.
Also used for MatchTool grepData.
REMOVED IMPLICIT SHARING OF ALL initial ggData via hostData. If you want MatchTool to copy values, they must be explicitly copied to the new ggData HERE.
ggData ← NEW[GGDataObj ← matchData.hostData^]; -- copy hostData values.
SEE GGWindowImpl.CreateGGData for up-to-date initialization of ggData.
Code in BOLDFACE indicates different from corresponding code in GGWindowImpl.CreateGGData.
scene: Scene ← GGScene.CreateScene[];
ggData ← NEW[GGDataObj]; -- removed implicit copying of initial hostData values
ggData.controls ← NEW[ControlsObj];
ggData.controlState ← NEW[StateDataObj];
ggData.controlState.doubleBuffer ← TRUE;
ggData.controlState.clientToViewer ← ImagerTransformation.Scale[1.0];
ggData.controlState.viewerToClient ← ImagerTransformation.Scale[1.0];
ggData.drag ← NEW[DragDataObj];
ggData.embed ← NEW[EmbedDataObj];
ggData.embed.scrollDue ← ImagerTransformation.Scale[1.0];
GGRefresh.RegisterPaintProc[ggData, NormalPaint, NIL];
GGStateExtras.RegisterViewportProc[ggData, GGStateExtras.DefaultViewport, NIL];
ggData.embed.beingBorn ← TRUE;
ggData.controls.bufferButton ← matchData.hostData.controls.bufferButton;
outer.data ← ggData;
ggData.controls.topper.newVersion ← ggData.controls.panel.newVersion ← FALSE;
ggData.controls.topper ← ggData.controls.panel ← outer;
Working Directory
ggData.originalWDir ← matchData.hostData.originalWDir;
GGStateExtras.SetWorkingDirectory[ggData, matchData.hostData.currentWDir];
Scene and Interface Objects
ggData.scene ← scene;
ggData.caret ← NEW[CaretObj];
ggData.drag.savedCaret ← GGCaret.Create[];
ggData.anchor ← GGCaret.Create[];
ggData.camera ← NEW[CameraObj];
Input Handling
ggData.lastEvents ← GGCoreOps.NewEventListt[];
GGMouseEvent.InitializeFSM[ggData];
ggData.history ← [list: LIST[NIL],
maxSize: 1,
currentIndex: 0]; -- no history tool. Only SHIFT-ESC
Selections
ggData.drag.selectState ← none;
ggData.drag.extendMode ← none;
GGState.SetSelectionCycler[ggData, GGMultiGravity.EmptyCycler[[0.0, 0.0]] ];
Hit Testing
ggData.hitTest ← NEW[FiltersObj ← matchData.hostData.hitTest^]; -- copy record containing AtomButtons, then overwrite bags
ggData.hitTest.triggerBag ← GGAlign.CreateTriggerBag[];
ggData.hitTest.oldTriggerBag ← GGAlign.CreateTriggerBag[];
ggData.hitTest.sceneBag ← GGAlign.CreateTriggerBag[];
ggData.hitTest.oldSceneBag ← GGAlign.CreateTriggerBag[];
ggData.hitTest.alignBag ← GGAlign.CreateAlignBag[];
ggData.hitTest.oldAlignBag ← GGAlign.CreateAlignBag[];
ggData.multiGravityPool ← GGMultiGravity.NewMultiGravityPool[];
Refresh
ggData.refresh ← NEW[RefreshDataObj];
GGAlign.CreateLineTable[ggData]; -- part of RefreshData
ggData.refresh.startBoundBox ← GGBoundBox.NullBoundBox[];
ggData.refresh.beforeBox ← GGBoundBox.NullBoundBox[];
ggData.refresh.paintBox ← GGBoundBox.NullBoundBox[];
ggData.refresh.totalBox ← GGBoundBox.NullBoundBox[];
ggData.refresh.sandwich ← GGRefresh.CreateSandwich[];
ggData.refresh.oldTransform ← ImagerTransformation.Scale[1.0];
Behavior
ggData.behavior.activeDoc ← NIL; -- next time this field is needed it will be recomputed
ggData.rootSlice ← GGSlice.MakeCircleSlice[[200.0, 200.0], [300.0, 200.0]].slice;
ggData.measure ← matchData.hostData.measure; -- share record containing Viewers
Debugging
ggData.debug ← NEW[GGInterfaceTypes.DebugDataObj ← matchData.hostData.debug^]; -- share record containing IO.STREAMS
ggData.defaults ← NEW[GGInterfaceTypes.DefaultDataObj ← [
strokeColor: Imager.black,
fillColor: GGOutline.fillColor,
font: GGUserProfile.GetDefaultDefaultFont[],
dropShadowColor: Imager.black
]];
ggData.slackHandle ← matchData.hostData.slackHandle;
ggData.height ← 0;
};
CreateFeedbackLine: PROC [data1, data2, grepData: GGData] = {
Creates a single feedback line for two action areas (represented by data1 and data2) and the grepData
BuildFeedbackLine[data1];
grepData.router ← data2.router ← data1.router;
data2.height ← data2.height + entryHeight; -- data1 was incremented by BuildFeedbackLine
grepData.height ← grepData.height + entryHeight; -- just for consistency. Unused.
};
fullColumn: CARDINAL = 600; -- the width of the standard large left column.
BuildFeedbackLine: PROC [ggData: GGData] = {
nextX: NAT ← AtomButtons.BuildButtonLine[ggData.controls.panel, 0, ggData.height, ggData, GGUserInput.EventNotify, LIST[
[label["Feedback . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Line", FeedbackLineInGGData, fullColumn]]
]];
ggData.height ← ggData.height + entryHeight;
};
FeedbackLineInGGData: AtomButtons.UpdateProc = {
ggData: GGData ← NARROW[clientData];
router: Feedback.MsgRouter ← Feedback.CreateRouter[];
FeedbackOps.SetMultiLabel[router, button, TRUE, LIST[$Error, $Complaint]]; -- blink
FeedbackOps.SetMultiLabel[router, button, FALSE, LIST[$DuringMouse, $Feedback, $Warning, $Confirm, $Show, $Statistics]];
FeedbackOps.SetMultiTypescript[router, $Gargoyle, LIST[$Error, $Warning, $Show, $Typescript, $Complaint, $Statistics]];
ggData.router ← router;
ggData.controls.feedbackLine ← button;
};
BuildActionArea: PROC [outer: Viewer, ggData: GGData] RETURNS [Viewer] = {
Create an action area, complete with handy Viewer-Control line
bs: BiScrollers.BiScroller ← BiScrollers.GetStyle[].CreateBiScroller[
class: actionAreaClass,
info: [
parent: ggData.controls.topper,
wx: 0, wy: actionAreaStartY,
ww: 700, --ggData.outer.ww,
wh: actionAreaHeight, --ggData.outer.wh,
only initial values for ww and wh. They are constrained below
data: ggData,
border: TRUE,
scrollable: FALSE],
paint: FALSE
];
ggData.controls.actionArea ← bs.QuaViewer[inner: TRUE];
ggData.controls.biScroller ← bs;
GGWindow.SetCursorLooks[ggData.hitTest.gravityType, ggData]; -- assumes gravity is turned ON
ggData.height ← ggData.height + actionAreaHeight;
RETURN[bs.QuaViewer[inner: FALSE]];
};
BuildActionAreaLine: PROC [ggData: GGData, startX: CARD] RETURNS [Viewer] = {
height1: CARDINAL = buttonAlign;
buttonHolder: Viewer ← GetButtonHolder[parent: ggData.controls.topper, wx: startX, wy: ggData.height];
nextX: INTEGER ← AtomButtons.BuildButtonLine [buttonHolder, 0, 0, ggData, GGUserInput.EventNotify, LIST[[button["Fetch", LIST[LIST[$MatchFetch]], -1, FALSE, NIL]]
]];
tV: Viewer ← BiScrollers.CreateFit[ [parent: buttonHolder, wx: nextX+entryHSpace, wy: height1, border: FALSE], ggData.controls.biScroller];
tV ← BiScrollers.CreateReset[ [parent: buttonHolder, wx: tV.wx+tV.ww+entryHSpace, wy: height1, border: FALSE], ggData.controls.biScroller];
tV← Buttons.Create[info: [parent: buttonHolder, name: "CenterSel", wx: tV.wx+tV.ww+entryHSpace, wy: 0, border: FALSE], proc: CenterSel, clientData: ggData, paint: TRUE];
RETURN[buttonHolder];
};
GetButtonHolder: PROC [parent: Viewer, wx, wy, ww: CARD ← 0, wh: CARD ← entryHeight] RETURNS [Viewer] = {
Makes a simple container to hold some buttons
IF ww = 0 THEN ww ← parent.ww;
RETURN[Containers.Create[info: [name: "holder", parent: parent, wx: wx, wy: wy, ww: ww, wh: wh, border: FALSE, scrollable: FALSE], paint: FALSE]];
};
BuildControlPanel: PROC [parent: Viewer, startHeight: CARD] = {
MakeFromToEntry: PROC [string: Rope.ROPE, parent: Viewer, height: CARD, fromAction: LIST OF REF ANY, toAction: LIST OF REF ANY, fromOn: BOOLFALSE, toOn: BOOLFALSE] RETURNS [fromState, toState: AtomButtons.TwoState, newHeight: CARD] = {
[] ← Labels.Create[[name: string, parent: parent, wx: column0, wy: height, border: FALSE]];
[] ← AtomButtons.BuildButtonLine[container: parent, x: column1 + fromBoxOffset, y: height, clientData: parent.data, handleProc: GGUserInput.EventNotify, entries: LIST [
[twoState[" ", fromAction, fromOn, FillGlobalTwoState]]]];
fromState ← globalTwoState;
[] ← AtomButtons.BuildButtonLine[container: parent, x: column2 + toBoxOffset, y: height, clientData: parent.data, handleProc: GGUserInput.EventNotify, entries: LIST [
[twoState[" ", toAction, toOn, FillGlobalTwoState]]]];
toState ← globalTwoState;
newHeight ← height + entryHeight + entryVSpace;
};
CHARACTERISTICS FROM/TO control section
height: CARD ← startHeight;
[] ← Labels.Create[[name: "CHARACTERISTICS", parent: parent, wx: column0, wy: height, border: FALSE]];
[] ← AtomButtons.BuildButtonLine[container: parent, x: column1, y: height, clientData: parent.data, handleProc: GGUserInput.EventNotify, entries: LIST[
[button["Search", LIST[LIST[$MatchSetFrom, $ClearAll], LIST[$MatchSetFrom, $ClearAll], LIST[$MatchSetFrom, $SetAll]]]]]];
[] ← AtomButtons.BuildButtonLine[container: parent, x: column2-15, y: height, clientData: parent.data, handleProc: GGUserInput.EventNotify, entries: LIST[
[button["Replace", LIST[LIST[$MatchSetTo, $ClearAll], LIST[$MatchSetTo, $ClearAll], LIST[$MatchSetTo, $SetAll]]]]]];
height ← height + entryHeight + lowerMargin;
[matchData.from.shape, matchData.to.shape, height] ← MakeFromToEntry["Shape", parent, height, LIST[$MatchToggle, $FromShape], LIST[$MatchToggle, $ToShape], TRUE, TRUE];
[matchData.from.class, matchData.to.class, height] ← MakeFromToEntry["Object Class", parent, height, LIST[$MatchToggle, $FromClass], LIST[$MatchToggle, $ToClass], TRUE, TRUE];
[matchData.from.type, matchData.to.type, height] ← MakeFromToEntry["Curve Type", parent, height, LIST[$MatchToggle, $FromType], LIST[$MatchToggle, $ToType], TRUE, TRUE];
[matchData.from.fillColor, matchData.to.fillColor, height] ← MakeFromToEntry["Area color", parent, height, LIST[$MatchToggle, $FromFillColor], LIST[$MatchToggle, $ToFillColor], FALSE, TRUE];
[matchData.from.color, matchData.to.color, height] ← MakeFromToEntry["Line color", parent, height, LIST[$MatchToggle, $FromColor], LIST[$MatchToggle, $ToColor], FALSE, TRUE];
[matchData.from.width, matchData.to.width, height] ← MakeFromToEntry["Line width", parent, height, LIST[$MatchToggle, $FromWidth], LIST[$MatchToggle, $ToWidth], FALSE, TRUE];
[matchData.from.dash, matchData.to.dash, height] ← MakeFromToEntry["Line dashes", parent, height, LIST[$MatchToggle, $FromDash], LIST[$MatchToggle, $ToDash], FALSE, TRUE];
[matchData.from.joints, matchData.to.joints, height] ← MakeFromToEntry["Line joints", parent, height, LIST[$MatchToggle, $FromJoints], LIST[$MatchToggle, $ToJoints], FALSE, TRUE];
[matchData.from.ends, matchData.to.ends, height] ← MakeFromToEntry["Line ends", parent, height, LIST[$MatchToggle, $FromEnds], LIST[$MatchToggle, $ToEnds], FALSE, TRUE];
[matchData.from.string, matchData.to.string, height] ← MakeFromToEntry["Text string", parent, height, LIST[$MatchToggle, $FromString], LIST[$MatchToggle, $ToString], FALSE, TRUE];
[matchData.from.font, matchData.to.font, height] ← MakeFromToEntry["Text font", parent, height, LIST[$MatchToggle, $FromFont], LIST[$MatchToggle, $ToFont], FALSE, TRUE];
[matchData.from.fontTform, matchData.to.fontTform, height] ← MakeFromToEntry["Text font & transform", parent, height, LIST[$MatchToggle, $FromFontTform], LIST[$MatchToggle, $ToFontTform], FALSE, TRUE];
BuildRightControlPanel[parent, startHeight];
};
BuildRightControlPanel: PROC [parent: Viewer, startHeight: CARD] = {
FIND/YES/NO/CHANGEALL, etc...
nextX: CARD;
height: CARD ← startHeight - buttonAlign;
Yes and ChangeAll must be unqueued so they don't cause SlackProcess monitor deadlock on doOps playback
[] ← AtomButtons.BuildButtonLine [parent, column3, height, parent.data, GGUserInput.UnQueuedEventNotify, LIST[
[button["Find", LIST[LIST[$MatchSearch, $SearchBelow], LIST[$MatchSearch, $SearchFromTop], LIST[$MatchSearch, $SearchAbove]]]],
[button["Yes", LIST[LIST[$MatchYes, $SearchBelow], LIST[$MatchYes, $SearchFromTop], LIST[$MatchYes, $SearchAbove]]]],
[button["No", LIST[LIST[$MatchSearch, $SearchBelow], LIST[$MatchSearch, $SearchFromTop], LIST[$MatchSearch, $SearchAbove]]]],
[button["ChangeAll", LIST[LIST[$MatchChangeAll, $SearchBelow], LIST[$MatchChangeAll, $SearchFromTop], LIST[$MatchChangeAll, $SearchAbove]]]]]];
height ← height + entryHeight + entryVSpace;
matchData.levelButton ← AtomButtons.BuildEnumTypeSelection[viewer: parent, x: column3, y: height, maxWidth: 0, clientData: parent.data, handleProc: GGUserInput.EventNotify, title: "Granularity:", default: "Cluster Level", borderOnButtons: TRUE, style: flipThru, allInOneRow: TRUE, buttonNames: LIST["Anywhere", "Trajectory Level", "Cluster Level"], atom: $MatchLevelChange];
matchData.matchLevel ← clusterLevel;
height ← height + entryHeight + entryVSpace;
nextX ← AtomButtons.BuildButtonLine[container: parent, x: column3, y: height, clientData: parent.data, handleProc: GGUserInput.EventNotify, entries: LIST[
[twoState["RotationInv", LIST[$MatchToggle, $RotationInv], FALSE, FillRotationInv]],
[twoState["ScaleInv", LIST[$MatchToggle, $ScaleInv], FALSE, FillScaleInv]]]];
height ← height + entryHeight + entryVSpace;
nextX ← AtomButtons.BuildButtonLine[container: parent, x: column3, y: height, clientData: parent.data, handleProc: GGUserInput.EventNotify, entries: LIST[
[twoState["Polarity", LIST[$MatchToggle, $Polarity], TRUE, FillPolarity]],
[twoState["ContextSensitive", LIST[$MatchToggle, $ContextSensitive], FALSE, FillContextSensitive]]]];
height ← height + entryHeight + entryVSpace;
nextX ← AtomButtons.BuildButtonLine[container: parent, x: column3, y: height, clientData: parent.data, handleProc: GGUserInput.EventNotify, entries: LIST[
[twoState["Exact", LIST[$MatchExact], TRUE, FillExact]],
[label["Tolerance:"]]]];
nextX ← GraphicsButton.BuildGraphicsButton[container: parent, x: nextX, y: height, w: 60, h: entryHeight, clientData: parent.data, choices: LIST[[LIST[$MatchToleranceChange, $ValueDown]], [LIST[$MatchToleranceChange, $InitialValue]], [LIST[$MatchToleranceChange, $ValueUp]]], handleProc: GGUserInput.EventNotify, repaintProc: ToleranceButtonRepaint, buttonData: NEW[ToleranceDataObj], updateProc: ToleranceButtonUpdate];
height ← height + entryHeight + entryVSpace;
matchData.replaceOpButton ← AtomButtons.BuildEnumTypeSelection[viewer: parent, x: column3, y: height, maxWidth: 0, clientData: parent.data, handleProc: GGUserInput.EventNotify, title: "Action:", default: "Do Replace", borderOnButtons: TRUE, style: flipThru, allInOneRow: TRUE, buttonNames: LIST["Do Replace", "Do Operations"], atom: $MatchActionChange];
matchData.replaceOp ← doReplace;
height ← height + entryHeight + entryVSpace;
matchData.searchOpButton ← AtomButtons.BuildEnumTypeSelection[viewer: parent, x: column3, y: height, maxWidth: 0, clientData: parent.data, handleProc: GGUserInput.EventNotify, title: "SearchOp:", default: "Conjunction", borderOnButtons: TRUE, style: flipThru, allInOneRow: TRUE, buttonNames: LIST["Disjunction", "Conjunction"], atom: $MatchSearchOpChange];
matchData.searchOp ← conjunction;
height ← height + entryHeight + entryVSpace;
nextX ← AtomButtons.BuildButtonLine[container: parent, x: column3, y: height, clientData: parent.data, handleProc: GGUserInput.EventNotify, entries: LIST[
[button["StartOps", LIST[LIST[$MatchStartOps], LIST[$MatchStartOps], LIST[$MatchStartOps]]]],
[button["EndOps", LIST[LIST[$MatchEndOps], LIST[$MatchEndOps], LIST[$MatchEndOps]]]]]];
height ← height + entryHeight + entryVSpace;
nextX ← AtomButtons.BuildButtonLine[container: parent, x: column3, y: height, clientData: parent.data, handleProc: GGUserInput.EventNotify, entries: LIST [
[button["Grep", LIST[LIST[$MatchGrep], LIST[$MatchGrep], LIST[$MatchGrepListOnly]]]],
[button["NextFile", LIST[LIST[$MatchNextFile], LIST[$MatchNextFile], LIST[$MatchNextFile]]]]]];
};
FillGlobalTwoState: AtomButtons.InitTwoStateProc = {globalTwoState ← twoState;};
FillExact: AtomButtons.InitTwoStateProc = {matchData.exactMatch ← twoState;};
FillRotationInv: AtomButtons.InitTwoStateProc = {matchData.rotationInv ← twoState;};
FillScaleInv: AtomButtons.InitTwoStateProc = {matchData.scaleInv ← twoState;};
FillPolarity: AtomButtons.InitTwoStateProc = {matchData.polarity ← twoState;};
FillStructure: AtomButtons.InitTwoStateProc = {matchData.structure ← twoState;};
FillMatchNodes: AtomButtons.InitTwoStateProc = {matchData.matchNodes ← twoState;};
FillContextSensitive: AtomButtons.InitTwoStateProc = {matchData.contextSensitive ← twoState;};
MatchContainerAdjust: ViewerClasses.AdjustProc = {
margin1: CARD ← (self.ww - actionAreaMargin) / 2;
margin2: CARD ← (self.ww + actionAreaMargin) / 2;
IF fromViewer # NIL THEN ViewerOps.MoveViewer[fromViewer, 0, fromViewer.wy, margin1, fromViewer.wh, FALSE];
IF toViewer # NIL THEN ViewerOps.MoveViewer[toViewer, margin2, toViewer.wy, margin1, toViewer.wh, FALSE];
IF fromLine # NIL THEN ViewerOps.MoveViewer[fromLine, fromLine.wx, fromLine.wy, margin1, fromLine.wh, FALSE];
IF toLine # NIL THEN ViewerOps.MoveViewer[toLine, margin2, toLine.wy, margin1, toLine.wh, FALSE];
};
GGExtremaProc: PROC [clientData: REF ANY, direction: Geom2D.Vec] RETURNS [min, max: Geom2D.Vec] = {
This proc is required by BiScrollers to return the extremes of the displayed data
area: Geom2D.Rect;
ggData: GGData ← NARROW[clientData];
bigBox: GGBoundBox.BoundBox ← GGBoundBox.BoundBoxOfBoxes[GGScene.BoundBoxesInScene[ggData.scene].list];
area ← IF bigBox#NIL THEN [x: bigBox.loX, y: bigBox.loY, w: bigBox.hiX-bigBox.loX, h: bigBox.hiY-bigBox.loY] ELSE [0.0, 0.0, 1.0, 1.0];
[min, max] ← Geom2D.ExtremaOfRect[r: area, n: direction];
};
GGActionAreaPaint: PROC [self: Viewer, context: Imager.Context, whatChanged: REF ANY, clear: BOOL] RETURNS [quit: BOOLFALSE] = {
ViewerClasses.PaintProc. whatChanged is a GGData. self is an ActionArea.
ggData: GGData ~ NARROW[BiScrollers.ClientDataOfViewer[self]];
sanityCheck: BOOL[TRUE..TRUE] ~ (ggData.controls.actionArea=self);
clientToViewer: Imager.Transformation ~ GGState.GetBiScrollersTransform[ggData];
paintAction: ATOM ← ggData.refresh.paintAction;
IF whatChanged=NIL THEN {
window scrolled or changed size or moved to different display
GGRefresh.SetScreen[ggData, self.cw, self.ch, context];
paintAction ← $ViewersPaintEntireScene;
If we are reopening the window we can take advantage of the fact that the sandwich may still be valid from when we closed the window. We detect this by comparing the new BiScrollers clientToViewer transform with the one we saved last time we came through here. If they are the same, we assume we can use the old sandwich.
IF ggData.refresh.clientToViewer#NIL AND ImagerTransformation.Equal[clientToViewer, ggData.refresh.clientToViewer] THEN paintAction ← $ViewersPaintAllPlanes;
}
ELSE IF paintAction=$PaintEntireViewer THEN {
DKW: why is $PaintEntireViewer different from $PaintEntireScene? Why is it necessary to reconfigure the sandwich here?
GGRefresh.SetScreen[ggData, self.cw, self.ch, context];
paintAction ← $PaintEntireScene;
};
GGRefresh.ActionAreaPaint[screen: context, whatHasChanged: paintAction, ggData: ggData, handleViewerAbort: (whatChanged=NIL)];
ggData.refresh.clientToViewer ← clientToViewer; -- for next time around
};
Handling MatchTool Button Presses (like GGEvent)
SetFromColumn: GGUserInput.UserInputProc = {
SetColumn[matchData.from, event.rest.first = $SetAll];
};
SetToColumn: GGUserInput.UserInputProc = {
SetColumn[matchData.to, event.rest.first = $SetAll];
};
SetColumn: PROC [choice: MatchViewer.ChoiceData, state: BOOLFALSE] = {
AtomButtons.SetBinaryState[choice.class, state];
AtomButtons.SetBinaryState[choice.type, state];
AtomButtons.SetBinaryState[choice.shape, state];
AtomButtons.SetBinaryState[choice.color, state];
AtomButtons.SetBinaryState[choice.fillColor, state];
AtomButtons.SetBinaryState[choice.string, state];
AtomButtons.SetBinaryState[choice.font, state];
AtomButtons.SetBinaryState[choice.fontTform, state];
AtomButtons.SetBinaryState[choice.dash, state];
AtomButtons.SetBinaryState[choice.joints, state];
AtomButtons.SetBinaryState[choice.ends, state];
AtomButtons.SetBinaryState[choice.width, state];
};
ToggleFromOrTo: GGUserInput.UserInputProc = {
FlipThreeAsUnit: PROC = {
AtomButtons.SwitchBinaryState[matchData.to.class];
AtomButtons.SwitchBinaryState[matchData.to.type];
AtomButtons.SwitchBinaryState[matchData.to.shape];
};
SELECT event.rest.first FROM
$FromClass => AtomButtons.SwitchBinaryState[matchData.from.class];
$ToClass => FlipThreeAsUnit[];
$FromType => AtomButtons.SwitchBinaryState[matchData.from.type];
$ToType => FlipThreeAsUnit;
$FromShape => AtomButtons.SwitchBinaryState[matchData.from.shape];
$ToShape => FlipThreeAsUnit;
$FromColor => AtomButtons.SwitchBinaryState[matchData.from.color];
$ToColor => AtomButtons.SwitchBinaryState[matchData.to.color];
$FromFillColor => AtomButtons.SwitchBinaryState[matchData.from.fillColor];
$ToFillColor => AtomButtons.SwitchBinaryState[matchData.to.fillColor];
$FromString => AtomButtons.SwitchBinaryState[matchData.from.string];
$ToString => AtomButtons.SwitchBinaryState[matchData.to.string];
$FromFont => AtomButtons.SwitchBinaryState[matchData.from.font];
$ToFont => AtomButtons.SwitchBinaryState[matchData.to.font];
$FromFontTform => AtomButtons.SwitchBinaryState[matchData.from.fontTform];
$ToFontTform => AtomButtons.SwitchBinaryState[matchData.to.fontTform];
$FromDash => AtomButtons.SwitchBinaryState[matchData.from.dash];
$ToDash => AtomButtons.SwitchBinaryState[matchData.to.dash];
$FromJoints => AtomButtons.SwitchBinaryState[matchData.from.joints];
$ToJoints => AtomButtons.SwitchBinaryState[matchData.to.joints];
$FromEnds => AtomButtons.SwitchBinaryState[matchData.from.ends];
$ToEnds => AtomButtons.SwitchBinaryState[matchData.to.ends];
$FromWidth => AtomButtons.SwitchBinaryState[matchData.from.width];
$ToWidth => AtomButtons.SwitchBinaryState[matchData.to.width];
$RotationInv => AtomButtons.SwitchBinaryState[matchData.rotationInv];
$ScaleInv => AtomButtons.SwitchBinaryState[matchData.scaleInv];
$Polarity => AtomButtons.SwitchBinaryState[matchData.polarity];
$Structure => AtomButtons.SwitchBinaryState[matchData.structure];
$MatchNodes => AtomButtons.SwitchBinaryState[matchData.matchNodes];
$ContextSensitive => AtomButtons.SwitchBinaryState[matchData.contextSensitive];
ENDCASE;
};
MatchActionChange: GGUserInput.UserInputProc = {
name: Rope.ROPE;
AtomButtons.TimeToFlipThru[LIST[$FlipForward, matchData.replaceOpButton]];
name ← matchData.replaceOpButton.flipLabel.name;
SELECT TRUE FROM
Rope.Equal[name, "Do Replace"] => {matchData.replaceOp ← doReplace;};
Rope.Equal[name, "Do Operations"] => {matchData.replaceOp ← doOperations;};
ENDCASE => ERROR;
};
MatchLevelChange: GGUserInput.UserInputProc = {
name: Rope.ROPE;
AtomButtons.TimeToFlipThru[LIST[$FlipForward, matchData.levelButton]];
name ← matchData.levelButton.flipLabel.name;
SELECT TRUE FROM
Rope.Equal[name, "Anywhere"] => matchData.matchLevel ← anywhere;
Rope.Equal[name, "Trajectory Level"] => matchData.matchLevel ← trajLevel;
Rope.Equal[name, "Cluster Level"] => matchData.matchLevel ← clusterLevel;
ENDCASE => ERROR;
};
MatchSearchOpChange: GGUserInput.UserInputProc = {
name: Rope.ROPE;
AtomButtons.TimeToFlipThru[LIST[$FlipForward, matchData.searchOpButton]];
name ← matchData.searchOpButton.flipLabel.name;
SELECT TRUE FROM
Rope.Equal[name, "Disjunction"] => matchData.searchOp ← disjunction;
Rope.Equal[name, "Conjunction"] => matchData.searchOp ← conjunction;
ENDCASE => ERROR;
};
MatchToleranceChange: GGUserInput.UserInputProc = {
toleranceData: ToleranceData ← NARROW[GraphicsButton.GetValue[matchData.toleranceButton].buttonData];
IF AtomButtons.GetBinaryState[matchData.exactMatch] THEN { -- Exact button is on
ErrorFeedback["First turn Exact match off."];
RETURN;
};
SELECT event.rest.first FROM
$ValueUp => toleranceData.current ← toleranceData.current + .1;
$InitialValue => toleranceData.current ← initialTolerance;
$ValueDown => toleranceData.current ← toleranceData.current - .1;
ENDCASE => ERROR;
toleranceData.current ← MAX[MIN[toleranceData.current, 1.0], minMatchTol];
GraphicsButton.SetButtonValueAndPaint[matchData.toleranceButton, ggData, toleranceData];
};
MatchExact: GGUserInput.UserInputProc = {
toleranceData: ToleranceData ← NARROW[GraphicsButton.GetValue[matchData.toleranceButton].buttonData];
AtomButtons.SwitchBinaryState[matchData.exactMatch];
GraphicsButton.SetButtonValueAndPaint[matchData.toleranceButton, ggData, toleranceData];
};
ToleranceButtonRepaint: PROC [dc: Imager.Context, clientData: REF ANY, buttonData: REF ANY, button: Viewer] = {
Essentially GGMenusImpl.GravityExtentRepain
caretPoint: Point;
tol: REAL ← GetMatchTolerance[];
extent: REAL ← tol * button.ww;
Imager.TranslateT[dc, [0, button.wh/2.0]];
caretPoint ← [extent, 0.0];
GGShapes.DrawCaret[dc, caretPoint, [0, -1], 1.0];
};
ToleranceButtonUpdate: AtomButtonsTypes.UpdateGraphicsButtonProc = {
matchData.toleranceButton ← stateInfo;
};
CenterSel: PUBLIC Buttons.ButtonProc = {
ggData: GGData ← NARROW[clientData];
caretPos: Point ← GGCaret.GetPoint[ggData.caret];
bBox: GGModelTypes.BoundBox ← GGScene.BoundBoxOfSelected[scene: ggData.scene, selectClass: normal];
IF bBox.null THEN BiScrollers.Align[bs: ggData.controls.biScroller, client: [variant: coord[x: caretPos.x, y: caretPos.y]], viewer: [variant: fraction[fx: 0.5, fy: 0.5]], paint: TRUE]
ELSE BiScrollers.Align[bs: ggData.controls.biScroller, client: [variant: coord[x: (bBox.loX+bBox.hiX)/2.0, y: (bBox.loY+bBox.hiY)/2.0]], viewer: [variant: fraction[fx: 0.5, fy: 0.5]], paint: TRUE];
};
FitSel: Buttons.ButtonProc = {
ggData: GGData ← NARROW[clientData];
tbBox, vBox, tvBox: Geom2D.Rect;
cToV: ImagerTransformation.Transformation;
bBox: GGModelTypes.BoundBox ← GGScene.BoundBoxOfSelected[scene: ggData.scene, selectClass: normal];
IF NOT bBox.null THEN {
cToV ← BiScrollers.GetStyle[].GetTransforms[ggData.controls.biScroller].clientToViewer;
tbBox ← ImagerTransformation.TransformRectangle[cToV, GGBoundBox.RectangleFromBoundBox[bBox] ];
vBox ← BiScrollers.ViewportBox[ggData.controls.biScroller];
tvBox ← ImagerTransformation.TransformRectangle[cToV, vBox];
BiScrollers.BoxScale[bs: ggData.controls.biScroller, from: tbBox, to: tvBox];
Change scale and offsets so that client data previously in `from' covers as much as possible of `to' (from and to in viewer coords).
};
};
Fetch: GGUserInput.UserInputProc = {
theirScene: Scene ← NIL;
ourScene: Scene ← ggData.scene;
newSlices: LIST OF Slice;
Get Scene from InputFocus' Viewer
theirData: GGData ← GetGGInputFocus[];
IF theirData = NIL THEN RETURN
ELSE theirScene ← theirData.scene;
newSlices ← GGScene.CopySelectedParts[fromScene: theirScene, toScene: ourScene];
GGAlign.UpdateBagsForNewSlices[newSlices, ggData];
ggData.refresh.startBoundBox ← GGScene.BoundBoxOfSelected[ggData.scene, normal];
GGWindow.RestoreScreenAndInvariants[paintAction: $FinishedDragging, ggData: ggData, remake: sceneBag, edited: TRUE, okToSkipCapture: FALSE];
};
SearchEvent: GGUserInput.UserInputProc = {
found: BOOLFALSE;
toSearch: GGData ← GetGGInputFocus[];
IF toSearch#NIL THEN {
Feedback.Append[GetToData[].router, oneLiner, $Feedback, "Finding ..."];
found ← Match.Search[toSearch, event, TRUE];
};
IF found THEN Feedback.Append[GetToData[].router, oneLiner, $Feedback, "Match found."] ELSE ErrorFeedback["No match found."];
};
YesEvent: GGUserInput.UserInputProc = {
success: BOOL ← Match.ReplaceOperation[refresh: TRUE];
IF success THEN SearchEvent[NIL, event];
};
StartOps: GGUserInput.UserInputProc = {
searchState: Match.SearchState ← Match.GetSearchState[];
searchState.macroOps ← NIL;
searchState.macroOn ← TRUE;
[] ← SlackProcess.RegisterLogger[ggData.slackHandle, SaveOps];
[] ← SlackProcess.EnableSessionLogging[ggData.slackHandle];
Feedback.Append[GetToData[].router, oneLiner, $Feedback, "Operation recording started ..."];
};
SaveOps: SlackProcess.LoggingProc = {
PROC [clientData: REF, inputAction: REF];
This LoggingProc is initiated by StartOps, and terminated by EndOps. Save the event at the front of searchState.macroOps.
event: LIST OF REFNARROW[inputAction];
toData: GGData ← GetToData[];
searchState: SearchState ← Match.GetSearchState[];
IF toData = clientData AND event.first # $MatchEndOps THEN { -- ie, event occurred in the To viewer, and it wasn't the $MatchEndOps atom
searchState.macroOps ← CONS[event, searchState.macroOps];
};
};
EndOps: GGUserInput.UserInputProc = {
searchState: Match.SearchState ← Match.GetSearchState[];
IF NOT searchState.macroOn THEN {
ErrorFeedback["Must do StartOps before EndOps!"];
RETURN;
};
searchState.macroOn ← FALSE;
searchState.macroOps ← NARROW[GList.DReverse[searchState.macroOps]]; --order ops temporally
[] ← SlackProcess.DisableSessionLogging[ggData.slackHandle];
[] ← SlackProcess.RegisterLogger[ggData.slackHandle, GGSessionLog.EnterAction]; -- re-install old logging function
Feedback.Append[GetToData[].router, oneLiner, $Feedback, "Operation recording stopped."];
};
MatchTool User Interface State (like GGState)
GetMatchData: PUBLIC PROC RETURNS [MatchData] = {
RETURN [matchData];
};
GetGGInputFocus: PUBLIC PROC RETURNS [theirData: GGData ← NIL] = {
inputFocus: InputFocus.Focus ← InputFocus.GetInputFocus[];
IF inputFocus = NIL OR inputFocus.owner = NIL OR inputFocus.owner.class.flavor # $ActionArea THEN ErrorFeedback["Place input focus in a Gargoyle viewer."]
ELSE theirData ← NARROW[BiScrollers.ClientDataOf[NARROW[inputFocus.owner.data, BiScrollers.BiScroller]], GGData];
};
GetFromData: PUBLIC PROC RETURNS [GGData] = {
RETURN[fromData];
};
GetToData: PUBLIC PROC RETURNS [GGData] = {
RETURN[toData];
};
GetGrepData: PUBLIC PROC RETURNS [GGData] = {
RETURN[grepData];
};
GetMatchTolerance: PUBLIC PROC RETURNS [REAL] = {
toleranceData: ToleranceData ← NARROW[GraphicsButton.GetValue[matchData.toleranceButton].buttonData];
IF AtomButtons.GetBinaryState[matchData.exactMatch] THEN RETURN[minMatchTol] -- Exact button is on, so it overrides tolerance value
ELSE RETURN[toleranceData.current];
};
GetContextSensitive: PUBLIC PROC [matchData: MatchData] RETURNS [contextSensitive: BOOLFALSE] = {
contextSensitive ← AtomButtons.GetBinaryState[matchData.contextSensitive];
};
Initialization (like GGWindow and GGEventImpl*)
MatchCommand: Commander.CommandProc = {
matchData.commander ← cmd;
matchData.hostData ← FindAGargoyleViewer[]; -- so we can leach on its menu and use its SlackProcess
IF matchData.hostData = NIL THEN IO.PutRope[cmd.err, "Invoke a gargoyle viewer first.\n"]
ELSE CreateTool[]; -- MatchContainerInit will be called by Viewers
};
MatchInit: PROC = {
actionAreaClass ← BiScrollers.GetStyle[].NewBiScrollerClass[[
flavor: $ActionArea,
extrema: GGExtremaProc,
notify: GGUserInput.InputNotify,
paint: GGActionAreaPaint,
tipTable: TIPUser.InstantiateNewTIPTable["Gargoyle.tip"],
mayStretch: FALSE, -- NOT OK to scale X and Y differently
offsetsMustBeIntegers: TRUE,
preferIntegerCoefficients: FALSE,
preserve: [X: 0.5, Y: 0.5] --this specifies point that stays fixed when viewer size changes
]];
matchData ← NewMatchDataObj[]; -- global match data
matchToolIcon ← Icons.NewIconFromFile["Gargoyle.icons", 4]; -- shares Icon file with Gargoyle
matchContainerClass ← NEW[ViewerClasses.ViewerClassRec ← [
init: MatchContainerInit,
adjust: MatchContainerAdjust,
topDownCoordSys: TRUE,
icon: matchToolIcon
]];
ViewerOps.RegisterViewerClass[$MatchContainer, matchContainerClass]; -- plug in to Viewers 
RegisterMatchActions[];
Commander.Register["MatchTool", MatchCommand];
};
Utilities
NewMatchDataObj: PUBLIC PROC RETURNS [matchData: MatchData] = {
matchData ← NEW[MatchDataObj];
matchData.from ← NEW[ChoiceDataObj];
matchData.to ← NEW[ChoiceDataObj];
};
FindAGargoyleViewer: PROC RETURNS [ggData: GGData ← NIL] = {
IsGargoyleViewer: ViewerOps.EnumProc = {
IF Rope.Equal["Gargoyle", Rope.Substr[v.name, 0, 8]] THEN {
IF BiScrollers.IsBiScroller[v.data] THEN {
biScroller: BiScrollers.BiScroller ← NARROW[v.data];
ggData ← NARROW[BiScrollers.ClientDataOf[biScroller]];
RETURN[FALSE]; -- terminate enumeration
}
ELSE IF MJSContainers.IsMJSContainer[v] THEN {
ggData ← NARROW[MJSContainers.GetClientData[v]];
RETURN[FALSE]; -- terminate enumeration
};
}
ELSE RETURN[TRUE]; -- continue enumerating
};
ViewerOps.EnumerateViewers[IsGargoyleViewer]; -- look for a viewer whose name starts with "Gargoyle"
};
FindAGargoyleViewer: PROC RETURNS [ggData: GGData ← NIL] = {
IsGargoyleViewer: PROC [v: Viewer] RETURNS [gD: GGData ← NIL] = {
ref: REFNIL;
IF v=NIL OR v.data=NIL THEN RETURN;
IF BiScrollers.IsBiScroller[v.data] THEN {
biScroller: BiScrollers.BiScroller ← NARROW[v.data];
ref ← BiScrollers.ClientDataOf[biScroller];
}
ELSE IF MJSContainers.IsMJSContainer[v] THEN {
ref ← MJSContainers.GetClientData[v];
};
IF ref#NIL THEN WITH ref SELECT FROM
gd: GGData => gD ← NARROW[gd];
ENDCASE;
};
SomeGargoyleViewer: ViewerOps.EnumProc = {
IF (ggData ← IsGargoyleViewer[v])#NIL THEN RETURN[FALSE] ELSE RETURN[TRUE];
};
ViewerOps.EnumerateViewers[SomeGargoyleViewer];
};
RegisterAction: PROC [atom: ATOM, eventProc: GGUserInput.UserInputProc] = {
GGUserInput.RegisterAction[atom: atom, eventProc: eventProc, argType: none, causeMouseEventsToComplete: TRUE, ensureUnique: FALSE];
};
RegisterMatchActions: PROC = {
RegisterAction[atom: $MatchFetch, eventProc: Fetch]; -- do the fetch
RegisterAction[atom: $MatchSetFrom, eventProc: SetFromColumn]; -- turn on all buttons
RegisterAction[atom: $MatchSetTo, eventProc: SetToColumn]; -- turn on all buttons
RegisterAction[atom: $MatchToggle, eventProc: ToggleFromOrTo]; -- toggle
RegisterAction[atom: $MatchSearch, eventProc: SearchEvent]; -- do search
RegisterAction[atom: $MatchYes, eventProc: YesEvent]; -- do yes
RegisterAction[atom: $MatchChangeAll, eventProc: Match.ChangeAll]; -- do ChangeAll
RegisterAction[atom: $MatchActionChange, eventProc: MatchActionChange]; -- flip thru
RegisterAction[atom: $MatchLevelChange, eventProc: MatchLevelChange]; -- flip thru
RegisterAction[atom: $MatchSearchOpChange, eventProc: MatchSearchOpChange]; -- flip thru
RegisterAction[atom: $MatchToleranceChange, eventProc: MatchToleranceChange]; -- adjust
RegisterAction[atom: $MatchExact, eventProc: MatchExact]; -- toggle
RegisterAction[atom: $MatchStartOps, eventProc: StartOps]; -- start a macro
RegisterAction[atom: $MatchEndOps, eventProc: EndOps]; -- end a macro
GGUserInput.RegisterAction[atom: $MatchGrep, eventProc: MatchGrep.Grep, argType: rope, ensureUnique: FALSE]; -- Grep for the pattern in the From viewer
GGUserInput.RegisterAction[atom: $MatchGrepListOnly, eventProc: MatchGrep.GrepListOnly, argType: rope, ensureUnique: FALSE]; -- Grep for the pattern in the From viewer
RegisterAction[atom: $MatchNextFile, eventProc: MatchGrep.NextFile]; -- Edit the last gargoyle file found with grep
};
MatchInit[];
END.