/* * Greenshot - a free and open source screenshot tool * Copyright (C) 2007-2013 Thomas Braun, Jens Klingen, Robin Krom * * For more information see: http://getgreenshot.org/ * The Greenshot project is hosted on Sourceforge: http://sourceforge.net/projects/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 System; using System.Collections.Generic; using System.ComponentModel; using System.Drawing; using System.Windows.Forms; using Greenshot.Drawing.Fields; using Greenshot.Drawing.Filters; using Greenshot.Helpers; using Greenshot.Plugin; using Greenshot.Plugin.Drawing; using Greenshot.Memento; using System.Drawing.Drawing2D; using Greenshot.Configuration; using Greenshot.IniFile; 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, INotifyPropertyChanged, IDrawableContainer { private static readonly log4net.ILog LOG = log4net.LogManager.GetLogger(typeof(DrawableContainer)); protected static readonly EditorConfiguration editorConfig = IniConfig.GetIniSection(); private bool isMadeUndoable = false; public virtual void Dispose() { Dispose(true); GC.SuppressFinalize(this); } protected virtual void Dispose(bool disposing) { if (disposing) { for (int i = 0; i < grippers.Length; i++) { grippers[i].Dispose(); grippers[i] = null; } 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 List 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] protected Gripper[] grippers; private bool layoutSuspended = false; [NonSerialized] private bool selected = false; 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 = 0; public int Left { get { return left; } set { if(value != left) { left = value; DoLayout(); } } } private int top = 0; public int Top { get { return top; } set { if(value != top) { top = value; DoLayout(); } } } private int width = 0; public int Width { get { return width; } set { if(value != width) { width = value; DoLayout(); } } } private int height = 0; public int Height { get { return height; } set { if(value != height) { height = value; DoLayout(); } } } public Point Location { get { return new Point(left, top); } } public Size Size { get { return new Size(width, height); } } [NonSerialized] /// /// will store current bounds of this DrawableContainer before starting a resize /// private Rectangle boundsBeforeResize = Rectangle.Empty; [NonSerialized] /// /// "workbench" rectangle - used for calculatoing bounds during resizing (to be applied to this DrawableContainer afterwards) /// private 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) { this.parent = parent; InitControls(); } public void Add(IFilter filter) { AddChild(filter); } public void Remove(IFilter filter) { RemoveChild(filter); } private int round(float f) { if(float.IsPositiveInfinity(f) || f>int.MaxValue/2) return int.MaxValue/2; else if (float.IsNegativeInfinity(f) || fint.MaxValue/2) return int.MaxValue/2; else if (Double.IsNegativeInfinity(d) || d grippers[Gripper.POSITION_BOTTOM_RIGHT].Left && grippers[Gripper.POSITION_TOP_LEFT].Top > grippers[Gripper.POSITION_BOTTOM_RIGHT].Top) { grippers[Gripper.POSITION_TOP_LEFT].Cursor = Cursors.SizeNWSE; grippers[Gripper.POSITION_TOP_RIGHT].Cursor = Cursors.SizeNESW; grippers[Gripper.POSITION_BOTTOM_RIGHT].Cursor = Cursors.SizeNWSE; grippers[Gripper.POSITION_BOTTOM_LEFT].Cursor = Cursors.SizeNESW; } else if((grippers[Gripper.POSITION_TOP_LEFT].Left > grippers[Gripper.POSITION_BOTTOM_RIGHT].Left && grippers[Gripper.POSITION_TOP_LEFT].Top < grippers[Gripper.POSITION_BOTTOM_RIGHT].Top) || grippers[Gripper.POSITION_TOP_LEFT].Left < grippers[Gripper.POSITION_BOTTOM_RIGHT].Left && grippers[Gripper.POSITION_TOP_LEFT].Top > grippers[Gripper.POSITION_BOTTOM_RIGHT].Top) { grippers[Gripper.POSITION_TOP_LEFT].Cursor = Cursors.SizeNESW; grippers[Gripper.POSITION_TOP_RIGHT].Cursor = Cursors.SizeNWSE; grippers[Gripper.POSITION_BOTTOM_RIGHT].Cursor = Cursors.SizeNESW; grippers[Gripper.POSITION_BOTTOM_LEFT].Cursor = Cursors.SizeNWSE; } else if (grippers[Gripper.POSITION_TOP_LEFT].Left == grippers[Gripper.POSITION_BOTTOM_RIGHT].Left) { grippers[Gripper.POSITION_TOP_LEFT].Cursor = Cursors.SizeNS; grippers[Gripper.POSITION_BOTTOM_RIGHT].Cursor = Cursors.SizeNS; } else if (grippers[Gripper.POSITION_TOP_LEFT].Top == grippers[Gripper.POSITION_BOTTOM_RIGHT].Top) { grippers[Gripper.POSITION_TOP_LEFT].Cursor = Cursors.SizeWE; grippers[Gripper.POSITION_BOTTOM_RIGHT].Cursor = Cursors.SizeWE; } } } int mx; int my; private void gripperMouseDown(object sender, MouseEventArgs e) { mx = e.X; my = e.Y; Status = EditStatus.RESIZING; boundsBeforeResize = new Rectangle(left, top, width, height); boundsAfterResize = new RectangleF(boundsBeforeResize.Left, boundsBeforeResize.Top, boundsBeforeResize.Width, boundsBeforeResize.Height); isMadeUndoable = false; } private void gripperMouseUp(object sender, MouseEventArgs e) { Status = EditStatus.IDLE; boundsBeforeResize = Rectangle.Empty; boundsAfterResize = RectangleF.Empty; isMadeUndoable = false; Invalidate(); } private void gripperMouseMove(object sender, MouseEventArgs e) { if(Status.Equals(EditStatus.RESIZING)) { // check if we already made this undoable if (!isMadeUndoable) { // don't allow another undo until we are finished with this move isMadeUndoable = true; // Make undo-able MakeBoundsChangeUndoable(false); } Invalidate(); SuspendLayout(); Gripper gr = (Gripper)sender; int absX = gr.Left + e.X; int absY = gr.Top + e.Y; // reset "workbench" rectangle to current bounds boundsAfterResize.X = boundsBeforeResize.X; boundsAfterResize.Y = boundsBeforeResize.Y; boundsAfterResize.Width = boundsBeforeResize.Width; boundsAfterResize.Height = boundsBeforeResize.Height; // calculate scaled rectangle ScaleHelper.Scale(ref boundsAfterResize, gr.Position, new PointF(absX, absY), ScaleHelper.GetScaleOptions()); // apply scaled bounds to this DrawableContainer ApplyBounds(boundsAfterResize); ResumeLayout(); Invalidate(); } } private void childLabelMouseMove(object sender, MouseEventArgs e) { if (Status.Equals(EditStatus.RESIZING)) { Invalidate(); SuspendLayout(); this.Left += e.X - mx; this.Top += e.Y - my; ResumeLayout(); Invalidate(); } } 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); 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 virtual void ShowGrippers() { for (int i=0; i /// Make a following bounds change on this drawablecontainer undoable! /// /// true means allow the moves to be merged public void MakeBoundsChangeUndoable(bool allowMerge) { this.parent.MakeUndoable(new DrawableContainerBoundsChangeMemento(this), allowMerge); } public void MoveBy(int dx, int dy) { this.SuspendLayout(); this.Left += dx; this.Top += dy; this.ResumeLayout(); } /// /// 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(); SuspendLayout(); // 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); ResumeLayout(); Invalidate(); return true; } /// /// A handler for the MouseUp /// /// current mouse x /// current mouse y public virtual void HandleMouseUp(int x, int y) { } private void SwitchParent(Surface newParent) { if (parent != null) { for (int i=0; i /// 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(Field 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; } Invalidate(); } public virtual bool CanRotate { get { return true; } } public virtual void Rotate(RotateFlipType rotateFlipType) { // somehow the rotation is the wrong way? int angle = 90; if (RotateFlipType.Rotate90FlipNone == rotateFlipType) { angle = 270; } Rectangle beforeBounds = new Rectangle(Left, Top, Width, Height); LOG.DebugFormat("Bounds before: {0}", beforeBounds); GraphicsPath translatePath = new GraphicsPath(); translatePath.AddRectangle(beforeBounds); Matrix rotateMatrix = new Matrix(); rotateMatrix.RotateAt(angle, new PointF(parent.Width >> 1, parent.Height >> 1)); translatePath.Transform(rotateMatrix); RectangleF newBounds = translatePath.GetBounds(); LOG.DebugFormat("New bounds by using graphics path: {0}", newBounds); int ox = 0; int oy = 0; int centerX = parent.Width >> 1; int centerY = parent.Height >> 1; // Transform from screen to normal coordinates int px = Left - centerX; int py = centerY - Top; double theta = Math.PI * angle / 180.0; double x1 = Math.Cos(theta) * (px - ox) - Math.Sin(theta) * (py - oy) + ox; double y1 = Math.Sin(theta) * (px - ox) + Math.Cos(theta) * (py - oy) + oy; // Transform from screen to normal coordinates px = (Left + Width) - centerX; py = centerY - (Top + Height); double x2 = Math.Cos(theta) * (px - ox) - Math.Sin(theta) * (py - oy) + ox; double y2 = Math.Sin(theta) * (px - ox) + Math.Cos(theta) * (py - oy) + oy; // Transform back to screen coordinates, as we rotate the bitmap we need to switch the center X&Y x1 += centerY; y1 = centerX - y1; x2 += centerY; y2 = centerX - y2; // Calculate to rectangle double newWidth = x2 - x1; double newHeight = y2 - y1; RectangleF newRectangle = new RectangleF( (float)x1, (float)y1, (float)newWidth, (float)newHeight ); ApplyBounds(newRectangle); LOG.DebugFormat("New bounds by using old method: {0}", newRectangle); } 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"); } } } }