//------------------------------------------------------------- // // Copyright © Microsoft Corporation. All Rights Reserved. // //------------------------------------------------------------- // @owner=alexgor, deliant //================================================================= // File: AnnotationCollection.cs // // Namespace: System.Web.UI.WebControls[Windows.Forms].Charting // // Classes: AnnotationCollection, AnnotationCollectionEditor // // Purpose: Collection of annotation objects. // // Reviewed: // //=================================================================== #region Used namespace using System; using System.Collections; using System.Collections.Specialized; using System.ComponentModel; using System.ComponentModel.Design; using System.Data; using System.Drawing; using System.Drawing.Design; using System.Drawing.Text; using System.Drawing.Drawing2D; using System.Globalization; using System.Diagnostics.CodeAnalysis; #if Microsoft_CONTROL using System.Windows.Forms; using System.Windows.Forms.DataVisualization.Charting; using System.Windows.Forms.DataVisualization.Charting.Data; using System.Windows.Forms.DataVisualization.Charting.ChartTypes; using System.Windows.Forms.DataVisualization.Charting.Utilities; using System.Windows.Forms.DataVisualization.Charting.Borders3D; #else using System.Web; using System.Web.UI; using System.Web.UI.DataVisualization.Charting; using System.Web.UI.DataVisualization.Charting.Data; using System.Web.UI.DataVisualization.Charting.Utilities; #endif #endregion #if Microsoft_CONTROL namespace System.Windows.Forms.DataVisualization.Charting #else namespace System.Web.UI.DataVisualization.Charting #endif { /// /// AnnotationCollection is a collection that stores chart annotation objects. /// /// /// /// All chart annotations are stored in this collection. It is exposed as /// a property of the chart. It is also used to /// store annotations inside the class. /// /// This class includes methods for adding, inserting, iterating and removing annotations. /// /// [ SRDescription("DescriptionAttributeAnnotations3"), ] #if ASPPERM_35 [AspNetHostingPermission(System.Security.Permissions.SecurityAction.InheritanceDemand, Level = AspNetHostingPermissionLevel.Minimal)] [AspNetHostingPermission(System.Security.Permissions.SecurityAction.LinkDemand, Level = AspNetHostingPermissionLevel.Minimal)] #endif public class AnnotationCollection : ChartNamedElementCollection { #region Fields /// /// Group this collection belongs too /// internal AnnotationGroup AnnotationGroup { get; set; } #if Microsoft_CONTROL // Annotation object that was last clicked on internal Annotation lastClickedAnnotation = null; // Start point of annotation moving or resizing private PointF _movingResizingStartPoint = PointF.Empty; // Current resizing mode private ResizingMode _resizingMode = ResizingMode.None; // Annotation object which is currently placed on the chart internal Annotation placingAnnotation = null; #endif #endregion #region Construction and Initialization /// /// Initializes a new instance of the class. /// /// The parent chart element. internal AnnotationCollection(IChartElement parent) : base(parent) { } #endregion #region Items Inserting and Removing Notification methods /// /// Initializes the specified item. /// /// The item. internal override void Initialize(Annotation item) { if (item != null) { TextAnnotation textAnnotation = item as TextAnnotation; if (textAnnotation != null && string.IsNullOrEmpty(textAnnotation.Text) && Chart != null && Chart.IsDesignMode()) { textAnnotation.Text = item.Name; } //If the collection belongs to annotation group we need to pass a ref to this group to all the child annotations if (this.AnnotationGroup != null) { item.annotationGroup = this.AnnotationGroup; } item.ResetCurrentRelativePosition(); } base.Initialize(item); } /// /// Deinitializes the specified item. /// /// The item. internal override void Deinitialize(Annotation item) { if (item != null) { item.annotationGroup = null; item.ResetCurrentRelativePosition(); } base.Deinitialize(item); } /// /// Finds an annotation in the collection by name. /// /// /// Name of the annotation to find. /// /// /// object, or null (or nothing) if it does not exist. /// public override Annotation FindByName(string name) { foreach(Annotation annotation in this) { // Compare annotation name if(annotation.Name == name) { return annotation; } // Check if annotation is a group AnnotationGroup annotationGroup = annotation as AnnotationGroup; if(annotationGroup != null) { Annotation result = annotationGroup.Annotations.FindByName(name); if(result != null) { return result; } } } return null; } #endregion #region Painting /// /// Paints all annotation objects in the collection. /// /// Chart graphics used for painting. /// Indicates that only annotation objects are redrawn. [SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", Justification="This parameter is used when compiling for the Microsoft version of Chart")] internal void Paint(ChartGraphics chartGraph, bool drawAnnotationOnly) { #if Microsoft_CONTROL ChartPicture chartPicture = this.Chart.chartPicture; // Restore previous background using double buffered bitmap if(!chartPicture.isSelectionMode && this.Count > 0 /*&& !this.Chart.chartPicture.isPrinting*/) { chartPicture.backgroundRestored = true; Rectangle chartPosition = new Rectangle(0, 0, chartPicture.Width, chartPicture.Height); if(chartPicture.nonTopLevelChartBuffer == null || !drawAnnotationOnly) { // Dispose previous bitmap if(chartPicture.nonTopLevelChartBuffer != null) { chartPicture.nonTopLevelChartBuffer.Dispose(); chartPicture.nonTopLevelChartBuffer = null; } // Copy chart area plotting rectangle from the chart's dubble buffer image into area dubble buffer image if (this.Chart.paintBufferBitmap != null && this.Chart.paintBufferBitmap.Size.Width >= chartPosition.Size.Width && this.Chart.paintBufferBitmap.Size.Height >= chartPosition.Size.Height) { chartPicture.nonTopLevelChartBuffer = this.Chart.paintBufferBitmap.Clone( chartPosition, this.Chart.paintBufferBitmap.PixelFormat); } } else if(drawAnnotationOnly && chartPicture.nonTopLevelChartBuffer != null) { // Restore previous background this.Chart.paintBufferBitmapGraphics.DrawImageUnscaled( chartPicture.nonTopLevelChartBuffer, chartPosition); } } #endif // Microsoft_CONTROL // Draw all annotation objects foreach(Annotation annotation in this) { // Reset calculated relative position annotation.ResetCurrentRelativePosition(); if(annotation.IsVisible()) { bool resetClip = false; // Check if anchor point ----osiated with plot area is inside the scaleView if(annotation.IsAnchorVisible()) { // Set annotation object clipping if(annotation.ClipToChartArea.Length > 0 && annotation.ClipToChartArea != Constants.NotSetValue && Chart != null) { int areaIndex = Chart.ChartAreas.IndexOf(annotation.ClipToChartArea); if( areaIndex >= 0 ) { // Get chart area object ChartArea chartArea = Chart.ChartAreas[areaIndex]; chartGraph.SetClip(chartArea.PlotAreaPosition.ToRectangleF()); resetClip = true; } } // Start Svg Selection mode string url = String.Empty; #if !Microsoft_CONTROL url = annotation.Url; #endif // !Microsoft_CONTROL chartGraph.StartHotRegion( annotation.ReplaceKeywords(url), annotation.ReplaceKeywords(annotation.ToolTip) ); // Draw annotation object annotation.Paint(Chart, chartGraph); // End Svg Selection mode chartGraph.EndHotRegion( ); // Reset clipping region if(resetClip) { chartGraph.ResetClip(); } } } } } #endregion #region Mouse Events Handlers #if Microsoft_CONTROL /// /// Mouse was double clicked. /// internal void OnDoubleClick() { if(lastClickedAnnotation != null && lastClickedAnnotation.AllowTextEditing) { TextAnnotation textAnnotation = lastClickedAnnotation as TextAnnotation; if(textAnnotation == null) { AnnotationGroup group = lastClickedAnnotation as AnnotationGroup; if (group != null) { // Try to edit text annotation in the group foreach (Annotation annot in group.Annotations) { TextAnnotation groupAnnot = annot as TextAnnotation; if (groupAnnot != null && groupAnnot.AllowTextEditing) { // Get annotation position in relative coordinates PointF firstPoint = PointF.Empty; PointF anchorPoint = PointF.Empty; SizeF size = SizeF.Empty; groupAnnot.GetRelativePosition(out firstPoint, out size, out anchorPoint); RectangleF textPosition = new RectangleF(firstPoint, size); // Check if last clicked coordinate is inside this text annotation if (groupAnnot.GetGraphics() != null && textPosition.Contains(groupAnnot.GetGraphics().GetRelativePoint(this._movingResizingStartPoint))) { textAnnotation = groupAnnot; lastClickedAnnotation = textAnnotation; break; } } } } } if(textAnnotation != null) { // Start annotation text editing textAnnotation.BeginTextEditing(); } } } /// /// Checks if specified point is contained by any of the selection handles. /// /// Point which is tested in pixel coordinates. /// Handle containing the point or None. /// Annotation that contains the point or Null. internal Annotation HitTestSelectionHandles(PointF point, ref ResizingMode resizingMode) { Annotation annotation = null; if( Common != null && Common.graph != null) { PointF pointRel = Common.graph.GetRelativePoint(point); foreach(Annotation annot in this) { // Reset selcted path point annot.currentPathPointIndex = -1; // Check if annotation is selected if(annot.IsSelected) { if(annot.selectionRects != null) { for(int index = 0; index < annot.selectionRects.Length; index++) { if(!annot.selectionRects[index].IsEmpty && annot.selectionRects[index].Contains(pointRel)) { annotation = annot; if(index > (int)ResizingMode.AnchorHandle) { resizingMode = ResizingMode.MovingPathPoints; annot.currentPathPointIndex = index - 9; } else { resizingMode = (ResizingMode)index; } } } } } } } return annotation; } /// /// Mouse button pressed in the control. /// /// Event arguments. /// Returns true if event is handled and no further processing required. internal void OnMouseDown(MouseEventArgs e, ref bool isHandled) { // Reset last clicked annotation object and stop text editing if(lastClickedAnnotation != null) { TextAnnotation textAnnotation = lastClickedAnnotation as TextAnnotation; if(textAnnotation != null) { // Stop annotation text editing textAnnotation.StopTextEditing(); } lastClickedAnnotation = null; } // Check if in annotation placement mode if( this.placingAnnotation != null) { // Process mouse down this.placingAnnotation.PlacementMouseDown(new PointF(e.X, e.Y), e.Button); // Set handled flag isHandled = true; return; } // Process only left mouse buttons if(e.Button == MouseButtons.Left) { bool updateRequired = false; this._resizingMode = ResizingMode.None; // Check if mouse buton was pressed in any selection handles areas Annotation annotation = HitTestSelectionHandles(new PointF(e.X, e.Y), ref this._resizingMode); // Check if mouse button was pressed over one of the annotation objects if(annotation == null && this.Count > 0) { HitTestResult result = this.Chart.HitTest(e.X, e.Y, ChartElementType.Annotation); if(result != null && result.ChartElementType == ChartElementType.Annotation) { annotation = (Annotation)result.Object; } } // Unselect all annotations if mouse clicked outside any annotations if(annotation == null || !annotation.IsSelected) { if((Control.ModifierKeys & Keys.Control) != Keys.Control && (Control.ModifierKeys & Keys.Shift) != Keys.Shift) { foreach (Annotation annot in this.Chart.Annotations) { if(annot != annotation && annot.IsSelected) { annot.IsSelected = false; updateRequired = true; // Call selection changed notification if (this.Chart != null) { this.Chart.OnAnnotationSelectionChanged(annot); } } } } } // Process mouse action in the annotation object if(annotation != null) { // Mouse down event handled isHandled = true; // Select/Unselect annotation Annotation selectableAnnotation = annotation; if(annotation.AnnotationGroup != null) { // Select annotation group when click on any child annotations selectableAnnotation = annotation.AnnotationGroup; } if(!selectableAnnotation.IsSelected && selectableAnnotation.AllowSelecting) { selectableAnnotation.IsSelected = true; updateRequired = true; // Call selection changed notification if (this.Chart != null) { this.Chart.OnAnnotationSelectionChanged(selectableAnnotation); } } else if((Control.ModifierKeys & Keys.Control) == Keys.Control || (Control.ModifierKeys & Keys.Shift) == Keys.Shift) { selectableAnnotation.IsSelected = false; updateRequired = true; // Call selection changed notification if (this.Chart != null) { this.Chart.OnAnnotationSelectionChanged(selectableAnnotation); } } // Remember last clicked and selected annotation lastClickedAnnotation = annotation; // Rember mouse position this._movingResizingStartPoint = new PointF(e.X, e.Y); // Start moving, repositioning or resizing of annotation if(annotation.IsSelected) { // Check if one of selection handles was clicked on this._resizingMode = annotation.GetSelectionHandle(this._movingResizingStartPoint); if(!annotation.AllowResizing && this._resizingMode >= ResizingMode.TopLeftHandle && this._resizingMode <= ResizingMode.LeftHandle) { this._resizingMode = ResizingMode.None; } if(!annotation.AllowAnchorMoving && this._resizingMode == ResizingMode.AnchorHandle) { this._resizingMode = ResizingMode.None; } if(this._resizingMode == ResizingMode.None && annotation.AllowMoving) { // Annotation moving mode this._resizingMode = ResizingMode.Moving; } } else { if(this._resizingMode == ResizingMode.None && annotation.AllowMoving) { // Do not allow moving child annotations inside the group. // Only the whole group can be selected, resized or repositioned. if (annotation.AnnotationGroup != null) { // Move the group instead lastClickedAnnotation = annotation.AnnotationGroup; } // Annotation moving mode this._resizingMode = ResizingMode.Moving; } } } // Update chart if(updateRequired) { // Invalidate and update the chart this.Chart.Invalidate(true); this.Chart.UpdateAnnotations(); } } } /// /// Mouse button released in the control. /// /// Event arguments. internal void OnMouseUp(MouseEventArgs e) { // Check if in annotation placement mode if( this.placingAnnotation != null) { if(!this.placingAnnotation.PlacementMouseUp(new PointF(e.X, e.Y), e.Button)) { return; } } if(e.Button == MouseButtons.Left) { // Reset moving sizing start point this._movingResizingStartPoint = PointF.Empty; this._resizingMode = ResizingMode.None; } // Loop through all annotation objects for(int index = 0; index < this.Count; index++) { Annotation annotation = this[index]; // NOTE: Automatic deleting feature was disabled. -AG. /* // Delete all annotation objects moved outside clipping region if( annotation.outsideClipRegion ) { this.List.RemoveAt(index); --index; } */ // Reset start position/location fields annotation.startMovePositionRel = RectangleF.Empty; annotation.startMoveAnchorLocationRel = PointF.Empty; if(annotation.startMovePathRel != null) { annotation.startMovePathRel.Dispose(); annotation.startMovePathRel = null; } // Fire position changed event if( annotation.positionChanged ) { annotation.positionChanged = false; if (this.Chart != null) { this.Chart.OnAnnotationPositionChanged(annotation); } } } } /// /// Mouse moved in the control. /// /// Event arguments. internal void OnMouseMove(MouseEventArgs e) { // Check if in annotation placement mode if(this.placingAnnotation != null) { System.Windows.Forms.Cursor newCursor = this.Chart.Cursor; if(this.placingAnnotation.IsValidPlacementPosition(e.X, e.Y)) { newCursor = Cursors.Cross; } else { newCursor = this.Chart.defaultCursor; } // Set current chart cursor if (newCursor != this.Chart.Cursor) { System.Windows.Forms.Cursor tmpCursor = this.Chart.defaultCursor; this.Chart.Cursor = newCursor; this.Chart.defaultCursor = tmpCursor; } this.placingAnnotation.PlacementMouseMove(new PointF(e.X, e.Y)); return; } // Check if currently resizing/moving annotation if(!this._movingResizingStartPoint.IsEmpty && this._resizingMode != ResizingMode.None) { // Calculate how far the mouse was moved SizeF moveDistance = new SizeF( this._movingResizingStartPoint.X - e.X, this._movingResizingStartPoint.Y - e.Y ); // Update location of all selected annotation objects foreach(Annotation annot in this) { if(annot.IsSelected && ( (this._resizingMode == ResizingMode.MovingPathPoints && annot.AllowPathEditing) || (this._resizingMode == ResizingMode.Moving && annot.AllowMoving) || (this._resizingMode == ResizingMode.AnchorHandle && annot.AllowAnchorMoving) || (this._resizingMode >= ResizingMode.TopLeftHandle && this._resizingMode <= ResizingMode.LeftHandle && annot.AllowResizing) ) ) { annot.AdjustLocationSize(moveDistance, this._resizingMode, true, true); } } // Move last clicked non-selected annotation if(lastClickedAnnotation != null && !lastClickedAnnotation.IsSelected) { if(this._resizingMode == ResizingMode.Moving && lastClickedAnnotation.AllowMoving) { lastClickedAnnotation.AdjustLocationSize(moveDistance, this._resizingMode, true, true); } } // Invalidate and update the chart this.Chart.Invalidate(true); this.Chart.UpdateAnnotations(); } else if(this.Count > 0) { // Check if currently placing annotation from the UserInterface bool process = true; if(process) { // Check if mouse pointer is over the annotation selection handle ResizingMode currentResizingMode = ResizingMode.None; Annotation annotation = HitTestSelectionHandles(new PointF(e.X, e.Y), ref currentResizingMode); // Check if mouse pointer over the annotation object movable area if(annotation == null) { HitTestResult result = this.Chart.HitTest(e.X, e.Y, ChartElementType.Annotation); if(result != null && result.ChartElementType == ChartElementType.Annotation) { annotation = (Annotation)result.Object; if(annotation != null) { // Check if annotation is in the collection if(this.Contains(annotation)) { currentResizingMode = ResizingMode.Moving; if(annotation.AllowMoving == false) { // Movement is not allowed annotation = null; currentResizingMode = ResizingMode.None; } } } } } // Set mouse cursor SetResizingCursor(annotation, currentResizingMode); } } } /// /// Sets mouse cursor shape. /// /// Annotation object. /// Resizing mode. private void SetResizingCursor(Annotation annotation, ResizingMode currentResizingMode) { // Change current cursor if(this.Chart != null) { System.Windows.Forms.Cursor newCursor = this.Chart.Cursor; if(annotation != null) { if(currentResizingMode == ResizingMode.MovingPathPoints && annotation.AllowPathEditing) { newCursor = Cursors.Cross; } if(currentResizingMode == ResizingMode.Moving && annotation.AllowMoving) { newCursor = Cursors.SizeAll; } if(currentResizingMode == ResizingMode.AnchorHandle && annotation.AllowAnchorMoving) { newCursor = Cursors.Cross; } if(currentResizingMode != ResizingMode.Moving && annotation.AllowResizing) { if(annotation.SelectionPointsStyle == SelectionPointsStyle.TwoPoints) { if(currentResizingMode == ResizingMode.TopLeftHandle || currentResizingMode == ResizingMode.BottomRightHandle) { newCursor = Cursors.Cross; } } else { if(currentResizingMode == ResizingMode.TopLeftHandle || currentResizingMode == ResizingMode.BottomRightHandle) { newCursor = Cursors.SizeNWSE; } else if(currentResizingMode == ResizingMode.TopRightHandle || currentResizingMode == ResizingMode.BottomLeftHandle) { newCursor = Cursors.SizeNESW; } else if(currentResizingMode == ResizingMode.TopHandle || currentResizingMode == ResizingMode.BottomHandle) { newCursor = Cursors.SizeNS; } else if(currentResizingMode == ResizingMode.LeftHandle || currentResizingMode == ResizingMode.RightHandle) { newCursor = Cursors.SizeWE; } } } } else { newCursor = this.Chart.defaultCursor; } // Set current chart cursor if (newCursor != this.Chart.Cursor) { System.Windows.Forms.Cursor tmpCursor = this.Chart.defaultCursor; this.Chart.Cursor = newCursor; this.Chart.defaultCursor = tmpCursor; } } } #endif // Microsoft_CONTROL #endregion #region Event handlers internal void ChartAreaNameReferenceChanged(object sender, NameReferenceChangedEventArgs e) { // If all the chart areas are removed and then a new one is inserted - Annotations don't get bound to it by default if (e.OldElement == null) return; foreach (Annotation annotation in this) { if (annotation.ClipToChartArea == e.OldName) annotation.ClipToChartArea = e.NewName; AnnotationGroup group = annotation as AnnotationGroup; if (group != null) { group.Annotations.ChartAreaNameReferenceChanged(sender, e); } } } #endregion } }