1422 lines
40 KiB
C#
1422 lines
40 KiB
C#
|
// Permission is hereby granted, free of charge, to any person obtaining
|
||
|
// a copy of this software and associated documentation files (the
|
||
|
// "Software"), to deal in the Software without restriction, including
|
||
|
// without limitation the rights to use, copy, modify, merge, publish,
|
||
|
// distribute, sublicense, and/or sell copies of the Software, and to
|
||
|
// permit persons to whom the Software is furnished to do so, subject to
|
||
|
// the following conditions:
|
||
|
//
|
||
|
// The above copyright notice and this permission notice shall be
|
||
|
// included in all copies or substantial portions of the Software.
|
||
|
//
|
||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||
|
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||
|
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||
|
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||
|
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||
|
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||
|
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||
|
//
|
||
|
// Copyright (c) 2005 Novell, Inc.
|
||
|
//
|
||
|
// Authors:
|
||
|
// Jackson Harper (jackson@ximian.com)
|
||
|
//
|
||
|
// NOTE: We have some tests in Test/System.Windows.Forms/DragAndDropTest.cs, which I *highly* recommend
|
||
|
// to run after any change made here, since those tests are interactive, and thus are not part of
|
||
|
// the common tests.
|
||
|
//
|
||
|
|
||
|
|
||
|
using System;
|
||
|
using System.IO;
|
||
|
using System.Text;
|
||
|
using System.Drawing;
|
||
|
using System.Threading;
|
||
|
using System.Collections;
|
||
|
using System.Runtime.Serialization;
|
||
|
using System.Runtime.InteropServices;
|
||
|
using System.Runtime.Serialization.Formatters.Binary;
|
||
|
|
||
|
namespace System.Windows.Forms {
|
||
|
|
||
|
internal class X11Dnd {
|
||
|
|
||
|
private enum State {
|
||
|
Accepting,
|
||
|
Dragging
|
||
|
}
|
||
|
|
||
|
private enum DragState {
|
||
|
None,
|
||
|
Beginning,
|
||
|
Dragging,
|
||
|
Entered
|
||
|
}
|
||
|
|
||
|
private interface IDataConverter {
|
||
|
void GetData (X11Dnd dnd, IDataObject data, ref XEvent xevent);
|
||
|
void SetData (X11Dnd dnd, object data, ref XEvent xevent);
|
||
|
}
|
||
|
|
||
|
private delegate void MimeConverter (IntPtr dsp,
|
||
|
IDataObject data, ref XEvent xevent);
|
||
|
|
||
|
private class MimeHandler {
|
||
|
public string Name;
|
||
|
public string [] Aliases;
|
||
|
public IntPtr Type;
|
||
|
public IntPtr NonProtocol;
|
||
|
public IDataConverter Converter;
|
||
|
|
||
|
public MimeHandler (string name, IDataConverter converter) : this (name, converter, name)
|
||
|
{
|
||
|
}
|
||
|
|
||
|
public MimeHandler (string name, IDataConverter converter, params string [] aliases)
|
||
|
{
|
||
|
Name = name;
|
||
|
Converter = converter;
|
||
|
Aliases = aliases;
|
||
|
}
|
||
|
|
||
|
public override string ToString ()
|
||
|
{
|
||
|
return "MimeHandler {" + Name + "}";
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private MimeHandler [] MimeHandlers = {
|
||
|
// new MimeHandler ("WCF_DIB"),
|
||
|
// new MimeHandler ("image/gif", new MimeConverter (ImageConverter)),
|
||
|
// new MimeHandler ("text/rtf", new MimeConverter (RtfConverter)),
|
||
|
// new MimeHandler ("text/richtext", new MimeConverter (RtfConverter)),
|
||
|
|
||
|
new MimeHandler ("text/plain", new TextConverter ()),
|
||
|
new MimeHandler ("text/plain", new TextConverter (), "System.String", DataFormats.Text),
|
||
|
new MimeHandler ("text/html", new HtmlConverter (), DataFormats.Html),
|
||
|
new MimeHandler ("text/uri-list", new UriListConverter (), DataFormats.FileDrop),
|
||
|
new MimeHandler ("application/x-mono-serialized-object",
|
||
|
new SerializedObjectConverter ())
|
||
|
};
|
||
|
|
||
|
private class SerializedObjectConverter : IDataConverter {
|
||
|
|
||
|
public void GetData (X11Dnd dnd, IDataObject data, ref XEvent xevent)
|
||
|
{
|
||
|
MemoryStream stream = dnd.GetData (ref xevent);
|
||
|
BinaryFormatter bf = new BinaryFormatter ();
|
||
|
|
||
|
if (stream.Length == 0)
|
||
|
return;
|
||
|
|
||
|
stream.Seek (0, 0);
|
||
|
object obj = bf.Deserialize (stream);
|
||
|
data.SetData (obj);
|
||
|
}
|
||
|
|
||
|
public void SetData (X11Dnd dnd, object data, ref XEvent xevent)
|
||
|
{
|
||
|
if (data == null)
|
||
|
return;
|
||
|
|
||
|
MemoryStream stream = new MemoryStream ();
|
||
|
BinaryFormatter bf = new BinaryFormatter ();
|
||
|
|
||
|
bf.Serialize (stream, data);
|
||
|
|
||
|
IntPtr buffer = Marshal.AllocHGlobal ((int) stream.Length);
|
||
|
stream.Seek (0, 0);
|
||
|
|
||
|
for (int i = 0; i < stream.Length; i++) {
|
||
|
Marshal.WriteByte (buffer, i, (byte) stream.ReadByte ());
|
||
|
}
|
||
|
|
||
|
dnd.SetProperty (ref xevent, buffer, (int) stream.Length);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private class HtmlConverter : IDataConverter {
|
||
|
|
||
|
public void GetData (X11Dnd dnd, IDataObject data, ref XEvent xevent)
|
||
|
{
|
||
|
string text = dnd.GetText (ref xevent, false);
|
||
|
if (text == null)
|
||
|
return;
|
||
|
data.SetData (DataFormats.Text, text);
|
||
|
data.SetData (DataFormats.UnicodeText, text);
|
||
|
}
|
||
|
|
||
|
public void SetData (X11Dnd dnd, object data, ref XEvent xevent)
|
||
|
{
|
||
|
IntPtr buffer;
|
||
|
int len;
|
||
|
string str = data as string;
|
||
|
|
||
|
if (str == null)
|
||
|
return;
|
||
|
|
||
|
if (xevent.SelectionRequestEvent.target == (IntPtr)Atom.XA_STRING) {
|
||
|
byte [] bytes = Encoding.ASCII.GetBytes (str);
|
||
|
buffer = Marshal.AllocHGlobal (bytes.Length);
|
||
|
len = bytes.Length;
|
||
|
for (int i = 0; i < len; i++)
|
||
|
Marshal.WriteByte (buffer, i, bytes [i]);
|
||
|
} else {
|
||
|
buffer = Marshal.StringToHGlobalAnsi (str);
|
||
|
len = 0;
|
||
|
while (Marshal.ReadByte (buffer, len) != 0)
|
||
|
len++;
|
||
|
}
|
||
|
|
||
|
dnd.SetProperty (ref xevent, buffer, len);
|
||
|
|
||
|
Marshal.FreeHGlobal (buffer);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private class TextConverter : IDataConverter {
|
||
|
|
||
|
public void GetData (X11Dnd dnd, IDataObject data, ref XEvent xevent)
|
||
|
{
|
||
|
string text = dnd.GetText (ref xevent, true);
|
||
|
if (text == null)
|
||
|
return;
|
||
|
data.SetData (DataFormats.Text, text);
|
||
|
data.SetData (DataFormats.UnicodeText, text);
|
||
|
}
|
||
|
|
||
|
public void SetData (X11Dnd dnd, object data, ref XEvent xevent)
|
||
|
{
|
||
|
IntPtr buffer;
|
||
|
int len;
|
||
|
string str = data as string;
|
||
|
|
||
|
if (str == null) {
|
||
|
IDataObject dobj = data as IDataObject;
|
||
|
if (dobj == null)
|
||
|
return;
|
||
|
str = (string) dobj.GetData ("System.String", true);
|
||
|
}
|
||
|
|
||
|
if (xevent.SelectionRequestEvent.target == (IntPtr)Atom.XA_STRING) {
|
||
|
byte [] bytes = Encoding.ASCII.GetBytes (str);
|
||
|
buffer = Marshal.AllocHGlobal (bytes.Length);
|
||
|
len = bytes.Length;
|
||
|
for (int i = 0; i < len; i++)
|
||
|
Marshal.WriteByte (buffer, i, bytes [i]);
|
||
|
} else {
|
||
|
buffer = Marshal.StringToHGlobalAnsi (str);
|
||
|
len = 0;
|
||
|
while (Marshal.ReadByte (buffer, len) != 0)
|
||
|
len++;
|
||
|
}
|
||
|
|
||
|
dnd.SetProperty (ref xevent, buffer, len);
|
||
|
|
||
|
Marshal.FreeHGlobal (buffer);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private class UriListConverter : IDataConverter {
|
||
|
|
||
|
public void GetData (X11Dnd dnd, IDataObject data, ref XEvent xevent)
|
||
|
{
|
||
|
string text = dnd.GetText (ref xevent, false);
|
||
|
if (text == null)
|
||
|
return;
|
||
|
|
||
|
// TODO: Do this in a loop instead of just splitting
|
||
|
ArrayList uri_list = new ArrayList ();
|
||
|
string [] lines = text.Split (new char [] { '\r', '\n' });
|
||
|
foreach (string line in lines) {
|
||
|
// # is a comment line (see RFC 2483)
|
||
|
if (line.StartsWith ("#"))
|
||
|
continue;
|
||
|
try {
|
||
|
Uri uri = new Uri (line);
|
||
|
uri_list.Add (uri.LocalPath);
|
||
|
} catch { }
|
||
|
}
|
||
|
|
||
|
string [] l = (string []) uri_list.ToArray (typeof (string));
|
||
|
if (l.Length < 1)
|
||
|
return;
|
||
|
data.SetData (DataFormats.FileDrop, l);
|
||
|
data.SetData ("FileName", l [0]);
|
||
|
data.SetData ("FileNameW", l [0]);
|
||
|
}
|
||
|
|
||
|
public void SetData (X11Dnd dnd, object data, ref XEvent xevent)
|
||
|
{
|
||
|
string [] uri_list = data as string [];
|
||
|
|
||
|
if (uri_list == null) {
|
||
|
IDataObject dobj = data as IDataObject;
|
||
|
if (dobj == null)
|
||
|
return;
|
||
|
uri_list = dobj.GetData (DataFormats.FileDrop, true) as string [];
|
||
|
}
|
||
|
|
||
|
if (uri_list == null)
|
||
|
return;
|
||
|
|
||
|
StringBuilder res = new StringBuilder ();
|
||
|
foreach (string uri_str in uri_list) {
|
||
|
Uri uri = new Uri (uri_str);
|
||
|
res.Append (uri.ToString ());
|
||
|
res.Append ("\r\n");
|
||
|
}
|
||
|
|
||
|
IntPtr buffer = Marshal.StringToHGlobalAnsi ((string) res.ToString ());
|
||
|
int len = 0;
|
||
|
while (Marshal.ReadByte (buffer, len) != 0)
|
||
|
len++;
|
||
|
|
||
|
dnd.SetProperty (ref xevent, buffer, len);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private class DragData {
|
||
|
public IntPtr Window;
|
||
|
public DragState State;
|
||
|
public object Data;
|
||
|
public IntPtr Action;
|
||
|
public IntPtr [] SupportedTypes;
|
||
|
public MouseButtons MouseState;
|
||
|
public DragDropEffects AllowedEffects;
|
||
|
public Point CurMousePos;
|
||
|
|
||
|
public IntPtr LastWindow;
|
||
|
public IntPtr LastTopLevel;
|
||
|
|
||
|
public bool WillAccept;
|
||
|
|
||
|
public void Reset ()
|
||
|
{
|
||
|
State = DragState.None;
|
||
|
Data = null;
|
||
|
SupportedTypes = null;
|
||
|
WillAccept = false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// This version seems to be the most common
|
||
|
private static readonly IntPtr [] XdndVersion = new IntPtr [] { new IntPtr (4) };
|
||
|
|
||
|
private IntPtr display;
|
||
|
private DragData drag_data;
|
||
|
|
||
|
private IntPtr XdndAware;
|
||
|
private IntPtr XdndSelection;
|
||
|
private IntPtr XdndEnter;
|
||
|
private IntPtr XdndLeave;
|
||
|
private IntPtr XdndPosition;
|
||
|
private IntPtr XdndDrop;
|
||
|
private IntPtr XdndFinished;
|
||
|
private IntPtr XdndStatus;
|
||
|
private IntPtr XdndTypeList;
|
||
|
private IntPtr XdndActionCopy;
|
||
|
private IntPtr XdndActionMove;
|
||
|
private IntPtr XdndActionLink;
|
||
|
//private IntPtr XdndActionPrivate;
|
||
|
private IntPtr XdndActionList;
|
||
|
//private IntPtr XdndActionDescription;
|
||
|
//private IntPtr XdndActionAsk;
|
||
|
|
||
|
//private State state;
|
||
|
|
||
|
private int converts_pending;
|
||
|
private bool position_recieved;
|
||
|
private bool status_sent;
|
||
|
private IntPtr target;
|
||
|
private IntPtr source;
|
||
|
private IntPtr toplevel;
|
||
|
private IDataObject data;
|
||
|
|
||
|
private Control control;
|
||
|
private int pos_x, pos_y;
|
||
|
private DragDropEffects allowed;
|
||
|
private DragEventArgs drag_event;
|
||
|
|
||
|
private Cursor CursorNo;
|
||
|
private Cursor CursorCopy;
|
||
|
private Cursor CursorMove;
|
||
|
private Cursor CursorLink;
|
||
|
// check out the TODO below
|
||
|
//private IntPtr CurrentCursorHandle;
|
||
|
|
||
|
private bool tracking = false;
|
||
|
private bool dropped = false;
|
||
|
private int motion_poll;
|
||
|
//private X11Keyboard keyboard;
|
||
|
|
||
|
public X11Dnd (IntPtr display, X11Keyboard keyboard)
|
||
|
{
|
||
|
this.display = display;
|
||
|
//this.keyboard = keyboard;
|
||
|
|
||
|
Init ();
|
||
|
}
|
||
|
|
||
|
public bool InDrag()
|
||
|
{
|
||
|
if (drag_data == null)
|
||
|
return false;
|
||
|
return drag_data.State != DragState.None;
|
||
|
}
|
||
|
|
||
|
public void SetAllowDrop (Hwnd hwnd, bool allow)
|
||
|
{
|
||
|
int[] atoms;
|
||
|
|
||
|
if (hwnd.allow_drop == allow)
|
||
|
return;
|
||
|
|
||
|
atoms = new int[XdndVersion.Length];
|
||
|
for (int i = 0; i < XdndVersion.Length; i++) {
|
||
|
atoms[i] = XdndVersion[i].ToInt32();
|
||
|
}
|
||
|
|
||
|
XplatUIX11.XChangeProperty (display, hwnd.whole_window, XdndAware,
|
||
|
(IntPtr) Atom.XA_ATOM, 32,
|
||
|
PropertyMode.Replace, atoms, allow ? 1 : 0);
|
||
|
hwnd.allow_drop = allow;
|
||
|
}
|
||
|
|
||
|
public DragDropEffects StartDrag (IntPtr handle, object data,
|
||
|
DragDropEffects allowed_effects)
|
||
|
{
|
||
|
drag_data = new DragData ();
|
||
|
drag_data.Window = handle;
|
||
|
drag_data.State = DragState.Beginning;
|
||
|
drag_data.MouseState = XplatUIX11.MouseState;
|
||
|
drag_data.Data = data;
|
||
|
drag_data.SupportedTypes = DetermineSupportedTypes (data);
|
||
|
drag_data.AllowedEffects = allowed_effects;
|
||
|
drag_data.Action = ActionFromEffect (allowed_effects);
|
||
|
|
||
|
if (CursorNo == null) {
|
||
|
// Make sure the cursors are created
|
||
|
CursorNo = new Cursor (typeof (X11Dnd), "DnDNo.cur");
|
||
|
CursorCopy = new Cursor (typeof (X11Dnd), "DnDCopy.cur");
|
||
|
CursorMove = new Cursor (typeof (X11Dnd), "DnDMove.cur");
|
||
|
CursorLink = new Cursor (typeof (X11Dnd), "DnDLink.cur");
|
||
|
}
|
||
|
|
||
|
drag_data.LastTopLevel = IntPtr.Zero;
|
||
|
control = null;
|
||
|
|
||
|
System.Windows.Forms.MSG msg = new MSG();
|
||
|
object queue_id = XplatUI.StartLoop (Thread.CurrentThread);
|
||
|
|
||
|
Timer timer = new Timer ();
|
||
|
timer.Tick += new EventHandler (DndTickHandler);
|
||
|
timer.Interval = 100;
|
||
|
|
||
|
int suc;
|
||
|
drag_data.State = DragState.Dragging;
|
||
|
|
||
|
suc = XplatUIX11.XSetSelectionOwner (display, XdndSelection,
|
||
|
drag_data.Window, IntPtr.Zero);
|
||
|
|
||
|
if (suc == 0) {
|
||
|
Console.Error.WriteLine ("Could not take ownership of XdndSelection aborting drag.");
|
||
|
drag_data.Reset ();
|
||
|
return DragDropEffects.None;
|
||
|
}
|
||
|
|
||
|
drag_data.State = DragState.Dragging;
|
||
|
drag_data.CurMousePos = new Point ();
|
||
|
source = toplevel = target = IntPtr.Zero;
|
||
|
dropped = false;
|
||
|
tracking = true;
|
||
|
motion_poll = -1;
|
||
|
timer.Start ();
|
||
|
|
||
|
// Send Enter to the window initializing the dnd operation - which initializes the data
|
||
|
SendEnter (drag_data.Window, drag_data.Window, drag_data.SupportedTypes);
|
||
|
drag_data.LastTopLevel = toplevel;
|
||
|
|
||
|
while (tracking && XplatUI.GetMessage (queue_id, ref msg, IntPtr.Zero, 0, 0)) {
|
||
|
|
||
|
if (msg.message >= Msg.WM_KEYFIRST && msg.message <= Msg.WM_KEYLAST) {
|
||
|
HandleKeyMessage (msg);
|
||
|
} else {
|
||
|
switch (msg.message) {
|
||
|
case Msg.WM_LBUTTONUP:
|
||
|
case Msg.WM_RBUTTONUP:
|
||
|
case Msg.WM_MBUTTONUP:
|
||
|
if (msg.message == Msg.WM_LBUTTONDOWN && drag_data.MouseState != MouseButtons.Left)
|
||
|
break;;
|
||
|
if (msg.message == Msg.WM_RBUTTONDOWN && drag_data.MouseState != MouseButtons.Right)
|
||
|
break;
|
||
|
if (msg.message == Msg.WM_MBUTTONDOWN && drag_data.MouseState != MouseButtons.Middle)
|
||
|
break;
|
||
|
|
||
|
HandleButtonUpMsg ();
|
||
|
|
||
|
// We don't want to dispatch button up neither (Match .Net)
|
||
|
// Thus we have to remove capture by ourselves
|
||
|
RemoveCapture (msg.hwnd);
|
||
|
continue;
|
||
|
case Msg.WM_MOUSEMOVE:
|
||
|
motion_poll = 0;
|
||
|
|
||
|
drag_data.CurMousePos.X = Control.LowOrder ((int) msg.lParam.ToInt32 ());
|
||
|
drag_data.CurMousePos.Y = Control.HighOrder ((int) msg.lParam.ToInt32 ());
|
||
|
|
||
|
HandleMouseOver ();
|
||
|
// We don't want to dispatch mouse move
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
XplatUI.DispatchMessage (ref msg);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
timer.Stop ();
|
||
|
|
||
|
// If the target is a mwf control, return until DragEnter/DragLeave has been fired,
|
||
|
// which means the respective -already sent- dnd ClientMessages have been received and handled.
|
||
|
if (control != null)
|
||
|
Application.DoEvents ();
|
||
|
|
||
|
if (!dropped)
|
||
|
return DragDropEffects.None;
|
||
|
if (drag_event != null)
|
||
|
return drag_event.Effect;
|
||
|
|
||
|
// Fallback.
|
||
|
return DragDropEffects.None;
|
||
|
}
|
||
|
|
||
|
private void DndTickHandler (object sender, EventArgs e)
|
||
|
{
|
||
|
// This is to make sure we don't get stuck in a loop if another
|
||
|
// app doesn't finish the DND operation
|
||
|
if (dropped) {
|
||
|
Timer t = (Timer) sender;
|
||
|
if (t.Interval == 500)
|
||
|
tracking = false;
|
||
|
else
|
||
|
t.Interval = 500;
|
||
|
}
|
||
|
|
||
|
|
||
|
// If motion_poll is -1, there hasn't been motion at all, so don't simulate motion yet.
|
||
|
// Otherwise if more than 100 milliseconds have lapsed, we assume the pointer is not
|
||
|
// in motion anymore, and we simulate the mouse over operation, like .Net does.
|
||
|
if (motion_poll > 1)
|
||
|
HandleMouseOver ();
|
||
|
else if (motion_poll > -1)
|
||
|
motion_poll++;
|
||
|
}
|
||
|
|
||
|
// This routines helps us to have a DndEnter/DndLeave fallback when there wasn't any mouse movement
|
||
|
// as .Net does
|
||
|
private void DefaultEnterLeave (object user_data)
|
||
|
{
|
||
|
IntPtr toplevel, window;
|
||
|
int x_root, y_root;
|
||
|
|
||
|
// The window generating the operation could be a different than the one under pointer
|
||
|
GetWindowsUnderPointer (out window, out toplevel, out x_root, out y_root);
|
||
|
Control source_control = Control.FromHandle (window);
|
||
|
if (source_control == null || !source_control.AllowDrop)
|
||
|
return;
|
||
|
|
||
|
// `data' and other members are already available
|
||
|
Point pos = Control.MousePosition;
|
||
|
DragEventArgs drag_args = new DragEventArgs (data, 0, pos.X, pos.Y, drag_data.AllowedEffects, DragDropEffects.None);
|
||
|
|
||
|
source_control.DndEnter (drag_args);
|
||
|
if ((drag_args.Effect & drag_data.AllowedEffects) != 0)
|
||
|
source_control.DndDrop (drag_args);
|
||
|
else
|
||
|
source_control.DndLeave (EventArgs.Empty);
|
||
|
}
|
||
|
|
||
|
public void HandleButtonUpMsg ()
|
||
|
{
|
||
|
if (drag_data.State == DragState.Beginning) {
|
||
|
//state = State.Accepting;
|
||
|
} else if (drag_data.State != DragState.None) {
|
||
|
|
||
|
if (drag_data.WillAccept) {
|
||
|
|
||
|
if (QueryContinue (false, DragAction.Drop))
|
||
|
return;
|
||
|
} else {
|
||
|
|
||
|
if (QueryContinue (false, DragAction.Cancel))
|
||
|
return;
|
||
|
|
||
|
// fallback if no movement was detected, as .net does.
|
||
|
if (motion_poll == -1)
|
||
|
DefaultEnterLeave (drag_data.Data);
|
||
|
}
|
||
|
|
||
|
drag_data.State = DragState.None;
|
||
|
// WE can't reset the drag data yet as it is still
|
||
|
// most likely going to be used by the SelectionRequest
|
||
|
// handlers
|
||
|
}
|
||
|
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
private void RemoveCapture (IntPtr handle)
|
||
|
{
|
||
|
Control c = MwfWindow (handle);
|
||
|
if (c.InternalCapture)
|
||
|
c.InternalCapture = false;
|
||
|
}
|
||
|
|
||
|
public bool HandleMouseOver ()
|
||
|
{
|
||
|
IntPtr toplevel, window;
|
||
|
int x_root, y_root;
|
||
|
|
||
|
GetWindowsUnderPointer (out window, out toplevel, out x_root, out y_root);
|
||
|
|
||
|
if (window != drag_data.LastWindow && drag_data.State == DragState.Entered) {
|
||
|
drag_data.State = DragState.Dragging;
|
||
|
|
||
|
// TODO: Send a Leave if this is an MWF window
|
||
|
|
||
|
if (toplevel != drag_data.LastTopLevel)
|
||
|
SendLeave (drag_data.LastTopLevel, toplevel);
|
||
|
}
|
||
|
|
||
|
drag_data.State = DragState.Entered;
|
||
|
if (toplevel != drag_data.LastTopLevel) {
|
||
|
// Entering a new toplevel window
|
||
|
SendEnter (toplevel, drag_data.Window, drag_data.SupportedTypes);
|
||
|
} else {
|
||
|
// Already in a toplevel window, so send a position
|
||
|
SendPosition (toplevel, drag_data.Window,
|
||
|
drag_data.Action,
|
||
|
x_root, y_root,
|
||
|
IntPtr.Zero);
|
||
|
}
|
||
|
|
||
|
drag_data.LastTopLevel = toplevel;
|
||
|
drag_data.LastWindow = window;
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
void GetWindowsUnderPointer (out IntPtr window, out IntPtr toplevel, out int x_root, out int y_root)
|
||
|
{
|
||
|
toplevel = IntPtr.Zero;
|
||
|
window = XplatUIX11.RootWindowHandle;
|
||
|
|
||
|
IntPtr root, child;
|
||
|
bool dnd_aware = false;
|
||
|
int x_temp, y_temp;
|
||
|
int mask_return;
|
||
|
int x = x_root = drag_data.CurMousePos.X;
|
||
|
int y = y_root = drag_data.CurMousePos.Y;
|
||
|
|
||
|
while (XplatUIX11.XQueryPointer (display, window, out root, out child,
|
||
|
out x_temp, out y_temp, out x, out y, out mask_return)) {
|
||
|
|
||
|
if (!dnd_aware) {
|
||
|
dnd_aware = IsWindowDndAware (window);
|
||
|
if (dnd_aware) {
|
||
|
toplevel = window;
|
||
|
x_root = x_temp;
|
||
|
y_root = y_temp;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (child == IntPtr.Zero)
|
||
|
break;
|
||
|
|
||
|
window = child;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public void HandleKeyMessage (MSG msg)
|
||
|
{
|
||
|
if (VirtualKeys.VK_ESCAPE == (VirtualKeys) msg.wParam.ToInt32()) {
|
||
|
QueryContinue (true, DragAction.Cancel);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// return true if the event is handled here
|
||
|
public bool HandleClientMessage (ref XEvent xevent)
|
||
|
{
|
||
|
// most common so we check it first
|
||
|
if (xevent.ClientMessageEvent.message_type == XdndPosition)
|
||
|
return Accepting_HandlePositionEvent (ref xevent);
|
||
|
if (xevent.ClientMessageEvent.message_type == XdndEnter)
|
||
|
return Accepting_HandleEnterEvent (ref xevent);
|
||
|
if (xevent.ClientMessageEvent.message_type == XdndDrop)
|
||
|
return Accepting_HandleDropEvent (ref xevent);
|
||
|
if (xevent.ClientMessageEvent.message_type == XdndLeave)
|
||
|
return Accepting_HandleLeaveEvent (ref xevent);
|
||
|
if (xevent.ClientMessageEvent.message_type == XdndStatus)
|
||
|
return HandleStatusEvent (ref xevent);
|
||
|
if (xevent.ClientMessageEvent.message_type == XdndFinished)
|
||
|
return HandleFinishedEvent (ref xevent);
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
public bool HandleSelectionNotifyEvent (ref XEvent xevent)
|
||
|
{
|
||
|
MimeHandler handler = FindHandler ((IntPtr) xevent.SelectionEvent.target);
|
||
|
if (handler == null)
|
||
|
return false;
|
||
|
if (data == null)
|
||
|
data = new DataObject ();
|
||
|
|
||
|
handler.Converter.GetData (this, data, ref xevent);
|
||
|
|
||
|
converts_pending--;
|
||
|
if (converts_pending <= 0 && position_recieved) {
|
||
|
drag_event = new DragEventArgs (data, 0, pos_x, pos_y,
|
||
|
allowed, DragDropEffects.None);
|
||
|
control.DndEnter (drag_event);
|
||
|
SendStatus (source, drag_event.Effect);
|
||
|
status_sent = true;
|
||
|
}
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
public bool HandleSelectionRequestEvent (ref XEvent xevent)
|
||
|
{
|
||
|
if (xevent.SelectionRequestEvent.selection != XdndSelection)
|
||
|
return false;
|
||
|
|
||
|
MimeHandler handler = FindHandler (xevent.SelectionRequestEvent.target);
|
||
|
if (handler == null)
|
||
|
return false;
|
||
|
|
||
|
handler.Converter.SetData (this, drag_data.Data, ref xevent);
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
private bool QueryContinue (bool escape, DragAction action)
|
||
|
{
|
||
|
QueryContinueDragEventArgs qce = new QueryContinueDragEventArgs ((int) XplatUI.State.ModifierKeys,
|
||
|
escape, action);
|
||
|
|
||
|
Control c = MwfWindow (source);
|
||
|
|
||
|
if (c == null) {
|
||
|
tracking = false;
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
c.DndContinueDrag (qce);
|
||
|
|
||
|
switch (qce.Action) {
|
||
|
case DragAction.Continue:
|
||
|
return true;
|
||
|
case DragAction.Drop:
|
||
|
SendDrop (drag_data.LastTopLevel, source, IntPtr.Zero);
|
||
|
tracking = false;
|
||
|
return true;
|
||
|
case DragAction.Cancel:
|
||
|
drag_data.Reset ();
|
||
|
c.InternalCapture = false;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
SendLeave (drag_data.LastTopLevel, toplevel);
|
||
|
|
||
|
RestoreDefaultCursor ();
|
||
|
tracking = false;
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
private void RestoreDefaultCursor ()
|
||
|
{
|
||
|
// Releasing the mouse buttons should automatically restore the default cursor,
|
||
|
// but canceling the operation using QueryContinue should restore it even if the
|
||
|
// mouse buttons are not released yet.
|
||
|
XplatUIX11.XChangeActivePointerGrab (display,
|
||
|
EventMask.ButtonMotionMask |
|
||
|
EventMask.PointerMotionMask |
|
||
|
EventMask.ButtonPressMask |
|
||
|
EventMask.ButtonReleaseMask,
|
||
|
Cursors.Default.Handle, IntPtr.Zero);
|
||
|
|
||
|
}
|
||
|
|
||
|
private void GiveFeedback (IntPtr action)
|
||
|
{
|
||
|
GiveFeedbackEventArgs gfe = new GiveFeedbackEventArgs (EffectFromAction (drag_data.Action), true);
|
||
|
|
||
|
Control c = MwfWindow (source);
|
||
|
c.DndFeedback (gfe);
|
||
|
|
||
|
if (gfe.UseDefaultCursors) {
|
||
|
Cursor cursor = CursorNo;
|
||
|
if (drag_data.WillAccept) {
|
||
|
// Same order as on MS
|
||
|
if (action == XdndActionCopy)
|
||
|
cursor = CursorCopy;
|
||
|
else if (action == XdndActionLink)
|
||
|
cursor = CursorLink;
|
||
|
else if (action == XdndActionMove)
|
||
|
cursor = CursorMove;
|
||
|
}
|
||
|
// TODO: Try not to set the cursor so much
|
||
|
//if (cursor.Handle != CurrentCursorHandle) {
|
||
|
XplatUIX11.XChangeActivePointerGrab (display,
|
||
|
EventMask.ButtonMotionMask |
|
||
|
EventMask.PointerMotionMask |
|
||
|
EventMask.ButtonPressMask |
|
||
|
EventMask.ButtonReleaseMask,
|
||
|
cursor.Handle, IntPtr.Zero);
|
||
|
//CurrentCursorHandle = cursor.Handle;
|
||
|
//}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private void SetProperty (ref XEvent xevent, IntPtr data, int length)
|
||
|
{
|
||
|
XEvent sel = new XEvent();
|
||
|
sel.SelectionEvent.type = XEventName.SelectionNotify;
|
||
|
sel.SelectionEvent.send_event = true;
|
||
|
sel.SelectionEvent.display = display;
|
||
|
sel.SelectionEvent.selection = xevent.SelectionRequestEvent.selection;
|
||
|
sel.SelectionEvent.target = xevent.SelectionRequestEvent.target;
|
||
|
sel.SelectionEvent.requestor = xevent.SelectionRequestEvent.requestor;
|
||
|
sel.SelectionEvent.time = xevent.SelectionRequestEvent.time;
|
||
|
sel.SelectionEvent.property = IntPtr.Zero;
|
||
|
|
||
|
XplatUIX11.XChangeProperty (display, xevent.SelectionRequestEvent.requestor,
|
||
|
xevent.SelectionRequestEvent.property,
|
||
|
xevent.SelectionRequestEvent.target,
|
||
|
8, PropertyMode.Replace, data, length);
|
||
|
sel.SelectionEvent.property = xevent.SelectionRequestEvent.property;
|
||
|
|
||
|
XplatUIX11.XSendEvent (display, xevent.SelectionRequestEvent.requestor, false,
|
||
|
(IntPtr)EventMask.NoEventMask, ref sel);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
private void Reset ()
|
||
|
{
|
||
|
ResetSourceData ();
|
||
|
ResetTargetData ();
|
||
|
}
|
||
|
|
||
|
private void ResetSourceData ()
|
||
|
{
|
||
|
converts_pending = 0;
|
||
|
data = null;
|
||
|
}
|
||
|
|
||
|
private void ResetTargetData ()
|
||
|
{
|
||
|
position_recieved = false;
|
||
|
status_sent = false;
|
||
|
}
|
||
|
|
||
|
private bool Accepting_HandleEnterEvent (ref XEvent xevent)
|
||
|
{
|
||
|
Reset ();
|
||
|
|
||
|
source = xevent.ClientMessageEvent.ptr1;
|
||
|
toplevel = xevent.AnyEvent.window;
|
||
|
target = IntPtr.Zero;
|
||
|
|
||
|
ConvertData (ref xevent);
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
private bool Accepting_HandlePositionEvent (ref XEvent xevent)
|
||
|
{
|
||
|
pos_x = (int) xevent.ClientMessageEvent.ptr3 >> 16;
|
||
|
pos_y = (int) xevent.ClientMessageEvent.ptr3 & 0xFFFF;
|
||
|
|
||
|
// Copy is implicitly allowed
|
||
|
Control source_control = MwfWindow (source);
|
||
|
if (source_control == null)
|
||
|
allowed = EffectsFromX11Source (source, xevent.ClientMessageEvent.ptr5) | DragDropEffects.Copy;
|
||
|
else
|
||
|
allowed = drag_data.AllowedEffects;
|
||
|
|
||
|
IntPtr parent, child, new_child, last_drop_child;
|
||
|
parent = XplatUIX11.XRootWindow (display, 0);
|
||
|
child = toplevel;
|
||
|
last_drop_child = IntPtr.Zero;
|
||
|
while (true) {
|
||
|
int xd, yd;
|
||
|
new_child = IntPtr.Zero;
|
||
|
|
||
|
if (!XplatUIX11.XTranslateCoordinates (display,
|
||
|
parent, child, pos_x, pos_y,
|
||
|
out xd, out yd, out new_child))
|
||
|
break;
|
||
|
if (new_child == IntPtr.Zero)
|
||
|
break;
|
||
|
child = new_child;
|
||
|
|
||
|
Hwnd h = Hwnd.ObjectFromHandle (child);
|
||
|
if (h != null) {
|
||
|
Control d = Control.FromHandle (h.client_window);
|
||
|
if (d != null && d.allow_drop)
|
||
|
last_drop_child = child;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (last_drop_child != IntPtr.Zero)
|
||
|
child = last_drop_child;
|
||
|
|
||
|
if (target != child) {
|
||
|
// We have moved into a new control
|
||
|
// or into a control for the first time
|
||
|
Finish ();
|
||
|
}
|
||
|
target = child;
|
||
|
Hwnd hwnd = Hwnd.ObjectFromHandle (target);
|
||
|
if (hwnd == null)
|
||
|
return true;
|
||
|
|
||
|
Control c = Control.FromHandle (hwnd.client_window);
|
||
|
|
||
|
if (c == null)
|
||
|
return true;
|
||
|
if (!c.allow_drop) {
|
||
|
SendStatus (source, DragDropEffects.None);
|
||
|
Finish ();
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
control = c;
|
||
|
position_recieved = true;
|
||
|
|
||
|
if (converts_pending > 0)
|
||
|
return true;
|
||
|
|
||
|
if (!status_sent) {
|
||
|
drag_event = new DragEventArgs (data, 0, pos_x, pos_y,
|
||
|
allowed, DragDropEffects.None);
|
||
|
control.DndEnter (drag_event);
|
||
|
|
||
|
SendStatus (source, drag_event.Effect);
|
||
|
status_sent = true;
|
||
|
} else {
|
||
|
drag_event.x = pos_x;
|
||
|
drag_event.y = pos_y;
|
||
|
control.DndOver (drag_event);
|
||
|
|
||
|
SendStatus (source, drag_event.Effect);
|
||
|
}
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
private void Finish ()
|
||
|
{
|
||
|
if (control != null) {
|
||
|
if (drag_event == null) {
|
||
|
if (data == null)
|
||
|
data = new DataObject ();
|
||
|
drag_event = new DragEventArgs (data,
|
||
|
0, pos_x, pos_y,
|
||
|
allowed, DragDropEffects.None);
|
||
|
}
|
||
|
control.DndLeave (drag_event);
|
||
|
control = null;
|
||
|
}
|
||
|
ResetTargetData ();
|
||
|
}
|
||
|
|
||
|
private bool Accepting_HandleDropEvent (ref XEvent xevent)
|
||
|
{
|
||
|
if (control != null && drag_event != null) {
|
||
|
drag_event = new DragEventArgs (data,
|
||
|
0, pos_x, pos_y,
|
||
|
allowed, drag_event.Effect);
|
||
|
control.DndDrop (drag_event);
|
||
|
}
|
||
|
SendFinished ();
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
private bool Accepting_HandleLeaveEvent (ref XEvent xevent)
|
||
|
{
|
||
|
if (control != null && drag_event != null)
|
||
|
control.DndLeave (drag_event);
|
||
|
// Reset ();
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
private bool HandleStatusEvent (ref XEvent xevent)
|
||
|
{
|
||
|
if (drag_data != null && drag_data.State == DragState.Entered) {
|
||
|
|
||
|
if (!QueryContinue (false, DragAction.Continue))
|
||
|
return true;
|
||
|
|
||
|
drag_data.WillAccept = ((int) xevent.ClientMessageEvent.ptr2 & 0x1) != 0;
|
||
|
|
||
|
GiveFeedback (xevent.ClientMessageEvent.ptr5);
|
||
|
}
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
private bool HandleFinishedEvent (ref XEvent xevent)
|
||
|
{
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
private DragDropEffects EffectsFromX11Source (IntPtr source, IntPtr action_atom)
|
||
|
{
|
||
|
DragDropEffects allowed = DragDropEffects.None;
|
||
|
IntPtr type, count, remaining, data = IntPtr.Zero;
|
||
|
int format;
|
||
|
|
||
|
XplatUIX11.XGetWindowProperty (display, source, XdndActionList,
|
||
|
IntPtr.Zero, new IntPtr (32), false, (IntPtr) Atom.AnyPropertyType,
|
||
|
out type, out format, out count, out remaining, ref data);
|
||
|
|
||
|
int intptr_size = Marshal.SizeOf (typeof (IntPtr));
|
||
|
for (int i = 0; i < count.ToInt32 (); i++) {
|
||
|
IntPtr current_atom = Marshal.ReadIntPtr (data, i * intptr_size);
|
||
|
allowed |= EffectFromAction (current_atom);
|
||
|
}
|
||
|
|
||
|
// if source is not providing the action list, use the
|
||
|
// default action passed in the x11 dnd position message
|
||
|
if (allowed == DragDropEffects.None)
|
||
|
allowed = EffectFromAction (action_atom);
|
||
|
|
||
|
return allowed;
|
||
|
}
|
||
|
|
||
|
private DragDropEffects EffectFromAction (IntPtr action)
|
||
|
{
|
||
|
if (action == XdndActionCopy)
|
||
|
return DragDropEffects.Copy;
|
||
|
else if (action == XdndActionMove)
|
||
|
return DragDropEffects.Move;
|
||
|
if (action == XdndActionLink)
|
||
|
return DragDropEffects.Link;
|
||
|
|
||
|
return DragDropEffects.None;
|
||
|
}
|
||
|
|
||
|
private IntPtr ActionFromEffect (DragDropEffects effect)
|
||
|
{
|
||
|
IntPtr action = IntPtr.Zero;
|
||
|
|
||
|
// We can't OR together actions on XDND so sadly the primary
|
||
|
// is the only one shown here
|
||
|
if ((effect & DragDropEffects.Copy) != 0)
|
||
|
action = XdndActionCopy;
|
||
|
else if ((effect & DragDropEffects.Move) != 0)
|
||
|
action = XdndActionMove;
|
||
|
else if ((effect & DragDropEffects.Link) != 0)
|
||
|
action = XdndActionLink;
|
||
|
return action;
|
||
|
}
|
||
|
|
||
|
private bool ConvertData (ref XEvent xevent)
|
||
|
{
|
||
|
bool match = false;
|
||
|
|
||
|
Control mwfcontrol = MwfWindow (source);
|
||
|
|
||
|
/* To take advantage of the mwfcontrol, we have to be sure
|
||
|
that the dnd operation is still happening (since messages are asynchronous) */
|
||
|
if (mwfcontrol != null && drag_data != null) {
|
||
|
if (!tracking)
|
||
|
return false;
|
||
|
|
||
|
IDataObject dragged = drag_data.Data as IDataObject;
|
||
|
if (dragged != null) {
|
||
|
data = dragged;
|
||
|
} else {
|
||
|
if (data == null)
|
||
|
data = new DataObject ();
|
||
|
SetDataWithFormats (drag_data.Data);
|
||
|
}
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
foreach (IntPtr atom in SourceSupportedList (ref xevent)) {
|
||
|
MimeHandler handler = FindHandler (atom);
|
||
|
if (handler == null)
|
||
|
continue;
|
||
|
XplatUIX11.XConvertSelection (display, XdndSelection, handler.Type,
|
||
|
handler.NonProtocol, toplevel, IntPtr.Zero /* CurrentTime */);
|
||
|
converts_pending++;
|
||
|
match = true;
|
||
|
}
|
||
|
return match;
|
||
|
}
|
||
|
|
||
|
private void SetDataWithFormats (object value)
|
||
|
{
|
||
|
if (value is string) {
|
||
|
data.SetData (DataFormats.Text, value);
|
||
|
data.SetData (DataFormats.UnicodeText, value);
|
||
|
}
|
||
|
|
||
|
data.SetData (value);
|
||
|
}
|
||
|
|
||
|
private MimeHandler FindHandler (IntPtr atom)
|
||
|
{
|
||
|
if (atom == IntPtr.Zero)
|
||
|
return null;
|
||
|
foreach (MimeHandler handler in MimeHandlers) {
|
||
|
if (handler.Type == atom)
|
||
|
return handler;
|
||
|
}
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
private MimeHandler FindHandler (string name)
|
||
|
{
|
||
|
foreach (MimeHandler handler in MimeHandlers) {
|
||
|
foreach (string alias in handler.Aliases) {
|
||
|
if (alias == name)
|
||
|
return handler;
|
||
|
}
|
||
|
}
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
private void SendStatus (IntPtr source, DragDropEffects effect)
|
||
|
{
|
||
|
XEvent xevent = new XEvent ();
|
||
|
|
||
|
xevent.AnyEvent.type = XEventName.ClientMessage;
|
||
|
xevent.AnyEvent.display = display;
|
||
|
xevent.ClientMessageEvent.window = source;
|
||
|
xevent.ClientMessageEvent.message_type = XdndStatus;
|
||
|
xevent.ClientMessageEvent.format = 32;
|
||
|
xevent.ClientMessageEvent.ptr1 = toplevel;
|
||
|
if (effect != DragDropEffects.None && (effect & allowed) != 0)
|
||
|
xevent.ClientMessageEvent.ptr2 = (IntPtr) 1;
|
||
|
|
||
|
xevent.ClientMessageEvent.ptr5 = ActionFromEffect (effect);
|
||
|
XplatUIX11.XSendEvent (display, source, false, IntPtr.Zero, ref xevent);
|
||
|
}
|
||
|
|
||
|
private void SendEnter (IntPtr handle, IntPtr from, IntPtr [] supported)
|
||
|
{
|
||
|
XEvent xevent = new XEvent ();
|
||
|
|
||
|
xevent.AnyEvent.type = XEventName.ClientMessage;
|
||
|
xevent.AnyEvent.display = display;
|
||
|
xevent.ClientMessageEvent.window = handle;
|
||
|
xevent.ClientMessageEvent.message_type = XdndEnter;
|
||
|
xevent.ClientMessageEvent.format = 32;
|
||
|
xevent.ClientMessageEvent.ptr1 = from;
|
||
|
|
||
|
// (int) xevent.ClientMessageEvent.ptr2 & 0x1)
|
||
|
// int ptr2 = 0x1;
|
||
|
// xevent.ClientMessageEvent.ptr2 = (IntPtr) ptr2;
|
||
|
// (e)->xclient.data.l[1] = ((e)->xclient.data.l[1] & ~(0xFF << 24)) | ((v) << 24)
|
||
|
xevent.ClientMessageEvent.ptr2 = (IntPtr) ((long)XdndVersion [0] << 24);
|
||
|
|
||
|
if (supported.Length > 0)
|
||
|
xevent.ClientMessageEvent.ptr3 = supported [0];
|
||
|
if (supported.Length > 1)
|
||
|
xevent.ClientMessageEvent.ptr4 = supported [1];
|
||
|
if (supported.Length > 2)
|
||
|
xevent.ClientMessageEvent.ptr5 = supported [2];
|
||
|
|
||
|
XplatUIX11.XSendEvent (display, handle, false, IntPtr.Zero, ref xevent);
|
||
|
}
|
||
|
|
||
|
private void SendDrop (IntPtr handle, IntPtr from, IntPtr time)
|
||
|
{
|
||
|
XEvent xevent = new XEvent ();
|
||
|
|
||
|
xevent.AnyEvent.type = XEventName.ClientMessage;
|
||
|
xevent.AnyEvent.display = display;
|
||
|
xevent.ClientMessageEvent.window = handle;
|
||
|
xevent.ClientMessageEvent.message_type = XdndDrop;
|
||
|
xevent.ClientMessageEvent.format = 32;
|
||
|
xevent.ClientMessageEvent.ptr1 = from;
|
||
|
xevent.ClientMessageEvent.ptr3 = time;
|
||
|
|
||
|
XplatUIX11.XSendEvent (display, handle, false, IntPtr.Zero, ref xevent);
|
||
|
dropped = true;
|
||
|
}
|
||
|
|
||
|
private void SendPosition (IntPtr handle, IntPtr from, IntPtr action, int x, int y, IntPtr time)
|
||
|
{
|
||
|
XEvent xevent = new XEvent ();
|
||
|
|
||
|
xevent.AnyEvent.type = XEventName.ClientMessage;
|
||
|
xevent.AnyEvent.display = display;
|
||
|
xevent.ClientMessageEvent.window = handle;
|
||
|
xevent.ClientMessageEvent.message_type = XdndPosition;
|
||
|
xevent.ClientMessageEvent.format = 32;
|
||
|
xevent.ClientMessageEvent.ptr1 = from;
|
||
|
xevent.ClientMessageEvent.ptr3 = (IntPtr) ((x << 16) | (y & 0xFFFF));
|
||
|
xevent.ClientMessageEvent.ptr4 = time;
|
||
|
xevent.ClientMessageEvent.ptr5 = action;
|
||
|
|
||
|
XplatUIX11.XSendEvent (display, handle, false, IntPtr.Zero, ref xevent);
|
||
|
}
|
||
|
|
||
|
private void SendLeave (IntPtr handle, IntPtr from)
|
||
|
{
|
||
|
XEvent xevent = new XEvent ();
|
||
|
|
||
|
xevent.AnyEvent.type = XEventName.ClientMessage;
|
||
|
xevent.AnyEvent.display = display;
|
||
|
xevent.ClientMessageEvent.window = handle;
|
||
|
xevent.ClientMessageEvent.message_type = XdndLeave;
|
||
|
xevent.ClientMessageEvent.format = 32;
|
||
|
xevent.ClientMessageEvent.ptr1 = from;
|
||
|
|
||
|
XplatUIX11.XSendEvent (display, handle, false, IntPtr.Zero, ref xevent);
|
||
|
}
|
||
|
|
||
|
private void SendFinished ()
|
||
|
{
|
||
|
XEvent xevent = new XEvent ();
|
||
|
|
||
|
xevent.AnyEvent.type = XEventName.ClientMessage;
|
||
|
xevent.AnyEvent.display = display;
|
||
|
xevent.ClientMessageEvent.window = source;
|
||
|
xevent.ClientMessageEvent.message_type = XdndFinished;
|
||
|
xevent.ClientMessageEvent.format = 32;
|
||
|
xevent.ClientMessageEvent.ptr1 = toplevel;
|
||
|
|
||
|
XplatUIX11.XSendEvent (display, source, false, IntPtr.Zero, ref xevent);
|
||
|
}
|
||
|
|
||
|
// There is a somewhat decent amount of overhead
|
||
|
// involved in setting up dnd so we do it lazily
|
||
|
// as a lot of applications do not even use it.
|
||
|
private void Init ()
|
||
|
{
|
||
|
XdndAware = XplatUIX11.XInternAtom (display, "XdndAware", false);
|
||
|
XdndEnter = XplatUIX11.XInternAtom (display, "XdndEnter", false);
|
||
|
XdndLeave = XplatUIX11.XInternAtom (display, "XdndLeave", false);
|
||
|
XdndPosition = XplatUIX11.XInternAtom (display, "XdndPosition", false);
|
||
|
XdndStatus = XplatUIX11.XInternAtom (display, "XdndStatus", false);
|
||
|
XdndDrop = XplatUIX11.XInternAtom (display, "XdndDrop", false);
|
||
|
XdndSelection = XplatUIX11.XInternAtom (display, "XdndSelection", false);
|
||
|
XdndFinished = XplatUIX11.XInternAtom (display, "XdndFinished", false);
|
||
|
XdndTypeList = XplatUIX11.XInternAtom (display, "XdndTypeList", false);
|
||
|
XdndActionCopy = XplatUIX11.XInternAtom (display, "XdndActionCopy", false);
|
||
|
XdndActionMove = XplatUIX11.XInternAtom (display, "XdndActionMove", false);
|
||
|
XdndActionLink = XplatUIX11.XInternAtom (display, "XdndActionLink", false);
|
||
|
//XdndActionPrivate = XplatUIX11.XInternAtom (display, "XdndActionPrivate", false);
|
||
|
XdndActionList = XplatUIX11.XInternAtom (display, "XdndActionList", false);
|
||
|
//XdndActionDescription = XplatUIX11.XInternAtom (display, "XdndActionDescription", false);
|
||
|
//XdndActionAsk = XplatUIX11.XInternAtom (display, "XdndActionAsk", false);
|
||
|
|
||
|
foreach (MimeHandler handler in MimeHandlers) {
|
||
|
handler.Type = XplatUIX11.XInternAtom (display, handler.Name, false);
|
||
|
handler.NonProtocol = XplatUIX11.XInternAtom (display,
|
||
|
String.Concat ("MWFNonP+", handler.Name), false);
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
private IntPtr [] SourceSupportedList (ref XEvent xevent)
|
||
|
{
|
||
|
IntPtr [] res;
|
||
|
|
||
|
|
||
|
if (((int) xevent.ClientMessageEvent.ptr2 & 0x1) == 0) {
|
||
|
res = new IntPtr [3];
|
||
|
res [0] = xevent.ClientMessageEvent.ptr3;
|
||
|
res [1] = xevent.ClientMessageEvent.ptr4;
|
||
|
res [2] = xevent.ClientMessageEvent.ptr5;
|
||
|
} else {
|
||
|
IntPtr type;
|
||
|
int format;
|
||
|
IntPtr count;
|
||
|
IntPtr remaining;
|
||
|
IntPtr data = IntPtr.Zero;
|
||
|
|
||
|
XplatUIX11.XGetWindowProperty (display, source, XdndTypeList,
|
||
|
IntPtr.Zero, new IntPtr(32), false, (IntPtr) Atom.XA_ATOM,
|
||
|
out type, out format, out count,
|
||
|
out remaining, ref data);
|
||
|
|
||
|
res = new IntPtr [count.ToInt32()];
|
||
|
for (int i = 0; i < count.ToInt32(); i++) {
|
||
|
res [i] = (IntPtr) Marshal.ReadInt32 (data, i *
|
||
|
Marshal.SizeOf (typeof (int)));
|
||
|
}
|
||
|
|
||
|
XplatUIX11.XFree (data);
|
||
|
}
|
||
|
|
||
|
return res;
|
||
|
}
|
||
|
|
||
|
private string GetText (ref XEvent xevent, bool unicode)
|
||
|
{
|
||
|
int nread = 0;
|
||
|
IntPtr nitems;
|
||
|
IntPtr bytes_after;
|
||
|
|
||
|
StringBuilder builder = new StringBuilder ();
|
||
|
do {
|
||
|
IntPtr actual_type;
|
||
|
int actual_fmt;
|
||
|
IntPtr data = IntPtr.Zero;
|
||
|
|
||
|
if (0 != XplatUIX11.XGetWindowProperty (display,
|
||
|
xevent.AnyEvent.window,
|
||
|
(IntPtr) xevent.SelectionEvent.property,
|
||
|
IntPtr.Zero, new IntPtr(0xffffff), false,
|
||
|
(IntPtr) Atom.AnyPropertyType, out actual_type,
|
||
|
out actual_fmt, out nitems, out bytes_after,
|
||
|
ref data)) {
|
||
|
XplatUIX11.XFree (data);
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
if (unicode)
|
||
|
builder.Append (Marshal.PtrToStringUni (data));
|
||
|
else
|
||
|
builder.Append (Marshal.PtrToStringAnsi (data));
|
||
|
nread += nitems.ToInt32();
|
||
|
|
||
|
XplatUIX11.XFree (data);
|
||
|
} while (bytes_after.ToInt32() > 0);
|
||
|
if (nread == 0)
|
||
|
return null;
|
||
|
return builder.ToString ();
|
||
|
}
|
||
|
|
||
|
private MemoryStream GetData (ref XEvent xevent)
|
||
|
{
|
||
|
int nread = 0;
|
||
|
IntPtr nitems;
|
||
|
IntPtr bytes_after;
|
||
|
|
||
|
MemoryStream res = new MemoryStream ();
|
||
|
do {
|
||
|
IntPtr actual_type;
|
||
|
int actual_fmt;
|
||
|
IntPtr data = IntPtr.Zero;
|
||
|
|
||
|
if (0 != XplatUIX11.XGetWindowProperty (display,
|
||
|
xevent.AnyEvent.window,
|
||
|
(IntPtr) xevent.SelectionEvent.property,
|
||
|
IntPtr.Zero, new IntPtr(0xffffff), false,
|
||
|
(IntPtr) Atom.AnyPropertyType, out actual_type,
|
||
|
out actual_fmt, out nitems, out bytes_after,
|
||
|
ref data)) {
|
||
|
XplatUIX11.XFree (data);
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
for (int i = 0; i < nitems.ToInt32(); i++)
|
||
|
res.WriteByte (Marshal.ReadByte (data, i));
|
||
|
nread += nitems.ToInt32();
|
||
|
|
||
|
XplatUIX11.XFree (data);
|
||
|
} while (bytes_after.ToInt32() > 0);
|
||
|
return res;
|
||
|
}
|
||
|
|
||
|
private Control MwfWindow (IntPtr window)
|
||
|
{
|
||
|
Hwnd hwnd = Hwnd.ObjectFromHandle (window);
|
||
|
if (hwnd == null)
|
||
|
return null;
|
||
|
|
||
|
Control res = Control.FromHandle (hwnd.client_window);
|
||
|
|
||
|
if (res == null)
|
||
|
res = Control.FromHandle (window);
|
||
|
|
||
|
return res;
|
||
|
}
|
||
|
|
||
|
private bool IsWindowDndAware (IntPtr handle)
|
||
|
{
|
||
|
bool res = true;
|
||
|
// Check the version number, we need greater than 3
|
||
|
IntPtr actual;
|
||
|
int format;
|
||
|
IntPtr count;
|
||
|
IntPtr remaining;
|
||
|
IntPtr data = IntPtr.Zero;
|
||
|
|
||
|
XplatUIX11.XGetWindowProperty (display, handle, XdndAware, IntPtr.Zero, new IntPtr(0x8000000), false,
|
||
|
(IntPtr) Atom.XA_ATOM, out actual, out format,
|
||
|
out count, out remaining, ref data);
|
||
|
|
||
|
if (actual != (IntPtr) Atom.XA_ATOM || format != 32 ||
|
||
|
count.ToInt32() == 0 || data == IntPtr.Zero) {
|
||
|
if (data != IntPtr.Zero)
|
||
|
XplatUIX11.XFree (data);
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
int version = Marshal.ReadInt32 (data, 0);
|
||
|
|
||
|
if (version < 3) {
|
||
|
Console.Error.WriteLine ("XDND Version too old (" + version + ").");
|
||
|
XplatUIX11.XFree (data);
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
// First type is actually the XDND version
|
||
|
if (count.ToInt32() > 1) {
|
||
|
res = false;
|
||
|
for (int i = 1; i < count.ToInt32(); i++) {
|
||
|
IntPtr type = (IntPtr) Marshal.ReadInt32 (data, i *
|
||
|
Marshal.SizeOf (typeof (int)));
|
||
|
for (int j = 0; j < drag_data.SupportedTypes.Length; j++) {
|
||
|
if (drag_data.SupportedTypes [j] == type) {
|
||
|
res = true;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
XplatUIX11.XFree (data);
|
||
|
return res;
|
||
|
}
|
||
|
|
||
|
private IntPtr [] DetermineSupportedTypes (object data)
|
||
|
{
|
||
|
ArrayList res = new ArrayList ();
|
||
|
|
||
|
if (data is string) {
|
||
|
MimeHandler handler = FindHandler ("text/plain");
|
||
|
if (handler != null)
|
||
|
res.Add (handler.Type);
|
||
|
}/* else if (data is Bitmap)
|
||
|
res.Add (data);
|
||
|
|
||
|
*/
|
||
|
|
||
|
IDataObject data_object = data as IDataObject;
|
||
|
if (data_object != null) {
|
||
|
foreach (string format in data_object.GetFormats (true)) {
|
||
|
MimeHandler handler = FindHandler (format);
|
||
|
if (handler != null && !res.Contains (handler.Type))
|
||
|
res.Add (handler.Type);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (data is ISerializable) {
|
||
|
MimeHandler handler = FindHandler ("application/x-mono-serialized-object");
|
||
|
if (handler != null)
|
||
|
res.Add (handler.Type);
|
||
|
}
|
||
|
|
||
|
return (IntPtr []) res.ToArray (typeof (IntPtr));
|
||
|
}
|
||
|
}
|
||
|
}
|