The "file types" we have are actually combinations of types and
[metze/wireshark/wip.git] / ui / gtk / drag_and_drop.c
1 /* drag_and_drop.c
2  * Drag and Drop
3  *
4  * $Id$
5  *
6  * Wireshark - Network traffic analyzer
7  * By Gerald Combs <gerald@wireshark.org>
8  * Copyright 1998 Gerald Combs
9  *
10  * This program is free software; you can redistribute it and/or
11  * modify it under the terms of the GNU General Public License
12  * as published by the Free Software Foundation; either version 2
13  * of the License, or (at your option) any later version.
14  *
15  * This program is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  * GNU General Public License for more details.
19  *
20  * You should have received a copy of the GNU General Public License
21  * along with this program; if not, write to the Free Software
22  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
23  */
24
25 #include "config.h"
26
27 #include <string.h>
28 #include <stdio.h>
29
30 #include <gtk/gtk.h>
31
32 #include <epan/prefs.h>
33
34 #include "ui/util.h"
35 #include "../file.h"
36 #ifdef HAVE_LIBPCAP
37 #include "../capture.h"
38 #endif
39
40 #ifdef HAVE_LIBPCAP
41 #include "ui/capture_globals.h"
42 #endif
43 #include "ui/recent_utils.h"
44 #include "ui/simple_dialog.h"
45
46 #include "ui/gtk/gtkglobals.h"
47 #include "ui/gtk/capture_file_dlg.h"
48 #include "ui/gtk/drag_and_drop.h"
49 #include "ui/gtk/main.h"
50
51 #include "ui/gtk/old-gtk-compat.h"
52
53 #ifdef HAVE_GTKOSXAPPLICATION
54 #include <gtkmacintegration/gtkosxapplication.h>
55 #endif
56
57 enum { DND_TARGET_STRING, DND_TARGET_ROOTWIN, DND_TARGET_URL };
58
59 /* convert drag and drop URI to a local filename */
60 static gchar *
61 dnd_uri2filename(gchar *cf_name)
62 {
63     gchar     *src, *dest;
64     gint      ret;
65     guint     i;
66     gchar     esc[3];
67
68
69     /* Remove URI header.
70      * we have to remove the prefix to get a valid filename. */
71 #ifdef _WIN32
72     /*
73      * On win32 (at least WinXP), this prefix looks like (UNC):
74      * file:////servername/sharename/dir1/dir2/capture-file.cap
75      * or (local filename):
76      * file:///d:/dir1/dir2/capture-file.cap
77      */
78     if (strncmp("file:////", cf_name, 9) == 0) {
79         /* win32 UNC: now becoming: //servername/sharename/dir1/dir2/capture-file.cap */
80         cf_name += 7;
81     } else if (strncmp("file:///", cf_name, 8) == 0) {
82         /* win32 local: now becoming: d:/dir1/dir2/capture-file.cap */
83         cf_name += 8;
84     }
85 #else
86     /*
87      * On UNIX (at least KDE 3.0 Konqueror), this prefix looks like:
88      * file:/dir1/dir2/capture-file.cap
89      *
90      * On UNIX (at least GNOME Nautilus 2.8.2), this prefix looks like:
91      * file:///dir1/dir2/capture-file.cap
92      */
93     if (strncmp("file:", cf_name, 5) == 0) {
94         /* now becoming: /dir1/dir2/capture-file.cap or ///dir1/dir2/capture-file.cap */
95         cf_name += 5;
96         /* shorten //////thing to /thing */
97         for(; cf_name[1] == '/'; ++cf_name);
98     }
99 #endif
100
101     /*
102      * unescape the escaped URI characters (spaces, ...)
103      *
104      * we have to replace escaped chars to their equivalents,
105      * e.g. %20 (always a two digit hexstring) -> ' '
106      * the percent character '%' is escaped be a double one "%%"
107      *
108      * we do this conversation "in place" as the result is always
109      * equal or smaller in size.
110      */
111     src = cf_name;
112     dest = cf_name;
113     while (*src) {
114         if (*src == '%') {
115             src++;
116             if (*src == '%') {
117                 /* this is an escaped '%' char (was: "%%") */
118                 *dest = *src;
119                 src++;
120                 dest++;
121             } else {
122                 /* convert escaped hexnumber to unscaped character */
123                 esc[0] = src[0];
124                 esc[1] = src[1];
125                 esc[2] = '\0';
126                 ret = sscanf(esc, "%x", &i);
127                 if (ret == 1) {
128                     src+=2;
129                     *dest = (gchar) i;
130                     dest++;
131                 } else {
132                     /* somethings wrong, just jump over that char
133                      * this will result in a wrong string, but we might get
134                      * user feedback and can fix it later ;-) */
135                     src++;
136                 }
137             }
138 #ifdef _WIN32
139         } else if (*src == '/') {
140             *dest = '\\';
141             src++;
142             dest++;
143 #endif
144         } else {
145             *dest = *src;
146             src++;
147             dest++;
148         }
149     }
150     *dest = '\0';
151
152     return cf_name;
153 }
154
155 /* open/merge the dnd file */
156 void
157 dnd_open_file_cmd(gchar *cf_names_freeme)
158 {
159     int       err;
160     gchar     *cf_name;
161     int       in_file_count;
162     int       files_work;
163     char      **in_filenames;
164     char      *tmpname;
165
166     if (cf_names_freeme == NULL) return;
167
168     /* DND_TARGET_URL:
169      * The cf_name_freeme is a single string, containing one or more URI's,
170      * terminated by CR/NL chars. The length of the whole field can be found
171      * in the selection_data->length field. If it contains one file, simply open it,
172      * If it contains more than one file, ask to merge these files. */
173
174     /* count the number of input files */
175     cf_name = cf_names_freeme;
176     for(in_file_count = 0; (cf_name = strstr(cf_name, "\r\n")) != NULL; ) {
177         cf_name += 2;
178         in_file_count++;
179     }
180     if (in_file_count == 0) {
181       g_free(cf_names_freeme);
182       return;
183     }
184
185     in_filenames = (char **)g_malloc(sizeof(char*) * in_file_count);
186
187     /* store the starts of the file entries in a gchar array */
188     cf_name = cf_names_freeme;
189     in_filenames[0] = cf_name;
190     for(files_work = 1; (cf_name = strstr(cf_name, "\r\n")) != NULL && files_work < in_file_count; ) {
191         cf_name += 2;
192         in_filenames[files_work] = cf_name;
193         files_work++;
194     }
195
196     /* replace trailing CR NL simply with zeroes (in place), so we get valid terminated strings */
197     cf_name = cf_names_freeme;
198     g_strdelimit(cf_name, "\r\n", '\0');
199
200     /* convert all filenames from URI to local filename (in place) */
201     for(files_work = 0; files_work < in_file_count; files_work++) {
202         in_filenames[files_work] = dnd_uri2filename(in_filenames[files_work]);
203     }
204
205     if (in_file_count == 1) {
206         /* open and read the capture file (this will close an existing file) */
207         if (cf_open(&cfile, in_filenames[0], FALSE, &err) == CF_OK) {
208             /* XXX - add this to the menu if the read fails? */
209             cf_read(&cfile, FALSE);
210             add_menu_recent_capture_file(in_filenames[0]);
211         } else {
212             /* the capture file couldn't be read (doesn't exist, file format unknown, ...) */
213         }
214     } else {
215         /* merge the files in chronological order */
216         tmpname = NULL;
217         if (cf_merge_files(&tmpname, in_file_count, in_filenames,
218                            WTAP_FILE_TYPE_SUBTYPE_PCAP, FALSE) == CF_OK) {
219             /* Merge succeeded; close the currently-open file and try
220                to open the merged capture file. */
221             cf_close(&cfile);
222             if (cf_open(&cfile, tmpname, TRUE /* temporary file */, &err) == CF_OK) {
223                 g_free(tmpname);
224                 cf_read(&cfile, FALSE);
225             } else {
226                 /* The merged file couldn't be read. */
227                 g_free(tmpname);
228             }
229         } else {
230             /* merge failed */
231             g_free(tmpname);
232         }
233     }
234
235     g_free(in_filenames);
236     g_free(cf_names_freeme);
237 }
238
239 /* we have received some drag and drop data */
240 /* (as we only registered to "text/uri-list", we will only get a file list here) */
241 static void
242 dnd_data_received(GtkWidget *widget _U_, GdkDragContext *dc _U_, gint x _U_, gint y _U_,
243                   GtkSelectionData *selection_data, guint info, guint t _U_, gpointer data _U_)
244 {
245     gchar *cf_names_freeme;
246     const guchar *sel_data_data;
247     gint sel_data_len;
248
249     if (info == DND_TARGET_URL) {
250         /* Usually we block incoming events by disabling the corresponding menu/toolbar items.
251          * This is the only place where an incoming event won't be blocked in such a way,
252          * so we have to take care of NOT loading a new file while a different process
253          * (e.g. capture/load/...) is still in progress. */
254
255 #ifdef HAVE_LIBPCAP
256         /* if a capture is running, do nothing but warn the user */
257         if((global_capture_session.state != CAPTURE_STOPPED)) {
258             simple_dialog(ESD_TYPE_CONFIRMATION,
259                         ESD_BTN_OK,
260                         "%sDrag and Drop currently not possible!%s\n\n"
261                         "Dropping a file isn't possible while a capture is in progress.",
262                         simple_dialog_primary_start(), simple_dialog_primary_end());
263             return;
264         }
265 #endif
266
267         /* if another file read is still in progress, do nothing but warn the user */
268         if(cfile.state == FILE_READ_IN_PROGRESS) {
269             simple_dialog(ESD_TYPE_CONFIRMATION,
270                         ESD_BTN_OK,
271                         "%sDrag and Drop currently not possible!%s\n\n"
272                         "Dropping a file isn't possible while loading another capture file.",
273                         simple_dialog_primary_start(), simple_dialog_primary_end());
274             return;
275         }
276
277         /* the selection_data will soon be gone, make a copy first */
278         /* the data string is not zero terminated -> make a zero terminated "copy" of it */
279         sel_data_len = gtk_selection_data_get_length(selection_data);
280         sel_data_data = gtk_selection_data_get_data(selection_data);
281         cf_names_freeme = (gchar *)g_malloc(sel_data_len + 1);
282         memcpy(cf_names_freeme, sel_data_data, sel_data_len);
283         cf_names_freeme[sel_data_len] = '\0';
284
285         /* If there's unsaved data, let the user save it first.
286            If they cancel out of it, don't open the file. */
287         if (do_file_close(&cfile, FALSE, " before opening a new capture file"))
288             dnd_open_file_cmd(cf_names_freeme);
289     }
290 }
291
292 #ifdef HAVE_GTKOSXAPPLICATION
293 gboolean
294 gtk_osx_openFile (GtkosxApplication *app _U_, gchar *path, gpointer user_data _U_)
295 {
296     GtkSelectionData selection_data;
297     gchar* selection_path;
298     size_t length = strlen(path);
299
300     selection_path = (gchar *)g_malloc(length + 3);
301     memcpy(selection_path, path, length);
302
303     selection_path[length] = '\r';
304     selection_path[length + 1] = '\n';
305     selection_path[length + 2] = '\0';
306
307     memset(&selection_data, 0, sizeof(selection_data));
308
309     gtk_selection_data_set(&selection_data, gdk_atom_intern_static_string ("text/uri-list"), 8, (guchar*) selection_path, (gint)(length + 2));
310     dnd_data_received(NULL, NULL, 0, 0, &selection_data, DND_TARGET_URL, 0, 0);
311
312     return TRUE;
313 }
314 #endif
315
316 /* init the drag and drop functionality */
317 void
318 dnd_init(GtkWidget *w)
319 {
320     /* we are only interested in the URI list containing filenames */
321     static GtkTargetEntry target_entry[] = {
322          /*{"STRING", 0, DND_TARGET_STRING},*/
323          /*{"text/plain", 0, DND_TARGET_STRING},*/
324          {(gchar *)"text/uri-list", 0, DND_TARGET_URL}
325     };
326
327     /* set this window as a dnd destination */
328     gtk_drag_dest_set(
329          w, GTK_DEST_DEFAULT_ALL, target_entry,
330          sizeof(target_entry) / sizeof(GtkTargetEntry),
331          (GdkDragAction)(GDK_ACTION_MOVE | GDK_ACTION_COPY) );
332
333     /* get notified, if some dnd coming in */
334     g_signal_connect(w, "drag_data_received", G_CALLBACK(dnd_data_received), NULL);
335 #ifdef HAVE_GTKOSXAPPLICATION
336     g_signal_connect(g_object_new(GTKOSX_TYPE_APPLICATION, NULL), "NSApplicationOpenFile", G_CALLBACK(gtk_osx_openFile), NULL);
337 #endif
338 }
339
340