MatchImplA.mesa
Last edited by: David Kurlander - September 6, 1987 7:29:54 pm PDT
Bier, September 4, 1987 11:19:42 pm PDT
DIRECTORY
AtomButtons, AtomButtonsTypes, Feedback, GGBasicTypes, GGCaret, GGContainer, GGFont, GGInterfaceTypes, GGModelTypes, GGOutline, GGScene, GGSegmentTypes, GGSelect, GGSequence, GGSlice, GGTraj, GGUserInput, GGUtility, GGWindow, GList, Imager, ImagerTransformation, Match, MatchTurtle, MatchViewer, Real, RealFns, Rope, SlackProcess, ViewerClasses;
MatchImplA: CEDAR PROGRAM
IMPORTS AtomButtons, GGCaret, GGOutline, GGScene, GGSelect, GGSequence, GGSlice, GGTraj, GGUtility, GGWindow, GList, ImagerTransformation, Match, MatchTurtle, MatchViewer, Real, RealFns, Rope
EXPORTS Match = BEGIN
BoundBox: TYPE = GGModelTypes.BoundBox;
Caret: TYPE = GGInterfaceTypes.Caret;
ChoiceData: TYPE = MatchViewer.ChoiceData;
DashInfo: TYPE = Match.DashInfo;
DashInfoObj: TYPE = Match.DashInfoObj;
GGData: TYPE = GGInterfaceTypes.GGData;
ItemID: TYPE = Match.ItemID;
ItemMatch: TYPE = Match.ItemMatch;
ItemMatchObj: TYPE = Match.ItemMatchObj;
LooksInfo: TYPE = Match.LooksInfo;
LooksInfoObj: TYPE = Match.LooksInfoObj;
MatchData: TYPE = MatchViewer.MatchData;
MatchDescriptor: TYPE = Match.MatchDescriptor;
MatchDObj: TYPE = Match.MatchDObj;
PositionDescriptor: TYPE = MatchTurtle.PositionDescriptor;
Point: TYPE = GGBasicTypes.Point;
SearchInfo: TYPE = Match.SearchInfo;
SearchInfoObj: TYPE = Match.SearchInfoObj;
SearchState: TYPE = Match.SearchState;
Segment: TYPE = GGSegmentTypes.Segment;
SegmentGenerator: TYPE = GGModelTypes.SegmentGenerator;
Sequence: TYPE = GGModelTypes.Sequence;
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;
Traj: TYPE = GGModelTypes.Traj;
TrajGenerator: TYPE = GGModelTypes.TrajGenerator;
TurtleHeader: TYPE = MatchTurtle.TurtleHeader;
TurtleInfo: TYPE = MatchTurtle.TurtleInfo;
Top-level search routines
SearchEvent: PUBLIC GGUserInput.UserInputProc = {
toSearch: GGData ← MatchViewer.GetGGInputFocus[];
IF toSearch # NIL THEN [] ← Search[toSearch, event, TRUE];
};
Search: PUBLIC PROC [toSearch: GGData, event: LIST OF REF ANY, refresh: BOOLTRUE] RETURNS [found: BOOL] = {
matchData: MatchData ← MatchViewer.GetMatchData[];
searchState: SearchState ← Match.GetSearchState[];
SELECT matchData.searchOp FROM
disjunction => {
SearchDisjInit[toSearch, event];
[searchState.ahead, found] ← SearchDisjNext[searchState.ahead, event.rest.first, refresh];
};
conjunction => {
Match.SearchConjInit[toSearch, event];
[searchState.ahead, searchState.behind, found] ← Match.SearchConjNext[searchState.ahead, searchState.behind, event.rest.first, refresh];
};
ENDCASE => {
MatchViewer.ErrorFeedback["This SearchOp unimplemented."];
RETURN[FALSE];
};
IF NOT found THEN MatchViewer.ErrorFeedback["No match found."];
};
Utilities for building and querying SearchInfos
AfterDescriptor: PROC [sliceD: SliceDescriptor, direction: REF ANY] RETURNS [after: SearchInfo ← NIL] = {
Not currently used.
Given a SliceDescriptor and a search direction, will create a SearchInfo which is the first place to look for a match after the given SliceDescriptor. Was for disjunction search. direction could be a BOOL.
SELECT sliceD.slice.class.type FROM
$Outline => {
First set up trajs part of SearchInfo
matchData: MatchData ← MatchViewer.GetMatchData[];
outlineParts: GGOutline.OutlineParts ← NARROW[sliceD.parts];
seqList: LIST OF Sequence ← outlineParts.seqs;
children: LIST OF Traj ← GGOutline.TrajectoriesOfOutline[sliceD.slice];
after ← NEW[SearchInfoObj ← [slice: sliceD.slice, reverse: direction=$SearchAbove]];
IF NOT after.reverse THEN {
seqList ← NARROW[GList.Reverse[seqList], LIST OF Sequence];
children ← NARROW[GList.Reverse[children], LIST OF Traj];
};
FOR seqList ← seqList, seqList.rest UNTIL seqList=NIL OR seqList.first#NIL DO
after.trajs ← CONS[children.first, after.trajs];
children ← children.rest;
ENDLOOP;
That was quick. Now set up segs if necessary.
IF matchData.matchLevel = anywhere THEN {
seq: Sequence ← IF seqList#NIL AND seqList.first#NIL THEN seqList.first ELSE NIL;
IF seqList#NIL AND seqList.first#NIL THEN after.trajs ← CONS[seq.traj, after.trajs];
IF seq=NIL OR GGSequence.IsEmpty[seq] THEN AddSegs[after] -- add ALL segs
ELSE {
IF NOT after.reverse THEN
FOR i: INT DECREASING IN [0..seq.traj.segCount) UNTIL seq.segments[i] DO
after.segs ← CONS[GGTraj.FetchSegment[seq.traj, i], after.segs];
ENDLOOP
ELSE
FOR i: INT IN [0..seq.traj.segCount) UNTIL seq.segments[i] DO
after.segs ← CONS[GGTraj.FetchSegment[seq.traj, i], after.segs];
ENDLOOP;
after.closedTraj ← FALSE; -- only some segs of the traj are included
};
};
};
$Box => RETURN[AfterBoxDescriptor[sliceD, direction]];
ENDCASE => after ← NIL;
};
AfterBoxDescriptor: PROC [sliceD: SliceDescriptor, direction: REF ANY] RETURNS [after: SearchInfo ← NIL] = {
matchData: MatchData ← MatchViewer.GetMatchData[];
IF matchData.matchLevel = anywhere THEN {
after ← NEW[SearchInfoObj ← [slice: sliceD.slice, reverse: direction=$SearchAbove, segs: NIL]];
IF sliceD.slice.class.isEmptyParts[sliceD] THEN AddSegs[after] -- add ALL segs
ELSE IF NOT sliceD.slice.class.isCompleteParts[sliceD] THEN {
creatively avoiding looking at the BoxData
segGen: SegmentGenerator ← GGSlice.SegmentsInDescriptor[sliceD];
firstSeg: Segment ← GGSlice.NextSegment[segGen];
lastSeg: Segment ← firstSeg;
FOR seg: Segment ← GGSlice.NextSegment[segGen], GGSlice.NextSegment[segGen] UNTIL seg = NIL DO
lastSeg ← seg;
ENDLOOP;
IF after.reverse THEN {
FOR i: INT IN [0..4) DO
nextSeg: Segment ← GGSlice.BoxFetchSegment[sliceD.slice, i];
IF nextSeg = firstSeg THEN EXIT;
after.segs ← CONS[nextSeg, after.segs];
ENDLOOP;
}
ELSE FOR i: INT DECREASING IN [0..4) DO
nextSeg: Segment ← GGSlice.BoxFetchSegment[sliceD.slice, i];
IF nextSeg = lastSeg THEN EXIT;
after.segs ← CONS[nextSeg, after.segs];
ENDLOOP;
after.closedTraj ← FALSE; -- only some segs of the box are included
};
};
};
IsEmptySearchInfo: PUBLIC PROC [searchInfo: SearchInfo] RETURNS [BOOLFALSE] = {
IF searchInfo = NIL OR searchInfo.empty = TRUE THEN RETURN[TRUE];
IF searchInfo.slice.class.type = $Outline AND searchInfo.trajs = NIL THEN RETURN[TRUE];
};
IsFullSearchInfo: PROC [searchInfo: SearchInfo] RETURNS [BOOLFALSE] = {
IF searchInfo = NIL THEN RETURN[FALSE];
IF searchInfo.complete THEN RETURN[TRUE];
};
CreateSearchInfo: PUBLIC PROC [slice: Slice, direction: REF ANY] RETURNS [searchInfo: SearchInfo] = {
Creates a new, complete SearchInfo for the specified slice
matchData: MatchData ← MatchViewer.GetMatchData[];
searchInfo ← NEW[SearchInfoObj ← [slice: slice, reverse: direction = $SearchAbove]];
SELECT slice.class.type FROM
$Outline => {
searchInfo.trajs ← GGOutline.TrajectoriesOfOutline[slice];
IF searchInfo.reverse THEN searchInfo.trajs ← NARROW[GList.Reverse[searchInfo.trajs], LIST OF Traj];
IF matchData.matchLevel = anywhere THEN AddSegs[searchInfo];
};
$Box => IF matchData.matchLevel = anywhere THEN AddSegs[searchInfo];
ENDCASE;
};
CopySearchInfo: PROC [info: SearchInfo] RETURNS [newInfo: SearchInfo] = {
Since only non-destructive list operations are permitted on the field of SearchInfo's, we can just copy the SearchInfoObj
newInfo ← NEW[SearchInfoObj ← info^];
};
MakeEmpty: PROC [info: SearchInfo] RETURNS [newInfo: SearchInfo] = {
newInfo ← CopySearchInfo[info];
newInfo.empty ← TRUE;
};
AddSegs: PUBLIC PROC [domain: SearchInfo] = {
Fills out the segs field of a SearchInfo to contain all the segs in the first trajectory/box
domain.segs ← NIL;
SELECT domain.slice.class.type FROM
$Outline => {
traj: Traj;
IF domain.trajs = NIL THEN RETURN; -- nothing to fill in
traj ← domain.trajs.first;
FOR i: INT IN [0..traj.segCount) DO
domain.segs ← CONS[GGTraj.FetchSegment[traj, i], domain.segs];
ENDLOOP;
IF NOT domain.reverse THEN domain.segs ← NARROW[GList.DReverse[domain.segs], LIST OF Segment];
Fill in closedTraj field
IF traj.role # open OR ManhattanDistance[GGTraj.FetchJointPos[traj, 0], GGTraj.FetchJointPos[traj, traj.segCount]] < 1.0 THEN domain.closedTraj ← TRUE
ELSE domain.closedTraj ← FALSE;
};
$Box => {
FOR i: INT IN [0..4) DO
domain.segs ← CONS[GGSlice.BoxFetchSegment[domain.slice, i], domain.segs];
ENDLOOP;
IF NOT domain.reverse THEN domain.segs ← NARROW[GList.DReverse[domain.segs], LIST OF Segment];
domain.closedTraj ← TRUE;
};
ENDCASE => ERROR;
};
Extracting Looks Information
GetFromLooks: PUBLIC PROC RETURNS [looksList: LIST OF REF ANYNIL] = {
matchData: MatchData ← MatchViewer.GetMatchData[];
fromData: GGData ← MatchViewer.GetFromData[];
sliceGen: SliceGenerator ← GGScene.SlicesInScene[fromData.scene];
FOR slice: Slice ← GGScene.NextSlice[sliceGen], GGScene.NextSlice[sliceGen] UNTIL slice = NIL DO
looksList ← CONS[GetLooksOfSlice[slice, matchData.from], looksList];
ENDLOOP;
};
GetToLooks: PUBLIC PROC RETURNS [looksList: LIST OF REF ANYNIL] = {
matchData: MatchData ← MatchViewer.GetMatchData[];
toData: GGData ← MatchViewer.GetToData[];
sliceGen: SliceGenerator ← GGScene.SlicesInScene[toData.scene];
FOR slice: Slice ← GGScene.NextSlice[sliceGen], GGScene.NextSlice[sliceGen] UNTIL slice = NIL DO
looksList ← CONS[GetLooksOfSlice[slice, matchData.to], looksList];
ENDLOOP;
};
GetLooksOfSlice: PUBLIC PROC [slice: Slice, choice: ChoiceData, value: BOOLTRUE] RETURNS [REF ANY] = {
Returns the Looks of the slice that are specified in choice (if value = TRUE) or not specified (if value = FALSE)
targetParts: SliceParts ← slice.class.newParts[slice, NIL, slice].parts;
looks: LooksInfo ← NEW[LooksInfoObj];
IF slice.class.type = $Outline OR slice.class.type = $Box THEN RETURN [GetCompositeLooksOfSlice[slice, choice, value]];
looks.owner.slice ← slice; -- this will suffice
IF AtomButtons.GetBinaryState[choice.class] = value THEN { -- Class
looks.classDef ← TRUE;
looks.class ← slice.class.type;
};
IF AtomButtons.GetBinaryState[choice.type] = value THEN { -- Type
looks.typeDef ← TRUE;
looks.type ← slice.class.type;
};
IF AtomButtons.GetBinaryState[choice.shape] = value THEN { -- Shape
looks.shapeDef ← TRUE;
looks.shape ← MatchTurtle.GetShapeOfSlice[slice];
};
IF AtomButtons.GetBinaryState[choice.color] = value THEN { -- Color
looks.colorDef ← TRUE;
looks.color ← slice.class.getStrokeColor[slice, targetParts];
};
IF AtomButtons.GetBinaryState[choice.fillColor] = value THEN { -- Fill Color
looks.fillColorDef ← TRUE;
looks.fillColor ← slice.class.getFillColor[slice];
};
IF AtomButtons.GetBinaryState[choice.string] = value THEN { -- String
IF slice.class.type = $Text THEN {
looks.stringDef ← TRUE;
looks.string ← GGSlice.GetText[slice];
}
ELSE looks.string ← NIL;
};
IF AtomButtons.GetBinaryState[choice.font] = value THEN { -- Font
looks.fontDef ← TRUE;
looks.font ← GGSlice.GetFontData[slice];
};
IF AtomButtons.GetBinaryState[choice.fontTform] = value THEN { -- Font & Transform
looks.fontTformDef ← TRUE;
looks.fontTform ← GGSlice.GetFontData[slice];
};
IF AtomButtons.GetBinaryState[choice.dash] = value THEN { -- Dashes
looks.dashesDef ← TRUE;
looks.dashes ← NEW[DashInfoObj];
[dashed: looks.dashes.dashed, pattern: looks.dashes.pattern, offset: looks.dashes.offset, length: looks.dashes.length] ← slice.class.getDashed[slice, targetParts];
};
IF AtomButtons.GetBinaryState[choice.joints] = value THEN { -- Joints
looks.jointsDef ← TRUE;
looks.joints ← slice.class.getStrokeJoint[slice, targetParts];
};
IF AtomButtons.GetBinaryState[choice.ends] = value THEN { -- Ends
looks.endsDef ← TRUE;
looks.ends ← slice.class.getStrokeEnd[slice, targetParts];
};
IF AtomButtons.GetBinaryState[choice.width] = value THEN { -- Width
looks.widthDef ← TRUE;
looks.width ← slice.class.getStrokeWidth[slice, targetParts];
};
RETURN [looks];
};
GetLooksOfSegment: PUBLIC PROC [slice: Slice, traj: Traj, seg: Segment, choice: ChoiceData, value: BOOL, shape: TurtleHeader] RETURNS [looks: LooksInfo] = {
looks ← NEW[LooksInfoObj];
looks.owner.slice ← slice;
looks.owner.traj ← traj;
looks.owner.seg ← seg;
IF AtomButtons.GetBinaryState[choice.shape] = value THEN { -- Shape
looks.shapeDef ← TRUE;
looks.shape ← shape;
};
IF AtomButtons.GetBinaryState[choice.class] = value THEN { -- Class
looks.classDef ← TRUE;
looks.class ← slice.class.type;
};
IF AtomButtons.GetBinaryState[choice.type] = value THEN { -- Type
looks.typeDef ← TRUE;
looks.type ← seg.class.type;
};
IF AtomButtons.GetBinaryState[choice.color] = value THEN { -- Color
looks.colorDef ← TRUE;
looks.color ← seg.color;
};
IF AtomButtons.GetBinaryState[choice.fillColor] = value THEN { -- Fill Color
looks.fillColorDef ← TRUE;
looks.fillColor ← slice.class.getFillColor[slice];
};
IF AtomButtons.GetBinaryState[choice.dash] = value THEN { -- Dashes
looks.dashesDef ← TRUE;
looks.dashes ← NEW[DashInfoObj ← [dashed: seg.dashed, pattern: seg.pattern, offset: seg.offset, length: seg.length]];
};
IF AtomButtons.GetBinaryState[choice.joints] = value THEN { -- Joints
looks.jointsDef ← TRUE;
looks.joints ← IF traj = NIL THEN slice.class.getStrokeJoint[slice, slice.class.newParts[slice, NIL, slice]] ELSE GGTraj.GetTrajStrokeJoint[traj];
};
IF AtomButtons.GetBinaryState[choice.ends] = value THEN { -- Ends
looks.endsDef ← TRUE;
looks.ends ← seg.strokeEnd;
};
IF AtomButtons.GetBinaryState[choice.width] = value THEN { -- Width
looks.widthDef ← TRUE;
looks.width ← seg.strokeWidth;
};
};
GetCompositeLooksOfSlice: PROC [slice: Slice, choice: ChoiceData, value: BOOLTRUE] RETURNS [looksList: LIST OF LIST OF LooksInfo ← NIL] = {
shape: TurtleHeader;
traj: Traj ← NIL; -- must be defined here so it's defined at GetLooksInSegment call-back
class: ATOM ← slice.class.type; -- for use in call-back
IF slice.class.type = $Outline THEN {
trajGen: TrajGenerator ← GGOutline.TrajsInOutline[slice];
FOR traj ← GGScene.NextTraj[trajGen], GGScene.NextTraj[trajGen] UNTIL traj = NIL DO
looksList ← CONS[GetLooksOfTraj[traj, choice, value], looksList];
ENDLOOP;
}
ELSE {
GatherSegs: GGSlice.WalkProc = {segList ← CONS[seg, segList];};
segList: LIST OF Segment ← NIL;
subList: LIST OF LooksInfo ← NIL;
IF AtomButtons.GetBinaryState[choice.shape] = value THEN shape ← MatchTurtle.GetShapeOfSlice[slice];
[] ← GGSlice.WalkSegments[slice, GatherSegs];
FOR segs: LIST OF Segment ← segList, segs.rest UNTIL segs = NIL DO
subList ← CONS[GetLooksOfSegment[slice: slice, traj: NIL, seg: segs.first, choice: choice, value: value, shape: shape], subList];
ENDLOOP;
looksList ← CONS[subList, looksList];
};
looksList ← NARROW[GList.DReverse[looksList], LIST OF LIST OF LooksInfo];
};
GetLooksOfTraj: PUBLIC PROC [traj: Traj, choice: ChoiceData, value: BOOLTRUE] RETURNS [trajLooks: LIST OF LooksInfo ← NIL] = {
shape: TurtleHeader ← IF AtomButtons.GetBinaryState[choice.shape] = value THEN MatchTurtle.TrajToTurtle[traj] ELSE NIL;
FOR i: INT DECREASING IN [0..traj.segCount) DO
trajLooks ← CONS[GetLooksOfSegment[slice: traj.parent, traj: traj, seg: GGTraj.FetchSegment[traj, i], choice: choice, value: value, shape: shape], trajLooks];
ENDLOOP;
};
WalkSegmentsInTraj: PROC [traj: Traj, walkProc: GGModelTypes.WalkProc] = {
walks the segments of a trajectory, but unlike most of the segment walk routines, does not return a sequence
segGen: SegmentGenerator ← GGSequence.SegmentsInTraj[traj];
FOR seg: Segment ← GGSequence.NextSegment[segGen], GGSequence.NextSegment[segGen] UNTIL seg = NIL DO
[] ← walkProc[seg, NIL];
ENDLOOP;
};
Matching a domain slice to the from looks.
FindMatchInSlice: PUBLIC PROC [domain: SearchInfo, fromLooks: LIST OF REF ANY, direction: REF ANY, matchD: MatchDescriptor] RETURNS [match: SliceDescriptor ← NIL, rest: SearchInfo ← NIL] = {
IF domain.empty THEN RETURN[NIL, domain];
SELECT domain.slice.class.type FROM
$Circle => [match, rest] ← FindMatchInCircle[domain, fromLooks, matchD];
$Text => [match, rest] ← FindMatchInText[domain, fromLooks, matchD];
$Box => [match, rest] ← FindMatchInBox[domain, fromLooks, matchD];
$Outline => [match, rest] ← FindMatchInOutline[domain, fromLooks, matchD];
ENDCASE; -- can't match on this kind of slice
};
FindMatchInCircle: PROC [domain: SearchInfo, fromLooks: LIST OF REF ANY, matchD: MatchDescriptor] RETURNS [match: SliceDescriptor, rest: SearchInfo] = {
entireCircle: SliceDescriptor ← domain.slice.class.newParts[domain.slice, NIL, slice];
looks: LooksInfo;
complexLooks: LIST OF LIST OF LooksInfo;
FOR fromLooks ← fromLooks, fromLooks.rest UNTIL fromLooks=NIL DO
WITH fromLooks.first SELECT FROM
simple: LooksInfo => {looks ← simple; complexLooks ← NIL;};
complex: LIST OF LIST OF LooksInfo => {
looks ← complex.first.first;
complexLooks ← complex;
};
ENDCASE => ERROR;
IF GList.Length[complexLooks] > 1 THEN LOOP;
IF looks.typeDef AND complexLooks#NIL THEN LOOP;
IF SimpleMatch[entireCircle, looks, matchD] THEN RETURN[entireCircle, MakeEmpty[domain]];
ENDLOOP;
RETURN[NIL, MakeEmpty[domain]];
};
FindMatchInText: PROC [domain: SearchInfo, fromLooks: LIST OF REF ANY, matchD: MatchDescriptor] RETURNS [match: SliceDescriptor, rest: SearchInfo] = {
entireText: SliceDescriptor ← domain.slice.class.newParts[domain.slice, NIL, slice];
looks: LooksInfo;
complexLooks: LIST OF LIST OF LooksInfo;
FOR fromLooks ← fromLooks, fromLooks.rest UNTIL fromLooks=NIL DO
WITH fromLooks.first SELECT FROM
simple: LooksInfo => {looks ← simple; complexLooks ← NIL;};
complex: LIST OF LIST OF LooksInfo => {
looks ← complex.first.first;
complexLooks ← complex;
};
ENDCASE => ERROR;
IF GList.Length[complexLooks] > 1 THEN LOOP;
IF looks.typeDef AND complexLooks#NIL THEN LOOP;
IF SimpleMatch[entireText, looks, matchD] THEN RETURN[entireText, MakeEmpty[domain]];
ENDLOOP;
RETURN[NIL, MakeEmpty[domain]];
};
FindMatchInBox: PROC [domain: SearchInfo, fromLooks: LIST OF REF ANY, matchD: MatchDescriptor] RETURNS [match: SliceDescriptor, rest: SearchInfo] = {
matchLevel: MatchViewer.MatchLevel ← MatchViewer.GetMatchData[].matchLevel;
entireBox: SliceDescriptor ← domain.slice.class.newParts[domain.slice, NIL, slice];
looks: LooksInfo;
complexLooks: LIST OF LIST OF LooksInfo;
IF matchLevel = anywhere THEN {
[match, rest] ← FindAnywhereMatchInBox[domain, fromLooks, matchD];
RETURN[match, rest];
};
FOR fromLooks ← fromLooks, fromLooks.rest UNTIL fromLooks=NIL DO
WITH fromLooks.first SELECT FROM
simple: LooksInfo => {looks ← simple; complexLooks ← NIL;};
complex: LIST OF LIST OF LooksInfo => {
looks ← complex.first.first;
complexLooks ← complex;
};
ENDCASE => ERROR;
IF matchLevel = trajLevel OR matchLevel = sliceLevel THEN {
complexLength: NAT ← GList.Length[complexLooks];
IF looks.shapeDef AND complexLength > 1 THEN LOOP; -- boxes are connected entities
IF looks.typeDef AND complexLength # 1 THEN LOOP;
IF looks.typeDef THEN {
IF ComplexMatch[entireBox, complexLooks, matchD] THEN RETURN[entireBox, MakeEmpty[domain]];
}
ELSE {
IF SimpleMatch[entireBox, looks, matchD] THEN RETURN[entireBox, MakeEmpty[domain]];
};
}
ELSE ERROR;
ENDLOOP;
RETURN[NIL, MakeEmpty[domain]];
};
FindMatchInOutline: PROC [domain: SearchInfo, fromLooks: LIST OF REF ANY, matchD: MatchDescriptor] RETURNS [match: SliceDescriptor, rest: SearchInfo] = {
entireSlice: SliceDescriptor ← domain.slice.class.newParts[domain.slice, NIL, slice];
looks: LooksInfo;
complexLooks: LIST OF LIST OF LooksInfo;
matchLevel: MatchViewer.MatchLevel ← MatchViewer.GetMatchData[].matchLevel;
IF matchLevel = trajLevel THEN {
[match, rest] ← FindTrajMatchInOutline[domain, fromLooks, matchD];
RETURN[match, rest];
};
IF matchLevel = anywhere THEN {
[match, rest] ← FindAnywhereMatchInOutline[domain, fromLooks, matchD];
RETURN[match, rest];
};
FOR fromLooks ← fromLooks, fromLooks.rest UNTIL fromLooks=NIL DO
WITH fromLooks.first SELECT FROM
simple: LooksInfo => {looks ← simple; complexLooks ← NIL;};
complex: LIST OF LIST OF LooksInfo => {
looks ← complex.first.first;
complexLooks ← complex;
};
ENDCASE => ERROR;
IF matchLevel = sliceLevel THEN {
trajList: LIST OF Traj ← GGOutline.TrajectoriesOfOutline[domain.slice];
IF (looks.typeDef OR looks.shapeDef) AND complexLooks # NIL THEN {
IF GList.Length[complexLooks] # GList.Length[trajList] THEN LOOP; -- different traj count
IF ComplexMatch[entireSlice, complexLooks, matchD] THEN RETURN[entireSlice, MakeEmpty[domain]];
}
ELSE {
IF SimpleMatch[entireSlice, looks, matchD] THEN RETURN[entireSlice, MakeEmpty[domain]];
};
}
ENDLOOP;
RETURN[NIL, MakeEmpty[domain]];
};
FindAnywhereMatchInBox: PROC [domain: SearchInfo, fromLooks: LIST OF REF ANY, matchD: MatchDescriptor] RETURNS [match: SliceDescriptor, rest: SearchInfo] = {
SelectInRange: GGModelTypes.WalkProc = {
keep ← IF segNum >= first AND segNum <= last THEN TRUE ELSE FALSE;
segNum ← segNum + 1;
};
segCount: NAT = 4; -- 4 segs per box
segNum: NAT ← 0;
start, length: NAT; -- refer to the span of segments on domain.segs
first, last: NAT; -- refer to the span of segments relative to the box
startSeg: Segment;
[start, length] ← SegLooksMatch[domain, fromLooks, matchD]; -- note: start is relative to start of list, not start of traj
IF length < 1 THEN RETURN[NIL, NIL];
Make a sliceD for the match, and a rest
startSeg ← NARROW[GList.NthElement[domain.segs, start+1], Segment];
FOR i:INT IN [0..4) DO
IF GGSlice.BoxFetchSegment[domain.slice, i] = startSeg THEN {first ← i; EXIT;};
ENDLOOP;
IF NOT domain.reverse THEN {
last ← (first + length - 1) MOD segCount;
}
ELSE {
last ← first;
first ← (first - length + 1 + segCount) MOD segCount; -- + segCount to keep positive
};
Now we have to splice out the used segments from domain.segs
match ← GGSlice.WalkSegments[domain.slice, SelectInRange];
domain ← CopySearchInfo[domain];
domain.segs ← NARROW[GList.NthTail[domain.segs, start + length], LIST OF Segment];
RETURN[match, domain];
};
FindAnywhereMatchInOutline: PROC [domain: SearchInfo, fromLooks: LIST OF REF ANY, matchD: MatchDescriptor] RETURNS [match: SliceDescriptor, rest: SearchInfo] = {
matchSeq: Sequence;
traj: Traj ← domain.trajs.first;
startSeg: Segment;
start, length: NAT; -- refer to a span of segments on domain.segs
first, last: NAT; -- refer to a span of segments in the new sequence (ie, relative to the traj)
domain ← CopySearchInfo[domain];
[start, length] ← SegLooksMatch[domain, fromLooks, matchD]; -- note: start is relative to start of list, not start of traj
IF length < 1 THEN { -- nothing on interest on this traj, go directly to next
domain.trajs ← domain.trajs.rest;
AddSegs[domain];
RETURN[NIL, domain];
};
Make a sliceD out of it and a rest, and return
startSeg ← NARROW[GList.NthElement[domain.segs, start+1], Segment];
FOR i:INT IN [0..traj.segCount) DO
IF GGTraj.FetchSegment[traj, i] = startSeg THEN {first ← i; EXIT;};
ENDLOOP;
IF NOT domain.reverse THEN {
last ← (first + length - 1) MOD traj.segCount;
}
ELSE {
last ← first;
first ← (first - length + 1 + traj.segCount) MOD traj.segCount; -- + segCount to keep positive
};
Now we have to splice out the used segments from domain.segs
domain.segs ← NARROW[GList.NthTail[domain.segs, start + length], LIST OF Segment];
matchSeq ← GGSequence.CreateFromSegments[traj, first, last];
match ← GGOutline.DescriptorFromSequence[domain.slice, matchSeq];
RETURN[match, domain];
};
FindTrajMatchInOutline: PROC [domain: SearchInfo, fromLooks: LIST OF REF ANY, matchD: MatchDescriptor] RETURNS [match: SliceDescriptor, rest: SearchInfo] = {
looks: LooksInfo;
complexLooks: LIST OF LIST OF LooksInfo;
domain ← CopySearchInfo[domain];
FOR trajs: LIST OF Traj ← domain.trajs, trajs.rest UNTIL trajs=NIL DO
currentTraj: Traj ← trajs.first;
domain.trajs ← trajs.rest;
FOR fromList: LIST OF REF ANY ← fromLooks, fromList.rest UNTIL fromList=NIL DO
WITH fromList.first SELECT FROM
simple: LooksInfo => {looks ← simple; complexLooks ← NIL;};
complex: LIST OF LIST OF LooksInfo => {
looks ← complex.first.first;
complexLooks ← complex;
};
ENDCASE => ERROR;
IF complexLooks=NIL THEN { -- ie, simple
seq: Sequence ← GGSequence.CreateComplete[currentTraj];
sliceD: SliceDescriptor ← GGOutline.DescriptorFromSequence[domain.slice, seq];
IF SimpleMatch[sliceD, looks, matchD] THEN RETURN[sliceD, domain];
}
ELSE {
FOR list: LIST OF LIST OF LooksInfo ← complexLooks, list.rest UNTIL list=NIL DO
IF TrajMatch[currentTraj, list.first, matchD] THEN {
seq: Sequence ← GGSequence.CreateComplete[currentTraj];
match ← GGOutline.DescriptorFromSequence[domain.slice, seq];
RETURN[match, domain];
};
ENDLOOP;
};
ENDLOOP;
ENDLOOP;
RETURN[NIL, domain];
};
Utilities for the above routines
ManhattanDistance: PROC [pt1, pt2: Point] RETURNS [REAL] = {
RETURN[ABS[pt2.x - pt1.x] + ABS[pt2.y - pt1.y]];
};
SegLooksMatch: PROC [domain: SearchInfo, fromLooks: LIST OF REF ANY, matchD: MatchDescriptor] RETURNS [start, length: NAT ← 0] = {
noneFound: BOOLTRUE;
newStart, newLength: NAT;
looks: LooksInfo;
complexLooks: LIST OF LooksInfo;
reverse: BOOLFALSE;
mapping: LIST OF ItemMatch ← NIL;
saveDObj, bestDObj: Match.MatchDObj;
fromLooks ← FlattenLooks[fromLooks];
FOR fromLooks ← fromLooks, fromLooks.rest UNTIL fromLooks=NIL DO
WITH fromLooks.first SELECT FROM
simple: LooksInfo => {looks ← simple; complexLooks ← NIL;};
complex: LIST OF LooksInfo => { -- not LIST OF LIST OF, since we've flattened slices
looks ← complex.first;
complexLooks ← complex;
};
ENDCASE => ERROR;
IF NOT SliceLevelMatch[domain.slice, IF domain.trajs = NIL THEN NIL ELSE domain.trajs.first, looks] THEN LOOP;
saveDObj ← matchD^;
IF NOT looks.typeDef THEN [newStart, newLength, reverse] ← SimpleSegMatch[domain, looks, matchD]
ELSE [newStart, newLength, reverse] ← CompoundSegMatch[domain, complexLooks, matchD];
IF newLength > 0 THEN { -- we've found a match, but is it the (closest, longest) match?
IF start = newStart AND newLength > length THEN length ← newLength;
IF noneFound OR newStart < start THEN {
start ← newStart;
length ← newLength;
mapping ← MakeSegMapping[domain, start, length, reverse, looks, complexLooks];
bestDObj ← saveDObj;
};
noneFound ← FALSE;
};
ENDLOOP;
IF length > 0 THEN {
matchD^ ← bestDObj;
matchD.mapping ← NARROW[GList.Append[mapping, matchD.mapping]];
};
};
FlattenLooks: PROC [looksList: LIST OF REF ANY] RETURNS [newList: LIST OF REF ANYNIL] = {
FOR looksList ← looksList, looksList.rest UNTIL looksList=NIL DO
WITH looksList.first SELECT FROM
simple: LooksInfo => newList ← CONS[simple, newList];
complex: LIST OF LIST OF LooksInfo => {
FOR complex ← complex, complex.rest UNTIL complex=NIL DO
newList ← CONS[complex.first, newList];
ENDLOOP;
};
ENDCASE => ERROR;
ENDLOOP;
newList ← NARROW[GList.DReverse[newList], LIST OF REF ANY];
};
MakeSegMapping: PROC [domain: SearchInfo, start, length: NAT, reverse: BOOL, looks: LooksInfo, complexLooks: LIST OF LooksInfo] RETURNS [mapping: LIST OF ItemMatch ← NIL] = {
segList: LIST OF Segment ← NARROW[GList.NthTail[domain.segs, start]];
traj: Traj ← IF domain.slice.class.type = $Outline THEN domain.trajs.first ELSE NIL;
IF reverse AND looks.typeDef THEN segList ← NARROW[GList.Reverse[segList]];
THROUGH [0..length) DO
newItem: ItemMatch;
IF looks.typeDef THEN {
newItem ← NEW[ItemMatchObj ← [matcher: complexLooks.first.owner, matchee: [slice: domain.slice, traj: traj, seg: segList.first], backwards: reverse]];
complexLooks ← complexLooks.rest;
}
ELSE {
newItem ← NEW[ItemMatchObj ← [matcher: looks.owner, matchee: [slice: domain.slice, traj: traj, seg: segList.first], backwards: reverse]];
};
mapping ← CONS[newItem, mapping];
segList ← segList.rest;
ENDLOOP;
};
SimpleSegMatch: PROC [domain: SearchInfo, looks: LooksInfo, matchD: MatchDescriptor] RETURNS [start, length: NAT ← 0, reverse: BOOL] = {
IF NOT looks.shapeDef THEN { -- Grab first run that fits description
index: NAT ← 0;
FOR segs: LIST OF Segment ← domain.segs, segs.rest UNTIL segs=NIL DO
match: BOOLSegmentMatch[segs.first, looks];
IF match AND length < 1 THEN start ← index;
IF match THEN length ← length + 1;
IF length > 0 AND NOT match THEN EXIT;
index ← index + 1;
ENDLOOP;
}
ELSE {
[start, length, reverse] ← ShapeDrivenSegMatch[domain, looks, matchD];
RETURN[start, length, reverse];
};
};
ShapeDrivenSegMatch: PROC [domain: SearchInfo, looks: LooksInfo, matchD: MatchDescriptor] RETURNS [start, length: NAT ← 0, reverse: BOOL] = {
looks.shapeDef is TRUE, looks.typeDef is FALSE
matchData: MatchData ← MatchViewer.GetMatchData[];
revCheck: BOOLNOT AtomButtons.GetBinaryState[matchData.polarity];
matchList: LIST OF Segment ← NIL;
IF looks.shapeDef AND looks.shape.closed THEN revCheck ← FALSE; -- can't flip closed turtles!
FOR segs: LIST OF Segment ← domain.segs, segs.rest UNTIL segs = NIL DO
tail: LIST OF TurtleInfo ← NIL;
ends: LIST OF LIST OF TurtleInfo ← NIL;
Find longest run, starting at subList.first, for which we have a basic match
FOR subList: LIST OF Segment ← segs, subList.rest UNTIL subList=NIL DO
IF SegmentMatch[subList.first, looks] THEN {
matchList ← CONS[subList.first, matchList];
tail ← MatchTurtle.SegmentToTurtle[subList.first, tail, domain.reverse];
ends ← CONS[MatchTurtle.CopyTurtleTail[tail], ends]; -- list of backwards turtleTails
}
ELSE EXIT;
ENDLOOP;
Now we must find the longest sub-run with a shape match in matchList
FOR matchList ← matchList, matchList.rest UNTIL matchList = NIL DO -- matchList is backwards
turtle: TurtleHeader ← MatchTurtle.PackageTurtleTail[ends.first];
revTurtle: TurtleHeader ← turtle;
IF domain.reverse THEN turtle ← MatchTurtle.FlipTurtle[turtle] -- so polarity works right
ELSE IF revCheck THEN revTurtle ← MatchTurtle.FlipTurtle[revTurtle];
IF MatchTurtle.TurtleEqual[looks.shape, turtle, MatchViewer.GetMatchTolerance[], matchD.posD] THEN RETURN[start, GList.Length[matchList], FALSE];
IF revCheck AND MatchTurtle.TurtleEqual[looks.shape, revTurtle, MatchViewer.GetMatchTolerance[], matchD.posD] THEN RETURN[start, GList.Length[matchList], TRUE];
ends ← ends.rest;
ENDLOOP;
start ← start + 1;
ENDLOOP;
};
CompoundSegMatch: PROC [domain: SearchInfo, looks: LIST OF LooksInfo, matchD: MatchDescriptor] RETURNS [start, length: NAT ← 0, reverse: BOOLFALSE] = {
We're being called, since typeDef is TRUE (shapeDef may be TRUE)
matchData: MatchData ← MatchViewer.GetMatchData[];
revCheck: BOOLNOT AtomButtons.GetBinaryState[matchData.polarity];
single: LooksInfo;
matchLength: NAT ← GList.Length[looks];
revLooks: LIST OF LooksInfo;
IF looks=NIL THEN RETURN ELSE single ← looks.first; -- if looks is NIL, target not compound
IF single.shapeDef AND single.shape.closed THEN revCheck ← FALSE; -- can't flip closed turtles!
revLooks ← IF revCheck THEN FlipLooks[looks] ELSE NIL;
FOR subList: LIST OF Segment ← domain.segs, subList.rest UNTIL subList = NIL DO
foreMatch: BOOLTRUE;
backMatch: BOOL ← revCheck;
foreLooks: LIST OF LooksInfo ← looks;
backLooks: LIST OF LooksInfo ← revLooks;
segs: LIST OF Segment ← subList;
tail: LIST OF TurtleInfo ← NIL;
turtle: TurtleHeader;
THROUGH [0..matchLength) DO
IF segs=NIL THEN RETURN; -- No matches found (signaled by length ← 0)
IF foreMatch AND NOT SegmentMatch[segs.first, foreLooks.first] THEN foreMatch ← FALSE;
IF backMatch AND NOT SegmentMatch[segs.first, backLooks.first] THEN backMatch ← FALSE;
IF NOT foreMatch AND NOT backMatch THEN EXIT;
IF single.shapeDef THEN tail ← MatchTurtle.SegmentToTurtle[segs.first, tail, domain.reverse];
segs ← segs.rest; -- do list bookkeeping . . .
foreLooks ← foreLooks.rest;
IF backLooks # NIL THEN backLooks ← backLooks.rest;
ENDLOOP;
IF single.shapeDef AND (foreMatch OR backMatch) THEN {
turtle ← MatchTurtle.PackageTurtleTail[tail];
IF domain.reverse THEN turtle ← MatchTurtle.FlipTurtle[turtle];
};
IF foreMatch THEN {
IF NOT single.shapeDef OR MatchTurtle.TurtleEqual[single.shape, turtle, MatchViewer.GetMatchTolerance[], matchD.posD] THEN RETURN[start, matchLength, FALSE];
};
IF backMatch THEN {
IF NOT single.shapeDef OR MatchTurtle.TurtleEqual[single.shape, MatchTurtle.FlipTurtle[turtle], MatchViewer.GetMatchTolerance[], matchD.posD] THEN RETURN[start, matchLength, TRUE];
};
start ← start + 1;
ENDLOOP;
No matches found (signaled by length ← 0)
};
FlipLooks: PROC [looksList: LIST OF LooksInfo] RETURNS [newList: LIST OF LooksInfo] = {
newList ← NARROW[GList.Reverse[looksList], LIST OF LooksInfo];
And more to come!!
};
SimpleMatch: PROC [sliceD: SliceDescriptor, looks: LooksInfo, matchD: MatchDescriptor ← NIL, checkShape: BOOLTRUE] RETURNS [match: BOOLTRUE] = {
Compare two single-path objects.
slice: Slice ← sliceD.slice;
IF looks.typeDef AND looks.type # slice.class.type THEN RETURN[FALSE];
IF looks.classDef AND looks.class # slice.class.type THEN RETURN[FALSE];
IF looks.colorDef AND NOT ColorEqual[looks.color, slice.class.getStrokeColor[slice, sliceD.parts]] THEN RETURN[FALSE];
IF looks.fillColorDef AND NOT ColorEqual[looks.fillColor, slice.class.getFillColor[slice]] THEN RETURN[FALSE];
IF looks.stringDef AND (slice.class.type # $Text OR NOT Rope.Equal[GGSlice.GetText[slice], looks.string]) THEN RETURN[FALSE];
IF looks.fontDef AND NOT FontEqual[looks.font, GGSlice.GetFontData[slice].fontData] THEN RETURN[FALSE];
IF looks.fontTformDef AND NOT FontAndTransformEqual[looks.fontTform, GGSlice.GetFontData[slice].fontData] THEN RETURN[FALSE];
IF looks.dashesDef THEN {
dashInfo: DashInfo ← NEW[DashInfoObj];
[dashed: dashInfo.dashed, pattern: dashInfo.pattern, offset: dashInfo.offset, length: dashInfo.length] ← slice.class.getDashed[slice, sliceD.parts];
IF NOT DashEqual[dashInfo, looks.dashes] THEN RETURN[FALSE];
};
IF looks.jointsDef AND looks.joints # slice.class.getStrokeJoint[slice, sliceD.parts] THEN RETURN[FALSE];
IF looks.endsDef AND looks.ends # slice.class.getStrokeEnd[slice, sliceD.parts] THEN RETURN[FALSE];
IF looks.widthDef AND looks.width # slice.class.getStrokeWidth[slice, sliceD.parts] THEN RETURN[FALSE];
IF checkShape AND looks.shapeDef THEN {
IF slice.class.type = $Outline THEN {
seqs: LIST OF Sequence ← GGOutline.SequencesOfOutline[sliceD];
IF seqs=NIL OR seqs.rest#NIL THEN RETURN[FALSE]; -- can't match multiple spans
IF NOT MatchTurtle.TurtleEqualEitherWay[looks.shape, MatchTurtle.TrajToTurtle[seqs.first.traj], MatchViewer.GetMatchTolerance[], matchD.posD] THEN RETURN[FALSE];
}
ELSE IF NOT MatchTurtle.TurtleEqualEitherWay[looks.shape, MatchTurtle.GetShapeOfSlice[slice], MatchViewer.GetMatchTolerance[], matchD.posD] THEN RETURN[FALSE];
};
IF checkShape THEN AddMapping[matchD, looks.owner, SliceDToItemID[sliceD]];
};
AddMapping: PROC [matchD: MatchDescriptor, matcher, matchee: ItemID, backwards: BOOLFALSE] = {
newMatch: ItemMatch ← NEW[ItemMatchObj ← [matcher: matcher, matchee: matchee, backwards: backwards]];
matchD.mapping ← CONS[newMatch, matchD.mapping];
};
ComplexMatch: PROC [sliceD: SliceDescriptor, sliceLooks: LIST OF LIST OF LooksInfo, matchD: MatchDescriptor] RETURNS [BOOLTRUE] = {
At this point it is known that the slice is either a box or an outline, and sliceLooks is of complex form
SELECT sliceD.slice.class.type FROM
$Outline => RETURN[ComplexOutlineMatch[sliceD, sliceLooks, matchD]];
$Box => RETURN[ComplexBoxMatch[sliceD, sliceLooks, matchD]];
ENDCASE => ERROR;
};
SliceDToItemID: PROC [sliceD: SliceDescriptor] RETURNS [itemID: ItemID] = {
NOTE: only works for SliceDescriptors of entire slices (if slice is not an outline) or SliceDesriptors of single trajectories (if slice is an outline)
itemID.slice ← sliceD.slice;
IF sliceD.slice.class.type = $Outline THEN {
seqs: LIST OF Sequence ← GGOutline.SequencesOfOutline[sliceD];
IF seqs=NIL OR seqs.rest#NIL THEN ERROR; -- must include a single trajectory
itemID.traj ← seqs.first.traj;
};
};
Outline utilities
ComplexOutlineMatch: PROC [sliceD: SliceDescriptor, sliceLooks: LIST OF LIST OF LooksInfo, matchD: MatchDescriptor] RETURNS [BOOLFALSE] = {
We're matching on the shape or type (or both) of the Outline trajectories
matchData: MatchData ← MatchViewer.GetMatchData[];
structure: BOOLTRUE;
trajs: LIST OF Traj ← GGOutline.TrajectoriesOfOutline[sliceD.slice]; -- sliceD is complete
IF structure THEN {
copyD: MatchDescriptor ← NEW[MatchDObj ← matchD^];
traj: Traj ← trajs.first;
trajLooks: LIST OF LooksInfo ← sliceLooks.first;
looks: LooksInfo ← trajLooks.first;
matcher: MatchTurtle.Matcher;
next: PositionDescriptor;
IF matchD.posD.valid THEN RETURN[OutlineMatchOriented[trajs, sliceLooks, structure, matchD]]; -- ie, we already have an orientation to match on.
Try to match traj with looks, for every possible orientation...
IF NOT TrajMatch[traj, trajLooks, copyD, FALSE] THEN RETURN[FALSE];
IF NOT looks.shapeDef THEN {
IF OutlineMatchOriented[trajs.rest, sliceLooks.rest, structure, copyD] THEN {
matchD^ ← copyD^;
RETURN[TRUE];
}
ELSE RETURN [FALSE];
};
matcher ← MatchTurtle.CreateMatcher[looks.shape, MatchTurtle.TrajToTurtle[traj], MatchViewer.GetMatchTolerance[], AtomButtons.GetBinaryState[matchData.polarity]];
WHILE (next ← MatchTurtle.NextMatch[matcher]) # NIL DO
copyD.posD ← next;
IF OutlineMatchOriented[trajs.rest, sliceLooks.rest, structure, copyD] THEN {
matchD^ ← copyD^;
RETURN[TRUE];
};
ENDLOOP;
}
ELSE {
MatchViewer.ErrorFeedback["Only can deal with structure matches now."];
};
};
OutlineMatchOriented: PROC [trajs: LIST OF Traj, sliceLooks: LIST OF LIST OF LooksInfo, structure: BOOL, matchD: MatchDescriptor] RETURNS [BOOLTRUE] = {
We're matching on the shape or type (or both) of the Outline trajectories.
IF trajs=NIL THEN RETURN[TRUE]; -- base case
IF structure THEN {
traj: Traj ← trajs.first;
trajLooks: LIST OF LooksInfo ← sliceLooks.first;
looks: LooksInfo ← trajLooks.first;
segGen: SegmentGenerator ← GGSequence.SegmentsInTraj[traj];
copyD: MatchDescriptor ← NEW[MatchDObj ← matchD^];
IF NOT TrajMatch[traj, trajLooks, copyD, FALSE] THEN RETURN[FALSE];
IF looks.shapeDef AND NOT MatchTurtle.TurtleEqual[looks.shape, MatchTurtle.TrajToTurtle[traj], MatchViewer.GetMatchTolerance[], matchD.posD] THEN RETURN[FALSE];
IF NOT OutlineMatchOriented[trajs.rest, sliceLooks.rest, structure, copyD] THEN RETURN[FALSE]
ELSE matchD^ ← copyD^; -- only make changes in the descriptor if there's a match!
}
ELSE {
MatchViewer.ErrorFeedback["Only can deal with structure matches now."];
RETURN[FALSE];
};
};
TrajMatch: PROC [traj: Traj, trajLooks: LIST OF LooksInfo, matchD: MatchDescriptor, checkShape: BOOLTRUE] RETURNS [BOOLTRUE] = {
We're matching on shape or type (or both) of Outline trajectories
segGen: SegmentGenerator ← GGSequence.SegmentsInTraj[traj];
looks: LooksInfo ← trajLooks.first;
localMapping: LIST OF ItemMatch ← NIL;
IF looks.typeDef THEN {
First do traj & slice specific checks
IF looks.classDef AND looks.class # $Outline THEN RETURN[FALSE];
IF looks.jointsDef AND looks.joints # GGTraj.GetTrajStrokeJoint[traj] THEN RETURN[FALSE];
IF looks.fillColorDef AND NOT ColorEqual[looks.fillColor, traj.parent.class.getFillColor[traj.parent]] THEN RETURN [FALSE];
Next do lower level segment checks if typeDef is TRUE
IF traj.segCount # GList.Length[trajLooks] THEN RETURN[FALSE];
FOR seg: Segment ← GGSequence.NextSegment[segGen], GGSequence.NextSegment[segGen] UNTIL seg = NIL DO
IF NOT SegmentMatch[seg, trajLooks.first] THEN RETURN[FALSE];
localMapping ← CONS[NEW[ItemMatchObj ← [matcher: trajLooks.first.owner, matchee: [slice: traj.parent, traj: traj, seg: seg]]], localMapping]; -- looks good so far
trajLooks ← trajLooks.rest;
ENDLOOP;
}
ELSE { -- match the looks characteristics on the traj as a whole
seq: Sequence ← GGSequence.CreateComplete[traj];
sliceD: SliceDescriptor ← GGOutline.DescriptorFromSequence[traj.parent, seq];
IF NOT SimpleMatch[sliceD, looks, matchD, FALSE] THEN RETURN[FALSE];
localMapping ← LIST[NEW[ItemMatchObj ← [matcher: looks.owner, matchee: [slice: traj.parent, traj: traj]]]];
};
IF checkShape AND looks.shapeDef AND NOT MatchTurtle.TurtleEqualEitherWay[looks.shape, MatchTurtle.TrajToTurtle[traj], MatchViewer.GetMatchTolerance[], matchD.posD] THEN RETURN [FALSE];
matchD.mapping ← NARROW[GList.Append[localMapping, matchD.mapping]]; -- save mapping
};
Box utilities
ComplexBoxMatch: PROC [sliceD: SliceDescriptor, sliceLooks: LIST OF LIST OF LooksInfo, matchD: MatchDescriptor] RETURNS [BOOLTRUE] = {
Object type or shape is set but object class may not be set. Consider the box to be a set of $Line segments.
GatherSegs: GGModelTypes.WalkProc = {segList ← CONS[seg, segList];};
segList: LIST OF Segment ← NIL;
looks: LooksInfo;
slice: Slice ← sliceD.slice;
IF GList.Length[sliceLooks.first] # 4 THEN RETURN[FALSE]; -- boxes have 4 segs. We may have to change this later on if we allow wildcard segs
First do slice specific checks
looks ← sliceLooks.first.first;
IF looks.fillColorDef AND NOT ColorEqual[looks.fillColor, slice.class.getFillColor[slice]] THEN RETURN [FALSE];
IF looks.shapeDef AND NOT MatchTurtle.TurtleEqualEitherWay[looks.shape, MatchTurtle.GetShapeOfSlice[slice], MatchViewer.GetMatchTolerance[], matchD.posD] THEN RETURN [FALSE]; -- shape may be out of phase with segments!
Looks good so far. Now we match against the individual segments
[] ← GGSlice.WalkSegments[slice, GatherSegs];
segList ← NARROW[GList.DReverse[segList]];
IF NOT SegListMatch[segList, sliceLooks.first] THEN RETURN [FALSE];
FOR looksList: LIST OF LooksInfo ← sliceLooks.first, looksList.rest UNTIL looksList = NIL DO
AddMapping[matchD, looksList.first.owner, [slice: sliceD.slice, seg: segList.first]];
segList ← segList.rest;
ENDLOOP; -- register matches
};
SegListMatch: PROC [segList: LIST OF Segment, looks: LIST OF LooksInfo] RETURNS [BOOLTRUE] = {
IF GList.Length[segList] # GList.Length[looks] THEN RETURN[FALSE]; -- this will change with advent of wildcards
FOR segList ← segList, segList.rest UNTIL segList=NIL DO
IF NOT SegmentMatch[segList.first, looks.first] THEN RETURN[FALSE];
looks ← looks.rest;
ENDLOOP;
};
SegmentMatch: PROC [seg: Segment, looks: LooksInfo] RETURNS [BOOLTRUE] = {
Segment-Level checks
IF looks.typeDef AND looks.type # seg.class.type THEN RETURN[FALSE];
IF looks.colorDef AND NOT ColorEqual[looks.color, seg.color] THEN RETURN[FALSE];
IF looks.dashesDef THEN {
dashInfo: DashInfo ← NEW[DashInfoObj ← [dashed: seg.dashed, pattern: seg.pattern, offset: seg.offset, length: seg.length]];
IF NOT DashEqual[dashInfo, looks.dashes] THEN RETURN[FALSE];
};
IF looks.endsDef AND looks.ends # seg.strokeEnd THEN RETURN[FALSE];
IF looks.widthDef AND looks.width # seg.strokeWidth THEN RETURN[FALSE];
};
SliceLevelMatch: PROC [slice: Slice, traj: Traj ← NIL, looks: LooksInfo] RETURNS [BOOLTRUE] = {
Slice-Level checks
IF looks.classDef AND looks.class # slice.class.type THEN RETURN[FALSE];
IF looks.fillColorDef AND NOT ColorEqual[looks.fillColor, slice.class.getFillColor[slice]] THEN RETURN[FALSE];
IF slice.class.type = $Outline AND looks.jointsDef AND traj # NIL AND looks.joints # GGTraj.GetTrajStrokeJoint[traj] THEN RETURN[FALSE];
};
Comparing Look Properties
LooksEqual: PUBLIC PROC [l1, l2: LooksInfo] RETURNS [BOOLTRUE] = {
Doesn't compare the shape fields
IF l1 = l2 THEN RETURN[TRUE];
IF l1 = NIL OR l2 = NIL THEN RETURN[FALSE];
IF (l1.classDef # l2.classDef) OR (l1.typeDef # l2.typeDef) OR (l1.colorDef # l2.colorDef) OR (l1.fillColorDef # l2.fillColorDef) OR (l1.stringDef # l2.stringDef) OR (l1.fontDef # l2.fontDef) OR (l1.fontTformDef # l2.fontTformDef) OR (l1.dashesDef # l2.dashesDef) OR (l1.jointsDef # l2.jointsDef) OR (l1.endsDef # l2.endsDef) OR (l1.widthDef # l2.widthDef) THEN RETURN[FALSE];
IF l1.classDef AND l1.class # l2.class THEN RETURN[FALSE];
IF l1.typeDef AND l1.type # l2.type THEN RETURN[FALSE];
IF l1.colorDef AND NOT ColorEqual[l1.color, l2.color] THEN RETURN[FALSE];
IF l1.fillColorDef AND NOT ColorEqual[l1.fillColor, l2.fillColor] THEN RETURN[FALSE];
IF l1.stringDef AND NOT Rope.Equal[l1.string, l2.string] THEN RETURN[FALSE];
IF l1.fontDef AND NOT FontEqual[l1.font, l2.font] THEN RETURN[FALSE];
IF l1.fontTformDef AND NOT FontAndTransformEqual[l1.fontTform, l2.fontTform] THEN RETURN[FALSE];
IF l1.dashesDef AND NOT DashEqual[l1.dashes, l2.dashes] THEN RETURN[FALSE];
IF l1.jointsDef AND l1.joints # l2.joints THEN RETURN[FALSE];
IF l1.endsDef AND l1.ends # l2.ends THEN RETURN[FALSE];
IF l1.widthDef AND l1.width # l2.width THEN RETURN[FALSE];
};
FontAndTransformEqual: PROC [f1, f2: GGFont.FontData] RETURNS [match: BOOLFALSE] = {
Uses the criteria of GGEvent.SelectMatchingData
maxPixels: REAL = 10000.0;
IF f1 # NIL AND f2 # NIL AND Rope.Equal[f1.literal, f2.literal, FALSE] AND RealFns.AlmostEqual[f1.storedSize, f2.storedSize, -9] AND ImagerTransformation.CloseToTranslation[f1.transform, f2.transform, maxPixels] THEN RETURN[TRUE];
};
FontEqual: PROC [f1, f2: GGFont.FontData] RETURNS [match: BOOLFALSE] = {
IF f1 # NIL AND f2 # NIL AND Rope.Equal[f1.literal, f2.literal, FALSE] AND RealFns.AlmostEqual[f1.storedSize, f2.storedSize, -9] THEN RETURN[TRUE];
};
DashEqual: PROC [d1, d2: DashInfo] RETURNS [match: BOOLTRUE] = {
IF d1.dashed = FALSE AND d2.dashed = FALSE THEN RETURN [TRUE];
IF d1.dashed # d2.dashed OR d1.offset # d2.offset OR d1.length # d2.length OR d1.pattern.len # d2.pattern.len THEN RETURN [FALSE];
FOR i:INT IN [0..d1.pattern.len) DO
IF d1.pattern[i] # d2.pattern[i] THEN RETURN[FALSE];
ENDLOOP;
};
ColorEqual: PROC [c1, c2: Imager.Color] RETURNS [BOOL] = {
epsilon: REAL = 1.0E-2;
r1, g1, b1, r2, g2, b2: REAL;
IF c1 = c2 THEN RETURN [TRUE];
IF c1 = NIL OR c2 = NIL THEN RETURN[FALSE];
[r1, g1, b1] ← GGUtility.ExtractRGB[c1];
[r2, g2, b2] ← GGUtility.ExtractRGB[c2];
RETURN [(r1=r2 AND g1=g2 AND b1=b2) OR (ABS[r1-r2]<epsilon AND ABS[g1-g2]<epsilon AND ABS[b1-b2]<epsilon)];
};
Disjunctive Search Routines
SearchDisjInit: PUBLIC PROC [them: GGData, event: LIST OF REF ANY] = {
Find any object that matches an object in the From viewer (searchOp = disjunction)
matchData: MatchData ← MatchViewer.GetMatchData[];
searchState: SearchState ← Match.GetSearchState[];
IF them # matchData.theirData OR GGCaret.GetPoint[them.caret] # matchData.lastCaretPos OR event.rest.first = $SearchFromTop THEN {
searchState.ahead ← RemakeDisjList[them, event.rest.first, TRUE];
matchData.theirData ← them;
matchData.lastCaretPos ← GGCaret.GetPoint[them.caret];
} --searching in new viewer or caret position changed or starting search from top
ELSE IF event.rest.first # searchState.lastDirection THEN {
normalGen: SliceDescriptorGenerator ← GGSelect.SelectedSlices[them.scene, normal];
matchSelectD: SliceDescriptor ← NIL;
FOR normalD: SliceDescriptor ← GGSelect.NextSliceDescriptor[normalGen], GGSelect.NextSliceDescriptor[normalGen] UNTIL normalD = NIL DO
GGSelect.SelectSlice[normalD, them.scene, match];
ENDLOOP; -- make all normal-selected objects "match selected", so they're not included in search
IF searchState.ahead # NIL THEN {
firstSlice: Slice ← searchState.ahead.first.slice;
matchSelectD ← GGSelect.FindSelectedSlice[firstSlice, them.scene, match];
IF matchSelectD = NIL THEN matchSelectD ← firstSlice.class.newParts[firstSlice, NIL, none];
};
searchState.ahead ← RemakeDisjList[them, event.rest.first, TRUE];
RemoveSelected[searchState.ahead, matchSelectD]; --do bookkeeping so that search will begin (in the other direction) where the last search left off
}; --search direction changed
searchState.lastDirection ← IF event.rest.first = $SearchFromTop THEN $SearchBelow ELSE event.rest.first;
};
SearchDisjNext: PUBLIC PROC [searchList: LIST OF SearchInfo, direction: REF ANY, refresh: BOOLTRUE] RETURNS [newSearchList: LIST OF SearchInfo, found: BOOL] = {
target: SliceDescriptor ← NIL;
matchData: MatchData ← MatchViewer.GetMatchData[];
check, checkNext: SearchInfo ← NIL;
fromLooks: LIST OF REF ANY ← GetFromLooks[];
matchD: MatchDescriptor ← Match.CreateMatchDescriptor[];
DO
IF NOT IsEmptySearchInfo[check] THEN { -- something left of this slice to match on
[target, checkNext] ← FindMatchInSlice[check, fromLooks, direction, matchD];
DisjClearMatchBits[check, checkNext, matchData.theirData.scene];
IF target = NIL THEN check ← checkNext -- no match
ELSE { -- we've matched something!
fromData: GGData ← MatchViewer.GetFromData[];
GGSelect.DeselectAll[matchData.theirData.scene, normal];
IF NOT AtomButtons.GetBinaryState[matchData.contextSensitive] THEN Match.SelectMapped[matchData.theirData.scene, matchD.mapping]
ELSE Match.SelectMapped[matchData.theirData.scene, Match.SelectionFilter[fromData.scene, matchD.mapping]];
matchData.lastCaretPos ← [GGCaret.GetPoint[matchData.theirData.caret].x, target.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, backgndOK: TRUE, edited: FALSE, okToClearFeedback: TRUE];
Match.SetLastMatchDescriptor[matchD];
IF checkNext = NIL THEN ERROR; -- we no longer consider nil SearchInfo's empty
RETURN[CONS[checkNext, searchList], TRUE];
};
}
ELSE -- no more interesting stuff in slice
IF searchList # NIL THEN {
IF check # NIL THEN GGSelect.DeselectEntireSlice[check.slice, matchData.theirData.scene, match];
check ← searchList.first;
searchList ← searchList.rest;
}
ELSE {
GGSelect.DeselectAll[matchData.theirData.scene, normal];
IF refresh THEN GGWindow.RestoreScreenAndInvariants[paintAction: $SelectionChanged, ggData: matchData.theirData, remake: none, backgndOK: TRUE, edited: FALSE, okToClearFeedback: TRUE];
Match.SetLastMatchDescriptor[NIL];
RETURN[NIL, FALSE];
};
ENDLOOP;
};
RemakeDisjList: PUBLIC PROC [theirData: GGData, direction: REF ANY, select: BOOL] RETURNS [toSearch: LIST OF SearchInfo] = {
toSearch will hold SearchInfo's for objects whose boundBox is below the caret if the search is progressing in a downwards direction, and above the caret if the search is progressing upwards. If select is true, then all slices ahead will be match selected (and those behind will be cleared). This routine is called during disjunction search.
GetOrderedSlices: PROC [ggData: GGData, direction: REF ANY] RETURNS [sliceList: LIST OF Slice ← NIL] = {
sliceGen: SliceGenerator ← GGScene.SlicesInScene[ggData.scene];
caretPos: Point ← GGCaret.GetPoint[ggData.caret];
SELECT direction FROM
$SearchBelow => {
FOR slice: Slice ← GGScene.NextSlice[sliceGen], GGScene.NextSlice[sliceGen] UNTIL slice = NIL DO
IF caretPos.y >= slice.class.getBoundBox[slice].hiY THEN sliceList ← CONS[slice, sliceList];
ENDLOOP;
};
$SearchAbove => {
FOR slice: Slice ← GGScene.NextSlice[sliceGen], GGScene.NextSlice[sliceGen] UNTIL slice = NIL DO
IF caretPos.y <= slice.class.getBoundBox[slice].hiY THEN sliceList ← CONS[slice, sliceList];
ENDLOOP;
};
$SearchFromTop => {
FOR slice: Slice ← GGScene.NextSlice[sliceGen], GGScene.NextSlice[sliceGen] UNTIL slice = NIL DO
sliceList ← CONS[slice, sliceList];
ENDLOOP;
};
ENDCASE => ERROR;
sliceList ← HeightSort[sliceList, IF direction = $SearchAbove THEN FALSE ELSE TRUE];
};
caretPos: Point ← GGCaret.GetPoint[theirData.caret];
slices: LIST OF Slice ← GetOrderedSlices[theirData, direction];
IF select THEN GGSelect.DeselectAll[theirData.scene, match]; -- makes the subsequent selections zoom!
FOR sList: LIST OF Slice ← slices, sList.rest UNTIL sList = NIL DO
IF select THEN GGSelect.SelectEntireSlice[sList.first, theirData.scene, match];
toSearch ← CONS[CreateSearchInfo[sList.first, direction], toSearch];
ENDLOOP;
};
DisjClearMatchBits: PROC [first, next: SearchInfo, scene: Scene] = {
SegmentsInInfo: GGModelTypes.WalkProc = { RETURN[GList.Member[seg, next.segs]]; };
If the next SearchInfo is Empty, then deselect the entire slice described by first
IF IsEmptySearchInfo[next] THEN GGSelect.DeselectEntireSlice[first.slice, scene, match]
ELSE { -- a bit more complicated (part of next still remains to be searched)
SELECT first.slice.class.type FROM
$Box => {
newSelection: SliceDescriptor ← GGSlice.WalkSegments[next.slice, SegmentsInInfo];
GGSelect.DeselectEntireSlice[first.slice, scene, match];
GGSelect.SelectSlice[newSelection, scene, match];
};
$Outline => {
newSelection: SliceDescriptor ← GGSlice.WalkSegments[next.slice, SegmentsInInfo];
FOR trajList: LIST OF Traj ← next.trajs.rest, trajList.rest UNTIL trajList = NIL DO
seq: Sequence ← GGSequence.CreateComplete[trajList.first];
trajD: SliceDescriptor ← GGOutline.DescriptorFromSequence[seq.traj.parent, seq];
newSelection ← next.slice.class.unionParts[newSelection, trajD];
ENDLOOP;
GGSelect.DeselectEntireSlice[first.slice, scene, match];
GGSelect.SelectSlice[newSelection, scene, match];
};
ENDCASE => GGSelect.DeselectEntireSlice[first.slice, scene, match];
};
};
Bookkeeping to keep track of what parts of a slice have been visited, when a substitution or change of search direction has occurred.
RemoveSelected: PUBLIC PROC [infoList: LIST OF SearchInfo, sliceD: SliceDescriptor] = {
We have done a replace or changed the search direction.
sliceD is the SliceDescriptor of the "in progress" SearchInfo.
infoList is the forward SearchInfo list.
We remove the sliceD pieces from the forward list since they have already been looked at.
IF sliceD = NIL THEN RETURN;
FOR iList: LIST OF SearchInfo ← infoList, iList.rest UNTIL iList = NIL DO
IF sliceD.slice = iList.first.slice THEN RemoveDescriptorFromInfo[sliceD, iList.first];
ENDLOOP;
};
RemoveDescriptorFromInfo: PUBLIC PROC [sliceD: SliceDescriptor, info: SearchInfo] = {
FilterSegs: PROC [sliceD: SliceDescriptor, info: SearchInfo] = {
segList, revSegs: LIST OF Segment ← NIL;
segGen: SegmentGenerator ← GGSlice.SegmentsInDescriptor[sliceD];
FOR seg: Segment ← GGSlice.NextSegment[segGen], GGSlice.NextSegment[segGen] UNTIL seg = NIL DO
segList ← CONS [seg, segList];
ENDLOOP; -- we've formed a list of segments in the descriptor
revSegs ← NARROW[GList.Reverse[info.segs]];
info.segs ← NIL;
FOR infoSegs: LIST OF Segment ← revSegs, infoSegs.rest UNTIL infoSegs = NIL DO
IF GList.Member[infoSegs.first, segList] THEN EXIT
ELSE info.segs ← CONS[infoSegs.first, info.segs];
ENDLOOP;
};
matchLevel: MatchViewer.MatchLevel ← MatchViewer.GetMatchData[].matchLevel;
IF info.slice # sliceD.slice THEN RETURN; -- sliceD and info must relate to same slice
IF sliceD.slice.class.isEmptyParts[sliceD] THEN RETURN; -- nothing to remove
SELECT sliceD.slice.class.type FROM
$Box => {
IF matchLevel = sliceLevel OR matchLevel = trajLevel THEN info.empty ← TRUE
ELSE FilterSegs[sliceD, info]; -- matchLevel = anywhere
};
$Outline => {
IF matchLevel = sliceLevel THEN info.empty ← TRUE
ELSE IF matchLevel = trajLevel THEN {
revTrajs: LIST OF Traj ← NARROW[GList.Reverse[info.trajs]];
info.trajs ← NIL;
FOR trajs: LIST OF Traj ← revTrajs, trajs.rest UNTIL trajs = NIL DO
seq: Sequence ← GGOutline.FindTrajInDescriptor[sliceD, trajs.first];
IF NOT GGSequence.IsEmpty[seq] THEN EXIT
ELSE info.trajs ← CONS[trajs.first, info.trajs];
ENDLOOP;
}
ELSE { -- matchLevel = anywhere
oldTrajs: LIST OF Traj ← info.trajs;
revTrajs: LIST OF Traj ← NARROW[GList.Reverse[info.trajs]];
info.trajs ← NIL;
FOR trajs: LIST OF Traj ← revTrajs, trajs.rest UNTIL trajs = NIL DO
seq: Sequence ← GGOutline.FindTrajInDescriptor[sliceD, trajs.first];
info.trajs ← CONS[trajs.first, info.trajs];
IF NOT GGSequence.IsEmpty[seq] THEN EXIT;
ENDLOOP;
IF info.trajs.first # oldTrajs.first THEN {
info.segs ← NIL;
AddSegs[info];
};
FilterSegs[sliceD, info];
};
};
ENDCASE => info.empty ← TRUE;
};
General Search Utilities
HeightSort: PUBLIC PROC [slices: LIST OF Slice, ascending: BOOL] RETURNS [LIST OF Slice] = {
Height sorts slices by upper left of bounding box (according to the ascending BOOL). Breaks ties by using loX values (ordered according to the complement of the ascending BOOL), so if ascending = FALSE, search will proceed downwards and left to right.
CompareProc: GList.CompareProc = {
[ref1: REF ANY, ref2: REF ANY] RETURNS [Basics.Comparison]
slice1: Slice ← NARROW[ref1];
slice2: Slice ← NARROW[ref2];
priority1: REAL ← slice1.class.getBoundBox[slice1].hiY;
priority2: REAL ← slice2.class.getBoundBox[slice2].hiY;
IF priority1 = priority2 THEN {
priority1 ← slice2.class.getBoundBox[slice2].loX; -- slices swapped intentionally
priority2 ← slice1.class.getBoundBox[slice1].loX;
};
IF ascending THEN RETURN[Real.CompareREAL[priority1, priority2]]
ELSE RETURN[Real.CompareREAL[priority2, priority1]];
};
slices ← NARROW[GList.Sort[slices, CompareProc]];
RETURN[slices];
};
END.