<> <> <> <> <> <> DIRECTORY Basics, AtomButtons, GGBasicTypes, GGCaret, GGCoreOps, GGFont, GGInterfaceTypes, GGModelTypes, GGOutline, GGParent, GGScene, GGSegmentTypes, GGSelect, GGSequence, GGSlice, GGSliceOps, GGTraj, GGWindow, GList, ImagerTransformation, Match, MatchTurtle, MatchViewer, RealFns, Rope; MatchImplA: CEDAR PROGRAM IMPORTS AtomButtons, GGCaret, GGCoreOps, GGOutline, GGParent, GGScene, GGSelect, GGSequence, GGSlice, GGSliceOps, GGTraj, GGWindow, GList, ImagerTransformation, Match, MatchTurtle, MatchViewer, 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; TrajData: TYPE = GGModelTypes.TrajData; TurtleHeader: TYPE = MatchTurtle.TurtleHeader; TurtleInfo: TYPE = MatchTurtle.TurtleInfo; <> <<>> Search: PUBLIC PROC [toSearch: GGData, event: LIST OF REF ANY, refresh: BOOL _ TRUE] RETURNS [found: BOOL _ FALSE] = { matchData: MatchData _ MatchViewer.GetMatchData[]; searchState: SearchState _ Match.GetSearchState[]; SELECT matchData.searchOp FROM disjunction => { SearchDisjInit[toSearch, event]; -- initialize searchInfos et al [searchState.ahead, found] _ SearchDisjNext[searchState.ahead, event.rest.first, refresh]; }; conjunction => { Match.InitializeSearch[toSearch, event]; [searchState.ahead, searchState.behind, found] _ Match.SearchConjNext[searchState.ahead, searchState.behind, event.rest.first, refresh]; }; ENDCASE => { MatchViewer.ErrorFeedback["This SearchOp is not implemented."]; RETURN[FALSE]; }; }; <> <<>> IsEmptySearchInfo: PUBLIC PROC [searchInfo: SearchInfo] RETURNS [BOOL _ FALSE] = { IF searchInfo = NIL OR searchInfo.empty = TRUE THEN RETURN[TRUE]; IF GGSliceOps.GetType[searchInfo.slice] = $Outline AND searchInfo.kids = NIL THEN RETURN[TRUE]; }; CreateSearchInfo: PUBLIC PROC [slice: Slice, direction: REF ANY] RETURNS [searchInfo: SearchInfo] = { <> matchData: MatchData _ MatchViewer.GetMatchData[]; searchInfo _ NEW[SearchInfoObj _ [slice: slice, reverse: direction = $SearchAbove]]; SELECT GGSliceOps.GetType[slice] FROM $Outline => { searchInfo.kids _ GGOutline.TrajectoriesOfOutline[slice]; IF searchInfo.reverse THEN searchInfo.kids _ NARROW[GList.Reverse[searchInfo.kids], LIST OF Traj]; IF matchData.matchLevel = anywhere THEN CacheSegsOfFirstTraj[searchInfo]; }; $Box => IF matchData.matchLevel = anywhere THEN CacheSegsOfFirstTraj[searchInfo]; ENDCASE; }; CopySearchInfo: PROC [info: SearchInfo] RETURNS [newInfo: SearchInfo] = { <> newInfo _ NEW[SearchInfoObj _ info^]; }; MakeEmpty: PROC [info: SearchInfo] RETURNS [newInfo: SearchInfo] = { newInfo _ CopySearchInfo[info]; newInfo.empty _ TRUE; }; CacheSegsOfFirstTraj: PUBLIC PROC [info: SearchInfo] = { <> info.segs _ NIL; SELECT GGSliceOps.GetType[info.slice] FROM $Outline => { traj: Slice; trajData: TrajData; IF info.kids = NIL THEN RETURN; -- nothing to fill in traj _ info.kids.first; trajData _ NARROW[traj.data]; FOR i: INT IN [0..GGTraj.HiSegment[traj]] DO info.segs _ CONS[GGTraj.FetchSegment[traj, i], info.segs]; ENDLOOP; IF NOT info.reverse THEN info.segs _ NARROW[GList.DReverse[info.segs]]; <> IF trajData.role # open OR ManhattanDistance[ GGTraj.FetchJointPos[traj, 0], GGTraj.FetchJointPos[traj, GGTraj.HiJoint[traj]]] < 1.0 THEN info.closedTraj _ TRUE ELSE info.closedTraj _ FALSE; }; $Box => { FOR i: INT IN [0..4) DO info.segs _ CONS[GGSlice.BoxFetchSegment[info.slice, i], info.segs]; ENDLOOP; IF NOT info.reverse THEN info.segs _ NARROW[GList.DReverse[info.segs], LIST OF Segment]; info.closedTraj _ TRUE; }; ENDCASE => ERROR; }; <<>> <> <<>> GetFromLooks: PUBLIC PROC RETURNS [looksList: LIST OF REF ANY _ NIL] = { AddLooksToList: PROC [slice: Slice] RETURNS [done: BOOL _ FALSE] = { looksList _ CONS[GetLooksOfSlice[slice, matchData.from], looksList]; }; matchData: MatchData _ MatchViewer.GetMatchData[]; fromData: GGData _ MatchViewer.GetFromData[]; [] _ GGScene.WalkSlices[fromData.scene, first, AddLooksToList]; }; GetToLooks: PUBLIC PROC RETURNS [looksList: LIST OF REF ANY _ NIL] = { AddLooksToList: PROC [slice: Slice] RETURNS [done: BOOL _ FALSE] = { looksList _ CONS[GetLooksOfSlice[slice, matchData.to], looksList]; }; matchData: MatchData _ MatchViewer.GetMatchData[]; toData: GGData _ MatchViewer.GetToData[]; [] _ GGScene.WalkSlices[toData.scene, first, AddLooksToList]; }; GetLooksOfSlice: PUBLIC PROC [slice: Slice, choice: ChoiceData, value: BOOL _ TRUE] RETURNS [REF ANY] = { <> targetParts: SliceParts _ GGSliceOps.NewParts[slice, NIL, slice].parts; looks: LooksInfo _ NEW[LooksInfoObj]; IF GGSliceOps.GetType[slice] = $Outline OR GGSliceOps.GetType[slice] = $Box THEN { looksList: LIST OF LIST OF LooksInfo _ GetCompositeLooksOfSlice[slice, choice, value]; RETURN[looksList]; }; looks.owner.slice _ slice; -- this will suffice IF AtomButtons.GetBinaryState[choice.class] = value THEN { -- Class looks.classDef _ TRUE; looks.class _ GGSliceOps.GetType[slice]; }; IF AtomButtons.GetBinaryState[choice.type] = value THEN { -- Type looks.typeDef _ TRUE; looks.type _ GGSliceOps.GetType[slice]; }; 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 _ GGSliceOps.GetStrokeColor[slice, targetParts].color; }; IF AtomButtons.GetBinaryState[choice.fillColor] = value THEN { -- Fill Color looks.fillColorDef _ TRUE; looks.fillColor _ GGSliceOps.GetFillColor[slice, targetParts].color; }; IF AtomButtons.GetBinaryState[choice.string] = value THEN { -- String IF GGSliceOps.GetType[slice] = $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] _ GGSliceOps.GetDashed[slice, targetParts]; }; IF AtomButtons.GetBinaryState[choice.joints] = value THEN { -- Joints looks.jointsDef _ TRUE; looks.joints _ GGSliceOps.GetStrokeJoint[slice, targetParts].strokeJoint; }; IF AtomButtons.GetBinaryState[choice.ends] = value THEN { -- Ends looks.endsDef _ TRUE; looks.ends _ GGSliceOps.GetStrokeEnd[slice, targetParts].strokeEnd; }; IF AtomButtons.GetBinaryState[choice.width] = value THEN { -- Width looks.widthDef _ TRUE; looks.width _ GGSliceOps.GetStrokeWidth[slice, targetParts].strokeWidth; }; RETURN [looks]; }; GetCompositeLooksOfSlice: PROC [slice: Slice, choice: ChoiceData, value: BOOL _ TRUE] RETURNS [looksList: LIST OF LIST OF LooksInfo _ NIL] = { DoConsLooks: PROC [traj: Slice] RETURNS [done: BOOL _ FALSE] = { looksList _ CONS[GetLooksOfTraj[traj, choice, value], looksList]; }; shape: TurtleHeader; traj: Traj _ NIL; -- must be defined here so it's defined at GetLooksInSegment call-back class: ATOM _ GGSliceOps.GetType[slice]; -- for use in call-back IF GGSliceOps.GetType[slice] = $Outline THEN { [] _ GGParent.WalkChildren[slice, first, DoConsLooks, $Traj]; } ELSE { GatherSegs: GGModelTypes.WalkProc = {segList _ CONS[seg, segList]; keep _ FALSE;}; segList: LIST OF Segment _ NIL; subList: LIST OF LooksInfo _ NIL; IF AtomButtons.GetBinaryState[choice.shape] = value THEN shape _ MatchTurtle.GetShapeOfSlice[slice]; [] _ GGSliceOps.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]; }; GetLooksOfSegment: PUBLIC PROC [slice: Slice, traj: Traj, seg: Segment, choice: ChoiceData, value: BOOL _ FALSE, 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 _ GGSliceOps.GetType[slice]; }; 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 _ GGSliceOps.GetFillColor[slice, NIL].color; }; 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 GGSliceOps.GetStrokeJoint[slice, GGSliceOps.NewParts[slice, NIL, slice]].strokeJoint ELSE GGSliceOps.GetStrokeJoint[traj, NIL].strokeJoint; }; 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; }; }; GetLooksOfTraj: PUBLIC PROC [traj: Traj, choice: ChoiceData, value: BOOL _ TRUE] 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..GGTraj.HiSegment[traj]] DO trajLooks _ CONS[GetLooksOfSegment[slice: traj.parent, traj: traj, seg: GGTraj.FetchSegment[traj, i], choice: choice, value: value, shape: shape], trajLooks]; ENDLOOP; }; <<>> <> <<>> FindMatchInSlice: PUBLIC PROC [searchInfo: SearchInfo, fromLooks: LIST OF REF ANY, direction: REF ANY, matchD: MatchDescriptor] RETURNS [match: SliceDescriptor _ NIL, rest: SearchInfo _ NIL] = { IF searchInfo.empty THEN RETURN[NIL, searchInfo]; SELECT GGSliceOps.GetType[searchInfo.slice] FROM $Circle => [match, rest] _ FindMatchInCircle[searchInfo, fromLooks, matchD]; $Text => [match, rest] _ FindMatchInText[searchInfo, fromLooks, matchD]; $Box => [match, rest] _ FindMatchInBox[searchInfo, fromLooks, matchD]; $Outline => [match, rest] _ FindMatchInOutline[searchInfo, fromLooks, matchD]; ENDCASE; -- can't match on this kind of slice }; FindMatchInCircle: PROC [searchInfo: SearchInfo, fromLooks: LIST OF REF ANY, matchD: MatchDescriptor] RETURNS [match: SliceDescriptor, rest: SearchInfo] = { entireCircleD: SliceDescriptor _ GGSliceOps.NewParts[searchInfo.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[entireCircleD, looks, matchD] THEN RETURN[entireCircleD, MakeEmpty[searchInfo]]; ENDLOOP; RETURN[NIL, MakeEmpty[searchInfo]]; }; FindMatchInText: PROC [searchInfo: SearchInfo, fromLooks: LIST OF REF ANY, matchD: MatchDescriptor] RETURNS [match: SliceDescriptor, rest: SearchInfo] = { entireTextD: SliceDescriptor _ GGSliceOps.NewParts[searchInfo.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[entireTextD, looks, matchD] THEN RETURN[entireTextD, MakeEmpty[searchInfo]]; ENDLOOP; RETURN[NIL, MakeEmpty[searchInfo]]; }; FindMatchInBox: PROC [searchInfo: SearchInfo, fromLooks: LIST OF REF ANY, matchD: MatchDescriptor] RETURNS [match: SliceDescriptor, rest: SearchInfo] = { matchLevel: MatchViewer.MatchLevel _ MatchViewer.GetMatchData[].matchLevel; entireBoxD: SliceDescriptor _ GGSliceOps.NewParts[searchInfo.slice, NIL, slice]; looks: LooksInfo; complexLooks: LIST OF LIST OF LooksInfo; IF matchLevel = anywhere THEN { [match, rest] _ FindAnywhereMatchInBox[searchInfo, 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 = clusterLevel 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[entireBoxD, complexLooks, matchD] THEN RETURN[entireBoxD, MakeEmpty[searchInfo]]; } ELSE { IF SimpleMatch[entireBoxD, looks, matchD] THEN RETURN[entireBoxD, MakeEmpty[searchInfo]]; }; } ELSE ERROR; ENDLOOP; RETURN[NIL, MakeEmpty[searchInfo]]; }; FindMatchInOutline: PROC [searchInfo: SearchInfo, fromLooks: LIST OF REF ANY, matchD: MatchDescriptor] RETURNS [match: SliceDescriptor, rest: SearchInfo] = { entireSliceD: SliceDescriptor _ GGSliceOps.NewParts[searchInfo.slice, NIL, slice]; looks: LooksInfo; complexLooks: LIST OF LIST OF LooksInfo; matchLevel: MatchViewer.MatchLevel _ MatchViewer.GetMatchData[].matchLevel; IF matchLevel = trajLevel THEN { [match, rest] _ FindTrajMatchInOutline[searchInfo, fromLooks, matchD]; RETURN[match, rest]; }; IF matchLevel = anywhere THEN { [match, rest] _ FindAnywhereMatchInOutline[searchInfo, 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 = clusterLevel THEN { trajList: LIST OF Traj _ GGOutline.TrajectoriesOfOutline[searchInfo.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[entireSliceD, complexLooks, matchD] THEN RETURN[entireSliceD, MakeEmpty[searchInfo]]; } ELSE { IF SimpleMatch[entireSliceD, looks, matchD] THEN RETURN[entireSliceD, MakeEmpty[searchInfo]]; }; } ENDLOOP; RETURN[NIL, MakeEmpty[searchInfo]]; }; FindAnywhereMatchInBox: PROC [searchInfo: 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 searchInfo.segs first, last: NAT; -- refer to the span of segments relative to the box startSeg: Segment; [start, length] _ SegLooksMatch[searchInfo, fromLooks, matchD]; -- note: start is relative to start of list, not start of traj IF length < 1 THEN RETURN[NIL, NIL]; <> startSeg _ NARROW[GList.NthElement[searchInfo.segs, start+1], Segment]; FOR i:INT IN [0..4) DO IF GGSlice.BoxFetchSegment[searchInfo.slice, i] = startSeg THEN {first _ i; EXIT;}; ENDLOOP; IF NOT searchInfo.reverse THEN { last _ (first + length - 1) MOD segCount; } ELSE { last _ first; first _ (first - length + 1 + segCount) MOD segCount; -- + segCount to keep positive }; <> match _ GGSliceOps.WalkSegments[searchInfo.slice, SelectInRange]; rest _ CopySearchInfo[searchInfo]; rest.segs _ NARROW[GList.NthTail[rest.segs, start + length], LIST OF Segment]; }; FindAnywhereMatchInOutline: PROC [searchInfo: SearchInfo, fromLooks: LIST OF REF ANY, matchD: MatchDescriptor] RETURNS [match: SliceDescriptor, rest: SearchInfo] = { matchSeq: Sequence; traj: Traj _ searchInfo.kids.first; startSeg: Segment; start, length: NAT; -- refer to a span of segments on searchInfo.segs first, last: NAT; -- refer to a span of segments in the new sequence (ie, relative to the traj) segCount: NAT; searchInfo _ CopySearchInfo[searchInfo]; [start, length] _ SegLooksMatch[searchInfo, 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 searchInfo.kids _ searchInfo.kids.rest; CacheSegsOfFirstTraj[searchInfo]; RETURN[NIL, searchInfo]; }; <> startSeg _ NARROW[GList.NthElement[searchInfo.segs, start+1], Segment]; segCount _ GGTraj.HiSegment[traj]+1; FOR i:INT IN [0..segCount) DO IF GGTraj.FetchSegment[traj, i] = startSeg THEN {first _ i; EXIT}; ENDLOOP; IF NOT searchInfo.reverse THEN { last _ (first + length - 1) MOD segCount; } ELSE { last _ first; first _ (first - length + 1 + segCount) MOD segCount; -- + segCount to keep positive }; <> BEGIN trajData: TrajData _ NARROW[traj.data]; searchInfo.segs _ NARROW[GList.NthTail[searchInfo.segs, start + length], LIST OF Segment]; matchSeq _ GGSlice.DescriptorFromParts[traj, GGSequence.CreateFromSegments[trajData, first, last]]; match _ GGParent.DescriptorFromChildDescriptor[searchInfo.slice, matchSeq]; END; RETURN[match, searchInfo]; }; FindTrajMatchInOutline: PROC [searchInfo: SearchInfo, fromLooks: LIST OF REF ANY, matchD: MatchDescriptor] RETURNS [match: SliceDescriptor, rest: SearchInfo] = { looks: LooksInfo; complexLooks: LIST OF LIST OF LooksInfo; searchInfo _ CopySearchInfo[searchInfo]; FOR trajs: LIST OF Traj _ searchInfo.kids, trajs.rest UNTIL trajs=NIL DO currentTraj: Traj _ trajs.first; searchInfo.kids _ 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 trajData: TrajData _ NARROW[currentTraj.data]; seq: SliceDescriptor _ GGSlice.DescriptorFromParts[ currentTraj, GGSequence.CreateComplete[trajData] ]; sliceD: SliceDescriptor _ GGParent.DescriptorFromChildDescriptor[searchInfo.slice, seq]; IF SimpleMatch[sliceD, looks, matchD] THEN RETURN[sliceD, searchInfo]; } ELSE { FOR list: LIST OF LIST OF LooksInfo _ complexLooks, list.rest UNTIL list=NIL DO IF TrajMatch[currentTraj, list.first, matchD] THEN { trajData: TrajData _ NARROW[currentTraj.data]; seq: SliceDescriptor _ GGSlice.DescriptorFromParts[ currentTraj, GGSequence.CreateComplete[trajData] ]; match _ GGParent.DescriptorFromChildDescriptor[searchInfo.slice, seq]; RETURN[match, searchInfo]; }; ENDLOOP; }; ENDLOOP; ENDLOOP; RETURN[NIL, searchInfo]; }; <> <<>> ManhattanDistance: PROC [pt1, pt2: Point] RETURNS [REAL] = { RETURN[ABS[pt2.x - pt1.x] + ABS[pt2.y - pt1.y]]; }; SegLooksMatch: PROC [searchInfo: SearchInfo, fromLooks: LIST OF REF ANY, matchD: MatchDescriptor] RETURNS [start, length: NAT _ 0] = { noneFound: BOOL _ TRUE; newStart, newLength: NAT; looks: LooksInfo; complexLooks: LIST OF LooksInfo; reverse: BOOL _ FALSE; 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[searchInfo.slice, IF searchInfo.kids = NIL THEN NIL ELSE searchInfo.kids.first, looks] THEN LOOP; saveDObj _ matchD^; IF NOT looks.typeDef THEN [newStart, newLength, reverse] _ SimpleSegMatch[searchInfo, looks, matchD] ELSE [newStart, newLength, reverse] _ CompoundSegMatch[searchInfo, 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[searchInfo, 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 ANY] = { 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 [searchInfo: SearchInfo, start, length: NAT, reverse: BOOL _ FALSE, looks: LooksInfo, complexLooks: LIST OF LooksInfo] RETURNS [mapping: LIST OF ItemMatch _ NIL] = { segList: LIST OF Segment _ NARROW[GList.NthTail[searchInfo.segs, start]]; traj: Traj _ IF GGSliceOps.GetType[searchInfo.slice] = $Outline THEN searchInfo.kids.first ELSE NIL; <> THROUGH [0..length) DO newItem: ItemMatch; IF looks.typeDef THEN { newItem _ NEW[ItemMatchObj _ [matcher: complexLooks.first.owner, matchee: [slice: searchInfo.slice, traj: traj, seg: segList.first], backwards: reverse]]; complexLooks _ complexLooks.rest; } ELSE { newItem _ NEW[ItemMatchObj _ [matcher: looks.owner, matchee: [slice: searchInfo.slice, traj: traj, seg: segList.first], backwards: reverse]]; }; mapping _ CONS[newItem, mapping]; segList _ segList.rest; ENDLOOP; }; SimpleSegMatch: PROC [searchInfo: SearchInfo, looks: LooksInfo, matchD: MatchDescriptor] RETURNS [start, length: NAT _ 0, reverse: BOOL _ FALSE] = { IF NOT looks.shapeDef THEN { -- Grab first run that fits description index: NAT _ 0; FOR segs: LIST OF Segment _ searchInfo.segs, segs.rest UNTIL segs=NIL DO match: BOOL _ SegmentMatch[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[searchInfo, looks, matchD]; RETURN[start, length, reverse]; }; }; ShapeDrivenSegMatch: PROC [searchInfo: SearchInfo, looks: LooksInfo, matchD: MatchDescriptor] RETURNS [start, length: NAT _ 0, reverse: BOOL _ FALSE] = { <> matchData: MatchData _ MatchViewer.GetMatchData[]; revCheck: BOOL _ NOT 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 _ searchInfo.segs, segs.rest UNTIL segs = NIL DO tail: LIST OF TurtleInfo _ NIL; ends: LIST OF LIST OF TurtleInfo _ NIL; <> 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, searchInfo.reverse]; ends _ CONS[MatchTurtle.CopyTurtleTail[tail], ends]; -- list of backwards turtleTails } ELSE EXIT; ENDLOOP; <> FOR matchList _ matchList, matchList.rest UNTIL matchList = NIL DO -- matchList is backwards turtle: TurtleHeader _ MatchTurtle.PackageTurtleTail[ends.first]; revTurtle: TurtleHeader _ turtle; IF searchInfo.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 [searchInfo: SearchInfo, looks: LIST OF LooksInfo, matchD: MatchDescriptor] RETURNS [start, length: NAT _ 0, reverse: BOOL _ FALSE] = { <> matchData: MatchData _ MatchViewer.GetMatchData[]; revCheck: BOOL _ NOT 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 _ searchInfo.segs, subList.rest UNTIL subList = NIL DO foreMatch: BOOL _ TRUE; 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, searchInfo.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 searchInfo.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; <> }; FlipLooks: PROC [looksList: LIST OF LooksInfo] RETURNS [newList: LIST OF LooksInfo] = { newList _ NARROW[GList.Reverse[looksList], LIST OF LooksInfo]; <> }; <<>> SimpleMatch: PROC [sliceD: SliceDescriptor, looks: LooksInfo, matchD: MatchDescriptor _ NIL, checkShape: BOOL _ TRUE] RETURNS [match: BOOL _ TRUE] = { <> slice: Slice _ sliceD.slice; IF looks.typeDef AND looks.type # GGSliceOps.GetType[slice] THEN RETURN[FALSE]; IF looks.classDef AND looks.class # GGSliceOps.GetType[slice] THEN RETURN[FALSE]; IF looks.colorDef AND NOT GGCoreOps.EquivalentColors[looks.color, GGSliceOps.GetStrokeColor[slice, sliceD.parts].color] THEN RETURN[FALSE]; IF looks.fillColorDef AND NOT GGCoreOps.EquivalentColors[looks.fillColor, GGSliceOps.GetFillColor[slice, sliceD.parts].color] THEN RETURN[FALSE]; IF looks.stringDef AND (GGSliceOps.GetType[slice] # $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] _ GGSliceOps.GetDashed[slice, sliceD.parts]; IF NOT DashEqual[dashInfo, looks.dashes] THEN RETURN[FALSE]; }; IF looks.jointsDef AND looks.joints # GGSliceOps.GetStrokeJoint[slice, sliceD.parts].strokeJoint THEN RETURN[FALSE]; IF looks.endsDef AND looks.ends # GGSliceOps.GetStrokeEnd[slice, sliceD.parts].strokeEnd THEN RETURN[FALSE]; IF looks.widthDef AND looks.width # GGSliceOps.GetStrokeWidth[slice, sliceD.parts].strokeWidth THEN RETURN[FALSE]; IF checkShape AND looks.shapeDef THEN { IF GGSliceOps.GetType[slice] = $Outline THEN { seqs: LIST OF SliceDescriptor _ GGParent.ListIncludedChildren[sliceD.slice, sliceD.parts, first]; 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.slice], 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: BOOL _ FALSE] = { 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 [BOOL _ TRUE] = { <> SELECT GGSliceOps.GetType[sliceD.slice] FROM $Outline => RETURN[ComplexOutlineMatch[sliceD, sliceLooks, matchD]]; $Box => RETURN[ComplexBoxMatch[sliceD, sliceLooks, matchD]]; ENDCASE => ERROR; }; SliceDToItemID: PROC [sliceD: SliceDescriptor] RETURNS [itemID: ItemID] = { <> itemID.slice _ sliceD.slice; IF GGSliceOps.GetType[sliceD.slice] = $Outline THEN { childD: SliceDescriptor _ GGParent.FirstIncludedChild[sliceD.slice, sliceD.parts, first]; <> itemID.traj _ childD.slice; }; }; <<>> <> ComplexOutlineMatch: PROC [sliceD: SliceDescriptor, sliceLooks: LIST OF LIST OF LooksInfo, matchD: MatchDescriptor] RETURNS [BOOL _ FALSE] = { <> matchData: MatchData _ MatchViewer.GetMatchData[]; structure: BOOL _ TRUE; 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. <> 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 _ FALSE, matchD: MatchDescriptor] RETURNS [BOOL _ TRUE] = { <> IF trajs=NIL THEN RETURN[TRUE]; -- base case IF structure THEN { traj: Traj _ trajs.first; trajData: TrajData _ NARROW[traj.data]; trajLooks: LIST OF LooksInfo _ sliceLooks.first; looks: LooksInfo _ trajLooks.first; segGen: SegmentGenerator _ GGSequence.SegmentsInTraj[trajData]; 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: BOOL _ TRUE] RETURNS [BOOL _ TRUE] = { <> trajData: TrajData _ NARROW[traj.data]; segGen: SegmentGenerator _ GGSequence.SegmentsInTraj[trajData]; looks: LooksInfo _ trajLooks.first; localMapping: LIST OF ItemMatch _ NIL; IF looks.typeDef THEN { <> IF looks.classDef AND looks.class # $Outline THEN RETURN[FALSE]; IF looks.jointsDef AND looks.joints # GGSliceOps.GetStrokeJoint[traj, NIL].strokeJoint THEN RETURN[FALSE]; IF looks.fillColorDef AND NOT GGCoreOps.EquivalentColors[looks.fillColor, GGSliceOps.GetFillColor[traj.parent, NIL].color] THEN RETURN [FALSE]; <> IF trajData.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 _ GGSlice.DescriptorFromParts[ traj, GGSequence.CreateComplete[trajData] ]; sliceD: SliceDescriptor _ GGParent.DescriptorFromChildDescriptor[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 }; <<>> <> <<>> ComplexBoxMatch: PROC [sliceD: SliceDescriptor, sliceLooks: LIST OF LIST OF LooksInfo, matchD: MatchDescriptor] RETURNS [BOOL _ TRUE] = { <> GatherSegs: GGModelTypes.WalkProc = {segList _ CONS[seg, segList]; keep _ TRUE}; 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 <> looks _ sliceLooks.first.first; IF looks.fillColorDef AND NOT GGCoreOps.EquivalentColors[looks.fillColor, GGSliceOps.GetFillColor[sliceD.slice, sliceD.parts].color] 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! <> [] _ GGSliceOps.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 [BOOL _ TRUE] = { 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 [BOOL _ TRUE] = { <> IF looks.typeDef AND looks.type # seg.class.type THEN RETURN[FALSE]; IF looks.colorDef AND NOT GGCoreOps.EquivalentColors[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 [BOOL _ TRUE] = { <> IF looks.classDef AND looks.class # GGSliceOps.GetType[slice] THEN RETURN[FALSE]; IF looks.fillColorDef AND NOT GGCoreOps.EquivalentColors[looks.fillColor, GGSliceOps.GetFillColor[slice, NIL].color] THEN RETURN[FALSE]; IF GGSliceOps.GetType[slice] = $Outline AND looks.jointsDef AND traj # NIL AND looks.joints # GGSliceOps.GetStrokeJoint[traj, NIL].strokeJoint THEN RETURN[FALSE]; }; <> LooksEqual: PUBLIC PROC [l1, l2: LooksInfo] RETURNS [BOOL _ TRUE] = { <> 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 GGCoreOps.EquivalentColors[l1.color, l2.color] THEN RETURN[FALSE]; IF l1.fillColorDef AND NOT GGCoreOps.EquivalentColors[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: BOOL _ FALSE] = { <> 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: BOOL _ FALSE] = { 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: BOOL _ TRUE] = { 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; }; <> SearchDisjInit: PUBLIC PROC [them: GGData, event: LIST OF REF ANY] = { <> 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]; } ELSE IF event.rest.first # searchState.lastDirection THEN { -- reverse search direction <> DoSelect: PROC [normalD: SliceDescriptor] RETURNS [done: BOOL _ FALSE] = { GGSelect.SelectSlice[normalD, them.scene, match]; }; matchSelectD: SliceDescriptor _ NIL; [] _ GGScene.WalkSelectedSlices[them.scene, first, DoSelect, normal]; IF searchState.ahead # NIL THEN { firstSlice: Slice _ searchState.ahead.first.slice; <> matchSelectD _ GGSelect.FindSelectedSlice[firstSlice, match]; IF matchSelectD = NIL THEN matchSelectD _ GGSliceOps.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 }; <> searchState.lastDirection _ IF event.rest.first = $SearchFromTop THEN $SearchBelow ELSE event.rest.first; }; SearchDisjNext: PUBLIC PROC [searchList: LIST OF SearchInfo, direction: REF ANY, refresh: BOOL _ TRUE] RETURNS [newSearchList: LIST OF SearchInfo, found: BOOL _ FALSE] = { 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, edited: FALSE, okToSkipCapture: FALSE]; Match.RememberMatchDescriptor[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, edited: FALSE, okToSkipCapture: FALSE]; Match.RememberMatchDescriptor[NIL]; RETURN[NIL, FALSE]; }; ENDLOOP; }; RemakeDisjList: PUBLIC PROC [theirData: GGData, direction: REF ANY, select: BOOL _ FALSE] RETURNS [toSearch: LIST OF SearchInfo] = { <> GetOrderedSlices: PROC [ggData: GGData, direction: REF ANY] RETURNS [sliceList: LIST OF Slice _ NIL] = { scene: Scene _ ggData.scene; caretPos: Point _ GGCaret.GetPoint[ggData.caret]; SELECT direction FROM $SearchBelow => { DoSearchBelow: PROC [slice: Slice] RETURNS [done: BOOL _ FALSE] = { IF caretPos.y >= GGSliceOps.GetBoundBox[slice].hiY THEN sliceList _ CONS[slice, sliceList]; }; [] _ GGScene.WalkSlices[scene, first, DoSearchBelow]; }; $SearchAbove => { DoSearchAbove: PROC [slice: Slice] RETURNS [done: BOOL _ FALSE] = { IF caretPos.y <= GGSliceOps.GetBoundBox[slice].hiY THEN sliceList _ CONS[slice, sliceList]; }; [] _ GGScene.WalkSlices[scene, first, DoSearchAbove]; }; $SearchFromTop => { DoSearchFromTop: PROC [slice: Slice] RETURNS [done: BOOL _ FALSE] = { sliceList _ CONS[slice, sliceList]; }; [] _ GGScene.WalkSlices[scene, first, DoSearchFromTop]; }; 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]; -- speeds subsequent selections 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 IsEmptySearchInfo[next] THEN GGSelect.DeselectEntireSlice[first.slice, scene, match] ELSE { -- a bit more complicated (part of next still remains to be searched) SELECT GGSliceOps.GetType[first.slice] FROM $Box => { newSelection: SliceDescriptor _ GGSliceOps.WalkSegments[next.slice, SegmentsInInfo]; GGSelect.DeselectEntireSlice[first.slice, scene, match]; GGSelect.SelectSlice[newSelection, scene, match]; }; $Outline => { newSelection: SliceDescriptor _ GGSliceOps.WalkSegments[next.slice, SegmentsInInfo]; FOR trajList: LIST OF Traj _ next.kids.rest, trajList.rest UNTIL trajList = NIL DO trajData: TrajData _ NARROW[trajList.first.data]; seq: Sequence _ GGSlice.DescriptorFromParts[ trajList.first, GGSequence.CreateComplete[trajData] ]; trajD: SliceDescriptor _ GGParent.DescriptorFromChildDescriptor[seq.slice.parent, seq]; newSelection _ GGSliceOps.UnionParts[newSelection, trajD]; ENDLOOP; GGSelect.DeselectEntireSlice[first.slice, scene, match]; GGSelect.SelectSlice[newSelection, scene, match]; }; ENDCASE => GGSelect.DeselectEntireSlice[first.slice, scene, match]; }; }; <> <<>> RemoveSelected: PUBLIC PROC [infoList: LIST OF SearchInfo, sliceD: SliceDescriptor] = { <> <> <> <> 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 _ GGSliceOps.SegmentsInDescriptor[sliceD]; FOR seg: Segment _ GGSliceOps.NextSegment[sliceD.slice, segGen].seg, GGSliceOps.NextSegment[sliceD.slice, segGen].seg 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 GGSliceOps.IsEmptyParts[sliceD] THEN RETURN; -- nothing to remove SELECT GGSliceOps.GetType[sliceD.slice] FROM $Box => { IF matchLevel = clusterLevel OR matchLevel = trajLevel THEN info.empty _ TRUE ELSE FilterSegs[sliceD, info]; -- matchLevel = anywhere }; $Outline => { IF matchLevel = clusterLevel THEN info.empty _ TRUE ELSE IF matchLevel = trajLevel THEN { revTrajs: LIST OF Traj _ NARROW[GList.Reverse[info.kids]]; info.kids _ NIL; FOR trajs: LIST OF Traj _ revTrajs, trajs.rest UNTIL trajs = NIL DO seq: SliceDescriptor _ GGParent.ChildDescriptorFromDescriptor[sliceD, trajs.first]; IF NOT GGSliceOps.IsEmptyParts[seq] THEN EXIT ELSE info.kids _ CONS[trajs.first, info.kids]; ENDLOOP; } ELSE { -- matchLevel = anywhere oldTrajs: LIST OF Traj _ info.kids; revTrajs: LIST OF Traj _ NARROW[GList.Reverse[info.kids]]; info.kids _ NIL; FOR trajs: LIST OF Traj _ revTrajs, trajs.rest UNTIL trajs = NIL DO seq: SliceDescriptor _ GGParent.ChildDescriptorFromDescriptor[sliceD, trajs.first]; info.kids _ CONS[trajs.first, info.kids]; IF NOT GGSliceOps.IsEmptyParts[seq] THEN EXIT; ENDLOOP; IF info.kids.first # oldTrajs.first THEN { info.segs _ NIL; CacheSegsOfFirstTraj[info]; }; FilterSegs[sliceD, info]; }; }; ENDCASE => info.empty _ TRUE; }; <<>> <> <<>> HeightSort: PUBLIC PROC [slices: LIST OF Slice, ascending: BOOL _ FALSE] RETURNS [LIST OF Slice] = { <> CompareReal: PROC [r1, r2: REAL] RETURNS [Basics.Comparison] ~ { RETURN [IF r1=r2 THEN equal ELSE IF r1>r2 THEN greater ELSE less]; }; CompareProc: GList.CompareProc = { <<[ref1: REF ANY, ref2: REF ANY] RETURNS [Basics.Comparison]>> slice1: Slice _ NARROW[ref1]; slice2: Slice _ NARROW[ref2]; epsilon: REAL _ 0.072; -- 0.001 inches priority1: REAL _ GGSliceOps.GetBoundBox[slice1].hiY; priority2: REAL _ GGSliceOps.GetBoundBox[slice2].hiY; IF ABS[priority1 - priority2] <= 0.0 THEN { -- sort left to right priority1 _ GGSliceOps.GetBoundBox[slice2].loX; -- slices swapped intentionally priority2 _ GGSliceOps.GetBoundBox[slice1].loX; }; IF ascending THEN RETURN[CompareReal[priority1, priority2]] ELSE RETURN[CompareReal[priority2, priority1]]; }; slices _ NARROW[GList.Sort[slices, CompareProc]]; RETURN[slices]; }; END.