1 /*
2  *  Copyright (C) 2005 Marc Pavot <marc.pavot@gmail.com>
3  *
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 2, or (at your option)
7  *  any later version.
8  *
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.
13  *
14  *  You should have received a copy of the GNU General Public License
15  *  along with this program; if not, write to the Free Software
16  *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
17  *
18  */
19 
20 #include "widgets/ario-dnd-tree.h"
21 #include <gtk/gtk.h>
22 #include <string.h>
23 #include <config.h>
24 #include "ario-util.h"
25 #include "ario-debug.h"
26 
27 #define DRAG_THRESHOLD 1
28 
29 static gboolean ario_dnd_tree_button_press_cb (GtkWidget *widget,
30                                                GdkEventButton *event,
31                                                ArioDndTree *dnd_tree);
32 static gboolean ario_dnd_tree_button_release_cb (GtkWidget *widget,
33                                                  GdkEventButton *event,
34                                                  ArioDndTree *dnd_tree);
35 static gboolean ario_dnd_tree_motion_notify (GtkWidget *widget,
36                                              GdkEventMotion *event,
37                                              ArioDndTree *dnd_tree);
38 
39 enum
40 {
41         POPUP,
42         ACTIVATE,
43         LAST_SIGNAL
44 };
45 static guint ario_dnd_tree_signals[LAST_SIGNAL] = { 0 };
46 
47 struct ArioDndTreePrivate
48 {
49         gboolean dragging;
50         gboolean pressed;
51         gint drag_start_x;
52         gint drag_start_y;
53 
54         gboolean browse_mode;
55 };
56 
57 #define ARIO_DND_TREE_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), TYPE_ARIO_DND_TREE, ArioDndTreePrivate))
G_DEFINE_TYPE(ArioDndTree,ario_dnd_tree,GTK_TYPE_TREE_VIEW)58 G_DEFINE_TYPE (ArioDndTree, ario_dnd_tree, GTK_TYPE_TREE_VIEW)
59 
60 static void
61 ario_dnd_tree_class_init (ArioDndTreeClass *klass)
62 {
63         ARIO_LOG_FUNCTION_START;
64         GObjectClass *object_class = G_OBJECT_CLASS (klass);
65 
66         /* Signals */
67         ario_dnd_tree_signals[POPUP] =
68                 g_signal_new ("popup",
69                               G_OBJECT_CLASS_TYPE (object_class),
70                               G_SIGNAL_RUN_LAST,
71                               G_STRUCT_OFFSET (ArioDndTreeClass, popup),
72                               NULL, NULL,
73                               g_cclosure_marshal_VOID__VOID,
74                               G_TYPE_NONE,
75                               0);
76 
77         ario_dnd_tree_signals[ACTIVATE] =
78                 g_signal_new ("activate",
79                               G_OBJECT_CLASS_TYPE (object_class),
80                               G_SIGNAL_RUN_LAST,
81                               G_STRUCT_OFFSET (ArioDndTreeClass, activate),
82                               NULL, NULL,
83                               g_cclosure_marshal_VOID__VOID,
84                               G_TYPE_NONE,
85                               0);
86 
87         /* Private attributes */
88         g_type_class_add_private (klass, sizeof (ArioDndTreePrivate));
89 }
90 
91 static void
ario_dnd_tree_init(ArioDndTree * dnd_tree)92 ario_dnd_tree_init (ArioDndTree *dnd_tree)
93 {
94         ARIO_LOG_FUNCTION_START;
95         dnd_tree->priv = ARIO_DND_TREE_GET_PRIVATE (dnd_tree);
96 }
97 
98 GtkWidget *
ario_dnd_tree_new(const GtkTargetEntry * targets,const gint n_targets,const gboolean browse_mode)99 ario_dnd_tree_new (const GtkTargetEntry* targets,
100                    const gint n_targets,
101                    const gboolean browse_mode)
102 {
103         ARIO_LOG_FUNCTION_START;
104         ArioDndTree *dnd_tree;
105 
106         dnd_tree = g_object_new (TYPE_ARIO_DND_TREE, NULL);
107         g_return_val_if_fail (dnd_tree->priv, NULL);
108 
109         /* Set the treeview as drag and drop source */
110         gtk_drag_source_set (GTK_WIDGET (dnd_tree),
111                              GDK_BUTTON1_MASK,
112                              targets,
113                              n_targets,
114                              GDK_ACTION_MOVE | GDK_ACTION_COPY);
115 
116         dnd_tree->priv->browse_mode = browse_mode;
117 
118         /* Signals for drag & drop */
119         g_signal_connect (dnd_tree,
120                           "button_press_event",
121                           G_CALLBACK (ario_dnd_tree_button_press_cb),
122                           dnd_tree);
123         g_signal_connect (dnd_tree,
124                           "button_release_event",
125                           G_CALLBACK (ario_dnd_tree_button_release_cb),
126                           dnd_tree);
127         g_signal_connect (dnd_tree,
128                           "motion_notify_event",
129                           G_CALLBACK (ario_dnd_tree_motion_notify),
130                           dnd_tree);
131 
132         return GTK_WIDGET (dnd_tree);
133 }
134 
135 static gboolean
ario_dnd_tree_button_press_cb(GtkWidget * widget,GdkEventButton * event,ArioDndTree * dnd_tree)136 ario_dnd_tree_button_press_cb (GtkWidget *widget,
137                                GdkEventButton *event,
138                                ArioDndTree *dnd_tree)
139 {
140         ARIO_LOG_FUNCTION_START;
141         GdkModifierType mods;
142         GtkTreePath *path;
143         int x, y, bx, by;
144         gboolean selected;
145 
146         /* Grab focus if needed */
147         if (!gtk_widget_is_focus (widget))
148                 gtk_widget_grab_focus (widget);
149 
150         /* Already in drag & drop we do nothing */
151         if (dnd_tree->priv->dragging)
152                 return FALSE;
153 
154         if (event->state & GDK_CONTROL_MASK || event->state & GDK_SHIFT_MASK)
155                 return FALSE;
156 
157         /* First button pressed */
158         if (event->button == 1) {
159                 /* Get real coordinates */
160                 gdk_window_get_device_position (gtk_widget_get_window (widget), event->device, &x, &y, &mods);
161                 gtk_tree_view_convert_widget_to_bin_window_coords (GTK_TREE_VIEW (widget), x, y, &bx, &by);
162 
163                 if (dnd_tree->priv->browse_mode)
164                         return FALSE;
165 
166                 if (bx >= 0 && by >= 0) {
167 
168                         /* Double click: we emit the activate signal */
169                         if (event->type == GDK_2BUTTON_PRESS) {
170                                 g_signal_emit (G_OBJECT (dnd_tree), ario_dnd_tree_signals[ACTIVATE], 0);
171                                 return FALSE;
172                         }
173 
174                         dnd_tree->priv->drag_start_x = x;
175                         dnd_tree->priv->drag_start_y = y;
176                         dnd_tree->priv->pressed = TRUE;
177 
178                         gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (widget), event->x, event->y, &path, NULL, NULL, NULL);
179                         if (path) {
180                                 selected = gtk_tree_selection_path_is_selected (gtk_tree_view_get_selection (GTK_TREE_VIEW (widget)), path);
181                                 gtk_tree_path_free (path);
182 
183                                 return selected;
184                         }
185 
186                         return TRUE;
187                 } else {
188                         return FALSE;
189                 }
190 
191                 return TRUE;
192         }
193 
194         /* Third button pressed */
195         if (event->button == 3) {
196                 gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (widget), event->x, event->y, &path, NULL, NULL, NULL);
197                 if (path) {
198                         /* Select the clicked row */
199                         GtkTreeSelection *selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
200                         if (!gtk_tree_selection_path_is_selected (selection, path)) {
201                                 gtk_tree_selection_unselect_all (selection);
202                                 gtk_tree_selection_select_path (selection, path);
203                         }
204                         gtk_tree_path_free (path);
205                 }
206 
207                 /* Emit the popup signal */
208                 g_signal_emit (G_OBJECT (dnd_tree), ario_dnd_tree_signals[POPUP], 0);
209                 return TRUE;
210         }
211 
212         return FALSE;
213 }
214 
215 static gboolean
ario_dnd_tree_button_release_cb(GtkWidget * widget,GdkEventButton * event,ArioDndTree * dnd_tree)216 ario_dnd_tree_button_release_cb (GtkWidget *widget,
217                                  GdkEventButton *event,
218                                  ArioDndTree *dnd_tree)
219 {
220         ARIO_LOG_FUNCTION_START;
221         if (!dnd_tree->priv->dragging && !(event->state & GDK_CONTROL_MASK) && !(event->state & GDK_SHIFT_MASK)) {
222                 /* Get real coordinates */
223                 int bx, by;
224                 gtk_tree_view_convert_widget_to_bin_window_coords (GTK_TREE_VIEW (widget), event->x, event->y, &bx, &by);
225                 if (bx >= 0 && by >= 0) {
226                         /* Select the clicked row */
227                         GtkTreePath *path;
228 
229                         gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (widget), event->x, event->y, &path, NULL, NULL, NULL);
230                         if (path) {
231                                 GtkTreeSelection *selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
232                                 gtk_tree_selection_unselect_all (selection);
233                                 gtk_tree_selection_select_path (selection, path);
234                                 gtk_tree_path_free (path);
235                         }
236                 }
237         }
238 
239         dnd_tree->priv->dragging = FALSE;
240         dnd_tree->priv->pressed = FALSE;
241 
242         return FALSE;
243 }
244 
245 static gboolean
ario_dnd_tree_motion_notify(GtkWidget * widget,GdkEventMotion * event,ArioDndTree * dnd_tree)246 ario_dnd_tree_motion_notify (GtkWidget *widget,
247                              GdkEventMotion *event,
248                              ArioDndTree *dnd_tree)
249 {
250         // desactivated to make the logs more readable
251         // ARIO_LOG_FUNCTION_START;
252         GdkModifierType mods;
253         int x, y;
254         int dx, dy;
255 
256         if ((dnd_tree->priv->dragging) || !(dnd_tree->priv->pressed))
257                 return FALSE;
258 
259         gdk_window_get_device_position (gtk_widget_get_window (widget), event->device, &x, &y, &mods);
260 
261         dx = x - dnd_tree->priv->drag_start_x;
262         dy = y - dnd_tree->priv->drag_start_y;
263 
264         /* Activate drag & drop if button pressed and mouse moved */
265         if ((ario_util_abs (dx) > DRAG_THRESHOLD) || (ario_util_abs (dy) > DRAG_THRESHOLD))
266                 dnd_tree->priv->dragging = TRUE;
267 
268         return FALSE;
269 }
270