X11Dnd.cs :  » 2.6.4-mono-.net-core » System.Windows.Forms » System » Windows » Forms » C# / CSharp Open Source

Home
C# / CSharp Open Source
1.2.6.4 mono .net core
2.2.6.4 mono core
3.Aspect Oriented Frameworks
4.Bloggers
5.Build Systems
6.Business Application
7.Charting Reporting Tools
8.Chat Servers
9.Code Coverage Tools
10.Content Management Systems CMS
11.CRM ERP
12.Database
13.Development
14.Email
15.Forum
16.Game
17.GIS
18.GUI
19.IDEs
20.Installers Generators
21.Inversion of Control Dependency Injection
22.Issue Tracking
23.Logging Tools
24.Message
25.Mobile
26.Network Clients
27.Network Servers
28.Office
29.PDF
30.Persistence Frameworks
31.Portals
32.Profilers
33.Project Management
34.RSS RDF
35.Rule Engines
36.Script
37.Search Engines
38.Sound Audio
39.Source Control
40.SQL Clients
41.Template Engines
42.Testing
43.UML
44.Web Frameworks
45.Web Service
46.Web Testing
47.Wiki Engines
48.Windows Presentation Foundation
49.Workflows
50.XML Parsers
C# / C Sharp
C# / C Sharp by API
C# / CSharp Tutorial
C# / CSharp Open Source » 2.6.4 mono .net core » System.Windows.Forms 
System.Windows.Forms » System » Windows » Forms » X11Dnd.cs
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
// 
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
// Copyright (c) 2005 Novell, Inc.
//
// Authors:
//  Jackson Harper (jackson@ximian.com)
//
// NOTE: We have some tests in Test/System.Windows.Forms/DragAndDropTest.cs, which I *highly* recommend
// to run after any change made here, since those tests are interactive, and thus are not part of
// the common tests.
//


using System;
using System.IO;
using System.Text;
using System.Drawing;
using System.Threading;
using System.Collections;
using System.Runtime.Serialization;
using System.Runtime.InteropServices;
using System.Runtime.Serialization.Formatters.Binary;

namespace System.Windows.Forms{

  internal class X11Dnd {

    private enum State {
      Accepting,
      Dragging
    }

    private enum DragState {
      None,
      Beginning,
      Dragging,
      Entered
    }

    private interface IDataConverter {
      void GetData (X11Dnd dnd, IDataObject data, ref XEvent xevent);
      void SetData (X11Dnd dnd, object data, ref XEvent xevent);
    }

    private delegate void MimeConverter (IntPtr dsp,
        IDataObject data, ref XEvent xevent);

    private class MimeHandler {
      public string Name;
      public string [] Aliases;
      public IntPtr Type;
      public IntPtr NonProtocol;
      public IDataConverter Converter;
      
      public MimeHandler (string name, IDataConverter converter) : this (name, converter, name)
      {
      }

      public MimeHandler (string name, IDataConverter converter, params string [] aliases)
      {
        Name = name;
        Converter = converter;
        Aliases = aliases;
      }

      public override string ToString ()
      {
        return "MimeHandler {" + Name + "}";
      }
    }

    private MimeHandler [] MimeHandlers = {
//        new MimeHandler ("WCF_DIB"),
//        new MimeHandler ("image/gif", new MimeConverter (ImageConverter)),
//      new MimeHandler ("text/rtf", new MimeConverter (RtfConverter)),
//      new MimeHandler ("text/richtext", new MimeConverter (RtfConverter)),

      new MimeHandler ("text/plain", new TextConverter ()),
      new MimeHandler ("text/plain", new TextConverter (), "System.String", DataFormats.Text),
      new MimeHandler ("text/html", new HtmlConverter (), DataFormats.Html),
      new MimeHandler ("text/uri-list", new UriListConverter (), DataFormats.FileDrop),
      new MimeHandler ("application/x-mono-serialized-object",
          new SerializedObjectConverter ())
    };

    private class SerializedObjectConverter : IDataConverter {

      public void GetData (X11Dnd dnd, IDataObject data, ref XEvent xevent)
      {
        MemoryStream stream = dnd.GetData (ref xevent);
        BinaryFormatter bf = new BinaryFormatter ();

        if (stream.Length == 0)
          return;

        stream.Seek (0, 0);
        object obj = bf.Deserialize (stream);
        data.SetData (obj);
      }

      public void SetData (X11Dnd dnd, object data, ref XEvent xevent)
      {
        if (data == null)
          return;

        MemoryStream stream = new MemoryStream ();
        BinaryFormatter bf = new BinaryFormatter ();

        bf.Serialize (stream, data);

        IntPtr buffer = Marshal.AllocHGlobal ((int) stream.Length);
        stream.Seek (0, 0);

        for (int i = 0; i < stream.Length; i++) {
          Marshal.WriteByte (buffer, i, (byte) stream.ReadByte ());
        }

        dnd.SetProperty (ref xevent, buffer, (int) stream.Length);
      }
    }

    private class HtmlConverter : IDataConverter {

      public void GetData (X11Dnd dnd, IDataObject data, ref XEvent xevent)
      {
        string text = dnd.GetText (ref xevent, false);
        if (text == null)
          return;
        data.SetData (DataFormats.Text, text);
        data.SetData (DataFormats.UnicodeText, text);
      }

      public void SetData (X11Dnd dnd, object data, ref XEvent xevent)
      {
        IntPtr buffer;
        int len;
        string str = data as string;

        if (str == null)
          return;

        if (xevent.SelectionRequestEvent.target == (IntPtr)Atom.XA_STRING) {
          byte [] bytes = Encoding.ASCII.GetBytes (str);
          buffer = Marshal.AllocHGlobal (bytes.Length);
          len = bytes.Length;
          for (int i = 0; i < len; i++)
            Marshal.WriteByte (buffer, i, bytes [i]);
        } else {
          buffer = Marshal.StringToHGlobalAnsi (str);
          len = 0;
          while (Marshal.ReadByte (buffer, len) != 0)
            len++;
        }

        dnd.SetProperty (ref xevent, buffer, len);

        Marshal.FreeHGlobal (buffer);
      }
    }

