Git Repositories

2005-09-20 Benedikt Meurer <benny@xfce.org>
authorBenedikt Meurer <benny@xfce.org>
Thu, 22 Sep 2005 16:48:49 +0000 (16:48 +0000)
committerBenedikt Meurer <benny@xfce.org>
Thu, 22 Sep 2005 16:48:49 +0000 (16:48 +0000)
* thunar-vfs/thunar-vfs-mime-application.{c,h},
  thunar-vfs/thunar-vfs.symbols: Load the supported mime types for the
  applications.
* thunar-vfs/thunar-vfs-mime-application.{c,h},
  thunar-vfs/thunar-vfs.symbols: Add a new constructor
  thunar_vfs_mime_application_new_from_file() for the special case
  where a particular file should be loaded by path.
* thunar-vfs/thunar-vfs-mime-application.c
  (thunar_vfs_mime_application_equal): Properly initialize the
  application variables.
* thunar-vfs/thunar-vfs-mime-application.c
  (thunar_vfs_mime_application_lookup_icon_name): Permit applications
  to specify absolute paths for the icon name.
* thunar-vfs/thunar-vfs-mime-database.{c,h},
  thunar-vfs/thunar-vfs.symbols: Extend the mime database by a new
  method thunar_vfs_mime_database_set_default_application(), which is
  used to set the default application for a given mime type.
* thunar-vfs/thunar-vfs-sysdep.c(_thunar_vfs_sysdep_parse_exec): Fix
  typo to properly execute applications that require a terminal.
* thunar-vfs/thunar-vfs-mime-database.c
  (thunar_vfs_mime_database_get_applications): Always prepend the
  default applications for the given mime info to the list returned
  from this method.
* thunar/thunar-favourites-model.c(thunar_favourites_model_save): Use
  g_mkstemp() to create the temporary file.
* thunar-vfs/thunar-vfs-mime-application.{c,h},
  thunar-vfs/thunar-vfs.symbols: Add public flags to the mime
  applications.
* thunar-vfs/thunar-vfs-mime-application.c
  (thunar_vfs_mime_application_new_from_file): Strip off known suffixes
  for image files if a themed icon is specified. This way we can
  work-around quite a few broken .desktop files.
* thunar-vfs/thunar-vfs-mime-database.{c,h},
  thunar-vfs/thunar-vfs.symbols: Support the addition of custom
  applications using thunar_vfs_mime_database_add_application() in
  a way compatible to what Nautilus does (which is actually quite a
  mess).
* thunar/thunar-chooser-dialog.{c,h}, thunar/thunar-chooser-model.{c,h},
  thunar/Makefile.am: Import the "Open With" dialog based on the two
  classes ThunarChooserDialog and ThunarChooserModel, which in turn are
  based on the new functionality provided by Thunar-VFS, and thereby
  permit the user to associate applications with files (actually mime
  types) and add new applications.
* thunar/thunar-launcher.c: Connect the "Open With Other" action to
  the new ThunarChooserDialog.
* thunar/thunar-favourites-model.c: Document the drag source functions.
* README: Add a list of dependencies for Thunar.
* TODO: Drop completed items.

(Old svn revision: 17760)

16 files changed:
ChangeLog
README
TODO
thunar-vfs/thunar-vfs-mime-application.c
thunar-vfs/thunar-vfs-mime-application.h
thunar-vfs/thunar-vfs-mime-database.c
thunar-vfs/thunar-vfs-mime-database.h
thunar-vfs/thunar-vfs-sysdep.c
thunar-vfs/thunar-vfs.symbols
thunar/Makefile.am
thunar/thunar-chooser-dialog.c [new file with mode: 0644]
thunar/thunar-chooser-dialog.h [new file with mode: 0644]
thunar/thunar-chooser-model.c [new file with mode: 0644]
thunar/thunar-chooser-model.h [new file with mode: 0644]
thunar/thunar-favourites-model.c
thunar/thunar-launcher.c

index 0daefe7..0f73cd5 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,54 @@
+2005-09-20     Benedikt Meurer <benny@xfce.org>
+
+       * thunar-vfs/thunar-vfs-mime-application.{c,h},
+         thunar-vfs/thunar-vfs.symbols: Load the supported mime types for the
+         applications.
+       * thunar-vfs/thunar-vfs-mime-application.{c,h},
+         thunar-vfs/thunar-vfs.symbols: Add a new constructor
+         thunar_vfs_mime_application_new_from_file() for the special case
+         where a particular file should be loaded by path.
+       * thunar-vfs/thunar-vfs-mime-application.c
+         (thunar_vfs_mime_application_equal): Properly initialize the
+         application variables.
+       * thunar-vfs/thunar-vfs-mime-application.c
+         (thunar_vfs_mime_application_lookup_icon_name): Permit applications
+         to specify absolute paths for the icon name.
+       * thunar-vfs/thunar-vfs-mime-database.{c,h},
+         thunar-vfs/thunar-vfs.symbols: Extend the mime database by a new
+         method thunar_vfs_mime_database_set_default_application(), which is
+         used to set the default application for a given mime type.
+       * thunar-vfs/thunar-vfs-sysdep.c(_thunar_vfs_sysdep_parse_exec): Fix
+         typo to properly execute applications that require a terminal.
+       * thunar-vfs/thunar-vfs-mime-database.c
+         (thunar_vfs_mime_database_get_applications): Always prepend the
+         default applications for the given mime info to the list returned
+         from this method.
+       * thunar/thunar-favourites-model.c(thunar_favourites_model_save): Use
+         g_mkstemp() to create the temporary file.
+       * thunar-vfs/thunar-vfs-mime-application.{c,h},
+         thunar-vfs/thunar-vfs.symbols: Add public flags to the mime
+         applications.
+       * thunar-vfs/thunar-vfs-mime-application.c
+         (thunar_vfs_mime_application_new_from_file): Strip off known suffixes
+         for image files if a themed icon is specified. This way we can
+         work-around quite a few broken .desktop files.
+       * thunar-vfs/thunar-vfs-mime-database.{c,h},
+         thunar-vfs/thunar-vfs.symbols: Support the addition of custom
+         applications using thunar_vfs_mime_database_add_application() in
+         a way compatible to what Nautilus does (which is actually quite a
+         mess).
+       * thunar/thunar-chooser-dialog.{c,h}, thunar/thunar-chooser-model.{c,h},
+         thunar/Makefile.am: Import the "Open With" dialog based on the two
+         classes ThunarChooserDialog and ThunarChooserModel, which in turn are
+         based on the new functionality provided by Thunar-VFS, and thereby
+         permit the user to associate applications with files (actually mime
+         types) and add new applications.
+       * thunar/thunar-launcher.c: Connect the "Open With Other" action to
+         the new ThunarChooserDialog.
+       * thunar/thunar-favourites-model.c: Document the drag source functions.
+       * README: Add a list of dependencies for Thunar.
+       * TODO: Drop completed items.
+
 2005-09-17     Benedikt Meurer <benny@xfce.org>
 
        * thunar-vfs/thunar-vfs-mime-database.c: Include exo/exo.h.
diff --git a/README b/README
index 9b92738..9b1f446 100644 (file)
--- a/README
+++ b/README
@@ -5,6 +5,25 @@ Thunar is a modern file manager for the Unix/Linux desktop, aiming to be
 easy-to-use and fast.
 
 
+Required packages
+=================
+
+Thunar depends on the following packages:
+
+ - perl 5.6 or above
+ - GTK+ 2.6.0 or above
+ - libexo 0.3.1 or above
+ - intltool 0.30 or above
+ - libpng12 1.2.0 or above
+ - shared-mime-info 0.15 or above
+ - desktop-file-utils 0.10 or above
+
+Thunar can optionally use the following packages:
+
+ - cairo 0.5 or above
+ - gamin 0.1.0 or above
+
+
 Installation
 ============
 
diff --git a/TODO b/TODO
index ae17009..2e0825c 100644 (file)
--- a/TODO
+++ b/TODO
@@ -29,10 +29,6 @@ Important for Thunar 1.0
    dropping files to the trash bin in the favourites pane (not for 1.0 I'd
    say).
 
- - ThunarFavouritesModel should watch the .gtk-bookmarks file for changes and
-   reload it on-demand (take care of not reloading if the change was caused by
-   thunar_favourites_model_save!).
-
  - The layouting code for ThunarLocationButtons is still buggy. Problem shows
    with paths that include a very long directory; you cannot scroll to the
    last path component then.
