using FastReport.Utils; using System; using System.Drawing; using System.Drawing.Drawing2D; using System.Windows.Forms; namespace FastReport { partial class ComponentBase { #region Public Methods /// /// Corrects the object's size and sizing point if the size becomes negative. /// /// Current mouse state. /// Typically you don't need to use or override this method. /// This method is called by the FastReport designer to check if the object's size becomes negative /// when resizing the object by the mouse. Method must correct the object's size and/or position to /// make it positive, also change the sizing point if needed. public virtual void CheckNegativeSize(FRMouseEventArgs e) { if (Width < 0 && Height < 0) { e.sizingPoint = SizingPointHelper.SwapDiagonally(e.sizingPoint); Bounds = new RectangleF(Right, Bottom, -Width, -Height); } else if (Width < 0) { e.sizingPoint = SizingPointHelper.SwapHorizontally(e.sizingPoint); Bounds = new RectangleF(Right, Top, -Width, Height); } else if (Height < 0) { e.sizingPoint = SizingPointHelper.SwapVertically(e.sizingPoint); Bounds = new RectangleF(Left, Bottom, Width, -Height); } else return; e.cursor = SizingPointHelper.ToCursor(e.sizingPoint); } /// /// Checks if the object is inside its parent. /// /// if true, check now independent of any conditions. /// /// Typically you don't need to use or override this method. /// When you move an object with the mouse, it may be moved outside its parent. If so, this method /// must find a new parent for the object and correct it's Left, Top and Parent /// properties. If immediately parameter is false, you can optimize the method /// to search for new parent only if the object's bounds are outside parent. If this parameter is /// true, you must skip any optimizations and search for a parent immediately. /// public virtual void CheckParent(bool immediately) { } /// /// Draws the object. /// /// Paint event args. /// /// This method is widely used in the FastReport. It is called each time when the object needs to draw /// or print itself. /// In order to draw the object correctly, you should multiply the object's bounds by the scale /// parameter. /// cache parameter is used to optimize the drawing speed. It holds all items such as /// pens, fonts, brushes, string formats that was used before. If the item with requested parameters /// exists in the cache, it will be returned (instead of create new item and then dispose it). /// public virtual void Draw(FRPaintEventArgs e) { if (IsDesigning) { if (IsAncestor) e.Graphics.DrawImage(Report.Designer.GetImage(99), (int)(AbsRight * e.ScaleX - Report.Designer.LogicalToDevice(14)), (int)(AbsTop * e.ScaleY)); } } /// /// Draw the frame around the object to indicate that it accepts the drag&drop operation. /// /// Paint event args. /// The color of frame. public virtual void DrawDragAcceptFrame(FRPaintEventArgs e, Color color) { RectangleF rect = new RectangleF(AbsLeft * e.ScaleX, AbsTop * e.ScaleY, Width * e.ScaleX, Height * e.ScaleY); Pen p = e.Cache.GetPen(color, 1, DashStyle.Dot); for (int i = 0; i < 3; i++) { e.Graphics.DrawRectangle(p, rect.Left, rect.Top, rect.Width, rect.Height); rect.Inflate(-1, -1); } } /// /// Draw the selection points. /// /// Paint event args. /// /// This method draws a set of selection points returned by the method. /// public virtual void DrawSelection(FRPaintEventArgs e) { if (Page == null) return; bool firstSelected = Report.Designer.SelectedObjects.IndexOf(this) == 0; Pen p = firstSelected ? Pens.Black : Pens.White; Brush b = firstSelected ? Brushes.White : Brushes.Black; SelectionPoint[] selectionPoints = GetSelectionPoints(); foreach (SelectionPoint pt in selectionPoints) { DrawSelectionPoint(e, p, b, pt.x, pt.y); } } /// public override ContextMenuBase GetContextMenu() { return new ComponentBaseMenu(Report.Designer); } /// /// Gets the preferred size of an object. /// /// Preferred size. /// /// This method is called by the FastReport designer when you insert a new object. /// public virtual SizeF GetPreferredSize() { return new SizeF(Units.Millimeters * 25, Units.Millimeters * 5); } /// /// Returns a "smart tag" menu. /// /// /// "Smart tag" is a little button that appears near the object's top-right corner when we are in the /// designer and move the mouse over the object. When you click that button you will see a popup window /// where you can set up some properties of the object. FastReport uses smart tags to quickly choose /// the datasource (for a band) or data column (for objects). /// public virtual SmartTagBase GetSmartTag() { return null; } /// /// Handles double click event in the designer. /// /// /// This method is called when the user doubleclicks the object in the designer. Typical implementation /// invokes the object's editor (calls the InvokeEditor method) and sets the designer's /// Modified flag. /// public virtual void HandleDoubleClick() { if (HasFlag(Flags.CanEdit) && !HasRestriction(Restrictions.DontEdit) && this is IHasEditor && (this as IHasEditor).InvokeEditor()) Report.Designer.SetModified(this, "Change"); } /// /// Handles the DragDrop event in the designer. /// /// Current mouse state. /// /// This method is called when the user drops an item from the Data Tree window into this object. /// This method should copy the information from the e.DraggedObject object and set the /// e.Handled flag to true to complete the drag operation. /// public virtual void HandleDragDrop(FRMouseEventArgs e) { } /// /// Handles the DragOver event in the designer. /// /// Current mouse state. /// /// This method is called when the user drags an item from the Data Tree window. This method should /// check that the mouse (e.X, e.Y) is inside the object, then set the e.Handled flag /// to true if an item can be dragged into this object. /// public virtual void HandleDragOver(FRMouseEventArgs e) { } /// /// Handles KeyDown event in the designer. /// /// The designer's workspace. /// Keyboard event parameters. /// /// This method is called when the user presses any key in the designer. Typical implementation /// does nothing. /// public virtual void HandleKeyDown(Control sender, KeyEventArgs e) { } /// /// Handles MouseDown event that occurs when the user clicks the mouse in the designer. /// /// /// This method is called when the user press the mouse button in the designer. /// The standard implementation does the following: /// /// checks if the mouse pointer is inside the object; /// add an object to the selected objects list of the designer; /// sets the e.Handled flag to true. /// /// /// Current mouse state. public virtual void HandleMouseDown(FRMouseEventArgs e) { if (PointInObject(new PointF(e.x, e.y))) { SelectedObjectCollection selection = Report.Designer.SelectedObjects; if (e.modifierKeys == Keys.Shift) { // toggle selection if (selection.IndexOf(this) != -1) { if (selection.Count > 1) selection.Remove(this); } else selection.Add(this); } else { // select the object if not selected yet if (selection.IndexOf(this) == -1) { selection.Clear(); selection.Add(this); } } e.handled = true; e.mode = WorkspaceMode2.Move; } } /// /// Handles MouseMove event that occurs when the user moves the mouse in the designer. /// /// /// This method is called when the user moves the mouse in the designer. Typical /// use of this method is to change the mouse cursor to SizeAll when it is over /// an object. The standard implementation does the following: /// /// checks if the mouse pointer is inside the object; /// changes the cursor shape (e.Cursor property); /// sets the e.Handled flag to true. /// /// /// Current mouse state. public virtual void HandleMouseHover(FRMouseEventArgs e) { if (PointInObject(new PointF(e.x, e.y))) { e.handled = true; e.cursor = Cursors.SizeAll; } } /// /// Handles MouseMove event that occurs when the user moves the mouse in the designer. /// /// /// This method is called when the user moves the mouse in the designer. The /// standard implementation does the following: /// /// /// if mouse button is not pressed, check that mouse pointer is inside one of /// the selection points returned by the /// method and set the e.SizingPoint member to the corresponding sizing /// point; /// /// if mouse button is pressed, and e.SizingPoint member is not /// SizingPoint.None, resize the object. /// /// /// Current mouse state. public virtual void HandleMouseMove(FRMouseEventArgs e) { if (!IsSelected) return; if (e.button == MouseButtons.None) { PointF point = new PointF(e.x, e.y); e.sizingPoint = SizingPoint.None; SelectionPoint[] selectionPoints = GetSelectionPoints(); foreach (SelectionPoint pt in selectionPoints) { if (PointInSelectionPoint(pt.x, pt.y, point)) { e.sizingPoint = pt.sizingPoint; break; } } if (e.sizingPoint != SizingPoint.None) { e.handled = true; e.mode = WorkspaceMode2.Size; e.cursor = SizingPointHelper.ToCursor(e.sizingPoint); } } else if (!IsParentSelected) { if (e.mode == WorkspaceMode2.Move) { Left += e.delta.X; Top += e.delta.Y; } else if (e.mode == WorkspaceMode2.Size) { if ((e.modifierKeys & Keys.Shift) > 0) { bool wider = Math.Abs(e.delta.X) > Math.Abs(e.delta.Y); float width = Width; float height = Height; switch (e.sizingPoint) { case SizingPoint.LeftTop: if (wider) { Left += e.delta.Y; Width -= e.delta.Y; if (width != 0) { Top += Height - (Height * Width / width); Height = Height * Width / width; } } else { Top += e.delta.X; Height -= e.delta.X; if (height != 0) { Left += Width - (Width * Height / height); Width = Width * Height / height; } } break; case SizingPoint.LeftBottom: if (wider) { Left -= e.delta.Y; Width += e.delta.Y; if (width != 0) Height = Height * Width / width; } else { Height -= e.delta.X; if (height != 0) { Left += Width - (Width * Height / height); Width = Width * Height / height; } } break; case SizingPoint.RightTop: if (wider) { Width -= e.delta.Y; if (width != 0) { Top += Height - (Height * Width / width); Height = Height * Width / width; } } else { Height += e.delta.X; Top -= e.delta.X; if (height != 0) Width = Width * Height / height; } break; case SizingPoint.RightBottom: if (wider) { Width += e.delta.Y; if (width != 0) Height = Height * Width / width; } else { Height += e.delta.X; if (height != 0) Width = Width * Height / height; } break; case SizingPoint.TopCenter: Top += e.delta.Y; Height -= e.delta.Y; if (height != 0) Width = Width * Height / height; break; case SizingPoint.BottomCenter: Height += e.delta.Y; if (height != 0) Width = Width * Height / height; break; case SizingPoint.LeftCenter: Left += e.delta.X; Width -= e.delta.X; if (width != 0) Height = Height * Width / width; break; case SizingPoint.RightCenter: Width += e.delta.X; if (width != 0) Height = Height * Width / width; break; } } else { switch (e.sizingPoint) { case SizingPoint.LeftTop: Left += e.delta.X; Width -= e.delta.X; Top += e.delta.Y; Height -= e.delta.Y; break; case SizingPoint.LeftBottom: Left += e.delta.X; Width -= e.delta.X; Height += e.delta.Y; break; case SizingPoint.RightTop: Width += e.delta.X; Top += e.delta.Y; Height -= e.delta.Y; break; case SizingPoint.RightBottom: Width += e.delta.X; Height += e.delta.Y; break; case SizingPoint.TopCenter: Top += e.delta.Y; Height -= e.delta.Y; break; case SizingPoint.BottomCenter: Height += e.delta.Y; break; case SizingPoint.LeftCenter: Left += e.delta.X; Width -= e.delta.X; break; case SizingPoint.RightCenter: Width += e.delta.X; break; } } CheckNegativeSize(e); } CheckParent(false); } } /// /// Handles MouseUp event that occurs when the user releases the mouse button in the designer. /// /// /// This method is called when the user releases the mouse button in the /// designer. The standard implementation does the following: /// /// if e.Mode is WorkspaceMode2.SelectionRect, checks if object /// is inside the selection rectangle and sets e.Handled flag if so; /// /// checks that object is inside its parent (calls the /// method). /// /// /// /// Current mouse state. public virtual void HandleMouseUp(FRMouseEventArgs e) { if (e.mode == WorkspaceMode2.SelectionRect) { if (e.selectionRect.IntersectsWith(new RectangleF(AbsLeft, AbsTop, Width, Height))) e.handled = true; } else if (e.mode == WorkspaceMode2.Move || e.mode == WorkspaceMode2.Size) { if (IsSelected) CheckParent(true); } } /// /// Handles mouse wheel event. /// /// Current mouse state. public virtual void HandleMouseWheel(FRMouseEventArgs e) { } /// /// Checks if given point is inside the object's bounds. /// /// point to check. /// true if point is inside the object's bounds. /// /// You can override this method if your objectis not of rectangular form. /// public virtual bool PointInObject(PointF point) { return AbsBounds.Contains(point); } #endregion Public Methods #region Protected Methods /// /// Draws the selection point. /// /// Paint event args. /// object. /// object. /// Left coordinate. /// Top coordinate. protected virtual void DrawSelectionPoint(FRPaintEventArgs e, Pen p, Brush b, float x, float y) { IGraphics g = e.Graphics; x = (float)Math.Round(x * e.ScaleX); y = (float)Math.Round(y * e.ScaleY); float m = Report?.Designer?.DpiMultiplier() ?? 1; Rectangle rect = Rectangle.Round(new RectangleF(x - 2 * m, y - 2 * m, 4 * m, 4 * m)); g.FillRectangle(b, rect); g.DrawRectangle(p, rect); } /// /// Gets the object's selection points. /// /// Array of objects. /// /// Selection point is a small square displayed at the object's sides when object is selected /// in the designer. You can drag this square by the mouse to change the object's size. For example, /// the TextObject has eight selection points to change its width and height by the mouse. /// If you are developing a new component for FastReport, you may override this method /// if your object has non-standard set of selection points. For example, if an object has something like /// "AutoSize" property, it would be good to disable all selection points if that property is true, /// to disable resizing of the object by the mouse. /// protected virtual SelectionPoint[] GetSelectionPoints() { return new SelectionPoint[] { new SelectionPoint(AbsLeft, AbsTop, SizingPoint.LeftTop), new SelectionPoint(AbsLeft + Width, AbsTop, SizingPoint.RightTop), new SelectionPoint(AbsLeft, AbsTop + Height, SizingPoint.LeftBottom), new SelectionPoint(AbsLeft + Width, AbsTop + Height, SizingPoint.RightBottom), new SelectionPoint(AbsLeft + Width / 2, AbsTop, SizingPoint.TopCenter), new SelectionPoint(AbsLeft + Width / 2, AbsTop + Height, SizingPoint.BottomCenter), new SelectionPoint(AbsLeft, AbsTop + Height / 2, SizingPoint.LeftCenter), new SelectionPoint(AbsLeft + Width, AbsTop + Height / 2, SizingPoint.RightCenter) }; } /// /// Gets a value indicating that given point is inside selection point. /// /// point's x coordinate. /// point's y coordinate. /// selection point. /// true if (x,y) is inside the point protected bool PointInSelectionPoint(float x, float y, PointF point) { return x >= point.X - 3 && x <= point.X + 3 && y >= point.Y - 3 && y <= point.Y + 3; } #endregion Protected Methods } }