    private class TextConverter : IDataConverter {

      public void GetData (X11Dnd dnd, IDataObject data, ref XEvent xevent)
      {
        string text = dnd.GetText (ref xevent, true);
        if (text == null)
          return;
        data.SetData (DataFormats.Text, text);
        data.SetData (DataFormats.UnicodeText, text);
      }

      public void SetData (X11Dnd dnd, object data, ref XEvent xevent)
      {
        IntPtr buffer;
        int len;
        string str = data as string;

        if (str == null) {
          IDataObject dobj = data as IDataObject;
          if (dobj == null)
            return;
          str = (string) dobj.GetData ("System.String", true);
        }

        if (xevent.SelectionRequestEvent.target == (IntPtr)Atom.XA_STRING) {
          byte [] bytes = Encoding.ASCII.GetBytes (str);
          buffer = Marshal.AllocHGlobal (bytes.Length);
          len = bytes.Length;
          for (int i = 0; i < len; i++)
            Marshal.WriteByte (buffer, i, bytes [i]);
        } else {
          buffer = Marshal.StringToHGlobalAnsi (str);
          len = 0;
          while (Marshal.ReadByte (buffer, len) != 0)
            len++;
        }

        dnd.SetProperty (ref xevent, buffer, len);

        Marshal.FreeHGlobal (buffer);
      }
    }

    private class UriListConverter : IDataConverter {

      public void GetData (X11Dnd dnd, IDataObject data, ref XEvent xevent)
      {
        string text = dnd.GetText (ref xevent, false);
        if (text == null)
          return;

        // TODO: Do this in a loop instead of just splitting
        ArrayList uri_list = new ArrayList ();
        string [] lines = text.Split (new char [] { '\r', '\n' });
        foreach (string line in lines) {
          // # is a comment line (see RFC 2483)
          if (line.StartsWith ("#"))
            continue;
          try {
            Uri uri = new Uri (line);
            uri_list.Add (uri.LocalPath);
          } catch { }
        }

        string [] l = (string []) uri_list.ToArray (typeof (string));
        if (l.Length < 1)
          return;
        data.SetData (DataFormats.FileDrop, l);
        data.SetData ("FileName", l [0]);
        data.SetData ("FileNameW", l [0]);
      }

      public void SetData (X11Dnd dnd, object data, ref XEvent xevent)
      {
        string [] uri_list = data as string [];

        if (uri_list == null) {
          IDataObject dobj = data as IDataObject;
          if (dobj == null)
            return;
          uri_list = dobj.GetData (DataFormats.FileDrop, true) as string [];
        }

        if (uri_list == null)
          return;

        StringBuilder res = new StringBuilder ();
        foreach (string uri_str in uri_list) {
          Uri uri = new Uri (uri_str);
          res.Append (uri.ToString ());
          res.Append ("\r\n");
        }

        IntPtr buffer = Marshal.StringToHGlobalAnsi ((string) res.ToString ());
        int len = 0;
        while (Marshal.ReadByte (buffer, len) != 0)
          len++;

        dnd.SetProperty (ref xevent, buffer, len);
      }
    }

    private class DragData {
      public IntPtr Window;
      public DragState State;
      public object Data;
      public IntPtr Action;
      public IntPtr [] SupportedTypes;
      public MouseButtons MouseState;
      public DragDropEffects AllowedEffects;
      public Point CurMousePos;
      
      public IntPtr LastWindow;
      public IntPtr LastTopLevel;

      public bool WillAccept;
      
      public void Reset ()
      {
        State = DragState.None;
        Data = null;
        SupportedTypes = null;
        WillAccept = false;
      }
    }

    // This version seems to be the most common
    private static readonly IntPtr [] XdndVersion = new IntPtr [] { new IntPtr (4) }; 

    private IntPtr display;
    private DragData drag_data;
    
    private IntPtr XdndAware;
    private IntPtr XdndSelection;
    private IntPtr XdndEnter;
    private IntPtr XdndLeave;
    private IntPtr XdndPosition;
    private IntPtr XdndDrop;
    private IntPtr XdndFinished;
    private IntPtr XdndStatus;
    private IntPtr XdndTypeList;
    private IntPtr XdndActionCopy;
    private IntPtr XdndActionMove;
    private IntPtr XdndActionLink;
    //private IntPtr XdndActionPrivate;
    private IntPtr XdndActionList;
    //private IntPtr XdndActionDescription;
    //private IntPtr XdndActionAsk;

    //private State state;

    private int converts_pending;
    private bool position_recieved;
    private bool status_sent;
    private IntPtr target;
    private IntPtr source;
    private IntPtr toplevel;
    private IDataObject data;

    private Control control;
    private int pos_x, pos_y;
    private DragDropEffects allowed;
    private DragEventArgs drag_event;

    private Cursor CursorNo;
    private Cursor CursorCopy;
    private Cursor CursorMove;
    private Cursor CursorLink;
    // check out the TODO below
    //private IntPtr CurrentCursorHandle;

    private bool tracking = false;
    private bool dropped = false;
    private int motion_poll;
    //private X11Keyboard keyboard;

    public X11Dnd (IntPtr display, X11Keyboard keyboard)
    {
      this.display = display;
      //this.keyboard = keyboard;

      Init ();
    }

    public bool InDrag()
    {
      if (drag_data == null)
        return false;
      return drag_data.State != DragState.None;
    }
    
