/* * 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 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 Greenshot.Core; using Greenshot.Drawing.Fields; using Greenshot.Helpers; using Greenshot.IniFile; using Greenshot.Memento; using Greenshot.Plugin; using Greenshot.Plugin.Drawing; using GreenshotPlugin; using GreenshotPlugin.Controls; using GreenshotPlugin.Core; using System; using System.Collections.Generic; using System.ComponentModel; using System.Drawing; using System.Drawing.Drawing2D; using System.Drawing.Imaging; using System.IO; using System.Runtime.Serialization.Formatters.Binary; using System.Windows.Forms; namespace Greenshot.Drawing { /// /// Description of Surface. /// public class Surface : Control, ISurface { public static int Count; private static readonly CoreConfiguration conf = IniConfig.GetIniSection(); // Property to identify the Surface ID private Guid _uniqueId = Guid.NewGuid(); /// /// The GUID of the surface /// public Guid ID { get { return _uniqueId; } set { _uniqueId = value; } } /// /// Event handlers (do not serialize!) /// [NonSerialized] private SurfaceElementEventHandler _movingElementChanged; public event SurfaceElementEventHandler MovingElementChanged { add { _movingElementChanged += value; } remove { _movingElementChanged -= value; } } [NonSerialized] private SurfaceDrawingModeEventHandler _drawingModeChanged; public event SurfaceDrawingModeEventHandler DrawingModeChanged { add { _drawingModeChanged += value; } remove { _drawingModeChanged -= value; } } [NonSerialized] private SurfaceSizeChangeEventHandler _surfaceSizeChanged; public event SurfaceSizeChangeEventHandler SurfaceSizeChanged { add { _surfaceSizeChanged += value; } remove { _surfaceSizeChanged -= value; } } [NonSerialized] private SurfaceMessageEventHandler _surfaceMessage; public event SurfaceMessageEventHandler SurfaceMessage { add { _surfaceMessage += value; } remove { _surfaceMessage -= value; } } /// /// inUndoRedo makes sure we don't undo/redo while in a undo/redo action /// [NonSerialized] private bool _inUndoRedo; /// /// Make only one surfacemove cycle undoable, see SurfaceMouseMove /// [NonSerialized] private bool _isSurfaceMoveMadeUndoable; /// /// Undo/Redo stacks, should not be serialized as the file would be way to big /// [NonSerialized] private readonly Stack _undoStack = new Stack(); [NonSerialized] private readonly Stack _redoStack = new Stack(); /// /// Last save location, do not serialize! /// [NonSerialized] private string _lastSaveFullPath; /// /// current drawing mode, do not serialize! /// [NonSerialized] private DrawingModes _drawingMode = DrawingModes.None; /// /// the keyslocked flag helps with focus issues /// [NonSerialized] private bool _keysLocked; /// /// Location of the mouse-down (it "starts" here), do not serialize /// [NonSerialized] private Point _mouseStart = Point.Empty; /// /// are we in a mouse down, do not serialize /// [NonSerialized] private bool _mouseDown; /// /// The selected element for the mouse down, do not serialize /// [NonSerialized] private IDrawableContainer _mouseDownElement; /// /// all selected elements, do not serialize /// [NonSerialized] private readonly DrawableContainerList selectedElements; /// /// the element we are drawing with, do not serialize /// [NonSerialized] private IDrawableContainer _drawingElement; /// /// the element we want to draw with (not yet drawn), do not serialize /// [NonSerialized] private IDrawableContainer _undrawnElement; /// /// the cropcontainer, when cropping this is set, do not serialize /// [NonSerialized] private IDrawableContainer _cropContainer; /// /// the brush which is used for transparent backgrounds, set by the editor, do not serialize /// [NonSerialized] private Brush _transparencyBackgroundBrush; /// /// The buffer is only for drawing on it when using filters (to supply access) /// This saves a lot of "create new bitmap" commands /// Should not be serialized, as it's generated. /// The actual bitmap is in the paintbox... /// Check if this buffer is still needed! /// [NonSerialized] private Bitmap _buffer; /// /// all stepLabels for the surface, needed with serialization /// private readonly List _stepLabels = new List(); public void AddStepLabel(StepLabelContainer stepLabel) { _stepLabels.Add(stepLabel); } public void RemoveStepLabel(StepLabelContainer stepLabel) { _stepLabels.Remove(stepLabel); } /// /// Count all the VISIBLE steplabels in the surface, up to the supplied one /// /// can be null, if not the counting stops here /// number of steplabels before the supplied container public int CountStepLabels(IDrawableContainer stopAtContainer) { int number = 1; foreach (var possibleThis in _stepLabels) { if (possibleThis.Equals(stopAtContainer)) { break; } if (IsOnSurface(possibleThis)) { number++; } } return number; } /// /// all elements on the surface, needed with serialization /// private readonly DrawableContainerList _elements; /// /// all elements on the surface, needed with serialization /// private FieldAggregator _fieldAggregator = new FieldAggregator(); /// /// the cursor container, needed with serialization as we need a direct acces to it. /// private IDrawableContainer _cursorContainer; /// /// the capture details, needed with serialization /// private ICaptureDetails _captureDetails; /// /// the modified flag specifies if the surface has had modifications after the last export. /// Initial state is modified, as "it's not saved" /// After serialization this should actually be "false" (the surface came from a stream) /// For now we just serialize it... /// private bool _modified = true; /// /// The image is the actual captured image, needed with serialization /// private Image _image; public Image Image { get { return _image; } set { _image = value; Size = _image.Size; } } /// /// The field aggregator is that which is used to have access to all the fields inside the currently selected elements. /// e.g. used to decided if and which line thickness is shown when multiple elements are selected. /// public FieldAggregator FieldAggregator { get { return _fieldAggregator; } set { _fieldAggregator = value; } } /// /// The cursor container has it's own accessor so we can find and remove this (when needed) /// public IDrawableContainer CursorContainer { get { return _cursorContainer; } } /// /// A simple getter to ask if this surface has a cursor /// public bool HasCursor { get { return _cursorContainer != null; } } /// /// A simple helper method to remove the cursor from the surface /// public void RemoveCursor() { RemoveElement(_cursorContainer, true); _cursorContainer = null; } /// /// The brush which is used to draw the transparent background /// public Brush TransparencyBackgroundBrush { get { return _transparencyBackgroundBrush; } set { _transparencyBackgroundBrush = value; } } /// /// Are the keys on this surface locked? /// public bool KeysLocked { get { return _keysLocked; } set { _keysLocked = value; } } /// /// Is this surface modified? This is only true if the surface has not been exported. /// public bool Modified { get { return _modified; } set { _modified = value; } } /// /// The DrawingMode property specifies the mode for drawing, more or less the element type. /// public DrawingModes DrawingMode { get { return _drawingMode; } set { _drawingMode = value; if (_drawingModeChanged != null) { SurfaceDrawingModeEventArgs eventArgs = new SurfaceDrawingModeEventArgs(); eventArgs.DrawingMode = _drawingMode; _drawingModeChanged.Invoke(this, eventArgs); } DeselectAllElements(); CreateUndrawnElement(); } } /// /// Property for accessing the last save "full" path /// public string LastSaveFullPath { get { return _lastSaveFullPath; } set { _lastSaveFullPath = value; } } /// /// Property for accessing the URL to which the surface was recently uploaded /// public string UploadURL { get; set; } /// /// Property for accessing the capture details /// public ICaptureDetails CaptureDetails { get { return _captureDetails; } set { _captureDetails = value; } } /// /// Base Surface constructor /// public Surface() : base() { Count++; _elements = new DrawableContainerList(_uniqueId); selectedElements = new DrawableContainerList(_uniqueId); LOG.Debug("Creating surface!"); MouseDown += SurfaceMouseDown; MouseUp += SurfaceMouseUp; MouseMove += SurfaceMouseMove; MouseDoubleClick += SurfaceDoubleClick; Paint += SurfacePaint; AllowDrop = true; DragDrop += OnDragDrop; DragEnter += OnDragEnter; // bind selected & elements to this, otherwise they can't inform of modifications selectedElements.Parent = this; _elements.Parent = this; // Make sure we are visible Visible = true; TabStop = false; // Enable double buffering DoubleBuffered = true; SetStyle(ControlStyles.UserPaint | ControlStyles.AllPaintingInWmPaint | ControlStyles.ResizeRedraw | ControlStyles.ContainerControl | ControlStyles.OptimizedDoubleBuffer | ControlStyles.SupportsTransparentBackColor, true); } /// /// Private method, the current image is disposed the new one will stay. /// /// The new image /// true if the old image needs to be disposed, when using undo this should not be true!! private void SetImage(Image newImage, bool dispose) { // Dispose if (_image != null && dispose) { _image.Dispose(); } // Set new values Image = newImage; Size = newImage.Size; _modified = true; } /// /// Surface constructor with an image /// /// public Surface(Image newImage) : this() { LOG.DebugFormat("Got image with dimensions {0} and format {1}", newImage.Size, newImage.PixelFormat); SetImage(newImage, true); } /// /// Surface contructor with a capture /// /// public Surface(ICapture capture) : this(capture.Image) { // check if cursor is captured, and visible if (capture.Cursor != null && capture.CursorVisible) { Rectangle cursorRect = new Rectangle(capture.CursorLocation, capture.Cursor.Size); Rectangle captureRect = new Rectangle(Point.Empty, capture.Image.Size); // check if cursor is on the capture, otherwise we leave it out. if (cursorRect.IntersectsWith(captureRect)) { _cursorContainer = AddIconContainer(capture.Cursor, capture.CursorLocation.X, capture.CursorLocation.Y); SelectElement(_cursorContainer); } } // Make sure the image is NOT disposed, we took the reference directly into ourselves ((Capture)capture).NullImage(); _captureDetails = capture.CaptureDetails; } protected override void Dispose(bool disposing) { if (disposing) { Count--; LOG.Debug("Disposing surface!"); if (_buffer != null) { _buffer.Dispose(); _buffer = null; } if (_transparencyBackgroundBrush != null) { _transparencyBackgroundBrush.Dispose(); _transparencyBackgroundBrush = null; } // Cleanup undo/redo stacks while (_undoStack != null && _undoStack.Count > 0) { _undoStack.Pop().Dispose(); } while (_redoStack != null && _redoStack.Count > 0) { _redoStack.Pop().Dispose(); } foreach (IDrawableContainer container in _elements) { container.Dispose(); } if (_undrawnElement != null) { _undrawnElement.Dispose(); _undrawnElement = null; } if (_cropContainer != null) { _cropContainer.Dispose(); _cropContainer = null; } } base.Dispose(disposing); } /// /// Undo the last action /// public void Undo() { if (_undoStack.Count > 0) { _inUndoRedo = true; IMemento top = _undoStack.Pop(); _redoStack.Push(top.Restore()); _inUndoRedo = false; } } /// /// Undo an undo (=redo) /// public void Redo() { if (_redoStack.Count > 0) { _inUndoRedo = true; IMemento top = _redoStack.Pop(); _undoStack.Push(top.Restore()); _inUndoRedo = false; } } /// /// Returns if the surface can do a undo /// public bool CanUndo { get { return _undoStack.Count > 0; } } /// /// Returns if the surface can do a redo /// public bool CanRedo { get { return _redoStack.Count > 0; } } /// /// Make an action undo-able /// /// The memento implementing the undo /// Allow changes to be merged public void MakeUndoable(IMemento memento, bool allowMerge) { if (_inUndoRedo) { throw new InvalidOperationException("Invoking do within an undo/redo action."); } if (memento != null) { bool allowPush = true; if (_undoStack.Count > 0 && allowMerge) { // Check if merge is possible allowPush = !_undoStack.Peek().Merge(memento); } if (allowPush) { // Clear the redo-stack and dispose while (_redoStack.Count > 0) { _redoStack.Pop().Dispose(); } _undoStack.Push(memento); } } } /// /// This saves the elements of this surface to a stream. /// Is used to save a template of the complete surface /// /// /// public long SaveElementsToStream(Stream streamWrite) { long bytesWritten = 0; try { long lengtBefore = streamWrite.Length; BinaryFormatter binaryWrite = new BinaryFormatter(); binaryWrite.Serialize(streamWrite, _elements); bytesWritten = streamWrite.Length - lengtBefore; } catch (Exception e) { LOG.Error("Error serializing elements to stream.", e); } return bytesWritten; } /// /// This loads elements from a stream, among others this is used to load a surface. /// /// public void LoadElementsFromStream(Stream streamRead) { try { BinaryFormatter binaryRead = new BinaryFormatter(); DrawableContainerList loadedElements = (DrawableContainerList)binaryRead.Deserialize(streamRead); loadedElements.Parent = this; // Make sure the steplabels are sorted accoring to their number _stepLabels.Sort(delegate (StepLabelContainer p1, StepLabelContainer p2) { return p1.Number.CompareTo(p2.Number); }); DeselectAllElements(); AddElements(loadedElements); SelectElements(loadedElements); FieldAggregator.BindElements(loadedElements); } catch (Exception e) { LOG.Error("Error serializing elements from stream.", e); } } /// /// This is called from the DrawingMode setter, which is not very correct... /// But here an element is created which is not yet draw, thus "undrawnElement". /// The element is than used while drawing on the surface. /// private void CreateUndrawnElement() { if (_undrawnElement != null) { FieldAggregator.UnbindElement(_undrawnElement); } switch (DrawingMode) { case DrawingModes.Rect: _undrawnElement = new RectangleContainer(this); break; case DrawingModes.Ellipse: _undrawnElement = new EllipseContainer(this); break; case DrawingModes.Text: _undrawnElement = new TextContainer(this); break; case DrawingModes.SpeechBubble: _undrawnElement = new SpeechbubbleContainer(this); break; case DrawingModes.StepLabel: _undrawnElement = new StepLabelContainer(this); break; case DrawingModes.Line: _undrawnElement = new LineContainer(this); break; case DrawingModes.Arrow: _undrawnElement = new ArrowContainer(this); break; case DrawingModes.Highlight: _undrawnElement = new HighlightContainer(this); break; case DrawingModes.Obfuscate: _undrawnElement = new ObfuscateContainer(this); break; case DrawingModes.Crop: _cropContainer = new CropContainer(this); _undrawnElement = _cropContainer; break; case DrawingModes.Bitmap: _undrawnElement = new ImageContainer(this); break; case DrawingModes.Path: _undrawnElement = new FreehandContainer(this); break; case DrawingModes.None: _undrawnElement = null; break; } if (_undrawnElement != null) { FieldAggregator.BindElement(_undrawnElement); } } #region Plugin interface implementations public IImageContainer AddImageContainer(Image image, int x, int y) { ImageContainer bitmapContainer = new ImageContainer(this); bitmapContainer.Image = image; bitmapContainer.Left = x; bitmapContainer.Top = y; AddElement(bitmapContainer); return bitmapContainer; } public IImageContainer AddImageContainer(string filename, int x, int y) { ImageContainer bitmapContainer = new ImageContainer(this); bitmapContainer.Load(filename); bitmapContainer.Left = x; bitmapContainer.Top = y; AddElement(bitmapContainer); return bitmapContainer; } public IIconContainer AddIconContainer(Icon icon, int x, int y) { IconContainer iconContainer = new IconContainer(this); iconContainer.Icon = icon; iconContainer.Left = x; iconContainer.Top = y; AddElement(iconContainer); return iconContainer; } public IIconContainer AddIconContainer(string filename, int x, int y) { IconContainer iconContainer = new IconContainer(this); iconContainer.Load(filename); iconContainer.Left = x; iconContainer.Top = y; AddElement(iconContainer); return iconContainer; } public ICursorContainer AddCursorContainer(Cursor cursor, int x, int y) { CursorContainer cursorContainer = new CursorContainer(this); cursorContainer.Cursor = cursor; cursorContainer.Left = x; cursorContainer.Top = y; AddElement(cursorContainer); return cursorContainer; } public ICursorContainer AddCursorContainer(string filename, int x, int y) { CursorContainer cursorContainer = new CursorContainer(this); cursorContainer.Load(filename); cursorContainer.Left = x; cursorContainer.Top = y; AddElement(cursorContainer); return cursorContainer; } public ITextContainer AddTextContainer(string text, HorizontalAlignment horizontalAlignment, VerticalAlignment verticalAlignment, FontFamily family, float size, bool italic, bool bold, bool shadow, int borderSize, Color color, Color fillColor) { TextContainer textContainer = new TextContainer(this); textContainer.Text = text; textContainer.SetFieldValue(FieldType.FONT_FAMILY, family.Name); textContainer.SetFieldValue(FieldType.FONT_BOLD, bold); textContainer.SetFieldValue(FieldType.FONT_ITALIC, italic); textContainer.SetFieldValue(FieldType.FONT_SIZE, size); textContainer.SetFieldValue(FieldType.FILL_COLOR, fillColor); textContainer.SetFieldValue(FieldType.LINE_COLOR, color); textContainer.SetFieldValue(FieldType.LINE_THICKNESS, borderSize); textContainer.SetFieldValue(FieldType.SHADOW, shadow); // Make sure the Text fits textContainer.FitToText(); // Align to Surface textContainer.AlignToParent(horizontalAlignment, verticalAlignment); //AggregatedProperties.UpdateElement(textContainer); AddElement(textContainer); return textContainer; } #endregion Plugin interface implementations #region DragDrop private void OnDragEnter(object sender, DragEventArgs e) { if (LOG.IsDebugEnabled) { LOG.Debug("DragEnter got following formats: "); foreach (string format in ClipboardHelper.GetFormats(e.Data)) { LOG.Debug(format); } } if ((e.AllowedEffect & DragDropEffects.Copy) != DragDropEffects.Copy) { e.Effect = DragDropEffects.None; } else { if (ClipboardHelper.ContainsImage(e.Data) || ClipboardHelper.ContainsFormat(e.Data, "DragImageBits")) { e.Effect = DragDropEffects.Copy; } else { e.Effect = DragDropEffects.None; } } } /// /// Handle the drag/drop /// /// /// private void OnDragDrop(object sender, DragEventArgs e) { Point mouse = PointToClient(new Point(e.X, e.Y)); if (e.Data.GetDataPresent("Text")) { string possibleUrl = ClipboardHelper.GetText(e.Data); // Test if it's an url and try to download the image so we have it in the original form if (possibleUrl != null && possibleUrl.StartsWith("http")) { using (Image image = NetworkHelper.DownloadImage(possibleUrl)) { if (image != null) { AddImageContainer(image, mouse.X, mouse.Y); return; } } } } foreach (Image image in ClipboardHelper.GetImages(e.Data)) { AddImageContainer(image, mouse.X, mouse.Y); mouse.Offset(10, 10); image.Dispose(); } } #endregion DragDrop /// /// Auto crop the image /// /// true if cropped public bool AutoCrop() { Rectangle cropRectangle; using (Image tmpImage = GetImageForExport()) { cropRectangle = ImageHelper.FindAutoCropRectangle(tmpImage, conf.AutoCropDifference); } if (!IsCropPossible(ref cropRectangle)) { return false; } DeselectAllElements(); // Maybe a bit obscure, but the following line creates a drop container // It's available as "undrawnElement" DrawingMode = DrawingModes.Crop; _undrawnElement.Left = cropRectangle.X; _undrawnElement.Top = cropRectangle.Y; _undrawnElement.Width = cropRectangle.Width; _undrawnElement.Height = cropRectangle.Height; _undrawnElement.Status = EditStatus.UNDRAWN; AddElement(_undrawnElement); SelectElement(_undrawnElement); _drawingElement = null; _undrawnElement = null; return true; } /// /// A simple clear /// /// The color for the background public void Clear(Color newColor) { //create a blank bitmap the same size as original Bitmap newBitmap = ImageHelper.CreateEmptyLike(Image, Color.Empty); if (newBitmap != null) { // Make undoable MakeUndoable(new SurfaceBackgroundChangeMemento(this, null), false); SetImage(newBitmap, false); Invalidate(); } } /// /// Apply a bitmap effect to the surface /// /// public void ApplyBitmapEffect(IEffect effect) { BackgroundForm backgroundForm = new BackgroundForm("Effect", "Please wait"); backgroundForm.Show(); Application.DoEvents(); try { Rectangle imageRectangle = new Rectangle(Point.Empty, Image.Size); Matrix matrix = new Matrix(); Image newImage = ImageHelper.ApplyEffect(Image, effect, matrix); if (newImage != null) { // Make sure the elements move according to the offset the effect made the bitmap move _elements.Transform(matrix); // Make undoable MakeUndoable(new SurfaceBackgroundChangeMemento(this, matrix), false); SetImage(newImage, false); Invalidate(); if (_surfaceSizeChanged != null && !imageRectangle.Equals(new Rectangle(Point.Empty, newImage.Size))) { _surfaceSizeChanged(this, null); } } else { // clean up matrix, as it hasn't been used in the undo stack. matrix.Dispose(); } } finally { // Always close the background form backgroundForm.CloseDialog(); } } /// /// check if a crop is possible /// /// /// true if this is possible public bool IsCropPossible(ref Rectangle cropRectangle) { cropRectangle = GuiRectangle.GetGuiRectangle(cropRectangle.Left, cropRectangle.Top, cropRectangle.Width, cropRectangle.Height); if (cropRectangle.Left < 0) { cropRectangle = new Rectangle(0, cropRectangle.Top, cropRectangle.Width + cropRectangle.Left, cropRectangle.Height); } if (cropRectangle.Top < 0) { cropRectangle = new Rectangle(cropRectangle.Left, 0, cropRectangle.Width, cropRectangle.Height + cropRectangle.Top); } if (cropRectangle.Left + cropRectangle.Width > Width) { cropRectangle = new Rectangle(cropRectangle.Left, cropRectangle.Top, Width - cropRectangle.Left, cropRectangle.Height); } if (cropRectangle.Top + cropRectangle.Height > Height) { cropRectangle = new Rectangle(cropRectangle.Left, cropRectangle.Top, cropRectangle.Width, Height - cropRectangle.Top); } if (cropRectangle.Height > 0 && cropRectangle.Width > 0) { return true; } return false; } /// /// Use to send any registered SurfaceMessageEventHandler a message, e.g. used for the notification area /// /// Who send /// Type of message /// Message itself public void SendMessageEvent(object source, SurfaceMessageTyp messageType, string message) { if (_surfaceMessage != null) { SurfaceMessageEventArgs eventArgs = new SurfaceMessageEventArgs(); eventArgs.Message = message; eventArgs.MessageType = messageType; eventArgs.Surface = this; _surfaceMessage(source, eventArgs); } } /// /// Crop the surface /// /// /// public bool ApplyCrop(Rectangle cropRectangle) { if (IsCropPossible(ref cropRectangle)) { Rectangle imageRectangle = new Rectangle(Point.Empty, Image.Size); Bitmap tmpImage; // Make sure we have information, this this fails try { tmpImage = ImageHelper.CloneArea(Image, cropRectangle, PixelFormat.DontCare); } catch (Exception ex) { ex.Data.Add("CropRectangle", cropRectangle); ex.Data.Add("Width", Image.Width); ex.Data.Add("Height", Image.Height); ex.Data.Add("Pixelformat", Image.PixelFormat); throw; } Matrix matrix = new Matrix(); matrix.Translate(-cropRectangle.Left, -cropRectangle.Top, MatrixOrder.Append); // Make undoable MakeUndoable(new SurfaceBackgroundChangeMemento(this, matrix), false); // Do not dispose otherwise we can't undo the image! SetImage(tmpImage, false); _elements.Transform(matrix); if (_surfaceSizeChanged != null && !imageRectangle.Equals(new Rectangle(Point.Empty, tmpImage.Size))) { _surfaceSizeChanged(this, null); } Invalidate(); return true; } return false; } /// /// The background here is the captured image. /// This is called from the SurfaceBackgroundChangeMemento. /// /// /// public void UndoBackgroundChange(Image previous, Matrix matrix) { SetImage(previous, false); if (matrix != null) { _elements.Transform(matrix); } if (_surfaceSizeChanged != null) { _surfaceSizeChanged(this, null); } Invalidate(); } /// /// This event handler is called when someone presses the mouse on a surface. /// /// /// private void SurfaceMouseDown(object sender, MouseEventArgs e) { _mouseStart = e.Location; // check contextmenu if (e.Button == MouseButtons.Right) { DrawableContainerList selectedList = null; if (selectedElements != null && selectedElements.Count > 0) { selectedList = selectedElements; } else { // Single element IDrawableContainer rightClickedContainer = _elements.ClickableElementAt(_mouseStart.X, _mouseStart.Y); if (rightClickedContainer != null) { selectedList = new DrawableContainerList(ID); selectedList.Add(rightClickedContainer); } } if (selectedList != null && selectedList.Count > 0) { selectedList.ShowContextMenu(e, this); } return; } _mouseDown = true; _isSurfaceMoveMadeUndoable = false; if (_cropContainer != null && ((_undrawnElement == null) || (_undrawnElement != null && DrawingMode != DrawingModes.Crop))) { RemoveElement(_cropContainer, false); _cropContainer = null; _drawingElement = null; } if (_drawingElement == null && DrawingMode != DrawingModes.None) { if (_undrawnElement == null) { DeselectAllElements(); if (_undrawnElement == null) { CreateUndrawnElement(); } } _drawingElement = _undrawnElement; // if a new element has been drawn, set location and register it if (_drawingElement != null) { if (_undrawnElement != null) { _drawingElement.Status = _undrawnElement.DefaultEditMode; } _drawingElement.PropertyChanged += ElementPropertyChanged; if (!_drawingElement.HandleMouseDown(_mouseStart.X, _mouseStart.Y)) { _drawingElement.Left = _mouseStart.X; _drawingElement.Top = _mouseStart.Y; } AddElement(_drawingElement); _drawingElement.Selected = true; } _undrawnElement = null; } else { // check whether an existing element was clicked // we save mouse down element separately from selectedElements (checked on mouse up), // since it could be moved around before it is actually selected _mouseDownElement = _elements.ClickableElementAt(_mouseStart.X, _mouseStart.Y); if (_mouseDownElement != null) { _mouseDownElement.Status = EditStatus.MOVING; } } } /// /// This event handle is called when the mouse button is unpressed /// /// /// private void SurfaceMouseUp(object sender, MouseEventArgs e) { Point currentMouse = new Point(e.X, e.Y); _elements.Status = EditStatus.IDLE; if (_mouseDownElement != null) { _mouseDownElement.Status = EditStatus.IDLE; } _mouseDown = false; _mouseDownElement = null; if (DrawingMode == DrawingModes.None) { // check whether an existing element was clicked IDrawableContainer element = _elements.ClickableElementAt(currentMouse.X, currentMouse.Y); bool shiftModifier = (ModifierKeys & Keys.Shift) == Keys.Shift; if (element != null) { element.Invalidate(); bool alreadySelected = selectedElements.Contains(element); if (shiftModifier) { if (alreadySelected) { DeselectElement(element); } else { SelectElement(element); } } else { if (!alreadySelected) { DeselectAllElements(); SelectElement(element); } } } else if (!shiftModifier) { DeselectAllElements(); } } if (selectedElements.Count > 0) { selectedElements.ShowGrippers(); selectedElements.Selected = true; } if (_drawingElement != null) { if (!_drawingElement.InitContent()) { _elements.Remove(_drawingElement); _drawingElement.Invalidate(); } else { _drawingElement.HandleMouseUp(currentMouse.X, currentMouse.Y); _drawingElement.Invalidate(); if (Math.Abs(_drawingElement.Width) < 5 && Math.Abs(_drawingElement.Height) < 5) { _drawingElement.Width = 25; _drawingElement.Height = 25; } SelectElement(_drawingElement); _drawingElement.Selected = true; } _drawingElement = null; } } /// /// This event handler is called when the mouse moves over the surface /// /// /// private void SurfaceMouseMove(object sender, MouseEventArgs e) { Point currentMouse = e.Location; if (DrawingMode != DrawingModes.None) { Cursor = Cursors.Cross; } else { Cursor = Cursors.Default; } if (_mouseDown) { if (_mouseDownElement != null) { // an element is currently dragged _mouseDownElement.Invalidate(); selectedElements.HideGrippers(); // Move the element if (_mouseDownElement.Selected) { if (!_isSurfaceMoveMadeUndoable) { // Only allow one undoable per mouse-down/move/up "cycle" _isSurfaceMoveMadeUndoable = true; selectedElements.MakeBoundsChangeUndoable(false); } // dragged element has been selected before -> move all selectedElements.MoveBy(currentMouse.X - _mouseStart.X, currentMouse.Y - _mouseStart.Y); } else { if (!_isSurfaceMoveMadeUndoable) { // Only allow one undoable per mouse-down/move/up "cycle" _isSurfaceMoveMadeUndoable = true; _mouseDownElement.MakeBoundsChangeUndoable(false); } // dragged element is not among selected elements -> just move dragged one _mouseDownElement.MoveBy(currentMouse.X - _mouseStart.X, currentMouse.Y - _mouseStart.Y); } _mouseStart = currentMouse; _mouseDownElement.Invalidate(); _modified = true; } else if (_drawingElement != null) { _drawingElement.HandleMouseMove(currentMouse.X, currentMouse.Y); _modified = true; } } } /// /// This event handler is called when the surface is double clicked. /// /// /// private void SurfaceDoubleClick(object sender, MouseEventArgs e) { selectedElements.OnDoubleClick(); selectedElements.Invalidate(); } /// /// Privately used to get the rendered image with all the elements on it. /// /// /// private Image GetImage(RenderMode renderMode) { // Generate a copy of the original image with a dpi equal to the default... Bitmap clone = ImageHelper.Clone(_image, PixelFormat.DontCare); // otherwise we would have a problem drawing the image to the surface... :( using (Graphics graphics = Graphics.FromImage(clone)) { // Do not set the following, the containers need to decide themselves //graphics.SmoothingMode = SmoothingMode.HighQuality; //graphics.PixelOffsetMode = PixelOffsetMode.HighQuality; //graphics.CompositingQuality = CompositingQuality.HighQuality; //graphics.InterpolationMode = InterpolationMode.HighQualityBicubic; _elements.Draw(graphics, clone, renderMode, new Rectangle(Point.Empty, clone.Size)); } return clone; } /// /// This returns the image "result" of this surface, with all the elements rendered on it. /// /// public Image GetImageForExport() { return GetImage(RenderMode.EXPORT); } /// /// This is the event handler for the Paint Event, try to draw as little as possible! /// /// /// private void SurfacePaint(object sender, PaintEventArgs e) { Graphics targetGraphics = e.Graphics; Rectangle clipRectangle = e.ClipRectangle; if (Rectangle.Empty.Equals(clipRectangle)) { LOG.Debug("Empty cliprectangle??"); return; } if (_elements.HasIntersectingFilters(clipRectangle)) { if (_buffer != null) { if (_buffer.Width != Image.Width || _buffer.Height != Image.Height || _buffer.PixelFormat != Image.PixelFormat) { _buffer.Dispose(); _buffer = null; } } if (_buffer == null) { _buffer = ImageHelper.CreateEmpty(Image.Width, Image.Height, Image.PixelFormat, Color.Empty, Image.HorizontalResolution, Image.VerticalResolution); LOG.DebugFormat("Created buffer with size: {0}x{1}", Image.Width, Image.Height); } // Elements might need the bitmap, so we copy the part we need using (Graphics graphics = Graphics.FromImage(_buffer)) { // do not set the following, the containers need to decide this themselves! //graphics.SmoothingMode = SmoothingMode.HighQuality; //graphics.PixelOffsetMode = PixelOffsetMode.HighQuality; //graphics.CompositingQuality = CompositingQuality.HighQuality; //graphics.InterpolationMode = InterpolationMode.HighQualityBicubic; DrawBackground(graphics, clipRectangle); graphics.DrawImage(Image, clipRectangle, clipRectangle, GraphicsUnit.Pixel); graphics.SetClip(targetGraphics); _elements.Draw(graphics, _buffer, RenderMode.EDIT, clipRectangle); } targetGraphics.DrawImage(_buffer, clipRectangle, clipRectangle, GraphicsUnit.Pixel); } else { DrawBackground(targetGraphics, clipRectangle); targetGraphics.DrawImage(Image, clipRectangle, clipRectangle, GraphicsUnit.Pixel); _elements.Draw(targetGraphics, null, RenderMode.EDIT, clipRectangle); } } private void DrawBackground(Graphics targetGraphics, Rectangle clipRectangle) { // check if we need to draw the checkerboard if (Image.IsAlphaPixelFormat(Image.PixelFormat) && _transparencyBackgroundBrush != null) { targetGraphics.FillRectangle(_transparencyBackgroundBrush, clipRectangle); } else { targetGraphics.Clear(BackColor); } } /// /// Draw a checkboard when capturing with transparency /// /// PaintEventArgs protected override void OnPaintBackground(PaintEventArgs e) { } /// /// Wrapper for makeUndoable flag which was introduced later, will call AddElement with makeundoable set to true /// /// the new element public void AddElement(IDrawableContainer element) { AddElement(element, true); } /// /// Add a new element to the surface /// /// the new element /// true if the adding should be undoable public void AddElement(IDrawableContainer element, bool makeUndoable) { _elements.Add(element); DrawableContainer container = element as DrawableContainer; if (container != null) { container.FieldChanged += element_FieldChanged; } element.PropertyChanged += ElementPropertyChanged; if (element.Status == EditStatus.UNDRAWN) { element.Status = EditStatus.IDLE; } element.Invalidate(); if (makeUndoable) { MakeUndoable(new AddElementMemento(this, element), false); } _modified = true; } /// /// Remove an element of the elements list /// /// Element to remove /// flag specifying if the remove needs to be undoable public void RemoveElement(IDrawableContainer elementToRemove, bool makeUndoable) { DeselectElement(elementToRemove); _elements.Remove(elementToRemove); DrawableContainer element = elementToRemove as DrawableContainer; if (element != null) { element.FieldChanged -= element_FieldChanged; } elementToRemove.PropertyChanged -= ElementPropertyChanged; // Do not dispose, the memento should!! element.Dispose(); Invalidate(); if (makeUndoable) { MakeUndoable(new DeleteElementMemento(this, elementToRemove), false); } _modified = true; } /// /// Add the supplied elements to the surface /// /// public void AddElements(DrawableContainerList elementsToAdd) { foreach (IDrawableContainer element in elementsToAdd) { AddElement(element, true); } } /// /// Returns if this surface has selected elements /// /// public bool HasSelectedElements { get { return selectedElements != null && selectedElements.Count > 0; } } /// /// Remove all the selected elements /// public void RemoveSelectedElements() { if (HasSelectedElements) { // As RemoveElement will remove the element from the selectedElements list we need to copy the element // to another list. List elementsToRemove = new List(); foreach (DrawableContainer element in selectedElements) { // Collect to remove later elementsToRemove.Add(element); } // Remove now foreach (DrawableContainer element in elementsToRemove) { RemoveElement(element, true); } selectedElements.Clear(); if (_movingElementChanged != null) { SurfaceElementEventArgs eventArgs = new SurfaceElementEventArgs(); eventArgs.Elements = selectedElements; _movingElementChanged(this, eventArgs); } } } /// /// Cut the selected elements from the surface to the clipboard /// public void CutSelectedElements() { if (HasSelectedElements) { ClipboardHelper.SetClipboardData(typeof(DrawableContainerList), selectedElements); RemoveSelectedElements(); } } /// /// Copy the selected elements to the clipboard /// public void CopySelectedElements() { if (HasSelectedElements) { ClipboardHelper.SetClipboardData(typeof(DrawableContainerList), selectedElements); } } /// /// This method is called to confirm/cancel "confirmable" elements, like the crop-container. /// Called when pressing enter or using the "check" in the editor. /// /// public void ConfirmSelectedConfirmableElements(bool confirm) { // create new collection so that we can iterate safely (selectedElements might change due with confirm/cancel) List selectedDCs = new List(selectedElements); if (_cropContainer == null) { return; } foreach (IDrawableContainer dc in selectedDCs) { if (dc.Equals(_cropContainer)) { DrawingMode = DrawingModes.None; // No undo memento for the cropcontainer itself, only for the effect RemoveElement(_cropContainer, false); if (confirm) { ApplyCrop(_cropContainer.Bounds); } _cropContainer.Dispose(); _cropContainer = null; break; } } } /// /// Paste all the elements that are on the clipboard /// public void PasteElementFromClipboard() { IDataObject clipboard = ClipboardHelper.GetDataObject(); List formats = ClipboardHelper.GetFormats(clipboard); if (formats == null || formats.Count == 0) { return; } if (LOG.IsDebugEnabled) { LOG.Debug("List of clipboard formats available for pasting:"); foreach (string format in formats) { LOG.Debug("\tgot format: " + format); } } if (formats.Contains(typeof(DrawableContainerList).FullName)) { DrawableContainerList dcs = (DrawableContainerList)ClipboardHelper.GetFromDataObject(clipboard, typeof(DrawableContainerList)); if (dcs != null) { // Make element(s) only move 10,10 if the surface is the same Point moveOffset; bool isSameSurface = dcs.ParentID == _uniqueId; dcs.Parent = this; if (isSameSurface) { moveOffset = new Point(10, 10); } else { moveOffset = Point.Empty; } // Here a fix for bug #1475, first calculate the bounds of the complete DrawableContainerList Rectangle drawableContainerListBounds = Rectangle.Empty; foreach (IDrawableContainer element in dcs) { if (drawableContainerListBounds == Rectangle.Empty) { drawableContainerListBounds = element.DrawingBounds; } else { drawableContainerListBounds = Rectangle.Union(drawableContainerListBounds, element.DrawingBounds); } } // And find a location inside the target surface to paste to bool containersCanFit = drawableContainerListBounds.Width < Bounds.Width && drawableContainerListBounds.Height < Bounds.Height; if (!containersCanFit) { Point containersLocation = drawableContainerListBounds.Location; containersLocation.Offset(moveOffset); if (!Bounds.Contains(containersLocation)) { // Easy fix for same surface if (isSameSurface) { moveOffset = new Point(-10, -10); } else { // For different surface, which is most likely smaller, we move to "10,10" moveOffset = new Point(-drawableContainerListBounds.Location.X + 10, -drawableContainerListBounds.Location.Y + 10); } } } else { Rectangle moveContainerListBounds = drawableContainerListBounds; moveContainerListBounds.Offset(moveOffset); // check if the element is inside if (!Bounds.Contains(moveContainerListBounds)) { // Easy fix for same surface if (isSameSurface) { moveOffset = new Point(-10, -10); } else { // For different surface, which is most likely smaller int offsetX = 0; int offsetY = 0; if (drawableContainerListBounds.Right > Bounds.Right) { offsetX = Bounds.Right - drawableContainerListBounds.Right; // Correction for the correction if (drawableContainerListBounds.Left + offsetX < 0) { offsetX += Math.Abs(drawableContainerListBounds.Left + offsetX); } } if (drawableContainerListBounds.Bottom > Bounds.Bottom) { offsetY = Bounds.Bottom - drawableContainerListBounds.Bottom; // Correction for the correction if (drawableContainerListBounds.Top + offsetY < 0) { offsetY += Math.Abs(drawableContainerListBounds.Top + offsetY); } } moveOffset = new Point(offsetX, offsetY); } } } dcs.MoveBy(moveOffset.X, moveOffset.Y); AddElements(dcs); FieldAggregator.BindElements(dcs); DeselectAllElements(); SelectElements(dcs); } } else if (ClipboardHelper.ContainsImage(clipboard)) { int x = 10; int y = 10; foreach (Image clipboardImage in ClipboardHelper.GetImages(clipboard)) { if (clipboardImage != null) { DeselectAllElements(); IImageContainer container = AddImageContainer(clipboardImage as Bitmap, x, y); SelectElement(container); clipboardImage.Dispose(); x += 10; y += 10; } } } else if (ClipboardHelper.ContainsText(clipboard)) { string text = ClipboardHelper.GetText(clipboard); if (text != null) { DeselectAllElements(); ITextContainer textContainer = AddTextContainer(text, HorizontalAlignment.Center, VerticalAlignment.CENTER, FontFamily.GenericSansSerif, 12f, false, false, false, 2, Color.Black, Color.Transparent); SelectElement(textContainer); } } } /// /// Duplicate all the selecteded elements /// public void DuplicateSelectedElements() { LOG.DebugFormat("Duplicating {0} selected elements", selectedElements.Count); DrawableContainerList dcs = selectedElements.Clone(); dcs.Parent = this; dcs.MoveBy(10, 10); AddElements(dcs); DeselectAllElements(); SelectElements(dcs); } /// /// Deselect the specified element /// /// public void DeselectElement(IDrawableContainer container) { container.HideGrippers(); container.Selected = false; selectedElements.Remove(container); FieldAggregator.UnbindElement(container); if (_movingElementChanged != null) { SurfaceElementEventArgs eventArgs = new SurfaceElementEventArgs(); eventArgs.Elements = selectedElements; _movingElementChanged(this, eventArgs); } } /// /// Deselect all the selected elements /// public void DeselectAllElements() { if (HasSelectedElements) { while (selectedElements.Count > 0) { IDrawableContainer element = selectedElements[0]; element.Invalidate(); element.HideGrippers(); element.Selected = false; selectedElements.Remove(element); FieldAggregator.UnbindElement(element); } if (_movingElementChanged != null) { SurfaceElementEventArgs eventArgs = new SurfaceElementEventArgs(); eventArgs.Elements = selectedElements; _movingElementChanged(this, eventArgs); } } } /// /// Select the supplied element /// /// public void SelectElement(IDrawableContainer container) { if (!selectedElements.Contains(container)) { selectedElements.Add(container); container.ShowGrippers(); container.Selected = true; FieldAggregator.BindElement(container); if (_movingElementChanged != null) { SurfaceElementEventArgs eventArgs = new SurfaceElementEventArgs(); eventArgs.Elements = selectedElements; _movingElementChanged(this, eventArgs); } container.Invalidate(); } } /// /// Select all elements, this is called when Ctrl+A is pressed /// public void SelectAllElements() { SelectElements(_elements); } /// /// Select the supplied elements /// /// public void SelectElements(DrawableContainerList elements) { foreach (DrawableContainer element in elements) { SelectElement(element); } } /// /// Process key presses on the surface, this is called from the editor (and NOT an override from the Control) /// /// Keys /// false if no keys were processed public bool ProcessCmdKey(Keys k) { if (selectedElements.Count > 0) { bool shiftModifier = (ModifierKeys & Keys.Shift) == Keys.Shift; int px = shiftModifier ? 10 : 1; Point moveBy = Point.Empty; switch (k) { case Keys.Left: case Keys.Left | Keys.Shift: moveBy = new Point(-px, 0); break; case Keys.Up: case Keys.Up | Keys.Shift: moveBy = new Point(0, -px); break; case Keys.Right: case Keys.Right | Keys.Shift: moveBy = new Point(px, 0); break; case Keys.Down: case Keys.Down | Keys.Shift: moveBy = new Point(0, px); break; case Keys.PageUp: PullElementsUp(); break; case Keys.PageDown: PushElementsDown(); break; case Keys.Home: PullElementsToTop(); break; case Keys.End: PushElementsToBottom(); break; case Keys.Enter: ConfirmSelectedConfirmableElements(true); break; case Keys.Escape: ConfirmSelectedConfirmableElements(false); break; /*case Keys.Delete: RemoveSelectedElements(); break;*/ default: return false; } if (!Point.Empty.Equals(moveBy)) { selectedElements.MakeBoundsChangeUndoable(true); selectedElements.MoveBy(moveBy.X, moveBy.Y); } return true; } return false; } /// /// Property for accessing the elements on the surface /// public DrawableContainerList Elements { get { return _elements; } } /// /// pulls selected elements up one level in hierarchy /// public void PullElementsUp() { _elements.PullElementsUp(selectedElements); _elements.Invalidate(); } /// /// pushes selected elements up to top in hierarchy /// public void PullElementsToTop() { _elements.PullElementsToTop(selectedElements); _elements.Invalidate(); } /// /// pushes selected elements down one level in hierarchy /// public void PushElementsDown() { _elements.PushElementsDown(selectedElements); _elements.Invalidate(); } /// /// pushes selected elements down to bottom in hierarchy /// public void PushElementsToBottom() { _elements.PushElementsToBottom(selectedElements); _elements.Invalidate(); } /// /// indicates whether the selected elements could be pulled up in hierarchy /// /// true if selected elements could be pulled up, false otherwise public bool CanPullSelectionUp() { return _elements.CanPullUp(selectedElements); } /// /// indicates whether the selected elements could be pushed down in hierarchy /// /// true if selected elements could be pushed down, false otherwise public bool CanPushSelectionDown() { return _elements.CanPushDown(selectedElements); } public void ElementPropertyChanged(object sender, PropertyChangedEventArgs e) { //Invalidate(); } public void element_FieldChanged(object sender, FieldChangedEventArgs e) { selectedElements.HandleFieldChangedEvent(sender, e); } public bool IsOnSurface(IDrawableContainer container) { return _elements.Contains(container); } } }