/* * Greenshot - a free and open source screenshot tool * Copyright (C) 2007-2015 Thomas Braun, Jens Klingen, Robin Krom * * For more information see: http://getgreenshot.org/ * The Greenshot project is hosted on GitHub https://github.com/greenshot/greenshot * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 1 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ using Greenshot.Configuration; using Greenshot.Drawing.Adorners; using Greenshot.Drawing.Fields; using Greenshot.Drawing.Filters; using Greenshot.Helpers; using Greenshot.IniFile; using Greenshot.Memento; using Greenshot.Plugin; using Greenshot.Plugin.Drawing; using Greenshot.Plugin.Drawing.Adorners; using GreenshotPlugin.Interfaces.Drawing; using log4net; using System; using System.Collections.Generic; using System.ComponentModel; using System.Drawing; using System.Drawing.Drawing2D; using System.Runtime.Serialization; using System.Windows.Forms; namespace Greenshot.Drawing { /// /// represents a rectangle, ellipse, label or whatever. Can contain filters, too. /// serializable for clipboard support /// Subclasses should fulfill INotifyPropertyChanged contract, i.e. call /// OnPropertyChanged whenever a public property has been changed. /// [Serializable] public abstract class DrawableContainer : AbstractFieldHolderWithChildren, IDrawableContainer { private static readonly ILog LOG = LogManager.GetLogger(typeof(DrawableContainer)); protected static readonly EditorConfiguration EditorConfig = IniConfig.GetIniSection(); private const int M11 = 0; private const int M22 = 3; [OnDeserialized] private void OnDeserializedInit(StreamingContext context) { _adorners = new List(); OnDeserialized(context); } /// /// Override to implement your own deserialization logic, like initializing properties which are not serialized /// /// protected virtual void OnDeserialized(StreamingContext streamingContext) { } protected EditStatus _defaultEditMode = EditStatus.DRAWING; public EditStatus DefaultEditMode { get { return _defaultEditMode; } } /// /// The public accessible Dispose /// Will call the GarbageCollector to SuppressFinalize, preventing being cleaned twice /// public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } protected virtual void Dispose(bool disposing) { if (!disposing) { return; } FieldAggregator aggProps = _parent.FieldAggregator; aggProps.UnbindElement(this); } ~DrawableContainer() { Dispose(false); } [NonSerialized] private PropertyChangedEventHandler _propertyChanged; public event PropertyChangedEventHandler PropertyChanged { add { _propertyChanged += value; } remove{ _propertyChanged -= value; } } public IList Filters { get { List ret = new List(); foreach(IFieldHolder c in Children) { if (c is IFilter) { ret.Add(c as IFilter); } } return ret; } } [NonSerialized] internal Surface _parent; public ISurface Parent { get { return _parent; } set { SwitchParent((Surface)value); } } [NonSerialized] private TargetAdorner _targetAdorner; public TargetAdorner TargetAdorner { get { return _targetAdorner; } } [NonSerialized] private bool _selected; public bool Selected { get {return _selected;} set { _selected = value; OnPropertyChanged("Selected"); } } [NonSerialized] private EditStatus _status = EditStatus.UNDRAWN; public EditStatus Status { get { return _status; } set { _status = value; } } private int left; public int Left { get { return left; } set { if (value == left) { return; } left = value; } } private int top; public int Top { get { return top; } set { if (value == top) { return; } top = value; } } private int width; public int Width { get { return width; } set { if (value == width) { return; } width = value; } } private int height; public int Height { get { return height; } set { if (value == height) { return; } height = value; } } public Point Location { get { return new Point(left, top); } set { left = value.X; top = value.Y; } } public Size Size { get { return new Size(width, height); } set { width = value.Width; height = value.Height; } } /// /// List of available Adorners /// [NonSerialized] private IList _adorners = new List(); public IList Adorners { get { return _adorners; } } [NonSerialized] // will store current bounds of this DrawableContainer before starting a resize protected Rectangle _boundsBeforeResize = Rectangle.Empty; [NonSerialized] // "workbench" rectangle - used for calculating bounds during resizing (to be applied to this DrawableContainer afterwards) protected RectangleF _boundsAfterResize = RectangleF.Empty; public Rectangle Bounds { get { return GuiRectangle.GetGuiRectangle(Left, Top, Width, Height); } set { Left = Round(value.Left); Top = Round(value.Top); Width = Round(value.Width); Height = Round(value.Height); } } public virtual void ApplyBounds(RectangleF newBounds) { Left = Round(newBounds.Left); Top = Round(newBounds.Top); Width = Round(newBounds.Width); Height = Round(newBounds.Height); } public DrawableContainer(Surface parent) { InitializeFields(); _parent = parent; } public void Add(IFilter filter) { AddChild(filter); } public void Remove(IFilter filter) { RemoveChild(filter); } private static int Round(float f) { if(float.IsPositiveInfinity(f) || f>int.MaxValue/2) return int.MaxValue/2; if (float.IsNegativeInfinity(f) || f /// Initialize a target gripper /// protected void InitAdorner(Color gripperColor, Point location) { _targetAdorner = new TargetAdorner(this, location); Adorners.Add(_targetAdorner); } /// /// Create the default adorners for a rectangle based container /// protected void CreateDefaultAdorners() { if (Adorners.Count > 0) { LOG.Warn("Adorners are already defined!"); } // Create the GripperAdorners Adorners.Add(new ResizeAdorner(this, Positions.TopLeft)); Adorners.Add(new ResizeAdorner(this, Positions.TopCenter)); Adorners.Add(new ResizeAdorner(this, Positions.TopRight)); Adorners.Add(new ResizeAdorner(this, Positions.BottomLeft)); Adorners.Add(new ResizeAdorner(this, Positions.BottomCenter)); Adorners.Add(new ResizeAdorner(this, Positions.BottomRight)); Adorners.Add(new ResizeAdorner(this, Positions.MiddleLeft)); Adorners.Add(new ResizeAdorner(this, Positions.MiddleRight)); } public bool hasFilters { get { return Filters.Count > 0; } } public abstract void Draw(Graphics graphics, RenderMode renderMode); public virtual void DrawContent(Graphics graphics, Bitmap bmp, RenderMode renderMode, Rectangle clipRectangle) { if (Children.Count > 0) { if (Status != EditStatus.IDLE) { DrawSelectionBorder(graphics, Bounds); } else { if (clipRectangle.Width != 0 && clipRectangle.Height != 0) { foreach(IFilter filter in Filters) { if (filter.Invert) { filter.Apply(graphics, bmp, Bounds, renderMode); } else { Rectangle drawingRect = new Rectangle(Bounds.Location, Bounds.Size); drawingRect.Intersect(clipRectangle); if(filter is MagnifierFilter) { // quick&dirty bugfix, because MagnifierFilter behaves differently when drawn only partially // what we should actually do to resolve this is add a better magnifier which is not that special filter.Apply(graphics, bmp, Bounds, renderMode); } else { filter.Apply(graphics, bmp, drawingRect, renderMode); } } } } } } Draw(graphics, renderMode); } public virtual bool Contains(int x, int y) { return Bounds.Contains(x , y); } public virtual bool ClickableAt(int x, int y) { Rectangle r = GuiRectangle.GetGuiRectangle(Left, Top, Width, Height); r.Inflate(5, 5); return r.Contains(x, y); } protected void DrawSelectionBorder(Graphics g, Rectangle rect) { using (Pen pen = new Pen(Color.MediumSeaGreen)) { pen.DashPattern = new float[]{1,2}; pen.Width = 1; g.DrawRectangle(pen, rect); } } public void ResizeTo(int width, int height, int anchorPosition) { Width = width; Height = height; } /// /// Make a following bounds change on this drawablecontainer undoable! /// /// true means allow the moves to be merged public void MakeBoundsChangeUndoable(bool allowMerge) { _parent.MakeUndoable(new DrawableContainerBoundsChangeMemento(this), allowMerge); } public void MoveBy(int dx, int dy) { Left += dx; Top += dy; } /// /// A handler for the MouseDown, used if you don't want the surface to handle this for you /// /// current mouse x /// current mouse y /// true if the event is handled, false if the surface needs to handle it public virtual bool HandleMouseDown(int x, int y) { Left = _boundsBeforeResize.X = x; Top = _boundsBeforeResize.Y = y; return true; } /// /// A handler for the MouseMove, used if you don't want the surface to handle this for you /// /// current mouse x /// current mouse y /// true if the event is handled, false if the surface needs to handle it public virtual bool HandleMouseMove(int x, int y) { Invalidate(); // reset "workrbench" rectangle to current bounds _boundsAfterResize.X = _boundsBeforeResize.Left; _boundsAfterResize.Y = _boundsBeforeResize.Top; _boundsAfterResize.Width = x - _boundsAfterResize.Left; _boundsAfterResize.Height = y - _boundsAfterResize.Top; ScaleHelper.Scale(_boundsBeforeResize, x, y, ref _boundsAfterResize, GetAngleRoundProcessor()); // apply scaled bounds to this DrawableContainer ApplyBounds(_boundsAfterResize); Invalidate(); return true; } /// /// A handler for the MouseUp /// /// current mouse x /// current mouse y public virtual void HandleMouseUp(int x, int y) { } protected virtual void SwitchParent(Surface newParent) { if (newParent == Parent) { return; } if (_parent != null) { // Remove FieldAggregator FieldAggregator fieldAggregator = _parent.FieldAggregator; if (fieldAggregator != null) { fieldAggregator.UnbindElement(this); } } _parent = newParent; foreach(IFilter filter in Filters) { filter.Parent = this; } } protected void OnPropertyChanged(string propertyName) { if (_propertyChanged != null) { _propertyChanged(this, new PropertyChangedEventArgs(propertyName)); Invalidate(); } } /// /// This method will be called before a field is changes. /// Using this makes it possible to invalidate the object as is before changing. /// /// The field to be changed /// The new value public virtual void BeforeFieldChange(IField fieldToBeChanged, object newValue) { _parent.MakeUndoable(new ChangeFieldHolderMemento(this, fieldToBeChanged), true); Invalidate(); } /// /// Handle the field changed event, this should invalidate the correct bounds (e.g. when shadow comes or goes more pixels!) /// /// /// public void HandleFieldChanged(object sender, FieldChangedEventArgs e) { LOG.DebugFormat("Field {0} changed", e.Field.FieldType); if (e.Field.FieldType == FieldType.SHADOW) { accountForShadowChange = true; } } /// /// Retrieve the Y scale from the matrix /// /// /// public static float CalculateScaleY(Matrix matrix) { return matrix.Elements[M22]; } /// /// Retrieve the X scale from the matrix /// /// /// public static float CalculateScaleX(Matrix matrix) { return matrix.Elements[M11]; } /// /// Retrieve the rotation angle from the matrix /// /// /// public static int CalculateAngle(Matrix matrix) { const int M11 = 0; const int M21 = 2; var radians = Math.Atan2(matrix.Elements[M21], matrix.Elements[M11]); return (int)-Math.Round(radians * 180 / Math.PI); } /// /// This method is called on a DrawableContainers when: /// 1) The capture on the surface is modified in such a way, that the elements would not be placed correctly. /// 2) Currently not implemented: an element needs to be moved, scaled or rotated. /// This basis implementation makes sure the coordinates of the element, including the TargetGripper, is correctly rotated/scaled/translated. /// But this implementation doesn't take care of any changes to the content!! /// /// public virtual void Transform(Matrix matrix) { if (matrix == null) { return; } Point topLeft = new Point(Left, Top); Point bottomRight = new Point(Left + Width, Top + Height); Point[] points = new[] { topLeft, bottomRight }; matrix.TransformPoints(points); Left = points[0].X; Top = points[0].Y; Width = points[1].X - points[0].X; Height = points[1].Y - points[0].Y; } protected virtual ScaleHelper.IDoubleProcessor GetAngleRoundProcessor() { return ScaleHelper.ShapeAngleRoundBehavior.Instance; } public virtual bool HasContextMenu { get { return true; } } public virtual bool HasDefaultSize { get { return false; } } public virtual Size DefaultSize { get { throw new NotSupportedException("Object doesn't have a default size"); } } /// /// Allows to override the initializing of the fields, so we can actually have our own defaults /// protected virtual void InitializeFields() { } } }