index 2a4fc94..706827d 100644 (file)
@@ -58,18 +58,14 @@ static gboolean thunar_vfs_mime_application_get_argv (const ThunarVfsMimeApplica
 
 struct _ThunarVfsMimeApplication
 {
-  gint   ref_count;
-
-  gchar *binary_name;
-  gchar *desktop_id;
-  gchar *exec;
-  gchar *icon;
-  gchar *name;
-
-  guint  requires_terminal : 1;
-  guint  supports_startup_notify : 1;
-  guint  supports_multi : 1; /* %F or %U */
-  guint  supports_uris : 1;  /* %u or %U */
+  gint                          ref_count;
+  gchar                        *binary_name;
+  gchar                        *desktop_id;
+  gchar                        *exec;
+  gchar                        *icon;
+  gchar                        *name;
+  gchar                       **mime_types;
+  ThunarVfsMimeApplicationFlags flags;
 };
 
 
@@ -98,7 +94,9 @@ thunar_vfs_mime_application_get_argv (const ThunarVfsMimeApplication *applicatio
                                       gchar                        ***argv,
                                       GError                        **error)
 {
-  return _thunar_vfs_sysdep_parse_exec (application->exec, uris, application->icon, application->name, NULL, application->requires_terminal, argc, argv, error);
+  return _thunar_vfs_sysdep_parse_exec (application->exec, uris, application->icon, application->name, NULL,
+                                        (application->flags & THUNAR_VFS_MIME_APPLICATION_REQUIRES_TERMINAL) != 0,
+                                        argc, argv, error);
 }
 
 
@@ -119,15 +117,9 @@ ThunarVfsMimeApplication*
 thunar_vfs_mime_application_new_from_desktop_id (const gchar *desktop_id)
 {
   ThunarVfsMimeApplication *application = NULL;
-  const gchar              *exec;
-  const gchar              *icon;
-  const gchar              *name;
-  XfceRc                   *rc;
-  gchar                   **argv;
   gchar                    *spec;
   gchar                    *path = NULL;
   gchar                    *s;
-  gint                      argc;
 
   g_return_val_if_fail (desktop_id != NULL, NULL);
 
@@ -150,9 +142,53 @@ thunar_vfs_mime_application_new_from_desktop_id (const gchar *desktop_id)
   if (G_UNLIKELY (path == NULL))
     return NULL;
 
+  /* try to load the application from the path */
+  application = thunar_vfs_mime_application_new_from_file (path, desktop_id);
+
+  /* cleanup */
+  g_free (path);
+
+  return application;
+}
+
+
+
+/**
+ * thunar_vfs_mime_application_new_from_file:
+ * @path       : the absolute path to the desktop file.
+ * @desktop_id : the desktop-id of the file.
+ *
+ * Generates a new #ThunarVfsMimeApplication for the application
+ * described by @path and @desktop_id.
+ *
+ * The caller is responsible to free the returned instance using
+ * thunar_vfs_mime_application_unref().
+ *
+ * You should really seldomly use this function and always
+ * prefer thunar_vfs_mime_application_new_from_desktop_id().
+ *
+ * Return value: the #ThunarVfsMimeApplication for @desktop_id
+ *               or %NULL.
+ **/
+ThunarVfsMimeApplication*
+thunar_vfs_mime_application_new_from_file (const gchar *path,
+                                           const gchar *desktop_id)
+{
+  ThunarVfsMimeApplication *application = NULL;
+  const gchar              *exec;
+  const gchar              *icon;
+  const gchar              *name;
+  XfceRc                   *rc;
+  gchar                   **argv;
+  gchar                   **ms;
+  gchar                   **mt;
+  gint                      argc;
+
+  g_return_val_if_fail (g_path_is_absolute (path), NULL);
+  g_return_val_if_fail (desktop_id != NULL && *desktop_id != '\0', NULL);
+
   /* try to open the .desktop file */
   rc = xfce_rc_simple_open (path, TRUE);
-  g_free (path);
   if (G_UNLIKELY (rc == NULL))
     return NULL;
 
@@ -173,6 +209,37 @@ thunar_vfs_mime_application_new_from_desktop_id (const gchar *desktop_id)
       application->name = g_strdup (name);
       application->icon = g_strdup (icon);
 
+      /* strip off known suffixes for image files if a themed icon is specified */
+      if (application->icon != NULL && !g_path_is_absolute (application->icon) && g_str_has_suffix (application->icon, ".png"))
+        application->icon[strlen (application->icon) - 4] = '\0';
+
+      /* determine the list of mime types supported by the application */
+      application->mime_types = xfce_rc_read_list_entry (rc, "MimeType", ";");
+      if (G_LIKELY (application->mime_types != NULL))
+        {
+          /* strip off the useless mime types */
+          for (ms = mt = application->mime_types; *ms != NULL; ++ms)
+            {
+              /* ignore empty entries, GNOME pseudo mime types and KDE junk */
+              if (**ms == '\0' || g_str_equal (*ms, "x-directory/gnome-default-handler") || g_str_has_prefix (*ms, "print/"))
+                g_free (*ms);
+              else
+                *mt++ = *ms;
+            }
+
+          /* verify that we have atleast one mime type left */
+          if (G_UNLIKELY (mt == application->mime_types))
+            {
+              g_free (application->mime_types);
+              application->mime_types = NULL;
+            }
+          else
+            {
+              /* be sure to zero-terminate the new list */
+              *mt = NULL;
+            }
+        }
+
       /* we assume %f if the application hasn't set anything else,
        * as that's also what KDE and Gnome do in this case.
        */
@@ -181,11 +248,20 @@ thunar_vfs_mime_application_new_from_desktop_id (const gchar *desktop_id)
       else
         application->exec = g_strdup (exec);
 
-      application->requires_terminal = xfce_rc_read_bool_entry (rc, "Terminal", FALSE);
-      application->supports_startup_notify = xfce_rc_read_bool_entry (rc, "StartupNotify", FALSE)
-                                          || xfce_rc_read_bool_entry (rc, "X-KDE-StartupNotify", FALSE);
-      application->supports_multi = (strstr (application->exec, "%F") != NULL) || (strstr (application->exec, "%U") != NULL);
-      application->supports_uris = (strstr (application->exec, "%u") != NULL) || (strstr (application->exec, "%U") != NULL);
+      if (G_UNLIKELY (xfce_rc_read_bool_entry (rc, "Terminal", FALSE)))
+        application->flags |= THUNAR_VFS_MIME_APPLICATION_REQUIRES_TERMINAL;
+
+      if (xfce_rc_read_bool_entry (rc, "Hidden", FALSE) || xfce_rc_read_bool_entry (rc, "NoDisplay", FALSE))
+        application->flags |= THUNAR_VFS_MIME_APPLICATION_HIDDEN;
+
+      if (xfce_rc_read_bool_entry (rc, "StartupNotify", FALSE) || xfce_rc_read_bool_entry (rc, "X-KDE-StartupNotify", FALSE))
+        application->flags |= THUNAR_VFS_MIME_APPLICATION_SUPPORTS_STARTUP_NOTIFY;
+
+      if ((strstr (application->exec, "%F") != NULL) || (strstr (application->exec, "%U") != NULL))
+        application->flags |= THUNAR_VFS_MIME_APPLICATION_SUPPORTS_MULTI;
+
+      if ((strstr (application->exec, "%u") != NULL) || (strstr (application->exec, "%U") != NULL))
+        application->flags |= THUNAR_VFS_MIME_APPLICATION_SUPPORTS_URIS;
 
       g_strfreev (argv);
     }
@@ -230,6 +306,7 @@ thunar_vfs_mime_application_unref (ThunarVfsMimeApplication *application)
   if (_thunar_vfs_sysdep_dec (&application->ref_count))
     {
       /* free resources */
+      g_strfreev (application->mime_types);
       g_free (application->binary_name);
       g_free (application->desktop_id);
       g_free (application->exec);
@@ -242,6 +319,22 @@ thunar_vfs_mime_application_unref (ThunarVfsMimeApplication *application)
 
 
 /**
+ * thunar_vfs_mime_application_get_command:
+ * @application : a #ThunarVfsMimeApplication.
+ *
+ * Returns the command line to run @application.
+ *
+ * Return value: the command to run @application.
+ **/
+const gchar*
+thunar_vfs_mime_application_get_command (const ThunarVfsMimeApplication *application)
+{
+  return application->exec;
+}
+
+
+
+/**
  * thunar_vfs_mime_application_get_desktop_id:
  * @application : a #ThunarVfsMimeApplication.
  *
@@ -258,6 +351,22 @@ thunar_vfs_mime_application_get_desktop_id (const ThunarVfsMimeApplication *appl
 
 
 /**
+ * thunar_vfs_mime_application_get_flags:
+ * @application : a #ThunarVfsMimeApplication.
+ *
+ * Returns the flags for @application.
+ *
+ * Return value: the flags for @application.
+ **/
+ThunarVfsMimeApplicationFlags
+thunar_vfs_mime_application_get_flags (const ThunarVfsMimeApplication *application)
+{
+  return application->flags;
+}
+
+
+
+/**
  * thunar_vfs_mime_application_get_name:
  * @application : a #ThunarVfsMimeApplication.
  *
@@ -274,6 +383,27 @@ thunar_vfs_mime_application_get_name (const ThunarVfsMimeApplication *applicatio
 
 
 /**
+ * thunar_vfs_mime_application_get_mime_types:
+ * @application : a #ThunarVfsMimeApplication.
+ *
+ * Returns the list of MIME-types supported by @application
+ * or %NULL if the @application doesn't support any MIME-types
+ * at all.
+ *
+ * The returned %NULL-terminated string array is owned by
+ * @application and must not be free by the caller.
+ *
+ * Return value: the list of supported MIME-types for @application.
+ **/
+const gchar* const*
+thunar_vfs_mime_application_get_mime_types (const ThunarVfsMimeApplication *application)
+{
+  return (gconstpointer) application->mime_types;
+}
+
+
+
+/**
  * thunar_vfs_mime_application_exec:
  * @application : a #ThunarVfsMimeApplication.
  * @screen      : a #GdkScreen or %NULL to use the default screen.
@@ -334,7 +464,7 @@ thunar_vfs_mime_application_exec_with_env (const ThunarVfsMimeApplication *appli
     screen = gdk_screen_get_default ();
 
   /* verify that either the application supports URIs or only local files are given */
-  if (G_LIKELY (!application->supports_uris))
+  if (G_LIKELY ((application->flags & THUNAR_VFS_MIME_APPLICATION_SUPPORTS_URIS) == 0))
     for (lp = uris; lp != NULL; lp = lp->next)
       if (thunar_vfs_uri_get_scheme (lp->data) != THUNAR_VFS_URI_SCHEME_FILE)
         {
@@ -344,7 +474,7 @@ thunar_vfs_mime_application_exec_with_env (const ThunarVfsMimeApplication *appli
         }
 
   /* check whether the application can open multiple documents at once */
-  if (G_LIKELY (!application->supports_multi))
+  if (G_LIKELY ((application->flags & THUNAR_VFS_MIME_APPLICATION_SUPPORTS_MULTI) == 0))
     {
       for (lp = uris; lp != NULL; lp = lp->next)
         {
@@ -410,7 +540,7 @@ const gchar*
 thunar_vfs_mime_application_lookup_icon_name (const ThunarVfsMimeApplication *application,
                                               GtkIconTheme                   *icon_theme)
 {
-  if (application->icon != NULL && gtk_icon_theme_has_icon (icon_theme, application->icon))
+  if (application->icon != NULL && (g_path_is_absolute (application->icon) || gtk_icon_theme_has_icon (icon_theme, application->icon)))
     return application->icon;
   else if (application->binary_name != NULL && gtk_icon_theme_has_icon (icon_theme, application->binary_name))
     return application->binary_name;
@@ -452,8 +582,8 @@ gboolean
 thunar_vfs_mime_application_equal (gconstpointer a,
                                    gconstpointer b)
 {
-  const ThunarVfsMimeApplication *a_application;
-  const ThunarVfsMimeApplication *b_application;
+  const ThunarVfsMimeApplication *a_application = a;
+  const ThunarVfsMimeApplication *b_application = b;
   return (strcmp (a_application->desktop_id, b_application->desktop_id) == 0);
 }
 
index 955c770..c0561cf 100644 (file)
@@ -35,36 +35,61 @@ typedef enum
 GQuark thunar_vfs_mime_application_error_quark (void) G_GNUC_CONST;
 
 
-typedef struct _ThunarVfsMimeApplication ThunarVfsMimeApplication;
-
-#define THUNAR_VFS_TYPE_MIME_APPLICATION (thunar_vfs_mime_application_get_type ())
-
-GType                     thunar_vfs_mime_application_get_type            (void) G_GNUC_CONST;
-
-ThunarVfsMimeApplication *thunar_vfs_mime_application_new_from_desktop_id (const gchar                    *desktop_id) G_GNUC_MALLOC;
-
-ThunarVfsMimeApplication *thunar_vfs_mime_application_ref                 (ThunarVfsMimeApplication       *application);
-void                      thunar_vfs_mime_application_unref               (ThunarVfsMimeApplication       *application);
+/**
+ * ThunarVfsMimeApplicationFlags:
+ * @THUNAR_VFS_MIME_APPLICATION_HIDDEN                  : the application should not be displayed in the menu system.
+ * @THUNAR_VFS_MIME_APPLICATION_REQUIRES_TERMINAL       : the application must be run in a terminal.
+ * @THUNAR_VFS_MIME_APPLICATION_SUPPORTS_STARTUP_NOTIFY : the application supports startup notification.
+ * @THUNAR_VFS_MIME_APPLICATION_SUPPORTS_MULTI          : the application supports opening multiple documents at once (%F or %U).
+ * @THUNAR_VFS_MIME_APPLICATION_SUPPORTS_URIS           : the application supports opening documents by their URIs (%u or %U).
+ *
+ * Various flags associated with a #ThunarVfsMimeApplication.
+ **/
+typedef enum /*< flags >*/
+{
+  THUNAR_VFS_MIME_APPLICATION_HIDDEN                  = (1 << 0L),
+  THUNAR_VFS_MIME_APPLICATION_REQUIRES_TERMINAL       = (1 << 1L),
+  THUNAR_VFS_MIME_APPLICATION_SUPPORTS_STARTUP_NOTIFY = (1 << 2L),
+  THUNAR_VFS_MIME_APPLICATION_SUPPORTS_MULTI          = (1 << 3L),
+  THUNAR_VFS_MIME_APPLICATION_SUPPORTS_URIS           = (1 << 4L),
+} ThunarVfsMimeApplicationFlags;
 
-const gchar              *thunar_vfs_mime_application_get_desktop_id      (const ThunarVfsMimeApplication *application);
-const gchar              *thunar_vfs_mime_application_get_name            (const ThunarVfsMimeApplication *application);
 
-gboolean                  thunar_vfs_mime_application_exec                (const ThunarVfsMimeApplication *application,
-                                                                           GdkScreen                      *screen,
-                                                                           GList                          *uris,
-                                                                           GError                        **error);
-gboolean                  thunar_vfs_mime_application_exec_with_env       (const ThunarVfsMimeApplication *application,
-                                                                           GdkScreen                      *screen,
-                                                                           GList                          *uris,
-                                                                           gchar                         **envp,
-                                                                           GError                        **error);
+typedef struct _ThunarVfsMimeApplication ThunarVfsMimeApplication;
 
-const gchar              *thunar_vfs_mime_application_lookup_icon_name    (const ThunarVfsMimeApplication *application,
-                                                                           GtkIconTheme                   *icon_theme);
+#define THUNAR_VFS_TYPE_MIME_APPLICATION (thunar_vfs_mime_application_get_type ())
 
-guint                     thunar_vfs_mime_application_hash                (gconstpointer                   application);
-gboolean                  thunar_vfs_mime_application_equal               (gconstpointer                   a,
-                                                                           gconstpointer                   b);
+GType                         thunar_vfs_mime_application_get_type            (void) G_GNUC_CONST;
+
+ThunarVfsMimeApplication     *thunar_vfs_mime_application_new_from_desktop_id (const gchar                    *desktop_id) G_GNUC_MALLOC;
+ThunarVfsMimeApplication     *thunar_vfs_mime_application_new_from_file       (const gchar                    *path,
+                                                                               const gchar                    *desktop_id) G_GNUC_MALLOC;
+
+ThunarVfsMimeApplication     *thunar_vfs_mime_application_ref                 (ThunarVfsMimeApplication       *application);
+void                          thunar_vfs_mime_application_unref               (ThunarVfsMimeApplication       *application);
+
+const gchar                  *thunar_vfs_mime_application_get_command         (const ThunarVfsMimeApplication *application);
+const gchar                  *thunar_vfs_mime_application_get_desktop_id      (const ThunarVfsMimeApplication *application);
+ThunarVfsMimeApplicationFlags thunar_vfs_mime_application_get_flags           (const ThunarVfsMimeApplication *application);
+const gchar                  *thunar_vfs_mime_application_get_name            (const ThunarVfsMimeApplication *application);
+const gchar * const          *thunar_vfs_mime_application_get_mime_types      (const ThunarVfsMimeApplication *application);
+
+gboolean                      thunar_vfs_mime_application_exec                (const ThunarVfsMimeApplication *application,
+                                                                               GdkScreen                      *screen,
+                                                                               GList                          *uris,
+                                                                               GError                        **error);
+gboolean                      thunar_vfs_mime_application_exec_with_env       (const ThunarVfsMimeApplication *application,
+                                                                               GdkScreen                      *screen,
+                                                                               GList                          *uris,
+                                                                               gchar                         **envp,
+                                                                               GError                        **error);
+
+const gchar                  *thunar_vfs_mime_application_lookup_icon_name    (const ThunarVfsMimeApplication *application,
+                                                                               GtkIconTheme                   *icon_theme);
+
+guint                         thunar_vfs_mime_application_hash                (gconstpointer                   application);
+gboolean                      thunar_vfs_mime_application_equal               (gconstpointer                   a,
+                                                                               gconstpointer                   b);
 
 G_END_DECLS;
 
index 4a52d56..4fe1cff 100644 (file)
 #include <sys/xattr.h>
 #endif
 
+#ifdef HAVE_ERRNO_H
+#include <errno.h>
+#endif
 #ifdef HAVE_FCNTL_H
 #include <fcntl.h>
 #endif
 #ifdef HAVE_MEMORY_H
 #include <memory.h>
 #endif
+#include <stdio.h>
 #ifdef HAVE_STRING_H
 #include <string.h>
 #endif
@@ -62,7 +66,9 @@
 #if GLIB_CHECK_VERSION(2,6,0)
 #include <glib/gstdio.h>
 #else
-#define g_open(path, flags, mode) (open ((path), (flags), (mode)))
+#define g_open(path, flagsremove, mode) (open ((path), (flags), (mode)))
+#define g_rename(oldfilename, newfilename) (rename ((oldfilename), (newfilename)))
+#define g_unlink(path) (unlink ((path)))
 #endif
 
 
@@ -1155,17 +1161,57 @@ thunar_vfs_mime_database_get_applications (ThunarVfsMimeDatabase *database,
 {
   ThunarVfsMimeDesktopStore *store;
   ThunarVfsMimeApplication  *application;
+  const gchar               *command;
   const gchar              **id;
   GList                     *applications = NULL;
   GList                     *infos;
   GList                     *lp;
+  GList                     *p;
   guint                      n;
 
   g_return_val_if_fail (THUNAR_VFS_IS_MIME_DATABASE (database), NULL);
 
   g_mutex_lock (database->lock);
 
+  /* determine the mime infos for info */
   infos = thunar_vfs_mime_database_get_infos_for_info_locked (database, info);
+
+  /* lookup the default applications for the infos */
+  for (lp = infos; lp != NULL; lp = lp->next)
+    {
+      for (n = database->n_stores, store = database->stores; n-- > 0; ++store)
+        {
+          /* lookup the application ids */
+          id = g_hash_table_lookup (store->defaults_list, lp->data);
+          if (G_LIKELY (id == NULL))
+            continue;
+
+          /* merge the applications */
+          for (; *id != NULL; ++id)
+            {
+              /* lookup the application for the id */
+              application = thunar_vfs_mime_database_get_application_locked (database, *id);
+              if (G_UNLIKELY (application == NULL))
+                continue;
+
+              /* check if we already have an application with an equal command
+               * (thanks again Nautilus for the .desktop file mess).
+               */
+              command = thunar_vfs_mime_application_get_command (application);
+              for (p = applications; p != NULL; p = p->next)
+                if (g_str_equal (thunar_vfs_mime_application_get_command (p->data), command))
+                  break;
+
+              /* merge the application */
+              if (G_LIKELY (p == NULL))
+                applications = g_list_append (applications, application);
+              else
+                thunar_vfs_mime_application_unref (application);
+            }
+        }
+    }
+
+  /* lookup the other applications */
   for (lp = infos; lp != NULL; lp = lp->next)
     {
       for (n = database->n_stores, store = database->stores; n-- > 0; ++store)
@@ -1183,8 +1229,16 @@ thunar_vfs_mime_database_get_applications (ThunarVfsMimeDatabase *database,
               if (G_UNLIKELY (application == NULL))
                 continue;
 
+              /* check if we already have an application with an equal command
+               * (thanks again Nautilus for the .desktop file mess).
+               */
+              command = thunar_vfs_mime_application_get_command (application);
+              for (p = applications; p != NULL; p = p->next)
+                if (g_str_equal (thunar_vfs_mime_application_get_command (p->data), command))
+                  break;
+
               /* merge the application */
-              if (g_list_find (applications, application) == NULL)
+              if (G_LIKELY (p == NULL))
                 applications = g_list_append (applications, application);
               else
                 thunar_vfs_mime_application_unref (application);
@@ -1274,5 +1328,238 @@ thunar_vfs_mime_database_get_default_application (ThunarVfsMimeDatabase *databas
 
 
 
+static void
+defaults_list_write (ThunarVfsMimeInfo *info,
+                     gchar            **ids,
+                     FILE              *fp)
+{
+  guint n;
+
+  fprintf (fp, "%s=%s", thunar_vfs_mime_info_get_name (info), ids[0]);
+  for (n = 1; ids[n] != NULL; ++n)
+    fprintf (fp, ";%s", ids[n]);
+  fprintf (fp, "\n");
+}
+
+
+
+/**
+ * thunar_vfs_mime_database_set_default_application:
+ * @database    : a #ThunarVfsMimeDatabase.
+ * @info        : a valid #ThunarVfsMimeInfo for @database.
+ * @application : a #ThunarVfsMimeApplication.
+ * @error       : return location for errors or %NULL.
+ *
+ * Sets @application to be the default #ThunarVfsMimeApplication to open files
+ * of type @info in @database.
+ *
+ * Return value: %TRUE if the operation was successfull, else %FALSE.
+ **/
+gboolean
+thunar_vfs_mime_database_set_default_application (ThunarVfsMimeDatabase    *database,
+                                                  ThunarVfsMimeInfo        *info,
+                                                  ThunarVfsMimeApplication *application,
+                                                  GError                  **error)
+{
+  ThunarVfsMimeDesktopStore *store;
+  gboolean                   succeed;
+  gchar                    **pids;
+  gchar                    **nids;
+  gchar                     *path;
+  guint                      n, m;
+  FILE                      *fp;
+  gint                       fd;
+
+  g_return_val_if_fail (THUNAR_VFS_IS_MIME_DATABASE (database), FALSE);
+  g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+  /* acquire the lock on the database */
+  g_mutex_lock (database->lock);
+
+  /* grab the user's desktop applications store */
+  store = database->stores;
+
+  /* verify that the applications/ directory exists */
+  path = g_path_get_dirname (thunar_vfs_uri_get_path (store->defaults_list_uri));
+  succeed = xfce_mkdirhier (path, 0700, error);
+  g_free (path);
+
+  /* associate the application with the info */
+  if (G_LIKELY (succeed))
+    {
+      /* generate the new application id list */
+      pids = g_hash_table_lookup (store->defaults_list, info);
+      if (G_UNLIKELY (pids == NULL))
+        {
+          /* generate a new list that contains only the application */
+          nids = g_new (gchar *, 2);
+          nids[0] = g_strdup (thunar_vfs_mime_application_get_desktop_id (application));
+          nids[1] = NULL;
+        }
+      else
+        {
+          /* count the number of previously associated application ids */
+          for (n = 0; pids[n] != NULL; ++n)
+            ;
+
+          /* allocate a new list and prepend the application */
+          nids = g_new (gchar *, n + 2);
+          nids[0] = g_strdup (thunar_vfs_mime_application_get_desktop_id (application));
+
+          /* append the previously associated application ids */
+          for (m = 0, n = 1; pids[m] != NULL; ++m)
+            if (strcmp (pids[m], nids[0]) != 0)
+              nids[n++] = g_strdup (pids[m]);
+
+          /* null-terminate the new application id list */
+          nids[n] = NULL;
+        }
+
+      /* activate the new application id list */
+      g_hash_table_replace (store->defaults_list, thunar_vfs_mime_info_ref (info), nids);
+
+      /* write the default applications list to a temporary file */
+      path = g_strdup_printf ("%s.XXXXXX", thunar_vfs_uri_get_path (store->defaults_list_uri));
+      fd = g_mkstemp (path);
+      if (G_LIKELY (fd >= 0))
+        {
+          /* wrap the descriptor in a file pointer */
+          fp = fdopen (fd, "w");
+
+          /* write the default application list content */
+          fprintf (fp, "[Default Applications]\n");
+          g_hash_table_foreach (store->defaults_list, (GHFunc) defaults_list_write, fp);
+          fclose (fp);
+
+          /* disable the monitor handle for the defaults.list, so we don't unneccessary reload it */
+          thunar_vfs_monitor_remove (database->monitor, store->defaults_list_handle);
+
+          /* try to atomically rename the file */
+          if (G_UNLIKELY (g_rename (path, thunar_vfs_uri_get_path (store->defaults_list_uri)) < 0))
+            {
+              /* tell the caller that we failed */
+              g_set_error (error, G_FILE_ERROR, g_file_error_from_errno (errno), g_strerror (errno));
+              succeed = FALSE;
+
+              /* be sure to remove the temporary file */
+              g_unlink (path);
+            }
+
+          /* re-enable the monitor handle for the defaults.list */
+          store->defaults_list_handle = thunar_vfs_monitor_add_file (database->monitor, store->defaults_list_uri,
+                                                                     thunar_vfs_mime_database_store_changed, database);
+        }
+      else
+        {
+          g_set_error (error, G_FILE_ERROR, g_file_error_from_errno (errno), g_strerror (errno));
+          succeed = FALSE;
+        }
+      g_free (path);
+    }
+
+  /* release the lock on the database */
+  g_mutex_unlock (database->lock);
+
+  return succeed;
+}
+
+
+
+/**
+ * thunar_vfs_mime_database_add_application:
+ * @database : a #ThunarVfsMimeDatabase.
+ * @info     : a #ThunarVfsMimeInfo.
+ * @name     : the name for the application.
+ * @exec     : the command for the application.
+ * @error    : return location for errors or %NULL.
+ *
+ * Adds a new #ThunarVfsMimeApplication to the @database, whose
+ * name is @name and command is @exec, and which can be used to
+ * open files of type @info.
+ *
+ * The caller is responsible to free the returned object
+ * using thunar_vfs_mime_application_unref() when no longer
+ * needed.
+ *
+ * Return value: the newly created #ThunarVfsMimeApplication
+ *               or %NULL on error.
+ **/
+ThunarVfsMimeApplication*
+thunar_vfs_mime_database_add_application (ThunarVfsMimeDatabase *database,
+                                          ThunarVfsMimeInfo     *info,
+                                          const gchar           *name,
+                                          const gchar           *exec,
+                                          GError               **error)
+{
+  ThunarVfsMimeApplication *application = NULL;
+  gboolean                  succeed;
+  gchar                    *desktop_id;
+  gchar                    *directory;
+  gchar                    *command;
+  gchar                    *file;
+  guint                     n;
+  FILE                     *fp;
+
+  g_return_val_if_fail (THUNAR_VFS_IS_MIME_DATABASE (database), NULL);
+  g_return_val_if_fail (info != NULL, NULL);
+  g_return_val_if_fail (g_utf8_validate (name, -1, NULL), NULL);
+  g_return_val_if_fail (exec != NULL, NULL);
+  g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+
+  /* determine a file name for the new applications .desktop file */
+  directory = xfce_resource_save_location (XFCE_RESOURCE_DATA, "applications/", TRUE);
+  file = g_strconcat (directory, G_DIR_SEPARATOR_S, name, "-usercreated.desktop", NULL);
+  for (n = 1; g_file_test (file, G_FILE_TEST_EXISTS); ++n)
+    {
+      g_free (file);
+      file = g_strdup_printf ("%s" G_DIR_SEPARATOR_S "%s-usercreated-%u.desktop", directory, name, n);
+    }
+
+  /* open the .desktop file for writing */
+  fp = fopen (file, "w");
+  if (G_UNLIKELY (fp == NULL))
+    {
+      g_set_error (error, G_FILE_ERROR, g_file_error_from_errno (errno), g_strerror (errno));
+    }
+  else
+    {
+      /* write the content and close the file */
+      fprintf (fp, "[Desktop Entry]\n");
+      fprintf (fp, "Encoding=UTF-8\n");
+      fprintf (fp, "Type=Application\n");
+      fprintf (fp, "NoDisplay=true\n");
+      fprintf (fp, "Name=%s\n", name);
+      fprintf (fp, "Exec=%s\n", exec);
+      fprintf (fp, "MimeType=%s\n", thunar_vfs_mime_info_get_name (info));
+      fclose (fp);
+
+      /* update the mimeinfo.cache file for the directory */
+      command = g_strdup_printf ("update-desktop-database %s", directory);
+      succeed = g_spawn_command_line_sync (command, NULL, NULL, NULL, error);
+      g_free (command);
+
+      /* check if the update was successfull */
+      if (G_LIKELY (succeed))
+        {
+          /* load the application from the .desktop file */
+          desktop_id = g_path_get_basename (file);
+          application = thunar_vfs_mime_application_new_from_file (file, desktop_id);
+          g_free (desktop_id);
+
+          /* this shouldn't happen, but you never know */
+          if (G_UNLIKELY (application == NULL))
+            g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_IO, _("Failed to load application from file %s"), file);
+        }
+    }
+
+  /* cleanup */
+  g_free (directory);
+  g_free (file);
+
+  return application;
+}
+
+
+
 #define __THUNAR_VFS_MIME_DATABASE_C__
 #include <thunar-vfs/thunar-vfs-aliasdef.c>
index 6d40121..bbd539c 100644 (file)
@@ -40,24 +40,33 @@ GType                     thunar_vfs_mime_database_get_type                 (voi
 
 ThunarVfsMimeDatabase    *thunar_vfs_mime_database_get_default              (void);
 
-ThunarVfsMimeInfo        *thunar_vfs_mime_database_get_info                 (ThunarVfsMimeDatabase *database,
-                                                                             const gchar           *mime_type);
-ThunarVfsMimeInfo        *thunar_vfs_mime_database_get_info_for_data        (ThunarVfsMimeDatabase *database,
-                                                                             gconstpointer          data,
-                                                                             gsize                  length);
-ThunarVfsMimeInfo        *thunar_vfs_mime_database_get_info_for_name        (ThunarVfsMimeDatabase *database,
-                                                                             const gchar           *name);
-ThunarVfsMimeInfo        *thunar_vfs_mime_database_get_info_for_file        (ThunarVfsMimeDatabase *database,
-                                                                             const gchar           *path,
-                                                                             const gchar           *name);
+ThunarVfsMimeInfo        *thunar_vfs_mime_database_get_info                 (ThunarVfsMimeDatabase    *database,
+                                                                             const gchar              *mime_type);
+ThunarVfsMimeInfo        *thunar_vfs_mime_database_get_info_for_data        (ThunarVfsMimeDatabase    *database,
+                                                                             gconstpointer             data,
+                                                                             gsize                     length);
+ThunarVfsMimeInfo        *thunar_vfs_mime_database_get_info_for_name        (ThunarVfsMimeDatabase    *database,
+                                                                             const gchar              *name);
+ThunarVfsMimeInfo        *thunar_vfs_mime_database_get_info_for_file        (ThunarVfsMimeDatabase    *database,
+                                                                             const gchar              *path,
+                                                                             const gchar              *name);
 
-GList                    *thunar_vfs_mime_database_get_infos_for_info       (ThunarVfsMimeDatabase *database,
-                                                                             ThunarVfsMimeInfo     *info);
+GList                    *thunar_vfs_mime_database_get_infos_for_info       (ThunarVfsMimeDatabase    *database,
+                                                                             ThunarVfsMimeInfo        *info);
 
-GList                    *thunar_vfs_mime_database_get_applications         (ThunarVfsMimeDatabase *database,
-                                                                             ThunarVfsMimeInfo     *info);
-ThunarVfsMimeApplication *thunar_vfs_mime_database_get_default_application  (ThunarVfsMimeDatabase *database,
-                                                                             ThunarVfsMimeInfo     *info);
+GList                    *thunar_vfs_mime_database_get_applications         (ThunarVfsMimeDatabase    *database,
+                                                                             ThunarVfsMimeInfo        *info);
+ThunarVfsMimeApplication *thunar_vfs_mime_database_get_default_application  (ThunarVfsMimeDatabase    *database,
+                                                                             ThunarVfsMimeInfo        *info);
+gboolean                  thunar_vfs_mime_database_set_default_application  (ThunarVfsMimeDatabase    *database,
+                                                                             ThunarVfsMimeInfo        *info,
+                                                                             ThunarVfsMimeApplication *application,
+                                                                             GError                  **error);
+ThunarVfsMimeApplication *thunar_vfs_mime_database_add_application          (ThunarVfsMimeDatabase    *database,
+                                                                             ThunarVfsMimeInfo        *info,
+                                                                             const gchar              *name,
+                                                                             const gchar              *exec,
+                                                                             GError                  **error);
 
 G_END_DECLS;
 
index a84414f..b872aea 100644 (file)
@@ -170,6 +170,7 @@ _thunar_vfs_sysdep_parse_exec (const gchar *exec,
           quoted = g_shell_quote (name);
           g_string_append (command_line, "-T ");
           g_string_append (command_line, quoted);
+          g_string_append_c (command_line, ' ');
           g_free (quoted);
         }
       g_string_append (command_line, "-x ");
index 05dfe29..e5741d4 100644 (file)
@@ -70,6 +70,7 @@ thunar_vfs_file_mode_get_type G_GNUC_CONST
 thunar_vfs_file_type_get_type G_GNUC_CONST
 thunar_vfs_interactive_job_response_get_type G_GNUC_CONST
 thunar_vfs_mime_application_error_get_type G_GNUC_CONST
+thunar_vfs_mime_application_flags_get_type G_GNUC_CONST
 thunar_vfs_monitor_event_get_type G_GNUC_CONST
 thunar_vfs_volume_kind_get_type G_GNUC_CONST
 thunar_vfs_volume_status_get_type G_GNUC_CONST
@@ -99,10 +100,14 @@ thunar_vfs_job_cancelled
 thunar_vfs_mime_application_error_quark G_GNUC_CONST
 thunar_vfs_mime_application_get_type G_GNUC_CONST
 thunar_vfs_mime_application_new_from_desktop_id G_GNUC_MALLOC
+thunar_vfs_mime_application_new_from_file G_GNUC_MALLOC
 thunar_vfs_mime_application_ref
 thunar_vfs_mime_application_unref
+thunar_vfs_mime_application_get_command
 thunar_vfs_mime_application_get_desktop_id
+thunar_vfs_mime_application_get_flags
 thunar_vfs_mime_application_get_name
+thunar_vfs_mime_application_get_mime_types
 thunar_vfs_mime_application_exec
 thunar_vfs_mime_application_exec_with_env
 thunar_vfs_mime_application_lookup_icon_name
@@ -123,6 +128,8 @@ thunar_vfs_mime_database_get_info_for_file
 thunar_vfs_mime_database_get_infos_for_info
 thunar_vfs_mime_database_get_applications
 thunar_vfs_mime_database_get_default_application
+thunar_vfs_mime_database_set_default_application
+thunar_vfs_mime_database_add_application
 #endif
 #endif
 
index d23c9e4..56b3504 100644 (file)
@@ -3,6 +3,7 @@
 INCLUDES =                                                             \
        -I$(top_builddir)                                               \
        -I$(top_srcdir)                                                 \
+       -DBINDIR=\"$(bindir)\"                                          \
        -DDATADIR=\"$(datadir)\"                                        \
        -DEXO_API_SUBJECT_TO_CHANGE                                     \
        -DEXO_DISABLE_DEPRECATED                                        \
@@ -24,6 +25,10 @@ Thunar_SOURCES =                                                     \
        main.c                                                          \
        thunar-application.c                                            \
        thunar-application.h                                            \
+       thunar-chooser-dialog.c                                         \
+       thunar-chooser-dialog.h                                         \
+       thunar-chooser-model.c                                          \
+       thunar-chooser-model.h                                          \
        thunar-clipboard-manager.c                                      \
        thunar-clipboard-manager.h                                      \
        thunar-computer-folder.c                                        \
diff --git a/thunar/thunar-chooser-dialog.c b/thunar/thunar-chooser-dialog.c
new file mode 100644 (file)
index 0000000..e773280
--- /dev/null
@@ -0,0 +1,951 @@
+/* $Id$ */
+/*-
+ * Copyright (c) 2005 Benedikt Meurer <benny@xfce.org>.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program; if not, write to the Free Software Foundation, Inc., 59 Temple
+ * Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#ifdef HAVE_MEMORY_H
+#include <memory.h>
+#endif
+#ifdef HAVE_STRING_H
+#include <string.h>
+#endif
+
+#include <thunar/thunar-chooser-dialog.h>
+#include <thunar/thunar-chooser-model.h>
+
+
+
+/* Property identifiers */
+enum
+{
+  PROP_0,
+  PROP_FILE,
+  PROP_OPEN,
+};
+
+
+
+static void     thunar_chooser_dialog_class_init      (ThunarChooserDialogClass *klass);
+static void     thunar_chooser_dialog_init            (ThunarChooserDialog      *dialog);
+static void     thunar_chooser_dialog_dispose         (GObject                  *object);
+static void     thunar_chooser_dialog_get_property    (GObject                  *object,
+                                                       guint                     prop_id,
+                                                       GValue                   *value,
+                                                       GParamSpec               *pspec);
+static void     thunar_chooser_dialog_set_property    (GObject                  *object,
+                                                       guint                     prop_id,
+                                                       const GValue             *value,
+                                                       GParamSpec               *pspec);
+static void     thunar_chooser_dialog_realize         (GtkWidget                *widget);
+static void     thunar_chooser_dialog_response        (GtkDialog                *widget,
+                                                       gint                      response);
+static gboolean thunar_chooser_dialog_selection_func  (GtkTreeSelection         *selection,
+                                                       GtkTreeModel             *model,
+                                                       GtkTreePath              *path,
+                                                       gboolean                  path_currently_selected,
+                                                       gpointer                  user_data);
+static void     thunar_chooser_dialog_update_accept   (ThunarChooserDialog      *dialog);
+static void     thunar_chooser_dialog_update_header   (ThunarChooserDialog      *dialog);
+static void     thunar_chooser_dialog_browse          (GtkWidget                *button,
+                                                       ThunarChooserDialog      *dialog);
+static void     thunar_chooser_dialog_notify_expanded (GtkExpander              *expander,
+                                                       GParamSpec               *pspec,
+                                                       ThunarChooserDialog      *dialog);
+static void     thunar_chooser_dialog_notify_loading  (ThunarChooserModel       *model,
+                                                       GParamSpec               *pspec,
+                                                       ThunarChooserDialog      *dialog);
+static void     thunar_chooser_dialog_row_activated   (GtkTreeView              *treeview,
+                                                       GtkTreePath              *path,
+                                                       GtkTreeViewColumn        *column,
+                                                       ThunarChooserDialog      *dialog);
+
+
+
+struct _ThunarChooserDialogClass
+{
+  GtkDialogClass __parent__;
+};
+
+struct _ThunarChooserDialog
+{
+  GtkDialog __parent__;
+
+  ThunarFile *file;
+  gboolean    open;
+
+  GtkWidget  *header_image;
+  GtkWidget  *header_label;
+  GtkWidget  *tree_view;
+  GtkWidget  *custom_expander;
+  GtkWidget  *custom_entry;
+  GtkWidget  *cancel_button;
+  GtkWidget  *accept_button;
+};
+
+
+
+G_DEFINE_TYPE (ThunarChooserDialog, thunar_chooser_dialog, GTK_TYPE_DIALOG);
+
+
+
+static void
+thunar_chooser_dialog_class_init (ThunarChooserDialogClass *klass)
+{
+  GtkDialogClass *gtkdialog_class;
+  GtkWidgetClass *gtkwidget_class;
+  GObjectClass   *gobject_class;
+
+  gobject_class = G_OBJECT_CLASS (klass);
+  gobject_class->dispose = thunar_chooser_dialog_dispose;
+  gobject_class->get_property = thunar_chooser_dialog_get_property;
+  gobject_class->set_property = thunar_chooser_dialog_set_property;
+
+  gtkwidget_class = GTK_WIDGET_CLASS (klass);
+  gtkwidget_class->realize = thunar_chooser_dialog_realize;
+
+  gtkdialog_class = GTK_DIALOG_CLASS (klass);
+  gtkdialog_class->response = thunar_chooser_dialog_response;
+
+  /**
+   * ThunarChooserDialog::file:
+   *
+   * The #ThunarFile for which an application should be chosen.
+   **/
+  g_object_class_install_property (gobject_class,
+                                   PROP_FILE,
+                                   g_param_spec_object ("file",
+                                                        _("File"),
+                                                        _("The file for which an application should be chosen"),
+                                                        THUNAR_TYPE_FILE,
+                                                        EXO_PARAM_READWRITE));
+
+  /**
+   * ThunarChooserDialog::open:
+   *
+   * Whether the chooser should open the specified file.
+   **/
+  g_object_class_install_property (gobject_class,
+                                   PROP_OPEN,
+                                   g_param_spec_boolean ("open",
+                                                         _("Open"),
+                                                         _("Whether the chooser should open the specified file"),
+                                                         FALSE,
+                                                         G_PARAM_CONSTRUCT | EXO_PARAM_READWRITE));
+}
+
+
+
+static void
+thunar_chooser_dialog_init (ThunarChooserDialog *dialog)
+{
+  GtkTreeViewColumn *column;
+  GtkTreeSelection  *selection;
+  GtkCellRenderer   *renderer;
+  GtkWidget         *header;
+  GtkWidget         *button;
+  GtkWidget         *image;
+  GtkWidget         *hbox;
+  GtkWidget         *vbox;
+  GtkWidget         *box;
+  GtkWidget         *swin;
+
+  /* setup basic window properties */
+  gtk_dialog_set_has_separator (GTK_DIALOG (dialog), FALSE);
+  gtk_window_set_destroy_with_parent (GTK_WINDOW (dialog), TRUE);
+  gtk_window_set_resizable (GTK_WINDOW (dialog), FALSE);
+  gtk_window_set_title (GTK_WINDOW (dialog), _("Open With"));
+
+  /* create the main widget box */
+  vbox = g_object_new (GTK_TYPE_VBOX, "border-width", 6, "spacing", 12, NULL);
+  gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dialog)->vbox), vbox, TRUE, TRUE, 0);
+  gtk_widget_show (vbox);
+
+  /* create the header box */
+  header = gtk_hbox_new (FALSE, 6);
+  gtk_box_pack_start (GTK_BOX (vbox), header, FALSE, FALSE, 0);
+  gtk_widget_show (header);
+
+  /* create the header image */
+  dialog->header_image = gtk_image_new ();
+  gtk_box_pack_start (GTK_BOX (header), dialog->header_image, FALSE, FALSE, 0);
+  gtk_widget_show (dialog->header_image);
+
+  /* create the header label */
+  dialog->header_label = gtk_label_new ("");
+  gtk_misc_set_alignment (GTK_MISC (dialog->header_label), 0.0f, 0.5f);
+  gtk_label_set_line_wrap (GTK_LABEL (dialog->header_label), TRUE);
+  gtk_widget_set_size_request (dialog->header_label, 350, -1);
+  gtk_box_pack_start (GTK_BOX (header), dialog->header_label, FALSE, FALSE, 0);
+  gtk_widget_show (dialog->header_label);
+
+  /* create the view box */
+  box = gtk_vbox_new (FALSE, 6);
+  gtk_box_pack_start (GTK_BOX (vbox), box, TRUE, TRUE, 0);
+  gtk_widget_show (box);
+
+  /* create the scrolled window for the tree view */
+  swin = gtk_scrolled_window_new (NULL, NULL);
+  gtk_widget_set_size_request (swin, -1, 275);
+  gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (swin), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
+  gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (swin), GTK_SHADOW_IN);
+  gtk_box_pack_start (GTK_BOX (box), swin, TRUE, TRUE, 0);
+  gtk_widget_show (swin);
+
+  /* create the tree view */
+  dialog->tree_view = g_object_new (GTK_TYPE_TREE_VIEW, "headers-visible", FALSE, NULL);
+  g_signal_connect (G_OBJECT (dialog->tree_view), "row-activated", G_CALLBACK (thunar_chooser_dialog_row_activated), dialog);
+  gtk_container_add (GTK_CONTAINER (swin), dialog->tree_view);
+  gtk_widget_show (dialog->tree_view);
+
+  /* append the tree view column */
+  column = g_object_new (GTK_TYPE_TREE_VIEW_COLUMN, "expand", TRUE, NULL);
+  renderer = gtk_cell_renderer_pixbuf_new ();
+  gtk_tree_view_column_pack_start (column, renderer, FALSE);
+  gtk_tree_view_column_set_attributes (column, renderer,
+                                       "pixbuf", THUNAR_CHOOSER_MODEL_COLUMN_ICON,
+                                       NULL);
+  renderer = gtk_cell_renderer_text_new ();
+  gtk_tree_view_column_pack_start (column, renderer, TRUE);
+  gtk_tree_view_column_set_attributes (column, renderer,
+                                       "style", THUNAR_CHOOSER_MODEL_COLUMN_STYLE,
+                                       "style-set", THUNAR_CHOOSER_MODEL_COLUMN_STYLE_SET,
+                                       "text", THUNAR_CHOOSER_MODEL_COLUMN_NAME,
+                                       NULL);
+  gtk_tree_view_column_set_sort_column_id (column, THUNAR_CHOOSER_MODEL_COLUMN_NAME);
+  gtk_tree_view_append_column (GTK_TREE_VIEW (dialog->tree_view), column);
+
+  /* create the "Custom command" expand */
+  dialog->custom_expander = gtk_expander_new_with_mnemonic (_("Use a _custom command:"));
+  exo_binding_new_with_negation (G_OBJECT (dialog->custom_expander), "expanded", G_OBJECT (dialog->tree_view), "sensitive");
+  g_signal_connect (G_OBJECT (dialog->custom_expander), "notify::expanded", G_CALLBACK (thunar_chooser_dialog_notify_expanded), dialog);
+  gtk_box_pack_start (GTK_BOX (box), dialog->custom_expander, FALSE, FALSE, 0);
+  gtk_widget_show (dialog->custom_expander);
+
+  /* create the "Custom command" box */
+  hbox = gtk_hbox_new (FALSE, 6);
+  gtk_container_add (GTK_CONTAINER (dialog->custom_expander), hbox);
+  gtk_widget_show (hbox);
+
+  /* create the "Custom command" entry */
+  dialog->custom_entry = g_object_new (GTK_TYPE_ENTRY, "activates-default", TRUE, NULL);
+  g_signal_connect_swapped (G_OBJECT (dialog->custom_entry), "changed", G_CALLBACK (thunar_chooser_dialog_update_accept), dialog);
+  gtk_box_pack_start (GTK_BOX (hbox), dialog->custom_entry, TRUE, TRUE, 0);
+  gtk_widget_show (dialog->custom_entry);
+
+  /* create the "Custom command" button */
+  button = gtk_button_new_with_mnemonic (_("_Browse"));
+  g_signal_connect (G_OBJECT (button), "clicked", G_CALLBACK (thunar_chooser_dialog_browse), dialog);
+  gtk_box_pack_start (GTK_BOX (hbox), button, FALSE, FALSE, 0);
+  gtk_widget_show (button);
+
+  /* create the image for the browse button */
+  image = gtk_image_new_from_stock (GTK_STOCK_OPEN, GTK_ICON_SIZE_BUTTON);
+  gtk_button_set_image (GTK_BUTTON (button), image);
+  gtk_widget_show (image);
+
+  /* add the "Cancel" button */
+  dialog->cancel_button = gtk_dialog_add_button (GTK_DIALOG (dialog), GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL);
+
+  /* add the "Ok"/"Open" button */
+  dialog->accept_button = gtk_dialog_add_button (GTK_DIALOG (dialog), GTK_STOCK_OK, GTK_RESPONSE_ACCEPT);
+  gtk_dialog_set_response_sensitive (GTK_DIALOG (dialog), GTK_RESPONSE_ACCEPT, FALSE);
+  gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_ACCEPT);
+
+  /* update the "Ok"/"Open" button whenever the tree selection changes */
+  selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (dialog->tree_view));
+  gtk_tree_selection_set_select_function (selection, thunar_chooser_dialog_selection_func, dialog, NULL);
+  g_signal_connect_swapped (G_OBJECT (selection), "changed", G_CALLBACK (thunar_chooser_dialog_update_accept), dialog);
+}
+
+
+
+static void
+thunar_chooser_dialog_dispose (GObject *object)
+{
+  ThunarChooserDialog *dialog = THUNAR_CHOOSER_DIALOG (object);
+
+  /* drop the reference on the file */
+  thunar_chooser_dialog_set_file (dialog, NULL);
+
+  (*G_OBJECT_CLASS (thunar_chooser_dialog_parent_class)->dispose) (object);
+}
+
+
+
+static void
+thunar_chooser_dialog_get_property (GObject    *object,
+                                    guint       prop_id,
+                                    GValue     *value,
+                                    GParamSpec *pspec)
+{
+  ThunarChooserDialog *dialog = THUNAR_CHOOSER_DIALOG (object);
+
+  switch (prop_id)
+    {
+    case PROP_FILE:
+      g_value_set_object (value, thunar_chooser_dialog_get_file (dialog));
+      break;
+
+    case PROP_OPEN:
+      g_value_set_boolean (value, thunar_chooser_dialog_get_open (dialog));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+    }
+}
+
+
+
+static void
+thunar_chooser_dialog_set_property (GObject      *object,
+                                    guint         prop_id,
+                                    const GValue *value,
+                                    GParamSpec   *pspec)
+{
+  ThunarChooserDialog *dialog = THUNAR_CHOOSER_DIALOG (object);
+
+  switch (prop_id)
+    {
+    case PROP_FILE:
+      thunar_chooser_dialog_set_file (dialog, g_value_get_object (value));
+      break;
+
+    case PROP_OPEN:
+      thunar_chooser_dialog_set_open (dialog, g_value_get_boolean (value));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+    }
+}
+
+
+
+static void
+thunar_chooser_dialog_realize (GtkWidget *widget)
+{
+  ThunarChooserDialog *dialog = THUNAR_CHOOSER_DIALOG (widget);
+  GtkTreeModel        *model;
+  GdkCursor           *cursor;
+
+  /* let the GtkWindow class realize the dialog */
+  (*GTK_WIDGET_CLASS (thunar_chooser_dialog_parent_class)->realize) (widget);
+
+  /* update the dialog header */
+  thunar_chooser_dialog_update_header (dialog);
+
+  /* setup a watch cursor if we're currently loading the model */
+  model = gtk_tree_view_get_model (GTK_TREE_VIEW (dialog->tree_view));
+  if (thunar_chooser_model_get_loading (THUNAR_CHOOSER_MODEL (model)))
+    {
+      cursor = gdk_cursor_new (GDK_WATCH);
+      gdk_window_set_cursor (widget->window, cursor);
+      gdk_cursor_unref (cursor);
+    }
+}
+
+
+
+static void
+thunar_chooser_dialog_response (GtkDialog *widget,
+                                gint       response)
+{
+  ThunarVfsMimeApplication *application = NULL;
+  ThunarVfsMimeDatabase    *mime_database;
+  ThunarChooserDialog      *dialog = THUNAR_CHOOSER_DIALOG (widget);
+  ThunarVfsMimeInfo        *mime_info;
+  GtkTreeSelection         *selection;
+  GtkTreeModel             *model;
+  GtkTreeIter               iter;
+  const gchar              *exec;
+  GtkWidget                *message;
+  gboolean                  succeed;
+  GError                   *error = NULL;
+  gchar                    *name;
+  gchar                    *s;
+  GList                     list;
+
+  /* no special processing for non-accept responses */
+  if (G_UNLIKELY (response != GTK_RESPONSE_ACCEPT))
+    return;
+
+  /* grab a reference on the mime database */
+  mime_database = thunar_vfs_mime_database_get_default ();
+
+  /* determine the mime info for the file */
+  mime_info = thunar_file_get_mime_info (dialog->file);
+
+  /* determine the application that was chosen by the user */
+  if (!gtk_expander_get_expanded (GTK_EXPANDER (dialog->custom_expander)))
+    {
+      selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (dialog->tree_view));
+      if (gtk_tree_selection_get_selected (selection, &model, &iter))
+        gtk_tree_model_get (model, &iter, THUNAR_CHOOSER_MODEL_COLUMN_APPLICATION, &application, -1);
+    }
+  else
+    {
+      /* determine the command line for the custom command */
+      exec = gtk_entry_get_text (GTK_ENTRY (dialog->custom_entry));
+
+      /* determine the name for the custom command */
+      name = g_strdup (exec);
+      s = strchr (name, ' ');
+      if (G_UNLIKELY (s != NULL))
+        *s = '\0';
+
+      /* try to add an application for the custom command */
+      application = thunar_vfs_mime_database_add_application (mime_database, mime_info, name, exec, &error);
+
+      /* verify the application */
+      if (G_UNLIKELY (application == NULL))
+        {
+          message = gtk_message_dialog_new (GTK_WINDOW (dialog),
+                                            GTK_DIALOG_DESTROY_WITH_PARENT
+                                            | GTK_DIALOG_MODAL,
+                                            GTK_MESSAGE_ERROR,
+                                            GTK_BUTTONS_CLOSE,
+                                            _("Failed to add new application %s."), name);
+          gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (message), "%s.", error->message);
+          gtk_dialog_run (GTK_DIALOG (message));
+          gtk_widget_destroy (message);
+          g_error_free (error);
+        }
+
+      /* cleanup */
+      g_free (name);
+    }
+
+  /* verify that we have a valid application */
+  if (G_UNLIKELY (application == NULL))
+    goto cleanup;
+
+  /* remember the application as default for these kind of file */
+  succeed = thunar_vfs_mime_database_set_default_application (mime_database, mime_info, application, &error);
+
+  /* verify that we were successfull */
+  if (G_UNLIKELY (!succeed))
+    {
+      message = gtk_message_dialog_new (GTK_WINDOW (dialog),
+                                        GTK_DIALOG_DESTROY_WITH_PARENT
+                                        | GTK_DIALOG_MODAL,
+                                        GTK_MESSAGE_ERROR,
+                                        GTK_BUTTONS_CLOSE,
+                                        _("Failed to set default application for \"%s\"."),
+                                        thunar_file_get_display_name (dialog->file));
+      gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (message), "%s.", error->message);
+      gtk_dialog_run (GTK_DIALOG (message));
+      gtk_widget_destroy (message);
+      g_error_free (error);
+    }
+  else if (G_LIKELY (dialog->open))
+    {
+      /* open the file using the specified application */
+      list.data = thunar_file_get_uri (dialog->file); list.next = list.prev = NULL;
+      if (!thunar_vfs_mime_application_exec (application, gtk_widget_get_screen (GTK_WIDGET (dialog)), &list, &error))
+        {
+          message = gtk_message_dialog_new (GTK_WINDOW (dialog),
+                                            GTK_DIALOG_DESTROY_WITH_PARENT
+                                            | GTK_DIALOG_MODAL,
+                                            GTK_MESSAGE_ERROR,
+                                            GTK_BUTTONS_CLOSE,
+                                            _("Failed execute %s."),
+                                            thunar_vfs_mime_application_get_name (application));
+          gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (message), "%s.", error->message);
+          gtk_dialog_run (GTK_DIALOG (message));
+          gtk_widget_destroy (message);
+          g_error_free (error);
+        }
+    }
+
+  /* cleanup */
+  thunar_vfs_mime_application_unref (application);
+cleanup:
+  g_object_unref (G_OBJECT (mime_database));
+  thunar_vfs_mime_info_unref (mime_info);
+}
+
+
+
+static gboolean
+thunar_chooser_dialog_selection_func (GtkTreeSelection *selection,
+                                      GtkTreeModel     *model,
+                                      GtkTreePath      *path,
+                                      gboolean          path_currently_selected,
+                                      gpointer          user_data)
+{
+  GtkTreeIter iter;
+  gboolean    permitted = TRUE;
+  GValue      value = { 0, };
+
+  /* we can always change the selection if the path is already selected */
+  if (G_UNLIKELY (!path_currently_selected))
+    {
+      /* check if there's an application for the path */
+      gtk_tree_model_get_iter (model, &iter, path);
+      gtk_tree_model_get_value (model, &iter, THUNAR_CHOOSER_MODEL_COLUMN_APPLICATION, &value);
+      permitted = (g_value_get_boxed (&value) != NULL);
+      g_value_unset (&value);
+    }
+
+  return permitted;
+}
+
+
+
+static void
+thunar_chooser_dialog_update_accept (ThunarChooserDialog *dialog)
+{
+  GtkTreeSelection *selection;
+  GtkTreeModel     *model;
+  GtkTreeIter       iter;
+  const gchar      *text;
+  gboolean          sensitive = FALSE;
+  GValue            value = { 0, };
+
+  g_return_if_fail (THUNAR_IS_CHOOSER_DIALOG (dialog));
+
+  if (gtk_expander_get_expanded (GTK_EXPANDER (dialog->custom_expander)))
+    {
+      /* check if the user entered a valid custom command */
+      text = gtk_entry_get_text (GTK_ENTRY (dialog->custom_entry));
+      sensitive = (text != NULL && *text != '\0');
+    }
+  else
+    {
+      selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (dialog->tree_view));
+      if (gtk_tree_selection_get_selected (selection, &model, &iter))
+        {
+          /* check if the selected row refers to a valid application */
+          gtk_tree_model_get_value (model, &iter, THUNAR_CHOOSER_MODEL_COLUMN_APPLICATION, &value);
+          sensitive = (g_value_get_boxed (&value) != NULL);
+          g_value_unset (&value);
+        }
+    }
+
+  /* update the "Ok"/"Open" button sensitivity */
+  gtk_dialog_set_response_sensitive (GTK_DIALOG (dialog), GTK_RESPONSE_ACCEPT, sensitive);
+}
+
+
+
+static void
+thunar_chooser_dialog_update_header (ThunarChooserDialog *dialog)
+{
+  ThunarVfsMimeInfo *mime_info;
+  ThunarIconFactory *icon_factory;
+  GtkIconTheme      *icon_theme;
+  const gchar       *icon_name;
+  GdkPixbuf         *icon;
+  gchar             *text;
+
+  g_return_if_fail (THUNAR_IS_CHOOSER_DIALOG (dialog));
+  g_return_if_fail (GTK_WIDGET_REALIZED (dialog));
+
+  /* check if we have a valid file set */
+  if (G_UNLIKELY (dialog->file == NULL))
+    {
+#if GTK_CHECK_VERSION(2,8,0)
+      gtk_image_clear (GTK_IMAGE (dialog->header_image));
+#endif
+      gtk_label_set_text (GTK_LABEL (dialog->header_label), NULL);
+    }
+  else
+    {
+      /* determine the mime info for the file */
+      mime_info = thunar_file_get_mime_info (dialog->file);
+
+      /* determine the icon theme/factory for the widget */
+      icon_theme = gtk_icon_theme_get_for_screen (gtk_widget_get_screen (GTK_WIDGET (dialog)));
+      icon_factory = thunar_icon_factory_get_for_icon_theme (icon_theme);
+
+      /* update the header image with the icon for the mime type */
+      icon_name = thunar_vfs_mime_info_lookup_icon_name (mime_info, icon_theme);
+      icon = thunar_icon_factory_load_icon (icon_factory, icon_name, 48, NULL, FALSE);
+      gtk_image_set_from_pixbuf (GTK_IMAGE (dialog->header_image), icon);
+      if (G_LIKELY (icon != NULL))
+        g_object_unref (G_OBJECT (icon));
+
+      /* update the header label */
+      text = g_strdup_printf (_("Open <i>%s</i> and other files of type \"%s\" with:"),
+                              thunar_file_get_display_name (dialog->file),
+                              thunar_vfs_mime_info_get_comment (mime_info));
+      gtk_label_set_markup (GTK_LABEL (dialog->header_label), text);
+      g_free (text);
+
+      /* cleanup */
+      g_object_unref (G_OBJECT (icon_factory));
+      thunar_vfs_mime_info_unref (mime_info);
+    }
+}
+
+
+
+static void
+thunar_chooser_dialog_browse (GtkWidget           *button,
+                              ThunarChooserDialog *dialog)
+{
+  GtkFileFilter *filter;
+  GtkWidget     *chooser;
+  gchar         *filename;
+  gchar         *s;
+
+  chooser = gtk_file_chooser_dialog_new (_("Select an Application"),
+                                         GTK_WINDOW (dialog),
+                                         GTK_FILE_CHOOSER_ACTION_OPEN,
+                                         GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
+                                         GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT,
+                                         NULL);
+  gtk_file_chooser_set_local_only (GTK_FILE_CHOOSER (chooser), TRUE);
+
+  /* add file chooser filters */
+  filter = gtk_file_filter_new ();
+  gtk_file_filter_set_name (filter, _("All Files"));
+  gtk_file_filter_add_pattern (filter, "*");
+  gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (chooser), filter);
+
+  filter = gtk_file_filter_new ();
+  gtk_file_filter_set_name (filter, _("Executable Files"));
+  gtk_file_filter_add_mime_type (filter, "application/x-csh");
+  gtk_file_filter_add_mime_type (filter, "application/x-executable");
+  gtk_file_filter_add_mime_type (filter, "application/x-perl");
+  gtk_file_filter_add_mime_type (filter, "application/x-python");
+  gtk_file_filter_add_mime_type (filter, "application/x-ruby");
+  gtk_file_filter_add_mime_type (filter, "application/x-shellscript");
+  gtk_file_filter_add_pattern (filter, "*.pl");
+  gtk_file_filter_add_pattern (filter, "*.py");
+  gtk_file_filter_add_pattern (filter, "*.rb");
+  gtk_file_filter_add_pattern (filter, "*.sh");
+  gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (chooser), filter);
+  gtk_file_chooser_set_filter (GTK_FILE_CHOOSER (chooser), filter);
+
+  filter = gtk_file_filter_new ();
+  gtk_file_filter_set_name (filter, _("Perl Scripts"));
+  gtk_file_filter_add_mime_type (filter, "application/x-perl");
+  gtk_file_filter_add_pattern (filter, "*.pl");
+  gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (chooser), filter);
+
+  filter = gtk_file_filter_new ();
+  gtk_file_filter_set_name (filter, _("Python Scripts"));
+  gtk_file_filter_add_mime_type (filter, "application/x-python");
+  gtk_file_filter_add_pattern (filter, "*.py");
+  gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (chooser), filter);
+
+  filter = gtk_file_filter_new ();
+  gtk_file_filter_set_name (filter, _("Ruby Scripts"));
+  gtk_file_filter_add_mime_type (filter, "application/x-ruby");
+  gtk_file_filter_add_pattern (filter, "*.rb");
+  gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (chooser), filter);
+
+  filter = gtk_file_filter_new ();
+  gtk_file_filter_set_name (filter, _("Shell Scripts"));
+  gtk_file_filter_add_mime_type (filter, "application/x-csh");
+  gtk_file_filter_add_mime_type (filter, "application/x-shellscript");
+  gtk_file_filter_add_pattern (filter, "*.sh");
+  gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (chooser), filter);
+
+  /* use the bindir as default folder */
+  gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (chooser), BINDIR);
+
+  /* setup the currently selected file */
+  filename = gtk_editable_get_chars (GTK_EDITABLE (dialog->custom_entry), 0, -1);
+  if (G_LIKELY (filename != NULL))
+    {
+      /* use only the first argument */
+      s = strchr (filename, ' ');
+      if (G_UNLIKELY (s != NULL))
+        *s = '\0';
+
+      if (G_LIKELY (*filename != '\0'))
+        gtk_file_chooser_set_filename (GTK_FILE_CHOOSER (chooser), filename);
+      g_free (filename);
+    }
+
+  /* run the chooser dialog */
+  if (gtk_dialog_run (GTK_DIALOG (chooser)) == GTK_RESPONSE_ACCEPT)
+    {
+      filename = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (chooser));
+      gtk_entry_set_text (GTK_ENTRY (dialog->custom_entry), filename);
+      g_free (filename);
+    }
+
+  gtk_widget_destroy (chooser);
+}
+
+
+
+static void
+thunar_chooser_dialog_notify_expanded (GtkExpander         *expander,
+                                       GParamSpec          *pspec,
+                                       ThunarChooserDialog *dialog)
+{
+  GtkTreeSelection *selection;
+
+  g_return_if_fail (GTK_IS_EXPANDER (expander));
+  g_return_if_fail (THUNAR_IS_CHOOSER_DIALOG (dialog));
+
+  /* clear the application selection whenever the expander
+   * is expanded to avoid confusion for the user.
+   */
+  if (gtk_expander_get_expanded (expander))
+    {
+      selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (dialog->tree_view));
+      gtk_tree_selection_unselect_all (selection);
+    }
+
+  /* update the sensitivity of the "Ok"/"Open" button */
+  thunar_chooser_dialog_update_accept (dialog);
+}
+
+
+
+static void
+thunar_chooser_dialog_notify_loading (ThunarChooserModel  *model,
+                                      GParamSpec          *pspec,
+                                      ThunarChooserDialog *dialog)
+{
+  GtkTreePath *path;
+  GtkTreeIter  iter;
+
+  g_return_if_fail (THUNAR_IS_CHOOSER_MODEL (model));
+  g_return_if_fail (THUNAR_IS_CHOOSER_DIALOG (dialog));
+
+  /* expand the first tree view row (the recommended applications) */
+  if (G_LIKELY (gtk_tree_model_get_iter_first (GTK_TREE_MODEL (model), &iter)))
+    {
+      path = gtk_tree_model_get_path (GTK_TREE_MODEL (model), &iter);
+      gtk_tree_view_expand_to_path (GTK_TREE_VIEW (dialog->tree_view), path);
+      gtk_tree_path_free (path);
+    }
+
+  /* reset the cursor */
+  if (G_LIKELY (GTK_WIDGET_REALIZED (dialog)))
+    gdk_window_set_cursor (GTK_WIDGET (dialog)->window, NULL);
+
+  /* grab focus to the tree view widget */
+  if (G_LIKELY (GTK_WIDGET_REALIZED (dialog->tree_view)))
+    gtk_widget_grab_focus (dialog->tree_view);
+}
+
+
+
+static void
+thunar_chooser_dialog_row_activated (GtkTreeView         *treeview,
+                                     GtkTreePath         *path,
+                                     GtkTreeViewColumn   *column,
+                                     ThunarChooserDialog *dialog)
+{
+  GtkTreeModel *model;
+  GtkTreeIter   iter;
+  GValue        value = { 0, };
+
+  g_return_if_fail (THUNAR_IS_CHOOSER_DIALOG (dialog));
+  g_return_if_fail (GTK_IS_TREE_VIEW (treeview));
+
+  /* determine the current chooser model */
+  model = gtk_tree_view_get_model (treeview);
+  if (G_UNLIKELY (model == NULL))
+    return;
+
+  /* determine the application for the tree path */
+  gtk_tree_model_get_iter (model, &iter, path);
+  gtk_tree_model_get_value (model, &iter, THUNAR_CHOOSER_MODEL_COLUMN_APPLICATION, &value);
+
+  /* check if the row refers to a valid application */
+  if (G_LIKELY (g_value_get_boxed (&value) != NULL))
+    {
+      /* emit the accept dialog response */
+      gtk_dialog_response (GTK_DIALOG (dialog), GTK_RESPONSE_ACCEPT);
+    }
+  else if (gtk_tree_view_row_expanded (treeview, path))
+    {
+      /* collapse the path that were double clicked */
+      gtk_tree_view_collapse_row (treeview, path);
+    }
+  else
+    {
+      /* expand the path that were double clicked */
+      gtk_tree_view_expand_to_path (treeview, path);
+    }
+
+  /* cleanup */
+  g_value_unset (&value);
+}
+
+
+
+/**
+ * thunar_chooser_dialog_new:
+ * @parent : transient parent of the dialog or %NULL.
+ * @file   : the #ThunarFile for which an application should be chosen.
+ * @open   : whether to also open the @file.
+ *
+ * Allocates a new #ThunarChooserDialog with the given parameters.
+ *
+ * Return value: the newly allocated #ThunarChooserDialog.
+ **/
+GtkWidget*
+thunar_chooser_dialog_new (GtkWindow  *parent,
+                           ThunarFile *file,
+                           gboolean    open)
+{
+  GtkWidget *dialog;
+
+  g_return_val_if_fail (parent == NULL || GTK_IS_WINDOW (parent), NULL);
+  g_return_val_if_fail (THUNAR_IS_FILE (file), NULL);
+
+  dialog = g_object_new (THUNAR_TYPE_CHOOSER_DIALOG,
+                         "file", file,
+                         "open", open,
+                         NULL);
+
+  if (G_LIKELY (parent != NULL))
+    gtk_window_set_transient_for (GTK_WINDOW (dialog), parent);
+
+  return dialog;
+}
+
+
+
+/**
+ * thunar_chooser_dialog_get_file:
+ * @dialog : a #ThunarChooserDialog.
+ *
+ * Returns the #ThunarFile currently associated with @dialog or
+ * %NULL if there's no such association.
+ *
+ * Return value: the #ThunarFile associated with @dialog.
+ **/
+ThunarFile*
+thunar_chooser_dialog_get_file (ThunarChooserDialog *dialog)
+{
+  g_return_val_if_fail (THUNAR_IS_CHOOSER_DIALOG (dialog), NULL);
+  return dialog->file;
+}
+
+
+
+/**
+ * thunar_chooser_dialog_set_file:
+ * @dialog : a #ThunarChooserDialog.
+ * @file   : a #ThunarFile or %NULL.
+ *
+ * Associates @dialog with @file.
+ **/
+void
+thunar_chooser_dialog_set_file (ThunarChooserDialog *dialog,
+                                ThunarFile          *file)
+{
+  ThunarChooserModel *model;
+  ThunarVfsMimeInfo  *mime_info;
+
+  g_return_if_fail (THUNAR_IS_CHOOSER_DIALOG (dialog));
+  g_return_if_fail (file == NULL || THUNAR_IS_FILE (file));
+
+  /* disconnect from the previous file */
+  if (G_LIKELY (dialog->file != NULL))
+    {
+      /* unset the chooser model */
+      gtk_tree_view_set_model (GTK_TREE_VIEW (dialog->tree_view), NULL);
+
+      /* disconnect us from the file */
+      g_signal_handlers_disconnect_by_func (G_OBJECT (dialog->file), gtk_widget_destroy, dialog);
+      thunar_file_unwatch (THUNAR_FILE (dialog->file));
+      g_object_unref (G_OBJECT (dialog->file));
+    }
+
+  /* activate the new file */
+  dialog->file = file;
+
+  /* connect to the new file */
+  if (G_LIKELY (file != NULL))
+    {
+      /* take a reference on the file */
+      g_object_ref (G_OBJECT (file));
+
+      /* watch the file for changes */
+      thunar_file_watch (dialog->file);
+
+      /* destroy the chooser dialog if the file is deleted */
+      g_signal_connect_swapped (G_OBJECT (file), "destroy", G_CALLBACK (gtk_widget_destroy), dialog);
+
+      /* allocate the new chooser model */
+      mime_info = thunar_file_get_mime_info (file);
+      model = thunar_chooser_model_new (mime_info);
+      gtk_tree_view_set_model (GTK_TREE_VIEW (dialog->tree_view), GTK_TREE_MODEL (model));
+      g_signal_connect (G_OBJECT (model), "notify::loading", G_CALLBACK (thunar_chooser_dialog_notify_loading), dialog);
+      thunar_vfs_mime_info_unref (mime_info);
+      g_object_unref (G_OBJECT (model));
+    }
+
+  /* update the header */
+  if (GTK_WIDGET_REALIZED (dialog))
+    thunar_chooser_dialog_update_header (dialog);
+
+  /* notify listeners */
+  g_object_notify (G_OBJECT (dialog), "file");
+}
+
+
+
+/**
+ * thunar_chooser_dialog_get_open:
+ * @dialog : a #ThunarChooserDialog.
+ *
+ * Tells whether the chooser @dialog should open the file.
+ *
+ * Return value: %TRUE if @dialog should open the file.
+ **/
+gboolean
+thunar_chooser_dialog_get_open (ThunarChooserDialog *dialog)
+{
+  g_return_val_if_fail (THUNAR_IS_CHOOSER_DIALOG (dialog), FALSE);
+  return dialog->open;
+}
+
+
+
+/**
+ * thunar_chooser_dialog_set_open:
+ * @dialog : a #ThunarChooserDialog.
+ * @open   : %TRUE if the chooser @dialog should open the file.
+ *
+ * Sets whether the chooser @dialog should open the file.
+ **/
+void
+thunar_chooser_dialog_set_open (ThunarChooserDialog *dialog,
+                                gboolean             open)
+{
+  g_return_if_fail (THUNAR_IS_CHOOSER_DIALOG (dialog));
+
+  /* apply the new state */
+  dialog->open = open;
+
+  /* change the accept button label text */
+  gtk_button_set_label (GTK_BUTTON (dialog->accept_button), open ? GTK_STOCK_OPEN : GTK_STOCK_OK);
+
+  /* notify listeners */
+  g_object_notify (G_OBJECT (dialog), "open");
+}
+
+
diff --git a/thunar/thunar-chooser-dialog.h b/thunar/thunar-chooser-dialog.h
new file mode 100644 (file)
index 0000000..5403808
--- /dev/null
@@ -0,0 +1,53 @@
+/* $Id$ */
+/*-
+ * Copyright (c) 2005 Benedikt Meurer <benny@xfce.org>.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program; if not, write to the Free Software Foundation, Inc., 59 Temple
+ * Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#ifndef __THUNAR_CHOOSER_DIALOG_H__
+#define __THUNAR_CHOOSER_DIALOG_H__
+
+#include <thunar/thunar-file.h>
+
+G_BEGIN_DECLS;
+
+typedef struct _ThunarChooserDialogClass ThunarChooserDialogClass;
+typedef struct _ThunarChooserDialog      ThunarChooserDialog;
+
+#define THUNAR_TYPE_CHOOSER_DIALOG            (thunar_chooser_dialog_get_type ())
+#define THUNAR_CHOOSER_DIALOG(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), THUNAR_TYPE_CHOOSER_DIALOG, ThunarChooserDialog))
+#define THUNAR_CHOOSER_DIALOG_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), THUNAR_TYPE_CHOOSER_DIALOG, ThunarChooserDialogClass))
+#define THUNAR_IS_CHOOSER_DIALOG(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), THUNAR_TYPE_CHOOSER_DIALOG))
+#define THUNAR_IS_CHOOSER_DIALOG_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), THUNAR_TYPE_CHOOSER_DIALOG))
+#define THUNAR_CHOOSER_DIALOG_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), THUNAR_TYPE_CHOOSER_DIALOG, ThunarChooserDialogClass))
+
+GType       thunar_chooser_dialog_get_type  (void) G_GNUC_CONST;
+
+GtkWidget  *thunar_chooser_dialog_new       (GtkWindow           *parent,
+                                             ThunarFile          *file,
+                                             gboolean             open) G_GNUC_MALLOC;
+
+ThunarFile *thunar_chooser_dialog_get_file  (ThunarChooserDialog *dialog);
+void        thunar_chooser_dialog_set_file  (ThunarChooserDialog *dialog,
+                                             ThunarFile          *file);
+
+gboolean    thunar_chooser_dialog_get_open  (ThunarChooserDialog *dialog);
+void        thunar_chooser_dialog_set_open  (ThunarChooserDialog *dialog,
+                                             gboolean             open);
+
+G_END_DECLS;
+
+#endif /* !__THUNAR_CHOOSER_DIALOG_H__ */
diff --git a/thunar/thunar-chooser-model.c b/thunar/thunar-chooser-model.c
new file mode 100644 (file)
index 0000000..599c3db
--- /dev/null
@@ -0,0 +1,599 @@
+/* $Id$ */
+/*-
+ * Copyright (c) 2005 Benedikt Meurer <benny@xfce.org>.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program; if not, write to the Free Software Foundation, Inc., 59 Temple
+ * Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#ifdef HAVE_MEMORY_H
+#include <memory.h>
+#endif
+#ifdef HAVE_STRING_H
+#include <string.h>
+#endif
+
+#include <thunar/thunar-chooser-model.h>
+#include <thunar/thunar-icon-factory.h>
+
+
+
+/* Property identifiers */
+enum
+{
+  PROP_0,
+  PROP_LOADING,
+  PROP_MIME_INFO,
+};
+
+
+
+static void     thunar_chooser_model_class_init     (ThunarChooserModelClass  *klass);
+static void     thunar_chooser_model_init           (ThunarChooserModel       *model);
+static void     thunar_chooser_model_finalize       (GObject                  *object);
+static void     thunar_chooser_model_get_property   (GObject                  *object,
+                                                     guint                     prop_id,
+                                                     GValue                   *value,
+                                                     GParamSpec               *pspec);
+static void     thunar_chooser_model_set_property   (GObject                  *object,
+                                                     guint                     prop_id,
+                                                     const GValue             *value,
+                                                     GParamSpec               *pspec);
+static void     thunar_chooser_model_append         (ThunarChooserModel       *model,
+                                                     const gchar              *title,
+                                                     const gchar              *icon_name,
+                                                     GList                    *applications);
+static void     thunar_chooser_model_import         (ThunarChooserModel       *model,
+                                                     GList                    *applications);
+static GList   *thunar_chooser_model_readdir        (ThunarChooserModel       *model,
+                                                     const gchar              *absdir,
+                                                     const gchar              *reldir);
+static gpointer thunar_chooser_model_thread         (gpointer                  user_data);
+static gboolean thunar_chooser_model_timer          (gpointer                  user_data);
+static void     thunar_chooser_model_timer_destroy  (gpointer                  user_data);
+
+
+
+
+struct _ThunarChooserModelClass
+{
+  GtkTreeStoreClass __parent__;
+};
+
+struct _ThunarChooserModel
+{
+  GtkTreeStore __parent__;
+
+  ThunarVfsMimeInfo *mime_info;
+
+  /* thread interaction */
+  gint               timer_id;
+  GThread           *thread;
+  volatile gboolean  finished;
+  volatile gboolean  cancelled;
+};
+
+
+
+G_DEFINE_TYPE (ThunarChooserModel, thunar_chooser_model, GTK_TYPE_TREE_STORE);
+
+
+
+static void
+thunar_chooser_model_class_init (ThunarChooserModelClass *klass)
+{
+  GObjectClass *gobject_class;
+
+  gobject_class = G_OBJECT_CLASS (klass);
+  gobject_class->finalize = thunar_chooser_model_finalize;
+  gobject_class->get_property = thunar_chooser_model_get_property;
+  gobject_class->set_property = thunar_chooser_model_set_property;
+
+  /**
+   * ThunarChooserModel::loading:
+   *
+   * Whether the contents of the #ThunarChooserModel are
+   * currently being loaded from disk.
+   **/
+  g_object_class_install_property (gobject_class,
+                                   PROP_LOADING,
+                                   g_param_spec_boolean ("loading",
+                                                         _("Loading"),
+                                                         _("Whether the model is currently being loaded"),
+                                                         FALSE,
+                                                         EXO_PARAM_READABLE));
+
+  /**
+   * ThunarChooserModel::mime-info:
+   *
+   * The #ThunarVfsMimeInfo info for the model.
+   **/
+  g_object_class_install_property (gobject_class,
+                                   PROP_MIME_INFO,
+                                   g_param_spec_boxed ("mime-info",
+                                                       _("Mime info"),
+                                                       _("The mime info for the model"),
+                                                       THUNAR_VFS_TYPE_MIME_INFO,
+                                                       G_PARAM_CONSTRUCT_ONLY | EXO_PARAM_READWRITE));
+}
+
+
+
+static void
+thunar_chooser_model_init (ThunarChooserModel *model)
+{
+  /* allocate the types array for the columns */
+  GType column_types[THUNAR_CHOOSER_MODEL_N_COLUMNS] =
+  {
+    G_TYPE_STRING,
+    GDK_TYPE_PIXBUF,
+    THUNAR_VFS_TYPE_MIME_APPLICATION,
+    PANGO_TYPE_STYLE,
+    G_TYPE_BOOLEAN,
+  };
+
+  /* register the column types */
+  gtk_tree_store_set_column_types (GTK_TREE_STORE (model), G_N_ELEMENTS (column_types), column_types);
+
+  /* start to load the applications installed on the system */
+  model->thread = g_thread_create (thunar_chooser_model_thread, model, TRUE, NULL);
+
+  /* start the timer to monitor the thread */
+  model->timer_id = g_timeout_add_full (G_PRIORITY_LOW, 200, thunar_chooser_model_timer,
+                                        model, thunar_chooser_model_timer_destroy);
+}
+
+
+
+static void
+thunar_chooser_model_finalize (GObject *object)
+{
+  ThunarChooserModel *model = THUNAR_CHOOSER_MODEL (object);
+  GList              *applications;
+
+  /* drop any pending timer */
+  if (G_UNLIKELY (model->timer_id >= 0))
+    g_source_remove (model->timer_id);
+
+  /* cancel the thread (if running) */
+  if (G_UNLIKELY (model->thread != NULL))
+    {
+      /* set the cancellation flag */
+      model->cancelled = TRUE;
+
+      /* join the thread */
+      applications = g_thread_join (model->thread);
+
+      /* ditch the returned application list */
+      g_list_foreach (applications, (GFunc) thunar_vfs_mime_application_unref, NULL);
+      g_list_free (applications);
+    }
+
+  /* drop the mime info (if any) */
+  if (G_LIKELY (model->mime_info != NULL))
+    thunar_vfs_mime_info_unref (model->mime_info);
+
+  (*G_OBJECT_CLASS (thunar_chooser_model_parent_class)->finalize) (object);
+}
+
+
+
+static void
+thunar_chooser_model_get_property (GObject    *object,
+                                   guint       prop_id,
+                                   GValue     *value,
+                                   GParamSpec *pspec)
+{
+  ThunarChooserModel *model = THUNAR_CHOOSER_MODEL (object);
+
+  switch (prop_id)
+    {
+    case PROP_LOADING:
+      g_value_set_boolean (value, thunar_chooser_model_get_loading (model));
+      break;
+
+    case PROP_MIME_INFO:
+      g_value_set_boxed (value, thunar_chooser_model_get_mime_info (model));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+    }
+}
+
+
+
+static void
+thunar_chooser_model_set_property (GObject      *object,
+                                   guint         prop_id,
+                                   const GValue *value,
+                                   GParamSpec   *pspec)
+{
+  ThunarChooserModel *model = THUNAR_CHOOSER_MODEL (object);
+
+  switch (prop_id)
+    {
+    case PROP_MIME_INFO:
+      if (G_LIKELY (model->mime_info == NULL))
+        model->mime_info = g_value_dup_boxed (value);
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+    }
+}
+
+
+
+static void
+thunar_chooser_model_append (ThunarChooserModel *model,
+                             const gchar        *title,
+                             const gchar        *icon_name,
+                             GList              *applications)
+{
+  ThunarIconFactory *icon_factory;
+  GtkIconTheme      *icon_theme;
+  GtkTreeIter        parent_iter;
+  GtkTreeIter        child_iter;
+  GdkPixbuf         *icon;
+  GList             *lp;
+
+  g_return_if_fail (THUNAR_IS_CHOOSER_MODEL (model));
+  g_return_if_fail (title != NULL);
+  g_return_if_fail (icon_name != NULL);
+
+  /* query the default icon theme/factory */
+  icon_theme = gtk_icon_theme_get_default ();
+  icon_factory = thunar_icon_factory_get_for_icon_theme (icon_theme);
+
+  /* insert the section title */
+  icon = gtk_icon_theme_load_icon (icon_theme, icon_name, 24, 0, NULL);
+  gtk_tree_store_append (GTK_TREE_STORE (model), &parent_iter, NULL);
+  gtk_tree_store_set (GTK_TREE_STORE (model), &parent_iter,
+                      THUNAR_CHOOSER_MODEL_COLUMN_NAME, title,
+                      THUNAR_CHOOSER_MODEL_COLUMN_ICON, icon,
+                      -1);
+  if (G_LIKELY (icon != NULL))
+    g_object_unref (G_OBJECT (icon));
+
+  /* check if we have any program items */
+  if (G_LIKELY (applications != NULL))
+    {
+      /* insert the program items */
+      for (lp = applications; lp != NULL; lp = lp->next)
+        {
+          /* determine the icon name for the application */
+          icon_name = thunar_vfs_mime_application_lookup_icon_name (lp->data, icon_theme);
+
+          /* try to load the themed icon for the program */
+          icon = thunar_icon_factory_load_icon (icon_factory, icon_name, 24, NULL, FALSE);
+
+          /* append the tree row with the program data */
+          gtk_tree_store_append (GTK_TREE_STORE (model), &child_iter, &parent_iter);
+          gtk_tree_store_set (GTK_TREE_STORE (model), &child_iter,
+                              THUNAR_CHOOSER_MODEL_COLUMN_NAME, thunar_vfs_mime_application_get_name (lp->data),
+                              THUNAR_CHOOSER_MODEL_COLUMN_ICON, icon,
+                              THUNAR_CHOOSER_MODEL_COLUMN_APPLICATION, lp->data,
+                              -1);
+
+          /* release the icon (if any) */
+          if (G_LIKELY (icon != NULL))
+            g_object_unref (G_OBJECT (icon));
+        }
+    }
+  else
+    {
+      /* tell the user that we don't have any applications for this category */
+      gtk_tree_store_append (GTK_TREE_STORE (model), &child_iter, &parent_iter);
+      gtk_tree_store_set (GTK_TREE_STORE (model), &child_iter,
+                          THUNAR_CHOOSER_MODEL_COLUMN_NAME, _("None available"),
+                          THUNAR_CHOOSER_MODEL_COLUMN_STYLE, PANGO_STYLE_ITALIC,
+                          THUNAR_CHOOSER_MODEL_COLUMN_STYLE_SET, TRUE,
+                          -1);
+    }
+
+  /* cleanup */
+  g_object_unref (G_OBJECT (icon_factory));
+}
+
+
+
+static void
+thunar_chooser_model_import (ThunarChooserModel *model,
+                             GList              *applications)
+{
+  ThunarVfsMimeDatabase *mime_database;
+  GList                 *recommended;
+  GList                 *other = NULL;
+  GList                 *lp;
+  GList                 *p;
+
+  g_return_if_fail (THUNAR_IS_CHOOSER_MODEL (model));
+  g_return_if_fail (model->mime_info != NULL);
+
+  /* determine the recommended applications for the mime info */
+  mime_database = thunar_vfs_mime_database_get_default ();
+  recommended = thunar_vfs_mime_database_get_applications (mime_database, model->mime_info);
+  g_object_unref (G_OBJECT (mime_database));
+
+  /* add all applications to other that are not already on recommended */
+  for (lp = applications; lp != NULL; lp = lp->next)
+    {
+      /* check if this application is already on the recommended list */
+      for (p = recommended; p != NULL; p = p->next)
+        if (thunar_vfs_mime_application_equal (p->data, lp->data))
+          break;
+
+      /* add to the list of other applications if it's not on recommended */
+      if (G_LIKELY (p == NULL))
+        other = g_list_append (other, lp->data);
+    }
+
+  /* append the "Recommended Applications:" category */
+  thunar_chooser_model_append (model, _("Recommended Applications:"), "gnome-settings-default-applications", recommended);
+
+  /* append the "Other Applications:" category */
+  thunar_chooser_model_append (model, _("Other Applications:"), "gnome-applications", other);
+
+  /* cleanup */
+  g_list_foreach (recommended, (GFunc) thunar_vfs_mime_application_unref, NULL);
+  g_list_free (recommended);
+  g_list_free (other);
+}
+
+
+
+static GList*
+thunar_chooser_model_readdir (ThunarChooserModel *model,
+                              const gchar        *absdir,
+                              const gchar        *reldir)
+{
+  ThunarVfsMimeApplication *application;
+  const gchar              *name;
+  GList                    *applications = NULL;
+  gchar                    *abspath;
+  gchar                    *relpath;
+  gchar                    *p;
+  GDir                     *dp;
+
+  g_return_val_if_fail (THUNAR_IS_CHOOSER_MODEL (model), NULL);
+  g_return_val_if_fail (reldir == NULL || !g_path_is_absolute (reldir), NULL);
+  g_return_val_if_fail (g_path_is_absolute (absdir), NULL);
+
+  /* try to open the directory */
+  dp = g_dir_open (absdir, 0, NULL);
+  if (G_LIKELY (dp != NULL))
+    {
+      /* process the files within the directory */
+      while (!model->cancelled)
+        {
+          /* read the next file entry */
+          name = g_dir_read_name (dp);
+          if (G_UNLIKELY (name == NULL))
+            break;
+
+          /* generate the absolute path to the file entry */
+          abspath = g_build_filename (absdir, name, NULL);
+
+          /* generate the relative path to the file entry */
+          relpath = (reldir != NULL) ? g_build_filename (reldir, name, NULL) : g_strdup (name);
+
+          /* check if we have a directory or a regular file here */
+          if (g_file_test (abspath, G_FILE_TEST_IS_DIR))
+            {
+              /* recurse for directories */
+              applications = g_list_concat (applications, thunar_chooser_model_readdir (model, abspath, relpath));
+            }
+          else if (g_file_test (abspath, G_FILE_TEST_IS_REGULAR) && g_str_has_suffix (name, ".desktop"))
+            {
+              /* generate the desktop-id from the relative path */
+              for (p = relpath; *p != '\0'; ++p)
+                if (*p == G_DIR_SEPARATOR)
+                  *p = '-';
+
+              /* try to load the application for the given desktop-id */
+              application = thunar_vfs_mime_application_new_from_file (abspath, relpath);
+
+              /* check if we have successfully loaded the application */
+              if (G_LIKELY (application != NULL))
+                {
+                  /* check if the application supports atleast one mime-type */
+                  if (thunar_vfs_mime_application_get_mime_types (application) != NULL)
+                    applications = g_list_append (applications, application);
+                  else
+                    thunar_vfs_mime_application_unref (application);
+                }
+            }
+
+          /* cleanup */
+          g_free (abspath);
+          g_free (relpath);
+        }
+
+      /* close the directory handle */
+      g_dir_close (dp);
+    }
+
+  return applications;
+}
+
+
+
+static gint
+compare_application_by_name (gconstpointer a,
+                             gconstpointer b)
+{
+  return strcmp (thunar_vfs_mime_application_get_name (a),
+                 thunar_vfs_mime_application_get_name (b));
+}
+
+
+
+static gpointer
+thunar_chooser_model_thread (gpointer user_data)
+{
+  ThunarChooserModel *model = THUNAR_CHOOSER_MODEL (user_data);
+  GList              *applications = NULL;
+  GList              *list;
+  GList              *lp;
+  GList              *p;
+  gchar             **paths;
+  guint               n;
+
+  /* determine the available applications/ directories */
+  paths = xfce_resource_lookup_all (XFCE_RESOURCE_DATA, "applications/");
+  for (n = 0; !model->cancelled && paths[n] != NULL; ++n)
+    {
+      /* lookup the applications in this directory */
+      list = thunar_chooser_model_readdir (model, paths[n], NULL);
+
+      /* merge the applications with what we already have */
+      for (lp = list; lp != NULL; lp = lp->next)
+        {
+          /* ignore hidden applications to be compatible with the Nautilus mess */
+          if ((thunar_vfs_mime_application_get_flags (lp->data) & THUNAR_VFS_MIME_APPLICATION_HIDDEN) != 0)
+            {
+              thunar_vfs_mime_application_unref (lp->data);
+              continue;
+            }
+
+          /* check if we have that application already */
+          for (p = applications; p != NULL; p = p->next)
+            {
+              /* compare the desktop-ids */
+              if (thunar_vfs_mime_application_equal (p->data, lp->data))
+                break;
+            }
+
+          /* no need to add if we have it already */
+          if (G_UNLIKELY (p != NULL))
+            {
+              thunar_vfs_mime_application_unref (lp->data);
+              continue;
+            }
+
+          /* insert the application into the list */
+          applications = g_list_insert_sorted (applications, lp->data, compare_application_by_name);
+        }
+      
+      /* free the temporary list */
+      g_list_free (list);
+    }
+  g_strfreev (paths);
+
+  /* tell the model that we're done */
+  model->finished = TRUE;
+
+  return applications;
+}
+
+
+
+static gboolean
+thunar_chooser_model_timer (gpointer user_data)
+{
+  ThunarChooserModel *model = THUNAR_CHOOSER_MODEL (user_data);
+  gboolean            finished;
+  GList              *applications;
+
+  /* check if the applications are loaded */
+  GDK_THREADS_ENTER ();
+  finished = model->finished;
+  if (G_LIKELY (finished))
+    {
+      /* grab the application list from the thread */
+      applications = g_thread_join (model->thread);
+      model->thread = NULL;
+
+      /* process the applications list */
+      thunar_chooser_model_import (model, applications);
+
+      /* tell everybody that the model is loaded */
+      g_object_notify (G_OBJECT (model), "loading");
+
+      /* free the application list */
+      g_list_foreach (applications, (GFunc) thunar_vfs_mime_application_unref, NULL);
+      g_list_free (applications);
+    }
+  GDK_THREADS_LEAVE ();
+
+  return !finished;
+}
+
+
+
+static void
+thunar_chooser_model_timer_destroy (gpointer user_data)
+{
+  THUNAR_CHOOSER_MODEL (user_data)->timer_id = -1;
+}
+
+
+
+/**
+ * thunar_chooser_model_new:
+ * @mime_info : a #ThunarVfsMimeInfo.
+ *
+ * Allocates a new #ThunarChooserModel for @mime_info.
+ *
+ * Return value: the newly allocated #ThunarChooserModel.
+ **/
+ThunarChooserModel*
+thunar_chooser_model_new (ThunarVfsMimeInfo *mime_info)
+{
+  return g_object_new (THUNAR_TYPE_CHOOSER_MODEL,
+                       "mime-info", mime_info,
+                       NULL);
+}
+
+
+
+/**
+ * thunar_chooser_model_get_loading:
+ * @model : a #ThunarChooserModel.
+ *
+ * Returns %TRUE if @model is currently being loaded.
+ *
+ * Return value: %TRUE if @model is currently being loaded.
+ **/
+gboolean
+thunar_chooser_model_get_loading (ThunarChooserModel *model)
+{
+  g_return_val_if_fail (THUNAR_IS_CHOOSER_MODEL (model), FALSE);
+  return (model->thread != NULL);
+}
+
+
+
+/**
+ * thunar_chooser_model_get_mime_info:
+ * @model : a #ThunarChooserModel.
+ *
+ * Returns the #ThunarVfsMimeInfo for @model.
+ *
+ * Return value: the #ThunarVfsMimeInfo for @model.
+ **/
+ThunarVfsMimeInfo*
+thunar_chooser_model_get_mime_info (ThunarChooserModel *model)
+{
+  g_return_val_if_fail (THUNAR_IS_CHOOSER_MODEL (model), NULL);
+  return model->mime_info;
+}
+
diff --git a/thunar/thunar-chooser-model.h b/thunar/thunar-chooser-model.h
new file mode 100644 (file)
index 0000000..74dd8fa
--- /dev/null
@@ -0,0 +1,68 @@
+/* $Id$ */
+/*-
+ * Copyright (c) 2005 Benedikt Meurer <benny@xfce.org>.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program; if not, write to the Free Software Foundation, Inc., 59 Temple
+ * Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#ifndef __THUNAR_CHOOSER_MODEL_H__
+#define __THUNAR_CHOOSER_MODEL_H__
+
+#include <thunar-vfs/thunar-vfs.h>
+
+G_BEGIN_DECLS;
+
+typedef struct _ThunarChooserModelClass ThunarChooserModelClass;
+typedef struct _ThunarChooserModel      ThunarChooserModel;
+
+#define THUNAR_TYPE_CHOOSER_MODEL            (thunar_chooser_model_get_type ())
+#define THUNAR_CHOOSER_MODEL(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), THUNAR_TYPE_CHOOSER_MODEL, ThunarChooserModel))
+#define THUNAR_CHOOSER_MODEL_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), THUNAR_TYPE_CHOOSER_MODEL, ThunarChooserModelClass))
+#define THUNAR_IS_CHOOSER_MODEL(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), THUNAR_TYPE_CHOOSER_MODEL))
+#define THUNAR_IS_CHOOSER_MODEL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), THUNAR_TYPE_CHOOSER_MODEL))
+#define THUNAR_CHOOSER_MODEL_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), THUNAR_TYPE_CHOOSER_MODEL, ThunarChooserModelClass))
+
+/**
+ * ThunarChooserModelColumn:
+ * @THUNAR_CHOOSER_MODEL_COLUMN_NAME        : the name of the application.
+ * @THUNAR_CHOOSER_MODEL_COLUMN_ICON        : the icon of the application.
+ * @THUNAR_CHOOSER_MODEL_COLUMN_APPLICATION : the #ThunarVfsMimeApplication object.
+ * @THUNAR_CHOOSER_MODEL_COLUMN_STYLE       : custom font style.
+ * @THUNAR_CHOOSER_MODEL_COLUMN_STYLE_SET   : whether to actually use the custom font style.
+ * @THUNAR_CHOOSER_MODEL_N_COLUMNS          : the number of columns in #ThunarChooserModel.
+ *
+ * The identifiers for the columns provided by the #ThunarChooserModel.
+ **/
+typedef enum
+{
+  THUNAR_CHOOSER_MODEL_COLUMN_NAME,
+  THUNAR_CHOOSER_MODEL_COLUMN_ICON,
+  THUNAR_CHOOSER_MODEL_COLUMN_APPLICATION,
+  THUNAR_CHOOSER_MODEL_COLUMN_STYLE,
+  THUNAR_CHOOSER_MODEL_COLUMN_STYLE_SET,
+  THUNAR_CHOOSER_MODEL_N_COLUMNS,
+} ThunarChooserModelColumn;
+
+GType               thunar_chooser_model_get_type       (void) G_GNUC_CONST;
+
+ThunarChooserModel *thunar_chooser_model_new            (ThunarVfsMimeInfo  *mime_info) G_GNUC_MALLOC;
+
+gboolean            thunar_chooser_model_get_loading    (ThunarChooserModel *model);
+
+ThunarVfsMimeInfo  *thunar_chooser_model_get_mime_info  (ThunarChooserModel *model);
+
+G_END_DECLS;
+
+#endif /* !__THUNAR_CHOOSER_MODEL_H__ */
index e2669f1..f455b61 100644 (file)
@@ -28,9 +28,6 @@
 #ifdef HAVE_STDLIB_H
 #include <stdlib.h>
 #endif
