1 /* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
2 
3 /*
4    eel-open-with-dialog.c: an open-with dialog
5 
6    Copyright (C) 2004 Novell, Inc.
7    Copyright (C) 2007 Red Hat, Inc.
8 
9    The Mate Library is free software; you can redistribute it and/or
10    modify it under the terms of the GNU Library General Public License as
11    published by the Free Software Foundation; either version 2 of the
12    License, or (at your option) any later version.
13 
14    The Mate Library is distributed in the hope that it will be useful,
15    but WITHOUT ANY WARRANTY; without even the implied warranty of
16    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
17    Library General Public License for more details.
18 
19    You should have received a copy of the GNU Library General Public
20    License along with the Mate Library; see the file COPYING.LIB.  If not,
21    write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
22    Boston, MA 02110-1301, USA.
23 
24    Authors: Dave Camp <dave@novell.com>
25             Alexander Larsson <alexl@redhat.com>
26 */
27 
28 #include <config.h>
29 #include <string.h>
30 #include <cairo-gobject.h>
31 
32 #include <glib/gi18n-lib.h>
33 #include <gtk/gtk.h>
34 #include <gio/gio.h>
35 
36 #include <eel/eel-stock-dialogs.h>
37 
38 #include "caja-open-with-dialog.h"
39 #include "caja-signaller.h"
40 
41 #define sure_string(s)                    ((const char *)((s)!=NULL?(s):""))
42 
43 struct _CajaOpenWithDialogDetails
44 {
45     GAppInfo *selected_app_info;
46 
47     char *content_type;
48     char *extension;
49 
50     GtkWidget *label;
51     GtkWidget *entry;
52     GtkWidget *button;
53     GtkWidget *checkbox;
54 
55     GtkWidget *desc_label;
56 
57     GtkWidget *open_label;
58 
59     GtkWidget     *program_list;
60     GtkListStore  *program_list_store;
61     GSList	      *add_icon_paths;
62     gint	       add_items_idle_id;
63     gint	       add_icons_idle_id;
64 
65     gboolean add_mode;
66 };
67 
68 enum
69 {
70     COLUMN_APP_INFO,
71     COLUMN_ICON,
72     COLUMN_GICON,
73     COLUMN_NAME,
74     COLUMN_COMMENT,
75     COLUMN_EXEC,
76     NUM_COLUMNS
77 };
78 
79 enum
80 {
81     RESPONSE_OPEN,
82     RESPONSE_REMOVE
83 };
84 
85 enum
86 {
87     APPLICATION_SELECTED,
88     LAST_SIGNAL
89 };
90 
91 static guint signals[LAST_SIGNAL] = { 0 };
92 G_DEFINE_TYPE (CajaOpenWithDialog, caja_open_with_dialog, GTK_TYPE_DIALOG);
93 
94 static void
caja_open_with_dialog_finalize(GObject * object)95 caja_open_with_dialog_finalize (GObject *object)
96 {
97     CajaOpenWithDialog *dialog;
98 
99     dialog = CAJA_OPEN_WITH_DIALOG (object);
100 
101     if (dialog->details->add_icons_idle_id)
102     {
103         g_source_remove (dialog->details->add_icons_idle_id);
104     }
105 
106     if (dialog->details->add_items_idle_id)
107     {
108         g_source_remove (dialog->details->add_items_idle_id);
109     }
110 
111     if (dialog->details->selected_app_info)
112     {
113         g_object_unref (dialog->details->selected_app_info);
114     }
115     g_free (dialog->details->content_type);
116     g_free (dialog->details->extension);
117 
118     g_free (dialog->details);
119 
120     G_OBJECT_CLASS (caja_open_with_dialog_parent_class)->finalize (object);
121 }
122 
123 /* An application is valid if:
124  *
125  * 1) The file exists
126  * 2) The user has permissions to run the file
127  */
128 static gboolean
check_application(CajaOpenWithDialog * dialog)129 check_application (CajaOpenWithDialog *dialog)
130 {
131     char *command;
132     char *path = NULL;
133     char **argv = NULL;
134     int argc;
135     GError *error = NULL;
136     gint retval = TRUE;
137 
138     command = NULL;
139     if (dialog->details->selected_app_info != NULL)
140     {
141         command = g_strdup (g_app_info_get_executable (dialog->details->selected_app_info));
142     }
143 
144     if (command == NULL)
145     {
146         command = g_strdup (gtk_entry_get_text (GTK_ENTRY (dialog->details->entry)));
147     }
148 
149     g_shell_parse_argv (command, &argc, &argv, &error);
150     if (error)
151     {
152         eel_show_error_dialog (_("Could not run application"),
153                                error->message,
154                                GTK_WINDOW (dialog));
155         g_error_free (error);
156         retval = FALSE;
157         goto cleanup;
158     }
159 
160     path = g_find_program_in_path (argv[0]);
161     if (!path)
162     {
163         char *error_message;
164 
165         error_message = g_strdup_printf (_("Could not find '%s'"),
166                                          argv[0]);
167 
168         eel_show_error_dialog (_("Could not find application"),
169                                error_message,
170                                GTK_WINDOW (dialog));
171         g_free (error_message);
172         retval = FALSE;
173         goto cleanup;
174     }
175 
176 cleanup:
177     g_strfreev (argv);
178     g_free (path);
179     g_free (command);
180 
181     return retval;
182 }
183 
184 /* Only called for non-desktop files */
185 static char *
get_app_name(const char * commandline,GError ** error)186 get_app_name (const char *commandline, GError **error)
187 {
188     char *basename;
189     char *unquoted;
190     char **argv;
191     int argc;
192 
193     if (!g_shell_parse_argv (commandline,
194                              &argc, &argv, error))
195     {
196         return NULL;
197     }
198 
199     unquoted = g_shell_unquote (argv[0], NULL);
200     if (unquoted)
201     {
202         basename = g_path_get_basename (unquoted);
203     }
204     else
205     {
206         basename = g_strdup (argv[0]);
207     }
208 
209     g_free (unquoted);
210     g_strfreev (argv);
211 
212     return basename;
213 }
214 
215 /* This will check if the application the user wanted exists will return that
216  * application.  If it doesn't exist, it will create one and return that.
217  * It also sets the app info as the default for this type.
218  */
219 static GAppInfo *
add_or_find_application(CajaOpenWithDialog * dialog)220 add_or_find_application (CajaOpenWithDialog *dialog)
221 {
222     GAppInfo *app;
223     GError *error;
224     gboolean success, should_set_default;
225     char *message;
226 
227     error = NULL;
228     app = NULL;
229     if (dialog->details->selected_app_info)
230     {
231         app = g_object_ref (dialog->details->selected_app_info);
232     }
233     else
234     {
235         char *app_name;
236         const char *commandline;
237 
238         commandline = gtk_entry_get_text (GTK_ENTRY (dialog->details->entry));
239         app_name = get_app_name (commandline, &error);
240         if (app_name != NULL)
241         {
242             app = g_app_info_create_from_commandline (commandline,
243                     app_name,
244                     G_APP_INFO_CREATE_NONE,
245                     &error);
246             g_free (app_name);
247         }
248     }
249 
250     if (app == NULL)
251     {
252         message = g_strdup_printf (_("Could not add application to the application database: %s"), error ? error->message : _("Unknown error"));
253         eel_show_error_dialog (_("Could not add application"),
254                                message,
255                                GTK_WINDOW (dialog));
256         g_free (message);
257 
258         if (error)
259             g_error_free (error);
260 
261         return NULL;
262     }
263 
264     should_set_default = (dialog->details->add_mode) ||
265                           gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (dialog->details->checkbox));
266     success = TRUE;
267 
268     if (should_set_default)
269     {
270         if (dialog->details->content_type)
271         {
272             success = g_app_info_set_as_default_for_type (app,
273                       dialog->details->content_type,
274                       &error);
275         }
276         else
277         {
278             success = g_app_info_set_as_default_for_extension (app,
279                       dialog->details->extension,
280                       &error);
281         }
282     }
283     else
284     {
285         GList *applications;
286 
287         applications = g_app_info_get_all_for_type (dialog->details->content_type);
288         if (dialog->details->content_type && applications != NULL)
289         {
290             /* we don't care about reporting errors here */
291             g_app_info_add_supports_type (app,
292                                           dialog->details->content_type,
293                                           NULL);
294         }
295 
296         if (applications != NULL)
297         {
298             g_list_free_full (applications, g_object_unref);
299         }
300     }
301 
302     if (!success && should_set_default)
303     {
304         message = g_strdup_printf (_("Could not set application as the default: %s"), error->message);
305         eel_show_error_dialog (_("Could not set as default application"),
306                                message,
307                                GTK_WINDOW (dialog));
308         g_free (message);
309         g_error_free (error);
310     }
311 
312     g_signal_emit_by_name (caja_signaller_get_current (),
313                            "mime_data_changed");
314     return app;
315 }
316 
317 static void
emit_application_selected(CajaOpenWithDialog * dialog,GAppInfo * application)318 emit_application_selected (CajaOpenWithDialog *dialog,
319                            GAppInfo *application)
320 {
321     g_signal_emit (G_OBJECT (dialog), signals[APPLICATION_SELECTED], 0,
322                    application);
323 }
324 
325 static void
response_cb(CajaOpenWithDialog * dialog,int response_id,gpointer data)326 response_cb (CajaOpenWithDialog *dialog,
327              int response_id,
328              gpointer data)
329 {
330     switch (response_id)
331     {
332     case RESPONSE_OPEN:
333         if (check_application (dialog))
334         {
335             GAppInfo *application;
336 
337             application = add_or_find_application (dialog);
338 
339             if (application)
340             {
341                 emit_application_selected (dialog, application);
342                 g_object_unref (application);
343 
344                 gtk_widget_destroy (GTK_WIDGET (dialog));
345             }
346         }
347 
348         break;
349     case RESPONSE_REMOVE:
350         if (dialog->details->selected_app_info != NULL)
351         {
352             if (g_app_info_delete (dialog->details->selected_app_info))
353             {
354                 GtkTreeModel *model;
355                 GtkTreeIter iter;
356                 GAppInfo *info, *selected;
357 
358                 selected = dialog->details->selected_app_info;
359                 dialog->details->selected_app_info = NULL;
360 
361                 model = GTK_TREE_MODEL (dialog->details->program_list_store);
362                 if (gtk_tree_model_get_iter_first (model, &iter))
363                 {
364                     do
365                     {
366                         gtk_tree_model_get (model, &iter,
367                                             COLUMN_APP_INFO, &info,
368                                             -1);
369                         if (g_app_info_equal (selected, info))
370                         {
371                             gtk_list_store_remove (dialog->details->program_list_store, &iter);
372                             break;
373                         }
374                     }
375                     while (gtk_tree_model_iter_next (model, &iter));
376                 }
377 
378                 g_object_unref (selected);
379             }
380         }
381         break;
382     case GTK_RESPONSE_NONE:
383     case GTK_RESPONSE_DELETE_EVENT:
384     case GTK_RESPONSE_CANCEL:
385         gtk_widget_destroy (GTK_WIDGET (dialog));
386         break;
387     default :
388         g_assert_not_reached ();
389     }
390 
391 }
392 
393 
394 static void
caja_open_with_dialog_class_init(CajaOpenWithDialogClass * class)395 caja_open_with_dialog_class_init (CajaOpenWithDialogClass *class)
396 {
397     GObjectClass *gobject_class;
398 
399     gobject_class = G_OBJECT_CLASS (class);
400     gobject_class->finalize = caja_open_with_dialog_finalize;
401 
402     signals[APPLICATION_SELECTED] =
403         g_signal_new ("application_selected",
404                       G_TYPE_FROM_CLASS (class),
405                       G_SIGNAL_RUN_LAST,
406                       G_STRUCT_OFFSET (CajaOpenWithDialogClass,
407                                        application_selected),
408                       NULL, NULL,
409                       g_cclosure_marshal_VOID__POINTER,
410                       G_TYPE_NONE,
411                       1, G_TYPE_POINTER);
412 }
413 
414 static void
chooser_response_cb(GtkFileChooser * chooser,int response,gpointer user_data)415 chooser_response_cb (GtkFileChooser *chooser,
416                      int response,
417                      gpointer user_data)
418 {
419     CajaOpenWithDialog *dialog;
420 
421     dialog = CAJA_OPEN_WITH_DIALOG (user_data);
422 
423     if (response == GTK_RESPONSE_OK)
424     {
425         char *filename;
426 
427         filename = gtk_file_chooser_get_filename (chooser);
428 
429         if (filename)
430         {
431             char *quoted_text;
432 
433             quoted_text = g_shell_quote (filename);
434 
435             gtk_entry_set_text (GTK_ENTRY (dialog->details->entry),
436                                 quoted_text);
437             gtk_editable_set_position (GTK_EDITABLE (dialog->details->entry), -1);
438             g_free (quoted_text);
439             g_free (filename);
440         }
441     }
442 
443     gtk_widget_destroy (GTK_WIDGET (chooser));
444 }
445 
446 static void
browse_clicked_cb(GtkWidget * button,gpointer user_data)447 browse_clicked_cb (GtkWidget *button,
448                    gpointer user_data)
449 {
450     CajaOpenWithDialog *dialog;
451     GtkWidget *chooser;
452 
453     dialog = CAJA_OPEN_WITH_DIALOG (user_data);
454 
455     chooser = eel_file_chooser_dialog_new (_("Select an Application"),
456                                            GTK_WINDOW (dialog),
457                                            GTK_FILE_CHOOSER_ACTION_OPEN,
458                                            "process-stop",
459                                            GTK_RESPONSE_CANCEL,
460                                            "document-open",
461                                            GTK_RESPONSE_OK,
462                                            NULL);
463     gtk_window_set_destroy_with_parent (GTK_WINDOW (chooser), TRUE);
464     g_signal_connect (chooser, "response",
465                       G_CALLBACK (chooser_response_cb), dialog);
466     gtk_dialog_set_default_response (GTK_DIALOG (chooser),
467                                      GTK_RESPONSE_OK);
468     gtk_file_chooser_set_local_only (GTK_FILE_CHOOSER (chooser), TRUE);
469     gtk_file_chooser_set_select_multiple (GTK_FILE_CHOOSER (chooser),
470                                           FALSE);
471     gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (chooser),
472                                          "/usr/bin");
473 
474     gtk_widget_show (chooser);
475 }
476 
477 static void
entry_changed_cb(GtkWidget * entry,CajaOpenWithDialog * dialog)478 entry_changed_cb (GtkWidget *entry,
479                   CajaOpenWithDialog *dialog)
480 {
481     /* We are writing in the entry, so we are not using a known appinfo anymore */
482     if (dialog->details->selected_app_info != NULL)
483     {
484         g_object_unref (dialog->details->selected_app_info);
485         dialog->details->selected_app_info = NULL;
486     }
487 
488     if (gtk_entry_get_text (GTK_ENTRY (dialog->details->entry))[0] == '\000')
489     {
490         gtk_widget_set_sensitive (dialog->details->button, FALSE);
491     }
492     else
493     {
494         gtk_widget_set_sensitive (dialog->details->button, TRUE);
495     }
496 }
497 
498 #define CAJA_OPEN_WITH_DIALOG_ICON_SIZE 24
499 static cairo_surface_t *
get_surface_for_icon(GIcon * icon)500 get_surface_for_icon (GIcon *icon)
501 {
502     cairo_surface_t  *surface;
503     gint icon_scale;
504 
505     surface = NULL;
506     icon_scale = gdk_window_get_scale_factor (gdk_get_default_root_window ());
507 
508     if (G_IS_FILE_ICON (icon))
509     {
510         char *filename;
511 
512         filename = g_file_get_path (g_file_icon_get_file (G_FILE_ICON (icon)));
513         if (filename)
514         {
515             GdkPixbuf *pixbuf;
516             pixbuf = gdk_pixbuf_new_from_file_at_size (filename,
517                                                        CAJA_OPEN_WITH_DIALOG_ICON_SIZE * icon_scale,
518                                                        CAJA_OPEN_WITH_DIALOG_ICON_SIZE * icon_scale,
519                                                        NULL);
520             surface = gdk_cairo_surface_create_from_pixbuf (pixbuf, icon_scale, NULL);
521             g_object_unref (pixbuf);
522         }
523         g_free (filename);
524     }
525     else if (G_IS_THEMED_ICON (icon))
526     {
527         const char * const *names;
528 
529         names = g_themed_icon_get_names (G_THEMED_ICON (icon));
530 
531         if (names != NULL && names[0] != NULL)
532         {
533             char *icon_no_extension;
534             char *p;
535 
536             icon_no_extension = g_strdup (names[0]);
537             p = strrchr (icon_no_extension, '.');
538             if (p &&
539                     (strcmp (p, ".png") == 0 ||
540                      strcmp (p, ".xpm") == 0 ||
541                      strcmp (p, ".svg") == 0))
542             {
543                 *p = 0;
544             }
545             surface = gtk_icon_theme_load_surface (gtk_icon_theme_get_default (),
546                                                    icon_no_extension,
547                                                    CAJA_OPEN_WITH_DIALOG_ICON_SIZE,
548                                                    icon_scale,
549                                                    NULL,
550                                                    GTK_ICON_LOOKUP_FORCE_SIZE,
551                                                    NULL);
552             g_free (icon_no_extension);
553         }
554     }
555     return surface;
556 }
557 
558 static gboolean
caja_open_with_dialog_add_icon_idle(CajaOpenWithDialog * dialog)559 caja_open_with_dialog_add_icon_idle (CajaOpenWithDialog *dialog)
560 {
561     cairo_surface_t *surface;
562     GIcon           *icon;
563     gboolean         long_operation;
564     GtkTreeIter      iter;
565     GtkTreePath     *path = NULL;
566 
567     long_operation = FALSE;
568     do
569     {
570         if (!dialog->details->add_icon_paths)
571         {
572             dialog->details->add_icons_idle_id = 0;
573             return FALSE;
574         }
575 
576         path = dialog->details->add_icon_paths->data;
577         dialog->details->add_icon_paths->data = NULL;
578         dialog->details->add_icon_paths = g_slist_delete_link (dialog->details->add_icon_paths,
579                                           dialog->details->add_icon_paths);
580 
581         if (!gtk_tree_model_get_iter (GTK_TREE_MODEL (dialog->details->program_list_store),
582                                       &iter, path))
583         {
584             gtk_tree_path_free (path);
585             continue;
586         }
587 
588         gtk_tree_path_free (path);
589 
590         gtk_tree_model_get (GTK_TREE_MODEL (dialog->details->program_list_store), &iter,
591                             COLUMN_GICON, &icon, -1);
592 
593         if (icon == NULL)
594         {
595             continue;
596         }
597 
598         surface = get_surface_for_icon (icon);
599         if (surface)
600         {
601             long_operation = TRUE;
602             gtk_list_store_set (dialog->details->program_list_store, &iter, COLUMN_ICON, surface, -1);
603             cairo_surface_destroy (surface);
604         }
605 
606         /* don't go back into the main loop if this wasn't very hard to do */
607     }
608     while (!long_operation);
609 
610     return TRUE;
611 }
612 
613 
614 static gboolean
caja_open_with_search_equal_func(GtkTreeModel * model,int column,const char * key,GtkTreeIter * iter,gpointer user_data)615 caja_open_with_search_equal_func (GtkTreeModel *model,
616                                   int column,
617                                   const char *key,
618                                   GtkTreeIter *iter,
619                                   gpointer user_data)
620 {
621     char *name;
622     char *path;
623     gboolean ret;
624 
625     if (key != NULL)
626     {
627         char *normalized_key;
628 
629         normalized_key = g_utf8_casefold (key, -1);
630         g_assert (normalized_key != NULL);
631 
632         ret = TRUE;
633 
634         gtk_tree_model_get (model, iter,
635                             COLUMN_NAME, &name,
636                             COLUMN_EXEC, &path,
637                             -1);
638 
639         if (name != NULL)
640         {
641             char *normalized_name;
642 
643             normalized_name = g_utf8_casefold (name, -1);
644             g_assert (normalized_name != NULL);
645 
646             if (strncmp (normalized_name, normalized_key, strlen (normalized_key)) == 0)
647             {
648                 ret = FALSE;
649             }
650 
651             g_free (normalized_name);
652         }
653 
654         if (ret && path != NULL)
655         {
656             char *normalized_path;
657             char *basename, *normalized_basename;
658 
659             normalized_path = g_utf8_casefold (path, -1);
660             g_assert (normalized_path != NULL);
661 
662             basename = g_path_get_basename (path);
663             g_assert (basename != NULL);
664 
665             normalized_basename = g_utf8_casefold (basename, -1);
666             g_assert (normalized_basename != NULL);
667 
668             if (strncmp (normalized_path, normalized_key, strlen (normalized_key)) == 0 ||
669                     strncmp (normalized_basename, normalized_key, strlen (normalized_key)) == 0)
670             {
671                 ret = FALSE;
672             }
673 
674             g_free (basename);
675             g_free (normalized_basename);
676             g_free (normalized_path);
677         }
678 
679         g_free (name);
680         g_free (path);
681         g_free (normalized_key);
682 
683         return ret;
684     }
685     else
686     {
687         return TRUE;
688     }
689 }
690 
691 
692 
693 static gboolean
caja_open_with_dialog_add_items_idle(CajaOpenWithDialog * dialog)694 caja_open_with_dialog_add_items_idle (CajaOpenWithDialog *dialog)
695 {
696     GtkCellRenderer   *renderer;
697     GtkTreeViewColumn *column;
698     GtkTreeModel      *sort;
699     GList             *all_applications;
700     GList             *l;
701 
702     /* create list store */
703     dialog->details->program_list_store = gtk_list_store_new (NUM_COLUMNS,
704                                           G_TYPE_APP_INFO,
705                                           CAIRO_GOBJECT_TYPE_SURFACE,
706                                           G_TYPE_ICON,
707                                           G_TYPE_STRING,
708                                           G_TYPE_STRING,
709                                           G_TYPE_STRING);
710     sort = gtk_tree_model_sort_new_with_model (GTK_TREE_MODEL (dialog->details->program_list_store));
711     all_applications = g_app_info_get_all ();
712 
713     for (l = all_applications; l; l = l->next)
714     {
715         GAppInfo *app = l->data;
716         GtkTreeIter     iter;
717         GtkTreePath    *path;
718 
719         if (!g_app_info_supports_uris (app) &&
720                 !g_app_info_supports_files (app))
721             continue;
722 
723         gtk_list_store_append (dialog->details->program_list_store, &iter);
724         gtk_list_store_set (dialog->details->program_list_store, &iter,
725                             COLUMN_APP_INFO,  app,
726                             COLUMN_ICON,      NULL,
727                             COLUMN_GICON,     g_app_info_get_icon (app),
728                             COLUMN_NAME,      g_app_info_get_display_name (app),
729                             COLUMN_COMMENT,   g_app_info_get_description (app),
730                             COLUMN_EXEC,      g_app_info_get_executable,
731                             -1);
732 
733         path = gtk_tree_model_get_path (GTK_TREE_MODEL (dialog->details->program_list_store), &iter);
734         if (path != NULL)
735         {
736             dialog->details->add_icon_paths = g_slist_prepend (dialog->details->add_icon_paths, path);
737         }
738     }
739     g_list_free (all_applications);
740 
741     gtk_tree_view_set_model (GTK_TREE_VIEW (dialog->details->program_list),
742                              GTK_TREE_MODEL (sort));
743     gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (sort),
744                                           COLUMN_NAME, GTK_SORT_ASCENDING);
745     gtk_tree_view_set_search_equal_func (GTK_TREE_VIEW (dialog->details->program_list),
746                                          caja_open_with_search_equal_func,
747                                          NULL, NULL);
748 
749     renderer = gtk_cell_renderer_pixbuf_new ();
750     column = gtk_tree_view_column_new ();
751     gtk_tree_view_column_pack_start (column, renderer, FALSE);
752     gtk_tree_view_column_set_attributes (column, renderer,
753                                          "surface", COLUMN_ICON,
754                                          NULL);
755 
756     renderer = gtk_cell_renderer_text_new ();
757     gtk_tree_view_column_pack_start (column, renderer, TRUE);
758     gtk_tree_view_column_set_attributes (column, renderer,
759                                          "text", COLUMN_NAME,
760                                          NULL);
761     gtk_tree_view_column_set_sort_column_id (column, COLUMN_NAME);
762     gtk_tree_view_append_column (GTK_TREE_VIEW (dialog->details->program_list), column);
763 
764     dialog->details->add_icon_paths = g_slist_reverse (dialog->details->add_icon_paths);
765 
766     if (!dialog->details->add_icons_idle_id)
767     {
768         dialog->details->add_icons_idle_id =
769             g_idle_add_full (G_PRIORITY_DEFAULT_IDLE, (GSourceFunc) caja_open_with_dialog_add_icon_idle,
770                              dialog, NULL);
771     }
772 
773     dialog->details->add_items_idle_id = 0;
774     return FALSE;
775 }
776 
777 static void
program_list_selection_changed(GtkTreeSelection * selection,CajaOpenWithDialog * dialog)778 program_list_selection_changed (GtkTreeSelection  *selection,
779                                 CajaOpenWithDialog *dialog)
780 {
781     GtkTreeModel     *model;
782     GtkTreeIter       iter;
783     GAppInfo *info;
784 
785     if (!gtk_tree_selection_get_selected (selection, &model, &iter))
786     {
787         gtk_widget_set_sensitive (dialog->details->button, FALSE);
788         gtk_dialog_set_response_sensitive (GTK_DIALOG (dialog),
789                                            RESPONSE_REMOVE,
790                                            FALSE);
791         return;
792     }
793 
794     info = NULL;
795     gtk_tree_model_get (model, &iter,
796                         COLUMN_APP_INFO, &info,
797                         -1);
798 
799     if (info == NULL)
800     {
801         return;
802     }
803 
804     gtk_entry_set_text (GTK_ENTRY (dialog->details->entry),
805                         sure_string (g_app_info_get_executable (info)));
806     gtk_label_set_text (GTK_LABEL (dialog->details->desc_label),
807                         sure_string (g_app_info_get_description (info)));
808     gtk_widget_set_sensitive (dialog->details->button, TRUE);
809     gtk_dialog_set_response_sensitive (GTK_DIALOG (dialog),
810                                        RESPONSE_REMOVE,
811                                        g_app_info_can_delete (info));
812 
813     if (dialog->details->selected_app_info)
814     {
815         g_object_unref (dialog->details->selected_app_info);
816     }
817 
818     dialog->details->selected_app_info = info;
819 }
820 
821 static void
program_list_selection_activated(GtkTreeView * view,GtkTreePath * path,GtkTreeViewColumn * column,CajaOpenWithDialog * dialog)822 program_list_selection_activated (GtkTreeView       *view,
823                                   GtkTreePath       *path,
824                                   GtkTreeViewColumn *column,
825                                   CajaOpenWithDialog *dialog)
826 {
827     GtkTreeSelection *selection;
828 
829     /* update the entry with the info from the selection */
830     selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (dialog->details->program_list));
831     program_list_selection_changed (selection, dialog);
832 
833     gtk_dialog_response (GTK_DIALOG (&dialog->parent), RESPONSE_OPEN);
834 }
835 
836 static void
expander_toggled(GtkWidget * expander,CajaOpenWithDialog * dialog)837 expander_toggled (GtkWidget *expander, CajaOpenWithDialog *dialog)
838 {
839     if (gtk_expander_get_expanded (GTK_EXPANDER (expander)) == TRUE)
840     {
841         gtk_widget_grab_focus (dialog->details->entry);
842         gtk_window_resize (GTK_WINDOW (dialog), 400, 1);
843     }
844     else
845     {
846         GtkTreeSelection *selection;
847 
848         gtk_widget_grab_focus (dialog->details->program_list);
849         selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (dialog->details->program_list));
850         program_list_selection_changed (selection, dialog);
851     }
852 }
853 
854 static void
caja_open_with_dialog_init(CajaOpenWithDialog * dialog)855 caja_open_with_dialog_init (CajaOpenWithDialog *dialog)
856 {
857     GtkWidget *hbox;
858     GtkWidget *vbox;
859     GtkWidget *vbox2;
860     GtkWidget *label;
861     GtkWidget *scrolled_window;
862     GtkWidget *expander;
863     GtkTreeSelection *selection;
864 
865     dialog->details = g_new0 (CajaOpenWithDialogDetails, 1);
866 
867     gtk_window_set_title (GTK_WINDOW (dialog), _("Open With"));
868     gtk_container_set_border_width (GTK_CONTAINER (dialog), 5);
869     gtk_window_set_resizable (GTK_WINDOW (dialog), FALSE);
870     gtk_window_set_destroy_with_parent (GTK_WINDOW (dialog), TRUE);
871 
872     gtk_box_set_spacing (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog))), 2);
873 
874     vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 12);
875     gtk_container_set_border_width (GTK_CONTAINER (vbox), 5);
876 
877     vbox2 = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6);
878     gtk_box_pack_start (GTK_BOX (vbox), vbox2, TRUE, TRUE, 0);
879 
880     dialog->details->label = gtk_label_new ("");
881     gtk_label_set_xalign (GTK_LABEL (dialog->details->label), 0.0);
882     gtk_label_set_line_wrap (GTK_LABEL (dialog->details->label), TRUE);
883     gtk_box_pack_start (GTK_BOX (vbox2), dialog->details->label,
884                         FALSE, FALSE, 0);
885     gtk_widget_show (dialog->details->label);
886 
887 
888     scrolled_window = gtk_scrolled_window_new (NULL, NULL);
889     gtk_widget_set_size_request (scrolled_window, 400, 300);
890 
891     gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scrolled_window),
892                                          GTK_SHADOW_IN);
893     gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_window),
894                                     GTK_POLICY_AUTOMATIC,
895                                     GTK_POLICY_AUTOMATIC);
896     gtk_scrolled_window_set_overlay_scrolling (GTK_SCROLLED_WINDOW (scrolled_window), FALSE);
897 
898     dialog->details->program_list = gtk_tree_view_new ();
899     gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (dialog->details->program_list),
900                                        FALSE);
901     gtk_container_add (GTK_CONTAINER (scrolled_window), dialog->details->program_list);
902 
903     gtk_box_pack_start (GTK_BOX (vbox2), scrolled_window, TRUE, TRUE, 0);
904 
905     dialog->details->desc_label = gtk_label_new (_("Select an application to view its description."));
906     gtk_label_set_xalign (GTK_LABEL (dialog->details->desc_label), 0.0);
907     gtk_label_set_justify (GTK_LABEL (dialog->details->desc_label), GTK_JUSTIFY_LEFT);
908     gtk_label_set_max_width_chars (GTK_LABEL (dialog->details->desc_label), 54);
909     gtk_label_set_line_wrap (GTK_LABEL (dialog->details->desc_label), TRUE);
910     gtk_label_set_single_line_mode (GTK_LABEL (dialog->details->desc_label), FALSE);
911     gtk_box_pack_start (GTK_BOX (vbox2), dialog->details->desc_label, FALSE, FALSE, 0);
912 
913     selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (dialog->details->program_list));
914     gtk_tree_selection_set_mode (selection, GTK_SELECTION_SINGLE);
915     g_signal_connect (selection, "changed",
916                       G_CALLBACK (program_list_selection_changed),
917                       dialog);
918     g_signal_connect (dialog->details->program_list, "row-activated",
919                       G_CALLBACK (program_list_selection_activated),
920                       dialog);
921 
922     dialog->details->add_items_idle_id = g_idle_add_full (G_PRIORITY_DEFAULT_IDLE,
923                                          (GSourceFunc) caja_open_with_dialog_add_items_idle,
924                                          dialog, NULL);
925 
926 
927     gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog))), vbox, TRUE, TRUE, 0);
928     gtk_widget_show_all (vbox);
929 
930 
931     expander = gtk_expander_new_with_mnemonic (_("_Use a custom command"));
932     gtk_box_pack_start (GTK_BOX (vbox), expander, FALSE, FALSE, 0);
933     g_signal_connect_after (expander, "activate", G_CALLBACK (expander_toggled), dialog);
934 
935     gtk_widget_show (expander);
936 
937     hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6);
938     gtk_container_add (GTK_CONTAINER (expander), hbox);
939     gtk_widget_show (hbox);
940 
941     dialog->details->entry = gtk_entry_new ();
942     gtk_entry_set_activates_default (GTK_ENTRY (dialog->details->entry), TRUE);
943 
944     gtk_box_pack_start (GTK_BOX (hbox), dialog->details->entry,
945                         TRUE, TRUE, 0);
946     gtk_widget_show (dialog->details->entry);
947 
948     dialog->details->button = gtk_button_new_with_mnemonic (_("_Browse..."));
949     g_signal_connect (dialog->details->button, "clicked",
950                       G_CALLBACK (browse_clicked_cb), dialog);
951     gtk_box_pack_start (GTK_BOX (hbox), dialog->details->button, FALSE, FALSE, 0);
952     gtk_widget_show (dialog->details->button);
953 
954     /* Add remember this application checkbox - only visible in open mode */
955     dialog->details->checkbox = gtk_check_button_new ();
956     gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (dialog->details->checkbox), TRUE);
957     gtk_button_set_use_underline (GTK_BUTTON (dialog->details->checkbox), TRUE);
958     gtk_widget_show (GTK_WIDGET (dialog->details->checkbox));
959     gtk_box_pack_start (GTK_BOX (vbox), dialog->details->checkbox, FALSE, FALSE, 0);
960 
961     eel_dialog_add_button (GTK_DIALOG (dialog),
962                            _("_Remove"),
963                            "list-remove",
964                            RESPONSE_REMOVE);
965 
966     gtk_dialog_set_response_sensitive (GTK_DIALOG (dialog),
967                                        RESPONSE_REMOVE,
968                                        FALSE);
969 
970     eel_dialog_add_button (GTK_DIALOG (dialog),
971                            _("_Cancel"),
972                            "process-stop",
973                            GTK_RESPONSE_CANCEL);
974 
975 
976     /* Create a custom stock icon */
977     dialog->details->button = gtk_button_new ();
978 
979     /* Hook up the entry to the button */
980     gtk_widget_set_sensitive (dialog->details->button, FALSE);
981     g_signal_connect (G_OBJECT (dialog->details->entry), "changed",
982                       G_CALLBACK (entry_changed_cb), dialog);
983 
984     hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 2);
985     gtk_widget_show (hbox);
986 
987     label = gtk_label_new_with_mnemonic (_("_Open"));
988     gtk_label_set_mnemonic_widget (GTK_LABEL (label), GTK_WIDGET (dialog->details->button));
989     gtk_widget_show (label);
990     dialog->details->open_label = label;
991 
992     gtk_box_pack_start (GTK_BOX (hbox), label, TRUE, FALSE, 0);
993 
994     gtk_widget_show (dialog->details->button);
995     gtk_widget_set_can_default (dialog->details->button, TRUE);
996 
997     gtk_container_add (GTK_CONTAINER (dialog->details->button), hbox);
998 
999     gtk_dialog_add_action_widget (GTK_DIALOG (dialog),
1000                                   dialog->details->button, RESPONSE_OPEN);
1001 
1002 
1003     gtk_dialog_set_default_response (GTK_DIALOG (dialog),
1004                                      RESPONSE_OPEN);
1005 
1006     g_signal_connect (dialog, "response",
1007                       G_CALLBACK (response_cb),
1008                       dialog);
1009 }
1010 
1011 static char *
get_extension(const char * basename)1012 get_extension (const char *basename)
1013 {
1014     char *p;
1015 
1016     p = strrchr (basename, '.');
1017 
1018     if (p && *(p + 1) != '\0')
1019     {
1020         return g_strdup (p + 1);
1021     }
1022     else
1023     {
1024         return NULL;
1025     }
1026 }
1027 
1028 static void
set_uri_and_type(CajaOpenWithDialog * dialog,const char * uri,const char * mime_type,const char * passed_extension,gboolean add_mode)1029 set_uri_and_type (CajaOpenWithDialog *dialog,
1030                   const char *uri,
1031                   const char *mime_type,
1032                   const char *passed_extension,
1033                   gboolean add_mode)
1034 {
1035     char *label;
1036     char *emname;
1037     char *name, *extension;
1038     char *checkbox_text;
1039 
1040     name = NULL;
1041     extension = NULL;
1042 
1043     if (uri != NULL)
1044     {
1045         GFile *file;
1046 
1047         file = g_file_new_for_uri (uri);
1048         name = g_file_get_basename (file);
1049         g_object_unref (file);
1050     }
1051     if (passed_extension == NULL && name != NULL)
1052     {
1053         extension = get_extension (name);
1054     }
1055     else
1056     {
1057         extension = g_strdup (passed_extension);
1058     }
1059 
1060     if (extension != NULL &&
1061             g_content_type_is_unknown (mime_type))
1062     {
1063         dialog->details->extension = g_strdup (extension);
1064 
1065         if (name != NULL)
1066         {
1067             emname = g_strdup_printf ("<i>%s</i>", name);
1068             if (add_mode)
1069             {
1070                 /* Translators: first %s is a filename and second %s is a file extension */
1071                 label = g_strdup_printf (_("Open %s and other %s document with:"),
1072                                          emname, dialog->details->extension);
1073             }
1074             else
1075             {
1076                 /* Translators: the %s here is a file name */
1077                 label = g_strdup_printf (_("Open %s with:"), emname);
1078                 checkbox_text = g_strdup_printf (_("_Remember this application for %s documents"),
1079                                                  dialog->details->extension);
1080 
1081                 gtk_button_set_label (GTK_BUTTON (dialog->details->checkbox), checkbox_text);
1082                 g_free (checkbox_text);
1083             }
1084             g_free (emname);
1085         }
1086         else
1087         {
1088             /* Only in add mode - the %s here is a file extension */
1089             label = g_strdup_printf (_("Open all %s documents with:"),
1090                                      dialog->details->extension);
1091         }
1092         g_free (extension);
1093     }
1094     else
1095     {
1096         char *description;
1097 
1098         dialog->details->content_type = g_strdup (mime_type);
1099         description = g_content_type_get_description (mime_type);
1100 
1101         if (description == NULL)
1102         {
1103             description = g_strdup (_("Unknown"));
1104         }
1105 
1106         if (name != NULL)
1107         {
1108             emname = g_strdup_printf ("<i>%s</i>", name);
1109             if (add_mode)
1110             {
1111                 /* Translators: First %s is a filename, second is a description
1112                  * of the type, eg "plain text document" */
1113                 label = g_strdup_printf (_("Open %s and other \"%s\" files with:"),
1114                                          emname, description);
1115             }
1116             else
1117             {
1118                 /* Translators: %s is a filename */
1119                 label = g_strdup_printf (_("Open %s with:"), emname);
1120                 /* Translators: %s is a file type description */
1121                 checkbox_text = g_strdup_printf (_("_Remember this application for \"%s\" files"),
1122                                                  description);
1123 
1124                 gtk_button_set_label (GTK_BUTTON (dialog->details->checkbox), checkbox_text);
1125                 g_free (checkbox_text);
1126             }
1127             g_free (emname);
1128         }
1129         else
1130         {
1131             /* Only in add mode */
1132             label = g_strdup_printf (_("Open all \"%s\" files with:"), description);
1133         }
1134 
1135         g_free (description);
1136     }
1137 
1138     dialog->details->add_mode = add_mode;
1139     if (add_mode)
1140     {
1141         gtk_widget_hide (dialog->details->checkbox);
1142 
1143         gtk_label_set_text_with_mnemonic (GTK_LABEL (dialog->details->open_label),
1144                                           _("_Add"));
1145         gtk_window_set_title (GTK_WINDOW (dialog), _("Add Application"));
1146     }
1147 
1148     gtk_label_set_markup (GTK_LABEL (dialog->details->label), label);
1149 
1150     g_free (label);
1151     g_free (name);
1152 }
1153 
1154 
1155 static GtkWidget *
real_caja_open_with_dialog_new(const char * uri,const char * mime_type,const char * extension,gboolean add_mode)1156 real_caja_open_with_dialog_new (const char *uri,
1157                                 const char *mime_type,
1158                                 const char *extension,
1159                                 gboolean add_mode)
1160 {
1161     GtkWidget *dialog;
1162 
1163     dialog = gtk_widget_new (CAJA_TYPE_OPEN_WITH_DIALOG, NULL);
1164 
1165     set_uri_and_type (CAJA_OPEN_WITH_DIALOG (dialog), uri, mime_type, extension, add_mode);
1166 
1167     return dialog;
1168 }
1169 
1170 GtkWidget *
caja_open_with_dialog_new(const char * uri,const char * mime_type,const char * extension)1171 caja_open_with_dialog_new (const char *uri,
1172                            const char *mime_type,
1173                            const char *extension)
1174 {
1175     return real_caja_open_with_dialog_new (uri, mime_type, extension, FALSE);
1176 }
1177 
1178 GtkWidget *
caja_add_application_dialog_new(const char * uri,const char * mime_type)1179 caja_add_application_dialog_new (const char *uri,
1180                                  const char *mime_type)
1181 {
1182     CajaOpenWithDialog *dialog;
1183 
1184     dialog = CAJA_OPEN_WITH_DIALOG (real_caja_open_with_dialog_new (uri, mime_type, NULL, TRUE));
1185 
1186     return GTK_WIDGET (dialog);
1187 }
1188 
1189 GtkWidget *
caja_add_application_dialog_new_for_multiple_files(const char * extension,const char * mime_type)1190 caja_add_application_dialog_new_for_multiple_files (const char *extension,
1191         const char *mime_type)
1192 {
1193     CajaOpenWithDialog *dialog;
1194 
1195     dialog = CAJA_OPEN_WITH_DIALOG (real_caja_open_with_dialog_new (NULL, mime_type, extension, TRUE));
1196 
1197     return GTK_WIDGET (dialog);
1198 }
1199 
1200