<> <> <> <> <> <> <<>> DIRECTORY Atom, BiScrollers, BufferedRefresh, CodeTimer, Feedback, FunctionCache, GGBasicTypes, GGBoundBox, GGCaret, GGGravity, GGInterfaceTypes, GGModelTypes, GGScene, GGRefresh, GGSegmentTypes, GGSelect, GGSequence, GGShapes, GGTraj, GGUtility, Imager, ImagerBackdoor, ImagerTransformation, List, Rope, Rosary, SlackProcess, TIPUser, Vectors2d, ViewerAbort, ViewerClasses; GGRefreshImpl: CEDAR MONITOR IMPORTS Atom, BiScrollers, BufferedRefresh, CodeTimer, Feedback, FunctionCache, GGBoundBox, GGCaret, GGGravity, GGScene, GGSelect, GGSequence, GGShapes, GGTraj, GGUtility, Imager, ImagerBackdoor, List, Rope, SlackProcess, Vectors2d, ViewerAbort EXPORTS GGRefresh = BEGIN BitVector: TYPE = GGBasicTypes.BitVector; BoundBox: TYPE = GGModelTypes.BoundBox; BoundBoxGenerator: TYPE = GGScene.BoundBoxGenerator; CameraData: TYPE = GGModelTypes.CameraData; Caret: TYPE = GGInterfaceTypes.Caret; Color: TYPE = Imager.Color; FeatureData: TYPE = GGGravity.FeatureData; OutlineDescriptor: TYPE = GGModelTypes.OutlineDescriptor; Rectangle: TYPE = Imager.Rectangle; Sandwich: TYPE = BufferedRefresh.Sandwich; Slice: TYPE = GGModelTypes.Slice; SliceParts: TYPE = GGModelTypes.SliceParts; SliceGenerator: TYPE = GGModelTypes.SliceGenerator; SliceDescriptor: TYPE = GGModelTypes.SliceDescriptor; SliceDescriptorGenerator: TYPE = GGModelTypes.SliceDescriptorGenerator; EntityGenerator: TYPE = GGModelTypes.EntityGenerator; GGData: TYPE = GGInterfaceTypes.GGData; Joint: TYPE = GGModelTypes.Joint; JointGenerator: TYPE = GGModelTypes.JointGenerator; Outline: TYPE = GGModelTypes.Outline; Point: TYPE = GGBasicTypes.Point; Scene: TYPE = GGModelTypes.Scene; Segment: TYPE = GGSegmentTypes.Segment; SegAndIndex: TYPE = GGSequence.SegAndIndex; SegmentGenerator: TYPE = GGModelTypes.SegmentGenerator; Sequence: TYPE = GGModelTypes.Sequence; SelectionClass: TYPE = GGInterfaceTypes.SelectionClass; SequenceGenerator: TYPE = GGModelTypes.SequenceGenerator; Traj: TYPE = GGModelTypes.Traj; Vector: TYPE = GGBasicTypes.Vector; Problem: PUBLIC SIGNAL [msg: Rope.ROPE] = CODE; ActionAreaPaint: PUBLIC PROC [screen: Imager.Context, whatHasChanged: ATOM, ggData: GGData] = TRUSTED { PaintWithAbort: PROC = TRUSTED { DoActionAreaPaint[screen, whatHasChanged, ggData ! UNWIND => { Feedback.Append[ggData.feedback, "Refresh Aborted", oneLiner]; SlackProcess.FlushQueue[ggData.slackHandle]; -- you have to do this HERE! ggData.refresh.suppressRefresh _ FALSE; -- in case you killed FastPlayback ggData.aborted _ ALL[TRUE]; -- copies of aborted for all purposes }; ]; }; IF whatHasChanged=$ViewersPaintEntireScene THEN ViewerAbort.CallWithAbortEnabled[ggData.actionArea, PaintWithAbort] ELSE DoActionAreaPaint[screen, whatHasChanged, ggData]; -- SlackProcess is watching for aborts }; DoActionAreaPaint: PROC [screen: Imager.Context, whatHasChanged: ATOM, ggData: GGData] = { <> <> <<>> showColors: BOOL _ ggData.refresh.showColors.state = on; IF ggData.aborted[refresh] THEN { -- last paint got killed => unknown bitmap cache states ggData.aborted[refresh] _ FALSE; PaintEntireScene[screen, ggData, showColors]; } ELSE SELECT whatHasChanged FROM $None => NULL; <> $PaintEntireScene, $ViewersPaintEntireScene => PaintEntireScene[screen, ggData, showColors]; $ViewersPaintAllPlanes => PaintAllPlanes[screen, ggData, showColors, FALSE, FALSE]; $NewAlignmentsDeselected => PaintAllPlanes[screen, ggData, showColors, FALSE, FALSE]; $NewAlignmentsSelected => PaintAllPlanes[screen, ggData, showColors, FALSE, FALSE]; $SequencesMadeHot => PaintAllPlanes[screen, ggData, showColors, FALSE, FALSE]; $SequencesMadeCold => PaintAllPlanes[screen, ggData, showColors, FALSE, FALSE]; $Everything => PaintEntireScene[screen, ggData, showColors]; $SelectionChanged => PaintAllPlanes[screen, ggData, showColors, FALSE, FALSE]; $FinishedAdding => FinishedAdding[screen, ggData, showColors]; $FinishedDragging => FinishedDragging[screen, ggData, showColors]; $CaretMoved => PaintAllPlanes[screen, ggData, showColors, FALSE, FALSE]; -- GGMouseEventImplA $AnchorAdded => PaintAllPlanes[screen, ggData, showColors, FALSE, FALSE]; -- GGEventImplB $AnchorRemoved => PaintAllPlanes[screen, ggData, showColors, FALSE, FALSE]; -- GGEventImplB <> $DuringMotion => PaintDragOverlay[screen, ggData, TRUE, showColors]; -- GGMouseEventImplA $DuringCaretPos => PaintDragOverlay[screen, ggData, FALSE, showColors]; -- GGMouseEventImplA $DuringSelect => DuringSelect[screen, ggData, showColors]; $ObjectChangedInPlace => ObjectChangedInPlace[screen, ggData, normal, showColors]; $ObjectChangedBoundBoxProvided => ObjectChangedBoundBoxProvided[screen, ggData, showColors]; $ObjectAdded => ObjectAdded[screen, ggData, showColors]; <> $PaintSpot => PaintSpot[screen, ggData]; $PaintHitLine => PaintHitLine[screen, ggData]; $PaintOddHitLine => PaintOddHitLine[screen, ggData]; $PaintAlign => PaintAlign[screen, ggData]; $PaintBoundBoxes => PaintBoundBoxes[screen, ggData]; $PaintTightBoxes => PaintTightBoxes[screen, ggData]; $PaintOutlineBoxes => PaintOutlineBoxes[screen, ggData]; $PaintSelectionBox => PaintSelectionBox[screen, ggData]; $DrawBackgroundBox, $DrawOverlayBox, $DrawRubberBox, $DrawDragBox => DrawMovingBox[screen, ggData, whatHasChanged]; ENDCASE => { Feedback.Append[ggData.feedback, Rope.Cat["Gargoyle GGRefreshImpl doesn't know how to ", Atom.GetPName[whatHasChanged], "."], oneLiner]; Feedback.Blink[ggData.feedback]; }; }; << [Artwork node; type 'ArtworkInterpress on' to command tool] >> CreateSandwich: PUBLIC PROC [] RETURNS [sandwich: Sandwich] = { sandwich _ BufferedRefresh.CreateSandwich[LIST[ [$Background, TRUE, RefreshBackground], -- back ... [$Overlay, FALSE, RefreshOverlay], [$CPFeedback, FALSE, RefreshCPFeedback], [$Foreground, TRUE, RefreshForeground], [$CaretPlane, FALSE, RefreshCaretPlane]]]; -- ... to front }; PaintEntireScene: PUBLIC PROC [screen: Imager.Context, ggData: GGData, showColors: BOOL] = { sandwich: Sandwich _ ggData.refresh.sandwich; CodeTimer.StartInt[$PaintEntireScene, $Gargoyle]; BufferedRefresh.SetLayerOK[sandwich, $Foreground, FALSE]; BufferedRefresh.SetLayerOK[sandwich, $Background, FALSE]; PaintAllPlanes[screen, ggData, showColors, FALSE, FALSE]; CodeTimer.StopInt[$PaintEntireScene, $Gargoyle]; }; PaintAllPlanes: PROC [screen: Imager.Context, ggData: GGData, showColors: BOOL, caretIsMoving: BOOL, dragInProgress: BOOL] = { sandwich: Sandwich _ ggData.refresh.sandwich; clientToViewer, viewerToClient: Imager.Transformation; ignoreBackingMap: BOOL; IF ggData.refresh.suppressRefresh THEN RETURN; ggData.refresh.caretIsMoving _ caretIsMoving; ggData.refresh.dragInProgress _ dragInProgress; [clientToViewer, viewerToClient] _ BiScrollers.GetStyle[].GetTransforms[BiScrollers.QuaBiScroller[ggData.actionArea]]; <> ignoreBackingMap _ showColors; BufferedRefresh.DrawSandwich[sandwich, screen, clientToViewer, viewerToClient, ggData, ignoreBackingMap]; }; <<>> <> <<>> RefreshBackground: PROC [dc: Imager.Context, boundRect: Rectangle, clientData: REF ANY] = { ggData: GGData _ NARROW[clientData]; IF ggData.refresh.suppressRefresh THEN RETURN; DrawObjectsFiltered[dc: dc, ggData: ggData, filter: GGBoundBox.BoundBoxFromRectangle[boundRect], excludeOverlay: FALSE, overObject: NIL]; }; RefreshOverlay: PROC [dc: Imager.Context, boundRect: Rectangle, clientData: REF ANY] = { <> ggData: GGData _ NARROW[clientData]; DrawDragOverlayAux: PROC = { IF ggData.refresh.orderedOverlayList=NIL THEN ggData.refresh.orderedOverlayList _ OrderOverlayList[ggData]; -- update ordered list FOR oList: LIST OF REF ANY _ ggData.refresh.orderedOverlayList, oList.rest UNTIL oList = NIL DO WITH oList.first SELECT FROM sliceD: SliceDescriptor => { sliceD.slice.class.drawTransform[sliceD, dc, ggData.camera, ggData.drag.transform]; }; outlineD: OutlineDescriptor => { outlineD.slice.class.drawTransform[outlineD, dc, ggData.camera, ggData.drag.transform]; }; caret: Caret => { caret _ NARROW[oList.first]; DrawCaret[dc, caret, Imager.black]; }; ENDCASE => ERROR; ENDLOOP; }; IF ggData.refresh.suppressRefresh THEN RETURN; Imager.DoSaveAll[dc, DrawDragOverlayAux]; }; RefreshCPFeedback: PROC [dc: Imager.Context, boundRect: Rectangle, clientData: REF ANY] = { ggData: GGData _ NARROW[clientData]; caretIsMoving: BOOL _ ggData.refresh.caretIsMoving; dragInProgress: BOOL _ ggData.refresh.dragInProgress; IF ggData.refresh.suppressRefresh THEN RETURN; IF ggData.camera.quality#quality THEN { DrawAttractorFeedback[dc, ggData, dragInProgress, caretIsMoving]; DrawCpsOfSelectedOutlines[dc, ggData.scene, ggData.camera, dragInProgress, caretIsMoving]; DrawCpsOfSelectedSlices[dc, ggData.scene, ggData.camera, dragInProgress, caretIsMoving]; }; }; RefreshForeground: PROC [dc: Imager.Context, boundRect: Rectangle, clientData: REF ANY] = { ggData: GGData _ NARROW[clientData]; IF ggData.refresh.suppressRefresh THEN RETURN; FunctionCache.Flush[ggData.refresh.lineCache]; GGGravity.DrawAlignBagRegardless[dc, ggData.hitTest.alignBag, ggData]; }; RefreshCaretPlane: PROC [dc: Imager.Context, boundRect: Rectangle, clientData: REF ANY] = { <> ggData: GGData _ NARROW[clientData]; IF ggData.refresh.suppressRefresh THEN RETURN; DrawCaret[dc, ggData.caret, Imager.black]; DrawAnchor[dc, ggData.anchor, Imager.black]; }; NoteNewForeground: PUBLIC PROC [alignObjects: LIST OF FeatureData, ggData: GGData] = { <> PaintForeground: PROC = { GGGravity.DrawFeatureList[foregroundContext, alignObjects, ggData]; }; foregroundContext: Imager.Context _ BufferedRefresh.GetLayerContext[ggData.refresh.sandwich, $Foreground]; Imager.DoSaveAll[foregroundContext, PaintForeground]; BufferedRefresh.SetLayerOK[ggData.refresh.sandwich, $Foreground, TRUE]; }; UpdateForegroundForMotion: PUBLIC PROC [ggData: GGData] = { <> PaintForeground: PROC = { GGGravity.DrawAlignBagRegardless[foregroundContext, ggData.hitTest.alignBag, ggData]; }; foregroundContext: Imager.Context _ BufferedRefresh.GetLayerContext[ggData.refresh.sandwich, $Foreground]; Imager.DoSaveAll[foregroundContext, PaintForeground]; BufferedRefresh.SetLayerOK[ggData.refresh.sandwich, $Foreground, TRUE]; }; <> DrawAttractorFeedback: PROC [dc: Imager.Context, ggData: GGData, dragInProgress, caretIsMoving: BOOL] = { <> attractor: REF ANY; selectedParts: SliceParts; scene: Scene _ ggData.scene; attractor _ GGCaret.GetAttractor[ggData.caret]; IF attractor = NIL THEN RETURN; WITH attractor SELECT FROM sliceD: SliceDescriptor => { selectedD: SliceDescriptor _ GGSelect.FindSelectedSlice[sliceD.slice, scene, normal]; selectedParts _ IF selectedD = NIL THEN NIL ELSE selectedD.parts; sliceD.slice.class.drawAttractorFeedback[sliceD, selectedParts, dragInProgress, dc, ggData.camera]; }; outlineD: OutlineDescriptor => { selectedD: OutlineDescriptor _ GGSelect.FindSelectedOutline[outlineD.slice, scene, normal]; selectedParts _ IF selectedD = NIL THEN NIL ELSE selectedD.parts; outlineD.slice.class.drawAttractorFeedback[outlineD, selectedParts, dragInProgress, dc, ggData.camera]; }; ENDCASE => ERROR; }; MemberTraj: PROC [ref: Traj, list: LIST OF Traj] RETURNS [BOOL] = { FOR tl: LIST OF Traj _ list, tl.rest UNTIL tl = NIL DO IF tl.first = ref THEN RETURN[TRUE]; ENDLOOP; RETURN[FALSE]; }; AllSelectedOutlines: PROC [scene: Scene] RETURNS [selectedList: LIST OF Outline _ NIL] = { <> ptr: LIST OF Outline; outDGen: GGModelTypes.OutlineDescriptorGenerator; outDGen _ GGSelect.SelectedOutlines[scene, hot]; [selectedList, ptr] _ GGUtility.StartOutlineList[]; FOR outD: OutlineDescriptor _ GGSelect.NextOutlineDescriptor[outDGen], GGSelect.NextOutlineDescriptor[outDGen] UNTIL outD = NIL DO [selectedList, ptr] _ GGUtility.AddOutline[outD.slice, selectedList, ptr]; ENDLOOP; outDGen _ GGSelect.SelectedOutlines[scene, normal]; FOR outD: OutlineDescriptor _ GGSelect.NextOutlineDescriptor[outDGen], GGSelect.NextOutlineDescriptor[outDGen] UNTIL outD = NIL DO IF NOT GGSelect.IsSelectedInPart[outD.slice, scene, hot] THEN [selectedList, ptr] _ GGUtility.AddOutline[outD.slice, selectedList, ptr]; ENDLOOP; }; AllSelectedSlices: PROC [scene: Scene] RETURNS [selectedList: LIST OF Slice _ NIL] = { <> ptr: LIST OF Slice; sGen: SliceDescriptorGenerator _ GGSelect.SelectedSlices[scene, hot]; [selectedList, ptr] _ GGUtility.StartSliceList[]; FOR sd: SliceDescriptor _ GGSelect.NextSliceDescriptor[sGen], GGSelect.NextSliceDescriptor[sGen] UNTIL sd = NIL DO [selectedList, ptr] _ GGUtility.AddSlice[sd.slice, selectedList, ptr]; ENDLOOP; sGen _ GGSelect.SelectedSlices[scene, normal]; FOR sd: SliceDescriptor _ GGSelect.NextSliceDescriptor[sGen], GGSelect.NextSliceDescriptor[sGen] UNTIL sd = NIL DO IF NOT GGSelect.IsSelectedInPart[sd.slice, scene, hot] THEN [selectedList, ptr] _ GGUtility.AddSlice[sd.slice, selectedList, ptr]; ENDLOOP; }; DrawCpsOfSelectedOutlines: PROC [dc: Imager.Context, scene: Scene, camera: CameraData, dragInProgress, caretIsMoving: BOOL] = { normalD, hotD: OutlineDescriptor; normalParts, hotParts: SliceParts; outline: Outline; IF caretIsMoving OR dragInProgress THEN RETURN; FOR oList: LIST OF Outline _ AllSelectedOutlines[scene], oList.rest UNTIL oList=NIL DO outline _ oList.first; normalD _ GGSelect.FindSelectedOutline[outline, scene, normal]; hotD _ GGSelect.FindSelectedOutline[outline, scene, hot]; normalParts _ IF normalD # NIL THEN normalD.parts ELSE NIL; hotParts _ IF hotD # NIL THEN hotD.parts ELSE NIL; outline.class.drawSelectionFeedback[outline, normalParts, hotParts, dc, camera, dragInProgress, caretIsMoving, FALSE, caretIsMoving]; ENDLOOP; }; DrawCpsOfSelectedSlices: PROC [dc: Imager.Context, scene: Scene, camera: CameraData, dragInProgress, caretIsMoving: BOOL] = { normalSliceD, hotSliceD: SliceDescriptor; normalParts, hotParts: SliceParts; slice: Slice; IF caretIsMoving OR dragInProgress THEN RETURN; FOR sList: LIST OF Slice _ AllSelectedSlices[scene], sList.rest UNTIL sList=NIL DO slice _ sList.first; normalSliceD _ GGSelect.FindSelectedSlice[slice, scene, normal]; hotSliceD _ GGSelect.FindSelectedSlice[slice, scene, hot]; normalParts _ IF normalSliceD # NIL THEN normalSliceD.parts ELSE NIL; hotParts _ IF hotSliceD # NIL THEN hotSliceD.parts ELSE NIL; slice.class.drawSelectionFeedback[slice, normalParts, hotParts, dc, camera, dragInProgress, caretIsMoving, FALSE, caretIsMoving]; ENDLOOP; }; <> <> <<[attractor: attractor, on: attOn, jointNum: attJointNum, seg: attSeg] _ GGCaret.GetAttractor[ggData.caret];>> <> < {};>> < {};>> < attOn _ nothing; >> <<};>> <<>> <> <> <> <> <> <> <> <<[attractor: attractor, on: attOn, jointNum: attJointNum, seg: attSeg, segNum: attSegNum] _ GGCaret.GetAttractor[ggData.caret];>> <<[chair: chair, on: chairOn, jointNum: chairJointNum, segNum: chairSegNum, seg: chairSeg] _ GGCaret.GetChair[ggData.caret];>> <> < {>> <> <> <> <> <> <> <> <> <<};>> <<}>> <> <> <> <> <> <> <<};>> <<}>> <> <> <<};>> < { -- if slices are the same (don't user parts) then chair=attractor>> <> < IF aSliceD.slice=chairSliceD.slice THEN attOn _ nothing;>> <> <<};>> < attOn _ nothing; >> <<};>> <<>> MemberSlice: PROC [ref: SliceDescriptor, list: LIST OF SliceDescriptor] RETURNS [BOOL] = { FOR tl: LIST OF SliceDescriptor _ list, tl.rest UNTIL tl = NIL DO IF tl.first = ref THEN RETURN[TRUE]; ENDLOOP; RETURN[FALSE]; }; <<>> <> <<>> ObjectChangedInPlace: PROC [screen: Imager.Context, ggData: GGData, selectClass: GGInterfaceTypes.SelectionClass _ normal, showColors: BOOL] = { <> bBox: BoundBox _ GGBoundBox.BoundBoxOfSelected[ggData.scene, selectClass]; RepairBackgroundInBoundBox[ggData, bBox, TRUE, NIL]; PaintAllPlanes[screen, ggData, showColors, FALSE, FALSE]; }; ObjectChangedBoundBoxProvided: PROC [screen: Imager.Context, ggData: GGData, showColors: BOOL] = { <> RepairBackgroundInBoundBox[ggData, ggData.refresh.startBoundBox, TRUE, NIL]; PaintAllPlanes[screen, ggData, showColors, FALSE, FALSE]; }; ObjectAdded: PROC [screen: Imager.Context, ggData: GGData, showColors: BOOL] = { <> RepairBackgroundInBoundBox[ggData, ggData.refresh.startBoundBox, FALSE, ggData.refresh.addedObject]; PaintAllPlanes[screen, ggData, showColors, FALSE, FALSE]; }; RepairBackgroundInBoundBox: PROC [ggData: GGData, bBox: BoundBox, eraseFirst: BOOL _ FALSE, overObject: REF ANY] = { backgroundContext: Imager.Context _ BufferedRefresh.GetLayerContext[ggData.refresh.sandwich, $Background]; IF ggData.refresh.suppressRefresh THEN RETURN; IF ggData.refresh.showColors.state = off THEN { PaintObjectsInBox: PROC = { IF eraseFirst THEN GGBoundBox.EraseWithinBoundBox[backgroundContext, bBox]; DrawObjectsFiltered[dc: backgroundContext, ggData: ggData, filter: bBox, overObject: overObject]; }; Imager.DoSaveAll[backgroundContext, PaintObjectsInBox]; BufferedRefresh.SetLayerOK[ggData.refresh.sandwich, $Background, TRUE]; }; }; <> <<>> DuringSelect: PROC [screen: Imager.Context, ggData: GGData, showColors: BOOL] = { IF ggData.refresh.suppressRefresh THEN RETURN; IF NOT showColors THEN { clientToViewer, viewerToClient: Imager.Transformation; [clientToViewer, viewerToClient] _ BiScrollers.GetStyle[].GetTransforms[BiScrollers.QuaBiScroller[ggData.actionArea]]; BufferedRefresh.DrawSandwich[ggData.refresh.sandwich, screen, clientToViewer, viewerToClient, ggData, showColors]; } ELSE {}; -- no feedback in SlowPaint mode }; SplitBackgroundAndOverlay: PUBLIC PROC [ggData: GGData, restoreBox: BoundBox] = { <> PaintAllButOverlayed: PROC = { GGBoundBox.EraseWithinBoundBox[backgroundContext, restoreBox]; DrawObjectsFiltered[dc: backgroundContext, ggData: ggData, filter: restoreBox, excludeOverlay: TRUE, overObject: NIL]; }; backgroundContext: Imager.Context; IF ggData.refresh.suppressRefresh THEN RETURN; backgroundContext _ BufferedRefresh.GetLayerContext[ggData.refresh.sandwich, $Background]; Imager.DoSaveAll[backgroundContext, PaintAllButOverlayed]; }; -- end StoreBackground PaintDragOverlay: PROC [screen: Imager.Context, ggData: GGData, dragInProgress: BOOL, showColors: BOOL] = { <> clientToViewer, viewerToClient: Imager.Transformation; ggData.refresh.dragInProgress _ dragInProgress; ggData.refresh.caretIsMoving _ TRUE; [clientToViewer, viewerToClient] _ BiScrollers.GetStyle[].GetTransforms[BiScrollers.QuaBiScroller[ggData.actionArea]]; BufferedRefresh.DrawSandwich[ggData.refresh.sandwich, screen, clientToViewer, viewerToClient, ggData, showColors]; }; FinishedAdding: PROC [screen: Imager.Context, ggData: GGData, showColors: BOOL] = { <> MergeBackgroundAndOverlay[ggData, ggData.refresh.startBoundBox, FALSE, ggData.refresh.addedObject, showColors]; PaintAllPlanes[screen, ggData, showColors, FALSE, FALSE]; }; BackmostSelectedSlice: PROC [scene: Scene] RETURNS [backmost: REF ANY] = { entityGen: GGModelTypes.EntityGenerator; entityGen _ GGScene.TopLevelEntitiesInScene[scene]; FOR entity: REF ANY _ GGScene.NextEntity[entityGen], GGScene.NextEntity[entityGen] UNTIL entity = NIL DO WITH entity SELECT FROM outline: Outline => { outD: OutlineDescriptor _ GGSelect.FindSelectedOutline[outline, scene, normal]; IF outD # NIL THEN RETURN[outline]; }; slice: Slice => { sliceD: SliceDescriptor _ GGSelect.FindSelectedSlice[slice, scene, normal]; IF sliceD # NIL THEN RETURN[slice]; }; ENDCASE => ERROR; ENDLOOP; RETURN[NIL]; }; FinishedDragging: PROC [screen: Imager.Context, ggData: GGData, showColors: BOOL] = { overObject: REF ANY _ BackmostSelectedSlice[ggData.scene]; GGBoundBox.EnlargeByBox[ggData.refresh.startBoundBox, GGBoundBox.BoundBoxOfMoving[ggData.scene]]; MergeBackgroundAndOverlay[ggData, ggData.refresh.startBoundBox, FALSE, overObject, showColors]; PaintAllPlanes[screen, ggData, showColors, FALSE, FALSE]; }; MergeBackgroundAndOverlay: PROC [ggData: GGData, bBox: BoundBox, eraseFirst: BOOL _ FALSE, overObject: REF ANY, showColors: BOOL] = { <> backgroundContext: Imager.Context _ BufferedRefresh.GetLayerContext[ggData.refresh.sandwich, $Background]; MergeBackgroundAndOverlayAux: PROC = { IF eraseFirst THEN GGBoundBox.EraseWithinBoundBox[backgroundContext, bBox]; DrawObjectsFiltered[dc: backgroundContext, ggData: ggData, filter: bBox, overObject: overObject]; }; IF ggData.refresh.suppressRefresh THEN RETURN; IF NOT showColors THEN { Imager.DoSaveAll[backgroundContext, MergeBackgroundAndOverlayAux]; BufferedRefresh.SetLayerOK[ggData.refresh.sandwich, $Background, TRUE]; }; }; <> <<>> DrawObjects: PROC [screen: Imager.Context, ggData: GGData] = { <> scene: Scene _ ggData.scene; camera: CameraData _ ggData.camera; entityGen: EntityGenerator; Imager.SetColor[screen, Imager.black]; entityGen _ GGScene.TopLevelEntitiesInScene[scene]; FOR entity: REF ANY _ GGScene.NextEntity[entityGen], GGScene.NextEntity[entityGen] UNTIL entity = NIL DO WITH entity SELECT FROM outline: Outline => outline.class.drawParts[outline, NIL, screen, camera, FALSE]; slice: Slice => slice.class.drawParts[slice, NIL, screen, camera, FALSE]; ENDCASE => ERROR; ENDLOOP; }; DrawObjectsFiltered: PROC [dc: Imager.Context, ggData: GGData, filter: GGBoundBox.BoundBox, excludeOverlay: BOOL _ FALSE, overObject: REF ANY] = { <> OutsideOf: PROC [test, bound: GGBoundBox.BoundBox] RETURNS [BOOL] = { RETURN[ test.hiX < bound.loX OR test.loX > bound.hiX OR test.hiY < bound.loY OR test.loY > bound.hiY ]; -- these tests may have to be <= or >= }; DrawObjectsFilteredAux: PROC = { -- need to clip to filter, then image entityGen: EntityGenerator _ GGScene.TopLevelEntitiesInScene[scene]; thisEntity: REF ANY _ GGScene.NextEntity[entityGen]; IF overObject # NIL THEN { UNTIL thisEntity = overObject OR thisEntity = NIL DO thisEntity _ GGScene.NextEntity[entityGen] ENDLOOP; IF thisEntity = NIL THEN RETURN; }; Imager.SetColor[dc, Imager.black]; GGBoundBox.Clip[dc: dc, bBox: filter]; FOR entity: REF ANY _ thisEntity, GGScene.NextEntity[entityGen] UNTIL entity = NIL DO WITH entity SELECT FROM outline: Outline => { IF excludeOverlay AND outline.onOverlay THEN LOOP; IF NOT OutsideOf[outline.class.getBoundBox[outline, NIL], filter] THEN outline.class.drawParts[outline, NIL, dc, camera, FALSE]; }; slice: Slice => { IF excludeOverlay AND slice.onOverlay THEN LOOP; IF NOT OutsideOf[slice.class.getBoundBox[slice, NIL], filter] THEN slice.class.drawParts[slice, NIL, dc, camera, FALSE]; }; ENDCASE => ERROR; ENDLOOP; }; scene: Scene _ ggData.scene; camera: CameraData _ ggData.camera; IF filter=NIL OR filter.null THEN RETURN; Imager.DoSaveAll[dc, DrawObjectsFilteredAux]; }; DrawCaret: PROC [dc: Imager.Context, caret: Caret, color: Imager.Color] = { caretPos: Point _ GGCaret.GetPoint[caret]; IF NOT GGCaret.Exists[caret] THEN RETURN; Imager.SetColor[dc, color]; GGShapes.DrawCaret[dc, caretPos]; }; DrawAnchor: PROC [dc: Imager.Context, caret: Caret, color: Imager.Color] = { caretPos: Point _ GGCaret.GetPoint[caret]; IF NOT GGCaret.Exists[caret] THEN RETURN; Imager.SetColor[dc, color]; GGShapes.DrawAnchor[dc, caretPos]; }; <<>> <> DrawNewAnchor: PROC [dc: Imager.Context, ggData: GGData] = { anchorPos: Point _ GGCaret.GetPoint[ggData.anchor]; Imager.SetColor[dc, Imager.black]; GGShapes.DrawAnchor[dc, anchorPos]; }; PaintSpot: PROC [dc: Imager.Context, ggData: GGData] = { IF ggData.refresh.suppressRefresh THEN RETURN; Imager.SetColor[dc, Imager.black]; GGShapes.DrawSpot[dc, ggData.refresh.spotPoint]; }; PaintHitLine: PROC [dc: Imager.Context, ggData: GGData] = { IF ggData.refresh.suppressRefresh THEN RETURN; Imager.SetColor[dc, Imager.black]; Imager.SetStrokeEnd[dc, round]; Imager.MaskVector[dc, [ggData.refresh.spotPoint.x, ggData.refresh.spotPoint.y], [ggData.refresh.hitPoint.x, ggData.refresh.hitPoint.y]]; GGShapes.DrawFilledRect[dc, ggData.refresh.spotPoint, 3.0]; }; PaintOddHitLine: PROC [dc: Imager.Context, ggData: GGData] = { IF ggData.refresh.suppressRefresh THEN RETURN; Imager.SetColor[dc, Imager.black]; Imager.SetStrokeEnd[dc, round]; Imager.MaskVector[dc, [ggData.refresh.spotPoint.x, ggData.refresh.spotPoint.y], [ggData.refresh.hitPoint.x, ggData.refresh.hitPoint.y]]; GGShapes.DrawCP[dc, ggData.refresh.spotPoint]; }; PaintAlign: PROC [dc: Imager.Context, ggData: GGData] = { <> IF ggData.refresh.suppressRefresh THEN RETURN; GGGravity.DrawAlignBagRegardless[dc, NARROW[ggData.hitTest.alignBag], ggData]; }; PaintBoundBoxes: PROC [dc: Imager.Context, ggData: GGData] = { bBoxGen: BoundBoxGenerator; PaintBoundBoxesAux: PROC = { <> bBoxGen _ GGScene.BoundBoxesInScene[ggData.scene]; FOR box: BoundBox _ GGScene.NextBox[bBoxGen], GGScene.NextBox[bBoxGen] UNTIL box = NIL DO GGBoundBox.DrawBoundBox[dc, box]; ENDLOOP; }; IF ggData.refresh.suppressRefresh THEN RETURN; Imager.DoSaveAll[dc, PaintBoundBoxesAux]; }; PaintTightBoxes: PROC [dc: Imager.Context, ggData: GGData] = { box: BoundBox; PaintTightBoxesAux: PROC = { <> entityGen: GGModelTypes.EntityGenerator; entityGen _ GGSelect.SelectedStuff[ggData.scene, normal]; FOR entity: REF ANY _ GGScene.NextEntity[entityGen], GGScene.NextEntity[entityGen] UNTIL entity = NIL DO WITH entity SELECT FROM sliceD: SliceDescriptor => { box _ sliceD.slice.class.getTightBox[sliceD.slice, sliceD.parts]; }; outlineD: OutlineDescriptor => { box _ outlineD.slice.class.getTightBox[outlineD.slice, outlineD.parts]; }; ENDCASE => ERROR; GGBoundBox.DrawBoundBox[dc, box]; ENDLOOP; }; IF ggData.refresh.suppressRefresh THEN RETURN; Imager.DoSaveAll[dc, PaintTightBoxesAux]; }; PaintOutlineBoxes: PROC [dc: Imager.Context, ggData: GGData] = { PaintBoundBoxesAux: PROC = { <> outSeqGen: GGSelect.OutlineSequenceGenerator; bBox: BoundBox; outSeqGen _ GGSelect.SelectedOutlineSequences[ggData.scene, normal]; FOR outSeq: GGSelect.OutlineSequence _ GGSelect.NextOutlineSequences[outSeqGen], GGSelect.NextOutlineSequences[outSeqGen] UNTIL outSeq = NIL DO IF outSeq.fenceSeq # NIL THEN { bBox _ GGTraj.GetBoundBox[outSeq.fenceSeq.traj]; GGBoundBox.DrawBoundBox[dc, bBox]; }; FOR holeSeq: Sequence _ GGSequence.NextSequence[outSeq.holeSeqs], GGSequence.NextSequence[outSeq.holeSeqs] UNTIL holeSeq = NIL DO bBox _ GGTraj.GetBoundBox[holeSeq.traj]; GGBoundBox.DrawBoundBox[dc, bBox]; ENDLOOP; ENDLOOP; }; IF ggData.refresh.suppressRefresh THEN RETURN; Imager.DoSaveAll[dc, PaintBoundBoxesAux]; }; PaintSelectionBox: PUBLIC PROC [dc: Imager.Context, ggData: GGData] = { box: BoundBox _ NIL; PaintSelectionBoxAux: PROC = { <> box _ GGBoundBox.BoundBoxOfSelected[ggData.scene]; IF NOT box.null THEN GGBoundBox.DrawBoundBox[dc, box]; }; IF ggData.refresh.suppressRefresh THEN RETURN; Imager.DoSaveAll[dc, PaintSelectionBoxAux]; }; GetOutlineParts: PROC [sliceD: OutlineDescriptor, atom: ATOM] RETURNS [parts: OutlineDescriptor] = { parts _ SELECT atom FROM $DrawBackgroundBox => sliceD.slice.class.movingParts[sliceD.slice, sliceD.parts].background, $DrawOverlayBox => sliceD.slice.class.movingParts[sliceD.slice, sliceD.parts].overlay, $DrawRubberBox => sliceD.slice.class.movingParts[sliceD.slice, sliceD.parts].rubber, $DrawDragBox => sliceD.slice.class.movingParts[sliceD.slice, sliceD.parts].drag, ENDCASE => ERROR; }; GetSliceParts: PROC [sliceD: SliceDescriptor, atom: ATOM] RETURNS [parts: SliceDescriptor] = { parts _ SELECT atom FROM $DrawBackgroundBox => sliceD.slice.class.movingParts[sliceD.slice, sliceD.parts].background, $DrawOverlayBox => sliceD.slice.class.movingParts[sliceD.slice, sliceD.parts].overlay, $DrawRubberBox => sliceD.slice.class.movingParts[sliceD.slice, sliceD.parts].rubber, $DrawDragBox => sliceD.slice.class.movingParts[sliceD.slice, sliceD.parts].drag, ENDCASE => ERROR; }; DrawMovingBox: PUBLIC PROC [dc: Imager.Context, ggData: GGData, atom: ATOM] = { OutlineEnlargeBox: PROC [outlineD: OutlineDescriptor] = { thisBox: BoundBox; theseParts: OutlineDescriptor; theseParts _ GetOutlineParts[outlineD, atom]; IF theseParts.parts = NIL THEN thisBox _ GGBoundBox.NullBoundBox[] ELSE thisBox _ outlineD.slice.class.getBoundBox[outlineD.slice, theseParts.parts]; GGBoundBox.EnlargeByBox[bBox: box, by: thisBox]; }; SliceEnlargeBox: PROC [sliceD: SliceDescriptor] = { thisBox: BoundBox; theseParts: SliceDescriptor; theseParts _ GetSliceParts[sliceD, atom]; IF theseParts.parts = NIL THEN thisBox _ GGBoundBox.NullBoundBox[] ELSE thisBox _ sliceD.slice.class.getBoundBox[sliceD.slice, theseParts.parts]; GGBoundBox.EnlargeByBox[bBox: box, by: thisBox]; }; box: BoundBox _ GGBoundBox.NullBoundBox[]; IF ggData.refresh.suppressRefresh THEN RETURN; GGSelect.DoForEachSelectedOutline[ggData.scene, normal, OutlineEnlargeBox]; GGSelect.DoForEachSelectedSlice[ggData.scene, normal, SliceEnlargeBox]; IF NOT box.null THEN GGBoundBox.DrawBoundBox[dc, box]; }; EraseAll: PROC [dc: Imager.Context] = { rect: Imager.Rectangle; rect _ ImagerBackdoor.GetBounds[dc]; Imager.SetColor[dc, Imager.white]; Imager.MaskRectangle[dc, rect]; }; <<>> <> EndFourPoints: PROC [traj: Traj] RETURNS [firstPoint, secondPoint, secondToLastPoint, lastPoint: Point, firstWidth, lastWidth: REAL] = { seg: Segment; cpCount: NAT; seg _ GGTraj.FetchSegment[traj, 0]; firstWidth _ seg.strokeWidth; firstPoint _ seg.lo; cpCount _ seg.class.controlPointCount[seg]; IF cpCount > 0 THEN { secondPoint _ seg.class.controlPointGet[seg, 0]; } ELSE { secondPoint _ seg.hi; }; seg _ GGTraj.FetchSegment[traj, GGTraj.HiSegment[traj]]; lastWidth _ seg.strokeWidth; lastPoint _ seg.hi; cpCount _ seg.class.controlPointCount[seg]; IF cpCount > 0 THEN { secondToLastPoint _ seg.class.controlPointGet[seg, cpCount-1]; } ELSE { secondToLastPoint _ seg.lo; }; }; ExcludeArrows: PROC [dc: Imager.Context, traj: Traj] = { OPEN Vectors2d; ClipPath: Imager.PathProc = { moveTo[Add[Add[tip, Scale[perp, -halfWidth]], Scale[axis, thisWidth/2.0]]]; lineTo[Add[Add[tip, Scale[perp, halfWidth]], Scale[axis, thisWidth/2.0]]]; lineTo[Sub[tip, Add[Scale[axis, height], Scale[perp, halfWidth]]]]; lineTo[Sub[tip, Add[Scale[axis, height], Scale[perp, -halfWidth]]]]; lineTo[Add[tip, Scale[perp, -halfWidth]]]; }; firstPoint, secondPoint, secondToLastPoint, lastPoint, tip, base: Point; firstWidth, lastWidth, thisWidth, height, halfWidth: REAL; axis, perp: Vector; IF NOT traj.loArrow AND NOT traj.hiArrow THEN RETURN; [firstPoint, secondPoint, secondToLastPoint, lastPoint, firstWidth, lastWidth] _ EndFourPoints[traj]; IF traj.loArrow THEN { thisWidth _ firstWidth; [height, halfWidth] _ GGShapes.ArrowSize[thisWidth]; tip _ firstPoint; base _ secondPoint; axis _ Vectors2d.Normalize[Vectors2d.Sub[tip, base]]; perp _ [axis.y, -axis.x]; Imager.Clip[dc, ClipPath, FALSE, TRUE]; }; IF traj.hiArrow THEN { thisWidth _ lastWidth; [height, halfWidth] _ GGShapes.ArrowSize[thisWidth]; tip _ lastPoint; base _ secondToLastPoint; axis _ Vectors2d.Normalize[Vectors2d.Sub[tip, base]]; perp _ [axis.y, -axis.x]; Imager.Clip[dc, ClipPath, FALSE, TRUE]; }; }; DrawArrows: PROC [dc: Imager.Context, traj: Traj, ggData: GGData] = { firstPoint, secondPoint, secondToLastPoint, lastPoint: Point; firstWidth, lastWidth: REAL; IF NOT traj.loArrow AND NOT traj.hiArrow THEN RETURN; [firstPoint, secondPoint, secondToLastPoint, lastPoint, firstWidth, lastWidth] _ EndFourPoints[traj]; IF traj.loArrow THEN GGShapes.DrawArrow[dc, firstPoint, secondPoint, firstWidth]; IF traj.hiArrow THEN GGShapes.DrawArrow[dc, lastPoint, secondToLastPoint, lastWidth]; }; <> DrawJoints: PROC [dc: Imager.Context, traj: Traj, ggData: GGData] = { <> <> <> <> <> <> IF ggData.camera.quality = quality OR traj.parent.onOverlay THEN RETURN; IF NOT traj.visibleJoints THEN RETURN; <> Imager.SetColor[dc, Imager.black]; FOR i: INT IN [0..GGTraj.HiJoint[traj]] DO GGShapes.DrawJoint[dc, GGTraj.FetchJointPos[traj, i]]; ENDLOOP; }; DrawJointsInSequenceFeedback: PROC [dc: Imager.Context, seq: Sequence, ggData: GGData, selectClass: SelectionClass _ normal] = { jointGen: JointGenerator; <> IF ggData.camera.quality = quality THEN RETURN; Imager.SetColor[dc, Imager.black]; jointGen _ GGSequence.JointsInSequence[seq]; FOR i: INT _ GGSequence.NextJoint[jointGen], GGSequence.NextJoint[jointGen] UNTIL i = -1 DO GGShapes.DrawSelectedJoint[dc, GGTraj.FetchJointPos[seq.traj, i], selectClass]; ENDLOOP; }; <<>> <> SnapShot: PUBLIC PROC [dc: Imager.Context, ggData: GGData] = { <> boundRect: Rectangle _ ImagerBackdoor.GetBounds[dc]; SnapshotBackground[dc, ggData]; RefreshOverlay[dc, boundRect, ggData]; }; SnapshotBackground: PROC [dc: Imager.Context, ggData: GGData] = { <> scene: Scene _ ggData.scene; camera: CameraData _ ggData.camera; entityGen: EntityGenerator; <> Imager.SetColor[dc, Imager.black]; <> entityGen _ GGScene.TopLevelEntitiesInScene[ggData.scene]; FOR entity: REF ANY _ GGScene.NextEntity[entityGen], GGScene.NextEntity[entityGen] UNTIL entity = NIL DO WITH entity SELECT FROM outline: Outline => { IF OnOverlay[outline, ggData] THEN LOOP ELSE outline.class.drawParts[outline, NIL, dc, camera, FALSE]; }; slice: Slice => { IF OnOverlay[slice, ggData] THEN LOOP ELSE slice.class.drawParts[slice, NIL, dc, camera, FALSE]; }; ENDCASE => ERROR; ENDLOOP; <> IF NOT OnOverlay[ggData.caret, ggData] THEN DrawCaret[dc, ggData.caret, Imager.black]; DrawAnchor[dc, ggData.anchor, Imager.black]; <> GGGravity.DrawAlignBagRegardless[dc, NARROW[ggData.hitTest.alignBag], ggData]; }; InterpressEntireScene: PUBLIC PROC [dc: Imager.Context, ggData: GGData] = { DrawObjects[dc, ggData]; }; <<>> <> <<>> MoveToOverlay: PUBLIC PROC [entity: REF ANY, ggData: GGData] = { WITH entity SELECT FROM caret: Caret => { IF OnOverlay[caret, ggData] THEN ERROR; GGCaret.TellOnOverlay[caret, TRUE]; ggData.refresh.overlayList _ List.Nconc[LIST[caret], ggData.refresh.overlayList]; }; outlineD: OutlineDescriptor => { IF OnOverlay[outlineD, ggData] THEN ERROR; outlineD.slice.onOverlay _ TRUE; ggData.refresh.overlayList _ List.Nconc[LIST[outlineD], ggData.refresh.overlayList]; }; sliceD: SliceDescriptor => { IF OnOverlay[sliceD, ggData] THEN ERROR; sliceD.slice.onOverlay _ TRUE; ggData.refresh.overlayList _ List.Nconc[LIST[sliceD], ggData.refresh.overlayList]; }; ENDCASE => ERROR; ggData.refresh.orderedOverlayList _ NIL; }; MoveAllSelectedToOverlay: PUBLIC PROC [ggData: GGData, selectClass: SelectionClass] = { entityGen: EntityGenerator _ GGSelect.SelectedStuff[ggData.scene, selectClass]; FOR entity: REF ANY _ GGScene.NextEntity[entityGen], GGScene.NextEntity[entityGen] UNTIL entity = NIL DO MoveToOverlay[entity, ggData]; ENDLOOP; }; MoveToBackground: PUBLIC PROC [entity: REF ANY, ggData: GGData] = { IF NOT OnOverlay[entity, ggData] THEN RETURN; ggData.refresh.overlayList _ List.DRemove[entity, ggData.refresh.overlayList]; WITH entity SELECT FROM sliceD: SliceDescriptor => sliceD.slice.onOverlay _ FALSE; outline: Outline => outline.onOverlay _ FALSE; caret: Caret => GGCaret.TellOnOverlay[caret, FALSE]; ENDCASE => ERROR; ggData.refresh.orderedOverlayList _ NIL; }; MoveOverlayToBackground: PUBLIC PROC [ggData: GGData] = { FOR overlayList: LIST OF REF ANY _ ggData.refresh.overlayList, overlayList.rest UNTIL overlayList = NIL DO WITH overlayList.first SELECT FROM sliceD: SliceDescriptor => sliceD.slice.onOverlay _ FALSE; outlineD: OutlineDescriptor => outlineD.slice.onOverlay _ FALSE; caret: Caret => GGCaret.TellOnOverlay[caret, FALSE]; ENDCASE => ERROR; ENDLOOP; ggData.refresh.overlayList _ NIL; ggData.refresh.orderedOverlayList _ NIL; }; EmptyOverlay: PUBLIC PROC [ggData: GGData] RETURNS [BOOL] = { RETURN[ggData.refresh.overlayList = NIL]; }; OnOverlay: PROC [entity: REF ANY, ggData: GGData] RETURNS [BOOL] = { WITH entity SELECT FROM caret: Caret => RETURN[GGCaret.IsOnOverlay[caret]]; outline: Outline => RETURN[outline.onOverlay]; slice: Slice => RETURN[slice.onOverlay]; sliceD: SliceDescriptor => RETURN[sliceD.slice.onOverlay]; outlineD: OutlineDescriptor => RETURN[outlineD.slice.onOverlay]; ENDCASE => ERROR; }; OrderOverlayList: PROC [ggData: GGData] RETURNS [orderedList: LIST OF REF ANY _ NIL] = { <> FindOverlayedD: PROC [slice: REF ANY] RETURNS [sliceD: REF ANY _ NIL] = { FOR ov: LIST OF REF ANY _ ggData.refresh.overlayList, ov.rest UNTIL ov=NIL DO WITH ov.first SELECT FROM sliceD: SliceDescriptor => IF sliceD.slice=slice THEN RETURN[ov.first]; outlineD: OutlineDescriptor => IF outlineD.slice=slice THEN RETURN[ov.first]; ENDCASE => ERROR; ENDLOOP; RETURN[NIL]; }; sliceD: REF ANY; finger: LIST OF REF ANY; entityGen: EntityGenerator; [orderedList, finger] _ GGUtility.StartList[]; entityGen _ GGScene.TopLevelEntitiesInScene[ggData.scene]; FOR entity: REF ANY _ GGScene.NextEntity[entityGen], GGScene.NextEntity[entityGen] UNTIL entity = NIL DO IF OnOverlay[entity, ggData] THEN { <> <> WITH entity SELECT FROM outline: Outline => { -- scene entity is identical to overlay entity sliceD _ FindOverlayedD[entity]; IF sliceD = NIL THEN sliceD _ outline.class.newParts[outline, NIL, topLevel]; [orderedList, finger] _ GGUtility.AddEntity[sliceD, orderedList, finger]; }; slice: Slice => { sliceD _ FindOverlayedD[entity]; IF sliceD = NIL THEN sliceD _ slice.class.newParts[slice, NIL, topLevel]; [orderedList, finger] _ GGUtility.AddEntity[sliceD, orderedList, finger]; }; ENDCASE => ERROR; }; ENDLOOP; IF OnOverlay[ggData.caret, ggData] THEN [orderedList, finger] _ GGUtility.AddEntity[ggData.caret, orderedList, finger]; }; <<>> <> AdjustContextForDrawBits: PROC [dc: Imager.Context, ggData: GGData] = { <> viewerToClient: Imager.Transformation _ BiScrollers.GetStyle[].GetTransforms[BiScrollers.QuaBiScroller[ggData.actionArea]].viewerToClient; Imager.ConcatT[dc, viewerToClient]; }; InitStats: PROC [] = { interval: CodeTimer.Interval; interval _ CodeTimer.CreateInterval[$PaintEntireScene]; CodeTimer.AddInt[interval, $Gargoyle]; }; InitStats[]; END. <> <>