namespace TACO.Widgets { using System; using System.Collections; using System.Drawing; using System.Drawing.Drawing2D; static class Cursor { private static Gdk.Cursor mPointer; private static Gdk.Cursor mFleur; public static Gdk.Cursor Pointer { get { if (mPointer == null) mPointer = new Gdk.Cursor(Gdk.CursorType.LeftPtr); return mPointer; } } public static Gdk.Cursor Fleur { get { if (mFleur == null) mFleur = new Gdk.Cursor(Gdk.CursorType.Fleur); return mFleur; } } } public class Canvas : Gtk.DrawingArea { private Hashtable mElements; private Gdk.Rectangle mDrawingExtents; private CanvasSelection mSelection; // Dragging private CanvasElement mDraggedElement = null; private int mDragOffsetX = 0; private int mDragOffsetY = 0; // Back buffer private Gdk.Pixmap mBacking; private Gdk.Rectangle mVisibleRect; public Canvas() { AddEvents((int)(Gdk.EventMask.ButtonPressMask | Gdk.EventMask.ButtonReleaseMask | Gdk.EventMask.PointerMotionMask)); DoubleBuffered = false; mElements = new Hashtable(); mSelection = new CanvasSelection(null); mSelection.BoundsChanged += OnElementBoundsChanged; mSelection.VisibilityChanged += OnElementChanged; } public Hashtable Elements { get { return mElements; } } public Gdk.Rectangle DrawingExtents { get { return mDrawingExtents; } } public void Add(CanvasElement element) { // XXX Elements.Add(element.Id, element); Redraw(mVisibleRect); element.BoundsChanged += OnElementBoundsChanged; element.VisibilityChanged += OnElementChanged; } public CanvasElement GetElement(double x, double y) { Point p = new Point((int)x, (int)y); foreach (DictionaryEntry entry in Elements) { CanvasElement element = entry.Value as CanvasElement; if (element.Visible && element.Bounds.Contains(p)) return element; } return null; } protected override bool OnExposeEvent(Gdk.EventExpose ev) { Gdk.Color white = new Gdk.Color(0xFF, 0xFF, 0xFF); Gdk.Colormap.System.AllocColor(ref white, true, true); Gdk.GC gc = new Gdk.GC(mBacking); gc.Foreground = white; mBacking.DrawRectangle(gc, true, ev.Area); using (Graphics g = Gtk.DotNet.Graphics.FromDrawable(mBacking)) { DrawElements(g); GdkWindow.DrawDrawable(gc, mBacking, ev.Area.X, ev.Area.Y, ev.Area.X, ev.Area.Y, ev.Area.Width, ev.Area.Height); } return true; } protected override bool OnConfigureEvent(Gdk.EventConfigure ev) { mDrawingExtents.Width = ev.Width; mDrawingExtents.Height = ev.Height; mBacking = new Gdk.Pixmap(GdkWindow, ev.Width, ev.Height, -1); mVisibleRect.Width = ev.Width; mVisibleRect.Height = ev.Height; CanvasElement.Drawable = GdkWindow; return base.OnConfigureEvent(ev); } protected override bool OnButtonPressEvent(Gdk.EventButton ev) { mDraggedElement = GetElement(ev.X, ev.Y); if (mDraggedElement == null || mDraggedElement != mSelection.Target) { mSelection.Target = null; } if (mDraggedElement != null) { mDragOffsetX = (int)ev.X - mDraggedElement.Bounds.X; mDragOffsetY = (int)ev.Y - mDraggedElement.Bounds.Y; GdkWindow.Cursor = Cursor.Fleur; } return base.OnButtonPressEvent(ev); } protected override bool OnButtonReleaseEvent(Gdk.EventButton ev) { mSelection.Target = mDraggedElement; if (mDraggedElement != null) { mDraggedElement.MoveTo((int)ev.X - mDragOffsetX, (int)ev.Y - mDragOffsetY); GdkWindow.Cursor = Cursor.Fleur; mDraggedElement = null; mDragOffsetX = 0; mDragOffsetY = 0; } return base.OnButtonReleaseEvent(ev); } protected override bool OnMotionNotifyEvent(Gdk.EventMotion ev) { if (mDraggedElement != null) { mSelection.Target = null; mDraggedElement.MoveTo((int)ev.X - mDragOffsetX, (int)ev.Y - mDragOffsetY); } else { CanvasElement element = GetElement(ev.X, ev.Y); if (element != null) GdkWindow.Cursor = Cursor.Fleur; else GdkWindow.Cursor = Cursor.Pointer; } return base.OnMotionNotifyEvent(ev); } private void Redraw(Gdk.Rectangle area) { if (GdkWindow != null) GdkWindow.InvalidateRect(area, false); } private void DrawElements(Graphics g) { g.ResetTransform(); g.TranslateTransform((float)-DrawingExtents.X, (float)-DrawingExtents.Y); Rectangle area = new Rectangle(); area.X = DrawingExtents.X; area.Y = DrawingExtents.Y; area.Width = DrawingExtents.Width; area.Height = DrawingExtents.Height; if (mSelection.Visible) DrawElement(mSelection, g); foreach (DictionaryEntry entry in Elements) { CanvasElement element = entry.Value as CanvasElement; if (element.Visible && (area.Width == 0 || element.Bounds.IntersectsWith(area))) { DrawElement(element, g); } } } private void DrawElement(CanvasElement element, Graphics g) { /* * NOTE: Saving the GraphicsState is broken in libgdiplus 1.1.9. * * Not that it matters much, because as of libgdiplus * 1.1.9 (and other versions really) there are horrible * bugs with transformations not really working at all. */ // GraphicsState state = g.Save(); //g.TranslateTransform(element.Bounds.X, element.Bounds.Y); element.Draw(g); // g.Restore(); //g.TranslateTransform(-element.Bounds.X, -element.Bounds.Y); } private void OnElementBoundsChanged(object o, BoundsChangedArgs args) { CanvasElement element = o as CanvasElement; Rectangle union = Rectangle.Union(args.OldBounds, element.Bounds); Gdk.Rectangle bounds = new Gdk.Rectangle(); bounds.X = union.X; bounds.Y = union.Y; bounds.Width = union.Width; bounds.Height = union.Height; Redraw(bounds); } private void OnElementChanged(object o, System.EventArgs args) { CanvasElement element = o as CanvasElement; Gdk.Rectangle bounds = new Gdk.Rectangle(); bounds.X = element.Bounds.X; bounds.Y = element.Bounds.Y; bounds.Width = element.Bounds.Width; bounds.Height = element.Bounds.Height; Redraw(bounds); } } }