    public void SetAllowDrop (Hwnd hwnd, bool allow)
    {
      int[] atoms;

      if (hwnd.allow_drop == allow)
        return;

      atoms = new int[XdndVersion.Length];
      for (int i = 0; i < XdndVersion.Length; i++) {
        atoms[i] = XdndVersion[i].ToInt32();
      }

      XplatUIX11.XChangeProperty (display, hwnd.whole_window, XdndAware,
          (IntPtr) Atom.XA_ATOM, 32,
          PropertyMode.Replace, atoms, allow ? 1 : 0);
      hwnd.allow_drop = allow;
    }

    public DragDropEffects StartDrag (IntPtr handle, object data,
        DragDropEffects allowed_effects)
    {
      drag_data = new DragData ();
      drag_data.Window = handle;
      drag_data.State = DragState.Beginning;
      drag_data.MouseState = XplatUIX11.MouseState;
      drag_data.Data = data;
      drag_data.SupportedTypes = DetermineSupportedTypes (data);
      drag_data.AllowedEffects = allowed_effects;
      drag_data.Action = ActionFromEffect (allowed_effects);

      if (CursorNo == null) {
        // Make sure the cursors are created
        CursorNo = new Cursor (typeof (X11Dnd), "DnDNo.cur");
        CursorCopy = new Cursor (typeof (X11Dnd), "DnDCopy.cur");
        CursorMove = new Cursor (typeof (X11Dnd), "DnDMove.cur");
        CursorLink = new Cursor (typeof (X11Dnd), "DnDLink.cur");
      }

      drag_data.LastTopLevel = IntPtr.Zero;
      control = null;

      System.Windows.Forms.MSG msg = new MSG();
      object queue_id = XplatUI.StartLoop (Thread.CurrentThread);

      Timer timer = new Timer ();
      timer.Tick += new EventHandler (DndTickHandler);
      timer.Interval = 100;

      int suc;
      drag_data.State = DragState.Dragging;

      suc = XplatUIX11.XSetSelectionOwner (display, XdndSelection,
          drag_data.Window, IntPtr.Zero);

      if (suc == 0) {
        Console.Error.WriteLine ("Could not take ownership of XdndSelection aborting drag.");
        drag_data.Reset ();
        return DragDropEffects.None;
      }

      drag_data.State = DragState.Dragging;
      drag_data.CurMousePos = new Point ();
      source = toplevel = target = IntPtr.Zero;
      dropped = false;
      tracking = true;
      motion_poll = -1;
      timer.Start ();

      // Send Enter to the window initializing the dnd operation - which initializes the data
      SendEnter (drag_data.Window, drag_data.Window, drag_data.SupportedTypes);
      drag_data.LastTopLevel = toplevel;

      while (tracking && XplatUI.GetMessage (queue_id, ref msg, IntPtr.Zero, 0, 0)) {

        if (msg.message >= Msg.WM_KEYFIRST && msg.message <= Msg.WM_KEYLAST) {
          HandleKeyMessage (msg);
        } else {
          switch (msg.message) {
          case Msg.WM_LBUTTONUP:
          case Msg.WM_RBUTTONUP:
          case Msg.WM_MBUTTONUP:
            if (msg.message == Msg.WM_LBUTTONDOWN && drag_data.MouseState != MouseButtons.Left)
              break;;
            if (msg.message == Msg.WM_RBUTTONDOWN && drag_data.MouseState != MouseButtons.Right)
              break;
            if (msg.message == Msg.WM_MBUTTONDOWN && drag_data.MouseState != MouseButtons.Middle)
              break;
            
            HandleButtonUpMsg ();

            // We don't want to dispatch button up neither (Match .Net)
            // Thus we have to remove capture by ourselves
            RemoveCapture (msg.hwnd);
            continue;
          case Msg.WM_MOUSEMOVE:
            motion_poll = 0;

            drag_data.CurMousePos.X = Control.LowOrder ((int) msg.lParam.ToInt32 ());
            drag_data.CurMousePos.Y = Control.HighOrder ((int) msg.lParam.ToInt32 ());

            HandleMouseOver ();
            // We don't want to dispatch mouse move
            continue;
          }

          XplatUI.DispatchMessage (ref msg);
        }
      }

      timer.Stop ();

      // If the target is a mwf control, return until DragEnter/DragLeave has been fired,
      // which means the respective -already sent- dnd ClientMessages have been received and handled.
      if (control != null)
        Application.DoEvents ();

      if (!dropped)
        return DragDropEffects.None;
      if (drag_event != null)
        return drag_event.Effect;

      // Fallback.
      return DragDropEffects.None;
    }

    private void DndTickHandler (object sender, EventArgs e)
    {
      // This is to make sure we don't get stuck in a loop if another
      // app doesn't finish the DND operation
      if (dropped) {
        Timer t = (Timer) sender;
        if (t.Interval == 500)
          tracking = false;
        else
          t.Interval = 500;
      }


      // If motion_poll is -1, there hasn't been motion at all, so don't simulate motion yet.
      // Otherwise if more than 100 milliseconds have lapsed, we assume the pointer is not
      // in motion anymore, and we simulate the mouse over operation, like .Net does.
      if (motion_poll > 1)
        HandleMouseOver ();
      else if (motion_poll > -1)
        motion_poll++;
    }

    // This routines helps us to have a DndEnter/DndLeave fallback when there wasn't any mouse movement
    // as .Net does
    private void DefaultEnterLeave (object user_data)
    {
      IntPtr toplevel, window;
      int x_root, y_root;

      // The window generating the operation could be a different than the one under pointer
      GetWindowsUnderPointer (out window, out toplevel, out x_root, out y_root);
      Control source_control = Control.FromHandle (window);
      if (source_control == null || !source_control.AllowDrop)
        return;

      // `data' and other members are already available
      Point pos = Control.MousePosition;
      DragEventArgs drag_args = new DragEventArgs (data, 0, pos.X, pos.Y, drag_data.AllowedEffects, DragDropEffects.None);

      source_control.DndEnter (drag_args);
      if ((drag_args.Effect & drag_data.AllowedEffects) != 0)
        source_control.DndDrop (drag_args);
      else
        source_control.DndLeave (EventArgs.Empty);
    }

