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