/* * Greenshot - a free and open source screenshot tool * Copyright (C) 2007-2011 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.Configuration; using Greenshot.Drawing.Fields; using Greenshot.Drawing.Filters; using Greenshot.Helpers; using Greenshot.Plugin; using GreenshotPlugin.Core; using Greenshot.Plugin.Drawing; using Greenshot.Memento; using 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)); private static System.ComponentModel.ComponentResourceManager editorFormResources = new System.ComponentModel.ComponentResourceManager(typeof(ImageEditorForm)); private static CoreConfiguration conf = IniConfig.GetIniSection(); private bool isMadeUndoable = 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] public EditStatus Status = EditStatus.UNDRAWN; 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 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; } } } public virtual void Dispose() { for(int i=0; i 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(); } protected virtual ScaleHelper.IDoubleProcessor GetAngleRoundProcessor() { return ScaleHelper.ShapeAngleRoundBehavior.Instance; } /// /// Add items to a context menu for the selected item /// /// public virtual void AddContextMenuItems(ContextMenuStrip menu) { DrawableContainerList myselfAsList = new DrawableContainerList(); myselfAsList.Add(this); ILanguage lang = Language.GetInstance(); bool push = parent.Elements.CanPushDown(myselfAsList); bool pull = parent.Elements.CanPullUp(myselfAsList); ToolStripMenuItem item; // Pull "up" if (pull) { item = new ToolStripMenuItem(lang.GetString(LangKey.editor_uptotop)); item.Click += delegate { parent.Elements.PullElementsToTop(myselfAsList); parent.Elements.Invalidate(); }; menu.Items.Add(item); item = new ToolStripMenuItem(lang.GetString(LangKey.editor_uponelevel)); item.Click += delegate { parent.Elements.PullElementsUp(myselfAsList); parent.Elements.Invalidate(); }; menu.Items.Add(item); } // Push "down" if (push) { item = new ToolStripMenuItem(lang.GetString(LangKey.editor_downtobottom)); item.Click += delegate { parent.Elements.PushElementsToBottom(myselfAsList); parent.Elements.Invalidate(); }; menu.Items.Add(item); item = new ToolStripMenuItem(lang.GetString(LangKey.editor_downonelevel)); item.Click += delegate { parent.Elements.PushElementsDown(myselfAsList); parent.Elements.Invalidate(); }; menu.Items.Add(item); } // Duplicate item = new ToolStripMenuItem(lang.GetString(LangKey.editor_duplicate)); item.Click += delegate { DrawableContainerList dcs = myselfAsList.Clone(); dcs.Parent = parent; dcs.MoveBy(10,10); parent.AddElements(dcs); parent.DeselectAllElements(); parent.SelectElements(dcs); }; menu.Items.Add(item); // Copy item = new ToolStripMenuItem(lang.GetString(LangKey.editor_copytoclipboard)); item.Image = ((System.Drawing.Image)(editorFormResources.GetObject("copyToolStripMenuItem.Image"))); item.Click += delegate { ClipboardHelper.SetClipboardData(typeof(DrawableContainerList), myselfAsList); }; menu.Items.Add(item); // Cut item = new ToolStripMenuItem(lang.GetString(LangKey.editor_cuttoclipboard)); item.Image = ((System.Drawing.Image)(editorFormResources.GetObject("editor_cuttoclipboard.Image"))); item.Click += delegate { ClipboardHelper.SetClipboardData(typeof(DrawableContainerList), myselfAsList); parent.RemoveElement(this, true); }; menu.Items.Add(item); // Delete item = new ToolStripMenuItem(lang.GetString(LangKey.editor_deleteelement)); item.Image = ((System.Drawing.Image)(editorFormResources.GetObject("removeObjectToolStripMenuItem.Image"))); item.Click += delegate { parent.RemoveElement(this, true); }; menu.Items.Add(item); } public virtual void ShowContextMenu(MouseEventArgs e) { if (conf.isExperimentalFeatureEnabled("Contextmenu")) { ContextMenuStrip menu = new ContextMenuStrip(); AddContextMenuItems(menu); if (menu.Items.Count > 0) { menu.Show(parent, e.Location); } } } } }