    public void HandleButtonUpMsg ()
    {
      if (drag_data.State == DragState.Beginning) {
        //state = State.Accepting;
      } else if (drag_data.State != DragState.None) {

        if (drag_data.WillAccept) {

          if (QueryContinue (false, DragAction.Drop))
            return;          
        } else {

          if (QueryContinue (false, DragAction.Cancel))
            return;

          // fallback if no movement was detected, as .net does.
          if (motion_poll == -1)
            DefaultEnterLeave (drag_data.Data);
        }

        drag_data.State = DragState.None;
        // WE can't reset the drag data yet as it is still
        // most likely going to be used by the SelectionRequest
        // handlers
      }

      return;
    }

    private void RemoveCapture (IntPtr handle)
    {
      Control c = MwfWindow (handle);
      if (c.InternalCapture)
        c.InternalCapture = false;
    }

    public bool HandleMouseOver ()
    {
      IntPtr toplevel, window;
      int x_root, y_root;

      GetWindowsUnderPointer (out window, out toplevel, out x_root, out y_root);

      if (window != drag_data.LastWindow && drag_data.State == DragState.Entered) {
        drag_data.State = DragState.Dragging;

        // TODO: Send a Leave if this is an MWF window

        if (toplevel != drag_data.LastTopLevel)
          SendLeave (drag_data.LastTopLevel, toplevel);
      }

      drag_data.State = DragState.Entered;
      if (toplevel != drag_data.LastTopLevel) {
        // Entering a new toplevel window
        SendEnter (toplevel, drag_data.Window, drag_data.SupportedTypes);
      } else {
        // Already in a toplevel window, so send a position
        SendPosition (toplevel, drag_data.Window,
            drag_data.Action,
            x_root,  y_root,
            IntPtr.Zero);
      }

      drag_data.LastTopLevel = toplevel;
      drag_data.LastWindow = window;
      return true;
    }

    void GetWindowsUnderPointer (out IntPtr window, out IntPtr toplevel, out int x_root, out int y_root)
    {
      toplevel = IntPtr.Zero;
      window = XplatUIX11.RootWindowHandle;

      IntPtr root, child;
      bool dnd_aware = false;
      int x_temp, y_temp;
      int mask_return;
      int x = x_root = drag_data.CurMousePos.X;
      int y = y_root = drag_data.CurMousePos.Y;

      while (XplatUIX11.XQueryPointer (display, window, out root, out child,
                 out x_temp, out y_temp, out x, out y, out mask_return)) {
          
        if (!dnd_aware) {
          dnd_aware = IsWindowDndAware (window);
          if (dnd_aware) {
            toplevel = window;
            x_root = x_temp;
            y_root = y_temp;
          }
        }

        if (child == IntPtr.Zero)
          break;
          
        window = child;
      }
    }

    public void HandleKeyMessage (MSG msg)
    {
      if (VirtualKeys.VK_ESCAPE == (VirtualKeys) msg.wParam.ToInt32()) {
        QueryContinue (true, DragAction.Cancel);
      }
    }
    
    // return true if the event is handled here
    public bool HandleClientMessage (ref XEvent xevent)
    {
      // most common so we check it first
      if (xevent.ClientMessageEvent.message_type == XdndPosition)
        return Accepting_HandlePositionEvent (ref xevent);
      if (xevent.ClientMessageEvent.message_type == XdndEnter)
        return Accepting_HandleEnterEvent (ref xevent);
      if (xevent.ClientMessageEvent.message_type == XdndDrop)
        return Accepting_HandleDropEvent (ref xevent);
      if (xevent.ClientMessageEvent.message_type == XdndLeave)
        return Accepting_HandleLeaveEvent (ref xevent);
      if (xevent.ClientMessageEvent.message_type == XdndStatus)
        return HandleStatusEvent (ref xevent);
      if (xevent.ClientMessageEvent.message_type == XdndFinished)
        return HandleFinishedEvent (ref xevent);

      return false;
    }

    public bool HandleSelectionNotifyEvent (ref XEvent xevent)
    {
      MimeHandler handler = FindHandler ((IntPtr) xevent.SelectionEvent.target);
      if (handler == null)
        return false;
      if (data == null)
        data = new DataObject ();

      handler.Converter.GetData (this, data, ref xevent);

      converts_pending--;
      if (converts_pending <= 0 && position_recieved) {
        drag_event = new DragEventArgs (data, 0, pos_x, pos_y,
          allowed, DragDropEffects.None);
        control.DndEnter (drag_event);
        SendStatus (source, drag_event.Effect);
        status_sent = true;
      }
      return true;
    }

    public bool HandleSelectionRequestEvent (ref XEvent xevent)
    {
      if (xevent.SelectionRequestEvent.selection != XdndSelection)
        return false;

      MimeHandler handler = FindHandler (xevent.SelectionRequestEvent.target);
      if (handler == null)
        return false;

      handler.Converter.SetData (this, drag_data.Data, ref xevent);

      return true;
    }

    private bool QueryContinue (bool escape, DragAction action)
    {
      QueryContinueDragEventArgs qce = new QueryContinueDragEventArgs ((int) XplatUI.State.ModifierKeys,
          escape, action);

      Control c = MwfWindow (source);
      
      if (c == null) {
        tracking = false;
        return false;
      }
      
      c.DndContinueDrag (qce);

      switch (qce.Action) {
      case DragAction.Continue:
        return true;
      case DragAction.Drop:
        SendDrop (drag_data.LastTopLevel, source, IntPtr.Zero);
        tracking = false;
        return true;
      case DragAction.Cancel:
        drag_data.Reset ();
        c.InternalCapture = false;
        break;
      }

      SendLeave (drag_data.LastTopLevel, toplevel);

      RestoreDefaultCursor ();
      tracking = false;
      return false;
    }