-#ifdef HAVE_UNISTD_H
-#include <unistd.h>
-#endif
 
 #include <thunar/thunar-favourites-model.h>
 #include <thunar/thunar-file.h>
@@ -623,7 +620,9 @@ thunar_favourites_model_drag_data_get (GtkTreeDragSource *source,
                                        GtkTreePath       *path,
                                        GtkSelectionData  *selection_data)
 {
-  // FIXME
+  /* we simply return FALSE here, as the drag handling is done in
+   * the ThunarFavouritesView class.
+   */
   return FALSE;
 }
 
@@ -633,9 +632,10 @@ static gboolean
 thunar_favourites_model_drag_data_delete (GtkTreeDragSource *source,
                                           GtkTreePath       *path)
 {
-  // we simply return FALSE here, as this function can only be
-  // called if the user is re-arranging favourites within the
-  // model, which will be handle by the exchange method
+  /* we simply return FALSE here, as this function can only be
+   * called if the user is re-arranging favourites within the
+   * model, which will be handle by the exchange method.
+   */
   return FALSE;
 }
 
@@ -817,17 +817,15 @@ thunar_favourites_model_save (ThunarFavouritesModel *model)
   gchar           *tmp_path;
   gchar           *uri;
   GList           *lp;
-  FILE            *fp = NULL;
+  FILE            *fp;
+  gint             fd;
 
   g_return_if_fail (THUNAR_IS_FAVOURITES_MODEL (model));
 
   /* open a temporary file for writing */
