1 // Permission is hereby granted, free of charge, to any person obtaining
2 // a copy of this software and associated documentation files (the
3 // "Software"), to deal in the Software without restriction, including
4 // without limitation the rights to use, copy, modify, merge, publish,
5 // distribute, sublicense, and/or sell copies of the Software, and to
6 // permit persons to whom the Software is furnished to do so, subject to
7 // the following conditions:
9 // The above copyright notice and this permission notice shall be
10 // included in all copies or substantial portions of the Software.
12 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
13 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
14 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
15 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
16 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
17 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
18 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
20 // Copyright (c) 2005 Novell, Inc.
23 // Jackson Harper (jackson@ximian.com)
25 // NOTE: We have some tests in Test/System.Windows.Forms/DragAndDropTest.cs, which I *highly* recommend
26 // to run after any change made here, since those tests are interactive, and thus are not part of
35 using System.Threading;
36 using System.Collections;
37 using System.Runtime.Serialization;
38 using System.Runtime.InteropServices;
39 using System.Runtime.Serialization.Formatters.Binary;
41 namespace System.Windows.Forms {
43 internal class X11Dnd {
50 private enum DragState {
57 private interface IDataConverter {
58 void GetData (X11Dnd dnd, IDataObject data, ref XEvent xevent);
59 void SetData (X11Dnd dnd, object data, ref XEvent xevent);
62 private delegate void MimeConverter (IntPtr dsp,
63 IDataObject data, ref XEvent xevent);
65 private class MimeHandler {
67 public string [] Aliases;
69 public IntPtr NonProtocol;
70 public IDataConverter Converter;
72 public MimeHandler (string name, IDataConverter converter) : this (name, converter, name)
76 public MimeHandler (string name, IDataConverter converter, params string [] aliases)
79 Converter = converter;
83 public override string ToString ()
85 return "MimeHandler {" + Name + "}";
89 private MimeHandler [] MimeHandlers = {
90 // new MimeHandler ("WCF_DIB"),
91 // new MimeHandler ("image/gif", new MimeConverter (ImageConverter)),
92 // new MimeHandler ("text/rtf", new MimeConverter (RtfConverter)),
93 // new MimeHandler ("text/richtext", new MimeConverter (RtfConverter)),
95 new MimeHandler ("text/plain", new TextConverter ()),
96 new MimeHandler ("text/plain", new TextConverter (), "System.String", DataFormats.Text),
97 new MimeHandler ("text/html", new HtmlConverter (), DataFormats.Html),
98 new MimeHandler ("text/uri-list", new UriListConverter (), DataFormats.FileDrop),
99 new MimeHandler ("application/x-mono-serialized-object",
100 new SerializedObjectConverter ())
103 private class SerializedObjectConverter : IDataConverter {
105 public void GetData (X11Dnd dnd, IDataObject data, ref XEvent xevent)
107 MemoryStream stream = dnd.GetData (ref xevent);
108 BinaryFormatter bf = new BinaryFormatter ();
110 if (stream.Length == 0)
114 object obj = bf.Deserialize (stream);
118 public void SetData (X11Dnd dnd, object data, ref XEvent xevent)
123 MemoryStream stream = new MemoryStream ();
124 BinaryFormatter bf = new BinaryFormatter ();
126 bf.Serialize (stream, data);
128 IntPtr buffer = Marshal.AllocHGlobal ((int) stream.Length);
131 for (int i = 0; i < stream.Length; i++) {
132 Marshal.WriteByte (buffer, i, (byte) stream.ReadByte ());
135 dnd.SetProperty (ref xevent, buffer, (int) stream.Length);
139 private class HtmlConverter : IDataConverter {
141 public void GetData (X11Dnd dnd, IDataObject data, ref XEvent xevent)
143 string text = dnd.GetText (ref xevent, false);
146 data.SetData (DataFormats.Text, text);
147 data.SetData (DataFormats.UnicodeText, text);
150 public void SetData (X11Dnd dnd, object data, ref XEvent xevent)
154 string str = data as string;
159 if (xevent.SelectionRequestEvent.target == (IntPtr)Atom.XA_STRING) {
160 byte [] bytes = Encoding.ASCII.GetBytes (str);
161 buffer = Marshal.AllocHGlobal (bytes.Length);
163 for (int i = 0; i < len; i++)
164 Marshal.WriteByte (buffer, i, bytes [i]);
166 buffer = Marshal.StringToHGlobalAnsi (str);
168 while (Marshal.ReadByte (buffer, len) != 0)
172 dnd.SetProperty (ref xevent, buffer, len);
174 Marshal.FreeHGlobal (buffer);
178 private class TextConverter : IDataConverter {
180 public void GetData (X11Dnd dnd, IDataObject data, ref XEvent xevent)
182 string text = dnd.GetText (ref xevent, true);
185 data.SetData (DataFormats.Text, text);
186 data.SetData (DataFormats.UnicodeText, text);
189 public void SetData (X11Dnd dnd, object data, ref XEvent xevent)
193 string str = data as string;
196 IDataObject dobj = data as IDataObject;
199 str = (string) dobj.GetData ("System.String", true);
202 if (xevent.SelectionRequestEvent.target == (IntPtr)Atom.XA_STRING) {
203 byte [] bytes = Encoding.ASCII.GetBytes (str);
204 buffer = Marshal.AllocHGlobal (bytes.Length);
206 for (int i = 0; i < len; i++)
207 Marshal.WriteByte (buffer, i, bytes [i]);
209 buffer = Marshal.StringToHGlobalAnsi (str);
211 while (Marshal.ReadByte (buffer, len) != 0)
215 dnd.SetProperty (ref xevent, buffer, len);
217 Marshal.FreeHGlobal (buffer);
221 private class UriListConverter : IDataConverter {
223 public void GetData (X11Dnd dnd, IDataObject data, ref XEvent xevent)
225 string text = dnd.GetText (ref xevent, false);
229 // TODO: Do this in a loop instead of just splitting
230 ArrayList uri_list = new ArrayList ();
231 string [] lines = text.Split (new char [] { '\r', '\n' });
232 foreach (string line in lines) {
233 // # is a comment line (see RFC 2483)
234 if (line.StartsWith ("#"))
237 Uri uri = new Uri (line);
238 uri_list.Add (uri.LocalPath);
242 string [] l = (string []) uri_list.ToArray (typeof (string));
245 data.SetData (DataFormats.FileDrop, l);
246 data.SetData ("FileName", l [0]);
247 data.SetData ("FileNameW", l [0]);
250 public void SetData (X11Dnd dnd, object data, ref XEvent xevent)
252 string [] uri_list = data as string [];
254 if (uri_list == null) {
255 IDataObject dobj = data as IDataObject;
258 uri_list = dobj.GetData (DataFormats.FileDrop, true) as string [];
261 if (uri_list == null)
264 StringBuilder res = new StringBuilder ();
265 foreach (string uri_str in uri_list) {
266 Uri uri = new Uri (uri_str);
267 res.Append (uri.ToString ());
271 IntPtr buffer = Marshal.StringToHGlobalAnsi ((string) res.ToString ());
273 while (Marshal.ReadByte (buffer, len) != 0)
276 dnd.SetProperty (ref xevent, buffer, len);
280 private class DragData {
281 public IntPtr Window;
282 public DragState State;
284 public IntPtr Action;
285 public IntPtr [] SupportedTypes;
286 public MouseButtons MouseState;
287 public DragDropEffects AllowedEffects;
288 public Point CurMousePos;
290 public IntPtr LastWindow;
291 public IntPtr LastTopLevel;
293 public bool WillAccept;
297 State = DragState.None;
299 SupportedTypes = null;
304 // This version seems to be the most common
305 private static readonly IntPtr [] XdndVersion = new IntPtr [] { new IntPtr (4) };
307 private IntPtr display;
308 private DragData drag_data;
310 private IntPtr XdndAware;
311 private IntPtr XdndSelection;
312 private IntPtr XdndEnter;
313 private IntPtr XdndLeave;
314 private IntPtr XdndPosition;
315 private IntPtr XdndDrop;
316 private IntPtr XdndFinished;
317 private IntPtr XdndStatus;
318 private IntPtr XdndTypeList;
319 private IntPtr XdndActionCopy;
320 private IntPtr XdndActionMove;
321 private IntPtr XdndActionLink;
322 //private IntPtr XdndActionPrivate;
323 private IntPtr XdndActionList;
324 //private IntPtr XdndActionDescription;
325 //private IntPtr XdndActionAsk;
327 //private State state;
329 private int converts_pending;
330 private bool position_recieved;
331 private bool status_sent;
332 private IntPtr target;
333 private IntPtr source;
334 private IntPtr toplevel;
335 private IDataObject data;
337 private Control control;
338 private int pos_x, pos_y;
339 private DragDropEffects allowed;
340 private DragEventArgs drag_event;
342 private Cursor CursorNo;
343 private Cursor CursorCopy;
344 private Cursor CursorMove;
345 private Cursor CursorLink;
346 // check out the TODO below
347 //private IntPtr CurrentCursorHandle;
349 private bool tracking = false;
350 private bool dropped = false;
351 private int motion_poll;
352 //private X11Keyboard keyboard;
354 public X11Dnd (IntPtr display, X11Keyboard keyboard)
356 this.display = display;
357 //this.keyboard = keyboard;
364 if (drag_data == null)
366 return drag_data.State != DragState.None;
369 public void SetAllowDrop (Hwnd hwnd, bool allow)
373 if (hwnd.allow_drop == allow)
376 atoms = new int[XdndVersion.Length];
377 for (int i = 0; i < XdndVersion.Length; i++) {
378 atoms[i] = XdndVersion[i].ToInt32();
381 XplatUIX11.XChangeProperty (display, hwnd.whole_window, XdndAware,
382 (IntPtr) Atom.XA_ATOM, 32,
383 PropertyMode.Replace, atoms, allow ? 1 : 0);
384 hwnd.allow_drop = allow;
387 public DragDropEffects StartDrag (IntPtr handle, object data,
388 DragDropEffects allowed_effects)
390 drag_data = new DragData ();
391 drag_data.Window = handle;
392 drag_data.State = DragState.Beginning;
393 drag_data.MouseState = XplatUIX11.MouseState;
394 drag_data.Data = data;
395 drag_data.SupportedTypes = DetermineSupportedTypes (data);
396 drag_data.AllowedEffects = allowed_effects;
397 drag_data.Action = ActionFromEffect (allowed_effects);
399 if (CursorNo == null) {
400 // Make sure the cursors are created
401 CursorNo = new Cursor (typeof (X11Dnd), "DnDNo.cur");
402 CursorCopy = new Cursor (typeof (X11Dnd), "DnDCopy.cur");
403 CursorMove = new Cursor (typeof (X11Dnd), "DnDMove.cur");
404 CursorLink = new Cursor (typeof (X11Dnd), "DnDLink.cur");
407 drag_data.LastTopLevel = IntPtr.Zero;
410 System.Windows.Forms.MSG msg = new MSG();
411 object queue_id = XplatUI.StartLoop (Thread.CurrentThread);
413 Timer timer = new Timer ();
414 timer.Tick += new EventHandler (DndTickHandler);
415 timer.Interval = 100;
418 drag_data.State = DragState.Dragging;
420 suc = XplatUIX11.XSetSelectionOwner (display, XdndSelection,
421 drag_data.Window, IntPtr.Zero);
424 Console.Error.WriteLine ("Could not take ownership of XdndSelection aborting drag.");
426 return DragDropEffects.None;
429 drag_data.State = DragState.Dragging;
430 drag_data.CurMousePos = new Point ();
431 source = toplevel = target = IntPtr.Zero;
437 // Send Enter to the window initializing the dnd operation - which initializes the data
438 SendEnter (drag_data.Window, drag_data.Window, drag_data.SupportedTypes);
439 drag_data.LastTopLevel = toplevel;
441 while (tracking && XplatUI.GetMessage (queue_id, ref msg, IntPtr.Zero, 0, 0)) {
443 if (msg.message >= Msg.WM_KEYFIRST && msg.message <= Msg.WM_KEYLAST) {
444 HandleKeyMessage (msg);
446 switch (msg.message) {
447 case Msg.WM_LBUTTONUP:
448 case Msg.WM_RBUTTONUP:
449 case Msg.WM_MBUTTONUP:
450 if (msg.message == Msg.WM_LBUTTONDOWN && drag_data.MouseState != MouseButtons.Left)
452 if (msg.message == Msg.WM_RBUTTONDOWN && drag_data.MouseState != MouseButtons.Right)
454 if (msg.message == Msg.WM_MBUTTONDOWN && drag_data.MouseState != MouseButtons.Middle)
457 HandleButtonUpMsg ();
459 // We don't want to dispatch button up neither (Match .Net)
460 // Thus we have to remove capture by ourselves
461 RemoveCapture (msg.hwnd);
463 case Msg.WM_MOUSEMOVE:
466 drag_data.CurMousePos.X = Control.LowOrder ((int) msg.lParam.ToInt32 ());
467 drag_data.CurMousePos.Y = Control.HighOrder ((int) msg.lParam.ToInt32 ());
470 // We don't want to dispatch mouse move
474 XplatUI.DispatchMessage (ref msg);
480 // If the target is a mwf control, return until DragEnter/DragLeave has been fired,
481 // which means the respective -already sent- dnd ClientMessages have been received and handled.
483 Application.DoEvents ();
486 return DragDropEffects.None;
487 if (drag_event != null)
488 return drag_event.Effect;
491 return DragDropEffects.None;
494 private void DndTickHandler (object sender, EventArgs e)
496 // This is to make sure we don't get stuck in a loop if another
497 // app doesn't finish the DND operation
499 Timer t = (Timer) sender;
500 if (t.Interval == 500)
507 // If motion_poll is -1, there hasn't been motion at all, so don't simulate motion yet.
508 // Otherwise if more than 100 milliseconds have lapsed, we assume the pointer is not
509 // in motion anymore, and we simulate the mouse over operation, like .Net does.
512 else if (motion_poll > -1)
516 // This routines helps us to have a DndEnter/DndLeave fallback when there wasn't any mouse movement
518 private void DefaultEnterLeave (object user_data)
520 IntPtr toplevel, window;
523 // The window generating the operation could be a different than the one under pointer
524 GetWindowsUnderPointer (out window, out toplevel, out x_root, out y_root);
525 Control source_control = Control.FromHandle (window);
526 if (source_control == null || !source_control.AllowDrop)
529 // `data' and other members are already available
530 Point pos = Control.MousePosition;
531 DragEventArgs drag_args = new DragEventArgs (data, 0, pos.X, pos.Y, drag_data.AllowedEffects, DragDropEffects.None);
533 source_control.DndEnter (drag_args);
534 if ((drag_args.Effect & drag_data.AllowedEffects) != 0)
535 source_control.DndDrop (drag_args);
537 source_control.DndLeave (EventArgs.Empty);
540 public void HandleButtonUpMsg ()
542 if (drag_data.State == DragState.Beginning) {
543 //state = State.Accepting;
544 } else if (drag_data.State != DragState.None) {
546 if (drag_data.WillAccept) {
548 if (QueryContinue (false, DragAction.Drop))
552 if (QueryContinue (false, DragAction.Cancel))
555 // fallback if no movement was detected, as .net does.
556 if (motion_poll == -1)
557 DefaultEnterLeave (drag_data.Data);
560 drag_data.State = DragState.None;
561 // WE can't reset the drag data yet as it is still
562 // most likely going to be used by the SelectionRequest
569 private void RemoveCapture (IntPtr handle)
571 Control c = MwfWindow (handle);
572 if (c.InternalCapture)
573 c.InternalCapture = false;
576 public bool HandleMouseOver ()
578 IntPtr toplevel, window;
581 GetWindowsUnderPointer (out window, out toplevel, out x_root, out y_root);
583 if (window != drag_data.LastWindow && drag_data.State == DragState.Entered) {
584 drag_data.State = DragState.Dragging;
586 // TODO: Send a Leave if this is an MWF window
588 if (toplevel != drag_data.LastTopLevel)
589 SendLeave (drag_data.LastTopLevel, toplevel);
592 drag_data.State = DragState.Entered;
593 if (toplevel != drag_data.LastTopLevel) {
594 // Entering a new toplevel window
595 SendEnter (toplevel, drag_data.Window, drag_data.SupportedTypes);
597 // Already in a toplevel window, so send a position
598 SendPosition (toplevel, drag_data.Window,
604 drag_data.LastTopLevel = toplevel;
605 drag_data.LastWindow = window;
609 void GetWindowsUnderPointer (out IntPtr window, out IntPtr toplevel, out int x_root, out int y_root)
611 toplevel = IntPtr.Zero;
612 window = XplatUIX11.RootWindowHandle;
615 bool dnd_aware = false;
618 int x = x_root = drag_data.CurMousePos.X;
619 int y = y_root = drag_data.CurMousePos.Y;
621 while (XplatUIX11.XQueryPointer (display, window, out root, out child,
622 out x_temp, out y_temp, out x, out y, out mask_return)) {
625 dnd_aware = IsWindowDndAware (window);
633 if (child == IntPtr.Zero)
640 public void HandleKeyMessage (MSG msg)
642 if (VirtualKeys.VK_ESCAPE == (VirtualKeys) msg.wParam.ToInt32()) {
643 QueryContinue (true, DragAction.Cancel);
647 // return true if the event is handled here
648 public bool HandleClientMessage (ref XEvent xevent)
650 // most common so we check it first
651 if (xevent.ClientMessageEvent.message_type == XdndPosition)
652 return Accepting_HandlePositionEvent (ref xevent);
653 if (xevent.ClientMessageEvent.message_type == XdndEnter)
654 return Accepting_HandleEnterEvent (ref xevent);
655 if (xevent.ClientMessageEvent.message_type == XdndDrop)
656 return Accepting_HandleDropEvent (ref xevent);
657 if (xevent.ClientMessageEvent.message_type == XdndLeave)
658 return Accepting_HandleLeaveEvent (ref xevent);
659 if (xevent.ClientMessageEvent.message_type == XdndStatus)
660 return HandleStatusEvent (ref xevent);
661 if (xevent.ClientMessageEvent.message_type == XdndFinished)
662 return HandleFinishedEvent (ref xevent);
667 public bool HandleSelectionNotifyEvent (ref XEvent xevent)
669 MimeHandler handler = FindHandler ((IntPtr) xevent.SelectionEvent.target);
673 data = new DataObject ();
675 handler.Converter.GetData (this, data, ref xevent);
678 if (converts_pending <= 0 && position_recieved) {
679 drag_event = new DragEventArgs (data, 0, pos_x, pos_y,
680 allowed, DragDropEffects.None);
681 control.DndEnter (drag_event);
682 SendStatus (source, drag_event.Effect);
688 public bool HandleSelectionRequestEvent (ref XEvent xevent)
690 if (xevent.SelectionRequestEvent.selection != XdndSelection)
693 MimeHandler handler = FindHandler (xevent.SelectionRequestEvent.target);
697 handler.Converter.SetData (this, drag_data.Data, ref xevent);
702 private bool QueryContinue (bool escape, DragAction action)
704 QueryContinueDragEventArgs qce = new QueryContinueDragEventArgs ((int) XplatUI.State.ModifierKeys,
707 Control c = MwfWindow (source);
714 c.DndContinueDrag (qce);
716 switch (qce.Action) {
717 case DragAction.Continue:
719 case DragAction.Drop:
720 SendDrop (drag_data.LastTopLevel, source, IntPtr.Zero);
723 case DragAction.Cancel:
725 c.InternalCapture = false;
729 SendLeave (drag_data.LastTopLevel, toplevel);
731 RestoreDefaultCursor ();
736 private void RestoreDefaultCursor ()
738 // Releasing the mouse buttons should automatically restore the default cursor,
739 // but canceling the operation using QueryContinue should restore it even if the
740 // mouse buttons are not released yet.
741 XplatUIX11.XChangeActivePointerGrab (display,
742 EventMask.ButtonMotionMask |
743 EventMask.PointerMotionMask |
744 EventMask.ButtonPressMask |
745 EventMask.ButtonReleaseMask,
746 Cursors.Default.Handle, IntPtr.Zero);
750 private void GiveFeedback (IntPtr action)
752 GiveFeedbackEventArgs gfe = new GiveFeedbackEventArgs (EffectFromAction (drag_data.Action), true);
754 Control c = MwfWindow (source);
757 if (gfe.UseDefaultCursors) {
758 Cursor cursor = CursorNo;
759 if (drag_data.WillAccept) {
760 // Same order as on MS
761 if (action == XdndActionCopy)
763 else if (action == XdndActionLink)
765 else if (action == XdndActionMove)
768 // TODO: Try not to set the cursor so much
769 //if (cursor.Handle != CurrentCursorHandle) {
770 XplatUIX11.XChangeActivePointerGrab (display,
771 EventMask.ButtonMotionMask |
772 EventMask.PointerMotionMask |
773 EventMask.ButtonPressMask |
774 EventMask.ButtonReleaseMask,
775 cursor.Handle, IntPtr.Zero);
776 //CurrentCursorHandle = cursor.Handle;
781 private void SetProperty (ref XEvent xevent, IntPtr data, int length)
783 XEvent sel = new XEvent();
784 sel.SelectionEvent.type = XEventName.SelectionNotify;
785 sel.SelectionEvent.send_event = true;
786 sel.SelectionEvent.display = display;
787 sel.SelectionEvent.selection = xevent.SelectionRequestEvent.selection;
788 sel.SelectionEvent.target = xevent.SelectionRequestEvent.target;
789 sel.SelectionEvent.requestor = xevent.SelectionRequestEvent.requestor;
790 sel.SelectionEvent.time = xevent.SelectionRequestEvent.time;
791 sel.SelectionEvent.property = IntPtr.Zero;
793 XplatUIX11.XChangeProperty (display, xevent.SelectionRequestEvent.requestor,
794 xevent.SelectionRequestEvent.property,
795 xevent.SelectionRequestEvent.target,
796 8, PropertyMode.Replace, data, length);
797 sel.SelectionEvent.property = xevent.SelectionRequestEvent.property;
799 XplatUIX11.XSendEvent (display, xevent.SelectionRequestEvent.requestor, false,
800 (IntPtr)EventMask.NoEventMask, ref sel);
804 private void Reset ()
810 private void ResetSourceData ()
812 converts_pending = 0;
816 private void ResetTargetData ()
818 position_recieved = false;
822 private bool Accepting_HandleEnterEvent (ref XEvent xevent)
826 source = xevent.ClientMessageEvent.ptr1;
827 toplevel = xevent.AnyEvent.window;
828 target = IntPtr.Zero;
830 ConvertData (ref xevent);
835 private bool Accepting_HandlePositionEvent (ref XEvent xevent)
837 pos_x = (int) xevent.ClientMessageEvent.ptr3 >> 16;
838 pos_y = (int) xevent.ClientMessageEvent.ptr3 & 0xFFFF;
840 // Copy is implicitly allowed
841 Control source_control = MwfWindow (source);
842 if (source_control == null)
843 allowed = EffectsFromX11Source (source, xevent.ClientMessageEvent.ptr5) | DragDropEffects.Copy;
845 allowed = drag_data.AllowedEffects;
847 IntPtr parent, child, new_child, last_drop_child;
848 parent = XplatUIX11.XRootWindow (display, 0);
850 last_drop_child = IntPtr.Zero;
853 new_child = IntPtr.Zero;
855 if (!XplatUIX11.XTranslateCoordinates (display,
856 parent, child, pos_x, pos_y,
857 out xd, out yd, out new_child))
859 if (new_child == IntPtr.Zero)
863 Hwnd h = Hwnd.ObjectFromHandle (child);
865 Control d = Control.FromHandle (h.client_window);
866 if (d != null && d.allow_drop)
867 last_drop_child = child;
871 if (last_drop_child != IntPtr.Zero)
872 child = last_drop_child;
874 if (target != child) {
875 // We have moved into a new control
876 // or into a control for the first time
880 Hwnd hwnd = Hwnd.ObjectFromHandle (target);
884 Control c = Control.FromHandle (hwnd.client_window);
889 SendStatus (source, DragDropEffects.None);
895 position_recieved = true;
897 if (converts_pending > 0)
901 drag_event = new DragEventArgs (data, 0, pos_x, pos_y,
902 allowed, DragDropEffects.None);
903 control.DndEnter (drag_event);
905 SendStatus (source, drag_event.Effect);
908 drag_event.x = pos_x;
909 drag_event.y = pos_y;
910 control.DndOver (drag_event);
912 SendStatus (source, drag_event.Effect);
918 private void Finish ()
920 if (control != null) {
921 if (drag_event == null) {
923 data = new DataObject ();
924 drag_event = new DragEventArgs (data,
926 allowed, DragDropEffects.None);
928 control.DndLeave (drag_event);
934 private bool Accepting_HandleDropEvent (ref XEvent xevent)
936 if (control != null && drag_event != null) {
937 drag_event = new DragEventArgs (data,
939 allowed, drag_event.Effect);
940 control.DndDrop (drag_event);
946 private bool Accepting_HandleLeaveEvent (ref XEvent xevent)
948 if (control != null && drag_event != null)
949 control.DndLeave (drag_event);
954 private bool HandleStatusEvent (ref XEvent xevent)
956 if (drag_data != null && drag_data.State == DragState.Entered) {
958 if (!QueryContinue (false, DragAction.Continue))
961 drag_data.WillAccept = ((int) xevent.ClientMessageEvent.ptr2 & 0x1) != 0;
963 GiveFeedback (xevent.ClientMessageEvent.ptr5);
968 private bool HandleFinishedEvent (ref XEvent xevent)
973 private DragDropEffects EffectsFromX11Source (IntPtr source, IntPtr action_atom)
975 DragDropEffects allowed = DragDropEffects.None;
976 IntPtr type, count, remaining, data = IntPtr.Zero;
979 XplatUIX11.XGetWindowProperty (display, source, XdndActionList,
980 IntPtr.Zero, new IntPtr (32), false, (IntPtr) Atom.AnyPropertyType,
981 out type, out format, out count, out remaining, ref data);
983 int intptr_size = Marshal.SizeOf (typeof (IntPtr));
984 for (int i = 0; i < count.ToInt32 (); i++) {
985 IntPtr current_atom = Marshal.ReadIntPtr (data, i * intptr_size);
986 allowed |= EffectFromAction (current_atom);
989 // if source is not providing the action list, use the
990 // default action passed in the x11 dnd position message
991 if (allowed == DragDropEffects.None)
992 allowed = EffectFromAction (action_atom);
997 private DragDropEffects EffectFromAction (IntPtr action)
999 if (action == XdndActionCopy)
1000 return DragDropEffects.Copy;
1001 else if (action == XdndActionMove)
1002 return DragDropEffects.Move;
1003 if (action == XdndActionLink)
1004 return DragDropEffects.Link;
1006 return DragDropEffects.None;
1009 private IntPtr ActionFromEffect (DragDropEffects effect)
1011 IntPtr action = IntPtr.Zero;
1013 // We can't OR together actions on XDND so sadly the primary
1014 // is the only one shown here
1015 if ((effect & DragDropEffects.Copy) != 0)
1016 action = XdndActionCopy;
1017 else if ((effect & DragDropEffects.Move) != 0)
1018 action = XdndActionMove;
1019 else if ((effect & DragDropEffects.Link) != 0)
1020 action = XdndActionLink;
1024 private bool ConvertData (ref XEvent xevent)
1028 Control mwfcontrol = MwfWindow (source);
1030 /* To take advantage of the mwfcontrol, we have to be sure
1031 that the dnd operation is still happening (since messages are asynchronous) */
1032 if (mwfcontrol != null && drag_data != null) {
1036 IDataObject dragged = drag_data.Data as IDataObject;
1037 if (dragged != null) {
1041 data = new DataObject ();
1042 SetDataWithFormats (drag_data.Data);
1047 foreach (IntPtr atom in SourceSupportedList (ref xevent)) {
1048 MimeHandler handler = FindHandler (atom);
1049 if (handler == null)
1051 XplatUIX11.XConvertSelection (display, XdndSelection, handler.Type,
1052 handler.NonProtocol, toplevel, IntPtr.Zero /* CurrentTime */);
1059 private void SetDataWithFormats (object value)
1061 if (value is string) {
1062 data.SetData (DataFormats.Text, value);
1063 data.SetData (DataFormats.UnicodeText, value);
1066 data.SetData (value);
1069 private MimeHandler FindHandler (IntPtr atom)
1071 if (atom == IntPtr.Zero)
1073 foreach (MimeHandler handler in MimeHandlers) {
1074 if (handler.Type == atom)
1080 private MimeHandler FindHandler (string name)
1082 foreach (MimeHandler handler in MimeHandlers) {
1083 foreach (string alias in handler.Aliases) {
1091 private void SendStatus (IntPtr source, DragDropEffects effect)
1093 XEvent xevent = new XEvent ();
1095 xevent.AnyEvent.type = XEventName.ClientMessage;
1096 xevent.AnyEvent.display = display;
1097 xevent.ClientMessageEvent.window = source;
1098 xevent.ClientMessageEvent.message_type = XdndStatus;
1099 xevent.ClientMessageEvent.format = 32;
1100 xevent.ClientMessageEvent.ptr1 = toplevel;
1101 if (effect != DragDropEffects.None && (effect & allowed) != 0)
1102 xevent.ClientMessageEvent.ptr2 = (IntPtr) 1;
1104 xevent.ClientMessageEvent.ptr5 = ActionFromEffect (effect);
1105 XplatUIX11.XSendEvent (display, source, false, IntPtr.Zero, ref xevent);
1108 private void SendEnter (IntPtr handle, IntPtr from, IntPtr [] supported)
1110 XEvent xevent = new XEvent ();
1112 xevent.AnyEvent.type = XEventName.ClientMessage;
1113 xevent.AnyEvent.display = display;
1114 xevent.ClientMessageEvent.window = handle;
1115 xevent.ClientMessageEvent.message_type = XdndEnter;
1116 xevent.ClientMessageEvent.format = 32;
1117 xevent.ClientMessageEvent.ptr1 = from;
1119 // (int) xevent.ClientMessageEvent.ptr2 & 0x1)
1121 // xevent.ClientMessageEvent.ptr2 = (IntPtr) ptr2;
1122 // (e)->xclient.data.l[1] = ((e)->xclient.data.l[1] & ~(0xFF << 24)) | ((v) << 24)
1123 xevent.ClientMessageEvent.ptr2 = (IntPtr) ((long)XdndVersion [0] << 24);
1125 if (supported.Length > 0)
1126 xevent.ClientMessageEvent.ptr3 = supported [0];
1127 if (supported.Length > 1)
1128 xevent.ClientMessageEvent.ptr4 = supported [1];
1129 if (supported.Length > 2)
1130 xevent.ClientMessageEvent.ptr5 = supported [2];
1132 XplatUIX11.XSendEvent (display, handle, false, IntPtr.Zero, ref xevent);
1135 private void SendDrop (IntPtr handle, IntPtr from, IntPtr time)
1137 XEvent xevent = new XEvent ();
1139 xevent.AnyEvent.type = XEventName.ClientMessage;
1140 xevent.AnyEvent.display = display;
1141 xevent.ClientMessageEvent.window = handle;
1142 xevent.ClientMessageEvent.message_type = XdndDrop;
1143 xevent.ClientMessageEvent.format = 32;
1144 xevent.ClientMessageEvent.ptr1 = from;
1145 xevent.ClientMessageEvent.ptr3 = time;
1147 XplatUIX11.XSendEvent (display, handle, false, IntPtr.Zero, ref xevent);
1151 private void SendPosition (IntPtr handle, IntPtr from, IntPtr action, int x, int y, IntPtr time)
1153 XEvent xevent = new XEvent ();
1155 xevent.AnyEvent.type = XEventName.ClientMessage;
1156 xevent.AnyEvent.display = display;
1157 xevent.ClientMessageEvent.window = handle;
1158 xevent.ClientMessageEvent.message_type = XdndPosition;
1159 xevent.ClientMessageEvent.format = 32;
1160 xevent.ClientMessageEvent.ptr1 = from;
1161 xevent.ClientMessageEvent.ptr3 = (IntPtr) ((x << 16) | (y & 0xFFFF));
1162 xevent.ClientMessageEvent.ptr4 = time;
1163 xevent.ClientMessageEvent.ptr5 = action;
1165 XplatUIX11.XSendEvent (display, handle, false, IntPtr.Zero, ref xevent);
1168 private void SendLeave (IntPtr handle, IntPtr from)
1170 XEvent xevent = new XEvent ();
1172 xevent.AnyEvent.type = XEventName.ClientMessage;
1173 xevent.AnyEvent.display = display;
1174 xevent.ClientMessageEvent.window = handle;
1175 xevent.ClientMessageEvent.message_type = XdndLeave;
1176 xevent.ClientMessageEvent.format = 32;
1177 xevent.ClientMessageEvent.ptr1 = from;
1179 XplatUIX11.XSendEvent (display, handle, false, IntPtr.Zero, ref xevent);
1182 private void SendFinished ()
1184 XEvent xevent = new XEvent ();
1186 xevent.AnyEvent.type = XEventName.ClientMessage;
1187 xevent.AnyEvent.display = display;
1188 xevent.ClientMessageEvent.window = source;
1189 xevent.ClientMessageEvent.message_type = XdndFinished;
1190 xevent.ClientMessageEvent.format = 32;
1191 xevent.ClientMessageEvent.ptr1 = toplevel;
1193 XplatUIX11.XSendEvent (display, source, false, IntPtr.Zero, ref xevent);
1196 // There is a somewhat decent amount of overhead
1197 // involved in setting up dnd so we do it lazily
1198 // as a lot of applications do not even use it.
1199 private void Init ()
1201 XdndAware = XplatUIX11.XInternAtom (display, "XdndAware", false);
1202 XdndEnter = XplatUIX11.XInternAtom (display, "XdndEnter", false);
1203 XdndLeave = XplatUIX11.XInternAtom (display, "XdndLeave", false);
1204 XdndPosition = XplatUIX11.XInternAtom (display, "XdndPosition", false);
1205 XdndStatus = XplatUIX11.XInternAtom (display, "XdndStatus", false);
1206 XdndDrop = XplatUIX11.XInternAtom (display, "XdndDrop", false);
1207 XdndSelection = XplatUIX11.XInternAtom (display, "XdndSelection", false);
1208 XdndFinished = XplatUIX11.XInternAtom (display, "XdndFinished", false);
1209 XdndTypeList = XplatUIX11.XInternAtom (display, "XdndTypeList", false);
1210 XdndActionCopy = XplatUIX11.XInternAtom (display, "XdndActionCopy", false);
1211 XdndActionMove = XplatUIX11.XInternAtom (display, "XdndActionMove", false);
1212 XdndActionLink = XplatUIX11.XInternAtom (display, "XdndActionLink", false);
1213 //XdndActionPrivate = XplatUIX11.XInternAtom (display, "XdndActionPrivate", false);
1214 XdndActionList = XplatUIX11.XInternAtom (display, "XdndActionList", false);
1215 //XdndActionDescription = XplatUIX11.XInternAtom (display, "XdndActionDescription", false);
1216 //XdndActionAsk = XplatUIX11.XInternAtom (display, "XdndActionAsk", false);
1218 foreach (MimeHandler handler in MimeHandlers) {
1219 handler.Type = XplatUIX11.XInternAtom (display, handler.Name, false);
1220 handler.NonProtocol = XplatUIX11.XInternAtom (display,
1221 String.Concat ("MWFNonP+", handler.Name), false);
1226 private IntPtr [] SourceSupportedList (ref XEvent xevent)
1231 if (((int) xevent.ClientMessageEvent.ptr2 & 0x1) == 0) {
1232 res = new IntPtr [3];
1233 res [0] = xevent.ClientMessageEvent.ptr3;
1234 res [1] = xevent.ClientMessageEvent.ptr4;
1235 res [2] = xevent.ClientMessageEvent.ptr5;
1241 IntPtr data = IntPtr.Zero;
1243 XplatUIX11.XGetWindowProperty (display, source, XdndTypeList,
1244 IntPtr.Zero, new IntPtr(32), false, (IntPtr) Atom.XA_ATOM,
1245 out type, out format, out count,
1246 out remaining, ref data);
1248 res = new IntPtr [count.ToInt32()];
1249 for (int i = 0; i < count.ToInt32(); i++) {
1250 res [i] = (IntPtr) Marshal.ReadInt32 (data, i *
1251 Marshal.SizeOf (typeof (int)));
1254 XplatUIX11.XFree (data);
1260 private string GetText (ref XEvent xevent, bool unicode)
1266 StringBuilder builder = new StringBuilder ();
1270 IntPtr data = IntPtr.Zero;
1272 if (0 != XplatUIX11.XGetWindowProperty (display,
1273 xevent.AnyEvent.window,
1274 (IntPtr) xevent.SelectionEvent.property,
1275 IntPtr.Zero, new IntPtr(0xffffff), false,
1276 (IntPtr) Atom.AnyPropertyType, out actual_type,
1277 out actual_fmt, out nitems, out bytes_after,
1279 XplatUIX11.XFree (data);
1284 builder.Append (Marshal.PtrToStringUni (data));
1286 builder.Append (Marshal.PtrToStringAnsi (data));
1287 nread += nitems.ToInt32();
1289 XplatUIX11.XFree (data);
1290 } while (bytes_after.ToInt32() > 0);
1293 return builder.ToString ();
1296 private MemoryStream GetData (ref XEvent xevent)
1302 MemoryStream res = new MemoryStream ();
1306 IntPtr data = IntPtr.Zero;
1308 if (0 != XplatUIX11.XGetWindowProperty (display,
1309 xevent.AnyEvent.window,
1310 (IntPtr) xevent.SelectionEvent.property,
1311 IntPtr.Zero, new IntPtr(0xffffff), false,
1312 (IntPtr) Atom.AnyPropertyType, out actual_type,
1313 out actual_fmt, out nitems, out bytes_after,
1315 XplatUIX11.XFree (data);
1319 for (int i = 0; i < nitems.ToInt32(); i++)
1320 res.WriteByte (Marshal.ReadByte (data, i));
1321 nread += nitems.ToInt32();
1323 XplatUIX11.XFree (data);
1324 } while (bytes_after.ToInt32() > 0);
1328 private Control MwfWindow (IntPtr window)
1330 Hwnd hwnd = Hwnd.ObjectFromHandle (window);
1334 Control res = Control.FromHandle (hwnd.client_window);
1337 res = Control.FromHandle (window);
1342 private bool IsWindowDndAware (IntPtr handle)
1345 // Check the version number, we need greater than 3
1350 IntPtr data = IntPtr.Zero;
1352 XplatUIX11.XGetWindowProperty (display, handle, XdndAware, IntPtr.Zero, new IntPtr(0x8000000), false,
1353 (IntPtr) Atom.XA_ATOM, out actual, out format,
1354 out count, out remaining, ref data);
1356 if (actual != (IntPtr) Atom.XA_ATOM || format != 32 ||
1357 count.ToInt32() == 0 || data == IntPtr.Zero) {
1358 if (data != IntPtr.Zero)
1359 XplatUIX11.XFree (data);
1363 int version = Marshal.ReadInt32 (data, 0);
1366 Console.Error.WriteLine ("XDND Version too old (" + version + ").");
1367 XplatUIX11.XFree (data);
1371 // First type is actually the XDND version
1372 if (count.ToInt32() > 1) {
1374 for (int i = 1; i < count.ToInt32(); i++) {
1375 IntPtr type = (IntPtr) Marshal.ReadInt32 (data, i *
1376 Marshal.SizeOf (typeof (int)));
1377 for (int j = 0; j < drag_data.SupportedTypes.Length; j++) {
1378 if (drag_data.SupportedTypes [j] == type) {
1386 XplatUIX11.XFree (data);
1390 private IntPtr [] DetermineSupportedTypes (object data)
1392 ArrayList res = new ArrayList ();
1394 if (data is string) {
1395 MimeHandler handler = FindHandler ("text/plain");
1396 if (handler != null)
1397 res.Add (handler.Type);
1398 }/* else if (data is Bitmap)
1403 IDataObject data_object = data as IDataObject;
1404 if (data_object != null) {
1405 foreach (string format in data_object.GetFormats (true)) {
1406 MimeHandler handler = FindHandler (format);
1407 if (handler != null && !res.Contains (handler.Type))
1408 res.Add (handler.Type);
1412 if (data is ISerializable) {
1413 MimeHandler handler = FindHandler ("application/x-mono-serialized-object");
1414 if (handler != null)
1415 res.Add (handler.Type);
1418 return (IntPtr []) res.ToArray (typeof (IntPtr));