    private void RestoreDefaultCursor ()
    {
      // Releasing the mouse buttons should automatically restore the default cursor,
      // but canceling the operation using QueryContinue should restore it even if the
      // mouse buttons are not released yet.
      XplatUIX11.XChangeActivePointerGrab (display,
          EventMask.ButtonMotionMask |
          EventMask.PointerMotionMask |
          EventMask.ButtonPressMask |
          EventMask.ButtonReleaseMask,
          Cursors.Default.Handle, IntPtr.Zero);

    }

    private void GiveFeedback (IntPtr action)
    {
      GiveFeedbackEventArgs gfe = new GiveFeedbackEventArgs (EffectFromAction (drag_data.Action), true);

      Control c = MwfWindow (source);
      c.DndFeedback (gfe);

      if (gfe.UseDefaultCursors) {
        Cursor cursor = CursorNo;
        if (drag_data.WillAccept) {
          // Same order as on MS
          if (action == XdndActionCopy)
            cursor = CursorCopy;
          else if (action == XdndActionLink)
            cursor = CursorLink;
          else if (action == XdndActionMove)
            cursor = CursorMove;
        }
        // TODO: Try not to set the cursor so much
        //if (cursor.Handle != CurrentCursorHandle) {
        XplatUIX11.XChangeActivePointerGrab (display,
            EventMask.ButtonMotionMask |
            EventMask.PointerMotionMask |
            EventMask.ButtonPressMask |
            EventMask.ButtonReleaseMask,
            cursor.Handle, IntPtr.Zero);
        //CurrentCursorHandle = cursor.Handle;
        //}
      }
    }

    private void SetProperty (ref XEvent xevent, IntPtr data, int length)
    {
      XEvent sel = new XEvent();
      sel.SelectionEvent.type = XEventName.SelectionNotify;
      sel.SelectionEvent.send_event = true;
      sel.SelectionEvent.display = display;
      sel.SelectionEvent.selection = xevent.SelectionRequestEvent.selection;
      sel.SelectionEvent.target = xevent.SelectionRequestEvent.target;
      sel.SelectionEvent.requestor = xevent.SelectionRequestEvent.requestor;
      sel.SelectionEvent.time = xevent.SelectionRequestEvent.time;
      sel.SelectionEvent.property = IntPtr.Zero;

      XplatUIX11.XChangeProperty (display, xevent.SelectionRequestEvent.requestor,
          xevent.SelectionRequestEvent.property,
          xevent.SelectionRequestEvent.target,
          8, PropertyMode.Replace, data, length);
      sel.SelectionEvent.property = xevent.SelectionRequestEvent.property;

      XplatUIX11.XSendEvent (display, xevent.SelectionRequestEvent.requestor, false,
          (IntPtr)EventMask.NoEventMask, ref sel);
      return;
    }

    private void Reset ()
    {
      ResetSourceData ();
      ResetTargetData ();
    }

    private void ResetSourceData ()
    {
      converts_pending = 0;
      data = null;
    }

    private void ResetTargetData ()
    {
      position_recieved = false;
      status_sent = false;
    }
    
    private bool Accepting_HandleEnterEvent (ref XEvent xevent)
    {
      Reset ();

      source = xevent.ClientMessageEvent.ptr1;
      toplevel = xevent.AnyEvent.window;
      target = IntPtr.Zero;

      ConvertData (ref xevent);

      return true;
    }

    private bool Accepting_HandlePositionEvent (ref XEvent xevent)
    {
      pos_x = (int) xevent.ClientMessageEvent.ptr3 >> 16;
      pos_y = (int) xevent.ClientMessageEvent.ptr3 & 0xFFFF;

      // Copy is implicitly allowed
      Control source_control = MwfWindow (source);
      if (source_control == null)
        allowed = EffectsFromX11Source (source, xevent.ClientMessageEvent.ptr5) | DragDropEffects.Copy;
      else
        allowed = drag_data.AllowedEffects;

      IntPtr parent, child, new_child, last_drop_child;
      parent = XplatUIX11.XRootWindow (display, 0);
      child = toplevel;
      last_drop_child = IntPtr.Zero;
      while (true) {
        int xd, yd;
        new_child = IntPtr.Zero;
        
        if (!XplatUIX11.XTranslateCoordinates (display,
                parent, child, pos_x, pos_y,
                out xd, out yd, out new_child))
          break;
        if (new_child == IntPtr.Zero)
          break;
        child = new_child;

        Hwnd h = Hwnd.ObjectFromHandle (child);
        if (h != null) {
          Control d = Control.FromHandle (h.client_window);
          if (d != null && d.allow_drop)
            last_drop_child = child;
        }
      }

      if (last_drop_child != IntPtr.Zero)
        child = last_drop_child;

      if (target != child) {
        // We have moved into a new control 
        // or into a control for the first time
        Finish ();
      }
      target = child;
      Hwnd hwnd = Hwnd.ObjectFromHandle (target);
      if (hwnd == null)
        return true;

      Control c = Control.FromHandle (hwnd.client_window);

      if (c == null)
        return true;
      if (!c.allow_drop) {
        SendStatus (source, DragDropEffects.None);
        Finish ();
        return true;
      }

      control = c;
      position_recieved = true;      

      if (converts_pending > 0)
        return true;

      if (!status_sent) {
        drag_event = new DragEventArgs (data, 0, pos_x, pos_y,
          allowed, DragDropEffects.None);
        control.DndEnter (drag_event);
        
        SendStatus (source, drag_event.Effect);
        status_sent = true;
      } else {
        drag_event.x = pos_x;
        drag_event.y = pos_y;
        control.DndOver (drag_event);

        SendStatus (source, drag_event.Effect);
      }
      
      return true;
    }

