<> <> <> DIRECTORY AtomButtons, Basics, Feedback, GGBasicTypes, GGBoundBox, GGCaret, GGContainer, GGFont, GGInterfaceTypes, GGModelTypes, GGOutline, GGScene, GGSegmentTypes, GGSelect, GGSequence, GGSessionLog, GGSlice, GGUserInput, GGUtility, GGWindow, GList, Imager, ImagerTransformation, IO, List, Match, MatchTurtle, MatchViewer, Real, SlackProcess, Vectors2d, ViewerClasses; MatchImplB: CEDAR MONITOR IMPORTS AtomButtons, Basics, Feedback, GGBoundBox, GGCaret, GGOutline, GGScene, GGSelect, GGSequence, GGSessionLog, GGSlice, GGUserInput, GGUtility, GGWindow, GList, ImagerTransformation, IO, List, Match, MatchTurtle, MatchViewer, Real, SlackProcess, Vectors2d EXPORTS 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; 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; TrajGenerator: TYPE = GGModelTypes.TrajGenerator; TurtleHeader: TYPE = MatchTurtle.TurtleHeader; TurtleInfo: TYPE = MatchTurtle.TurtleInfo; 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.InlineRound[simpleLooks.shape.length]]; -- match on larger first }; looksList _ NARROW[GList.Sort[looksList, BestMatchOrder]]; RETURN[looksList]; }; <> <<>> searchState: SearchState _ NEW[Match.SearchStateObj]; GetSearchState: PUBLIC PROC RETURNS [SearchState] = { RETURN[searchState]; }; <> <<>> SearchConjInit: PUBLIC PROC [them: GGData, event: LIST OF REF ANY] = { <> matchData: MatchData _ MatchViewer.GetMatchData[]; searchState: SearchState _ GetSearchState[]; <> IF them # matchData.theirData OR GGCaret.GetPoint[matchData.theirData.caret] # matchData.lastCaretPos THEN { <> searchState.ahead _ RemakeConjList[them, event.rest.first, TRUE]; searchState.behind _ NIL; matchData.theirData _ them; matchData.lastCaretPos _ GGCaret.GetPoint[them.caret]; } --searching in new viewer ELSE IF event.rest.first # searchState.lastDirection THEN { <> normalGen: SliceDescriptorGenerator _ GGSelect.SelectedSlices[them.scene, normal]; matchSelectD: SliceDescriptor _ NIL; <<"match select" all normal-selected objects so they're not included in search>> FOR normalD: SliceDescriptor _ GGSelect.NextSliceDescriptor[normalGen], GGSelect.NextSliceDescriptor[normalGen] UNTIL normalD = NIL DO GGSelect.SelectSlice[normalD, them.scene, match]; ENDLOOP; IF searchState.ahead # NIL THEN { matchSelectD _ GGSelect.FindSelectedSlice[searchState.ahead.first.slice, them.scene, match]; IF matchSelectD = NIL THEN matchSelectD _ searchState.ahead.first.slice.class.newParts[searchState.ahead.first.slice, NIL, none]; }; searchState.ahead _ RemakeConjList[them, event.rest.first, TRUE]; searchState.behind _ NIL; Match.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; }; RemakeConjList: PUBLIC PROC [theirData: GGData, direction: REF ANY, select: BOOL] RETURNS [ahead: LIST OF SearchInfo _ NIL] = { <> <> matchData: MatchData _ MatchViewer.GetMatchData[]; matchLevel: MatchLevel _ matchData.matchLevel; caretPos: Point _ GGCaret.GetPoint[theirData.caret]; sliceGen: SliceGenerator _ GGScene.SlicesInScene[theirData.scene]; slices: LIST OF Slice; FOR slice: Slice _ GGScene.NextSlice[sliceGen], GGScene.NextSlice[sliceGen] UNTIL slice = NIL DO IF (direction = $SearchAbove AND slice.class.getBoundBox[slice].hiY >= caretPos.y) OR (direction = $SearchBelow AND slice.class.getBoundBox[slice].hiY <= caretPos.y) OR (direction = $SearchFromTop) THEN slices _ CONS[slice, slices]; ENDLOOP; <> 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: LIST OF SearchInfo _ ExpandSlice[slices.first, matchLevel, direction]; ahead _ NARROW[GList.Nconc[infoList, ahead]]; IF select THEN GGSelect.SelectEntireSlice[slices.first, theirData.scene, match]; ENDLOOP; }; SearchConjNext: PUBLIC PROC [forward, backward: LIST OF SearchInfo, direction: REF ANY, refresh: BOOL _ TRUE] RETURNS [newForward, newBackward: LIST OF SearchInfo, found: BOOL] = { <> matchData: MatchData _ MatchViewer.GetMatchData[]; fromData: GGData _ MatchViewer.GetFromData[]; fromLooks: LIST OF REF ANY _ EfficientOrder[Match.GetFromLooks[]]; -- ordered for fast search matchD: MatchDescriptor; [matchD, forward, backward] _ MatchLeadingObject[fromLooks, forward, backward, direction]; IF matchD.isMatch AND matchD.matchedSlices # NIL THEN { -- we've matched something! <> GGSelect.DeselectAll[matchData.theirData.scene, normal]; IF NOT MatchViewer.GetContextSensitive[matchData] THEN SelectMapped[matchData.theirData.scene, matchD.mapping] ELSE SelectMapped[matchData.theirData.scene, SelectionFilter[fromData.scene, matchD.mapping]]; matchData.lastCaretPos _ [GGCaret.GetPoint[matchData.theirData.caret].x, 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, backgndOK: TRUE, edited: FALSE, okToClearFeedback: TRUE]; forward _ DeleteMatchesFromList[forward, matchD.matchedSlices.rest]; backward _ DeleteMatchesFromList[backward, matchD.matchedSlices.rest]; SetLastMatchDescriptor[matchD]; RETURN[forward, backward, TRUE]; } ELSE { -- match failed <> 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[forward, backward, FALSE]; }; }; MatchLeadingObject: PROC [fromLooks: LIST OF REF ANY, forward, backward: LIST OF SearchInfo, direction: REF ANY] RETURNS [matchD: MatchDescriptor, newForward, newBackward: LIST OF SearchInfo] = { matchData: MatchViewer.MatchData _ MatchViewer.GetMatchData[]; domain: LIST OF SearchInfo _ NARROW[GList.Append[forward, backward]]; domainVec: BitVector _ NewBitVector[GList.Length[domain], FALSE]; vecIndex: NAT _ 0; thisInfo, nextInfo: SearchInfo; matchD _ CreateMatchDescriptor[]; IF fromLooks = NIL OR forward = NIL THEN RETURN[matchD, forward, backward]; -- nothing to match on or nothing to match thisInfo _ forward.first; DO -- iterate through forward SearchInfos thisD: SliceDescriptor; ResetDefaults[matchD]; IF NOT Match.IsEmptySearchInfo[thisInfo] THEN { leadObj: REF ANY _ fromLooks.first; [thisD, nextInfo] _ Match.FindMatchInSlice[thisInfo, LIST[leadObj], direction, matchD]; -- those parts of thisInfo that match leadObj, going in direction, using transform in matchD (if any). IF thisD#NIL THEN { -- we've matched leadObj, now try rest domainVec[vecIndex] _ TRUE; -- mark that thisInfo slice is taken matchD _ MatchOtherObjects[fromLooks.rest, domain, domainVec, direction, matchD]; IF matchD.isMatch THEN { ConjClearMatchBits[thisInfo, nextInfo, matchData.theirData.scene]; matchD.matchedSlices _ CONS[thisD, matchD.matchedSlices]; forward _ CONS[nextInfo, forward.rest]; RETURN[matchD, forward, backward]; }; IF matchD.posD.valid THEN { -- must try all possible orientations of this first element nextPosD: MatchTurtle.PositionDescriptor; WHILE (nextPosD _ MatchTurtle.NextMatch[matchD.posD.others]) # NIL DO matchD.posD _ nextPosD; [thisD, nextInfo] _ Match.FindMatchInSlice[thisInfo, LIST[leadObj], direction, matchD]; -- sets matchD.posD and matchD.mapping, etc. IF thisD = NIL THEN LOOP; matchD _ MatchOtherObjects[fromLooks.rest, domain, domainVec, direction, matchD]; -- sets matchD.mapping, etc. IF matchD.isMatch THEN { ConjClearMatchBits[thisInfo, nextInfo, matchData.theirData.scene]; matchD.matchedSlices _ CONS[thisD, matchD.matchedSlices]; forward _ CONS[nextInfo, forward.rest]; RETURN[matchD, forward, backward]; }; matchD.mapping _ NIL; ENDLOOP; }; domainVec[vecIndex] _ FALSE; -- no match: free up this element }; ConjClearMatchBits[thisInfo, nextInfo, matchData.theirData.scene]; thisInfo _ nextInfo; } ELSE { -- we've run out of matches on thisInfo backward _ CONS[forward.first, backward]; forward _ forward.rest; IF forward = NIL THEN { matchD.isMatch _ FALSE; RETURN[matchD, forward, backward]; --nothing to search }; thisInfo _ forward.first; vecIndex _ vecIndex + 1; }; ENDLOOP; }; MatchOtherObjects: PROC [looksList: LIST OF REF ANY, domain: LIST OF SearchInfo, domainVec: BitVector, direction: REF ANY, matchD: MatchDescriptor] RETURNS [MatchDescriptor] = { bbox: BoundBox; simpleLooks: LooksInfo; vecIndex: INT _ -1; restrictMatch: BOOL; 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; restrictMatch _ simpleLooks.shapeDef AND simpleLooks.shape # NIL AND matchD.posD.valid; IF restrictMatch 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 _ domain, nextItem.rest UNTIL nextItem = NIL DO slice: Slice _ nextItem.first.slice; match: SliceDescriptor; vecIndex _ vecIndex + 1; <> IF domainVec[vecIndex] THEN LOOP; -- This puppy is taken IF restrictMatch THEN IF NOT BoundBoxIntersect[bbox, slice.class.getBoundBox[slice]] THEN LOOP; -- A quick rejection of slice [match: match] _ Match.FindMatchInSlice[nextItem.first, LIST[looksList.first], direction, matchD]; IF match # NIL THEN { -- we've got a match on this level. recurse! domainVec[vecIndex] _ TRUE; matchD _ MatchOtherObjects[looksList.rest, domain, domainVec, direction, matchD]; IF matchD.isMatch THEN { matchD.matchedSlices _ CONS[match, matchD.matchedSlices]; RETURN[matchD]; }; domainVec[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] = { IF sliceD.slice.class.type # searchInfo.slice.class.type THEN RETURN[FALSE] ELSE IF searchInfo.slice.class.type # $Outline OR matchLevel = sliceLevel THEN RETURN [searchInfo.slice = sliceD.slice] ELSE -- match if the traj is in the descriptor IF searchInfo.trajs = NIL THEN RETURN[FALSE] -- searchInfo is empty ELSE RETURN[GGOutline.FindTrajInDescriptor[sliceD, searchInfo.trajs.first] # NIL]; }; DeleteMatchesFromList: PROC [searchList: LIST OF SearchInfo, sliceDList: LIST OF SliceDescriptor] RETURNS [newList: LIST OF SearchInfo _ NIL] = { matchLevel: MatchLevel _ MatchViewer.GetMatchData[].matchLevel; FOR l1: LIST OF SearchInfo _ searchList, 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]; }; ExpandSlice: PROC [slice: Slice, matchLevel: MatchLevel, direction: REF ANY] RETURNS [infoList: LIST OF SearchInfo _ NIL] = { IF slice.class.type = $Outline AND (matchLevel = trajLevel OR matchLevel = anywhere) THEN { trajGen: TrajGenerator _ GGOutline.TrajsInOutline[slice]; FOR traj: Traj _ GGScene.NextTraj[trajGen], GGScene.NextTraj[trajGen] UNTIL traj=NIL DO newInfo: SearchInfo _ SearchInfoFromTraj[traj, matchLevel, direction]; infoList _ CONS[newInfo, infoList]; ENDLOOP; } ELSE infoList _ LIST[Match.CreateSearchInfo[slice, direction]]; }; SearchInfoFromTraj: PROC [traj: Traj, matchLevel: MatchLevel, direction: REF ANY] RETURNS [searchInfo: SearchInfo] = { searchInfo _ NEW[Match.SearchInfoObj _ [slice: traj.parent, reverse: direction = $SearchAbove]]; searchInfo.trajs _ LIST[traj]; IF matchLevel = anywhere THEN Match.AddSegs[searchInfo]; }; BoundBoxIntersect: PROC [b1, b2: BoundBox] RETURNS [BOOL] = { 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]]; }; ResetDefaults: 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 slice.class.type FROM $Circle, $Text => sliceD _ slice.class.newParts[slice, NIL, slice]; $Box => { thisSeg _ itemList.first.matchee.seg; IF thisSeg = NIL THEN sliceD _ slice.class.newParts[slice, NIL, slice] -- whole box ELSE sliceD _ GGSlice.WalkSegments[slice, TrueForSeg]; }; $Outline => { traj: Traj _ itemList.first.matchee.traj; thisSeg _ itemList.first.matchee.seg; IF traj = NIL AND thisSeg = NIL THEN sliceD _ slice.class.newParts[slice, NIL, slice] ELSE IF thisSeg = NIL THEN { seq: Sequence _ GGSequence.CreateComplete[traj]; sliceD _ GGOutline.DescriptorFromSequence[traj.parent, seq]; } ELSE sliceD _ GGSlice.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 slice.class.type FROM $Circle, $Text => IF GGSelect.IsSelectedInFull[slice, scene, normal] THEN selected _ TRUE; $Box => { seg: Segment _ itemList.first.matcher.seg; IF seg = NIL THEN selected _ GGSelect.IsSelectedInFull[slice, scene, normal] ELSE { sliceD: SliceDescriptor _ GGSelect.FindSelectedSlice[slice, scene, normal]; IF sliceD # NIL THEN { segGen: SegmentGenerator _ GGSlice.SegmentsInDescriptor[sliceD]; FOR nextSeg: Segment _ GGSlice.NextSegment[segGen], GGSlice.NextSegment[segGen] UNTIL nextSeg = NIL DO IF seg = nextSeg THEN {selected _ TRUE; EXIT;}; ENDLOOP; }; }; }; $Outline => { traj: Traj _ itemList.first.matcher.traj; seg: Segment _ itemList.first.matcher.seg; IF traj = NIL AND seg = NIL THEN selected _ GGSelect.IsSelectedInFull[slice, scene, normal] ELSE IF seg = NIL THEN selected _ GGSelect.IsSelectedInPart[traj, scene, normal] ELSE { sliceD: SliceDescriptor _ GGSelect.FindSelectedSlice[slice, scene, normal]; IF sliceD # NIL THEN { segGen: SegmentGenerator _ GGSlice.SegmentsInDescriptor[sliceD]; FOR nextSeg: Segment _ GGSlice.NextSegment[segGen], GGSlice.NextSegment[segGen] UNTIL nextSeg = NIL DO IF seg = nextSeg THEN {selected _ TRUE; EXIT;}; ENDLOOP; }; }; }; ENDCASE; IF selected THEN selectList _ CONS [itemList.first, selectList]; ENDLOOP; }; <<>> <> YesEvent: PUBLIC GGUserInput.UserInputProc = { success: BOOL _ ReplaceOperation[]; IF success THEN Match.SearchEvent[NIL, event]; }; ReplaceOperation: PROC [] RETURNS [success: BOOL] = { <> 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[] ELSE success _ ReplaceParameters[]; } ELSE IF matchData.replaceOp = doOperations THEN success _ DoOps[] ELSE ERROR; -- unknown replaceOp }; ReplaceParameters: PROC 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]; }; ReplaceConverse: PROC RETURNS [success: BOOL _ TRUE] = { matchData: MatchData _ MatchViewer.GetMatchData[]; scene: Scene _ matchData.theirData.scene; toScene: Scene _ MatchViewer.GetToData[].scene; selected: LIST OF SliceDescriptor _ GGScene.GetSelections[scene, normal]; additions: LIST OF Slice _ NIL; minPriority: INT _ IF selected = NIL THEN -1 ELSE GGScene.GetPriority[scene, selected.first.slice]; matchD: MatchDescriptor _ GetLastMatchDescriptor[]; sliceGen: SliceGenerator _ GGScene.SlicesInScene[toScene]; 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]; }; <> FOR sList: LIST OF SliceDescriptor _ selected.rest, sList.rest UNTIL sList = NIL DO minPriority _ MIN[GGScene.GetPriority[scene, sList.first.slice], minPriority]; ENDLOOP; <> FOR original: Slice _ GGScene.NextSlice[sliceGen], GGScene.NextSlice[sliceGen] UNTIL original = NIL DO entireD: SliceDescriptor; newSlice: Slice _ original.class.copy[original]; newSlice.priority _ GGScene.GetPriority[toScene, original]; entireD _ newSlice.class.newParts[newSlice, NIL, slice]; CopyLooksToSlice[entireD, singleLook]; newSlice.class.transform[entireD, mapTform]; additions _ CONS[newSlice, additions]; ENDLOOP; <> 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, addList.first.class.getBoundBox[addList.first]]; ENDLOOP; GGWindow.RestoreScreenAndInvariants[paintAction: $ObjectChangedBoundBoxProvided, ggData: matchData.theirData, remake: triggerBag, backgndOK: FALSE, edited: TRUE, okToClearFeedback: TRUE]; 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]]; MatchViewer.ErrorFeedback["Warning: using centering transform."]; }; }; 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 _ RemakeConjList[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 _ sliceD.slice.class.newParts[sliceD.slice, NIL, slice]; RETURN[sliceD.slice.class.differenceParts[entire, sliceD]]; }; RemoveAllUnselected: PROC [searchList: LIST OF SearchInfo, selectClass: GGSegmentTypes.SelectionClass, scene: Scene] = { FOR info: LIST OF SearchInfo _ searchList, info.rest UNTIL info = NIL DO selectD: SliceDescriptor _ GGSelect.FindSelectedSlice[info.first.slice, scene, selectClass]; IF selectD # NIL THEN selectD _ ComplementDescriptor[selectD] ELSE selectD _ info.first.slice.class.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] = { oldBBox: BoundBox _ GGScene.BoundBoxOfSelected[ggData.scene, normal]; newBBox: BoundBox; sliceDescGen: SliceDescriptorGenerator _ GGSelect.SelectedSlices[ggData.scene, normal]; FOR sliceD: SliceDescriptor _ GGSelect.NextSliceDescriptor[sliceDescGen], GGSelect.NextSliceDescriptor[sliceDescGen] UNTIL sliceD=NIL DO CopyLooksToSlice[sliceD, toLooks]; ENDLOOP; newBBox _ GGScene.BoundBoxOfSelected[ggData.scene, normal]; ggData.refresh.startBoundBox^ _ GGBoundBox.BoundBoxOfBoxes[LIST[oldBBox, newBBox]]^; -- following traditional gargoyle form, copying structures and not pointers GGWindow.RestoreScreenAndInvariants[paintAction: $ObjectChangedBoundBoxProvided, ggData: ggData, remake: none, backgndOK: FALSE, edited: TRUE, okToClearFeedback: FALSE]; }; CopyLooksToSlice: PROC [sliceD: SliceDescriptor, looks: LooksInfo] = { slice: Slice _ sliceD.slice; parts: SliceParts _ sliceD.parts; IF looks.colorDef THEN slice.class.setStrokeColor[slice, parts, looks.color]; IF looks.fillColorDef THEN slice.class.setFillColor[slice, looks.fillColor]; IF looks.stringDef AND slice.class.type = $Text THEN GGSlice.SetText[slice, looks.string]; IF looks.fontDef AND slice.class.type = $Text THEN { looks.font.transform _ GGSlice.GetFontData[slice].transform; [] _ GGSlice.SetTextFont[slice, looks.font, MatchViewer.GetToData[].feedback]; }; IF looks.fontTformDef AND slice.class.type = $Text THEN [] _ GGSlice.SetTextFont[slice, looks.fontTform, MatchViewer.GetToData[].feedback]; -- SetTextFont will set the transform too! IF looks.dashesDef THEN slice.class.setDashed[slice, parts, looks.dashes.dashed, looks.dashes.pattern, looks.dashes.offset, looks.dashes.length]; IF looks.jointsDef THEN slice.class.setStrokeJoint[slice, parts, looks.joints]; IF looks.endsDef THEN slice.class.setStrokeEnd[slice, parts, looks.ends]; IF looks.widthDef THEN [] _ slice.class.setStrokeWidth[slice, parts, looks.width]; }; GetLooksOfMatch: PROC [matchD: MatchDescriptor, choice: MatchViewer.ChoiceData, value: BOOL] 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 looks _ looks, looks.rest UNTIL looks = NIL DO WITH looks.first SELECT FROM single: LooksInfo => flatList _ CONS[single, flatList]; multiple: LIST OF LIST OF LooksInfo => { FOR multiple _ multiple, multiple.rest UNTIL multiple = NIL DO flatList _ NARROW[GList.Append[multiple.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 SetLastMatchDescriptor: 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[info.slice.class.newParts[info.slice, NIL, none]]; SELECT info.slice.class.type FROM $Box => { IF matchLevel # anywhere THEN RETURN[info.slice.class.newParts[info.slice, NIL, slice]] ELSE RETURN[GGSlice.WalkSegments[info.slice, ContainsSegs]]; }; $Outline => { trajGen: TrajGenerator _ GGOutline.TrajsInOutline[info.slice]; sliceD _ info.slice.class.newParts[info.slice, NIL, slice]; FOR traj: Traj _ GGScene.NextTraj[trajGen], GGScene.NextTraj[trajGen] UNTIL traj = NIL DO IF NOT GList.Member[traj, info.trajs] THEN sliceD _ GGOutline.RemoveTraj[sliceD, traj]; ENDLOOP; IF matchLevel = anywhere THEN { -- only include segs in info.segs segDescriptor: SliceDescriptor _ GGSlice.WalkSegments[info.slice, ContainsSegs]; sliceD _ GGOutline.RemoveTraj[sliceD, info.trajs.first]; sliceD _ info.slice.class.unionParts[sliceD, segDescriptor]; }; }; ENDCASE => RETURN[info.slice.class.newParts[info.slice, NIL, slice]]; }; ChangeAll: PUBLIC GGUserInput.UserInputProc = { matchData: MatchData _ MatchViewer.GetMatchData[]; searchState: SearchState _ GetSearchState[]; toSearch: GGData _ MatchViewer.GetGGInputFocus[]; subCount: NAT _ 0; IF toSearch = NIL THEN RETURN; -- input focus wasn't in a Gargoyle viewer SELECT matchData.searchOp FROM disjunction => { Match.SearchDisjInit[toSearch, event]; DO -- now do the substitute found, success: BOOL; [searchState.ahead, found] _ Match.SearchDisjNext[searchState.ahead, event.rest.first]; IF NOT found THEN EXIT; success _ ReplaceOperation[]; IF NOT success THEN EXIT; subCount _ subCount + 1; ENDLOOP; }; conjunction => { SearchConjInit[toSearch, event]; DO -- now do the substitute found, success: BOOL; [searchState.ahead, searchState.behind, found] _ SearchConjNext[searchState.ahead, searchState.behind, event.rest.first]; IF NOT found THEN EXIT; success _ ReplaceOperation[]; IF NOT success THEN EXIT; subCount _ subCount + 1; ENDLOOP; }; ENDCASE => ERROR; -- Somebody must have implemented a new SearchOp Feedback.PutFHerald[MatchViewer.GetToData[].feedback, oneLiner, "%g substitutions.", IO.card[subCount]]; }; <> <<>> StartOps: PUBLIC GGUserInput.UserInputProc = { searchState: Match.SearchState _ Match.GetSearchState[]; searchState.macroOps _ NIL; searchState.macroOn _ TRUE; [] _ SlackProcess.RegisterLogger[ggData.slackHandle, SaveOps]; [] _ SlackProcess.EnableSessionLogging[ggData.slackHandle]; }; SaveOps: SlackProcess.LoggingProc = { <> toData: GGData _ MatchViewer.GetToData[]; IF toData = clientData AND event.first # $MatchEndOps THEN { -- ie, event occurred in the To viewer, and it wasn't the $MatchEndOps atom searchState.macroOps _ CONS[event, searchState.macroOps]; }; }; EndOps: PUBLIC GGUserInput.UserInputProc = { searchState: Match.SearchState _ Match.GetSearchState[]; IF NOT searchState.macroOn THEN { MatchViewer.ErrorFeedback["Must do StartOps before EndOps!"]; RETURN; }; searchState.macroOn _ FALSE; searchState.macroOps _ NARROW[GList.DReverse[searchState.macroOps]]; --order ops temporally [] _ SlackProcess.DisableSessionLogging[ggData.slackHandle]; [] _ SlackProcess.RegisterLogger[ggData.slackHandle, GGSessionLog.EnterAction]; -- re-install old logging function }; <<>> 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]; }; 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]; GGWindow.RestoreScreenAndInvariants[paintAction: $CaretMoved, ggData: matchData.theirData, remake: none, backgndOK: TRUE, edited: FALSE, okToClearFeedback: FALSE]; }; <> <<>> 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; }; GGUserInput.RegisterAction[atom: $MatchReleaseSemaphore, eventProc: MatchReleaseSemaphore, argType: none, ensureUnique: FALSE]; -- An ACK, used to avoid monitor deadlock in the doOps mechanism END.