<> <> <> <> <> <> DIRECTORY AtomButtons, Basics, Feedback, GGBasicTypes, GGBoundBox, GGCaret, GGFont, GGHistory, GGInterfaceTypes, GGModelTypes, GGParent, GGRefreshTypes, GGScene, GGSegmentTypes, GGSelect, GGSequence, <> GGSlice, GGSliceOps, GGUserInput, GGUtility, GGWindow, GList, Imager, ImagerTransformation, IO, List, Match, MatchTurtle, MatchViewer, Real, <> Vectors2d; MatchImplB: CEDAR MONITOR IMPORTS AtomButtons, Basics, Feedback, GGBoundBox, GGCaret, GGHistory, GGParent, GGScene, GGSelect, GGSequence, <> GGSlice, GGSliceOps, GGUserInput, GGUtility, GGWindow, GList, ImagerTransformation, IO, List, Match, MatchTurtle, MatchViewer, Real, <> Vectors2d EXPORTS GGInterfaceTypes, Match = BEGIN BitVector: TYPE = GGBasicTypes.BitVector; BoundBox: TYPE = GGModelTypes.BoundBox; Caret: TYPE = REF CaretObj; CaretObj: TYPE = GGInterfaceTypes.CaretObj; ChoiceData: TYPE = MatchViewer.ChoiceData; FontData: TYPE = GGFont.FontData; GGDataObj: TYPE = GGInterfaceTypes.GGDataObj; GGData: TYPE = GGInterfaceTypes.GGData; ItemMatch: TYPE = Match.ItemMatch; LooksInfo: TYPE = Match.LooksInfo; MatchData: TYPE = MatchViewer.MatchData; MatchDescriptor: TYPE = Match.MatchDescriptor; MatchLevel: TYPE = MatchViewer.MatchLevel; Point: TYPE = GGBasicTypes.Point; PositionDescriptor: TYPE = MatchTurtle.PositionDescriptor; RefreshDataObj: PUBLIC TYPE = GGRefreshTypes.RefreshDataObj; SearchInfo: TYPE = Match.SearchInfo; SearchState: TYPE = Match.SearchState; Segment: TYPE = GGSegmentTypes.Segment; SegmentGenerator: TYPE = GGModelTypes.SegmentGenerator; Sequence: TYPE = GGModelTypes.Sequence; SequenceOfReal: TYPE = GGModelTypes.SequenceOfReal; Scene: TYPE = GGModelTypes.Scene; Slice: TYPE = GGModelTypes.Slice; SliceDescriptor: TYPE = GGModelTypes.SliceDescriptor; SliceDescriptorGenerator: TYPE = GGModelTypes.SliceDescriptorGenerator; SliceDescriptorObj: TYPE = GGModelTypes.SliceDescriptorObj; SliceGenerator: TYPE = GGModelTypes.SliceGenerator; SliceParts: TYPE = REF ANY; StrokeEnd: TYPE = Imager.StrokeEnd; -- TYPE = {square, butt, round} StrokeJoint: TYPE = Imager.StrokeJoint; -- TYPE = {miter, bevel, round} Traj: TYPE = GGModelTypes.Traj; TrajData: TYPE = GGModelTypes.TrajData; TurtleHeader: TYPE = MatchTurtle.TurtleHeader; TurtleInfo: TYPE = MatchTurtle.TurtleInfo; <> <<>> searchState: SearchState _ NEW[Match.SearchStateObj]; GetSearchState: PUBLIC PROC RETURNS [SearchState] = { RETURN[searchState]; }; EfficientOrder: PROC [looksList: LIST OF REF ANY] RETURNS [LIST OF REF ANY] = { <> BestMatchOrder: GList.CompareProc = { <<[ref1: REF ANY, ref2: REF ANY] RETURNS [Basics.Comparison]>> priority1: INT _ SortOrder[ref1]; priority2: INT _ SortOrder[ref2]; RETURN[Basics.CompareInt[priority2, priority1]]; }; SortOrder: PROC [looks: REF ANY] RETURNS [INT] = { <> simpleLooks: LooksInfo; WITH looks SELECT FROM simple: LooksInfo => simpleLooks _ simple; complex: LIST OF LIST OF LooksInfo => simpleLooks _ complex.first.first; ENDCASE => ERROR; IF NOT simpleLooks.shapeDef OR simpleLooks.shape = NIL THEN RETURN[-2] ELSE IF simpleLooks.shape.closed THEN RETURN[-1] ELSE RETURN[Real.Round[simpleLooks.shape.length]]; -- match on larger first }; looksList _ NARROW[GList.Sort[looksList, BestMatchOrder]]; RETURN[looksList]; }; <> <<>> InitializeSearch: PUBLIC PROC [them: GGData, event: LIST OF REF ANY] = { <> <> matchData: MatchData _ MatchViewer.GetMatchData[]; searchState: SearchState _ GetSearchState[]; direction: REF ANY _ event.rest.first; <> IF them # matchData.theirData OR GGCaret.GetPoint[matchData.theirData.caret] # matchData.lastCaretPos THEN { searchState.ahead _ MakeTheAheadList[them, direction, TRUE]; searchState.behind _ NIL; matchData.theirData _ them; matchData.lastCaretPos _ GGCaret.GetPoint[them.caret]; } <> ELSE IF direction # searchState.lastDirection THEN { SetMatchBits: PROC [normalD: SliceDescriptor] RETURNS [done: BOOL _ FALSE] = { GGSelect.SelectSlice[normalD, them.scene, match]; }; matchSelectD: SliceDescriptor _ NIL; <> [] _ GGScene.WalkSelectedSlices[them.scene, first, SetMatchBits, normal]; <> IF searchState.ahead # NIL THEN { <> matchSelectD _ GGSelect.FindSelectedSlice[searchState.ahead.first.slice, match]; IF matchSelectD = NIL THEN matchSelectD _ GGSliceOps.NewParts[searchState.ahead.first.slice, NIL, none]; }; searchState.ahead _ MakeTheAheadList[them, direction, TRUE]; searchState.behind _ NIL; Match.RemoveSelected[searchState.ahead, matchSelectD]; }; <> searchState.lastDirection _ IF direction = $SearchFromTop THEN $SearchBelow ELSE direction; }; MakeTheAheadList: PROC [theirData: GGData, direction: REF ANY, select: BOOL _ FALSE] RETURNS [ahead: LIST OF SearchInfo _ NIL] = { <<"ahead" will hold SearchInfo's for objects whose boundbox top edge is below the caret if the search is progressing in a downwards direction, and above the caret if the search is progessing upwards.>> <> AddToSearchList: PROC [slice: Slice] RETURNS [done: BOOL _ FALSE] = { IF (direction = $SearchAbove AND GGSliceOps.GetBoundBox[slice].hiY >= caretPos.y) OR (direction = $SearchBelow AND GGSliceOps.GetBoundBox[slice].hiY <= caretPos.y) OR (direction = $SearchFromTop) THEN slices _ CONS[slice, slices]; }; matchData: MatchData _ MatchViewer.GetMatchData[]; matchLevel: MatchLevel _ matchData.matchLevel; caretPos: Point _ GGCaret.GetPoint[theirData.caret]; slices: LIST OF Slice; <> [] _ GGScene.WalkSlices[theirData.scene, first, AddToSearchList]; slices _ Match.HeightSort[slices, direction # $SearchAbove]; <> IF select THEN GGSelect.DeselectAll[theirData.scene, match]; -- speeds subsequent selections <> FOR slices _ slices, slices.rest UNTIL slices = NIL DO infoList, tail: LIST OF SearchInfo; [infoList, tail] _ DescribeObjectForSearch[slices.first, matchLevel, direction]; tail.rest _ ahead; ahead _ infoList; IF select THEN GGSelect.SelectEntireSlice[slices.first, theirData.scene, match]; ENDLOOP; }; SearchConjNext: PUBLIC PROC [ahead, behind: LIST OF SearchInfo, direction: REF ANY, refresh: BOOL _ TRUE] RETURNS [newAhead, newBehind: LIST OF SearchInfo, found: BOOL _ FALSE] = { <> matchData: MatchData _ MatchViewer.GetMatchData[]; fromData: GGData _ MatchViewer.GetFromData[]; fromLooks: LIST OF REF ANY; matchD: MatchDescriptor; fromLooks _ Match.GetFromLooks[]; -- characterize the search ("from") pattern fromLooks _ EfficientOrder[fromLooks]; -- order the pattern properties to speed up search [matchD, ahead, behind] _ MatchSearchPattern[fromLooks, ahead, behind, direction]; IF matchD.isMatch AND matchD.matchedSlices # NIL THEN { -- we've matched something! <> GGSelect.DeselectAll[matchData.theirData.scene, normal]; IF MatchViewer.GetContextSensitive[matchData] THEN SelectMapped[matchData.theirData.scene, SelectionFilter[fromData.scene, matchD.mapping]] ELSE SelectMapped[matchData.theirData.scene, matchD.mapping]; <> matchData.lastCaretPos _ [matchD.matchedSlices.first.slice.boundBox.loX, matchD.matchedSlices.first.slice.boundBox.hiY]; GGCaret.SetAttractor[matchData.theirData.caret, matchData.lastCaretPos, [0, -1], NIL]; IF refresh THEN GGWindow.RestoreScreenAndInvariants[paintAction: $SelectionChanged, ggData: matchData.theirData, remake: none, edited: FALSE, okToSkipCapture: FALSE]; ahead _ DeleteMatchesFromList[ahead, matchD.matchedSlices.rest]; -- why .rest? behind _ DeleteMatchesFromList[behind, matchD.matchedSlices.rest]; RememberMatchDescriptor[matchD]; RETURN[ahead, behind, TRUE]; } ELSE { -- match failed <> GGSelect.DeselectAll[matchData.theirData.scene, normal]; IF refresh THEN GGWindow.RestoreScreenAndInvariants[paintAction: $SelectionChanged, ggData: matchData.theirData, remake: none, edited: FALSE, okToSkipCapture: FALSE]; RememberMatchDescriptor[NIL]; RETURN[ahead, behind, FALSE]; }; }; MatchSearchPattern: PROC [fromLooks: LIST OF REF ANY, ahead, behind: LIST OF SearchInfo, direction: REF ANY] RETURNS [matchD: MatchDescriptor, newAhead, newBehind: LIST OF SearchInfo] = { matchData: MatchViewer.MatchData _ MatchViewer.GetMatchData[]; sceneList: LIST OF SearchInfo; sceneListVec: BitVector; vecIndex: NAT _ 0; thisAhead, nextAhead: SearchInfo; leadObj: REF ANY; otherObjs: LIST OF REF ANY; thisAheadParts: SliceDescriptor; sceneList _ AppendSearchInfoList[ahead, behind]; sceneListVec _ NewBitVector[GList.Length[sceneList], FALSE]; matchD _ CreateMatchDescriptor[]; IF fromLooks = NIL OR ahead = NIL THEN RETURN[matchD, ahead, behind]; -- nothing to match on or nothing to match leadObj _ fromLooks.first; otherObjs _ fromLooks.rest; thisAhead _ ahead.first; DO -- iterate through ahead search list (i.e., through the elligible scene objects) InitializeMatchDescriptor[matchD]; IF NOT Match.IsEmptySearchInfo[thisAhead] THEN { <> [thisAheadParts, nextAhead] _ Match.FindMatchInSlice[thisAhead, LIST[leadObj], direction, matchD]; IF thisAheadParts#NIL THEN { <> sceneListVec[vecIndex] _ TRUE; -- mark that thisAhead slice is taken matchD _ MatchOtherObjects[otherObjs, sceneList, sceneListVec, direction, matchD]; IF matchD.isMatch THEN GOTO MatchFound; <> IF matchD.posD.valid THEN { FOR nextPosD: PositionDescriptor _ MatchTurtle.NextMatch[matchD.posD.others], MatchTurtle.NextMatch[matchD.posD.others] UNTIL nextPosD = NIL DO matchD.posD _ nextPosD; [thisAheadParts, nextAhead] _ Match.FindMatchInSlice[thisAhead, LIST[leadObj], direction, matchD]; IF thisAheadParts # NIL THEN { matchD _ MatchOtherObjects[otherObjs, sceneList, sceneListVec, direction, matchD]; IF matchD.isMatch THEN GOTO MatchFound; matchD.mapping _ NIL; }; ENDLOOP; }; <> sceneListVec[vecIndex] _ FALSE; -- no match: free up this element GOTO MatchFailed; } ELSE GOTO MatchFailed; EXITS MatchFound => { ConjClearMatchBits[thisAhead, nextAhead, matchData.theirData.scene]; matchD.matchedSlices _ CONS[thisAheadParts, matchD.matchedSlices]; ahead _ CONS[nextAhead, ahead.rest]; RETURN[matchD, ahead, behind]; }; MatchFailed => { ConjClearMatchBits[thisAhead, nextAhead, matchData.theirData.scene]; thisAhead _ nextAhead; }; } ELSE { <> behind _ CONS[ahead.first, behind]; ahead _ ahead.rest; IF ahead = NIL THEN { matchD.isMatch _ FALSE; RETURN[matchD, ahead, behind]; -- we've run out of objects. No match. }; thisAhead _ ahead.first; vecIndex _ vecIndex + 1; }; ENDLOOP; }; MatchOtherObjects: PROC [looksList: LIST OF REF ANY, sceneList: LIST OF SearchInfo, sceneListVec: BitVector, direction: REF ANY, matchD: MatchDescriptor] RETURNS [MatchDescriptor] = { bbox: BoundBox; simpleLooks: LooksInfo; vecIndex: INT _ -1; transformKnown: BOOL _ FALSE; saveDObj: Match.MatchDObj _ matchD^; -- copy so state can be restored if no match IF looksList = NIL THEN {matchD.isMatch _ TRUE; RETURN[matchD]}; -- base case = success WITH looksList.first SELECT FROM simple: LooksInfo => simpleLooks _ simple; complex: LIST OF LIST OF LooksInfo => simpleLooks _ complex.first.first; ENDCASE => ERROR; transformKnown _ simpleLooks.shapeDef AND simpleLooks.shape # NIL AND matchD.posD.valid; IF transformKnown THEN { <> averageLength: REAL = 300.0; -- average length of a trajectory (exactness unimportant) maxError: REAL _ MatchViewer.GetMatchTolerance[] * averageLength; pt: Point _ ImagerTransformation.Transform[matchD.posD.tform1, simpleLooks.shape.originalStart]; -- any point in the pre-normalized shape will do pt _ ImagerTransformation.InverseTransform[matchD.posD.tform2, pt]; bbox _ GGBoundBox.CreateBoundBox[pt.x - maxError, pt.y - maxError, pt.x + maxError, pt.y + maxError]; }; FOR nextItem: LIST OF SearchInfo _ sceneList, nextItem.rest UNTIL nextItem = NIL DO thisInfo: SearchInfo _ nextItem.first; slice: Slice _ thisInfo.slice; match: SliceDescriptor; vecIndex _ vecIndex + 1; IF sceneListVec[vecIndex] THEN LOOP; -- This puppy is taken IF transformKnown AND NOT BoundBoxIntersect[bbox, GGSliceOps.GetTightBox[slice]] THEN LOOP; -- A quick rejection of slice [match,----] _ Match.FindMatchInSlice[thisInfo, LIST[looksList.first], direction, matchD]; IF match # NIL THEN { -- we've got a match on this level. recurse! sceneListVec[vecIndex] _ TRUE; matchD _ MatchOtherObjects[looksList.rest, sceneList, sceneListVec, direction, matchD]; IF matchD.isMatch THEN { matchD.matchedSlices _ CONS[match, matchD.matchedSlices]; RETURN[matchD]; }; sceneListVec[vecIndex] _ FALSE; matchD^ _ saveDObj; -- restore original descriptor }; ENDLOOP; matchD.isMatch _ FALSE; -- found no match matchD.matchedSlices _ NIL; -- clear it out RETURN[matchD]; }; <> <<>> DescriptorInSearchInfo: PROC [sliceD: SliceDescriptor, searchInfo: SearchInfo, matchLevel: MatchLevel] RETURNS [BOOL _ FALSE] = { IF GGSliceOps.GetType[sliceD.slice] # GGSliceOps.GetType[searchInfo.slice] THEN RETURN[FALSE] ELSE IF GGSliceOps.GetType[searchInfo.slice] # $Outline OR matchLevel = clusterLevel THEN RETURN [searchInfo.slice = sliceD.slice] ELSE -- match if the traj is in the descriptor IF searchInfo.kids = NIL THEN RETURN[FALSE] -- searchInfo is empty ELSE RETURN[GGParent.ChildDescriptorFromDescriptor[sliceD, searchInfo.kids.first] # NIL]; }; DeleteMatchesFromList: PROC [sceneList: LIST OF SearchInfo, sliceDList: LIST OF SliceDescriptor] RETURNS [newList: LIST OF SearchInfo _ NIL] = { matchLevel: MatchLevel _ MatchViewer.GetMatchData[].matchLevel; FOR l1: LIST OF SearchInfo _ sceneList, l1.rest UNTIL l1 = NIL DO delete: BOOL _ FALSE; FOR l2: LIST OF SliceDescriptor _ sliceDList, l2.rest UNTIL l2 = NIL DO IF DescriptorInSearchInfo[l2.first, l1.first, matchLevel] THEN {delete _ TRUE; EXIT;}; ENDLOOP; IF NOT delete THEN newList _ CONS[l1.first, newList]; ENDLOOP; newList _ NARROW[GList.DReverse[newList], LIST OF SearchInfo]; }; DescribeObjectForSearch: PROC [slice: Slice, matchLevel: MatchLevel, direction: REF ANY] RETURNS [infoList, tail: LIST OF SearchInfo _ NIL] = { newInfo: SearchInfo; <> IF GGSliceOps.GetType[slice] = $Outline AND (matchLevel = trajLevel OR matchLevel = anywhere) THEN { DoGetTrajInfo: PROC [traj: Slice] RETURNS [done: BOOL _ FALSE] = { newInfo _ SearchInfoFromTraj[traj, matchLevel, direction]; [infoList, tail] _ AddSearchInfo[newInfo, infoList, tail]; }; [] _ GGParent.WalkChildren[slice, first, DoGetTrajInfo, $Traj]; } <> ELSE { newInfo _ Match.CreateSearchInfo[slice, direction]; [infoList, tail] _ AddSearchInfo[newInfo, infoList, tail]; }; }; SearchInfoFromTraj: PROC [traj: Traj, matchLevel: MatchLevel, direction: REF ANY] RETURNS [searchInfo: SearchInfo] = { searchInfo _ NEW[Match.SearchInfoObj _ [slice: traj.parent, reverse: direction = $SearchAbove]]; searchInfo.kids _ LIST[traj]; IF matchLevel = anywhere THEN Match.CacheSegsOfFirstTraj[searchInfo]; }; BoundBoxIntersect: PROC [b1, b2: BoundBox] RETURNS [BOOL _ FALSE] = { IF b1.infinite OR b2.infinite THEN RETURN[TRUE]; IF b1.null OR b2.null THEN RETURN[FALSE]; IF b1.loX > b2.hiX OR b2.loX > b1.hiX OR b1.loY > b2.hiY OR b2.loY > b1.hiY THEN RETURN[FALSE] ELSE RETURN[TRUE]; }; CreateMatchDescriptor: PUBLIC PROC [] RETURNS [matchD: MatchDescriptor] = { matchD _ NEW[Match.MatchDObj]; matchD.posD _ NEW[MatchTurtle.PositionDObj _ [valid: FALSE, settable: TRUE]]; }; InitializeMatchDescriptor: PROC [matchD: MatchDescriptor] = { matchD.posD.valid _ FALSE; matchD.posD.settable _ TRUE; matchD.posD.others _ NIL; matchD.isMatch _ FALSE; matchD.matchedSlices _ NIL; matchD.mapping _ NIL; }; NewBitVector: PROC [length: NAT, initialValue: BOOL _ FALSE] RETURNS [bitVec: BitVector] = { bitVec _ NEW[GGBasicTypes.BitVectorObj[length]]; FOR i: NAT IN [0..bitVec.len) DO bitVec[i] _ initialValue; ENDLOOP; }; SelectMapped: PUBLIC PROC [scene: GGModelTypes.Scene, matches: LIST OF ItemMatch] = { TrueForSeg: GGModelTypes.WalkProc = { IF seg = thisSeg THEN RETURN[TRUE] ELSE RETURN[FALSE]; }; thisSeg: Segment; FOR itemList: LIST OF ItemMatch _ matches, itemList.rest UNTIL itemList = NIL DO sliceD: SliceDescriptor; slice: Slice _ itemList.first.matchee.slice; SELECT GGSliceOps.GetType[slice] FROM $Circle, $Text => sliceD _ GGSliceOps.NewParts[slice, NIL, slice]; $Box => { thisSeg _ itemList.first.matchee.seg; IF thisSeg = NIL THEN sliceD _ GGSliceOps.NewParts[slice, NIL, slice] -- whole box ELSE sliceD _ GGSliceOps.WalkSegments[slice, TrueForSeg]; }; $Outline => { traj: Traj _ itemList.first.matchee.traj; thisSeg _ itemList.first.matchee.seg; IF traj = NIL AND thisSeg = NIL THEN sliceD _ GGSliceOps.NewParts[slice, NIL, slice] ELSE IF thisSeg = NIL THEN { trajData: TrajData _ NARROW[traj.data]; seq: Sequence _ GGSlice.DescriptorFromParts[ traj, GGSequence.CreateComplete[trajData] ]; sliceD _ GGParent.DescriptorFromChildDescriptor[traj.parent, seq]; } ELSE sliceD _ GGSliceOps.WalkSegments[slice, TrueForSeg]; }; ENDCASE; GGSelect.SelectSlice[sliceD, scene, normal]; ENDLOOP; }; <> SelectionFilter: PUBLIC PROC [scene: GGModelTypes.Scene, matches: LIST OF ItemMatch] RETURNS [selectList: LIST OF ItemMatch _ NIL] = { <> FOR itemList: LIST OF ItemMatch _ matches, itemList.rest UNTIL itemList = NIL DO slice: Slice _ itemList.first.matcher.slice; selected: BOOL _ FALSE; SELECT GGSliceOps.GetType[slice] FROM $Circle, $Text => IF GGSelect.IsSelectedInFull[slice, scene, normal] THEN selected _ TRUE; $Box => { seg: Segment _ itemList.first.matcher.seg; IF seg = NIL THEN selected _ GGSelect.IsSelectedInFull[slice, scene, normal] ELSE { <> sliceD: SliceDescriptor _ GGSelect.FindSelectedSlice[slice, normal]; IF sliceD # NIL THEN { segGen: SegmentGenerator _ GGSliceOps.SegmentsInDescriptor[sliceD]; FOR nextSeg: Segment _ GGSliceOps.NextSegment[sliceD.slice, segGen].seg, GGSliceOps.NextSegment[sliceD.slice, segGen].seg UNTIL nextSeg = NIL DO IF seg = nextSeg THEN {selected _ TRUE; EXIT;}; ENDLOOP; }; }; }; $Outline => { traj: Traj _ itemList.first.matcher.traj; seg: Segment _ itemList.first.matcher.seg; IF traj = NIL AND seg = NIL THEN selected _ GGSelect.IsSelectedInFull[slice, scene, normal] ELSE IF seg = NIL THEN selected _ GGSelect.IsSelectedInPart[traj, scene, normal] ELSE { <> sliceD: SliceDescriptor _ GGSelect.FindSelectedSlice[slice, normal]; IF sliceD # NIL THEN { segGen: SegmentGenerator _ GGSliceOps.SegmentsInDescriptor[sliceD]; FOR nextSeg: Segment _ GGSliceOps.NextSegment[slice, segGen].seg, GGSliceOps.NextSegment[slice, segGen].seg UNTIL nextSeg = NIL DO IF seg = nextSeg THEN {selected _ TRUE; EXIT;}; ENDLOOP; }; }; }; ENDCASE; IF selected THEN selectList _ CONS [itemList.first, selectList]; ENDLOOP; }; <<>> <> ReplaceOperation: PUBLIC PROC [refresh: BOOL _ TRUE] RETURNS [success: BOOL _ FALSE] = { <> matchData: MatchData _ MatchViewer.GetMatchData[]; IF matchData.theirData = NIL OR GetLastMatchDescriptor[] = NIL THEN { MatchViewer.ErrorFeedback["Must search before replacing."]; RETURN[FALSE]; }; IF matchData.replaceOp = doReplace THEN IF AtomButtons.GetBinaryState[matchData.to.shape] THEN success _ ReplaceConverse[refresh] ELSE success _ ReplaceParameters[refresh] ELSE IF matchData.replaceOp = doOperations THEN success _ DoOps[] ELSE ERROR; -- unknown replaceOp }; ReplaceParameters: PROC [refresh: BOOL _ TRUE] RETURNS [success: BOOL _ TRUE] = { <<1) find out if there's any characteristics ambiguity in To viewer>> matchData: MatchData _ MatchViewer.GetMatchData[]; toLooks: LIST OF REF ANY _ Match.GetToLooks[]; singleLook: LooksInfo _ VerifyLooksEquivalence[toLooks]; IF singleLook = NIL THEN { MatchViewer.ErrorFeedback["Ambiguous characteristics in To viewer."]; RETURN[FALSE]; }; <<2) we're in good shape, so now we need only copy looks to selection>> CopyLooksToSelection[matchData.theirData, singleLook, refresh]; }; ReplaceConverse: PROC [refresh: BOOL _ TRUE] RETURNS [success: BOOL _ TRUE] = { DoCopyReplacementSlice: PROC [original: Slice] RETURNS [done: BOOL _ FALSE] = { newSlice: Slice _ GGSliceOps.Copy[original].first; -- new slice for destination scene entireD: SliceDescriptor _ GGSliceOps.NewParts[newSlice, NIL, slice]; newSlice.priority _ GGScene.GetPriority[toScene, original]; CopyLooksToSlice[entireD, singleLook]; GGSliceOps.Transform[entireD.slice, entireD.parts, mapTform, none, NIL]; additions _ CONS[newSlice, additions]; }; matchData: MatchData _ MatchViewer.GetMatchData[]; -- data for target scene scene: Scene _ matchData.theirData.scene; -- target scene toScene: Scene _ MatchViewer.GetToData[].scene; -- replacement scene selected: LIST OF SliceDescriptor _ GGScene.ListSelectedSlices[scene, first, normal]; -- target slices to replace additions: LIST OF Slice _ NIL; minPriority: INT _ LAST[INT]; -- large number needed later for loop initialization matchD: MatchDescriptor _ GetLastMatchDescriptor[]; mapTform: ImagerTransformation.Transformation _ GetMatchTransform[matchData, matchD]; <> matchLooks: LIST OF REF ANY _ GetLooksOfMatch[matchD, matchData.to, FALSE]; singleLook: LooksInfo _ VerifyLooksEquivalence[matchLooks]; IF singleLook = NIL THEN { MatchViewer.ErrorFeedback["Ambiguous characteristics in Matched objects."]; RETURN[FALSE]; }; <> IF selected = NIL THEN minPriority _ -1 ELSE { FOR sList: LIST OF SliceDescriptor _ selected, sList.rest UNTIL sList = NIL DO minPriority _ MIN[GGScene.GetPriority[scene, sList.first.slice], minPriority]; ENDLOOP; }; GGHistory.NewCapture["MatchTool replace of", matchData.theirData]; <> [] _ GGScene.WalkSlices[toScene, first, DoCopyReplacementSlice]; <> matchData.theirData.refresh.startBoundBox^ _ DeleteSelected[matchData.theirData]^; <> additions _ InverseSortByPriority[additions]; FOR addList: LIST OF Slice _ additions, addList.rest UNTIL addList = NIL DO GGScene.AddSlice[scene, addList.first, minPriority]; GGBoundBox.EnlargeByBox[matchData.theirData.refresh.startBoundBox, GGSliceOps.GetBoundBox[addList.first]]; ENDLOOP; GGHistory.PushCurrent[matchData.theirData]; IF refresh THEN GGWindow.RestoreScreenAndInvariants[paintAction: $ObjectChangedBoundBoxProvided, ggData: matchData.theirData, remake: triggerBag, edited: TRUE, okToSkipCapture: FALSE]; FixStateAfterSubstitute[]; }; GetMatchTransform: PROC [matchData: MatchData, matchD: MatchDescriptor] RETURNS [tform: ImagerTransformation.Transformation] = { IF matchD.posD.valid THEN { tform _ ImagerTransformation.Concat[matchD.posD.tform1, ImagerTransformation.Invert[matchD.posD.tform2]]; } ELSE { -- we don't have an explicit match transform <> <> box1: BoundBox _ GGScene.BoundBoxOfScene[MatchViewer.GetToData[].scene]; box2: BoundBox _ GGScene.BoundBoxOfSelected[matchData.theirData.scene]; source: Point _ [(box1.loX + box1.hiX) / 2.0, (box1.loY + box1.hiY) / 2.0]; destination: Point _ IF NOT box2.null AND NOT box2.infinite THEN [(box2.loX + box2.hiX) / 2.0, (box2.loY + box2.hiY) / 2.0] ELSE GGCaret.GetPoint[matchData.theirData.caret]; tform _ ImagerTransformation.Translate[Vectors2d.Sub[destination, source]]; <> }; }; FixStateAfterSubstitute: PROC = { <> matchData: MatchViewer.MatchData _ MatchViewer.GetMatchData[]; searchState: SearchState _ Match.GetSearchState[]; IF matchData.searchOp = disjunction THEN searchState.ahead _ Match.RemakeDisjList[matchData.theirData, searchState.lastDirection, FALSE] ELSE IF matchData.searchOp = conjunction THEN searchState.ahead _ MakeTheAheadList[matchData.theirData, searchState.lastDirection, FALSE] ELSE ERROR; -- unknown SearchOp IF searchState.ahead # NIL THEN { -- find leading elements that are not match selected RemoveAllUnselected[searchState.ahead, match, matchData.theirData.scene]; }; }; ComplementDescriptor: PROC [sliceD: SliceDescriptor] RETURNS [SliceDescriptor] = { entire: SliceDescriptor _ GGSliceOps.NewParts[sliceD.slice, NIL, slice]; RETURN[GGSliceOps.DifferenceParts[entire, sliceD]]; }; RemoveAllUnselected: PROC [sceneList: LIST OF SearchInfo, selectClass: GGSegmentTypes.SelectionClass, scene: Scene] = { FOR info: LIST OF SearchInfo _ sceneList, info.rest UNTIL info = NIL DO <> selectD: SliceDescriptor _ GGSelect.FindSelectedSlice[info.first.slice, selectClass]; IF selectD # NIL THEN selectD _ ComplementDescriptor[selectD] ELSE selectD _ GGSliceOps.NewParts[info.first.slice, NIL, slice]; Match.RemoveDescriptorFromInfo[selectD, info.first]; ENDLOOP; }; InverseSortByPriority: PROC [entityList: LIST OF Slice] RETURNS [sorted: LIST OF Slice] = { <> CompareProc: GGUtility.SliceCompareProc = { <<[ref1: Slice, ref2: Slice] RETURNS [Basics.Comparison]>> priority1, priority2: INT; priority1 _ ref1.priority; priority2 _ ref2.priority; RETURN[Basics.CompareInt[priority2, priority1]]; }; sorted _ GGUtility.SortSliceList[entityList, CompareProc]; }; CopyLooksToSelection: PROC [ggData: GGData, toLooks: LooksInfo, refresh: BOOL _ TRUE] = { DoCopyLooks: PROC [sliceD: SliceDescriptor] RETURNS [done: BOOL _ FALSE] = { CopyLooksToSlice[sliceD, toLooks]; }; oldBBox: BoundBox _ GGScene.BoundBoxOfSelected[ggData.scene, normal]; newBBox: BoundBox; scene: Scene _ ggData.scene; GGHistory.NewCapture["MatchTool replace looks of", ggData]; [] _ GGScene.WalkSelectedSlices[scene, first, DoCopyLooks, normal]; newBBox _ GGScene.BoundBoxOfSelected[scene, normal]; ggData.refresh.startBoundBox^ _ GGBoundBox.BoundBoxOfBoxes[LIST[oldBBox, newBBox]]^; GGHistory.PushCurrent[ggData]; IF refresh THEN GGWindow.RestoreScreenAndInvariants[paintAction: $ObjectChangedBoundBoxProvided, ggData: ggData, remake: none, edited: TRUE, okToSkipCapture: FALSE]; }; CopyLooksToSlice: PROC [sliceD: SliceDescriptor, looks: LooksInfo] = { slice: Slice _ sliceD.slice; parts: SliceParts _ sliceD.parts; <> IF looks.colorDef THEN GGSliceOps.SetStrokeColor[slice, parts, looks.color, $Set, NIL]; IF looks.fillColorDef THEN GGSliceOps.SetFillColor[slice, parts, looks.fillColor, $Set, NIL]; IF looks.stringDef AND GGSliceOps.GetType[slice] = $Text THEN GGSlice.SetText[slice, looks.string]; IF looks.fontDef AND GGSliceOps.GetType[slice] = $Text THEN { looks.font.transform _ GGSlice.GetFontData[slice].transform; [] _ GGSlice.SetTextFont[slice, looks.font, MatchViewer.GetToData[].router, NIL]; }; IF looks.fontTformDef AND GGSliceOps.GetType[slice] = $Text THEN [] _ GGSlice.SetTextFont[slice, looks.fontTform, MatchViewer.GetToData[].router, NIL]; -- SetTextFont will set the transform too! IF looks.dashesDef THEN GGSliceOps.SetDashed[slice, parts, looks.dashes.dashed, looks.dashes.pattern, looks.dashes.offset, looks.dashes.length, NIL]; IF looks.jointsDef THEN GGSliceOps.SetStrokeJoint[slice, parts, looks.joints, NIL]; IF looks.endsDef THEN GGSliceOps.SetStrokeEnd[slice, parts, looks.ends, NIL]; IF looks.widthDef THEN [] _ GGSliceOps.SetStrokeWidth[slice, parts, looks.width, NIL]; }; GetLooksOfMatch: PROC [matchD: MatchDescriptor, choice: MatchViewer.ChoiceData, value: BOOL _ FALSE] RETURNS [looksList: LIST OF REF ANY _ NIL] = { <> matchData: MatchData _ MatchViewer.GetMatchData[]; itemList: LIST OF ItemMatch _ IF MatchViewer.GetContextSensitive[matchData] THEN SelectionFilter[matchData.theirData.scene, matchD.mapping] ELSE matchD.mapping; FOR itemList _ itemList, itemList.rest UNTIL itemList = NIL DO item: Match.ItemID _ itemList.first.matchee; IF item.traj = NIL AND item.seg = NIL THEN { <> looksList _ CONS[Match.GetLooksOfSlice[item.slice, choice, FALSE], looksList]; } ELSE IF item.seg = NIL THEN { <> looksList _ CONS[Match.GetLooksOfTraj[item.traj, choice, FALSE], looksList]; } ELSE { <> <> looksList _ CONS[Match.GetLooksOfSegment[item.slice, item.traj, item.seg, choice, FALSE, NIL], looksList]; } ENDLOOP; }; VerifyLooksEquivalence: PROC [sceneLooks: LIST OF REF ANY] RETURNS [singleLook: LooksInfo _ NIL] = { FlattenLooks: PROC [looks: LIST OF REF ANY] RETURNS [flatList: LIST OF LooksInfo _ NIL] = { FOR tLooks: LIST OF REF ANY _ looks, tLooks.rest UNTIL tLooks = NIL DO WITH tLooks.first SELECT FROM single: LooksInfo => flatList _ CONS[single, flatList]; multiple: LIST OF LIST OF LooksInfo => { FOR tMultiple: LIST OF LIST OF LooksInfo _ multiple, tMultiple.rest UNTIL tMultiple = NIL DO flatList _ NARROW[GList.Append[tMultiple.first, flatList]]; ENDLOOP; }; ENDCASE => ERROR; ENDLOOP; }; flatLooks: LIST OF LooksInfo _ FlattenLooks[sceneLooks]; IF flatLooks = NIL THEN RETURN[NIL]; -- no single Looks FOR lptr: LIST OF LooksInfo _ flatLooks, lptr.rest UNTIL lptr = NIL OR lptr.rest=NIL DO IF NOT Match.LooksEqual[lptr.first, lptr.rest.first] THEN RETURN[NIL]; -- different Looks ENDLOOP; RETURN[flatLooks.first]; }; DeleteSelected: PROC [ggData: GGData] RETURNS [bbox: BoundBox] = { bbox _ GGScene.DeleteAllSelected[ggData.scene]; GGCaret.NoAttractor[ggData.caret]; GGCaret.SitOn[caret: ggData.caret, chair: NIL]; }; lastMatchD: MatchDescriptor; -- This global holds the MatchDescriptor from the most recent match RememberMatchDescriptor: PUBLIC PROC [matchD: MatchDescriptor] = { lastMatchD _ matchD; }; GetLastMatchDescriptor: PROC RETURNS [MatchDescriptor] = { RETURN[lastMatchD]; }; ConjClearMatchBits: PROC [first, next: SearchInfo, scene: Scene] = { <> firstInfo: SliceDescriptor _ SearchInfoToDescriptor[first]; nextInfo: SliceDescriptor _ IF next = NIL THEN NIL ELSE SearchInfoToDescriptor[next]; GGSelect.DeselectSlice[firstInfo.slice, firstInfo.parts, scene, match]; IF nextInfo # NIL THEN GGSelect.SelectSlice[nextInfo, scene, match]; }; SearchInfoToDescriptor: PROC [info: SearchInfo] RETURNS [sliceD: SliceDescriptor] = { ContainsSegs: GGModelTypes.WalkProc = { RETURN[GList.Member[seg, info.segs]];}; matchLevel: MatchLevel _ MatchViewer.GetMatchData[].matchLevel; IF info = NIL THEN RETURN[NIL]; -- can't make an empty descriptor: we don't know the slice IF Match.IsEmptySearchInfo[info] THEN RETURN[GGSliceOps.NewParts[info.slice, NIL, none]]; SELECT GGSliceOps.GetType[info.slice] FROM $Box => { IF matchLevel # anywhere THEN RETURN[GGSliceOps.NewParts[info.slice, NIL, slice]] ELSE RETURN[GGSliceOps.WalkSegments[info.slice, ContainsSegs]]; }; $Outline => { DoSelectiveRemove: PROC [traj: Slice] RETURNS [done: BOOL _ FALSE] = { IF NOT GList.Member[traj, info.kids] THEN sliceD _ GGParent.RemoveTraj[sliceD, traj]; }; sliceD _ GGSliceOps.NewParts[info.slice, NIL, slice]; [] _ GGParent.WalkChildren[info.slice, first, DoSelectiveRemove, $Traj]; IF matchLevel = anywhere THEN { -- only include segs in info.segs segDescriptor: SliceDescriptor _ GGSliceOps.WalkSegments[info.slice, ContainsSegs]; sliceD _ GGParent.RemoveTraj[sliceD, info.kids.first]; sliceD _ GGSliceOps.UnionParts[sliceD, segDescriptor]; }; }; ENDCASE => RETURN[GGSliceOps.NewParts[info.slice, NIL, slice]]; }; ChangeAll: PUBLIC GGUserInput.UserInputProc = { matchData: MatchData _ MatchViewer.GetMatchData[]; searchState: SearchState _ GetSearchState[]; direction: REF ANY _ event.rest.first; toSearch: GGData _ MatchViewer.GetGGInputFocus[]; subCount: NAT _ 0; IF toSearch = NIL THEN { -- input focus wasn't in a Gargoyle viewer MatchViewer.ErrorFeedback["Place input focus in a Gargoyle viewer for ChangeAll."]; RETURN; }; Feedback.Append[MatchViewer.GetToData[].router, begin, $Feedback, "Changing ... "]; SELECT matchData.searchOp FROM disjunction => { Match.SearchDisjInit[toSearch, event]; DO -- now do the substitute found, success: BOOL _ FALSE; [searchState.ahead, found] _ Match.SearchDisjNext[searchState.ahead, direction, doEveryRefresh]; IF NOT found THEN EXIT; success _ ReplaceOperation[refresh: FALSE]; IF NOT success THEN EXIT; subCount _ subCount + 1; ENDLOOP; }; conjunction => { InitializeSearch[toSearch, event]; DO -- now do the substitute found, success: BOOL _ FALSE; [searchState.ahead, searchState.behind, found] _ SearchConjNext[searchState.ahead, searchState.behind, direction, doEveryRefresh]; IF NOT found THEN EXIT; success _ ReplaceOperation[refresh: FALSE]; IF NOT success THEN EXIT; subCount _ subCount + 1; ENDLOOP; }; ENDCASE => ERROR; -- Somebody must have implemented a new SearchOp Feedback.PutF[MatchViewer.GetToData[].router, end, $Feedback, "%g substitution%g", IO.card[subCount], IO.char[IF subCount=1 THEN ' ELSE 's] ]; GGWindow.RestoreScreenAndInvariants[paintAction: $PaintEntireScene, ggData: matchData.theirData, remake: triggerBag, edited: TRUE, okToSkipCapture: FALSE]; }; <> <<>> <> toData: GGData _ MatchViewer.GetToData[]; IF toData = clientData AND event.first # $MatchEndOps THEN { -- ie, event occurred in the To viewer, and it wasn't the $MatchEndOps atom searchState.macroOps _ CONS[event, searchState.macroOps]; }; };>> <> DoOps: PROC RETURNS [success: BOOL _ TRUE] = { matchData: MatchData _ MatchViewer.GetMatchData[]; matchD: MatchDescriptor _ GetLastMatchDescriptor[]; searchState: Match.SearchState _ Match.GetSearchState[]; IF searchState.macroOn THEN { MatchViewer.ErrorFeedback["First press EndOps button."]; RETURN[FALSE]; }; IF searchState.macroOps = NIL THEN { MatchViewer.ErrorFeedback["No operations have been fetched from To viewer."]; RETURN[FALSE]; }; GGHistory.NewCapture["MatchTool DoOperations on", matchData.theirData]; FOR opList: LIST OF LIST OF REF ANY _ searchState.macroOps, opList.rest UNTIL opList = NIL DO newOp: LIST OF REF ANY _ NIL; <> FOR op: LIST OF REF ANY _ opList.first, op.rest UNTIL op = NIL DO IF ISTYPE[op.first, REF Point] THEN { newPoint: REF Point _ NEW[Point]; newPoint^ _ ImagerTransformation.Transform[GetMatchTransform[matchData, matchD], NARROW[op.first, REF Point]^]; newOp _ CONS[newPoint, newOp]; } ELSE newOp _ CONS[op.first, newOp]; ENDLOOP; newOp _ List.DReverse[newOp]; GGUserInput.PlayAction[matchData.theirData, newOp]; ENDLOOP; <> GGUserInput.PlayAction[matchData.theirData, LIST[$MatchReleaseSemaphore]]; P[]; GGCaret.SetAttractor[matchData.theirData.caret, matchData.lastCaretPos, [0, -1], NIL]; GGHistory.PushCurrent[matchData.theirData]; GGWindow.RestoreScreenAndInvariants[paintAction: $CaretMoved, ggData: matchData.theirData, remake: none, edited: FALSE, okToSkipCapture: FALSE]; }; StartSearchInfoList: PUBLIC PROC [] RETURNS [entityList, ptr: LIST OF SearchInfo] = { ptr _ entityList _ NIL; }; AddSearchInfo: PUBLIC PROC [entity: SearchInfo, entityList, ptr: LIST OF SearchInfo] RETURNS [newList, newPtr: LIST OF SearchInfo] = { IF ptr = NIL THEN { IF NOT entityList = NIL THEN ERROR; newPtr _ newList _ CONS[entity, NIL]; RETURN; } ELSE { newList _ entityList; ptr.rest _ CONS[entity, NIL]; newPtr _ ptr.rest; }; }; AppendSearchInfoList: PUBLIC PROC [list1, list2: LIST OF SearchInfo] RETURNS [result: LIST OF SearchInfo] = { pos: LIST OF SearchInfo; newCell: LIST OF SearchInfo; <> IF list1 = NIL THEN RETURN[list2]; result _ CONS[list1.first, NIL]; pos _ result; FOR l: LIST OF SearchInfo _ list1.rest, l.rest UNTIL l = NIL DO newCell _ CONS[l.first, NIL]; pos.rest _ newCell; pos _ newCell; ENDLOOP; pos.rest _ list2; }; <> <<>> MatchReleaseSemaphore: GGUserInput.UserInputProc = { V[]; }; wakeUp: CONDITION; resourceAllocated: BOOL _ TRUE; P: ENTRY PROCEDURE [] = { WHILE resourceAllocated DO WAIT wakeUp; ENDLOOP; resourceAllocated _ TRUE; }; V: ENTRY PROCEDURE [] = { resourceAllocated _ FALSE; NOTIFY wakeUp; }; doEveryRefresh: BOOL _ FALSE; GGUserInput.RegisterAction[atom: $MatchReleaseSemaphore, eventProc: MatchReleaseSemaphore, argType: none, ensureUnique: FALSE]; -- An ACK, used to avoid monitor deadlock in the doOps mechanism END.