    private void Finish ()
    {
      if (control != null) {
        if (drag_event == null) {
          if (data == null)
            data = new DataObject ();
          drag_event = new DragEventArgs (data,
              0, pos_x, pos_y,
          allowed, DragDropEffects.None);
        }
        control.DndLeave (drag_event);
        control = null;
      }
      ResetTargetData ();
    }

    private bool Accepting_HandleDropEvent (ref XEvent xevent)
    {
      if (control != null && drag_event != null) {
        drag_event = new DragEventArgs (data,
            0, pos_x, pos_y,
          allowed, drag_event.Effect);
        control.DndDrop (drag_event);
      }
      SendFinished ();
      return true;
    }

    private bool Accepting_HandleLeaveEvent (ref XEvent xevent)
    {
      if (control != null && drag_event != null)
        control.DndLeave (drag_event);
      // Reset ();
      return true;
    }

    private bool HandleStatusEvent (ref XEvent xevent)
    {
      if (drag_data != null && drag_data.State == DragState.Entered) {

        if (!QueryContinue (false, DragAction.Continue))
          return true;

        drag_data.WillAccept = ((int) xevent.ClientMessageEvent.ptr2 & 0x1) != 0;
        
        GiveFeedback (xevent.ClientMessageEvent.ptr5);
      }
      return true;
    }

    private bool HandleFinishedEvent (ref XEvent xevent)
    {
      return true;
    }

    private DragDropEffects EffectsFromX11Source (IntPtr source, IntPtr action_atom)
    {
      DragDropEffects allowed = DragDropEffects.None;
      IntPtr type, count, remaining, data = IntPtr.Zero;
      int format;

      XplatUIX11.XGetWindowProperty (display, source, XdndActionList,
          IntPtr.Zero, new IntPtr (32), false, (IntPtr) Atom.AnyPropertyType,
          out type, out format, out count, out remaining, ref data);

      int intptr_size = Marshal.SizeOf (typeof (IntPtr));
      for (int i = 0; i < count.ToInt32 (); i++) {
        IntPtr current_atom = Marshal.ReadIntPtr (data, i * intptr_size);
        allowed |= EffectFromAction (current_atom);
      }

      // if source is not providing the action list, use the
      // default action passed in the x11 dnd position message
      if (allowed == DragDropEffects.None)
        allowed = EffectFromAction (action_atom);

      return allowed;
    }

    private DragDropEffects EffectFromAction (IntPtr action)
    {
      if (action == XdndActionCopy)
        return DragDropEffects.Copy;
      else if (action == XdndActionMove)
        return DragDropEffects.Move;
      if (action == XdndActionLink)
        return DragDropEffects.Link;

      return DragDropEffects.None;
    }

    private IntPtr ActionFromEffect (DragDropEffects effect)
    {
      IntPtr action = IntPtr.Zero;

      // We can't OR together actions on XDND so sadly the primary
      // is the only one shown here
      if ((effect & DragDropEffects.Copy) != 0)
        action = XdndActionCopy;
      else if ((effect & DragDropEffects.Move) != 0)
        action = XdndActionMove;
      else if ((effect & DragDropEffects.Link) != 0)
        action = XdndActionLink;
      return action;
    }

    private bool ConvertData (ref XEvent xevent)
    {
      bool match = false;

      Control mwfcontrol = MwfWindow (source);

      /* To take advantage of the mwfcontrol, we have to be sure
         that the dnd operation is still happening (since messages are asynchronous) */
      if (mwfcontrol != null && drag_data != null) {
        if (!tracking)
          return false;

        IDataObject dragged = drag_data.Data as IDataObject;
        if (dragged != null) {
          data = dragged;
        } else {
          if (data == null)
            data = new DataObject ();
          SetDataWithFormats (drag_data.Data);
        }
        return true;
      }

      foreach (IntPtr atom in SourceSupportedList (ref xevent)) {
        MimeHandler handler = FindHandler (atom);
        if (handler == null)
          continue;
        XplatUIX11.XConvertSelection (display, XdndSelection, handler.Type,
          handler.NonProtocol, toplevel, IntPtr.Zero /* CurrentTime */);
        converts_pending++;
        match = true;
      }
      return match;
    }

    private void SetDataWithFormats (object value)
    {
      if (value is string) {
        data.SetData (DataFormats.Text, value);
        data.SetData (DataFormats.UnicodeText, value);
      }

      data.SetData (value);
    }

    private MimeHandler FindHandler (IntPtr atom)
    {
      if (atom == IntPtr.Zero)
        return null;
      foreach (MimeHandler handler in MimeHandlers) {
        if (handler.Type == atom)
          return handler;
      }
      return null;
    }

    private MimeHandler FindHandler (string name)
    {
      foreach (MimeHandler handler in MimeHandlers) {
        foreach (string alias in handler.Aliases) {
          if (alias == name)
            return handler;
        }
      }
      return null;
    }

    private void SendStatus (IntPtr source, DragDropEffects effect)
    {
      XEvent xevent = new XEvent ();

      xevent.AnyEvent.type = XEventName.ClientMessage;
      xevent.AnyEvent.display = display;
      xevent.ClientMessageEvent.window = source;
      xevent.ClientMessageEvent.message_type = XdndStatus;
      xevent.ClientMessageEvent.format = 32;
      xevent.ClientMessageEvent.ptr1 = toplevel;
      if (effect != DragDropEffects.None && (effect & allowed) != 0)
        xevent.ClientMessageEvent.ptr2 = (IntPtr) 1;

      xevent.ClientMessageEvent.ptr5 = ActionFromEffect (effect);
      XplatUIX11.XSendEvent (display, source, false, IntPtr.Zero, ref xevent);
    }

