1 /*
2 * fm-dnd-src.c
3 *
4 * Copyright 2009 PCMan <pcman.tw@gmail.com>
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19 * MA 02110-1301, USA.
20 */
21
22 /**
23 * SECTION:fm-dnd-src
24 * @short_description: Libfm support for drag&drop source.
25 * @title: FmDndSrc
26 *
27 * @include: libfm/fm-gtk.h
28 *
29 * The #FmDndSrc can be used by some widget to provide support for drag
30 * operations on #FmFileInfo objects that are represented in that widget.
31 *
32 * To use #FmDndSrc the widget should create it with fm_dnd_src_new() and
33 * connect to the #FmDndSrc::data-get signal of the created #FmDndSrc
34 * object.
35 * <example id="example-fmdndsrc-usage">
36 * <title>Sample Usage</title>
37 * <programlisting>
38 * {
39 * widget->ds = fm_dnd_src_new(widget);
40 * g_signal_connect(widget->ds, "data-get", G_CALLBACK(on_data_get), widget);
41 *
42 * ...
43 * }
44 *
45 * static void on_object_finalize(MyWidget *widget)
46 * {
47 * ...
48 *
49 * g_signal_handlers_disconnect_by_data(widget->ds, widget);
50 * g_object_unref(G_OBJECT(widget->ds));
51 * }
52 *
53 * static void on_data_get(FmDndSrc *ds, MyWidget *widget)
54 * {
55 * FmFileInfo *file = widget->selected_file;
56 *
57 * fm_dnd_src_set_file(ds, file);
58 * }
59 * </programlisting>
60 * </example>
61 * The #FmDndSrc will set drag activation for the widget by left mouse
62 * button so if widget wants to use mouse movement with left button
63 * pressed for something else (rubberbanding for example) then it should
64 * disable Gtk drag handlers when needs (by blocking handlers that match
65 * object data "gtk-site-data" usually).
66 *
67 * If widget wants to handle some types of data other than #FmFileInfo
68 * objects it should do it the usual way by connecting handlers for the
69 * #GtkWidget::drag-data-get, #GtkWidget::drag-begin, and #GtkWidget::drag-end
70 * signals and adding own targets to widget's drag source target list. To
71 * exclude conflicts the widget's specific handlers should use info
72 * indices starting from N_FM_DND_SRC_DEFAULT_TARGETS.
73 */
74
75 #ifdef HAVE_CONFIG_H
76 #include <config.h>
77 #endif
78
79 #define FM_DISABLE_SEAL
80
81 #include "fm-dnd-src.h"
82 #include "fm-icon-pixbuf.h"
83
84 GtkTargetEntry fm_default_dnd_src_targets[] =
85 {
86 /* bug #3614629: the "text/uri-list" should be primary target */
87 {"text/uri-list", 0, FM_DND_SRC_TARGET_URI_LIST},
88 {"application/x-fmlist-ptr", GTK_TARGET_SAME_APP, FM_DND_SRC_TARGET_FM_LIST}
89 };
90
91 enum
92 {
93 DATA_GET,
94 N_SIGNALS
95 };
96
97 static void fm_dnd_src_dispose (GObject *object);
98
99 static void
100 on_drag_data_get ( GtkWidget *src_widget,
101 GdkDragContext *drag_context,
102 GtkSelectionData *sel_data,
103 guint info,
104 guint time,
105 FmDndSrc* ds );
106
107 static void
108 on_drag_begin ( GtkWidget *src_widget,
109 GdkDragContext *drag_context,
110 FmDndSrc* ds );
111
112 static void
113 on_drag_end ( GtkWidget *src_widget,
114 GdkDragContext *drag_context,
115 FmDndSrc* ds );
116
117 static guint signals[N_SIGNALS];
118
119
120 G_DEFINE_TYPE(FmDndSrc, fm_dnd_src, G_TYPE_OBJECT);
121
122
fm_dnd_src_class_init(FmDndSrcClass * klass)123 static void fm_dnd_src_class_init(FmDndSrcClass *klass)
124 {
125 GObjectClass *g_object_class;
126
127 g_object_class = G_OBJECT_CLASS(klass);
128 g_object_class->dispose = fm_dnd_src_dispose;
129
130 /**
131 * FmDndSrc::data-get:
132 * @object: the object which emitted the signal
133 *
134 * The #FmDndSrc::data-get signal is emitted when information of
135 * source files is needed. Handler of the signal should then call
136 * fm_dnd_src_set_files() to provide info of dragged source files.
137 *
138 * Since: 0.1.0
139 */
140 signals[ DATA_GET ] =
141 g_signal_new ( "data-get",
142 G_TYPE_FROM_CLASS( klass ),
143 G_SIGNAL_RUN_FIRST,
144 G_STRUCT_OFFSET ( FmDndSrcClass, data_get ),
145 NULL, NULL,
146 g_cclosure_marshal_VOID__VOID,
147 G_TYPE_NONE, 0 );
148 }
149
150
fm_dnd_src_dispose(GObject * object)151 static void fm_dnd_src_dispose(GObject *object)
152 {
153 FmDndSrc *ds;
154
155 g_return_if_fail(object != NULL);
156 g_return_if_fail(FM_IS_DND_SRC(object));
157
158 ds = (FmDndSrc*)object;
159
160 if(ds->files)
161 {
162 fm_file_info_list_unref(ds->files);
163 ds->files = NULL;
164 }
165
166 fm_dnd_src_set_widget(ds, NULL);
167
168 G_OBJECT_CLASS(fm_dnd_src_parent_class)->dispose(object);
169 }
170
171
fm_dnd_src_init(FmDndSrc * self)172 static void fm_dnd_src_init(FmDndSrc *self)
173 {
174
175 }
176
177 /**
178 * fm_dnd_src_new
179 * @w: (allow-none): the widget where source files are selected
180 *
181 * Creates new drag source descriptor.
182 *
183 * Before 1.0.1 this API didn't update drag source on widget so caller
184 * should set it itself. Since access to fm_default_dnd_src_targets
185 * outside of this API considered unsecure, that behavior was changed.
186 *
187 * Returns: (transfer full): a new #FmDndSrc object.
188 *
189 * Since: 0.1.0
190 */
fm_dnd_src_new(GtkWidget * w)191 FmDndSrc *fm_dnd_src_new(GtkWidget* w)
192 {
193 FmDndSrc* ds = (FmDndSrc*)g_object_new(FM_TYPE_DND_SRC, NULL);
194 fm_dnd_src_set_widget(ds, w);
195 return ds;
196 }
197
198 /**
199 * fm_dnd_src_set_widget
200 * @ds: the drag source descriptor
201 * @w: (allow-none): the widget where source files are selected
202 *
203 * Resets drag source widget in @ds.
204 *
205 * Before 1.0.1 this API didn't update drag source on widget so caller
206 * should set and unset it itself. Access to fm_default_dnd_src_targets
207 * outside of this API considered unsecure so that behavior was changed.
208 *
209 * Since: 0.1.0
210 */
fm_dnd_src_set_widget(FmDndSrc * ds,GtkWidget * w)211 void fm_dnd_src_set_widget(FmDndSrc* ds, GtkWidget* w)
212 {
213 if(w == ds->widget)
214 return;
215 if(ds->widget) /* there is an old widget connected */
216 {
217 gtk_drag_source_unset(ds->widget);
218 g_object_remove_weak_pointer(G_OBJECT(ds->widget), (gpointer*)&ds->widget);
219 g_signal_handlers_disconnect_by_func(ds->widget, on_drag_data_get, ds);
220 g_signal_handlers_disconnect_by_func(ds->widget, on_drag_begin, ds);
221 g_signal_handlers_disconnect_by_func(ds->widget, on_drag_end, ds);
222 }
223 ds->widget = w;
224 if( w )
225 {
226 GtkTargetList *targets;
227 gtk_drag_source_set(w, GDK_BUTTON1_MASK, fm_default_dnd_src_targets,
228 G_N_ELEMENTS(fm_default_dnd_src_targets),
229 GDK_ACTION_MOVE | GDK_ACTION_COPY | GDK_ACTION_LINK | GDK_ACTION_ASK);
230 targets = gtk_drag_source_get_target_list(w);
231 gtk_target_list_add_text_targets(targets, FM_DND_SRC_TARGET_TEXT);
232 g_object_add_weak_pointer(G_OBJECT(w), (gpointer*)&ds->widget);
233 g_signal_connect(w, "drag-data-get", G_CALLBACK(on_drag_data_get), ds);
234 g_signal_connect(w, "drag-begin", G_CALLBACK(on_drag_begin), ds);
235 g_signal_connect_after(w, "drag-end", G_CALLBACK(on_drag_end), ds);
236 }
237 }
238
239 /**
240 * fm_dnd_src_set_files
241 * @ds: the drag source descriptor
242 * @files: list of files to set
243 *
244 * Sets @files as selection list in the source descriptor.
245 *
246 * Since: 0.1.0
247 */
fm_dnd_src_set_files(FmDndSrc * ds,FmFileInfoList * files)248 void fm_dnd_src_set_files(FmDndSrc* ds, FmFileInfoList* files)
249 {
250 if(ds->files)
251 fm_file_info_list_unref(ds->files);
252 ds->files = fm_file_info_list_ref(files);
253 }
254
255 /**
256 * fm_dnd_src_set_file
257 * @ds: the drag source descriptor
258 * @file: files to set
259 *
260 * Sets @file as selection in the source descriptor.
261 *
262 * Since: 0.1.0
263 */
fm_dnd_src_set_file(FmDndSrc * ds,FmFileInfo * file)264 void fm_dnd_src_set_file(FmDndSrc* ds, FmFileInfo* file)
265 {
266 FmFileInfoList* files = fm_file_info_list_new();
267 fm_file_info_list_push_tail(files, file);
268 if(ds->files)
269 fm_file_info_list_unref(ds->files);
270 ds->files = files;
271 }
272
273 static void
on_drag_data_get(GtkWidget * src_widget,GdkDragContext * drag_context,GtkSelectionData * sel_data,guint info,guint time,FmDndSrc * ds)274 on_drag_data_get ( GtkWidget *src_widget,
275 GdkDragContext *drag_context,
276 GtkSelectionData *sel_data,
277 guint info,
278 guint time,
279 FmDndSrc* ds )
280 {
281 GdkAtom type;
282
283 if(info == 0 || info >= N_FM_DND_SRC_DEFAULT_TARGETS)
284 return;
285
286 type = gtk_selection_data_get_target(sel_data);
287 switch( info )
288 {
289 case FM_DND_SRC_TARGET_FM_LIST:
290 /* just store the pointer in GtkSelection since this is used
291 * within the same app. */
292 gtk_selection_data_set(sel_data, type, 8,
293 (guchar*)&ds->files, sizeof(gpointer));
294 break;
295 case FM_DND_SRC_TARGET_URI_LIST:
296 case FM_DND_SRC_TARGET_TEXT:
297 {
298 gchar* uri;
299 GString* uri_list = g_string_sized_new( 8192 );
300 GList* l;
301 FmFileInfo* file;
302
303 for( l = fm_file_info_list_peek_head_link(ds->files); l; l=l->next )
304 {
305 file = FM_FILE_INFO(l->data);
306 uri = fm_path_to_uri(fm_file_info_get_path(file));
307 g_string_append( uri_list, uri );
308 g_free( uri );
309 /* #3614629: text/uri-list should be ended with \r\n */
310 g_string_append( uri_list, "\r\n" );
311 }
312 if(info == FM_DND_SRC_TARGET_URI_LIST)
313 gtk_selection_data_set(sel_data, type, 8,
314 (guchar*)uri_list->str, uri_list->len);
315 else /* FM_DND_SRC_TARGET_TEXT */
316 gtk_selection_data_set_text(sel_data, uri_list->str, uri_list->len);
317 g_string_free( uri_list, TRUE );
318 }
319 break;
320 }
321 }
322
323 static void
on_drag_begin(GtkWidget * src_widget,GdkDragContext * drag_context,FmDndSrc * ds)324 on_drag_begin ( GtkWidget *src_widget,
325 GdkDragContext *drag_context,
326 FmDndSrc* ds )
327 {
328 gtk_drag_set_icon_default( drag_context );
329
330 /* ask drag source to provide list of source files. */
331 g_signal_emit(ds, signals[DATA_GET], 0);
332
333 /* set icon if it's single file and it's possible to obtain it */
334 if (ds->files && fm_file_info_list_get_length(ds->files) == 1)
335 {
336 FmFileInfo *fi = fm_file_info_list_peek_head(ds->files);
337 FmIcon *icon = fm_file_info_get_icon(fi);
338 if (icon)
339 #if GTK_CHECK_VERSION(3, 2, 0)
340 gtk_drag_set_icon_gicon(drag_context, fm_icon_get_gicon(icon), 0, 0);
341 #else
342 {
343 gint w;
344 GdkPixbuf *pix;
345 gtk_icon_size_lookup(GTK_ICON_SIZE_DND, &w, NULL);
346 pix = fm_pixbuf_from_icon(icon, w);
347 if (pix)
348 {
349 gtk_drag_set_icon_pixbuf(drag_context, pix, 0, 0);
350 g_object_unref(pix);
351 }
352 }
353 #endif
354 }
355 else if (ds->files)
356 gtk_drag_set_icon_stock(drag_context, GTK_STOCK_DND_MULTIPLE, 0, 0);
357 }
358
359 static void
on_drag_end(GtkWidget * src_widget,GdkDragContext * drag_context,FmDndSrc * ds)360 on_drag_end ( GtkWidget *src_widget,
361 GdkDragContext *drag_context,
362 FmDndSrc* ds )
363 {
364
365 }
366