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 = { 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] = { 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] = { 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]; }; 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 = { 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]; }; <> <> 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. ’MatchImplB.mesa Contents: Conjunctive (Find All) Search Operations Copyright Σ 1988 by Xerox Corporation. All rights reserved. Kurlander, September 6, 1987 8:25:14 pm PDT Bier, April 8, 1991 10:18 pm PDT Pier, February 20, 1990 5:31 pm PST Global Variables to reduce Search Allocations Orders the looksList to be in the best (most efficient) order for matching. This means that open curves will be before closed curves which will be before objects with no orientation. [ref1: REF ANY, ref2: REF ANY] RETURNS [Basics.Comparison] Items with the largest SortOrder are best to match first Conjunctive Searching (must match All objects in the From viewer) Pre-processing that is done once before one or more searches are performed. Creates a snapshot of the scene (the search list), stored in searchState.ahead. If searching in new viewer or caret has moved, update search list & store a pointer to the current Gargoyle viewer. If reversing direction, set up the search list so that objects that are selected are not considered again. Match-select all normal-selected objects. If we were searching in the middle of a slice just before the search direction was reversed, find those parts that are selected and remove them from consideration. matchSelectD _ GGSelect.FindSelectedSlice[searchState.ahead.first.slice, them.scene, match]; Otherwise, leave the search list alone and search in the same viewer as before. "ahead" will hold SearchInfo's for objects whose boundbox top edge is below the caret if the search is progressing in a downwards direction, and above the caret if the search is progessing upwards. If select is true, then all slices ahead will be match selected (and those behind will be cleared). List those slices whose bounding boxes are OK. Clear the match bits. Build the ahead list. Here's where Graphical Search happens. Find the next match. If found, normal-select match, move caret, restore screen, update search list. matchData.lastCaretPos _ [GGCaret.GetPoint[matchData.theirData.caret].x, matchD.matchedSlices.first.slice.boundBox.hiY]; Otherwise, nothing is selected. Match the leading object at its first orientation. thisAheadParts = parts matching leadObj. If possible, try another orientation. There are no more orientations. No match. There are no more parts left in thisAhead. On to the next thisAhead. We know where where a match for simpleLooks.shape would have to be. Conjunctive Search Utilities For all but cluster level searches, trajectories get their own SearchInfos. Otherwise, searching is done at slice-level. For Context-Sensitive Searches Returns a list of those elements of matches whose matcher field points to an item which is selected. Got that? sliceD: SliceDescriptor _ GGSelect.FindSelectedSlice[slice, scene, normal]; sliceD: SliceDescriptor _ GGSelect.FindSelectedSlice[slice, scene, normal]; Graphical Replace Routines A dispatch procedure: establishes that state is OK and calls the proper replace routine. 1) find out if there's any characteristics ambiguity in To viewer 2) we're in good shape, so now we need only copy looks to selection Get Relevant Looks from Match (that we aren't supposed to change) Find minimum priority of objects to replace Copy originals from To viewer Delete selected slices in active search viewer Add slices to scene (at a reasonable priority) Try to do something reasonable until we add a set of user options Let's center the replacement on that which is being replaced (if nothing is being replaced, as in a context sensitive search with no selection, then we'll center the replacement on the caret MatchViewer.ErrorFeedback["Warning: using centering transform."]; This routine will remake the ahead list to contain only those elements whose boundbox has the proper relationship to the caret, AND which have their match selection bit on. selectD: SliceDescriptor _ GGSelect.FindSelectedSlice[info.first.slice, scene, selectClass]; Sort back to front (in decreasing order). [ref1: Slice, ref2: Slice] RETURNS [Basics.Comparison] added NIL history events to following calls. KAP. August 16, 1988 Creates a Looks List that describes all the objects that were matched. must be that entire slice was matched, so add its looks to the looksList must be that entire traj was matched, so add its looks to the looksList must be that we have a seg match. we're ignoring shape info, which is OK for current application of this routine. May need to be revised later This is similar to MatchImplA's AdjustBitsOfDifference, except AdjustBitsOfDifference makes the simplifying assumption that given a slice in the scene there is only a single SearchInfo for that slice. Thus, it need only deselect the entire slice, and select next. This routine will first deselect first, and then select next. It runs a bit slower, but is more general. Do Operations Routines This LoggingProc is initiated by StartOps, and terminated by EndOps. Save the event at the front of searchState.macroOps. For each event, replace each REF Point with the proper transformed Point Now, we must move the caret back to the last position, so the search proceeds harmoniously. This is complicated by the fact that we must set the caret back AFTER the queue has emptied. For this reason we've created a semaphore which is released when the $MatchReleaseSemaphore atom is reached by the slack process dispatcher. Grumble. Non-destructive (copies the first list). Synchronization Primitives (prevents doing another macro operation before the first is done). Κ$ύ˜Icode™šΟnœ*™2K™Kšœ žœžœ ˜Kšœ˜Kšœ žœ˜K˜!Kšœ žœžœ˜Kš œ žœžœžœžœ˜K˜ K˜Kšœ0˜0Kšœ5žœ˜K˜K˜—šœžœ3žœžœžœžœžœžœ˜K˜Kš K™Kšžœ&žœžœžœ˜dš  œžœžœžœžœ˜BKšœ  œ˜:Kšœ:˜:J˜—Kšœ'Οtœ’˜?K˜—Kš ,™,šžœ˜Kšœ3˜3Kšœ:˜:K˜—K˜K˜—š œžœ1žœžœžœ˜vKšœ žœP˜`Kšœžœ˜Kšžœžœ(˜EK˜K˜—š œžœžœžœžœ˜EKš žœ žœ žœžœžœ˜0Kš žœ žœ žœžœžœ˜)Kšžœžœžœžœžœžœžœ˜^Kšžœžœžœ˜K˜K˜—šœžœžœžœ˜KKšœ žœ˜Kšœžœ$žœ žœ˜MK˜K˜—šœžœ˜=Kšœžœ˜Kšœžœ˜Kšœžœ˜Kšœžœ˜Kšœžœ˜Kšœžœ˜K˜K˜—š  œžœ žœžœžœžœ˜\Kšœ žœ$˜0šžœžœžœž˜ K˜Kšžœ˜—K˜K˜—š  œžœžœ&žœžœ˜Uš œ˜%Kšžœžœžœžœžœžœžœ˜6Kšœ˜—K˜š žœ žœžœ$žœ žœž˜PK˜K˜,šžœž˜%Kšœ6žœ ˜B˜ K˜%Kš žœ žœžœ%žœ Ÿ ˜RKšžœ5˜9K˜—˜ K˜)K˜%Kš žœžœžœ žœžœ%žœ˜Tšžœžœ žœžœ˜Kšœžœ ˜'˜,K˜Kšœ#˜#K˜—K˜BK˜—Kšžœ5˜9K˜—Kšžœ˜—K˜,Kšžœ˜—K˜K˜—K˜K™šœžœžœ&žœžœ žœžœžœ žœ˜†Kšœ$ œC™oš žœ žœžœ$žœ žœž˜PK˜,Kšœ žœžœ˜šžœž˜%Kšœžœ1žœ žœ˜Z˜ K˜*Kšžœžœžœ;˜Lšžœ˜K™KK˜Dšžœ žœžœ˜K˜Cšžœwžœ žœž˜Kšžœžœ žœžœ˜/Kšžœ˜—K˜—K˜—K˜—˜ K˜)K˜*Kš žœžœžœžœžœ;˜[Kšžœžœžœžœ:˜Pšžœ˜K™KK˜Dšžœ žœžœ˜K˜Cšžœižœ žœž˜‚Kšžœžœ žœžœ˜/Kšžœ˜—K˜—K˜—K˜—Kšžœ˜—Kšžœ žœžœ˜@Kšžœ˜—K˜—K™™K˜—šœžœžœ žœžœžœ žœžœ˜XKšœ0žœ&™XK˜2š žœžœžœžœžœ˜EK˜;Kšžœžœ˜K˜—Kš žœ!žœžœ0žœ#žœ%˜«Kšžœžœ$žœ˜AKšžœžœŸ˜ K˜K˜K˜—šœžœ žœžœžœ žœžœ˜QK™AK˜2Kš œ žœžœžœžœ˜.K˜8šžœžœžœ˜Kšœ8 œ ˜EKšžœžœ˜K˜—K™CKšœ?˜?K˜K˜—šœžœ žœžœžœ žœžœ˜Oš œžœžœžœžœ˜OKšœ3Ÿ"˜UKšœ9žœ ˜EKšœ;˜;K˜&KšœCžœ˜HKšœ žœ˜&J˜—Kšœ3Ÿ˜KKšœ*Ÿ˜9Kšœ0Ÿ˜DKš œ žœžœ œŸ˜qKšœ žœžœ žœ˜Kšœ žœžœžœŸ4˜RK˜3K˜UKš A™AKš œ žœžœžœžœ œžœ˜LKšœ;˜;šžœžœžœ˜KšœK˜KKšžœžœ˜K˜—Kš +™+šžœ žœžœžœ˜.š žœžœžœ(žœ žœž˜NKšœžœ=˜NKšžœ˜—K˜—Kšœ   œ.˜BKš ™Kšœ&’œ’˜@Kš .™.K˜RKš .™.K˜-š žœ žœžœ!žœ žœž˜KK˜4Kšœj˜jKšžœ˜—Kšœ   œ˜+Kšžœ žœ‹žœžœ˜ΈKšœ˜˜K˜——šœžœ1žœ1˜€šžœžœ˜K˜iK˜—šžœŸ,˜3K™AK™ΎK˜HK˜GK˜KKš œžœžœ žœžœžœ<žœ-˜­K˜KK™AK˜—K˜K˜—šœžœ˜!Kšœ€žœ)™¬K˜>K˜2Kšžœ"žœZžœ˜ˆKšžœžœ"žœVžœ˜‰KšžœžœŸ˜šžœžœžœŸ4˜VK˜IK˜—˜K˜——šœžœžœ˜RKšœ<žœ ˜HKšžœ-˜3K˜K˜—šœžœ žœžœJ˜wš žœžœžœ#žœžœž˜GKšœ\™\KšœU˜UKšžœ žœžœ(˜=Kšžœ1žœ ˜AKšœ4˜4Kšžœ˜—˜K˜——šœžœžœžœžœ žœžœ ˜[K•StartOfExpansion> -- [ref1: REF ANY, ref2: REF ANY] RETURNS [Basics.Comparison]™)š œ ˜+Kšœžœ™6Kšœžœ˜Kšœ˜Kšœ˜Kšžœ*˜0K˜—Kšœ:˜:K˜K˜—šœžœ/žœžœ˜Yš œ žœžœžœžœ˜LKšœ"˜"J˜—KšœE˜EK˜K˜Kšœ   œ'˜;K˜Kšœ,’œ ’œ’˜CKšœ4˜4Kšœ;žœ˜TK˜Kšœ   œ ˜Kšžœ žœxžœžœ˜₯˜K˜——šœžœ0˜FK˜K˜!Kšœžœ$žœ™AKšžœžœ<žœ˜WKšžœžœ>žœ˜]Kšžœžœ#žœ&˜cšžœžœ#žœ˜=Kšœ<˜K˜,š žœ žœžœ žœžœ˜,K™HKšœ žœ+žœ˜NK˜—šžœžœ žœžœ˜K™GKšœ žœ)žœ˜LK˜—šžœ˜K™!Kšœ$žœG™mKšœ žœBžœžœ˜jK˜—Kšžœ˜—K˜—K˜šœžœžœžœžœžœžœžœ˜dš œžœ žœžœžœžœžœ žœžœ žœ˜[šžœ žœžœžœžœžœ žœž˜Fšžœžœž˜Kšœ žœ˜7š œ žœžœžœžœ˜(šžœ žœžœžœžœ&žœ žœž˜\Kšœ žœ*˜;Kšžœ˜—K˜—Kšžœžœ˜—Kšžœ˜—K˜—Kšœ žœžœ&˜8Kš žœ žœžœžœžœŸ˜7šžœžœžœ"žœžœžœ žœž˜WKš žœžœ/žœžœžœŸ˜YKšžœ˜—Kšžœ˜K˜K˜—šœžœžœ˜BKšœ œ˜/Kšœ"˜"K–3[caret: GGInterfaceTypes.Caret, chair: REF ANY]šœ*žœ˜/K˜K˜—KšœŸC˜`šœžœžœ˜BK˜K˜K˜—šœžœžœ˜:Kšžœ ˜K˜K˜—šœžœ,˜DK™τK˜;Kš œžœžœžœžœžœ˜UK˜GKšžœ žœžœ.˜DK˜K˜—šœžœžœ˜UKš œžœ!˜OK˜?Kš žœžœžœžœžœŸ:˜ZKšžœžœžœ!žœ ˜Yšžœ ž˜*˜ Kšžœžœžœ!žœ ˜QKšžœžœ4˜?K˜—˜ š œžœžœžœžœ˜FKšžœžœžœ,˜UJ˜—Kšœ)žœ ˜5Kšœ,’œ’˜HšžœžœŸ!˜AK˜SKšœ6˜6K˜6K˜—K˜—Kšžœžœ!žœ ˜?—˜K˜——š œžœ˜/K˜2K˜,Kšœ žœžœ˜&K˜1Kšœ žœ˜šžœ žœžœŸ*˜CKšœS˜SKšžœ˜K˜—KšœS˜Sšžœž˜˜K˜&šžœŸ˜Kšœžœžœ˜Kšœ`˜`Kšžœžœžœžœ˜Kšœ$žœ˜+Kšžœžœ žœžœ˜Kšœ˜Kšžœ˜—K˜—˜Kšœ"˜"šžœŸ˜Kšœžœžœ˜Kšœ‚˜‚Kšžœžœžœžœ˜Kšœ$žœ˜+Kšžœžœ žœžœ˜Kšœ˜Kšžœ˜—K˜—KšžœžœŸ0˜B—Kš œSžœžœžœ žœžœ˜Kšœi  œ žœžœ˜›K˜—K˜šœ™K™—š œ  œ˜'K™zK˜)šžœžœžœŸK˜ˆKšœžœ˜9K˜—Kšœ˜K˜—šœžœ˜.K˜8šžœžœžœ˜!Kšœ=˜=Kšžœ˜K˜—Kšœžœ˜Kšœžœ(Ÿ˜[K˜