    private void SendEnter (IntPtr handle, IntPtr from, IntPtr [] supported)
    {
      XEvent xevent = new XEvent ();

      xevent.AnyEvent.type = XEventName.ClientMessage;
      xevent.AnyEvent.display = display;
      xevent.ClientMessageEvent.window = handle;
      xevent.ClientMessageEvent.message_type = XdndEnter;
      xevent.ClientMessageEvent.format = 32;
      xevent.ClientMessageEvent.ptr1 = from;

      // (int) xevent.ClientMessageEvent.ptr2 & 0x1)
      // int ptr2 = 0x1;
      // xevent.ClientMessageEvent.ptr2 = (IntPtr) ptr2;
      // (e)->xclient.data.l[1] = ((e)->xclient.data.l[1] & ~(0xFF << 24)) | ((v) << 24)
      xevent.ClientMessageEvent.ptr2 = (IntPtr) ((long)XdndVersion [0] << 24);
      
      if (supported.Length > 0)
        xevent.ClientMessageEvent.ptr3 = supported [0];
      if (supported.Length > 1)
        xevent.ClientMessageEvent.ptr4 = supported [1];
      if (supported.Length > 2)
        xevent.ClientMessageEvent.ptr5 = supported [2];

      XplatUIX11.XSendEvent (display, handle, false, IntPtr.Zero, ref xevent);
    }

    private void SendDrop (IntPtr handle, IntPtr from, IntPtr time)
    {
      XEvent xevent = new XEvent ();

      xevent.AnyEvent.type = XEventName.ClientMessage;
      xevent.AnyEvent.display = display;
      xevent.ClientMessageEvent.window = handle;
      xevent.ClientMessageEvent.message_type = XdndDrop;
      xevent.ClientMessageEvent.format = 32;
      xevent.ClientMessageEvent.ptr1 = from;
      xevent.ClientMessageEvent.ptr3 = time;
      
      XplatUIX11.XSendEvent (display, handle, false, IntPtr.Zero, ref xevent);
      dropped = true;
    }

    private void SendPosition (IntPtr handle, IntPtr from, IntPtr action, int x, int y, IntPtr time)
    {
      XEvent xevent = new XEvent ();

      xevent.AnyEvent.type = XEventName.ClientMessage;
      xevent.AnyEvent.display = display;
      xevent.ClientMessageEvent.window = handle;
      xevent.ClientMessageEvent.message_type = XdndPosition;
      xevent.ClientMessageEvent.format = 32;
      xevent.ClientMessageEvent.ptr1 = from;
      xevent.ClientMessageEvent.ptr3 = (IntPtr) ((x << 16) | (y & 0xFFFF));
      xevent.ClientMessageEvent.ptr4 = time;
      xevent.ClientMessageEvent.ptr5 = action;
      
      XplatUIX11.XSendEvent (display, handle, false, IntPtr.Zero, ref xevent);
    }

    private void SendLeave (IntPtr handle, IntPtr from)
    {
      XEvent xevent = new XEvent ();

      xevent.AnyEvent.type = XEventName.ClientMessage;
      xevent.AnyEvent.display = display;
      xevent.ClientMessageEvent.window = handle;
      xevent.ClientMessageEvent.message_type = XdndLeave;
      xevent.ClientMessageEvent.format = 32;
      xevent.ClientMessageEvent.ptr1 = from;

      XplatUIX11.XSendEvent (display, handle, false, IntPtr.Zero, ref xevent);
    }

    private void SendFinished ()
    {
      XEvent xevent = new XEvent ();

      xevent.AnyEvent.type = XEventName.ClientMessage;
      xevent.AnyEvent.display = display;
      xevent.ClientMessageEvent.window = source;
      xevent.ClientMessageEvent.message_type = XdndFinished;
      xevent.ClientMessageEvent.format = 32;
      xevent.ClientMessageEvent.ptr1 = toplevel;

      XplatUIX11.XSendEvent (display, source, false, IntPtr.Zero, ref xevent);
    }

    // There is a somewhat decent amount of overhead
    // involved in setting up dnd so we do it lazily
    // as a lot of applications do not even use it.
    private void Init ()
    {
      XdndAware = XplatUIX11.XInternAtom (display, "XdndAware", false);
      XdndEnter = XplatUIX11.XInternAtom (display, "XdndEnter", false);
      XdndLeave = XplatUIX11.XInternAtom (display, "XdndLeave", false);
      XdndPosition = XplatUIX11.XInternAtom (display, "XdndPosition", false);
      XdndStatus = XplatUIX11.XInternAtom (display, "XdndStatus", false);
      XdndDrop = XplatUIX11.XInternAtom (display, "XdndDrop", false);
      XdndSelection = XplatUIX11.XInternAtom (display, "XdndSelection", false);
      XdndFinished = XplatUIX11.XInternAtom (display, "XdndFinished", false);
      XdndTypeList = XplatUIX11.XInternAtom (display, "XdndTypeList", false);
      XdndActionCopy = XplatUIX11.XInternAtom (display, "XdndActionCopy", false);
      XdndActionMove = XplatUIX11.XInternAtom (display, "XdndActionMove", false);
      XdndActionLink = XplatUIX11.XInternAtom (display, "XdndActionLink", false);
      //XdndActionPrivate = XplatUIX11.XInternAtom (display, "XdndActionPrivate", false);
      XdndActionList = XplatUIX11.XInternAtom (display, "XdndActionList", false);
      //XdndActionDescription = XplatUIX11.XInternAtom (display, "XdndActionDescription", false);
      //XdndActionAsk = XplatUIX11.XInternAtom (display, "XdndActionAsk", false);

      foreach (MimeHandler handler in MimeHandlers) {
        handler.Type = XplatUIX11.XInternAtom (display, handler.Name, false);
        handler.NonProtocol = XplatUIX11.XInternAtom (display,
            String.Concat ("MWFNonP+", handler.Name), false);
      }

    }

