MatchImplB.mesa
Contents: Conjunctive (Find All) Search Operations
Copyright Ó 1988 by Xerox Corporation. All rights reserved.
Kurlander, September 6, 1987 8:25:14 pm PDT
Bier, April 8, 1991 10:18 pm PDT
Pier, February 20, 1990 5:31 pm PST
DIRECTORY
AtomButtons, Basics, Feedback, GGBasicTypes, GGBoundBox, GGCaret, GGFont, GGHistory, GGInterfaceTypes, GGModelTypes, GGParent, GGRefreshTypes, GGScene, GGSegmentTypes, GGSelect, GGSequence, <<GGSessionLog,>> GGSlice, GGSliceOps, GGUserInput, GGUtility, GGWindow, GList, Imager, ImagerTransformation, IO, List, Match, MatchTurtle, MatchViewer, Real, <<SlackProcess,>> Vectors2d;
MatchImplB:
CEDAR
MONITOR
IMPORTS AtomButtons, Basics, Feedback, GGBoundBox, GGCaret, GGHistory, GGParent, GGScene, GGSelect, GGSequence, <<GGSessionLog,>> GGSlice, GGSliceOps, GGUserInput, GGUtility, GGWindow, GList, ImagerTransformation, IO, List, Match, MatchTurtle, MatchViewer, Real, <<SlackProcess,>> Vectors2d
EXPORTS GGInterfaceTypes, Match = BEGIN
BitVector: TYPE = GGBasicTypes.BitVector;
BoundBox: TYPE = GGModelTypes.BoundBox;
Caret: TYPE = REF CaretObj;
CaretObj: TYPE = GGInterfaceTypes.CaretObj;
ChoiceData: TYPE = MatchViewer.ChoiceData;
FontData: TYPE = GGFont.FontData;
GGDataObj: TYPE = GGInterfaceTypes.GGDataObj;
GGData: TYPE = GGInterfaceTypes.GGData;
ItemMatch: TYPE = Match.ItemMatch;
LooksInfo: TYPE = Match.LooksInfo;
MatchData: TYPE = MatchViewer.MatchData;
MatchDescriptor: TYPE = Match.MatchDescriptor;
MatchLevel: TYPE = MatchViewer.MatchLevel;
Point: TYPE = GGBasicTypes.Point;
PositionDescriptor: TYPE = MatchTurtle.PositionDescriptor;
RefreshDataObj: PUBLIC TYPE = GGRefreshTypes.RefreshDataObj;
SearchInfo: TYPE = Match.SearchInfo;
SearchState: TYPE = Match.SearchState;
Segment: TYPE = GGSegmentTypes.Segment;
SegmentGenerator: TYPE = GGModelTypes.SegmentGenerator;
Sequence: TYPE = GGModelTypes.Sequence;
SequenceOfReal: TYPE = GGModelTypes.SequenceOfReal;
Scene: TYPE = GGModelTypes.Scene;
Slice: TYPE = GGModelTypes.Slice;
SliceDescriptor: TYPE = GGModelTypes.SliceDescriptor;
SliceDescriptorGenerator: TYPE = GGModelTypes.SliceDescriptorGenerator;
SliceDescriptorObj: TYPE = GGModelTypes.SliceDescriptorObj;
SliceGenerator: TYPE = GGModelTypes.SliceGenerator;
SliceParts: TYPE = REF ANY;
StrokeEnd: TYPE = Imager.StrokeEnd; -- TYPE = {square, butt, round}
StrokeJoint: TYPE = Imager.StrokeJoint; -- TYPE = {miter, bevel, round}
Traj: TYPE = GGModelTypes.Traj;
TrajData: TYPE = GGModelTypes.TrajData;
TurtleHeader: TYPE = MatchTurtle.TurtleHeader;
TurtleInfo: TYPE = MatchTurtle.TurtleInfo;
Global Variables to reduce Search Allocations
searchState: SearchState ← NEW[Match.SearchStateObj];
GetSearchState:
PUBLIC
PROC
RETURNS [SearchState] = {
RETURN[searchState];
};
EfficientOrder:
PROC [looksList:
LIST
OF
REF
ANY]
RETURNS [
LIST
OF
REF
ANY] = {
Orders the looksList to be in the best (most efficient) order for matching. This means that open curves will be before closed curves which will be before objects with no orientation.
BestMatchOrder: GList.CompareProc = {
[ref1: REF ANY, ref2: REF ANY] RETURNS [Basics.Comparison]
priority1: INT ← SortOrder[ref1];
priority2: INT ← SortOrder[ref2];
RETURN[Basics.CompareInt[priority2, priority1]];
};
SortOrder:
PROC [looks:
REF
ANY]
RETURNS [
INT] = {
Items with the largest SortOrder are best to match first
simpleLooks: LooksInfo;
WITH looks
SELECT
FROM
simple: LooksInfo => simpleLooks ← simple;
complex: LIST OF LIST OF LooksInfo => simpleLooks ← complex.first.first;
ENDCASE => ERROR;
IF NOT simpleLooks.shapeDef OR simpleLooks.shape = NIL THEN RETURN[-2]
ELSE IF simpleLooks.shape.closed THEN RETURN[-1]
ELSE RETURN[Real.Round[simpleLooks.shape.length]]; -- match on larger first
};
looksList ← NARROW[GList.Sort[looksList, BestMatchOrder]];
RETURN[looksList];
};
Conjunctive Searching (must match All objects in the From viewer)
InitializeSearch:
PUBLIC
PROC [them: GGData, event:
LIST
OF
REF
ANY] = {
Pre-processing that is done once before one or more searches are performed.
Creates a snapshot of the scene (the search list), stored in searchState.ahead.
matchData: MatchData ← MatchViewer.GetMatchData[];
searchState: SearchState ← GetSearchState[];
direction: REF ANY ← event.rest.first;
If searching in new viewer or caret has moved, update search list & store a pointer to the current Gargoyle viewer.
IF them # matchData.theirData OR
GGCaret.GetPoint[matchData.theirData.caret] # matchData.lastCaretPos
THEN {
searchState.ahead ← MakeTheAheadList[them, direction, TRUE];
searchState.behind ← NIL;
matchData.theirData ← them;
matchData.lastCaretPos ← GGCaret.GetPoint[them.caret];
}
If reversing direction, set up the search list so that objects that are selected are not considered again.
ELSE
IF direction # searchState.lastDirection
THEN {
SetMatchBits:
PROC [normalD: SliceDescriptor]
RETURNS [done:
BOOL ←
FALSE] = {
GGSelect.SelectSlice[normalD, them.scene, match];
};
matchSelectD: SliceDescriptor ← NIL;
Match-select all normal-selected objects.
[] ← GGScene.WalkSelectedSlices[them.scene, first, SetMatchBits, normal];
If we were searching in the middle of a slice just before the search direction was reversed, find those parts that are selected and remove them from consideration.
IF searchState.ahead #
NIL
THEN {
matchSelectD ← GGSelect.FindSelectedSlice[searchState.ahead.first.slice, them.scene, match];
matchSelectD ← GGSelect.FindSelectedSlice[searchState.ahead.first.slice, match];
IF matchSelectD = NIL
THEN matchSelectD ← GGSliceOps.NewParts[searchState.ahead.first.slice, NIL, none];
};
searchState.ahead ← MakeTheAheadList[them, direction, TRUE];
searchState.behind ← NIL;
Match.RemoveSelected[searchState.ahead, matchSelectD];
};
Otherwise, leave the search list alone and search in the same viewer as before.
searchState.lastDirection ← IF direction = $SearchFromTop THEN $SearchBelow ELSE direction;
};
MakeTheAheadList:
PROC [theirData: GGData, direction:
REF
ANY, select:
BOOL ←
FALSE]
RETURNS [ahead:
LIST
OF SearchInfo ←
NIL] = {
"ahead" will hold SearchInfo's for objects whose boundbox top edge is below the caret if the search is progressing in a downwards direction, and above the caret if the search is progessing upwards.
If select is true, then all slices ahead will be match selected (and those behind will be cleared).
AddToSearchList:
PROC [slice: Slice]
RETURNS [done:
BOOL ←
FALSE] = {
IF
(direction = $SearchAbove AND GGSliceOps.GetBoundBox[slice].hiY >= caretPos.y) OR
(direction = $SearchBelow AND GGSliceOps.GetBoundBox[slice].hiY <= caretPos.y) OR
(direction = $SearchFromTop)
THEN slices ← CONS[slice, slices];
};
matchData: MatchData ← MatchViewer.GetMatchData[];
matchLevel: MatchLevel ← matchData.matchLevel;
caretPos: Point ← GGCaret.GetPoint[theirData.caret];
slices: LIST OF Slice;
List those slices whose bounding boxes are OK.
[] ← GGScene.WalkSlices[theirData.scene, first, AddToSearchList];
slices ← Match.HeightSort[slices, direction # $SearchAbove];
Clear the match bits.
IF select THEN GGSelect.DeselectAll[theirData.scene, match]; -- speeds subsequent selections
Build the ahead list.
FOR slices ← slices, slices.rest
UNTIL slices =
NIL
DO
infoList, tail: LIST OF SearchInfo;
[infoList, tail] ← DescribeObjectForSearch[slices.first, matchLevel, direction];
tail.rest ← ahead;
ahead ← infoList;
IF select THEN GGSelect.SelectEntireSlice[slices.first, theirData.scene, match];
ENDLOOP;
};
SearchConjNext:
PUBLIC
PROC [ahead, behind:
LIST
OF SearchInfo, direction:
REF
ANY, refresh:
BOOL ←
TRUE]
RETURNS [newAhead, newBehind:
LIST
OF SearchInfo, found:
BOOL ←
FALSE] = {
Here's where Graphical Search happens. Find the next match.
matchData: MatchData ← MatchViewer.GetMatchData[];
fromData: GGData ← MatchViewer.GetFromData[];
fromLooks: LIST OF REF ANY;
matchD: MatchDescriptor;
fromLooks ← Match.GetFromLooks[]; -- characterize the search ("from") pattern
fromLooks ← EfficientOrder[fromLooks]; -- order the pattern properties to speed up search
[matchD, ahead, behind] ← MatchSearchPattern[fromLooks, ahead, behind, direction];
IF matchD.isMatch
AND matchD.matchedSlices #
NIL
THEN {
-- we've matched something!
If found, normal-select match, move caret, restore screen, update search list.
GGSelect.DeselectAll[matchData.theirData.scene, normal];
IF MatchViewer.GetContextSensitive[matchData]
THEN SelectMapped[matchData.theirData.scene, SelectionFilter[fromData.scene, matchD.mapping]]
ELSE SelectMapped[matchData.theirData.scene, matchD.mapping];
matchData.lastCaretPos ← [GGCaret.GetPoint[matchData.theirData.caret].x, matchD.matchedSlices.first.slice.boundBox.hiY];
matchData.lastCaretPos ← [matchD.matchedSlices.first.slice.boundBox.loX, matchD.matchedSlices.first.slice.boundBox.hiY];
GGCaret.SetAttractor[matchData.theirData.caret, matchData.lastCaretPos, [0, -1], NIL];
IF refresh THEN GGWindow.RestoreScreenAndInvariants[paintAction: $SelectionChanged, ggData: matchData.theirData, remake: none, edited: FALSE, okToSkipCapture: FALSE];
ahead ← DeleteMatchesFromList[ahead, matchD.matchedSlices.rest]; -- why .rest?
behind ← DeleteMatchesFromList[behind, matchD.matchedSlices.rest];
RememberMatchDescriptor[matchD];
RETURN[ahead, behind, TRUE];
}
ELSE {
-- match failed
Otherwise, nothing is selected.
GGSelect.DeselectAll[matchData.theirData.scene, normal];
IF refresh THEN GGWindow.RestoreScreenAndInvariants[paintAction: $SelectionChanged, ggData: matchData.theirData, remake: none, edited: FALSE, okToSkipCapture: FALSE];
RememberMatchDescriptor[NIL];
RETURN[ahead, behind, FALSE];
};
};
MatchSearchPattern:
PROC [fromLooks:
LIST
OF
REF
ANY, ahead, behind:
LIST
OF SearchInfo, direction:
REF
ANY]
RETURNS [matchD: MatchDescriptor, newAhead, newBehind:
LIST
OF SearchInfo] = {
matchData: MatchViewer.MatchData ← MatchViewer.GetMatchData[];
sceneList: LIST OF SearchInfo;
sceneListVec: BitVector;
vecIndex: NAT ← 0;
thisAhead, nextAhead: SearchInfo;
leadObj: REF ANY;
otherObjs: LIST OF REF ANY;
thisAheadParts: SliceDescriptor;
sceneList ← AppendSearchInfoList[ahead, behind];
sceneListVec ← NewBitVector[GList.Length[sceneList], FALSE];
matchD ← CreateMatchDescriptor[];
IF fromLooks =
NIL
OR ahead =
NIL
THEN RETURN[matchD, ahead, behind]; -- nothing to match on or nothing to match
leadObj ← fromLooks.first;
otherObjs ← fromLooks.rest;
thisAhead ← ahead.first;
DO
-- iterate through ahead search list (i.e., through the elligible scene objects)
InitializeMatchDescriptor[matchD];
IF
NOT Match.IsEmptySearchInfo[thisAhead]
THEN {
Match the leading object at its first orientation.
[thisAheadParts, nextAhead] ← Match.FindMatchInSlice[thisAhead, LIST[leadObj], direction, matchD];
IF thisAheadParts#
NIL
THEN {
thisAheadParts = parts matching leadObj.
sceneListVec[vecIndex] ← TRUE; -- mark that thisAhead slice is taken
matchD ← MatchOtherObjects[otherObjs, sceneList, sceneListVec, direction, matchD];
IF matchD.isMatch THEN GOTO MatchFound;
If possible, try another orientation.
IF matchD.posD.valid
THEN {
FOR nextPosD: PositionDescriptor ← MatchTurtle.NextMatch[matchD.posD.others], MatchTurtle.NextMatch[matchD.posD.others]
UNTIL nextPosD =
NIL
DO
matchD.posD ← nextPosD;
[thisAheadParts, nextAhead] ← Match.FindMatchInSlice[thisAhead, LIST[leadObj], direction, matchD];
IF thisAheadParts #
NIL
THEN {
matchD ← MatchOtherObjects[otherObjs, sceneList, sceneListVec, direction, matchD];
IF matchD.isMatch THEN GOTO MatchFound;
matchD.mapping ← NIL;
};
ENDLOOP;
};
There are no more orientations. No match.
sceneListVec[vecIndex] ← FALSE; -- no match: free up this element
GOTO MatchFailed;
}
ELSE GOTO MatchFailed;
EXITS
MatchFound => {
ConjClearMatchBits[thisAhead, nextAhead, matchData.theirData.scene];
matchD.matchedSlices ← CONS[thisAheadParts, matchD.matchedSlices];
ahead ← CONS[nextAhead, ahead.rest];
RETURN[matchD, ahead, behind];
};
MatchFailed => {
ConjClearMatchBits[thisAhead, nextAhead, matchData.theirData.scene];
thisAhead ← nextAhead;
};
}
ELSE {
There are no more parts left in thisAhead. On to the next thisAhead.
behind ← CONS[ahead.first, behind];
ahead ← ahead.rest;
IF ahead =
NIL
THEN {
matchD.isMatch ← FALSE;
RETURN[matchD, ahead, behind]; -- we've run out of objects. No match.
};
thisAhead ← ahead.first;
vecIndex ← vecIndex + 1;
};
ENDLOOP;
};
MatchOtherObjects:
PROC [looksList:
LIST
OF
REF
ANY, sceneList:
LIST
OF SearchInfo, sceneListVec: BitVector, direction:
REF
ANY, matchD: MatchDescriptor]
RETURNS [MatchDescriptor] = {
bbox: BoundBox;
simpleLooks: LooksInfo;
vecIndex: INT ← -1;
transformKnown: BOOL ← FALSE;
saveDObj: Match.MatchDObj ← matchD^; -- copy so state can be restored if no match
IF looksList = NIL THEN {matchD.isMatch ← TRUE; RETURN[matchD]}; -- base case = success
WITH looksList.first
SELECT
FROM
simple: LooksInfo => simpleLooks ← simple;
complex: LIST OF LIST OF LooksInfo => simpleLooks ← complex.first.first;
ENDCASE => ERROR;
transformKnown ← simpleLooks.shapeDef AND simpleLooks.shape # NIL AND matchD.posD.valid;
IF transformKnown
THEN {
We know where where a match for simpleLooks.shape would have to be.
averageLength: REAL = 300.0; -- average length of a trajectory (exactness unimportant)
maxError: REAL ← MatchViewer.GetMatchTolerance[] * averageLength;
pt: Point ← ImagerTransformation.Transform[matchD.posD.tform1, simpleLooks.shape.originalStart]; -- any point in the pre-normalized shape will do
pt ← ImagerTransformation.InverseTransform[matchD.posD.tform2, pt];
bbox ← GGBoundBox.CreateBoundBox[pt.x - maxError, pt.y - maxError, pt.x + maxError, pt.y + maxError];
};
FOR nextItem:
LIST
OF SearchInfo ← sceneList, nextItem.rest
UNTIL nextItem =
NIL
DO
thisInfo: SearchInfo ← nextItem.first;
slice: Slice ← thisInfo.slice;
match: SliceDescriptor;
vecIndex ← vecIndex + 1;
IF sceneListVec[vecIndex] THEN LOOP; -- This puppy is taken
IF transformKnown
AND
NOT BoundBoxIntersect[bbox, GGSliceOps.GetTightBox[slice]]
THEN LOOP; -- A quick rejection of slice
[match,----] ← Match.FindMatchInSlice[thisInfo, LIST[looksList.first], direction, matchD];
IF match #
NIL
THEN {
-- we've got a match on this level. recurse!
sceneListVec[vecIndex] ← TRUE;
matchD ← MatchOtherObjects[looksList.rest, sceneList, sceneListVec, direction, matchD];
IF matchD.isMatch
THEN {
matchD.matchedSlices ← CONS[match, matchD.matchedSlices];
RETURN[matchD];
};
sceneListVec[vecIndex] ← FALSE;
matchD^ ← saveDObj; -- restore original descriptor
};
ENDLOOP;
matchD.isMatch ← FALSE; -- found no match
matchD.matchedSlices ← NIL; -- clear it out
RETURN[matchD];
};
Conjunctive Search Utilities
DescriptorInSearchInfo:
PROC [sliceD: SliceDescriptor, searchInfo: SearchInfo, matchLevel: MatchLevel]
RETURNS [
BOOL ←
FALSE] = {
IF GGSliceOps.GetType[sliceD.slice] # GGSliceOps.GetType[searchInfo.slice] THEN RETURN[FALSE]
ELSE IF GGSliceOps.GetType[searchInfo.slice] # $Outline OR matchLevel = clusterLevel THEN RETURN [searchInfo.slice = sliceD.slice]
ELSE
-- match if the traj is in the descriptor
IF searchInfo.kids = NIL THEN RETURN[FALSE] -- searchInfo is empty
ELSE RETURN[GGParent.ChildDescriptorFromDescriptor[sliceD, searchInfo.kids.first] # NIL];
};
DeleteMatchesFromList:
PROC [sceneList:
LIST
OF SearchInfo, sliceDList:
LIST
OF SliceDescriptor]
RETURNS [newList:
LIST
OF SearchInfo ←
NIL] = {
matchLevel: MatchLevel ← MatchViewer.GetMatchData[].matchLevel;
FOR l1:
LIST
OF SearchInfo ← sceneList, l1.rest
UNTIL l1 =
NIL
DO
delete: BOOL ← FALSE;
FOR l2:
LIST
OF SliceDescriptor ← sliceDList, l2.rest
UNTIL l2 =
NIL
DO
IF DescriptorInSearchInfo[l2.first, l1.first, matchLevel] THEN {delete ← TRUE; EXIT;};
ENDLOOP;
IF NOT delete THEN newList ← CONS[l1.first, newList];
ENDLOOP;
newList ← NARROW[GList.DReverse[newList], LIST OF SearchInfo];
};
DescribeObjectForSearch:
PROC [slice: Slice, matchLevel: MatchLevel, direction:
REF
ANY]
RETURNS [infoList, tail:
LIST
OF SearchInfo ←
NIL] = {
newInfo: SearchInfo;
For all but cluster level searches, trajectories get their own SearchInfos.
IF GGSliceOps.GetType[slice] = $Outline
AND (matchLevel = trajLevel
OR matchLevel = anywhere)
THEN {
DoGetTrajInfo:
PROC [traj: Slice]
RETURNS [done:
BOOL ←
FALSE] = {
newInfo ← SearchInfoFromTraj[traj, matchLevel, direction];
[infoList, tail] ← AddSearchInfo[newInfo, infoList, tail];
};
[] ← GGParent.WalkChildren[slice, first, DoGetTrajInfo, $Traj];
}
Otherwise, searching is done at slice-level.
ELSE {
newInfo ← Match.CreateSearchInfo[slice, direction];
[infoList, tail] ← AddSearchInfo[newInfo, infoList, tail];
};
};
SearchInfoFromTraj:
PROC [traj: Traj, matchLevel: MatchLevel, direction:
REF
ANY]
RETURNS [searchInfo: SearchInfo] = {
searchInfo ← NEW[Match.SearchInfoObj ← [slice: traj.parent, reverse: direction = $SearchAbove]];
searchInfo.kids ← LIST[traj];
IF matchLevel = anywhere THEN Match.CacheSegsOfFirstTraj[searchInfo];
};
BoundBoxIntersect:
PROC [b1, b2: BoundBox]
RETURNS [
BOOL ←
FALSE] = {
IF b1.infinite OR b2.infinite THEN RETURN[TRUE];
IF b1.null OR b2.null THEN RETURN[FALSE];
IF b1.loX > b2.hiX OR b2.loX > b1.hiX OR b1.loY > b2.hiY OR b2.loY > b1.hiY THEN RETURN[FALSE]
ELSE RETURN[TRUE];
};
CreateMatchDescriptor:
PUBLIC
PROC []
RETURNS [matchD: MatchDescriptor] = {
matchD ← NEW[Match.MatchDObj];
matchD.posD ← NEW[MatchTurtle.PositionDObj ← [valid: FALSE, settable: TRUE]];
};
InitializeMatchDescriptor:
PROC [matchD: MatchDescriptor] = {
matchD.posD.valid ← FALSE;
matchD.posD.settable ← TRUE;
matchD.posD.others ← NIL;
matchD.isMatch ← FALSE;
matchD.matchedSlices ← NIL;
matchD.mapping ← NIL;
};
NewBitVector:
PROC [length:
NAT, initialValue:
BOOL ←
FALSE]
RETURNS [bitVec: BitVector] = {
bitVec ← NEW[GGBasicTypes.BitVectorObj[length]];
FOR i:
NAT
IN [0..bitVec.len)
DO
bitVec[i] ← initialValue;
ENDLOOP;
};
SelectMapped:
PUBLIC
PROC [scene: GGModelTypes.Scene, matches:
LIST
OF ItemMatch] = {
TrueForSeg: GGModelTypes.WalkProc = {
IF seg = thisSeg THEN RETURN[TRUE] ELSE RETURN[FALSE];
};
thisSeg: Segment;
FOR itemList:
LIST
OF ItemMatch ← matches, itemList.rest
UNTIL itemList =
NIL
DO
sliceD: SliceDescriptor;
slice: Slice ← itemList.first.matchee.slice;
SELECT GGSliceOps.GetType[slice]
FROM
$Circle, $Text => sliceD ← GGSliceOps.NewParts[slice, NIL, slice];
$Box => {
thisSeg ← itemList.first.matchee.seg;
IF thisSeg = NIL THEN sliceD ← GGSliceOps.NewParts[slice, NIL, slice] -- whole box
ELSE sliceD ← GGSliceOps.WalkSegments[slice, TrueForSeg];
};
$Outline => {
traj: Traj ← itemList.first.matchee.traj;
thisSeg ← itemList.first.matchee.seg;
IF traj = NIL AND thisSeg = NIL THEN sliceD ← GGSliceOps.NewParts[slice, NIL, slice]
ELSE
IF thisSeg =
NIL
THEN {
trajData: TrajData ← NARROW[traj.data];
seq: Sequence ← GGSlice.DescriptorFromParts[
traj,
GGSequence.CreateComplete[trajData]
];
sliceD ← GGParent.DescriptorFromChildDescriptor[traj.parent, seq];
}
ELSE sliceD ← GGSliceOps.WalkSegments[slice, TrueForSeg];
};
ENDCASE;
GGSelect.SelectSlice[sliceD, scene, normal];
ENDLOOP;
};
For Context-Sensitive Searches
SelectionFilter:
PUBLIC
PROC [scene: GGModelTypes.Scene, matches:
LIST
OF ItemMatch]
RETURNS [selectList:
LIST
OF ItemMatch ←
NIL] = {
Returns a list of those elements of matches whose matcher field points to an item which is selected. Got that?
FOR itemList:
LIST
OF ItemMatch ← matches, itemList.rest
UNTIL itemList =
NIL
DO
slice: Slice ← itemList.first.matcher.slice;
selected: BOOL ← FALSE;
SELECT GGSliceOps.GetType[slice]
FROM
$Circle, $Text => IF GGSelect.IsSelectedInFull[slice, scene, normal] THEN selected ← TRUE;
$Box => {
seg: Segment ← itemList.first.matcher.seg;
IF seg = NIL THEN selected ← GGSelect.IsSelectedInFull[slice, scene, normal]
ELSE {
sliceD: SliceDescriptor ← GGSelect.FindSelectedSlice[slice, scene, normal];
sliceD: SliceDescriptor ← GGSelect.FindSelectedSlice[slice, normal];
IF sliceD #
NIL
THEN {
segGen: SegmentGenerator ← GGSliceOps.SegmentsInDescriptor[sliceD];
FOR nextSeg: Segment ← GGSliceOps.NextSegment[sliceD.slice, segGen].seg, GGSliceOps.NextSegment[sliceD.slice, segGen].seg
UNTIL nextSeg =
NIL
DO
IF seg = nextSeg THEN {selected ← TRUE; EXIT;};
ENDLOOP;
};
};
};
$Outline => {
traj: Traj ← itemList.first.matcher.traj;
seg: Segment ← itemList.first.matcher.seg;
IF traj = NIL AND seg = NIL THEN selected ← GGSelect.IsSelectedInFull[slice, scene, normal]
ELSE IF seg = NIL THEN selected ← GGSelect.IsSelectedInPart[traj, scene, normal]
ELSE {
sliceD: SliceDescriptor ← GGSelect.FindSelectedSlice[slice, scene, normal];
sliceD: SliceDescriptor ← GGSelect.FindSelectedSlice[slice, normal];
IF sliceD #
NIL
THEN {
segGen: SegmentGenerator ← GGSliceOps.SegmentsInDescriptor[sliceD];
FOR nextSeg: Segment ← GGSliceOps.NextSegment[slice, segGen].seg, GGSliceOps.NextSegment[slice, segGen].seg
UNTIL nextSeg =
NIL
DO
IF seg = nextSeg THEN {selected ← TRUE; EXIT;};
ENDLOOP;
};
};
};
ENDCASE;
IF selected THEN selectList ← CONS [itemList.first, selectList];
ENDLOOP;
};
Graphical Replace Routines
ReplaceOperation:
PUBLIC
PROC [refresh:
BOOL ←
TRUE]
RETURNS [success:
BOOL ←
FALSE] = {
A dispatch procedure: establishes that state is OK and calls the proper replace routine.
matchData: MatchData ← MatchViewer.GetMatchData[];
IF matchData.theirData =
NIL
OR GetLastMatchDescriptor[] =
NIL
THEN {
MatchViewer.ErrorFeedback["Must search before replacing."];
RETURN[FALSE];
};
IF matchData.replaceOp = doReplace THEN IF AtomButtons.GetBinaryState[matchData.to.shape] THEN success ← ReplaceConverse[refresh] ELSE success ← ReplaceParameters[refresh]
ELSE IF matchData.replaceOp = doOperations THEN success ← DoOps[]
ELSE ERROR; -- unknown replaceOp
};
ReplaceParameters:
PROC [refresh:
BOOL ←
TRUE]
RETURNS [success:
BOOL ←
TRUE] = {
1) find out if there's any characteristics ambiguity in To viewer
matchData: MatchData ← MatchViewer.GetMatchData[];
toLooks: LIST OF REF ANY ← Match.GetToLooks[];
singleLook: LooksInfo ← VerifyLooksEquivalence[toLooks];
IF singleLook =
NIL
THEN {
MatchViewer.ErrorFeedback["Ambiguous characteristics in To viewer."];
RETURN[FALSE];
};
2) we're in good shape, so now we need only copy looks to selection
CopyLooksToSelection[matchData.theirData, singleLook, refresh];
};
ReplaceConverse:
PROC [refresh:
BOOL ←
TRUE]
RETURNS [success:
BOOL ←
TRUE] = {
DoCopyReplacementSlice:
PROC [original: Slice]
RETURNS [done:
BOOL ←
FALSE] = {
newSlice: Slice ← GGSliceOps.Copy[original].first; -- new slice for destination scene
entireD: SliceDescriptor ← GGSliceOps.NewParts[newSlice, NIL, slice];
newSlice.priority ← GGScene.GetPriority[toScene, original];
CopyLooksToSlice[entireD, singleLook];
GGSliceOps.Transform[entireD.slice, entireD.parts, mapTform, none, NIL];
additions ← CONS[newSlice, additions];
};
matchData: MatchData ← MatchViewer.GetMatchData[]; -- data for target scene
scene: Scene ← matchData.theirData.scene; -- target scene
toScene: Scene ← MatchViewer.GetToData[].scene; -- replacement scene
selected: LIST OF SliceDescriptor ← GGScene.ListSelectedSlices[scene, first, normal]; -- target slices to replace
additions: LIST OF Slice ← NIL;
minPriority: INT ← LAST[INT]; -- large number needed later for loop initialization
matchD: MatchDescriptor ← GetLastMatchDescriptor[];
mapTform: ImagerTransformation.Transformation ← GetMatchTransform[matchData, matchD];
Get Relevant Looks from Match (that we aren't supposed to change)
matchLooks: LIST OF REF ANY ← GetLooksOfMatch[matchD, matchData.to, FALSE];
singleLook: LooksInfo ← VerifyLooksEquivalence[matchLooks];
IF singleLook =
NIL
THEN {
MatchViewer.ErrorFeedback["Ambiguous characteristics in Matched objects."];
RETURN[FALSE];
};
Find minimum priority of objects to replace
IF selected =
NIL
THEN minPriority ← -1
ELSE {
FOR sList:
LIST
OF SliceDescriptor ← selected, sList.rest
UNTIL sList =
NIL
DO
minPriority ← MIN[GGScene.GetPriority[scene, sList.first.slice], minPriority];
ENDLOOP;
};
GGHistory.NewCapture["MatchTool replace of", matchData.theirData];
Copy originals from To viewer
[] ← GGScene.WalkSlices[toScene, first, DoCopyReplacementSlice];
Delete selected slices in active search viewer
matchData.theirData.refresh.startBoundBox^ ← DeleteSelected[matchData.theirData]^;
Add slices to scene (at a reasonable priority)
additions ← InverseSortByPriority[additions];
FOR addList:
LIST
OF Slice ← additions, addList.rest
UNTIL addList =
NIL
DO
GGScene.AddSlice[scene, addList.first, minPriority];
GGBoundBox.EnlargeByBox[matchData.theirData.refresh.startBoundBox, GGSliceOps.GetBoundBox[addList.first]];
ENDLOOP;
GGHistory.PushCurrent[matchData.theirData];
IF refresh THEN GGWindow.RestoreScreenAndInvariants[paintAction: $ObjectChangedBoundBoxProvided, ggData: matchData.theirData, remake: triggerBag, edited: TRUE, okToSkipCapture: FALSE];
FixStateAfterSubstitute[];
GetMatchTransform:
PROC [matchData: MatchData, matchD: MatchDescriptor]
RETURNS [tform: ImagerTransformation.Transformation] = {
IF matchD.posD.valid
THEN {
tform ← ImagerTransformation.Concat[matchD.posD.tform1, ImagerTransformation.Invert[matchD.posD.tform2]];
}
ELSE {
-- we don't have an explicit match transform
Try to do something reasonable until we add a set of user options
Let's center the replacement on that which is being replaced (if nothing is being replaced, as in a context sensitive search with no selection, then we'll center the replacement on the caret
box1: BoundBox ← GGScene.BoundBoxOfScene[MatchViewer.GetToData[].scene];
box2: BoundBox ← GGScene.BoundBoxOfSelected[matchData.theirData.scene];
source: Point ← [(box1.loX + box1.hiX) / 2.0, (box1.loY + box1.hiY) / 2.0];
destination: Point ← IF NOT box2.null AND NOT box2.infinite THEN [(box2.loX + box2.hiX) / 2.0, (box2.loY + box2.hiY) / 2.0] ELSE GGCaret.GetPoint[matchData.theirData.caret];
tform ← ImagerTransformation.Translate[Vectors2d.Sub[destination, source]];
MatchViewer.ErrorFeedback["Warning: using centering transform."];
};
};
FixStateAfterSubstitute:
PROC = {
This routine will remake the ahead list to contain only those elements whose boundbox has the proper relationship to the caret, AND which have their match selection bit on.
matchData: MatchViewer.MatchData ← MatchViewer.GetMatchData[];
searchState: SearchState ← Match.GetSearchState[];
IF matchData.searchOp = disjunction THEN searchState.ahead ← Match.RemakeDisjList[matchData.theirData, searchState.lastDirection, FALSE]
ELSE IF matchData.searchOp = conjunction THEN searchState.ahead ← MakeTheAheadList[matchData.theirData, searchState.lastDirection, FALSE]
ELSE ERROR; -- unknown SearchOp
IF searchState.ahead #
NIL
THEN {
-- find leading elements that are not match selected
RemoveAllUnselected[searchState.ahead, match, matchData.theirData.scene];
};
ComplementDescriptor:
PROC [sliceD: SliceDescriptor]
RETURNS [SliceDescriptor] = {
entire: SliceDescriptor ← GGSliceOps.NewParts[sliceD.slice, NIL, slice];
RETURN[GGSliceOps.DifferenceParts[entire, sliceD]];
};
RemoveAllUnselected:
PROC [sceneList:
LIST
OF SearchInfo, selectClass: GGSegmentTypes.SelectionClass, scene: Scene] = {
FOR info:
LIST
OF SearchInfo ← sceneList, info.rest
UNTIL info =
NIL
DO
selectD: SliceDescriptor ← GGSelect.FindSelectedSlice[info.first.slice, scene, selectClass];
selectD: SliceDescriptor ← GGSelect.FindSelectedSlice[info.first.slice, selectClass];
IF selectD # NIL THEN selectD ← ComplementDescriptor[selectD]
ELSE selectD ← GGSliceOps.NewParts[info.first.slice, NIL, slice];
Match.RemoveDescriptorFromInfo[selectD, info.first];
ENDLOOP;
InverseSortByPriority:
PROC [entityList:
LIST
OF Slice]
RETURNS [sorted:
LIST
OF Slice] = {
Sort back to front (in decreasing order).
CompareProc: GGUtility.SliceCompareProc = {
[ref1: Slice, ref2: Slice] RETURNS [Basics.Comparison]
priority1, priority2: INT;
priority1 ← ref1.priority;
priority2 ← ref2.priority;
RETURN[Basics.CompareInt[priority2, priority1]];
};
sorted ← GGUtility.SortSliceList[entityList, CompareProc];
};
CopyLooksToSelection:
PROC [ggData: GGData, toLooks: LooksInfo, refresh:
BOOL ←
TRUE] = {
DoCopyLooks:
PROC [sliceD: SliceDescriptor]
RETURNS [done:
BOOL ←
FALSE] = {
CopyLooksToSlice[sliceD, toLooks];
};
oldBBox: BoundBox ← GGScene.BoundBoxOfSelected[ggData.scene, normal];
newBBox: BoundBox;
scene: Scene ← ggData.scene;
GGHistory.NewCapture["MatchTool replace looks of", ggData];
[] ← GGScene.WalkSelectedSlices[scene, first, DoCopyLooks, normal];
newBBox ← GGScene.BoundBoxOfSelected[scene, normal];
ggData.refresh.startBoundBox^ ← GGBoundBox.BoundBoxOfBoxes[LIST[oldBBox, newBBox]]^;
GGHistory.PushCurrent[ggData];
IF refresh THEN GGWindow.RestoreScreenAndInvariants[paintAction: $ObjectChangedBoundBoxProvided, ggData: ggData, remake: none, edited: TRUE, okToSkipCapture: FALSE];
CopyLooksToSlice:
PROC [sliceD: SliceDescriptor, looks: LooksInfo] = {
slice: Slice ← sliceD.slice;
parts: SliceParts ← sliceD.parts;
added NIL history events to following calls. KAP. August 16, 1988
IF looks.colorDef THEN GGSliceOps.SetStrokeColor[slice, parts, looks.color, $Set, NIL];
IF looks.fillColorDef THEN GGSliceOps.SetFillColor[slice, parts, looks.fillColor, $Set, NIL];
IF looks.stringDef AND GGSliceOps.GetType[slice] = $Text THEN GGSlice.SetText[slice, looks.string];
IF looks.fontDef
AND GGSliceOps.GetType[slice] = $Text
THEN {
looks.font.transform ← GGSlice.GetFontData[slice].transform;
[] ← GGSlice.SetTextFont[slice, looks.font, MatchViewer.GetToData[].router, NIL];
};
IF looks.fontTformDef AND GGSliceOps.GetType[slice] = $Text THEN [] ← GGSlice.SetTextFont[slice, looks.fontTform, MatchViewer.GetToData[].router, NIL]; -- SetTextFont will set the transform too!
IF looks.dashesDef THEN GGSliceOps.SetDashed[slice, parts, looks.dashes.dashed, looks.dashes.pattern, looks.dashes.offset, looks.dashes.length, NIL];
IF looks.jointsDef THEN GGSliceOps.SetStrokeJoint[slice, parts, looks.joints, NIL];
IF looks.endsDef THEN GGSliceOps.SetStrokeEnd[slice, parts, looks.ends, NIL];
IF looks.widthDef THEN [] ← GGSliceOps.SetStrokeWidth[slice, parts, looks.width, NIL];
};
GetLooksOfMatch:
PROC [matchD: MatchDescriptor, choice: MatchViewer.ChoiceData, value:
BOOL ←
FALSE]
RETURNS [looksList:
LIST
OF
REF
ANY ←
NIL] = {
Creates a Looks List that describes all the objects that were matched.
matchData: MatchData ← MatchViewer.GetMatchData[];
itemList: LIST OF ItemMatch ← IF MatchViewer.GetContextSensitive[matchData] THEN SelectionFilter[matchData.theirData.scene, matchD.mapping] ELSE matchD.mapping;
FOR itemList ← itemList, itemList.rest
UNTIL itemList =
NIL
DO
item: Match.ItemID ← itemList.first.matchee;
IF item.traj =
NIL
AND item.seg =
NIL
THEN {
must be that entire slice was matched, so add its looks to the looksList
looksList ← CONS[Match.GetLooksOfSlice[item.slice, choice, FALSE], looksList];
}
ELSE
IF item.seg =
NIL
THEN {
must be that entire traj was matched, so add its looks to the looksList
looksList ← CONS[Match.GetLooksOfTraj[item.traj, choice, FALSE], looksList];
}
ELSE {
must be that we have a seg match.
we're ignoring shape info, which is OK for current application of this routine. May need to be revised later
looksList ← CONS[Match.GetLooksOfSegment[item.slice, item.traj, item.seg, choice, FALSE, NIL], looksList];
}
ENDLOOP;
};
VerifyLooksEquivalence:
PROC [sceneLooks:
LIST
OF
REF
ANY]
RETURNS [singleLook: LooksInfo ←
NIL] = {
FlattenLooks:
PROC [looks:
LIST
OF
REF
ANY]
RETURNS [flatList:
LIST
OF LooksInfo ←
NIL] = {
FOR tLooks:
LIST
OF
REF
ANY ← looks, tLooks.rest
UNTIL tLooks =
NIL
DO
WITH tLooks.first
SELECT
FROM
single: LooksInfo => flatList ← CONS[single, flatList];
multiple:
LIST
OF
LIST
OF LooksInfo => {
FOR tMultiple:
LIST
OF
LIST
OF LooksInfo ← multiple, tMultiple.rest
UNTIL tMultiple =
NIL
DO
flatList ← NARROW[GList.Append[tMultiple.first, flatList]];
ENDLOOP;
};
ENDCASE => ERROR;
ENDLOOP;
};
flatLooks: LIST OF LooksInfo ← FlattenLooks[sceneLooks];
IF flatLooks = NIL THEN RETURN[NIL]; -- no single Looks
FOR lptr:
LIST
OF LooksInfo ← flatLooks, lptr.rest
UNTIL lptr =
NIL
OR lptr.rest=
NIL
DO
IF NOT Match.LooksEqual[lptr.first, lptr.rest.first] THEN RETURN[NIL]; -- different Looks
ENDLOOP;
RETURN[flatLooks.first];
};
DeleteSelected:
PROC [ggData: GGData]
RETURNS [bbox: BoundBox] = {
bbox ← GGScene.DeleteAllSelected[ggData.scene];
GGCaret.NoAttractor[ggData.caret];
GGCaret.SitOn[caret: ggData.caret, chair: NIL];
};
lastMatchD: MatchDescriptor; -- This global holds the MatchDescriptor from the most recent match
RememberMatchDescriptor:
PUBLIC
PROC [matchD: MatchDescriptor] = {
lastMatchD ← matchD;
};
GetLastMatchDescriptor:
PROC
RETURNS [MatchDescriptor] = {
RETURN[lastMatchD];
};
ConjClearMatchBits:
PROC [first, next: SearchInfo, scene: Scene] = {
This is similar to MatchImplA's AdjustBitsOfDifference, except AdjustBitsOfDifference makes the simplifying assumption that given a slice in the scene there is only a single SearchInfo for that slice. Thus, it need only deselect the entire slice, and select next. This routine will first deselect first, and then select next. It runs a bit slower, but is more general.
firstInfo: SliceDescriptor ← SearchInfoToDescriptor[first];
nextInfo: SliceDescriptor ← IF next = NIL THEN NIL ELSE SearchInfoToDescriptor[next];
GGSelect.DeselectSlice[firstInfo.slice, firstInfo.parts, scene, match];
IF nextInfo # NIL THEN GGSelect.SelectSlice[nextInfo, scene, match];
};
SearchInfoToDescriptor:
PROC [info: SearchInfo]
RETURNS [sliceD: SliceDescriptor] = {
ContainsSegs: GGModelTypes.WalkProc = { RETURN[GList.Member[seg, info.segs]];};
matchLevel: MatchLevel ← MatchViewer.GetMatchData[].matchLevel;
IF info = NIL THEN RETURN[NIL]; -- can't make an empty descriptor: we don't know the slice
IF Match.IsEmptySearchInfo[info] THEN RETURN[GGSliceOps.NewParts[info.slice, NIL, none]];
SELECT GGSliceOps.GetType[info.slice]
FROM
$Box => {
IF matchLevel # anywhere THEN RETURN[GGSliceOps.NewParts[info.slice, NIL, slice]]
ELSE RETURN[GGSliceOps.WalkSegments[info.slice, ContainsSegs]];
};
$Outline => {
DoSelectiveRemove:
PROC [traj: Slice]
RETURNS [done:
BOOL ←
FALSE] = {
IF NOT GList.Member[traj, info.kids] THEN sliceD ← GGParent.RemoveTraj[sliceD, traj];
};
sliceD ← GGSliceOps.NewParts[info.slice, NIL, slice];
[] ← GGParent.WalkChildren[info.slice, first, DoSelectiveRemove, $Traj];
IF matchLevel = anywhere
THEN {
-- only include segs in info.segs
segDescriptor: SliceDescriptor ← GGSliceOps.WalkSegments[info.slice, ContainsSegs];
sliceD ← GGParent.RemoveTraj[sliceD, info.kids.first];
sliceD ← GGSliceOps.UnionParts[sliceD, segDescriptor];
};
};
ENDCASE => RETURN[GGSliceOps.NewParts[info.slice, NIL, slice]];
ChangeAll:
PUBLIC GGUserInput.UserInputProc = {
matchData: MatchData ← MatchViewer.GetMatchData[];
searchState: SearchState ← GetSearchState[];
direction: REF ANY ← event.rest.first;
toSearch: GGData ← MatchViewer.GetGGInputFocus[];
subCount: NAT ← 0;
IF toSearch =
NIL
THEN {
-- input focus wasn't in a Gargoyle viewer
MatchViewer.ErrorFeedback["Place input focus in a Gargoyle viewer for ChangeAll."];
RETURN;
};
Feedback.Append[MatchViewer.GetToData[].router, begin, $Feedback, "Changing ... "];
SELECT matchData.searchOp
FROM
disjunction => {
Match.SearchDisjInit[toSearch, event];
DO
-- now do the substitute
found, success: BOOL ← FALSE;
[searchState.ahead, found] ← Match.SearchDisjNext[searchState.ahead, direction, doEveryRefresh];
IF NOT found THEN EXIT;
success ← ReplaceOperation[refresh: FALSE];
IF NOT success THEN EXIT;
subCount ← subCount + 1;
ENDLOOP;
};
conjunction => {
InitializeSearch[toSearch, event];
DO
-- now do the substitute
found, success: BOOL ← FALSE;
[searchState.ahead, searchState.behind, found] ← SearchConjNext[searchState.ahead, searchState.behind, direction, doEveryRefresh];
IF NOT found THEN EXIT;
success ← ReplaceOperation[refresh: FALSE];
IF NOT success THEN EXIT;
subCount ← subCount + 1;
ENDLOOP;
};
ENDCASE => ERROR; -- Somebody must have implemented a new SearchOp
Feedback.PutF[MatchViewer.GetToData[].router, end, $Feedback, "%g substitution%g", IO.card[subCount], IO.char[IF subCount=1 THEN ' ELSE 's] ];
GGWindow.RestoreScreenAndInvariants[paintAction: $PaintEntireScene, ggData: matchData.theirData, remake: triggerBag, edited: TRUE, okToSkipCapture: FALSE];
};
<<SaveOps: SlackProcess.
LoggingProc = {
This LoggingProc is initiated by StartOps, and terminated by EndOps. Save the event at the front of searchState.macroOps.
toData: GGData ← MatchViewer.GetToData[];
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:
PUBLIC GGUserInput.UserInputProc = {
searchState: Match.SearchState ← Match.GetSearchState[];
IF
NOT searchState.macroOn
THEN {
MatchViewer.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[MatchViewer.GetToData[].router, oneLiner, $Feedback, "Operation recording stopped."];
};>>
DoOps:
PROC
RETURNS [success:
BOOL ←
TRUE] = {
matchData: MatchData ← MatchViewer.GetMatchData[];
matchD: MatchDescriptor ← GetLastMatchDescriptor[];
searchState: Match.SearchState ← Match.GetSearchState[];
IF searchState.macroOn
THEN {
MatchViewer.ErrorFeedback["First press EndOps button."];
RETURN[FALSE];
};
IF searchState.macroOps =
NIL
THEN {
MatchViewer.ErrorFeedback["No operations have been fetched from To viewer."];
RETURN[FALSE];
};
GGHistory.NewCapture["MatchTool DoOperations on", matchData.theirData];
FOR opList:
LIST
OF
LIST
OF
REF
ANY ← searchState.macroOps, opList.rest
UNTIL opList =
NIL
DO
newOp: LIST OF REF ANY ← NIL;
For each event, replace each REF Point with the proper transformed Point
FOR op:
LIST
OF
REF
ANY ← opList.first, op.rest
UNTIL op =
NIL
DO
IF
ISTYPE[op.first,
REF Point]
THEN {
newPoint: REF Point ← NEW[Point];
newPoint^ ← ImagerTransformation.Transform[GetMatchTransform[matchData, matchD], NARROW[op.first, REF Point]^];
newOp ← CONS[newPoint, newOp];
}
ELSE newOp ← CONS[op.first, newOp];
ENDLOOP;
newOp ← List.DReverse[newOp];
GGUserInput.PlayAction[matchData.theirData, newOp];
ENDLOOP;
Now, we must move the caret back to the last position, so the search proceeds harmoniously. This is complicated by the fact that we must set the caret back AFTER the queue has emptied. For this reason we've created a semaphore which is released when the $MatchReleaseSemaphore atom is reached by the slack process dispatcher. Grumble.
GGUserInput.PlayAction[matchData.theirData, LIST[$MatchReleaseSemaphore]];
P[];
GGCaret.SetAttractor[matchData.theirData.caret, matchData.lastCaretPos, [0, -1], NIL];
GGHistory.PushCurrent[matchData.theirData];
GGWindow.RestoreScreenAndInvariants[paintAction: $CaretMoved, ggData: matchData.theirData, remake: none, edited: FALSE, okToSkipCapture: FALSE];
};
StartSearchInfoList:
PUBLIC
PROC []
RETURNS [entityList, ptr:
LIST
OF SearchInfo] = {
ptr ← entityList ← NIL;
};
AddSearchInfo:
PUBLIC
PROC [entity: SearchInfo, entityList, ptr:
LIST
OF SearchInfo]
RETURNS [newList, newPtr:
LIST
OF SearchInfo] = {
IF ptr =
NIL
THEN {
IF NOT entityList = NIL THEN ERROR;
newPtr ← newList ← CONS[entity, NIL];
RETURN;
}
ELSE {
newList ← entityList;
ptr.rest ← CONS[entity, NIL];
newPtr ← ptr.rest;
};
};
AppendSearchInfoList:
PUBLIC
PROC [list1, list2:
LIST
OF SearchInfo]
RETURNS [result:
LIST
OF SearchInfo] = {
pos: LIST OF SearchInfo;
newCell: LIST OF SearchInfo;
Non-destructive (copies the first list).
IF list1 = NIL THEN RETURN[list2];
result ← CONS[list1.first, NIL];
pos ← result;
FOR l:
LIST
OF SearchInfo ← list1.rest, l.rest
UNTIL l =
NIL
DO
newCell ← CONS[l.first, NIL];
pos.rest ← newCell;
pos ← newCell;
ENDLOOP;
pos.rest ← list2;
};
Synchronization Primitives (prevents doing another macro operation before the first is done).
MatchReleaseSemaphore: GGUserInput.UserInputProc = {
V[];
};
wakeUp: CONDITION;
resourceAllocated: BOOL ← TRUE;
P:
ENTRY
PROCEDURE [] = {
WHILE resourceAllocated
DO
WAIT wakeUp;
ENDLOOP;
resourceAllocated ← TRUE;
};
V:
ENTRY
PROCEDURE [] = {
resourceAllocated ← FALSE;
NOTIFY wakeUp;
};
doEveryRefresh: BOOL ← FALSE;
GGUserInput.RegisterAction[atom: $MatchReleaseSemaphore, eventProc: MatchReleaseSemaphore, argType: none, ensureUnique: FALSE]; -- An ACK, used to avoid monitor deadlock in the doOps mechanism
END.