/*
* 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);
}
}
}
}
}