    private IntPtr [] SourceSupportedList (ref XEvent xevent)
    {
      IntPtr [] res;

      
      if (((int) xevent.ClientMessageEvent.ptr2 & 0x1) == 0) {
        res = new IntPtr [3];
        res [0] = xevent.ClientMessageEvent.ptr3;
        res [1] = xevent.ClientMessageEvent.ptr4;
        res [2] = xevent.ClientMessageEvent.ptr5;
      } else {
        IntPtr type;
        int format;
        IntPtr count;
        IntPtr remaining;
        IntPtr data = IntPtr.Zero;

        XplatUIX11.XGetWindowProperty (display, source, XdndTypeList,
            IntPtr.Zero, new IntPtr(32), false, (IntPtr) Atom.XA_ATOM,
            out type, out format, out count,
            out remaining, ref data);

        res = new IntPtr [count.ToInt32()];
        for (int i = 0; i < count.ToInt32(); i++) {
          res [i] = (IntPtr) Marshal.ReadInt32 (data, i *
              Marshal.SizeOf (typeof (int)));
        }

        XplatUIX11.XFree (data);
      }

      return res;
    }

    private string GetText (ref XEvent xevent, bool unicode)
    {
      int nread = 0;
      IntPtr nitems;
      IntPtr bytes_after;

      StringBuilder builder = new StringBuilder ();
      do {
        IntPtr actual_type;
        int actual_fmt;
        IntPtr data = IntPtr.Zero;

        if (0 != XplatUIX11.XGetWindowProperty (display,
                xevent.AnyEvent.window,
                (IntPtr) xevent.SelectionEvent.property,
                IntPtr.Zero, new IntPtr(0xffffff), false,
                (IntPtr) Atom.AnyPropertyType, out actual_type,
                out actual_fmt, out nitems, out bytes_after,
                ref data)) {
          XplatUIX11.XFree (data);
          break;
        }

        if (unicode)
          builder.Append (Marshal.PtrToStringUni (data));
        else
          builder.Append (Marshal.PtrToStringAnsi (data));
        nread += nitems.ToInt32();

        XplatUIX11.XFree (data);
      } while (bytes_after.ToInt32() > 0);
      if (nread == 0)
        return null;
      return builder.ToString ();
    }

    private MemoryStream GetData (ref XEvent xevent)
    {
      int nread = 0;
      IntPtr nitems;
      IntPtr bytes_after;

      MemoryStream res = new MemoryStream ();
      do {
        IntPtr actual_type;
        int actual_fmt;
        IntPtr data = IntPtr.Zero;

        if (0 != XplatUIX11.XGetWindowProperty (display,
                xevent.AnyEvent.window,
                (IntPtr) xevent.SelectionEvent.property,
                IntPtr.Zero, new IntPtr(0xffffff), false,
                (IntPtr) Atom.AnyPropertyType, out actual_type,
                out actual_fmt, out nitems, out bytes_after,
                ref data)) {
          XplatUIX11.XFree (data);
          break;
        }

        for (int i = 0; i < nitems.ToInt32(); i++)
          res.WriteByte (Marshal.ReadByte (data, i));
        nread += nitems.ToInt32();

        XplatUIX11.XFree (data);
      } while (bytes_after.ToInt32() > 0);
      return res;
    }

    private Control MwfWindow (IntPtr window)
    {
      Hwnd hwnd = Hwnd.ObjectFromHandle (window);
      if (hwnd == null)
        return null;

      Control res = Control.FromHandle (hwnd.client_window);
      
      if (res == null)
        res = Control.FromHandle (window);
        
      return res;
    }

    private bool IsWindowDndAware (IntPtr handle)
    {
      bool res = true;
      // Check the version number, we need greater than 3
      IntPtr actual;
      int format;
      IntPtr count;
      IntPtr remaining;
      IntPtr data = IntPtr.Zero;
      
      XplatUIX11.XGetWindowProperty (display, handle, XdndAware, IntPtr.Zero, new IntPtr(0x8000000), false,
          (IntPtr) Atom.XA_ATOM, out actual, out format,
          out count, out remaining, ref data);
      
      if (actual != (IntPtr) Atom.XA_ATOM || format != 32 ||
          count.ToInt32() == 0 || data == IntPtr.Zero) {
        if (data != IntPtr.Zero)
          XplatUIX11.XFree (data);
        return false;
      }

      int version = Marshal.ReadInt32 (data, 0);

      if (version < 3) {
        Console.Error.WriteLine ("XDND Version too old (" + version + ").");
        XplatUIX11.XFree (data);
        return false;
      }

      // First type is actually the XDND version
      if (count.ToInt32() > 1) {
        res = false;
        for (int i = 1; i < count.ToInt32(); i++) {
          IntPtr type = (IntPtr) Marshal.ReadInt32 (data, i *
              Marshal.SizeOf (typeof (int)));
          for (int j = 0; j < drag_data.SupportedTypes.Length; j++) {
            if (drag_data.SupportedTypes [j] == type) {
              res = true;
              break;
            }
          }
        }
      }

      XplatUIX11.XFree (data);
      return res;
    }

    private IntPtr [] DetermineSupportedTypes (object data)
    {
      ArrayList res = new ArrayList ();

      if (data is string) {
        MimeHandler handler = FindHandler ("text/plain");
        if (handler != null)
          res.Add (handler.Type);
      }/* else if (data is Bitmap)
        res.Add (data);

       */

      IDataObject data_object = data as IDataObject;
      if (data_object != null) {
        foreach (string format in data_object.GetFormats (true)) {
          MimeHandler handler = FindHandler (format);
          if (handler != null && !res.Contains (handler.Type))
            res.Add (handler.Type);
        }
      }

      if (data is ISerializable) {
        MimeHandler handler = FindHandler ("application/x-mono-serialized-object");
        if (handler != null)
          res.Add (handler.Type);
      }

      return (IntPtr []) res.ToArray (typeof (IntPtr));
    }
  }
}
www.java2v.com | Contact Us
Copyright 2009 - 12 Demo Source and Support. All rights reserved.
All other trademarks are property of their respective owners.