2007-03-19 Chris Toshok <toshok@ximian.com>
[mono-project.git] / mcs / class / Managed.Windows.Forms / System.Windows.Forms / X11Dnd.cs
bloba44f261a6759858a3dbc4a812100d2e2cbab6ca6
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:
8 //
9 // The above copyright notice and this permission notice shall be
10 // included in all copies or substantial portions of the Software.
11 //
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.
22 // Authors:
23 // Jackson Harper (jackson@ximian.com)
28 using System;
29 using System.IO;
30 using System.Text;
31 using System.Drawing;
32 using System.Collections;
33 using System.Runtime.Serialization;
34 using System.Runtime.InteropServices;
35 using System.Runtime.Serialization.Formatters.Binary;
37 namespace System.Windows.Forms {
39 internal class X11Dnd {
41 private enum State {
42 Accepting,
43 Dragging
46 private enum DragState {
47 None,
48 Beginning,
49 Dragging,
50 Entered
53 private interface IDataConverter {
54 void GetData (X11Dnd dnd, IDataObject data, ref XEvent xevent);
55 void SetData (X11Dnd dnd, object data, ref XEvent xevent);
58 private delegate void MimeConverter (IntPtr dsp,
59 IDataObject data, ref XEvent xevent);
61 private class MimeHandler {
62 public string Name;
63 public string [] Aliases;
64 public IntPtr Type;
65 public IntPtr NonProtocol;
66 public IDataConverter Converter;
68 public MimeHandler (string name, IDataConverter converter) : this (name, converter, name)
72 public MimeHandler (string name, IDataConverter converter, params string [] aliases)
74 Name = name;
75 Converter = converter;
76 Aliases = aliases;
79 public override string ToString ()
81 return "MimeHandler {" + Name + "}";
85 private MimeHandler [] MimeHandlers = {
86 // new MimeHandler ("WCF_DIB"),
87 // new MimeHandler ("image/gif", new MimeConverter (ImageConverter)),
88 // new MimeHandler ("text/rtf", new MimeConverter (RtfConverter)),
89 // new MimeHandler ("text/richtext", new MimeConverter (RtfConverter)),
91 new MimeHandler ("text/plain", new TextConverter ()),
92 new MimeHandler ("text/plain", new TextConverter (), "System.String", DataFormats.Text),
93 new MimeHandler ("text/html", new HtmlConverter (), DataFormats.Html),
94 new MimeHandler ("text/uri-list", new UriListConverter (), DataFormats.FileDrop),
95 new MimeHandler ("application/x-mono-serialized-object",
96 new SerializedObjectConverter ())
99 private class SerializedObjectConverter : IDataConverter {
101 public void GetData (X11Dnd dnd, IDataObject data, ref XEvent xevent)
103 MemoryStream stream = dnd.GetData (ref xevent);
104 BinaryFormatter bf = new BinaryFormatter ();
106 if (stream.Length == 0)
107 return;
109 stream.Seek (0, 0);
110 object obj = bf.Deserialize (stream);
111 data.SetData (obj);
114 public void SetData (X11Dnd dnd, object data, ref XEvent xevent)
116 if (data == null)
117 return;
119 MemoryStream stream = new MemoryStream ();
120 BinaryFormatter bf = new BinaryFormatter ();
122 bf.Serialize (stream, data);
124 IntPtr buffer = Marshal.AllocHGlobal ((int) stream.Length);
125 stream.Seek (0, 0);
127 for (int i = 0; i < stream.Length; i++) {
128 Marshal.WriteByte (buffer, i, (byte) stream.ReadByte ());
131 dnd.SetProperty (ref xevent, buffer, (int) stream.Length);
135 private class HtmlConverter : IDataConverter {
137 public void GetData (X11Dnd dnd, IDataObject data, ref XEvent xevent)
139 string text = dnd.GetText (ref xevent, false);
140 if (text == null)
141 return;
142 data.SetData (DataFormats.Text, text);
143 data.SetData (DataFormats.UnicodeText, text);
146 public void SetData (X11Dnd dnd, object data, ref XEvent xevent)
148 IntPtr buffer;
149 int len;
150 string str = data as string;
152 if (str == null)
153 return;
155 if (xevent.SelectionRequestEvent.target == (IntPtr)Atom.XA_STRING) {
156 byte [] bytes = Encoding.ASCII.GetBytes (str);
157 buffer = Marshal.AllocHGlobal (bytes.Length);
158 len = bytes.Length;
159 for (int i = 0; i < len; i++)
160 Marshal.WriteByte (buffer, i, bytes [i]);
161 } else {
162 buffer = Marshal.StringToHGlobalAnsi (str);
163 len = 0;
164 while (Marshal.ReadByte (buffer, len) != 0)
165 len++;
168 dnd.SetProperty (ref xevent, buffer, len);
170 Marshal.FreeHGlobal (buffer);
174 private class TextConverter : IDataConverter {
176 public void GetData (X11Dnd dnd, IDataObject data, ref XEvent xevent)
178 string text = dnd.GetText (ref xevent, true);
179 if (text == null)
180 return;
181 data.SetData (DataFormats.Text, text);
182 data.SetData (DataFormats.UnicodeText, text);
185 public void SetData (X11Dnd dnd, object data, ref XEvent xevent)
187 IntPtr buffer;
188 int len;
189 string str = data as string;
191 if (str == null) {
192 IDataObject dobj = data as IDataObject;
193 if (dobj == null)
194 return;
195 str = (string) dobj.GetData ("System.String", true);
198 if (xevent.SelectionRequestEvent.target == (IntPtr)Atom.XA_STRING) {
199 byte [] bytes = Encoding.ASCII.GetBytes (str);
200 buffer = Marshal.AllocHGlobal (bytes.Length);
201 len = bytes.Length;
202 for (int i = 0; i < len; i++)
203 Marshal.WriteByte (buffer, i, bytes [i]);
204 } else {
205 buffer = Marshal.StringToHGlobalAnsi (str);
206 len = 0;
207 while (Marshal.ReadByte (buffer, len) != 0)
208 len++;
211 dnd.SetProperty (ref xevent, buffer, len);
213 Marshal.FreeHGlobal (buffer);
217 private class UriListConverter : IDataConverter {
219 public void GetData (X11Dnd dnd, IDataObject data, ref XEvent xevent)
221 string text = dnd.GetText (ref xevent, false);
222 if (text == null)
223 return;
225 // TODO: Do this in a loop instead of just splitting
226 ArrayList uri_list = new ArrayList ();
227 string [] lines = text.Split (new char [] { '\r', '\n' });
228 foreach (string line in lines) {
229 // # is a comment line (see RFC 2483)
230 if (line.StartsWith ("#"))
231 continue;
232 try {
233 Uri uri = new Uri (line);
234 uri_list.Add (uri.LocalPath);
235 } catch { }
238 string [] l = (string []) uri_list.ToArray (typeof (string));
239 if (l.Length < 1)
240 return;
241 data.SetData (DataFormats.FileDrop, l);
242 data.SetData ("FileName", l [0]);
243 data.SetData ("FileNameW", l [0]);
246 public void SetData (X11Dnd dnd, object data, ref XEvent xevent)
248 string [] uri_list = data as string [];
250 if (uri_list == null) {
251 IDataObject dobj = data as IDataObject;
252 if (dobj == null)
253 return;
254 uri_list = dobj.GetData (DataFormats.FileDrop, true) as string [];
257 if (uri_list == null)
258 return;
260 StringBuilder res = new StringBuilder ();
261 foreach (string uri_str in uri_list) {
262 Uri uri = new Uri (uri_str);
263 res.Append (uri.ToString ());
264 res.Append ("\r\n");
267 IntPtr buffer = Marshal.StringToHGlobalAnsi ((string) res.ToString ());
268 int len = 0;
269 while (Marshal.ReadByte (buffer, len) != 0)
270 len++;
272 dnd.SetProperty (ref xevent, buffer, len);
276 private class DragData {
277 public IntPtr Window;
278 public DragState State;
279 public object Data;
280 public IntPtr Action;
281 public IntPtr [] SupportedTypes;
282 public MouseButtons MouseState;
284 public IntPtr LastWindow;
285 public IntPtr LastTopLevel;
287 public bool WillAccept;
289 public void Reset ()
291 State = DragState.None;
292 Data = null;
293 SupportedTypes = null;
294 WillAccept = false;
298 // This version seems to be the most common
299 private static readonly IntPtr [] XdndVersion = new IntPtr [] { new IntPtr (4) };
301 private IntPtr display;
302 private DragData drag_data;
304 private IntPtr XdndAware;
305 private IntPtr XdndSelection;
306 private IntPtr XdndEnter;
307 private IntPtr XdndLeave;
308 private IntPtr XdndPosition;
309 private IntPtr XdndDrop;
310 private IntPtr XdndFinished;
311 private IntPtr XdndStatus;
312 private IntPtr XdndTypeList;
313 private IntPtr XdndActionCopy;
314 private IntPtr XdndActionMove;
315 private IntPtr XdndActionLink;
316 //private IntPtr XdndActionPrivate;
317 //private IntPtr XdndActionList;
318 //private IntPtr XdndActionDescription;
319 //private IntPtr XdndActionAsk;
321 //private State state;
323 private int converts_pending;
324 private bool position_recieved;
325 private bool status_sent;
326 private IntPtr target;
327 private IntPtr source;
328 private IntPtr toplevel;
329 private IDataObject data;
331 private Control control;
332 private int pos_x, pos_y;
333 private DragDropEffects allowed;
334 private DragEventArgs drag_event;
336 private Cursor CursorNo;
337 private Cursor CursorCopy;
338 private Cursor CursorMove;
339 private Cursor CursorLink;
340 // check out the TODO below
341 //private IntPtr CurrentCursorHandle;
343 private X11Keyboard keyboard;
345 public X11Dnd (IntPtr display, X11Keyboard keyboard)
347 this.display = display;
348 this.keyboard = keyboard;
350 Init ();
353 public bool InDrag()
355 if (drag_data == null)
356 return false;
357 return drag_data.State != DragState.None;
360 public void SetAllowDrop (Hwnd hwnd, bool allow)
362 int[] atoms;
364 if (hwnd.allow_drop == allow)
365 return;
367 atoms = new int[XdndVersion.Length];
368 for (int i = 0; i < XdndVersion.Length; i++) {
369 atoms[i] = XdndVersion[i].ToInt32();
372 XplatUIX11.XChangeProperty (display, hwnd.whole_window, XdndAware,
373 (IntPtr) Atom.XA_ATOM, 32,
374 PropertyMode.Replace, atoms, allow ? 1 : 0);
375 hwnd.allow_drop = allow;
378 public DragDropEffects StartDrag (IntPtr handle, object data,
379 DragDropEffects allowed_effects)
381 drag_data = new DragData ();
382 drag_data.Window = handle;
383 drag_data.State = DragState.Beginning;
384 drag_data.MouseState = XplatUIX11.MouseState;
385 drag_data.Data = data;
386 drag_data.SupportedTypes = DetermineSupportedTypes (data);
388 drag_data.Action = ActionFromEffect (allowed_effects);
390 if (CursorNo == null) {
391 // Make sure the cursors are created
392 CursorNo = new Cursor (typeof (X11Dnd), "DnDNo.cur");
393 CursorCopy = new Cursor (typeof (X11Dnd), "DnDCopy.cur");
394 CursorMove = new Cursor (typeof (X11Dnd), "DnDMove.cur");
395 CursorLink = new Cursor (typeof (X11Dnd), "DnDLink.cur");
398 drag_data.LastTopLevel = IntPtr.Zero;
399 return DragDropEffects.Copy;
402 public bool HandleButtonRelease (ref XEvent xevent)
404 if (drag_data == null)
405 return false;
407 if (!((drag_data.MouseState == MouseButtons.Left &&
408 xevent.ButtonEvent.button == 1) ||
409 (drag_data.MouseState == MouseButtons.Right &&
410 xevent.ButtonEvent.button == 3)))
411 return false;
413 if (drag_data.State == DragState.Beginning) {
414 //state = State.Accepting;
415 } else if (drag_data.State != DragState.None) {
417 if (drag_data.WillAccept) {
419 if (QueryContinue (xevent, false, DragAction.Drop))
420 return true;
424 drag_data.State = DragState.None;
425 // WE can't reset the drag data yet as it is still
426 // most likely going to be used by the SelectionRequest
427 // handlers
430 return false;
433 public bool HandleMotionNotify (ref XEvent xevent)
435 if (drag_data == null)
436 return false;
438 if (drag_data.State == DragState.Beginning) {
439 int suc;
441 drag_data.State = DragState.Dragging;
443 suc = XplatUIX11.XSetSelectionOwner (display, XdndSelection,
444 drag_data.Window,
445 xevent.ButtonEvent.time);
447 if (suc == 0) {
448 Console.Error.WriteLine ("Could not take ownership of XdndSelection aborting drag.");
449 drag_data.Reset ();
450 return false;
453 drag_data.State = DragState.Dragging;
454 } else if (drag_data.State != DragState.None) {
455 bool dnd_aware = false;
456 IntPtr toplevel = IntPtr.Zero;
457 IntPtr window = XplatUIX11.RootWindowHandle;
459 IntPtr root, child;
460 int x_temp, y_temp;
461 int mask_return;
463 while (XplatUIX11.XQueryPointer (display,
464 window,
465 out root, out child,
466 out x_temp, out y_temp,
467 out xevent.MotionEvent.x,
468 out xevent.MotionEvent.y,
469 out mask_return)) {
471 if (!dnd_aware) {
472 dnd_aware = IsWindowDndAware (window);
473 if (dnd_aware) {
474 toplevel = window;
475 xevent.MotionEvent.x_root = x_temp;
476 xevent.MotionEvent.y_root = y_temp;
480 if (child == IntPtr.Zero)
481 break;
483 window = child;
486 if (window != drag_data.LastWindow && drag_data.State == DragState.Entered) {
487 drag_data.State = DragState.Dragging;
489 // TODO: Send a Leave if this is an MWF window
491 if (toplevel != drag_data.LastTopLevel)
492 SendLeave (drag_data.LastTopLevel, xevent.MotionEvent.window);
495 drag_data.State = DragState.Entered;
496 if (toplevel != drag_data.LastTopLevel) {
497 // Entering a new toplevel window
498 SendEnter (toplevel, drag_data.Window, drag_data.SupportedTypes);
499 } else {
500 // Already in a toplevel window, so send a position
501 SendPosition (toplevel, drag_data.Window,
502 drag_data.Action,
503 xevent.MotionEvent.x_root,
504 xevent.MotionEvent.y_root,
505 xevent.MotionEvent.time);
508 drag_data.LastTopLevel = toplevel;
509 drag_data.LastWindow = window;
510 return true;
512 return false;
515 public bool HandleKeyPress (ref XEvent xevent)
517 if (VirtualKeys.VK_ESCAPE == (VirtualKeys) keyboard.EventToVkey (xevent)) {
518 if (!QueryContinue (xevent, true, DragAction.Cancel))
519 return true;
522 return false;
525 // return true if the event is handled here
526 public bool HandleClientMessage (ref XEvent xevent)
528 // most common so we check it first
529 if (xevent.ClientMessageEvent.message_type == XdndPosition)
530 return Accepting_HandlePositionEvent (ref xevent);
531 if (xevent.ClientMessageEvent.message_type == XdndEnter)
532 return Accepting_HandleEnterEvent (ref xevent);
533 if (xevent.ClientMessageEvent.message_type == XdndDrop)
534 return Accepting_HandleDropEvent (ref xevent);
535 if (xevent.ClientMessageEvent.message_type == XdndLeave)
536 return Accepting_HandleLeaveEvent (ref xevent);
537 if (xevent.ClientMessageEvent.message_type == XdndStatus)
538 return HandleStatusEvent (ref xevent);
540 return false;
543 public bool HandleSelectionNotifyEvent (ref XEvent xevent)
545 if (source != XplatUIX11.XGetSelectionOwner (display, XdndSelection))
546 return false;
548 MimeHandler handler = FindHandler ((IntPtr) xevent.SelectionEvent.target);
549 if (handler == null)
550 return false;
551 if (data == null)
552 data = new DataObject ();
554 handler.Converter.GetData (this, data, ref xevent);
556 converts_pending--;
557 if (converts_pending <= 0 && position_recieved) {
558 drag_event = new DragEventArgs (data, 0, pos_x, pos_y,
559 allowed, DragDropEffects.None);
560 control.DndEnter (drag_event);
561 SendStatus (source, drag_event.Effect);
562 status_sent = true;
564 return true;
567 public bool HandleSelectionRequestEvent (ref XEvent xevent)
569 if (xevent.SelectionRequestEvent.selection != XdndSelection)
570 return false;
572 MimeHandler handler = FindHandler (xevent.SelectionRequestEvent.target);
573 if (handler == null)
574 return false;
576 handler.Converter.SetData (this, drag_data.Data, ref xevent);
578 return true;
581 private bool QueryContinue (XEvent xevent, bool escape, DragAction action)
583 QueryContinueDragEventArgs qce = new QueryContinueDragEventArgs ((int) XplatUI.State.ModifierKeys,
584 escape, action);
586 Control c = MwfWindow (source);
587 c.DndContinueDrag (qce);
589 switch (qce.Action) {
590 case DragAction.Continue:
591 return true;
592 case DragAction.Drop:
593 SendDrop (drag_data.LastTopLevel, source, xevent.ButtonEvent.time);
594 break;
595 case DragAction.Cancel:
596 drag_data.Reset ();
597 c.InternalCapture = false;
598 break;
601 return false;
604 private void GiveFeedback (IntPtr action)
606 GiveFeedbackEventArgs gfe = new GiveFeedbackEventArgs (EffectFromAction (drag_data.Action), true);
608 Control c = MwfWindow (source);
609 c.DndFeedback (gfe);
611 if (gfe.UseDefaultCursors) {
612 Cursor cursor = CursorNo;
613 if (drag_data.WillAccept) {
614 // Same order as on MS
615 if (action == XdndActionCopy)
616 cursor = CursorCopy;
617 else if (action == XdndActionLink)
618 cursor = CursorLink;
619 else if (action == XdndActionMove)
620 cursor = CursorMove;
622 // TODO: Try not to set the cursor so much
623 //if (cursor.Handle != CurrentCursorHandle) {
624 XplatUIX11.XChangeActivePointerGrab (display,
625 EventMask.ButtonMotionMask |
626 EventMask.PointerMotionMask |
627 EventMask.ButtonPressMask |
628 EventMask.ButtonReleaseMask,
629 cursor.Handle, IntPtr.Zero);
630 //CurrentCursorHandle = cursor.Handle;
635 private void SetProperty (ref XEvent xevent, IntPtr data, int length)
637 XEvent sel = new XEvent();
638 sel.SelectionEvent.type = XEventName.SelectionNotify;
639 sel.SelectionEvent.send_event = true;
640 sel.SelectionEvent.display = display;
641 sel.SelectionEvent.selection = xevent.SelectionRequestEvent.selection;
642 sel.SelectionEvent.target = xevent.SelectionRequestEvent.target;
643 sel.SelectionEvent.requestor = xevent.SelectionRequestEvent.requestor;
644 sel.SelectionEvent.time = xevent.SelectionRequestEvent.time;
645 sel.SelectionEvent.property = IntPtr.Zero;
647 XplatUIX11.XChangeProperty (display, xevent.SelectionRequestEvent.requestor,
648 xevent.SelectionRequestEvent.property,
649 xevent.SelectionRequestEvent.target,
650 8, PropertyMode.Replace, data, length);
651 sel.SelectionEvent.property = xevent.SelectionRequestEvent.property;
653 XplatUIX11.XSendEvent (display, xevent.SelectionRequestEvent.requestor, false,
654 (IntPtr)EventMask.NoEventMask, ref sel);
655 return;
658 private void Reset ()
660 ResetSourceData ();
661 ResetTargetData ();
664 private void ResetSourceData ()
666 converts_pending = 0;
667 data = null;
670 private void ResetTargetData ()
672 position_recieved = false;
673 status_sent = false;
676 private bool Accepting_HandleEnterEvent (ref XEvent xevent)
678 Reset ();
680 source = xevent.ClientMessageEvent.ptr1;
681 toplevel = xevent.AnyEvent.window;
682 target = IntPtr.Zero;
684 ConvertData (ref xevent);
686 return true;
689 private bool Accepting_HandlePositionEvent (ref XEvent xevent)
691 pos_x = (int) xevent.ClientMessageEvent.ptr3 >> 16;
692 pos_y = (int) xevent.ClientMessageEvent.ptr3 & 0xFFFF;
694 // Copy is implicitly allowed
695 allowed = EffectFromAction (xevent.ClientMessageEvent.ptr5) | DragDropEffects.Copy;
697 IntPtr parent, child, new_child, last_drop_child;
698 parent = XplatUIX11.XRootWindow (display, 0);
699 child = toplevel;
700 last_drop_child = IntPtr.Zero;
701 while (true) {
702 int xd, yd;
703 new_child = IntPtr.Zero;
705 if (!XplatUIX11.XTranslateCoordinates (display,
706 parent, child, pos_x, pos_y,
707 out xd, out yd, out new_child))
708 break;
709 if (new_child == IntPtr.Zero)
710 break;
711 child = new_child;
713 Hwnd h = Hwnd.ObjectFromHandle (child);
714 Control d = Control.FromHandle (h.client_window);
715 if (d != null && d.allow_drop)
716 last_drop_child = child;
719 if (last_drop_child != IntPtr.Zero)
720 child = last_drop_child;
722 if (target != child) {
723 // We have moved into a new control
724 // or into a control for the first time
725 Finish ();
727 target = child;
728 Hwnd hwnd = Hwnd.ObjectFromHandle (target);
729 Control c = Control.FromHandle (hwnd.client_window);
731 if (c == null)
732 return true;
733 if (!c.allow_drop) {
734 SendStatus (source, DragDropEffects.None);
735 Finish ();
736 return true;
739 control = c;
740 position_recieved = true;
742 if (converts_pending > 0)
743 return true;
744 if (!status_sent) {
745 drag_event = new DragEventArgs (data, 0, pos_x, pos_y,
746 allowed, DragDropEffects.None);
747 control.DndEnter (drag_event);
748 SendStatus (source, drag_event.Effect);
749 status_sent = true;
750 } else {
751 drag_event.x = pos_x;
752 drag_event.y = pos_y;
753 control.DndOver (drag_event);
754 SendStatus (source, drag_event.Effect);
757 return true;
760 private void Finish ()
762 if (control != null) {
763 if (drag_event == null) {
764 if (data == null)
765 data = new DataObject ();
766 drag_event = new DragEventArgs (data,
767 0, pos_x, pos_y,
768 allowed, DragDropEffects.None);
770 control.DndLeave (drag_event);
772 ResetTargetData ();
775 private bool Accepting_HandleDropEvent (ref XEvent xevent)
777 if (control != null && drag_event != null) {
778 drag_event = new DragEventArgs (data,
779 0, pos_x, pos_y,
780 allowed, DragDropEffects.None);
781 control.DndDrop (drag_event);
783 SendFinished ();
784 return true;
787 private bool Accepting_HandleLeaveEvent (ref XEvent xevent)
789 if (control != null && drag_event != null)
790 control.DndLeave (drag_event);
791 // Reset ();
792 return true;
795 private bool HandleStatusEvent (ref XEvent xevent)
797 if (drag_data != null && drag_data.State == DragState.Entered) {
799 if (!QueryContinue (xevent, false, DragAction.Continue))
800 return true;
802 drag_data.WillAccept = ((int) xevent.ClientMessageEvent.ptr2 & 0x1) != 0;
804 GiveFeedback (xevent.ClientMessageEvent.ptr5);
806 return true;
809 private DragDropEffects EffectFromAction (IntPtr action)
811 DragDropEffects allowed = DragDropEffects.None;
813 if (action == XdndActionCopy)
814 allowed = DragDropEffects.Copy;
815 else if (action == XdndActionMove)
816 allowed |= DragDropEffects.Move;
817 if (action == XdndActionLink)
818 allowed |= DragDropEffects.Link;
819 return allowed;
822 private IntPtr ActionFromEffect (DragDropEffects effect)
824 IntPtr action = IntPtr.Zero;
826 // We can't OR together actions on XDND so sadly the primary
827 // is the only one shown here
828 if ((effect & DragDropEffects.Copy) != 0)
829 action = XdndActionCopy;
830 else if ((effect & DragDropEffects.Move) != 0)
831 action = XdndActionMove;
832 else if ((effect & DragDropEffects.Link) != 0)
833 action = XdndActionLink;
834 return action;
837 private bool ConvertData (ref XEvent xevent)
839 bool match = false;
841 if (source != XplatUIX11.XGetSelectionOwner (display, XdndSelection)) {
842 return false;
845 Control mwfcontrol = MwfWindow (source);
847 if (mwfcontrol != null && drag_data != null) {
848 IDataObject dragged = drag_data.Data as IDataObject;
849 if (dragged != null) {
850 data = dragged;
851 } else {
852 if (data == null)
853 data = new DataObject ();
854 SetDataWithFormats (drag_data.Data);
856 return true;
859 foreach (IntPtr atom in SourceSupportedList (ref xevent)) {
860 MimeHandler handler = FindHandler (atom);
861 if (handler == null)
862 continue;
863 XplatUIX11.XConvertSelection (display, XdndSelection, handler.Type,
864 handler.NonProtocol, toplevel, IntPtr.Zero /* CurrentTime */);
865 converts_pending++;
866 match = true;
868 return match;
871 private void SetDataWithFormats (object value)
873 if (value is string) {
874 data.SetData (DataFormats.Text, value);
875 data.SetData (DataFormats.UnicodeText, value);
878 data.SetData (value);
881 private MimeHandler FindHandler (IntPtr atom)
883 if (atom == IntPtr.Zero)
884 return null;
885 foreach (MimeHandler handler in MimeHandlers) {
886 if (handler.Type == atom)
887 return handler;
889 return null;
892 private MimeHandler FindHandler (string name)
894 foreach (MimeHandler handler in MimeHandlers) {
895 foreach (string alias in handler.Aliases) {
896 if (alias == name)
897 return handler;
900 return null;
903 private void SendStatus (IntPtr source, DragDropEffects effect)
905 XEvent xevent = new XEvent ();
907 xevent.AnyEvent.type = XEventName.ClientMessage;
908 xevent.AnyEvent.display = display;
909 xevent.ClientMessageEvent.window = source;
910 xevent.ClientMessageEvent.message_type = XdndStatus;
911 xevent.ClientMessageEvent.format = 32;
912 xevent.ClientMessageEvent.ptr1 = toplevel;
913 if (effect != DragDropEffects.None)
914 xevent.ClientMessageEvent.ptr2 = (IntPtr) 1;
916 xevent.ClientMessageEvent.ptr5 = ActionFromEffect (effect);
917 XplatUIX11.XSendEvent (display, source, false, IntPtr.Zero, ref xevent);
920 private void SendEnter (IntPtr handle, IntPtr from, IntPtr [] supported)
922 XEvent xevent = new XEvent ();
924 xevent.AnyEvent.type = XEventName.ClientMessage;
925 xevent.AnyEvent.display = display;
926 xevent.ClientMessageEvent.window = handle;
927 xevent.ClientMessageEvent.message_type = XdndEnter;
928 xevent.ClientMessageEvent.format = 32;
929 xevent.ClientMessageEvent.ptr1 = from;
931 // (int) xevent.ClientMessageEvent.ptr2 & 0x1)
932 // int ptr2 = 0x1;
933 // xevent.ClientMessageEvent.ptr2 = (IntPtr) ptr2;
934 // (e)->xclient.data.l[1] = ((e)->xclient.data.l[1] & ~(0xFF << 24)) | ((v) << 24)
935 xevent.ClientMessageEvent.ptr2 = (IntPtr) ((long)XdndVersion [0] << 24);
937 if (supported.Length > 0)
938 xevent.ClientMessageEvent.ptr3 = supported [0];
939 if (supported.Length > 1)
940 xevent.ClientMessageEvent.ptr4 = supported [1];
941 if (supported.Length > 2)
942 xevent.ClientMessageEvent.ptr5 = supported [2];
944 XplatUIX11.XSendEvent (display, handle, false, IntPtr.Zero, ref xevent);
947 private void SendDrop (IntPtr handle, IntPtr from, IntPtr time)
949 XEvent xevent = new XEvent ();
951 xevent.AnyEvent.type = XEventName.ClientMessage;
952 xevent.AnyEvent.display = display;
953 xevent.ClientMessageEvent.window = handle;
954 xevent.ClientMessageEvent.message_type = XdndDrop;
955 xevent.ClientMessageEvent.format = 32;
956 xevent.ClientMessageEvent.ptr1 = from;
957 xevent.ClientMessageEvent.ptr3 = time;
959 XplatUIX11.XSendEvent (display, handle, false, IntPtr.Zero, ref xevent);
962 private void SendPosition (IntPtr handle, IntPtr from, IntPtr action, int x, int y, IntPtr time)
964 XEvent xevent = new XEvent ();
966 xevent.AnyEvent.type = XEventName.ClientMessage;
967 xevent.AnyEvent.display = display;
968 xevent.ClientMessageEvent.window = handle;
969 xevent.ClientMessageEvent.message_type = XdndPosition;
970 xevent.ClientMessageEvent.format = 32;
971 xevent.ClientMessageEvent.ptr1 = from;
972 xevent.ClientMessageEvent.ptr3 = (IntPtr) ((x << 16) | (y & 0xFFFF));
973 xevent.ClientMessageEvent.ptr4 = time;
974 xevent.ClientMessageEvent.ptr5 = action;
976 XplatUIX11.XSendEvent (display, handle, false, IntPtr.Zero, ref xevent);
979 private void SendLeave (IntPtr handle, IntPtr from)
981 XEvent xevent = new XEvent ();
983 xevent.AnyEvent.type = XEventName.ClientMessage;
984 xevent.AnyEvent.display = display;
985 xevent.ClientMessageEvent.window = handle;
986 xevent.ClientMessageEvent.message_type = XdndLeave;
987 xevent.ClientMessageEvent.format = 32;
988 xevent.ClientMessageEvent.ptr1 = from;
990 XplatUIX11.XSendEvent (display, handle, false, IntPtr.Zero, ref xevent);
993 private void SendFinished ()
995 XEvent xevent = new XEvent ();
997 xevent.AnyEvent.type = XEventName.ClientMessage;
998 xevent.AnyEvent.display = display;
999 xevent.ClientMessageEvent.window = source;
1000 xevent.ClientMessageEvent.message_type = XdndFinished;
1001 xevent.ClientMessageEvent.format = 32;
1002 xevent.ClientMessageEvent.ptr1 = toplevel;
1004 XplatUIX11.XSendEvent (display, source, false, IntPtr.Zero, ref xevent);
1007 // There is a somewhat decent amount of overhead
1008 // involved in setting up dnd so we do it lazily
1009 // as a lot of applications do not even use it.
1010 private void Init ()
1012 XdndAware = XplatUIX11.XInternAtom (display, "XdndAware", false);
1013 XdndEnter = XplatUIX11.XInternAtom (display, "XdndEnter", false);
1014 XdndLeave = XplatUIX11.XInternAtom (display, "XdndLeave", false);
1015 XdndPosition = XplatUIX11.XInternAtom (display, "XdndPosition", false);
1016 XdndStatus = XplatUIX11.XInternAtom (display, "XdndStatus", false);
1017 XdndDrop = XplatUIX11.XInternAtom (display, "XdndDrop", false);
1018 XdndSelection = XplatUIX11.XInternAtom (display, "XdndSelection", false);
1019 XdndFinished = XplatUIX11.XInternAtom (display, "XdndFinished", false);
1020 XdndTypeList = XplatUIX11.XInternAtom (display, "XdndTypeList", false);
1021 XdndActionCopy = XplatUIX11.XInternAtom (display, "XdndActionCopy", false);
1022 XdndActionMove = XplatUIX11.XInternAtom (display, "XdndActionMove", false);
1023 XdndActionLink = XplatUIX11.XInternAtom (display, "XdndActionLink", false);
1024 //XdndActionPrivate = XplatUIX11.XInternAtom (display, "XdndActionPrivate", false);
1025 //XdndActionList = XplatUIX11.XInternAtom (display, "XdndActionList", false);
1026 //XdndActionDescription = XplatUIX11.XInternAtom (display, "XdndActionDescription", false);
1027 //XdndActionAsk = XplatUIX11.XInternAtom (display, "XdndActionAsk", false);
1029 foreach (MimeHandler handler in MimeHandlers) {
1030 handler.Type = XplatUIX11.XInternAtom (display, handler.Name, false);
1031 handler.NonProtocol = XplatUIX11.XInternAtom (display,
1032 String.Concat ("MWFNonP+", handler.Name), false);
1037 private IntPtr [] SourceSupportedList (ref XEvent xevent)
1039 IntPtr [] res;
1042 if (((int) xevent.ClientMessageEvent.ptr2 & 0x1) == 0) {
1043 res = new IntPtr [3];
1044 res [0] = xevent.ClientMessageEvent.ptr3;
1045 res [1] = xevent.ClientMessageEvent.ptr4;
1046 res [2] = xevent.ClientMessageEvent.ptr5;
1047 } else {
1048 IntPtr type;
1049 int format;
1050 IntPtr count;
1051 IntPtr remaining;
1052 IntPtr data = IntPtr.Zero;
1054 XplatUIX11.XGetWindowProperty (display, source, XdndTypeList,
1055 IntPtr.Zero, new IntPtr(32), false, (IntPtr) Atom.XA_ATOM,
1056 out type, out format, out count,
1057 out remaining, ref data);
1059 res = new IntPtr [count.ToInt32()];
1060 for (int i = 0; i < count.ToInt32(); i++) {
1061 res [i] = (IntPtr) Marshal.ReadInt32 (data, i *
1062 Marshal.SizeOf (typeof (int)));
1065 XplatUIX11.XFree (data);
1068 return res;
1071 private string GetText (ref XEvent xevent, bool unicode)
1073 int nread = 0;
1074 IntPtr nitems;
1075 IntPtr bytes_after;
1077 StringBuilder builder = new StringBuilder ();
1078 do {
1079 IntPtr actual_type;
1080 int actual_fmt;
1081 IntPtr data = IntPtr.Zero;
1083 if (0 != XplatUIX11.XGetWindowProperty (display,
1084 xevent.AnyEvent.window,
1085 (IntPtr) xevent.SelectionEvent.property,
1086 IntPtr.Zero, new IntPtr(0xffffff), false,
1087 (IntPtr) Atom.AnyPropertyType, out actual_type,
1088 out actual_fmt, out nitems, out bytes_after,
1089 ref data)) {
1090 XplatUIX11.XFree (data);
1091 break;
1094 if (unicode)
1095 builder.Append (Marshal.PtrToStringUni (data));
1096 else
1097 builder.Append (Marshal.PtrToStringAnsi (data));
1098 nread += nitems.ToInt32();
1100 XplatUIX11.XFree (data);
1101 } while (bytes_after.ToInt32() > 0);
1102 if (nread == 0)
1103 return null;
1104 return builder.ToString ();
1107 private MemoryStream GetData (ref XEvent xevent)
1109 int nread = 0;
1110 IntPtr nitems;
1111 IntPtr bytes_after;
1113 MemoryStream res = new MemoryStream ();
1114 do {
1115 IntPtr actual_type;
1116 int actual_fmt;
1117 IntPtr data = IntPtr.Zero;
1119 if (0 != XplatUIX11.XGetWindowProperty (display,
1120 xevent.AnyEvent.window,
1121 (IntPtr) xevent.SelectionEvent.property,
1122 IntPtr.Zero, new IntPtr(0xffffff), false,
1123 (IntPtr) Atom.AnyPropertyType, out actual_type,
1124 out actual_fmt, out nitems, out bytes_after,
1125 ref data)) {
1126 XplatUIX11.XFree (data);
1127 break;
1130 for (int i = 0; i < nitems.ToInt32(); i++)
1131 res.WriteByte (Marshal.ReadByte (data, i));
1132 nread += nitems.ToInt32();
1134 XplatUIX11.XFree (data);
1135 } while (bytes_after.ToInt32() > 0);
1136 return res;
1139 private Control MwfWindow (IntPtr window)
1141 Hwnd hwnd = Hwnd.ObjectFromHandle (window);
1142 if (hwnd == null)
1143 return null;
1145 Control res = Control.FromHandle (hwnd.client_window);
1146 return res;
1149 private bool IsWindowDndAware (IntPtr handle)
1151 bool res = true;
1152 // Check the version number, we need greater than 3
1153 IntPtr actual;
1154 int format;
1155 IntPtr count;
1156 IntPtr remaining;
1157 IntPtr data = IntPtr.Zero;
1159 XplatUIX11.XGetWindowProperty (display, handle, XdndAware, IntPtr.Zero, new IntPtr(0x8000000), false,
1160 (IntPtr) Atom.XA_ATOM, out actual, out format,
1161 out count, out remaining, ref data);
1163 if (actual != (IntPtr) Atom.XA_ATOM || format != 32 ||
1164 count.ToInt32() == 0 || data == IntPtr.Zero) {
1165 if (data != IntPtr.Zero)
1166 XplatUIX11.XFree (data);
1167 return false;
1170 int version = Marshal.ReadInt32 (data, 0);
1172 if (version < 3) {
1173 Console.Error.WriteLine ("XDND Version too old (" + version + ").");
1174 XplatUIX11.XFree (data);
1175 return false;
1178 // First type is actually the XDND version
1179 if (count.ToInt32() > 1) {
1180 res = false;
1181 for (int i = 1; i < count.ToInt32(); i++) {
1182 IntPtr type = (IntPtr) Marshal.ReadInt32 (data, i *
1183 Marshal.SizeOf (typeof (int)));
1184 for (int j = 0; j < drag_data.SupportedTypes.Length; j++) {
1185 if (drag_data.SupportedTypes [j] == type) {
1186 res = true;
1187 break;
1193 XplatUIX11.XFree (data);
1194 return res;
1197 private IntPtr [] DetermineSupportedTypes (object data)
1199 ArrayList res = new ArrayList ();
1201 if (data is string) {
1202 MimeHandler handler = FindHandler ("text/plain");
1203 if (handler != null)
1204 res.Add (handler.Type);
1205 }/* else if (data is Bitmap)
1206 res.Add (data);
1210 IDataObject data_object = data as IDataObject;
1211 if (data_object != null) {
1212 foreach (string format in data_object.GetFormats (true)) {
1213 MimeHandler handler = FindHandler (format);
1214 if (handler != null && !res.Contains (handler.Type))
1215 res.Add (handler.Type);
1219 if (data is ISerializable) {
1220 MimeHandler handler = FindHandler ("application/x-mono-serialized-object");
1221 if (handler != null)
1222 res.Add (handler.Type);
1225 return (IntPtr []) res.ToArray (typeof (IntPtr));