Index: thunar/thunar-chooser-dialog.c =================================================================== --- thunar/thunar-chooser-dialog.c (revision 21698) +++ thunar/thunar-chooser-dialog.c (working copy) @@ -48,42 +48,51 @@ -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_finalize (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); -static void thunar_chooser_dialog_selection_changed (GtkTreeSelection *selection, - ThunarChooserDialog *dialog); +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_finalize (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 gboolean thunar_chooser_dialog_context_menu (ThunarChooserDialog *dialog, + guint button, + guint32 time); +static void thunar_chooser_dialog_update_accept (ThunarChooserDialog *dialog); +static void thunar_chooser_dialog_update_header (ThunarChooserDialog *dialog); +static void thunar_chooser_dialog_action_remove (ThunarChooserDialog *dialog); +static void thunar_chooser_dialog_browse_clicked (GtkWidget *button, + ThunarChooserDialog *dialog); +static gboolean thunar_chooser_dialog_button_press_event (GtkWidget *tree_view, + GdkEventButton *event, + 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 gboolean thunar_chooser_dialog_popup_menu (GtkWidget *tree_view, + ThunarChooserDialog *dialog); +static void thunar_chooser_dialog_row_activated (GtkTreeView *treeview, + GtkTreePath *path, + GtkTreeViewColumn *column, + ThunarChooserDialog *dialog); +static void thunar_chooser_dialog_selection_changed (GtkTreeSelection *selection, + ThunarChooserDialog *dialog); @@ -253,6 +262,8 @@ /* 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), "button-press-event", G_CALLBACK (thunar_chooser_dialog_button_press_event), dialog); + g_signal_connect (G_OBJECT (dialog->tree_view), "popup-menu", G_CALLBACK (thunar_chooser_dialog_popup_menu), dialog); 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); @@ -298,7 +309,7 @@ /* create the "Custom command" button */ dialog->custom_button = gtk_button_new_with_mnemonic (_("_Browse...")); - g_signal_connect (G_OBJECT (dialog->custom_button), "clicked", G_CALLBACK (thunar_chooser_dialog_browse), dialog); + g_signal_connect (G_OBJECT (dialog->custom_button), "clicked", G_CALLBACK (thunar_chooser_dialog_browse_clicked), dialog); gtk_box_pack_start (GTK_BOX (hbox), dialog->custom_button, FALSE, FALSE, 0); gtk_widget_show (dialog->custom_button); @@ -572,6 +583,68 @@ +static gboolean +thunar_chooser_dialog_context_menu (ThunarChooserDialog *dialog, + guint button, + guint32 time) +{ + ThunarVfsMimeApplication *mime_application; + GtkTreeSelection *selection; + GtkTreeModel *model; + GtkTreeIter iter; + GtkWidget *image; + GtkWidget *item; + GtkWidget *menu; + GMainLoop *loop; + + g_return_val_if_fail (THUNAR_IS_CHOOSER_DIALOG (dialog), FALSE); + + /* determine the selected row */ + selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (dialog->tree_view)); + if (!gtk_tree_selection_get_selected (selection, &model, &iter)) + return FALSE; + + /* determine the mime application for the row */ + gtk_tree_model_get (model, &iter, THUNAR_CHOOSER_MODEL_COLUMN_APPLICATION, &mime_application, -1); + if (G_UNLIKELY (mime_application == NULL)) + return FALSE; + + /* prepare the internal loop */ + loop = g_main_loop_new (NULL, FALSE); + + /* prepare the popup menu */ + menu = gtk_menu_new (); + gtk_menu_set_screen (GTK_MENU (menu), gtk_widget_get_screen (GTK_WIDGET (dialog))); + g_signal_connect_swapped (G_OBJECT (menu), "deactivate", G_CALLBACK (g_main_loop_quit), loop); + exo_gtk_object_ref_sink (GTK_OBJECT (menu)); + + /* append the "Remove Launcher" item */ + item = gtk_image_menu_item_new_with_mnemonic (_("_Remove Launcher")); + gtk_widget_set_sensitive (item, thunar_vfs_mime_application_is_usercreated (mime_application)); + g_signal_connect_swapped (G_OBJECT (item), "activate", G_CALLBACK (thunar_chooser_dialog_action_remove), dialog); + gtk_menu_shell_append (GTK_MENU_SHELL (menu), item); + gtk_widget_show (item); + + image = gtk_image_new_from_stock (GTK_STOCK_REMOVE, GTK_ICON_SIZE_MENU); + gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image); + gtk_widget_show (image); + + /* run the internal loop */ + gtk_grab_add (menu); + gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL, button, time); + g_main_loop_run (loop); + gtk_grab_remove (menu); + + /* clean up */ + g_object_unref (G_OBJECT (mime_application)); + g_object_unref (G_OBJECT (menu)); + g_main_loop_unref (loop); + + return TRUE; +} + + + static void thunar_chooser_dialog_update_accept (ThunarChooserDialog *dialog) { @@ -673,9 +746,83 @@ static void -thunar_chooser_dialog_browse (GtkWidget *button, - ThunarChooserDialog *dialog) +thunar_chooser_dialog_action_remove (ThunarChooserDialog *dialog) { + ThunarVfsMimeApplication *mime_application; + GtkTreeSelection *selection; + GtkTreeModel *model; + GtkTreeIter iter; + const gchar *name; + GtkWidget *message; + GError *error = NULL; + gint response; + + g_return_if_fail (THUNAR_IS_CHOOSER_DIALOG (dialog)); + + /* determine the selected row */ + selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (dialog->tree_view)); + if (!gtk_tree_selection_get_selected (selection, &model, &iter)) + return; + + /* determine the mime application for the row */ + gtk_tree_model_get (model, &iter, THUNAR_CHOOSER_MODEL_COLUMN_APPLICATION, &mime_application, -1); + if (G_UNLIKELY (mime_application == NULL)) + return; + + /* verify that the application is usercreated */ + if (thunar_vfs_mime_application_is_usercreated (mime_application)) + { + /* determine the name of the mime application */ + name = thunar_vfs_mime_handler_get_name (THUNAR_VFS_MIME_HANDLER (mime_application)); + + /* ask the user whether to remove the application launcher */ + message = gtk_message_dialog_new (GTK_WINDOW (dialog), + GTK_DIALOG_DESTROY_WITH_PARENT + | GTK_DIALOG_MODAL, + GTK_MESSAGE_QUESTION, + GTK_BUTTONS_NONE, + _("Are you sure that you want to remove \"%s\"?"), name); + gtk_dialog_add_buttons (GTK_DIALOG (message), + GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, + GTK_STOCK_REMOVE, GTK_RESPONSE_YES, + NULL); + gtk_dialog_set_default_response (GTK_DIALOG (message), GTK_RESPONSE_YES); + gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (message), _("This will remove the application launcher that appears in the file " + "context menu, but will not uninstall the application itself.\n\n" + "You can only remove application launchers that were created using " + "the custom command box in the \"Open With\" dialog of the file " + "manager.")); + response = gtk_dialog_run (GTK_DIALOG (message)); + gtk_widget_destroy (message); + + /* check if the user confirmed the removal */ + if (G_LIKELY (response == GTK_RESPONSE_YES)) + { + /* try to delete the application from the model */ + if (!thunar_chooser_model_remove (THUNAR_CHOOSER_MODEL (model), &iter, &error)) + { + /* display an error to the user */ + thunar_dialogs_show_error (dialog, error, _("Failed to remove \"%s\""), name); + g_error_free (error); + } + else if (G_LIKELY (dialog->file != NULL)) + { + /* emit "changed" for the file, so the context menu is updated */ + thunar_file_changed (dialog->file); + } + } + } + + /* cleanup */ + g_object_unref (G_OBJECT (mime_application)); +} + + + +static void +thunar_chooser_dialog_browse_clicked (GtkWidget *button, + ThunarChooserDialog *dialog) +{ GtkFileFilter *filter; GtkWidget *chooser; gchar *filename; @@ -785,6 +932,40 @@ +static gboolean +thunar_chooser_dialog_button_press_event (GtkWidget *tree_view, + GdkEventButton *event, + ThunarChooserDialog *dialog) +{ + GtkTreeSelection *selection; + GtkTreePath *path; + + g_return_val_if_fail (THUNAR_IS_CHOOSER_DIALOG (dialog), FALSE); + g_return_val_if_fail (dialog->tree_view == tree_view, FALSE); + g_return_val_if_fail (GTK_IS_TREE_VIEW (tree_view), FALSE); + + /* check if we should popup the context menu */ + if (G_LIKELY (event->button == 3 && event->type == GDK_BUTTON_PRESS)) + { + /* determine the path for the clicked row */ + if (gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (tree_view), event->x, event->y, &path, NULL, NULL, NULL)) + { + /* be sure to select exactly this row... */ + selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (tree_view)); + gtk_tree_selection_unselect_all (selection); + gtk_tree_selection_select_path (selection, path); + gtk_tree_path_free (path); + + /* ...and popup the context menu */ + return thunar_chooser_dialog_context_menu (dialog, event->button, event->time); + } + } + + return FALSE; +} + + + static void thunar_chooser_dialog_notify_expanded (GtkExpander *expander, GParamSpec *pspec, @@ -840,6 +1021,20 @@ +static gboolean +thunar_chooser_dialog_popup_menu (GtkWidget *tree_view, + ThunarChooserDialog *dialog) +{ + g_return_val_if_fail (THUNAR_IS_CHOOSER_DIALOG (dialog), FALSE); + g_return_val_if_fail (dialog->tree_view == tree_view, FALSE); + g_return_val_if_fail (GTK_IS_TREE_VIEW (tree_view), FALSE); + + /* popup the context menu */ + return thunar_chooser_dialog_context_menu (dialog, 0, gtk_get_current_event_time ()); +} + + + static void thunar_chooser_dialog_row_activated (GtkTreeView *treeview, GtkTreePath *path, Index: thunar/thunar-chooser-model.c =================================================================== --- thunar/thunar-chooser-model.c (revision 21698) +++ thunar/thunar-chooser-model.c (working copy) @@ -630,3 +630,49 @@ return model->mime_info; } + + +/** + * thunar_chooser_model_remove: + * @model : a #ThunarChooserModel. + * @iter : the #GtkTreeIter for the application to remove. + * @error : return location for errors or %NULL. + * + * Tries to remove the application at the specified @iter from + * the systems application database. Returns %TRUE on success, + * otherwise %FALSE is returned. + * + * Return value: %TRUE on success, %FALSE otherwise. + **/ +gboolean +thunar_chooser_model_remove (ThunarChooserModel *model, + GtkTreeIter *iter, + GError **error) +{ + ThunarVfsMimeApplication *mime_application; + ThunarVfsMimeDatabase *mime_database; + gboolean succeed; + + g_return_val_if_fail (THUNAR_IS_CHOOSER_MODEL (model), FALSE); + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + g_return_val_if_fail (gtk_tree_store_iter_is_valid (GTK_TREE_STORE (model), iter), FALSE); + + /* determine the mime application for the iter */ + gtk_tree_model_get (GTK_TREE_MODEL (model), iter, THUNAR_CHOOSER_MODEL_COLUMN_APPLICATION, &mime_application, -1); + if (G_UNLIKELY (mime_application == NULL)) + return TRUE; + + /* try to remove the application from the database */ + mime_database = thunar_vfs_mime_database_get_default (); + succeed = thunar_vfs_mime_database_remove_application (mime_database, mime_application, error); + g_object_unref (G_OBJECT (mime_application)); + g_object_unref (G_OBJECT (mime_database)); + + /* if the removal was successfull, delete the row from the model */ + if (G_LIKELY (succeed)) + gtk_tree_store_remove (GTK_TREE_STORE (model), iter); + + return succeed; +} + + Index: thunar/thunar-chooser-model.h =================================================================== --- thunar/thunar-chooser-model.h (revision 21698) +++ thunar/thunar-chooser-model.h (working copy) @@ -65,6 +65,10 @@ ThunarVfsMimeInfo *thunar_chooser_model_get_mime_info (ThunarChooserModel *model); +gboolean thunar_chooser_model_remove (ThunarChooserModel *model, + GtkTreeIter *iter, + GError **error); + G_END_DECLS; #endif /* !__THUNAR_CHOOSER_MODEL_H__ */ Index: thunar-vfs/thunar-vfs.symbols =================================================================== --- thunar-vfs/thunar-vfs.symbols (revision 21698) +++ thunar-vfs/thunar-vfs.symbols (working copy) @@ -141,11 +141,12 @@ thunar_vfs_mime_application_get_type G_GNUC_CONST thunar_vfs_mime_application_new_from_desktop_id G_GNUC_MALLOC G_GNUC_WARN_UNUSED_RESULT thunar_vfs_mime_application_new_from_file G_GNUC_MALLOC G_GNUC_WARN_UNUSED_RESULT +thunar_vfs_mime_application_is_usercreated G_GNUC_WARN_UNUSED_RESULT thunar_vfs_mime_application_get_actions G_GNUC_MALLOC G_GNUC_WARN_UNUSED_RESULT -thunar_vfs_mime_application_get_desktop_id -thunar_vfs_mime_application_get_mime_types -thunar_vfs_mime_application_hash -thunar_vfs_mime_application_equal +thunar_vfs_mime_application_get_desktop_id G_GNUC_WARN_UNUSED_RESULT +thunar_vfs_mime_application_get_mime_types G_GNUC_WARN_UNUSED_RESULT +thunar_vfs_mime_application_hash G_GNUC_WARN_UNUSED_RESULT +thunar_vfs_mime_application_equal G_GNUC_WARN_UNUSED_RESULT #endif #endif @@ -163,6 +164,7 @@ thunar_vfs_mime_database_get_default_application G_GNUC_WARN_UNUSED_RESULT thunar_vfs_mime_database_set_default_application G_GNUC_WARN_UNUSED_RESULT thunar_vfs_mime_database_add_application G_GNUC_WARN_UNUSED_RESULT +thunar_vfs_mime_database_remove_application G_GNUC_WARN_UNUSED_RESULT #endif #endif Index: thunar-vfs/thunar-vfs-mime-application.c =================================================================== --- thunar-vfs/thunar-vfs-mime-application.c (revision 21698) +++ thunar-vfs/thunar-vfs-mime-application.c (working copy) @@ -361,6 +361,25 @@ /** + * thunar_vfs_mime_application_is_usercreated: + * @mime_application : a #ThunarVfsMimeApplication. + * + * Returns %TRUE if the @mime_application was created by the user + * using a file manager, i.e. through the "Open With" dialog in + * Thunar. + * + * Return value: %TRUE if @mime_application is usercreated. + **/ +gboolean +thunar_vfs_mime_application_is_usercreated (const ThunarVfsMimeApplication *mime_application) +{ + g_return_val_if_fail (THUNAR_VFS_IS_MIME_APPLICATION (mime_application), FALSE); + return (strstr (mime_application->desktop_id, "-usercreated") != NULL); +} + + + +/** * thunar_vfs_mime_application_get_actions: * @mime_application : a #ThunarVfsMimeApplication. * Index: thunar-vfs/thunar-vfs-mime-database.c =================================================================== --- thunar-vfs/thunar-vfs-mime-database.c (revision 21698) +++ thunar-vfs/thunar-vfs-mime-database.c (working copy) @@ -1695,7 +1695,7 @@ fclose (fp); /* update the mimeinfo.cache file for the directory */ - command = g_strdup_printf ("update-desktop-database %s", directory); + command = g_strdup_printf ("update-desktop-database \"%s\"", directory); succeed = g_spawn_command_line_sync (command, NULL, NULL, NULL, error); g_free (command); @@ -1722,5 +1722,86 @@ +/** + * thunar_vfs_mime_database_remove_application: + * @database : a #ThunarVfsMimeDatabase. + * @application : a #ThunarVfsMimeApplication. + * @error : return location for errors or %NULL. + * + * Undoes the effect of thunar_vfs_mime_database_add_application() by removing + * the user created @application from the @database. The @application must be + * user created, and the removal will fail if its not (for example if its a + * system-wide installed application launcher). See the documentation for + * thunar_vfs_mime_application_is_usercreated() for details. + * + * Return value: %TRUE if the removal was successfull, %FALSE otherwise and + * @error will be set. + **/ +gboolean +thunar_vfs_mime_database_remove_application (ThunarVfsMimeDatabase *database, + ThunarVfsMimeApplication *application, + GError **error) +{ + const gchar *desktop_id; + gboolean succeed = FALSE; + gchar *directory; + gchar *command; + gchar *file; + + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + g_return_val_if_fail (THUNAR_VFS_IS_MIME_DATABASE (database), FALSE); + g_return_val_if_fail (THUNAR_VFS_IS_MIME_APPLICATION (application), FALSE); + + /* verify that the application is usercreated */ + if (!thunar_vfs_mime_application_is_usercreated (application)) + { + g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_INVAL, g_strerror (EINVAL)); + return FALSE; + } + + /* determine the desktop-id of the application */ + desktop_id = thunar_vfs_mime_application_get_desktop_id (application); + + /* determine the save location for applications */ + directory = xfce_resource_save_location (XFCE_RESOURCE_DATA, "applications/", TRUE); + if (G_UNLIKELY (directory == NULL)) + { + g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_NOTDIR, g_strerror (ENOTDIR)); + return FALSE; + } + + /* try to delete the desktop file */ + file = g_build_filename (directory, desktop_id, NULL); + if (G_UNLIKELY (g_unlink (file) < 0)) + { + /* tell the user that we failed to delete the application launcher */ + g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_INVAL, _("Failed to remove \"%s\": %s"), file, g_strerror (errno)); + } + else + { + /* 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 we succeed */ + if (G_LIKELY (succeed)) + { + /* clear the application cache */ + g_mutex_lock (database->lock); + g_hash_table_foreach_remove (database->applications, (GHRFunc) exo_noop_true, NULL); + g_mutex_unlock (database->lock); + } + } + + /* cleanup */ + g_free (directory); + g_free (file); + + return succeed; +} + + + #define __THUNAR_VFS_MIME_DATABASE_C__ #include Index: thunar-vfs/thunar-vfs-mime-application.h =================================================================== --- thunar-vfs/thunar-vfs-mime-application.h (revision 21698) +++ thunar-vfs/thunar-vfs-mime-application.h (working copy) @@ -46,13 +46,15 @@ ThunarVfsMimeApplication *thunar_vfs_mime_application_new_from_file (const gchar *path, const gchar *desktop_id) G_GNUC_MALLOC G_GNUC_WARN_UNUSED_RESULT; +gboolean thunar_vfs_mime_application_is_usercreated (const ThunarVfsMimeApplication *mime_application) G_GNUC_WARN_UNUSED_RESULT; + GList *thunar_vfs_mime_application_get_actions (ThunarVfsMimeApplication *mime_application) G_GNUC_MALLOC G_GNUC_WARN_UNUSED_RESULT; -const gchar *thunar_vfs_mime_application_get_desktop_id (const ThunarVfsMimeApplication *mime_application); -const gchar * const *thunar_vfs_mime_application_get_mime_types (const ThunarVfsMimeApplication *mime_application); +const gchar *thunar_vfs_mime_application_get_desktop_id (const ThunarVfsMimeApplication *mime_application) G_GNUC_WARN_UNUSED_RESULT; +const gchar * const *thunar_vfs_mime_application_get_mime_types (const ThunarVfsMimeApplication *mime_application) G_GNUC_WARN_UNUSED_RESULT; -guint thunar_vfs_mime_application_hash (gconstpointer mime_application); +guint thunar_vfs_mime_application_hash (gconstpointer mime_application) G_GNUC_WARN_UNUSED_RESULT; gboolean thunar_vfs_mime_application_equal (gconstpointer a, - gconstpointer b); + gconstpointer b) G_GNUC_WARN_UNUSED_RESULT; /** * thunar_vfs_mime_application_get_command: Index: thunar-vfs/thunar-vfs-mime-database.h =================================================================== --- thunar-vfs/thunar-vfs-mime-database.h (revision 21698) +++ thunar-vfs/thunar-vfs-mime-database.h (working copy) @@ -67,6 +67,9 @@ const gchar *name, const gchar *exec, GError **error) G_GNUC_WARN_UNUSED_RESULT; +gboolean thunar_vfs_mime_database_remove_application (ThunarVfsMimeDatabase *database, + ThunarVfsMimeApplication *application, + GError **error) G_GNUC_WARN_UNUSED_RESULT; G_END_DECLS;