2
// Copyright (C) 2009 GNOME Do
4
// This program is free software: you can redistribute it and/or modify
5
// it under the terms of the GNU General Public License as published by
6
// the Free Software Foundation, either version 3 of the License, or
7
// (at your option) any later version.
9
// This program is distributed in the hope that it will be useful,
10
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
// GNU General Public License for more details.
14
// You should have received a copy of the GNU General Public License
15
// along with this program. If not, see <http://www.gnu.org/licenses/>.
19
using System.Collections.Generic;
20
using System.Collections.ObjectModel;
22
using System.Text.RegularExpressions;
28
using Docky.Utilities;
32
namespace Docky.Interface
34
public enum DragEdge {
41
public class DnDTracker : IDisposable
43
public event EventHandler DrawRequired;
44
public event EventHandler DragEnded;
46
const int DragHotZoneSize = 8;
48
public bool DragResizing { get; private set; }
50
public DragEdge DragEdge { get; private set; }
52
public DragState DragState { get; set; }
54
public bool GtkDragging { get; set; }
56
public bool PreviewIsDesktopFile { get; set; }
58
bool gtk_drag_source_set;
60
int drag_start_icon_size;
64
Gdk.Point drag_start_point;
66
Gdk.CursorType cursor_type;
68
Gdk.Window drag_proxy;
70
Gdk.DragContext drag_context;
72
IEnumerable<string> uri_list;
75
get { return CursorTracker.Cursor; }
78
Gdk.Rectangle MinimumDockArea {
79
get { return parent.MinimumDockArea; }
82
ReadOnlyCollection<AbstractDockItem> DockItems {
83
get { return DockServices.ItemsService.DockItems; }
86
ItemPositionProvider PositionProvider { get; set; }
88
CursorTracker CursorTracker { get; set; }
90
AbstractDockItem CurrentDockItem {
92
try { return DockItems [PositionProvider.IndexAtPosition (Cursor)]; }
93
catch { return null; }
97
bool CursorNearTopDraggableEdge {
99
return MinimumDockArea.Contains (Cursor) && CurrentDockItem is SeparatorItem;
103
bool CursorNearLeftEdge {
105
return parent.CursorIsOverDockArea && Math.Abs (Cursor.X - MinimumDockArea.X) < DragHotZoneSize;
109
bool CursorNearRightEdge {
111
return parent.CursorIsOverDockArea && Math.Abs (Cursor.X - (MinimumDockArea.X + MinimumDockArea.Width)) < DragHotZoneSize;
115
bool CursorNearDraggableEdge {
117
return CursorNearTopDraggableEdge ||
118
CursorNearRightEdge ||
123
public bool InternalDragActive {
124
get { return DragResizing && drag_start_point != Cursor; }
127
DragEdge CurrentDragEdge {
129
if (CursorNearTopDraggableEdge)
131
else if (CursorNearLeftEdge)
132
return DragEdge.Left;
133
else if (CursorNearRightEdge)
134
return DragEdge.Right;
135
return DragEdge.None;
139
IEnumerable<Gdk.Window> WindowStack {
142
return parent.Screen.WindowStack;
145
return Wnck.Screen.Default.WindowsStacked.Select (wnk => Gdk.Window.ForeignNew ((uint) wnk.Xid));
153
internal DnDTracker (DockArea parent, ItemPositionProvider position, CursorTracker cursorTracker)
155
this.parent = parent;
156
cursor_type = CursorType.LeftPtr;
157
PositionProvider = position;
158
CursorTracker = cursorTracker;
160
DragState = new DragState (Cursor, null);
161
DragState.IsFinished = true;
165
RegisterGtkDragDest ();
166
RegisterGtkDragSource ();
169
public void Enable ()
171
RegisterGtkDragSource ();
174
public void Disable ()
176
UnregisterGtkDragSource ();
179
void RegisterEvents ()
181
parent.DragDataReceived += HandleDragDataReceived;
182
parent.DragMotion += HandleDragMotionEvent;
183
parent.DragBegin += HandleDragBegin;
184
parent.DragEnd += HandleDragEnd;
185
parent.DragDrop += HandleDragDrop;
186
parent.DragFailed += HandleDragFailed;
187
parent.ButtonPressEvent += HandleButtonPressEvent;
188
parent.MotionNotifyEvent += HandleMotionNotifyEvent;
189
parent.ButtonReleaseEvent += HandleButtonReleaseEvent;
191
CursorTracker.CursorUpdated += HandleCursorUpdated;
194
void UnregisterEvents ()
196
parent.DragDataReceived -= HandleDragDataReceived;
197
parent.DragMotion -= HandleDragMotionEvent;
198
parent.DragBegin -= HandleDragBegin;
199
parent.DragEnd -= HandleDragEnd;
200
parent.DragDrop -= HandleDragDrop;
201
parent.DragFailed -= HandleDragFailed;
202
parent.ButtonPressEvent -= HandleButtonPressEvent;
203
parent.MotionNotifyEvent -= HandleMotionNotifyEvent;
204
parent.ButtonReleaseEvent -= HandleButtonReleaseEvent;
206
CursorTracker.CursorUpdated -= HandleCursorUpdated;
209
void HandleMotionNotifyEvent(object o, MotionNotifyEventArgs args)
214
void HandleButtonPressEvent(object o, ButtonPressEventArgs args)
216
if (CursorNearDraggableEdge)
220
void HandleButtonReleaseEvent(object o, ButtonReleaseEventArgs args)
226
void HandleCursorUpdated(object sender, CursorUpdatedArgs e)
228
if (GtkDragging && (CursorTracker.ModifierType & ModifierType.Button1Mask) != ModifierType.Button1Mask) {
240
void HandleDragFailed (object o, DragFailedArgs args)
242
// disable the animation
243
args.RetVal = parent.CursorIsOverDockArea || DockServices.ItemsService.ItemCanBeRemoved (DockItems.IndexOf (DragState.DragItem));
246
void HandleDragDrop (object o, DragDropArgs args)
248
int index = PositionProvider.IndexAtPosition (Cursor);
249
if (parent.CursorIsOverDockArea && index >= 0 && index < DockItems.Count && uri_list != null) {
250
foreach (string uri in uri_list) {
251
if (CurrentDockItem != null && CurrentDockItem.IsAcceptingDrops && !uri.EndsWith (".desktop")) {
252
CurrentDockItem.ReceiveItem (uri);
254
Gdk.Point center = PositionProvider.IconUnzoomedPosition (index);
255
if (center.X < Cursor.X && index < DockItems.Count - 1)
257
DockServices.ItemsService.AddItemToDock (uri, index);
262
Gtk.Drag.Finish (args.Context, true, true, args.Time);
266
void HandleDragEnd (object o, DragEndArgs args)
268
if (parent.CursorIsOverDockArea) {
269
int currentPosition = PositionProvider.IndexAtPosition (Cursor);
270
if (currentPosition != -1)
271
DockServices.ItemsService.DropItemOnPosition (DragState.DragItem, currentPosition);
273
bool result = DockServices.ItemsService.RemoveItem (DragState.DragItem);
275
PoofWindow poof = new PoofWindow (DockPreferences.FullIconSize);
276
poof.SetCenterPosition (CursorTracker.RootCursor);
281
DragState.IsFinished = true;
290
void HandleDragBegin (object o, DragBeginArgs args)
293
// the user might not end the drag on the same horizontal position they start it on
294
int item = PositionProvider.IndexAtPosition (Cursor);
296
if (item != -1 && DockServices.ItemsService.ItemCanBeMoved (item))
297
DragState = new DragState (Cursor, DockItems [item]);
299
DragState = new DragState (Cursor, null);
302
if (DragState.DragItem == null) {
303
pbuf = IconProvider.PixbufFromIconName ("gtk-remove", DockPreferences.IconSize);
305
pbuf = DragState.DragItem.GetDragPixbuf ();
309
Gtk.Drag.SetIconPixbuf (args.Context, pbuf, pbuf.Width / 2, pbuf.Height / 2);
313
void HandleDragMotionEvent (object o, DragMotionArgs args)
318
if (DragState.DragItem == null || DragState.IsFinished ||
319
!DockItems.Contains (DragState.DragItem) || !parent.CursorIsOverDockArea)
322
int draggedPosition = DockItems.IndexOf (DragState.DragItem);
323
int currentPosition = PositionProvider.IndexAtPosition (Cursor);
324
if (draggedPosition == currentPosition || currentPosition == -1)
327
DockServices.ItemsService.MoveItemToPosition (draggedPosition, currentPosition);
332
if (drag_context != args.Context) {
333
Gdk.Atom target = Gtk.Drag.DestFindTarget (parent, args.Context, null);
335
Gtk.Drag.GetData (parent, args.Context, target, Gtk.Global.CurrentEventTime);
336
drag_context = args.Context;
340
Gdk.Drag.Status (args.Context, DragAction.Copy, args.Time);
344
void HandleDragDataReceived (object o, DragDataReceivedArgs args)
346
IEnumerable<string> uriList;
348
string data = System.Text.Encoding.UTF8.GetString (args.SelectionData.Data);
349
data = System.Uri.UnescapeDataString (data);
350
//sometimes we get a null at the end, and it crashes us
351
data = data.TrimEnd ('\0');
353
uriList = Regex.Split (data, "\r\n")
354
.Where (uri => uri.StartsWith ("file://"))
355
.Select (uri => uri.Substring ("file://".Length));
357
uriList = Enumerable.Empty<string> ();
361
PreviewIsDesktopFile = !uriList.Any () || uriList.Any (s => s.EndsWith (".desktop"));
366
void RegisterGtkDragSource ()
368
gtk_drag_source_set = true;
369
// we dont really want to offer the drag to anything, merely pretend to, so we set a mimetype nothing takes
370
TargetEntry te = new TargetEntry ("nomatch", TargetFlags.App | TargetFlags.OtherApp, 0);
371
Gtk.Drag.SourceSet (parent, Gdk.ModifierType.Button1Mask, new [] {te}, DragAction.Copy);
374
void RegisterGtkDragDest ()
376
TargetEntry dest_te = new TargetEntry ("text/uri-list", 0, 0);
377
Gtk.Drag.DestSet (parent, 0, new [] {dest_te}, Gdk.DragAction.Copy);
380
void UnregisterGtkDragSource ()
382
gtk_drag_source_set = false;
383
Gtk.Drag.SourceUnset (parent);
388
if ((CursorTracker.ModifierType & ModifierType.Button1Mask) != ModifierType.Button1Mask || parent.CursorIsOverDockArea) {
389
if (drag_proxy == null)
392
RegisterGtkDragDest ();
394
Gdk.Point local_cursor = CursorTracker.RootCursor;
396
IEnumerable<Gdk.Window> windows = WindowStack;
398
foreach (Gdk.Window w in windows.Reverse ()) {
399
if (w == null || w == DockWindow.Window.GdkWindow || !w.IsVisible)
404
w.GetGeometry (out rect.X, out rect.Y, out rect.Width, out rect.Height, out depth);
405
if (rect.Contains (local_cursor)) {
410
Gtk.Drag.DestSetProxy (parent, w, DragProtocol.Xdnd, true);
417
void ConfigureCursor ()
419
// we do this so that our custom drag isn't destroyed by gtk's drag
420
if (gtk_drag_source_set && CursorNearDraggableEdge) {
421
UnregisterGtkDragSource ();
423
if (cursor_type != CursorType.SbVDoubleArrow && CursorNearTopDraggableEdge) {
424
SetCursor (CursorType.SbVDoubleArrow);
426
} else if (cursor_type != CursorType.LeftSide && CursorNearLeftEdge) {
427
SetCursor (CursorType.LeftSide);
429
} else if (cursor_type != CursorType.RightSide && CursorNearRightEdge) {
430
SetCursor (CursorType.RightSide);
433
} else if (!gtk_drag_source_set && !DragResizing && !CursorNearDraggableEdge) {
434
if (!parent.PainterOverlayVisible)
435
RegisterGtkDragSource ();
436
if (cursor_type != CursorType.LeftPtr)
437
SetCursor (CursorType.LeftPtr);
441
void SetCursor (Gdk.CursorType type)
444
Gdk.Cursor tmp_cursor = new Gdk.Cursor (type);
445
parent.GdkWindow.Cursor = tmp_cursor;
446
tmp_cursor.Dispose ();
451
if (parent.PainterOverlayVisible) return;
453
drag_start_point = Cursor;
454
drag_start_icon_size = DockPreferences.IconSize;
456
DragEdge = CurrentDragEdge;
461
if (parent.PainterOverlayVisible) return;
463
DragEdge = DragEdge.None;
464
DragResizing = false;
469
void HandleDragMotion ()
474
int delta = drag_start_point.Y - Cursor.Y;
475
if (DockPreferences.Orientation == DockOrientation.Top)
477
DockPreferences.IconSize = Math.Min (drag_start_icon_size + delta, DockPreferences.MaxIconSize);
480
movement = drag_start_point.X - Cursor.X;
483
movement = Cursor.X - drag_start_point.X;
487
if (movement > DockPreferences.IconSize / 2 + 2) {
488
DockPreferences.AutomaticIcons++;
489
} else if (movement < 0 - (DockPreferences.IconSize / 2 + 2)) {
490
DockPreferences.AutomaticIcons--;
495
drag_start_point = Cursor;
498
void OnDrawRequired ()
500
if (DrawRequired != null)
501
DrawRequired (this, EventArgs.Empty);
506
if (DragEnded != null)
507
DragEnded (this, EventArgs.Empty);
510
#region IDisposable implementation
511
public void Dispose ()