1 /*
2 * Copyright (C) 2008 Giuseppe Torelli - <colossus73@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 of the License, or
7 * (at your option) 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., 51 Franklin Street, Fifth Floor, Boston,
17 * MA 02110-1301 USA.
18 */
19
20 #include <dirent.h>
21 #include <string.h>
22 #include <gtk/gtk.h>
23 #include "open-with-dlg.h"
24 #include "main.h"
25 #include "mime.h"
26 #include "pref_dialog.h"
27 #include "support.h"
28 #include "window.h"
29
30 typedef struct
31 {
32 gchar *name;
33 gchar *exec;
34 gboolean multiple;
35 GdkPixbuf *icon;
36 } App;
37
38 typedef struct
39 {
40 GtkWidget *dialog1;
41 GtkWidget *custom_command_entry;
42 gchar *files;
43 GSList *apps;
44 } Open_with_data;
45
xa_app_compare(const App * a,const App * b)46 static gint xa_app_compare (const App *a, const App *b)
47 {
48 if (strcmp(a->exec, b->exec) == 0)
49 {
50 if (a->name == b->name)
51 return 0;
52
53 if (a->name && b->name)
54 return strcmp(a->name, b->name);
55 }
56
57 return 1;
58 }
59
xa_app_free(App * app)60 static void xa_app_free (App *app)
61 {
62 g_free(app->name);
63 g_free(app->exec);
64 g_object_unref(app->icon);
65 g_free(app);
66 }
67
xa_open_with_dialog_expander_expanded(GObject * object,GParamSpec * param_spec,Open_with_data * data)68 static void xa_open_with_dialog_expander_expanded (GObject *object, GParamSpec *param_spec, Open_with_data *data)
69 {
70 gchar *cmd;
71
72 if (gtk_expander_get_expanded(GTK_EXPANDER(object)))
73 {
74 cmd = gtk_combo_box_text_get_active_text(GTK_COMBO_BOX_TEXT(prefs_window->combo_prefered_custom_cmd));
75
76 if (cmd && *cmd)
77 gtk_entry_set_text(GTK_ENTRY(data->custom_command_entry), cmd);
78 }
79 }
80
xa_open_with_dialog_selection_changed(GtkTreeSelection * selection,Open_with_data * data)81 static void xa_open_with_dialog_selection_changed (GtkTreeSelection *selection, Open_with_data *data)
82 {
83 gchar *exec;
84 GtkTreeIter iter;
85 GtkTreeModel *model;
86
87 if (gtk_tree_selection_get_selected(selection,&model,&iter))
88 {
89 gtk_tree_model_get(model,&iter,2,&exec,-1);
90 gtk_entry_set_text(GTK_ENTRY(data->custom_command_entry),exec);
91 g_free(exec);
92 }
93 }
94
xa_open_with_dialog_execute_command(GtkButton * button,Open_with_data * data)95 static void xa_open_with_dialog_execute_command (GtkButton *button, Open_with_data *data)
96 {
97 const char *application;
98
99 application = gtk_entry_get_text(GTK_ENTRY(data->custom_command_entry));
100 xa_launch_external_program(application, data->files);
101 gtk_widget_destroy(data->dialog1);
102 }
103
xa_open_with_dialog_row_selected(GtkTreeView * tree_view,GtkTreePath * path,GtkTreeViewColumn * column,Open_with_data * data)104 static void xa_open_with_dialog_row_selected (GtkTreeView *tree_view, GtkTreePath *path, GtkTreeViewColumn *column, Open_with_data *data)
105 {
106 xa_open_with_dialog_execute_command(NULL, data);
107 }
108
xa_open_with_dialog_custom_entry_activated(GtkEntry * entry,Open_with_data * data)109 static void xa_open_with_dialog_custom_entry_activated (GtkEntry *entry, Open_with_data *data)
110 {
111 xa_open_with_dialog_execute_command(NULL, data);
112 }
113
xa_open_with_dialog_mouse_button_event(GtkWidget * widget,GdkEventButton * event,Open_with_data * data)114 static gboolean xa_open_with_dialog_mouse_button_event (GtkWidget *widget, GdkEventButton *event, Open_with_data *data)
115 {
116 if (event->type == GDK_BUTTON_RELEASE && event->button == 2)
117 {
118 xa_open_with_dialog_execute_command(NULL, data);
119 return TRUE;
120 }
121
122 return FALSE;
123 }
124
xa_destroy_open_with_dialog(GTK_COMPAT_DESTROY_TYPE object,Open_with_data * data)125 static void xa_destroy_open_with_dialog (GTK_COMPAT_DESTROY_TYPE object, Open_with_data *data)
126 {
127 g_free(data->files);
128 g_slist_free_full(data->apps, (GDestroyNotify) xa_app_free);
129 g_free(data);
130 }
131
xa_open_with_dialog_browse_custom_command(GtkButton * button,Open_with_data * data)132 static void xa_open_with_dialog_browse_custom_command (GtkButton *button, Open_with_data *data)
133 {
134 GtkWidget *file_selector;
135 gchar *dest_dir, *dest_dir_utf8;
136 gint response;
137
138 file_selector = gtk_file_chooser_dialog_new (_("Select an application"),
139 GTK_WINDOW (xa_main_window),
140 GTK_FILE_CHOOSER_ACTION_OPEN,
141 GTK_STOCK_CANCEL,
142 GTK_RESPONSE_CANCEL,
143 GTK_STOCK_OPEN,
144 GTK_RESPONSE_ACCEPT,
145 NULL);
146
147 gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER (file_selector),"/usr/bin");
148 response = gtk_dialog_run (GTK_DIALOG(file_selector));
149 if (response == GTK_RESPONSE_ACCEPT)
150 {
151 dest_dir = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER (file_selector));
152 dest_dir_utf8 = g_filename_display_name(dest_dir);
153 gtk_entry_set_text(GTK_ENTRY(data->custom_command_entry), dest_dir_utf8);
154 g_free(dest_dir_utf8);
155 g_free(dest_dir);
156 }
157 gtk_widget_destroy(file_selector);
158 }
159
xa_parse_desktop_file(const gchar * path,const gchar * name,Open_with_data * data)160 static void xa_parse_desktop_file (const gchar *path, const gchar *name, Open_with_data *data)
161 {
162 gchar *filename, *line, *key;
163 gchar *app_name = NULL, *app_exec = NULL, *app_icon = NULL;
164 GIOStatus status;
165 GIOChannel *file;
166 gboolean group_line = FALSE, app_multiple = FALSE, has_mimetype = FALSE;
167 const gchar * const *langs, * const *l;
168 gint size;
169
170 filename = g_strconcat(path,"/",name,NULL);
171 file = g_io_channel_new_file(filename,"r",NULL);
172 g_free(filename);
173 if (file == NULL)
174 return;
175 langs = g_get_language_names();
176 g_io_channel_set_encoding(file,NULL,NULL);
177 do
178 {
179 status = g_io_channel_read_line (file, &line, NULL, NULL, NULL);
180 if (line != NULL)
181 {
182 if (strcmp(g_strchomp(line), "[Desktop Entry]") == 0)
183 {
184 group_line = TRUE;
185 continue;
186 }
187 if (group_line && *line == '[')
188 break;
189 if (!group_line)
190 continue;
191 if (g_str_has_prefix(line, "Name["))
192 {
193 l = langs;
194
195 while (*l)
196 {
197 key = g_strconcat("Name[", *l, "]=", NULL);
198
199 if (g_str_has_prefix(line, key))
200 {
201 g_free(app_name);
202 app_name = g_strndup(line + strlen(key), strlen(line) - strlen(key));
203 g_free(key);
204 break;
205 }
206
207 g_free(key);
208 l++;
209 }
210 }
211 if (!app_name && g_str_has_prefix(line, "Name="))
212 {
213 app_name = g_strndup(line + 5, strlen(line) - 5);
214 continue;
215 }
216 if (g_str_has_prefix(line,"Exec="))
217 {
218 app_exec = strstr(line, " %");
219 if (app_exec)
220 {
221 app_multiple = (app_exec[2] == 'F' || app_exec[2] == 'U');
222 app_exec = g_strndup(line + 5, app_exec - (line + 5));
223 }
224 else
225 app_exec = g_strndup(line + 5, strlen(line) - 5);
226 continue;
227 }
228 if (g_str_has_prefix(line,"Icon="))
229 {
230 app_icon = strrchr(line, '.');
231 if (app_icon)
232 app_icon = g_strndup(line + 5,app_icon - (line+5));
233 else
234 app_icon = g_strndup(line + 5, strlen(line) - 5);
235 continue;
236 }
237 if (g_str_has_prefix(line,"MimeType="))
238 has_mimetype = TRUE;
239 g_free(line);
240 }
241 }
242 while (status != G_IO_STATUS_EOF);
243
244 g_io_channel_shutdown(file, FALSE, NULL);
245
246 if (app_exec && has_mimetype)
247 {
248 App app;
249 GSList *duplicate;
250
251 app.name = app_name;
252 app.exec = app_exec;
253 app.icon = NULL;
254
255 duplicate = g_slist_find_custom(data->apps, &app, (GCompareFunc) xa_app_compare);
256
257 if (!duplicate)
258 {
259 App *app;
260 GdkPixbuf *pixbuf = NULL;
261
262 app = g_new0(App, 1);
263
264 app->name = app_name;
265 app->exec = app_exec;
266 app->multiple = app_multiple;
267
268 switch (gtk_combo_box_get_active(GTK_COMBO_BOX(prefs_window->combo_icon_size)))
269 {
270 case 0:
271 size = 24;
272 break;
273
274 case 1:
275 case 2:
276 size = 32;
277 break;
278
279 case 3:
280 size = 40;
281 break;
282
283 default:
284 size = 48;
285 break;
286 }
287
288 if (app_icon)
289 {
290 pixbuf = gtk_icon_theme_load_icon(icon_theme, app_icon, size, GTK_ICON_LOOKUP_FORCE_SIZE, NULL);
291 g_free(app_icon);
292 }
293
294 if (!pixbuf)
295 {
296 pixbuf = gdk_pixbuf_new(GDK_COLORSPACE_RGB, TRUE, 8, size, size);
297 gdk_pixbuf_fill(pixbuf, 0x00000000);
298 }
299
300 app->icon = pixbuf;
301
302 data->apps = g_slist_prepend(data->apps, app);
303
304 return;
305 }
306 }
307
308 g_free(app_name);
309 g_free(app_exec);
310 g_free(app_icon);
311 }
312
xa_read_desktop_directory(const gchar * dirname,GtkListStore * liststore,Open_with_data * data)313 static void xa_read_desktop_directory (const gchar *dirname, GtkListStore *liststore, Open_with_data *data)
314 {
315 DIR *dir;
316 gchar *filename = NULL;
317 struct dirent *dirlist;
318
319 filename = g_build_filename(dirname,"applications",NULL);
320 dir = opendir(filename);
321
322 if (dir == NULL)
323 {
324 g_free(filename);
325 return;
326 }
327
328 while ((dirlist = readdir(dir)))
329 {
330 if (g_str_has_suffix(dirlist->d_name,".desktop"))
331 xa_parse_desktop_file(filename, dirlist->d_name, data);
332 }
333
334 closedir(dir);
335 g_free(filename);
336 }
337
xa_create_open_with_dialog(const gchar * filename,gchar * filenames,gint nr)338 void xa_create_open_with_dialog (const gchar *filename, gchar *filenames, gint nr)
339 {
340 Open_with_data *data = NULL;
341 GtkListStore *apps_liststore;
342 GtkWidget *dialog_vbox1, *vbox1, *hbox1, *mime_icon, *open_text, *scrolledwindow1, *apps_treeview,
343 *custom_command_expander,*hbox_expander,*browse,*cancelbutton1,*okbutton1;
344 GtkCellRenderer *renderer;
345 GtkTreeViewColumn *column;
346 GtkTreeIter iter;
347 GdkPixbuf *pixbuf;
348 GSList *apps;
349 gchar *text = NULL;
350 gchar *title;
351 const gchar *icon_name = NULL;
352 const gchar *desktop_dir;
353 const gchar* const *desktop_dirs;
354 gint x = 0;
355
356 data = g_new0(Open_with_data,1);
357 data->files = filenames;
358 data->dialog1 = gtk_dialog_new ();
359 if (nr == 1)
360 title = _("Open With");
361 else
362 title = _("Open the selected files with");
363
364 gtk_window_set_title (GTK_WINDOW (data->dialog1),title);
365 gtk_window_set_position (GTK_WINDOW (data->dialog1), GTK_WIN_POS_CENTER_ON_PARENT);
366 gtk_window_set_modal (GTK_WINDOW (data->dialog1), TRUE);
367 gtk_window_set_type_hint (GTK_WINDOW (data->dialog1), GDK_WINDOW_TYPE_HINT_DIALOG);
368 gtk_window_set_transient_for(GTK_WINDOW(data->dialog1),GTK_WINDOW(xa_main_window));
369 gtk_container_set_border_width (GTK_CONTAINER (data->dialog1),5);
370 gtk_widget_set_size_request(data->dialog1, 380, 460);
371 dialog_vbox1 = gtk_dialog_get_content_area(GTK_DIALOG(data->dialog1));
372
373 vbox1 = gtk_vbox_new (FALSE, 5);
374 gtk_box_pack_start (GTK_BOX (dialog_vbox1),vbox1,TRUE,TRUE,0);
375
376 hbox1 = gtk_hbox_new (FALSE, 1);
377 gtk_box_pack_start (GTK_BOX (vbox1),hbox1,FALSE,FALSE,0);
378
379 if (nr == 1)
380 {
381 icon_name = xa_get_stock_mime_icon(filename);
382 pixbuf = gtk_icon_theme_load_icon(icon_theme, icon_name, 40, (GtkIconLookupFlags) 0, NULL);
383 mime_icon = gtk_image_new_from_pixbuf(pixbuf);
384 if (pixbuf)
385 g_object_unref(pixbuf);
386 gtk_box_pack_start (GTK_BOX (hbox1),mime_icon,FALSE,TRUE,0);
387 gtk_misc_set_alignment (GTK_MISC (mime_icon),0,0);
388
389 open_text = gtk_label_new("");
390 gtk_box_pack_start (GTK_BOX (hbox1),open_text,FALSE,FALSE,10);
391 text = g_strdup_printf(_("Open <i>%s</i> with:"),filename);
392 gtk_label_set_use_markup (GTK_LABEL (open_text),TRUE);
393 gtk_label_set_markup (GTK_LABEL (open_text),text);
394 g_free(text);
395 }
396 scrolledwindow1 = gtk_scrolled_window_new (NULL,NULL);
397 gtk_box_pack_start (GTK_BOX (vbox1),scrolledwindow1,TRUE,TRUE,0);
398 g_object_set (G_OBJECT (scrolledwindow1),"hscrollbar-policy",GTK_POLICY_AUTOMATIC,"shadow-type",GTK_SHADOW_IN,"vscrollbar-policy",GTK_POLICY_AUTOMATIC,NULL);
399
400 apps_liststore = gtk_list_store_new(3, GDK_TYPE_PIXBUF, G_TYPE_STRING, G_TYPE_STRING);
401 apps_treeview = gtk_tree_view_new_with_model(GTK_TREE_MODEL(apps_liststore));
402 gtk_container_add (GTK_CONTAINER (scrolledwindow1),apps_treeview);
403 gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(apps_treeview),FALSE);
404 GtkTreeSelection *sel = gtk_tree_view_get_selection( GTK_TREE_VIEW (apps_treeview));
405 g_signal_connect(sel, "changed", G_CALLBACK(xa_open_with_dialog_selection_changed), data);
406
407 /* First column: icon + text */
408 column = gtk_tree_view_column_new();
409 renderer = gtk_cell_renderer_pixbuf_new();
410 gtk_tree_view_column_pack_start(column,renderer, FALSE);
411 gtk_tree_view_column_set_attributes(column,renderer,"pixbuf",0,NULL);
412
413 renderer = gtk_cell_renderer_text_new ();
414 gtk_tree_view_column_pack_start(column,renderer, TRUE);
415 gtk_tree_view_column_set_attributes( column,renderer,"text",1,NULL);
416 gtk_tree_view_column_set_resizable (column, TRUE);
417 gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(apps_liststore), 1, GTK_SORT_ASCENDING);
418 gtk_tree_view_append_column (GTK_TREE_VIEW (apps_treeview), column);
419 gtk_tree_view_column_set_sizing (column, GTK_TREE_VIEW_COLUMN_AUTOSIZE);
420
421 /* Hidden column with the application executable name */
422 column = gtk_tree_view_column_new();
423 gtk_tree_view_column_set_visible(column,FALSE);
424 gtk_tree_view_append_column (GTK_TREE_VIEW (apps_treeview), column);
425
426 custom_command_expander = gtk_expander_new_with_mnemonic(_("Use a custom command:"));
427 g_signal_connect(G_OBJECT(custom_command_expander), "notify::expanded", G_CALLBACK(xa_open_with_dialog_expander_expanded), data);
428 gtk_box_pack_start (GTK_BOX (vbox1),custom_command_expander,FALSE,FALSE,0);
429
430 hbox_expander = gtk_hbox_new(FALSE,5);
431
432 data->custom_command_entry = gtk_entry_new();
433 g_signal_connect (G_OBJECT (data->custom_command_entry),"activate",G_CALLBACK (xa_open_with_dialog_custom_entry_activated),data);
434
435 browse = gtk_button_new_with_label(_("Browse"));
436 g_signal_connect (G_OBJECT (browse),"clicked",G_CALLBACK (xa_open_with_dialog_browse_custom_command),data);
437
438 gtk_box_pack_start (GTK_BOX (hbox_expander),data->custom_command_entry,TRUE,TRUE,0);
439 gtk_box_pack_start (GTK_BOX (hbox_expander),browse,FALSE,TRUE,0);
440 gtk_container_add(GTK_CONTAINER(custom_command_expander),hbox_expander);
441
442 cancelbutton1 = gtk_button_new_from_stock ("gtk-cancel");
443 gtk_widget_show (cancelbutton1);
444 gtk_dialog_add_action_widget (GTK_DIALOG (data->dialog1),cancelbutton1,GTK_RESPONSE_CANCEL);
445 g_signal_connect_swapped (G_OBJECT (cancelbutton1),"clicked",G_CALLBACK (gtk_widget_destroy),G_OBJECT(data->dialog1));
446
447 okbutton1 = gtk_button_new_from_stock ("gtk-open");
448 gtk_widget_show (okbutton1);
449 gtk_dialog_add_action_widget (GTK_DIALOG (data->dialog1),okbutton1,GTK_RESPONSE_OK);
450 g_signal_connect (G_OBJECT (okbutton1),"clicked",G_CALLBACK (xa_open_with_dialog_execute_command),data);
451 gtk_widget_set_can_default(okbutton1, TRUE);
452 gtk_widget_grab_default(okbutton1);
453 gtk_widget_show_all(data->dialog1);
454
455 /* let's parse the desktop files in the user data dir */
456 desktop_dir = g_get_user_data_dir();
457
458 if (desktop_dir)
459 xa_read_desktop_directory(desktop_dir, apps_liststore, data);
460
461 /* let's parse the desktop files in all the system data dirs */
462 desktop_dirs = g_get_system_data_dirs();
463
464 while (desktop_dirs[x])
465 {
466 xa_read_desktop_directory(desktop_dirs[x], apps_liststore, data);
467 x++;
468 }
469
470 apps = data->apps;
471
472 while (apps)
473 {
474 App *app = apps->data;
475
476 if (app->multiple || (nr == 1))
477 {
478 gtk_list_store_append(apps_liststore, &iter);
479 gtk_list_store_set(apps_liststore, &iter, 0, app->icon, 1, app->name, 2, app->exec, -1);
480 }
481
482 apps = apps->next;
483 }
484
485 if (data->apps)
486 {
487 gtk_tree_model_get_iter_first(GTK_TREE_MODEL(apps_liststore), &iter);
488 gtk_tree_selection_select_iter(gtk_tree_view_get_selection(GTK_TREE_VIEW(apps_treeview)), &iter);
489 }
490 else
491 gtk_widget_set_sensitive(okbutton1, FALSE);
492
493 g_signal_connect (G_OBJECT (apps_treeview), "row-activated",G_CALLBACK(xa_open_with_dialog_row_selected),data);
494 g_signal_connect(G_OBJECT(apps_treeview), "button-release-event", G_CALLBACK(xa_open_with_dialog_mouse_button_event), data);
495 g_signal_connect (G_OBJECT (data->dialog1), "destroy", G_CALLBACK(xa_destroy_open_with_dialog),data);
496 }
497