XplatUIX11.cs: Tie into the X11Dnd subsystem.
[mono.git] / mcs / class / Managed.Windows.Forms / System.Windows.Forms / X11Dnd.cs
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.
19 //
20 // Copyright (c) 2005 Novell, Inc.
21 //
22 // Authors:
23 //      Jackson Harper (jackson@ximian.com)
24 //
25 //
26
27
28 using System;
29 using System.Text;
30 using System.Collections;
31 using System.Runtime.InteropServices;
32
33
34 namespace System.Windows.Forms {
35
36         internal class X11Dnd {
37
38                 private delegate void MimeConverter (IntPtr dsp,
39                                 DataObject data, ref XEvent xevent);
40
41                 private class MimeHandler {
42                         public string Name;
43                         public IntPtr Type;
44                         public IntPtr NonProtocol;
45                         public MimeConverter Convert;
46                         
47                         public MimeHandler (string name, MimeConverter converter)
48                         {
49                                 Name = name;
50                                 Convert = converter;
51                         }
52                 }
53
54                 private MimeHandler [] MimeHandlers = {
55 //                        new MimeHandler ("WCF_DIB"),
56 //                        new MimeHandler ("image/gif", new MimeConverter (ImageConverter)),
57
58                         
59                         new MimeHandler ("text/rtf", new MimeConverter (RtfConverter)),
60                         new MimeHandler ("text/richtext", new MimeConverter (RtfConverter)),
61                         new MimeHandler ("text/plain", new MimeConverter (TextConverter)),
62                         new MimeHandler ("text/html", new MimeConverter (HtmlConverter)),
63                         new MimeHandler ("text/uri-list", new MimeConverter (UriListConverter)),
64                 };
65
66                 // This version seems to be the most common
67                 private static readonly uint [] XdndVersion = new uint [] { 4 }; 
68
69                 private IntPtr display;
70                 
71              
72                 private bool initialized;
73
74                 private IntPtr XdndAware;
75                 private IntPtr XdndSelection;
76                 private IntPtr XdndEnter;
77                 private IntPtr XdndLeave;
78                 private IntPtr XdndPosition;
79                 private IntPtr XdndDrop;
80                 private IntPtr XdndFinished;
81                 private IntPtr XdndStatus;
82                 private IntPtr XdndTypeList;
83                 private IntPtr XdndActionCopy;
84                 private IntPtr XdndActionMove;
85                 private IntPtr XdndActionLink;
86                 private IntPtr XdndActionPrivate;
87                 private IntPtr XdndActionList;
88                 private IntPtr XdndActionDescription;
89                 private IntPtr XdndActionAsk;
90
91                 private int converts_pending;
92                 private bool position_recieved;
93                 private bool status_sent;
94                 private IntPtr target;
95                 private IntPtr source;
96                 private IntPtr toplevel;
97                 private DataObject data;
98                 private Control control;
99                 private int pos_x, pos_y;
100                 private DragDropEffects allowed;
101                 private DragEventArgs drag_event;
102
103                 public X11Dnd (IntPtr display)
104                 {
105                         this.display = display;
106                 }
107
108                 public void SetAllowDrop (Hwnd hwnd, bool allow)
109                 {
110                         if (!initialized)
111                                 Init ();
112
113 //                        if (hwnd.allow_drop == allow)
114 //                                return;
115
116                         XChangeProperty (display, hwnd.whole_window, XdndAware,
117                                         (IntPtr) Atom.XA_ATOM, 32,
118                                         PropertyMode.Replace, XdndVersion, allow ? 1 : 0);
119 //                        hwnd.allow_drop = allow;
120                 }
121
122                 // return true if the event is handled here
123                 public bool HandleClientMessage (ref XEvent xevent)
124                 {
125                         if (!initialized)
126                                 Init ();
127
128                         // most common so we check it first
129                         if (xevent.ClientMessageEvent.message_type == XdndPosition)
130                                 return HandlePositionEvent (ref xevent);
131                         if (xevent.ClientMessageEvent.message_type == XdndEnter)
132                                 return HandleEnterEvent (ref xevent);
133                         if (xevent.ClientMessageEvent.message_type == XdndDrop)
134                                 return HandleDropEvent (ref xevent);
135                         if (xevent.ClientMessageEvent.message_type == XdndLeave)
136                                 return HandleLeaveEvent (ref xevent);
137                         
138                         return false;
139                 }
140
141                 public bool HandleSelectionNotifyEvent (ref XEvent xevent)
142                 {
143                         if (source != XGetSelectionOwner (display, XdndSelection))
144                                 return false;
145
146                         MimeHandler handler = FindHandler ((IntPtr) xevent.SelectionEvent.target);
147                         if (handler == null)
148                                 return false;
149                         if (data == null)
150                                 data = new DataObject ();
151
152                         handler.Convert (display, data, ref xevent);
153
154                         converts_pending--;
155                         if (converts_pending <= 0 && position_recieved)
156                                 SendEnterStatus ();
157                         return true;
158                 }
159
160                 private void Reset ()
161                 {
162                         ResetSourceData ();
163                         ResetTargetData ();
164                 }
165
166                 private void ResetSourceData ()
167                 {
168                         converts_pending = 0;
169                         data = null;
170                 }
171
172                 private void ResetTargetData ()
173                 {
174                         position_recieved = false;
175                         status_sent = false;
176                 }
177                 
178                 private bool HandleEnterEvent (ref XEvent xevent)
179                 {
180                         Reset ();
181
182                         source = xevent.ClientMessageEvent.ptr1;
183                         toplevel = xevent.AnyEvent.window;
184                         target = IntPtr.Zero;
185
186                         ConvertData (ref xevent);
187
188                         return true;
189                 }
190
191                 private bool HandlePositionEvent (ref XEvent xevent)
192                 {
193                         int x = (int) xevent.ClientMessageEvent.ptr3 >> 16;
194                         int y = (int) xevent.ClientMessageEvent.ptr3 & 0xFFFF;
195
196                         allowed = EffectFromAction (xevent.ClientMessageEvent.ptr5);
197
198                         IntPtr parent, child, new_child;
199                         parent = XplatUIX11.XRootWindow (display, 0);
200                         child = toplevel;
201                         while (true) {
202                                 int xd, yd;
203                                 new_child = IntPtr.Zero;
204                                 
205                                 if (!XplatUIX11.XTranslateCoordinates (display,
206                                                     parent, child, x, y,
207                                                     out xd, out yd, out new_child))
208                                         break;
209                                 if (new_child == IntPtr.Zero)
210                                         break;
211                                 child = new_child;
212                         }
213
214                         if (target != child) {
215                                 // We have moved into a new control 
216                                 // or into a control for the first time
217                                 Finish ();
218                         }
219                         target = child;
220                         Hwnd hwnd = Hwnd.ObjectFromHandle (target);
221                         Control c = Control.FromHandle (hwnd.client_window);
222
223                         if (c == null)
224                                 return true;
225 //                      if (!c.AllowDrop) {
226 //                              Finish ();
227 //                              return true;
228 //                      }
229
230                         control = c;
231                         position_recieved = true;                       
232
233                         if (converts_pending > 0)
234                                 return true;
235                         if (!status_sent) {
236                                 SendEnterStatus ();
237                         } else {
238                                 SendStatus ();
239                                 control.DndOver (drag_event);
240                         }
241                         
242                         return true;
243                 }
244
245                 private void Finish ()
246                 {
247                         if (control != null) {
248                                 if (drag_event == null) {
249                                         if (data == null)
250                                                 data = new DataObject ();
251                                         drag_event = new DragEventArgs (data,
252                                                         0, pos_x, pos_y,
253                                         allowed, DragDropEffects.None);
254                                 }
255                                 control.DndLeave (drag_event);
256                         }
257                         ResetTargetData ();
258                 }
259
260                 private bool HandleDropEvent (ref XEvent xevent)
261                 {
262                         Console.WriteLine ("DROPPING EVENT");
263                         if (control != null && drag_event != null)
264                                 control.DndDrop (drag_event);
265                         SendFinished ();
266                         return true;
267                 }
268
269                 private bool HandleLeaveEvent (ref XEvent xevent)
270                 {
271                         if (control != null && drag_event != null)
272                                 control.DndLeave (drag_event);
273                         Reset ();
274                         return true;
275                 }
276
277                 private DragDropEffects EffectFromAction (IntPtr action)
278                 {
279                         DragDropEffects allowed = DragDropEffects.None;
280                         if (action == XdndActionCopy)
281                                 allowed = DragDropEffects.Copy;
282                         if (action == XdndActionMove)
283                                 allowed = DragDropEffects.Move;
284                         if (action == XdndActionLink)
285                                 allowed = DragDropEffects.Link;
286                         return allowed;
287                 }
288
289                 private IntPtr ActionFromEffect (DragDropEffects effect)
290                 {
291                         IntPtr action = IntPtr.Zero;
292                         if (effect == DragDropEffects.Copy)
293                                 action = XdndActionCopy;
294                         if (effect == DragDropEffects.Move)
295                                 action = XdndActionMove;
296                         if (effect == DragDropEffects.Link)
297                                 action = XdndActionLink;
298                         return action;
299                 }
300
301                 private bool ConvertData (ref XEvent xevent)
302                 {
303                         bool match = false;
304
305                         foreach (IntPtr atom in SourceSupportedList (ref xevent)) {
306                                 MimeHandler handler = FindHandler (atom);
307                                 if (handler == null)
308                                         continue;
309                                 XConvertSelection (display, XdndSelection, handler.Type,
310                                         handler.NonProtocol, toplevel, 0 /* CurrentTime */);
311                                 converts_pending++;
312                                 match = true;
313                         }
314                         return match;
315                 }
316
317                 private MimeHandler FindHandler (IntPtr atom)
318                 {
319                         if (atom == IntPtr.Zero)
320                                 return null;
321                         foreach (MimeHandler handler in MimeHandlers) {
322                                 if (handler.Type == atom)
323                                         return handler;
324                         }
325                         return null;
326                 }
327
328                 private void SendStatus ()
329                 {
330                         DragDropEffects action = drag_event.Effect;
331                         XEvent xevent = new XEvent ();
332
333                         xevent.AnyEvent.type = XEventName.ClientMessage;
334                         xevent.AnyEvent.display = display;
335                         xevent.ClientMessageEvent.window = source;
336                         xevent.ClientMessageEvent.message_type = XdndStatus;
337                         xevent.ClientMessageEvent.format = 32;
338                         xevent.ClientMessageEvent.ptr1 = toplevel;
339                         if (drag_event.Effect != DragDropEffects.None)
340                                 xevent.ClientMessageEvent.ptr2 = (IntPtr) 1;
341
342                         xevent.ClientMessageEvent.ptr5 = ActionFromEffect (action);
343                         XSendEvent (display, source, false, 0, ref xevent);
344                 }
345
346                 private void SendEnterStatus ()
347                 {
348                         drag_event = new DragEventArgs (data, 0, pos_x, pos_y,
349                                         allowed, DragDropEffects.None);
350                         control.DndEnter (drag_event);
351
352                         XEvent xevent = new XEvent ();
353
354                         xevent.AnyEvent.type = XEventName.ClientMessage;
355                         xevent.AnyEvent.display = display;
356                         xevent.ClientMessageEvent.window = source;
357                         xevent.ClientMessageEvent.message_type = XdndStatus;
358                         xevent.ClientMessageEvent.format = 32;
359                         xevent.ClientMessageEvent.ptr1 = toplevel;
360                         if (drag_event.Effect != DragDropEffects.None)
361                                 xevent.ClientMessageEvent.ptr2 = (IntPtr) 1;
362
363                         xevent.ClientMessageEvent.ptr5 = ActionFromEffect (drag_event.Effect);
364                         XSendEvent (display, source, false, 0, ref xevent);
365
366                         status_sent = true;
367                 }
368
369                 private void SendFinished ()
370                 {
371                         XEvent xevent = new XEvent ();
372
373                         xevent.AnyEvent.type = XEventName.ClientMessage;
374                         xevent.AnyEvent.display = display;
375                         xevent.ClientMessageEvent.window = source;
376                         xevent.ClientMessageEvent.message_type = XdndFinished;
377                         xevent.ClientMessageEvent.format = 32;
378                         xevent.ClientMessageEvent.ptr1 = toplevel;
379
380                         XSendEvent (display, source, false, 0, ref xevent);
381                 }
382
383                 // There is a somewhat decent amount of overhead
384                 // involved in setting up dnd so we do it lazily
385                 // as a lot of applications do not even use it.
386                 private void Init ()
387                 {
388                         XdndAware = XInternAtom (display, "XdndAware", false);
389                         XdndEnter = XInternAtom (display, "XdndEnter", false);
390                         XdndLeave = XInternAtom (display, "XdndLeave", false);
391                         XdndPosition = XInternAtom (display, "XdndPosition", false);
392                         XdndStatus = XInternAtom (display, "XdndStatus", false);
393                         XdndDrop = XInternAtom (display, "XdndDrop", false);
394                         XdndSelection = XInternAtom (display, "XdndSelection", false);
395                         XdndFinished = XInternAtom (display, "XdndFinished", false);
396                         XdndTypeList = XInternAtom (display, "XdndTypeList", false);
397                         XdndActionCopy = XInternAtom (display, "XdndActionCopy", false);
398                         XdndActionMove = XInternAtom (display, "XdndActionMove", false);
399                         XdndActionLink = XInternAtom (display, "XdndActionLink", false);
400                         XdndActionPrivate = XInternAtom (display, "XdndActionPrivate", false);
401                         XdndActionList = XInternAtom (display, "XdndActionList", false);
402                         XdndActionDescription = XInternAtom (display, "XdndActionDescription", false);
403                         XdndActionAsk = XInternAtom (display, "XdndActionAsk", false);
404
405                         foreach (MimeHandler handler in MimeHandlers) {
406                                 handler.Type = XInternAtom (display, handler.Name, false);
407                                 handler.NonProtocol = XInternAtom (display,
408                                                 String.Concat ("MWFNonP+", handler.Name), false);
409                         }
410
411                         initialized = true;
412                 }
413
414                 private IntPtr [] SourceSupportedList (ref XEvent xevent)
415                 {
416                         IntPtr [] res;
417
418                         if (((int) xevent.ClientMessageEvent.ptr2 & 0x1) == 0) {
419                                 res = new IntPtr [3];
420                                 res [0] = xevent.ClientMessageEvent.ptr3;
421                                 res [1] = xevent.ClientMessageEvent.ptr4;
422                                 res [2] = xevent.ClientMessageEvent.ptr5;
423                         } else {
424                                 IntPtr type;
425                                 int format, count, remaining;
426                                 IntPtr data = IntPtr.Zero;
427
428                                 XGetWindowProperty (display, source, XdndTypeList,
429                                                 0, 32, false, (IntPtr) Atom.XA_ATOM,
430                                                 out type, out format, out count,
431                                                 out remaining, out data);
432
433                                 res = new IntPtr [count];
434                                 for (int i = 0; i < count; i++) {
435                                         res [i] = (IntPtr) Marshal.ReadInt32 (data, i * sizeof (int));
436                                 }
437                         }
438
439                         return res;
440                 }
441
442                 private static void UriListConverter (IntPtr display, DataObject data,
443                                 ref XEvent xevent)
444                 {
445                         string text = GetText (display, ref xevent, false);
446                         if (text == null)
447                                 return;
448
449                         // TODO: Do this in a loop instead of just splitting
450                         ArrayList uri_list = new ArrayList ();
451                         string [] lines = text.Split (new char [] { '\r', '\n' });
452                         foreach (string line in lines) {
453                                 // # is a comment line (see RFC 2483)
454                                 if (line.StartsWith ("#"))
455                                         continue;
456                                 try {
457                                         Uri uri = new Uri (line);
458                                         uri_list.Add (uri.LocalPath);
459                                 } catch { }
460                         }
461
462                         string [] l = (string []) uri_list.ToArray (typeof (string));
463                         if (l.Length < 1)
464                                 return;
465                         data.SetData (DataFormats.FileDrop, l);
466                         data.SetData ("FileName", l [0]);
467                         data.SetData ("FileNameW", l [0]);
468                 }
469
470                 private static void TextConverter (IntPtr display, DataObject data, ref XEvent xevent)
471                 {
472                         string text = GetText (display, ref xevent, false);
473                         if (text == null)
474                                 return;
475                         data.SetData (DataFormats.Text, text);
476                         data.SetData (DataFormats.UnicodeText, text);
477                 }
478
479                 private static void HtmlConverter (IntPtr display, DataObject data, ref XEvent xevent)
480                 {
481                         string html = GetText (display, ref xevent, true);
482                         if (html == null)
483                                 return;
484                         data.SetData (DataFormats.Html, html);
485                 }
486
487                 private static void ImageConverter (IntPtr display, DataObject data, ref XEvent xevent)
488                 {
489                 }
490
491                 private static void RtfConverter (IntPtr display, DataObject data, ref XEvent xevent)
492                 {
493                 }
494
495                 private static string GetText (IntPtr display, ref XEvent xevent, bool unicode)
496                 {
497                         int nread = 0;
498                         int nitems;
499                         int bytes_after;
500
501                         StringBuilder builder = new StringBuilder ();
502                         do {
503                                 IntPtr actual_type;
504                                 int actual_fmt;
505                                 IntPtr data = IntPtr.Zero;
506
507                                 if (0 != XGetWindowProperty (display,
508                                                     xevent.AnyEvent.window,
509                                                     (IntPtr) xevent.SelectionEvent.property,
510                                                     0, 0xffffff, false,
511                                                     (IntPtr) Atom.AnyPropertyType, out actual_type,
512                                                     out actual_fmt, out nitems, out bytes_after,
513                                                     out data)) {
514                                         XFree (data);
515                                         break;
516                                 }
517
518                                 if (unicode)
519                                         builder.Append (Marshal.PtrToStringUni (data));
520                                 else
521                                         builder.Append (Marshal.PtrToStringAnsi (data));
522                                 nread += nitems;
523
524                                 XFree (data);
525                         } while (bytes_after > 0);
526                         if (nread == 0)
527                                 return null;
528                         return builder.ToString ();
529                 }
530
531                 [DllImport ("libX11")]
532                 private extern static string XGetAtomName (IntPtr display, IntPtr atom);
533
534                 [DllImport ("libX11")]
535                 private extern static IntPtr XInternAtom (IntPtr display, string atom_name, bool only_if_exists);
536
537                 [DllImport ("libX11")]
538                 private extern static int XChangeProperty (IntPtr display, IntPtr window, IntPtr property,
539                                 IntPtr format, int type, PropertyMode  mode, uint [] atoms, int nelements);
540
541                 [DllImport ("libX11")]
542                 private extern static int XGetWindowProperty (IntPtr display, IntPtr window,
543                                 IntPtr atom, int long_offset, int long_length, bool delete,
544                                 IntPtr req_type, out IntPtr actual_type, out int actual_format,
545                                 out int nitems, out int bytes_after, out IntPtr prop);
546
547                 [DllImport ("libX11")]
548                 internal extern static int XSendEvent (IntPtr display, IntPtr window,
549                                 bool propagate, EventMask event_mask, ref XEvent send_event);
550
551                 [DllImport ("libX11")]
552                 internal extern static int XConvertSelection (IntPtr display, IntPtr selection,
553                                 IntPtr target, IntPtr property, IntPtr requestor, int time);
554
555                 [DllImport ("libX11")]
556                 internal extern static IntPtr XGetSelectionOwner(IntPtr display, IntPtr selection);
557
558                 [DllImport ("libX11")]
559                 internal extern static int XFree(IntPtr data);
560         }
561
562 }