-  tmp_path = g_strdup_printf ("%s%c.gtk-bookmarks.tmp-%d",
-                              xfce_get_homedir (),
-                              G_DIR_SEPARATOR,
-                              (gint) getpid ());
-  fp = fopen (tmp_path, "w");
-  if (G_UNLIKELY (fp == NULL))
+  tmp_path = xfce_get_homefile (".gtk-bookmarks.XXXXXX", NULL);
+  fd = g_mkstemp (tmp_path);
+  if (G_UNLIKELY (fd < 0))
     {
       g_warning ("Failed to open `%s' for writing: %s",
                  tmp_path, g_strerror (errno));
@@ -836,6 +834,7 @@ thunar_favourites_model_save (ThunarFavouritesModel *model)
     }
 
   /* write the uris of user customizable favourites */
+  fp = fdopen (fd, "w");
   for (lp = model->favourites; lp != NULL; lp = lp->next)
     {
       favourite = THUNAR_FAVOURITE (lp->data);
index eb60804..964063c 100644 (file)
@@ -22,6 +22,7 @@
 #endif
 
 #include <thunar/thunar-application.h>
+#include <thunar/thunar-chooser-dialog.h>
 #include <thunar/thunar-launcher.h>
 #include <thunar/thunar-open-with-action.h>
 
@@ -67,6 +68,8 @@ static void thunar_launcher_open_new_windows          (ThunarLauncher
 static void thunar_launcher_update                    (ThunarLauncher           *launcher);
 static void thunar_launcher_action_open               (GtkAction                *action,
                                                        ThunarLauncher           *launcher);
+static void thunar_launcher_action_open_with          (GtkAction                *action,
+                                                       ThunarLauncher           *launcher);
 static void thunar_launcher_action_open_application   (GtkAction                *action,
                                                        ThunarVfsMimeApplication *application,
                                                        GList                    *uri_list,
@@ -192,6 +195,7 @@ thunar_launcher_init (ThunarLauncher *launcher)
   /* the "Open with" action */
   launcher->action_open_with = thunar_open_with_action_new ("open-with", _("Open With"));
   g_signal_connect (G_OBJECT (launcher->action_open_with), "open-application", G_CALLBACK (thunar_launcher_action_open_application), launcher);
+  g_signal_connect (G_OBJECT (launcher->action_open_with), "activate", G_CALLBACK (thunar_launcher_action_open_with), launcher);
 }
 
 
@@ -378,7 +382,7 @@ thunar_launcher_open_files (ThunarLauncher *launcher,
   ThunarVfsMimeInfo        *info;
   GHashTable               *applications;
   GtkWidget                *window;
-  GtkWidget                *message;
+  GtkWidget                *dialog;
   GList                    *uri_list;
   GList                    *lp;
 
@@ -393,11 +397,12 @@ thunar_launcher_open_files (ThunarLauncher *launcher,
 
   for (lp = files; lp != NULL; lp = lp->next)
     {
-      /* determine the MIME type for the file */
-      info = thunar_file_get_mime_info (lp->data);
-
       /* determine the default application for the MIME type */
+      info = thunar_file_get_mime_info (lp->data);
       application = thunar_vfs_mime_database_get_default_application (database, info);
+      thunar_vfs_mime_info_unref (info);
+
+      /* check if we have an application here */
       if (G_LIKELY (application != NULL))
         {
           /* check if we have that application already */
@@ -417,20 +422,11 @@ thunar_launcher_open_files (ThunarLauncher *launcher,
       else
         {
           window = (launcher->widget != NULL) ? gtk_widget_get_toplevel (launcher->widget) : NULL;
-          message = gtk_message_dialog_new ((GtkWindow *) window, 
-                                            GTK_DIALOG_MODAL
-                                            | GTK_DIALOG_DESTROY_WITH_PARENT,
-                                            GTK_MESSAGE_ERROR,
-                                            GTK_BUTTONS_OK,
-                                            _("No application available to open \"%s\".\n"
-                                              "This will be fixed soon!"),
-                                            thunar_file_get_display_name (lp->data));
-          gtk_dialog_run (GTK_DIALOG (message));
-          gtk_widget_destroy (message);
+          dialog = thunar_chooser_dialog_new ((GtkWindow *) window, lp->data, TRUE);
+          gtk_dialog_run (GTK_DIALOG (dialog));
+          gtk_widget_destroy (dialog);
+          break;
         }
-
-      /* cleanup */
-      thunar_vfs_mime_info_unref (info);
     }
 
   /* run all collected applications */
@@ -613,6 +609,30 @@ thunar_launcher_action_open (GtkAction      *action,
 
 
 static void
+thunar_launcher_action_open_with (GtkAction      *action,
+                                  ThunarLauncher *launcher)
+{
+  ThunarFile *file;
+  GtkWidget  *dialog;
+  GtkWidget  *window;
+
+  g_return_if_fail (GTK_IS_ACTION (action));
+  g_return_if_fail (THUNAR_IS_LAUNCHER (launcher));
+
+  /* determine the first selected file */
+  file = g_list_nth_data (launcher->selected_files, 0);
+  if (G_UNLIKELY (file == NULL))
+    return;
+
+  window = (launcher->widget != NULL) ? gtk_widget_get_toplevel (launcher->widget) : NULL;
+  dialog = thunar_chooser_dialog_new ((GtkWindow *) window, file, TRUE);
+  gtk_dialog_run (GTK_DIALOG (dialog));
+  gtk_widget_destroy (dialog);
+}
+
+
+
+static void
 thunar_launcher_action_open_application (GtkAction                *action,
                                          ThunarVfsMimeApplication *application,
                                          GList                    *uri_list,