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