From 2a91e49505a8090ef36f8bdc9384f5343e072866 Mon Sep 17 00:00:00 2001 From: Benedikt Meurer <benny@xfce.org> Date: Thu, 1 Sep 2005 23:40:33 +0000 Subject: [PATCH] 2005-09-01 Benedikt Meurer <benny@xfce.org> * thunar-vfs/thunar-vfs-listdir-job.c: Fix the pre-sorting of files, so upper layers always receive the info list sorted by name. * thunar/thunar-details-view.c(thunar_details_view_button_press_event), thunar/thunar-icon-view.c(thunar_icon_view_button_press_event): Don't popup the context menu immediately on right-clicks, but schedule the menu popup using thunar_standard_view_queue_popup(). * thunar/thunar-standard-view.{c,h}: Add the ability to start a drag operation using the right mouse button. * thunar/thunar-file.{c,h}: Add virtual method accepts_uri_drop() and method thunar_file_accepts_uri_drop(), which are used to determine whether it is possible to drop a certain list of ThunarVfsURIs on a given ThunarFile (using a set of actions specified by the drag source). * thunar/thunar-local-file.c: Implement the accepts_uri_drop() method for local file handling. * thunar/thunar-progress-dialog.c(thunar_progress_dialog_ask), (thunar_progress_dialog_error): Be sure to display the progress dialog prior to opening an error or question dialog. * thunar-vfs/thunar-vfs-info.c(thunar_vfs_info_rename): Fix gcc4 warning. * thunar-vfs/thunar-vfs.symbols: Add missing thunar_vfs_rename symbol. * thunar/thunar-favourites-model.c(thunar_favourites_model_get_value): Work-around a compiler bug with newer gcc versions. * thunar/thunar-standard-view.{c,h}: Turn ThunarStandardView into a valid drop site with support for text/uri-list drops. * thunar/Makefile.am, thunar/thunar-dnd.{c,h}: Add DnD helper functions, which can be used by other modules as well (e.g. for the desktop view). (Old svn revision: 17266) --- ChangeLog | 31 ++ thunar-vfs/thunar-vfs-info.c | 2 +- thunar-vfs/thunar-vfs-listdir-job.c | 13 +- thunar-vfs/thunar-vfs.symbols | 1 + thunar/Makefile.am | 2 + thunar/thunar-details-view.c | 98 +++-- thunar/thunar-dnd.c | 210 ++++++++++ thunar/thunar-dnd.h | 38 ++ thunar/thunar-favourites-model.c | 5 +- thunar/thunar-file.c | 67 ++++ thunar/thunar-file.h | 8 + thunar/thunar-icon-view.c | 104 +++-- thunar/thunar-local-file.c | 28 ++ thunar/thunar-progress-dialog.c | 6 + thunar/thunar-standard-view.c | 603 +++++++++++++++++++++++++--- thunar/thunar-standard-view.h | 45 ++- 16 files changed, 1117 insertions(+), 144 deletions(-) create mode 100644 thunar/thunar-dnd.c create mode 100644 thunar/thunar-dnd.h diff --git a/ChangeLog b/ChangeLog index 4dea705b3..54d1f02a4 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,34 @@ +2005-09-01 Benedikt Meurer <benny@xfce.org> + + * thunar-vfs/thunar-vfs-listdir-job.c: Fix the pre-sorting of files, so + upper layers always receive the info list sorted by name. + * thunar/thunar-details-view.c(thunar_details_view_button_press_event), + thunar/thunar-icon-view.c(thunar_icon_view_button_press_event): Don't + popup the context menu immediately on right-clicks, but schedule the + menu popup using thunar_standard_view_queue_popup(). + * thunar/thunar-standard-view.{c,h}: Add the ability to start a drag + operation using the right mouse button. + * thunar/thunar-file.{c,h}: Add virtual method accepts_uri_drop() and + method thunar_file_accepts_uri_drop(), which are used to determine + whether it is possible to drop a certain list of ThunarVfsURIs on + a given ThunarFile (using a set of actions specified by the drag + source). + * thunar/thunar-local-file.c: Implement the accepts_uri_drop() method + for local file handling. + * thunar/thunar-progress-dialog.c(thunar_progress_dialog_ask), + (thunar_progress_dialog_error): Be sure to display the progress dialog + prior to opening an error or question dialog. + * thunar-vfs/thunar-vfs-info.c(thunar_vfs_info_rename): Fix gcc4 + warning. + * thunar-vfs/thunar-vfs.symbols: Add missing thunar_vfs_rename symbol. + * thunar/thunar-favourites-model.c(thunar_favourites_model_get_value): + Work-around a compiler bug with newer gcc versions. + * thunar/thunar-standard-view.{c,h}: Turn ThunarStandardView into a + valid drop site with support for text/uri-list drops. + * thunar/Makefile.am, thunar/thunar-dnd.{c,h}: Add DnD helper functions, + which can be used by other modules as well (e.g. for the desktop + view). + 2005-08-30 Benedikt Meurer <benny@xfce.org> * thunar/thunar-favourites-model.c: Drop the icon caching from the diff --git a/thunar-vfs/thunar-vfs-info.c b/thunar-vfs/thunar-vfs-info.c index 691f5d040..605b84e8f 100644 --- a/thunar-vfs/thunar-vfs-info.c +++ b/thunar-vfs/thunar-vfs-info.c @@ -458,7 +458,7 @@ thunar_vfs_info_rename (ThunarVfsInfo *info, const gchar * const *locale; const gchar *src_path; GKeyFile *key_file; - gssize data_length; + gsize data_length; gchar *data; gchar *key; gchar *dir_name; diff --git a/thunar-vfs/thunar-vfs-listdir-job.c b/thunar-vfs/thunar-vfs-listdir-job.c index 4d2db0264..338230c02 100644 --- a/thunar-vfs/thunar-vfs-listdir-job.c +++ b/thunar-vfs/thunar-vfs-listdir-job.c @@ -159,15 +159,6 @@ thunar_vfs_listdir_job_finalize (ExoObject *object) -static gint -namecmp (gconstpointer name_a, - gconstpointer name_b) -{ - return -1 * strcmp (name_a, name_b); -} - - - static void thunar_vfs_listdir_job_execute (ThunarVfsJob *job) { @@ -198,7 +189,7 @@ thunar_vfs_listdir_job_execute (ThunarVfsJob *job) * disk seeking. */ while (_thunar_vfs_sysdep_readdir (dp, &d_buffer, &d, &error) && d != NULL) - names = g_slist_insert_sorted (names, g_string_chunk_insert (names_chunk, d->d_name), namecmp); + names = g_slist_insert_sorted (names, g_string_chunk_insert (names_chunk, d->d_name), (GCompareFunc) strcmp); closedir (dp); @@ -216,7 +207,7 @@ thunar_vfs_listdir_job_execute (ThunarVfsJob *job) file_uri = thunar_vfs_uri_relative (THUNAR_VFS_LISTDIR_JOB (job)->uri, lp->data); info = thunar_vfs_info_new_for_uri (file_uri, NULL); if (G_LIKELY (info != NULL)) - infos = g_slist_prepend (infos, info); + infos = g_slist_append (infos, info); thunar_vfs_uri_unref (file_uri); current_time = time (NULL); diff --git a/thunar-vfs/thunar-vfs.symbols b/thunar-vfs/thunar-vfs.symbols index 7d54dd836..d30f36d64 100644 --- a/thunar-vfs/thunar-vfs.symbols +++ b/thunar-vfs/thunar-vfs.symbols @@ -53,6 +53,7 @@ thunar_vfs_info_new_for_uri thunar_vfs_info_ref thunar_vfs_info_unref thunar_vfs_info_execute +thunar_vfs_info_rename thunar_vfs_info_get_hint thunar_vfs_info_matches thunar_vfs_info_list_free diff --git a/thunar/Makefile.am b/thunar/Makefile.am index 320ad26b0..6999f2296 100644 --- a/thunar/Makefile.am +++ b/thunar/Makefile.am @@ -31,6 +31,8 @@ Thunar_SOURCES = \ thunar-desktop-window.h \ thunar-details-view.c \ thunar-details-view.h \ + thunar-dnd.c \ + thunar-dnd.h \ thunar-fallback-icon.c \ thunar-fallback-icon.h \ thunar-favourites-model.c \ diff --git a/thunar/thunar-details-view.c b/thunar/thunar-details-view.c index 39aa85b5e..d13fd6b47 100644 --- a/thunar/thunar-details-view.c +++ b/thunar/thunar-details-view.c @@ -27,33 +27,37 @@ #include <thunar/thunar-text-renderer.h> - -static void thunar_details_view_class_init (ThunarDetailsViewClass *klass); -static void thunar_details_view_init (ThunarDetailsView *details_view); -static AtkObject *thunar_details_view_get_accessible (GtkWidget *widget); -static GList *thunar_details_view_get_selected_items (ThunarStandardView *standard_view); -static void thunar_details_view_select_all (ThunarStandardView *standard_view); -static void thunar_details_view_unselect_all (ThunarStandardView *standard_view); -static void thunar_details_view_select_path (ThunarStandardView *standard_view, - GtkTreePath *path); -static void thunar_details_view_set_cursor (ThunarStandardView *standard_view, - GtkTreePath *path, - gboolean start_editing); -static void thunar_details_view_scroll_to_path (ThunarStandardView *standard_view, - GtkTreePath *path); -static void thunar_details_view_notify_model (GtkTreeView *tree_view, - GParamSpec *pspec, - ThunarDetailsView *details_view); -static gboolean thunar_details_view_button_press_event (GtkTreeView *tree_view, - GdkEventButton *event, - ThunarDetailsView *details_view); -static gboolean thunar_details_view_key_press_event (GtkTreeView *tree_view, - GdkEventKey *event, - ThunarDetailsView *details_view); -static void thunar_details_view_row_activated (GtkTreeView *tree_view, - GtkTreePath *path, - GtkTreeViewColumn *column, - ThunarDetailsView *details_view); +static void thunar_details_view_class_init (ThunarDetailsViewClass *klass); +static void thunar_details_view_init (ThunarDetailsView *details_view); +static AtkObject *thunar_details_view_get_accessible (GtkWidget *widget); +static GList *thunar_details_view_get_selected_items (ThunarStandardView *standard_view); +static void thunar_details_view_select_all (ThunarStandardView *standard_view); +static void thunar_details_view_unselect_all (ThunarStandardView *standard_view); +static void thunar_details_view_select_path (ThunarStandardView *standard_view, + GtkTreePath *path); +static void thunar_details_view_set_cursor (ThunarStandardView *standard_view, + GtkTreePath *path, + gboolean start_editing); +static void thunar_details_view_scroll_to_path (ThunarStandardView *standard_view, + GtkTreePath *path); +static GtkTreePath *thunar_details_view_get_path_at_pos (ThunarStandardView *standard_view, + gint x, + gint y); +static void thunar_details_view_highlight_path (ThunarStandardView *standard_view, + GtkTreePath *path); +static void thunar_details_view_notify_model (GtkTreeView *tree_view, + GParamSpec *pspec, + ThunarDetailsView *details_view); +static gboolean thunar_details_view_button_press_event (GtkTreeView *tree_view, + GdkEventButton *event, + ThunarDetailsView *details_view); +static gboolean thunar_details_view_key_press_event (GtkTreeView *tree_view, + GdkEventKey *event, + ThunarDetailsView *details_view); +static void thunar_details_view_row_activated (GtkTreeView *tree_view, + GtkTreePath *path, + GtkTreeViewColumn *column, + ThunarDetailsView *details_view); @@ -89,6 +93,8 @@ thunar_details_view_class_init (ThunarDetailsViewClass *klass) thunarstandard_view_class->select_path = thunar_details_view_select_path; thunarstandard_view_class->set_cursor = thunar_details_view_set_cursor; thunarstandard_view_class->scroll_to_path = thunar_details_view_scroll_to_path; + thunarstandard_view_class->get_path_at_pos = thunar_details_view_get_path_at_pos; + thunarstandard_view_class->highlight_path = thunar_details_view_highlight_path; } @@ -315,6 +321,33 @@ thunar_details_view_scroll_to_path (ThunarStandardView *standard_view, +static GtkTreePath* +thunar_details_view_get_path_at_pos (ThunarStandardView *standard_view, + gint x, + gint y) +{ + GtkTreePath *path; + + g_return_val_if_fail (THUNAR_IS_DETAILS_VIEW (standard_view), NULL); + + if (gtk_tree_view_get_dest_row_at_pos (GTK_TREE_VIEW (GTK_BIN (standard_view)->child), x, y, &path, NULL)) + return path; + + return NULL; +} + + + +static void +thunar_details_view_highlight_path (ThunarStandardView *standard_view, + GtkTreePath *path) +{ + g_return_if_fail (THUNAR_IS_DETAILS_VIEW (standard_view)); + gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (GTK_BIN (standard_view)->child), path, GTK_TREE_VIEW_DROP_INTO_OR_AFTER); +} + + + static void thunar_details_view_notify_model (GtkTreeView *tree_view, GParamSpec *pspec, @@ -361,10 +394,15 @@ thunar_details_view_button_press_event (GtkTreeView *tree_view, gtk_tree_selection_select_path (selection, path); } gtk_tree_path_free (path); - } - /* open the context menu */ - thunar_standard_view_context_menu (THUNAR_STANDARD_VIEW (details_view), event->button, event->time); + /* queue the menu popup */ + thunar_standard_view_queue_popup (THUNAR_STANDARD_VIEW (details_view), event); + } + else + { + /* open the context menu */ + thunar_standard_view_context_menu (THUNAR_STANDARD_VIEW (details_view), event->button, event->time); + } return TRUE; } diff --git a/thunar/thunar-dnd.c b/thunar/thunar-dnd.c new file mode 100644 index 000000000..118c58fa1 --- /dev/null +++ b/thunar/thunar-dnd.c @@ -0,0 +1,210 @@ +/* $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 + +#include <thunar/thunar-application.h> +#include <thunar/thunar-dnd.h> + + + +static void +action_selected (GtkWidget *item, + GdkDragAction *action) +{ + *action = GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (item), "action")); +} + + + +/** + * thunar_dnd_ask: + * @widget : the widget on which the drop was performed. + * @time : the time of the drop event. + * @actions : the list of actions supported for the drop. + * + * Pops up a menu that asks the user to choose one of the + * @actions or to cancel the drop. If the user chooses a + * valid #GdkDragAction from @actions, then this action is + * returned. Else if the user cancels the drop, 0 will be + * returned. + * + * This method can be used to implement a response to the + * #GDK_ACTION_ASK action on drops. + * + * Return value: the selected #GdkDragAction or 0 to cancel. + **/ +GdkDragAction +thunar_dnd_ask (GtkWidget *widget, + guint time, + GdkDragAction actions) +{ + static const GdkDragAction action_items[] = { GDK_ACTION_COPY, GDK_ACTION_MOVE, GDK_ACTION_LINK }; + static const gchar *action_names[] = { N_ ("_Copy here"), N_ ("_Move here"), N_ ("_Link here") }; + + GdkDragAction action = 0; + GtkWidget *menu; + GtkWidget *item; + GMainLoop *loop; + guint n; + + g_return_val_if_fail (GTK_IS_WIDGET (widget), 0); + + /* 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 (widget)); + g_signal_connect_swapped (G_OBJECT (menu), "deactivate", G_CALLBACK (g_main_loop_quit), loop); + + /* append the various items */ + for (n = 0; n < G_N_ELEMENTS (action_items); ++n) + if (G_LIKELY ((actions & action_items[n]) != 0)) + { + item = gtk_image_menu_item_new_with_mnemonic (_(action_names[n])); + g_object_set_data (G_OBJECT (item), "action", GUINT_TO_POINTER (action_items[n])); + g_signal_connect (G_OBJECT (item), "activate", G_CALLBACK (action_selected), &action); + gtk_menu_shell_append (GTK_MENU_SHELL (menu), item); + gtk_widget_show (item); + } + + /* append the separator */ + item = gtk_separator_menu_item_new (); + gtk_menu_shell_append (GTK_MENU_SHELL (menu), item); + gtk_widget_show (item); + + /* append the cancel item */ + item = gtk_image_menu_item_new_from_stock (GTK_STOCK_CANCEL, NULL); + gtk_menu_shell_append (GTK_MENU_SHELL (menu), item); + gtk_widget_show (item); + + /* run the internal loop */ + gtk_grab_add (menu); + gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL, 3, time); + g_main_loop_run (loop); + gtk_grab_remove (menu); + + /* clean up */ + gtk_object_sink (GTK_OBJECT (menu)); + g_main_loop_unref (loop); + + return action; +} + + + +/** + * thunar_dnd_perform: + * @widget : the #GtkWidget on which the drop was done. + * @file : the #ThunarFile on which the @uri_list was dropped. + * @uri_list : the list of #ThunarVfsURI<!---->s that was dropped. + * @action : the #GdkDragAction that was performed. + * + * Performs the drop of @uri_list on @file in @widget, as given in + * @action and returns %TRUE if the drop was started successfully + * (or even completed successfully), else %FALSE. + * + * Return value: %TRUE if the DnD operation was started + * successfully, else %FALSE. + **/ +gboolean +thunar_dnd_perform (GtkWidget *widget, + ThunarFile *file, + GList *uri_list, + GdkDragAction action) +{ + ThunarApplication *application; + GtkWidget *message; + GtkWidget *window; + gboolean succeed = TRUE; + GError *error = NULL; + + g_return_val_if_fail (GTK_IS_WIDGET (widget), FALSE); + g_return_val_if_fail (THUNAR_IS_FILE (file), FALSE); + + /* query a reference on the application object */ + application = thunar_application_get (); + + /* determine the toplevel window for the widget */ + window = gtk_widget_get_toplevel (widget); + + /* check if the file is a directory */ + if (thunar_file_is_directory (file)) + { + /* perform the given directory operation */ + switch (action) + { + case GDK_ACTION_COPY: + thunar_application_copy_uris (application, GTK_WINDOW (window), uri_list, thunar_file_get_uri (file)); + break; + + case GDK_ACTION_MOVE: + thunar_application_move_uris (application, GTK_WINDOW (window), uri_list, thunar_file_get_uri (file)); + break; + + case GDK_ACTION_LINK: + // FIXME + message = gtk_message_dialog_new (GTK_WINDOW (window), + GTK_DIALOG_MODAL + | GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_MESSAGE_ERROR, + GTK_BUTTONS_OK, + _("Creating links is not yet supported. This will be fixed soon!")); + gtk_dialog_run (GTK_DIALOG (message)); + gtk_widget_destroy (message); + break; + + default: + succeed = FALSE; + } + } + else if (thunar_file_is_executable (file)) + { + succeed = thunar_file_execute (file, gtk_widget_get_screen (widget), uri_list, &error); + if (G_UNLIKELY (!succeed)) + { + message = gtk_message_dialog_new (GTK_WINDOW (window), + GTK_DIALOG_MODAL + | GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_MESSAGE_ERROR, + GTK_BUTTONS_OK, + _("Unable to execute file \"%s\"."), + thunar_file_get_display_name (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 + { + succeed = FALSE; + } + + /* release the application reference */ + g_object_unref (G_OBJECT (application)); + + return succeed; +} + + + diff --git a/thunar/thunar-dnd.h b/thunar/thunar-dnd.h new file mode 100644 index 000000000..930a112ba --- /dev/null +++ b/thunar/thunar-dnd.h @@ -0,0 +1,38 @@ +/* $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_DND_H__ +#define __THUNAR_DND_H__ + +#include <thunar/thunar-file.h> + +G_BEGIN_DECLS; + +GdkDragAction thunar_dnd_ask (GtkWidget *widget, + guint time, + GdkDragAction actions); + +gboolean thunar_dnd_perform (GtkWidget *widget, + ThunarFile *file, + GList *uri_list, + GdkDragAction action); + +G_END_DECLS; + +#endif /* !__THUNAR_DND_H__ */ diff --git a/thunar/thunar-favourites-model.c b/thunar/thunar-favourites-model.c index ddf3890aa..c1ed2a434 100644 --- a/thunar/thunar-favourites-model.c +++ b/thunar/thunar-favourites-model.c @@ -454,7 +454,7 @@ thunar_favourites_model_get_value (GtkTreeModel *tree_model, ThunarFavourite *favourite; GtkIconTheme *icon_theme; const gchar *icon_name; - GdkPixbuf *icon; + GdkPixbuf *icon = NULL; g_return_if_fail (THUNAR_IS_FAVOURITES_MODEL (model)); g_return_if_fail (iter->stamp == model->stamp); @@ -489,7 +489,8 @@ thunar_favourites_model_get_value (GtkTreeModel *tree_model, { icon = thunar_file_load_icon (favourite->file, icon_factory, 32); } - g_value_take_object (value, icon); + if (G_LIKELY (icon != NULL)) + g_value_take_object (value, icon); break; case THUNAR_FAVOURITES_MODEL_COLUMN_MUTABLE: diff --git a/thunar/thunar-file.c b/thunar/thunar-file.c index a58e1689d..e147b722c 100644 --- a/thunar/thunar-file.c +++ b/thunar/thunar-file.c @@ -163,6 +163,7 @@ thunar_file_class_init (ThunarFileClass *klass) klass->execute = thunar_file_real_execute; klass->rename = thunar_file_real_rename; klass->open_as_folder = thunar_file_real_open_as_folder; + klass->accepts_uri_drop = (gpointer) exo_noop_zero; klass->get_mime_info = (gpointer) exo_noop_null; klass->get_special_name = thunar_file_real_get_special_name; klass->get_date = (gpointer) exo_noop_false; @@ -688,6 +689,72 @@ thunar_file_open_as_folder (ThunarFile *file, +/** + * thunar_file_accepts_uri_drop: + * @file : a #ThunarFile instance. + * @uri_list : the list of #ThunarVfsURI<!---->s that will be droppped. + * @actions : the #GdkDragAction<!---->s provided by the drag source. + * + * Checks whether @file can accept @uri_list for the given @actions and + * returns the #GdkDragAction<!---->s that can be used or 0 if no + * actions apply. + * + * Return value: the #GdkDragAction<!---->s supported for the drop or + * 0 if no drop is possible. + **/ +GdkDragAction +thunar_file_accepts_uri_drop (ThunarFile *file, + GList *uri_list, + GdkDragAction actions) +{ + GdkDragAction action; + ThunarVfsURI *parent_uri; + ThunarVfsURI *uri; + GList *lp; + guint n; + + g_return_val_if_fail (THUNAR_IS_FILE (file), FALSE); + + /* we can never drop an empty list */ + if (G_UNLIKELY (uri_list == NULL)) + return 0; + + /* determine the uri of the file */ + uri = thunar_file_get_uri (file); + + /* check up to 500 of the URIs (just in case somebody tries to + * drag around his music collection with 5000 files). + */ + for (lp = uri_list, n = 0; actions != 0 && lp != NULL && n < 500; lp = lp->next, ++n) + { + /* we cannot drop a file on itself */ + if (G_UNLIKELY (thunar_vfs_uri_equal (uri, lp->data))) + return 0; + + /* check whether source and destination are the same */ + parent_uri = thunar_vfs_uri_parent (lp->data); + if (G_LIKELY (parent_uri != NULL)) + { + if (thunar_vfs_uri_equal (uri, parent_uri)) + actions = 0; + thunar_vfs_uri_unref (parent_uri); + } + + /* check if this URI is supported */ + action = (*THUNAR_FILE_GET_CLASS (file)->accepts_uri_drop) (file, lp->data, action); + if (G_UNLIKELY (action == 0)) + return 0; + + /* drop actions not supported for this URI from the actions list */ + actions &= action; + } + + /* check if we can drop */ + return actions; +} + + + /** * thunar_file_get_uri: * @file : a #ThunarFile instance. diff --git a/thunar/thunar-file.h b/thunar/thunar-file.h index 1e8f37a87..fd19ac3d6 100644 --- a/thunar/thunar-file.h +++ b/thunar/thunar-file.h @@ -83,6 +83,10 @@ struct _ThunarFileClass ThunarFolder *(*open_as_folder) (ThunarFile *file, GError **error); + GdkDragAction (*accepts_uri_drop) (ThunarFile *file, + const ThunarVfsURI *uri, + GdkDragAction actions); + ThunarVfsURI *(*get_uri) (ThunarFile *file); ThunarVfsMimeInfo *(*get_mime_info) (ThunarFile *file); @@ -159,6 +163,10 @@ gboolean thunar_file_rename (ThunarFile *file, ThunarFolder *thunar_file_open_as_folder (ThunarFile *file, GError **error); +GdkDragAction thunar_file_accepts_uri_drop (ThunarFile *file, + GList *uri_list, + GdkDragAction actions); + ThunarVfsURI *thunar_file_get_uri (ThunarFile *file); ThunarVfsMimeInfo *thunar_file_get_mime_info (ThunarFile *file); diff --git a/thunar/thunar-icon-view.c b/thunar/thunar-icon-view.c index bacb66564..4217051d1 100644 --- a/thunar/thunar-icon-view.c +++ b/thunar/thunar-icon-view.c @@ -28,37 +28,42 @@ -static void thunar_icon_view_class_init (ThunarIconViewClass *klass); -static void thunar_icon_view_init (ThunarIconView *icon_view); -static AtkObject *thunar_icon_view_get_accessible (GtkWidget *widget); -static void thunar_icon_view_connect_ui_manager (ThunarStandardView *standard_view, - GtkUIManager *ui_manager); -static void thunar_icon_view_disconnect_ui_manager (ThunarStandardView *standard_view, - GtkUIManager *ui_manager); -static GList *thunar_icon_view_get_selected_items (ThunarStandardView *standard_view); -static void thunar_icon_view_select_all (ThunarStandardView *standard_view); -static void thunar_icon_view_unselect_all (ThunarStandardView *standard_view); -static void thunar_icon_view_select_path (ThunarStandardView *standard_view, - GtkTreePath *path); -static void thunar_icon_view_set_cursor (ThunarStandardView *standard_view, - GtkTreePath *path, - gboolean start_editing); -static void thunar_icon_view_scroll_to_path (ThunarStandardView *standard_view, - GtkTreePath *path); -static void thunar_icon_view_action_sort (GtkAction *action, - GtkAction *current, - ThunarStandardView *standard_view); -static gboolean thunar_icon_view_button_press_event (ExoIconView *view, - GdkEventButton *event, - ThunarIconView *icon_view); -static gboolean thunar_icon_view_key_press_event (ExoIconView *view, - GdkEventKey *event, - ThunarIconView *icon_view); -static void thunar_icon_view_item_activated (ExoIconView *view, - GtkTreePath *path, - ThunarIconView *icon_view); -static void thunar_icon_view_sort_column_changed (GtkTreeSortable *sortable, - ThunarIconView *icon_view); +static void thunar_icon_view_class_init (ThunarIconViewClass *klass); +static void thunar_icon_view_init (ThunarIconView *icon_view); +static AtkObject *thunar_icon_view_get_accessible (GtkWidget *widget); +static void thunar_icon_view_connect_ui_manager (ThunarStandardView *standard_view, + GtkUIManager *ui_manager); +static void thunar_icon_view_disconnect_ui_manager (ThunarStandardView *standard_view, + GtkUIManager *ui_manager); +static GList *thunar_icon_view_get_selected_items (ThunarStandardView *standard_view); +static void thunar_icon_view_select_all (ThunarStandardView *standard_view); +static void thunar_icon_view_unselect_all (ThunarStandardView *standard_view); +static void thunar_icon_view_select_path (ThunarStandardView *standard_view, + GtkTreePath *path); +static void thunar_icon_view_set_cursor (ThunarStandardView *standard_view, + GtkTreePath *path, + gboolean start_editing); +static void thunar_icon_view_scroll_to_path (ThunarStandardView *standard_view, + GtkTreePath *path); +static GtkTreePath *thunar_icon_view_get_path_at_pos (ThunarStandardView *standard_view, + gint x, + gint y); +static void thunar_icon_view_highlight_path (ThunarStandardView *standard_view, + GtkTreePath *path); +static void thunar_icon_view_action_sort (GtkAction *action, + GtkAction *current, + ThunarStandardView *standard_view); +static gboolean thunar_icon_view_button_press_event (ExoIconView *view, + GdkEventButton *event, + ThunarIconView *icon_view); +static gboolean thunar_icon_view_key_press_event (ExoIconView *view, + GdkEventKey *event, + ThunarIconView *icon_view); +static void thunar_icon_view_item_activated (ExoIconView *view, + GtkTreePath *path, + ThunarIconView *icon_view); +static void thunar_icon_view_sort_column_changed (GtkTreeSortable *sortable, + ThunarIconView *icon_view); @@ -119,6 +124,8 @@ thunar_icon_view_class_init (ThunarIconViewClass *klass) thunarstandard_view_class->select_path = thunar_icon_view_select_path; thunarstandard_view_class->set_cursor = thunar_icon_view_set_cursor; thunarstandard_view_class->scroll_to_path = thunar_icon_view_scroll_to_path; + thunarstandard_view_class->get_path_at_pos = thunar_icon_view_get_path_at_pos; + thunarstandard_view_class->highlight_path = thunar_icon_view_highlight_path; } @@ -295,6 +302,32 @@ thunar_icon_view_scroll_to_path (ThunarStandardView *standard_view, +static GtkTreePath* +thunar_icon_view_get_path_at_pos (ThunarStandardView *standard_view, + gint x, + gint y) +{ + g_return_val_if_fail (THUNAR_IS_ICON_VIEW (standard_view), NULL); + + /* translate the widget coordinates to icon window coordinates */ + exo_icon_view_widget_to_icon_coords (EXO_ICON_VIEW (GTK_BIN (standard_view)->child), x, y, &x, &y); + + /* determine the path at the position */ + return exo_icon_view_get_path_at_pos (EXO_ICON_VIEW (GTK_BIN (standard_view)->child), x, y); +} + + + +static void +thunar_icon_view_highlight_path (ThunarStandardView *standard_view, + GtkTreePath *path) +{ + g_return_if_fail (THUNAR_IS_ICON_VIEW (standard_view)); + exo_icon_view_set_drag_dest_item (EXO_ICON_VIEW (GTK_BIN (standard_view)->child), path, EXO_ICON_VIEW_DROP_INTO); +} + + + static void thunar_icon_view_action_sort (GtkAction *action, GtkAction *current, @@ -338,6 +371,9 @@ thunar_icon_view_button_press_event (ExoIconView *view, exo_icon_view_select_path (view, path); } gtk_tree_path_free (path); + + /* queue the menu popup */ + thunar_standard_view_queue_popup (THUNAR_STANDARD_VIEW (icon_view), event); } else if ((event->state & gtk_accelerator_get_default_mod_mask ()) == 0) { @@ -345,11 +381,11 @@ thunar_icon_view_button_press_event (ExoIconView *view, * to make sure that the folder context menu is opened. */ exo_icon_view_unselect_all (view); + + /* open the context menu */ + thunar_standard_view_context_menu (THUNAR_STANDARD_VIEW (icon_view), event->button, event->time); } - /* open the context menu */ - thunar_standard_view_context_menu (THUNAR_STANDARD_VIEW (icon_view), event->button, event->time); - return TRUE; } diff --git a/thunar/thunar-local-file.c b/thunar/thunar-local-file.c index feab8653a..2da5ee736 100644 --- a/thunar/thunar-local-file.c +++ b/thunar/thunar-local-file.c @@ -39,6 +39,9 @@ static gboolean thunar_local_file_rename (ThunarFile GError **error); static ThunarFolder *thunar_local_file_open_as_folder (ThunarFile *file, GError **error); +static GdkDragAction thunar_local_file_accepts_uri_drop (ThunarFile *file, + const ThunarVfsURI *uri, + GdkDragAction actions); static ThunarVfsURI *thunar_local_file_get_uri (ThunarFile *file); static ThunarVfsMimeInfo *thunar_local_file_get_mime_info (ThunarFile *file); static const gchar *thunar_local_file_get_display_name (ThunarFile *file); @@ -145,6 +148,7 @@ thunar_local_file_class_init (ThunarLocalFileClass *klass) thunarfile_class->execute = thunar_local_file_execute; thunarfile_class->rename = thunar_local_file_rename; thunarfile_class->open_as_folder = thunar_local_file_open_as_folder; + thunarfile_class->accepts_uri_drop = thunar_local_file_accepts_uri_drop; thunarfile_class->get_uri = thunar_local_file_get_uri; thunarfile_class->get_mime_info = thunar_local_file_get_mime_info; thunarfile_class->get_display_name = thunar_local_file_get_display_name; @@ -271,6 +275,30 @@ thunar_local_file_open_as_folder (ThunarFile *file, +static GdkDragAction +thunar_local_file_accepts_uri_drop (ThunarFile *file, + const ThunarVfsURI *uri, + GdkDragAction actions) +{ + ThunarLocalFile *local_file = THUNAR_LOCAL_FILE (file); + + /* we can only drop local files here (for now) */ + if (G_LIKELY (thunar_vfs_uri_get_scheme (uri) == THUNAR_VFS_URI_SCHEME_FILE)) + { + /* check if we have a writable directory here */ + if (G_LIKELY (local_file->info->type == THUNAR_VFS_FILE_TYPE_DIRECTORY)) + return GDK_ACTION_COPY | GDK_ACTION_MOVE | GDK_ACTION_LINK | GDK_ACTION_ASK; + + /* check if we can execute the file */ + if ((local_file->info->flags & THUNAR_VFS_FILE_FLAGS_EXECUTABLE) != 0) + return GDK_ACTION_COPY | GDK_ACTION_MOVE | GDK_ACTION_LINK | GDK_ACTION_PRIVATE; + } + + return 0; +} + + + static ThunarVfsURI* thunar_local_file_get_uri (ThunarFile *file) { diff --git a/thunar/thunar-progress-dialog.c b/thunar/thunar-progress-dialog.c index e9cc3ab9d..b9f16c1af 100644 --- a/thunar/thunar-progress-dialog.c +++ b/thunar/thunar-progress-dialog.c @@ -258,6 +258,9 @@ thunar_progress_dialog_ask (ThunarProgressDialog *dialog, g_return_val_if_fail (THUNAR_VFS_IS_INTERACTIVE_JOB (job), THUNAR_VFS_INTERACTIVE_JOB_RESPONSE_CANCEL); g_return_val_if_fail (dialog->job == job, THUNAR_VFS_INTERACTIVE_JOB_RESPONSE_CANCEL); + /* be sure to display the progress dialog prior to opening the question dialog */ + gtk_widget_show_now (GTK_WIDGET (dialog)); + question = g_object_new (GTK_TYPE_DIALOG, "has-separator", FALSE, "resizable", FALSE, @@ -340,6 +343,9 @@ thunar_progress_dialog_error (ThunarProgressDialog *dialog, g_return_if_fail (THUNAR_VFS_IS_INTERACTIVE_JOB (job)); g_return_if_fail (dialog->job == job); + /* be sure to display the progress dialog prior to opening the error dialog */ + gtk_widget_show_now (GTK_WIDGET (dialog)); + message = gtk_message_dialog_new (GTK_WINDOW (dialog), GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT, diff --git a/thunar/thunar-standard-view.c b/thunar/thunar-standard-view.c index a1e9c6c70..65c345cc7 100644 --- a/thunar/thunar-standard-view.c +++ b/thunar/thunar-standard-view.c @@ -30,6 +30,7 @@ #include <thunarx/thunarx-gtk-extensions.h> +#include <thunar/thunar-dnd.h> #include <thunar/thunar-icon-renderer.h> #include <thunar/thunar-launcher.h> #include <thunar/thunar-properties-dialog.h> @@ -81,6 +82,8 @@ static void thunar_standard_view_set_property (GObject static void thunar_standard_view_realize (GtkWidget *widget); static void thunar_standard_view_unrealize (GtkWidget *widget); static void thunar_standard_view_grab_focus (GtkWidget *widget); +static gboolean thunar_standard_view_expose_event (GtkWidget *widget, + GdkEventExpose *event); static ThunarFile *thunar_standard_view_get_current_directory (ThunarNavigator *navigator); static void thunar_standard_view_set_current_directory (ThunarNavigator *navigator, ThunarFile *current_directory); @@ -89,6 +92,12 @@ static const gchar *thunar_standard_view_get_statusbar_text (ThunarView static GtkUIManager *thunar_standard_view_get_ui_manager (ThunarView *view); static void thunar_standard_view_set_ui_manager (ThunarView *view, GtkUIManager *ui_manager); +static GdkDragAction thunar_standard_view_get_dest_actions (ThunarStandardView *standard_view, + GdkDragContext *context, + gint x, + gint y, + guint time, + ThunarFile **file_return); static GList *thunar_standard_view_get_selected_files (ThunarStandardView *standard_view); static GList *thunar_standard_view_get_selected_uris (ThunarStandardView *standard_view); static void thunar_standard_view_action_properties (GtkAction *action, @@ -109,20 +118,55 @@ static void thunar_standard_view_action_rename (GtkAction ThunarStandardView *standard_view); static void thunar_standard_view_action_show_hidden_files (GtkToggleAction *toggle_action, ThunarStandardView *standard_view); -static void thunar_standard_view_drag_begin (GtkWidget *widget, +static gboolean thunar_standard_view_button_release_event (GtkWidget *view, + GdkEventButton *event, + ThunarStandardView *standard_view); +static gboolean thunar_standard_view_motion_notify_event (GtkWidget *view, + GdkEventMotion *event, + ThunarStandardView *standard_view); +static gboolean thunar_standard_view_drag_drop (GtkWidget *view, + GdkDragContext *context, + gint x, + gint y, + guint time, + ThunarStandardView *standard_view); +static void thunar_standard_view_drag_data_received (GtkWidget *view, + GdkDragContext *context, + gint x, + gint y, + GtkSelectionData *selection_data, + guint info, + guint time, + ThunarStandardView *standard_view); +static void thunar_standard_view_drag_leave (GtkWidget *view, + GdkDragContext *context, + guint time, + ThunarStandardView *standard_view); +static gboolean thunar_standard_view_drag_motion (GtkWidget *view, + GdkDragContext *context, + gint x, + gint y, + guint time, + ThunarStandardView *standard_view); +static void thunar_standard_view_drag_begin (GtkWidget *view, GdkDragContext *context, ThunarStandardView *standard_view); -static void thunar_standard_view_drag_data_get (GtkWidget *widget, +static void thunar_standard_view_drag_data_get (GtkWidget *view, GdkDragContext *context, GtkSelectionData *selection_data, guint info, guint time, ThunarStandardView *standard_view); +static void thunar_standard_view_drag_end (GtkWidget *view, + GdkDragContext *context, + ThunarStandardView *standard_view); static void thunar_standard_view_renamed (ThunarTextRenderer *text_renderer, const gchar *path_string, const gchar *text, ThunarStandardView *standard_view); static void thunar_standard_view_loading_unbound (gpointer user_data); +static gboolean thunar_standard_view_drag_timer (gpointer user_data); +static void thunar_standard_view_drag_timer_destroy (gpointer user_data); @@ -138,6 +182,18 @@ struct _ThunarStandardViewPrivate GtkAction *action_select_by_pattern; GtkAction *action_rename; GtkAction *action_show_hidden_files; + + /* right-click drag/popup support */ + GList *drag_uri_list; + gint drag_timer_id; + gint drag_x; + gint drag_y; + + /* drop site support */ + guint drop_data_ready : 1; /* whether the drop data was received already */ + guint drop_highlight : 1; + guint drop_occurred : 1; /* whether the data was dropped */ + GList *drop_uri_list; /* the list of URIs that are contained in the drop data */ }; @@ -167,6 +223,14 @@ static const GtkTargetEntry drag_targets[] = { "text/uri-list", 0, TEXT_URI_LIST, }, }; +/* Target types for dropping to the view */ +static const GtkTargetEntry drop_targets[] = +{ + { "text/uri-list", 0, TEXT_URI_LIST, }, +}; + + + static GObjectClass *thunar_standard_view_parent_class; @@ -240,6 +304,7 @@ thunar_standard_view_class_init (ThunarStandardViewClass *klass) gtkwidget_class->realize = thunar_standard_view_realize; gtkwidget_class->unrealize = thunar_standard_view_unrealize; gtkwidget_class->grab_focus = thunar_standard_view_grab_focus; + gtkwidget_class->expose_event = thunar_standard_view_expose_event; klass->connect_ui_manager = (gpointer) exo_noop; klass->disconnect_ui_manager = (gpointer) exo_noop; @@ -291,6 +356,7 @@ static void thunar_standard_view_init (ThunarStandardView *standard_view) { standard_view->priv = THUNAR_STANDARD_VIEW_GET_PRIVATE (standard_view); + standard_view->priv->drag_timer_id = -1; gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (standard_view), GTK_POLICY_AUTOMATIC, @@ -354,7 +420,7 @@ thunar_standard_view_constructor (GType type, guint n_construct_properties, GObjectConstructParam *construct_properties) { - GtkWidget *widget; + GtkWidget *view; GObject *object; /* let the GObject constructor create the instance */ @@ -362,23 +428,28 @@ thunar_standard_view_constructor (GType type, n_construct_properties, construct_properties); + /* determine the real view widget (treeview or iconview) */ + view = GTK_BIN (object)->child; + /* apply our list model to the real view (the child of the scrolled window), * we therefore assume that all real views have the "model" property. */ - g_object_set (G_OBJECT (GTK_BIN (object)->child), - "model", THUNAR_STANDARD_VIEW (object)->model, - NULL); + g_object_set (G_OBJECT (view), "model", THUNAR_STANDARD_VIEW (object)->model, NULL); - /* determine the real view widget (treeview or iconview) */ - widget = GTK_BIN (object)->child; + /* setup the real view as drop site */ + gtk_drag_dest_set (view, 0, drop_targets, G_N_ELEMENTS (drop_targets), GDK_ACTION_ASK | GDK_ACTION_COPY | GDK_ACTION_LINK | GDK_ACTION_MOVE); + g_signal_connect (G_OBJECT (view), "drag-drop", G_CALLBACK (thunar_standard_view_drag_drop), object); + g_signal_connect (G_OBJECT (view), "drag-data-received", G_CALLBACK (thunar_standard_view_drag_data_received), object); + g_signal_connect (G_OBJECT (view), "drag-leave", G_CALLBACK (thunar_standard_view_drag_leave), object); + g_signal_connect (G_OBJECT (view), "drag-motion", G_CALLBACK (thunar_standard_view_drag_motion), object); /* setup the real view as drag source */ - gtk_drag_source_set (widget, GDK_BUTTON1_MASK | GDK_BUTTON3_MASK, - drag_targets, G_N_ELEMENTS (drag_targets), - GDK_ACTION_COPY | GDK_ACTION_MOVE - | GDK_ACTION_LINK | GDK_ACTION_ASK); - g_signal_connect (G_OBJECT (widget), "drag-begin", G_CALLBACK (thunar_standard_view_drag_begin), object); - g_signal_connect (G_OBJECT (widget), "drag-data-get", G_CALLBACK (thunar_standard_view_drag_data_get), object); + gtk_drag_source_set (view, GDK_BUTTON1_MASK, drag_targets, + G_N_ELEMENTS (drag_targets), GDK_ACTION_COPY + | GDK_ACTION_MOVE | GDK_ACTION_LINK); + g_signal_connect (G_OBJECT (view), "drag-begin", G_CALLBACK (thunar_standard_view_drag_begin), object); + g_signal_connect (G_OBJECT (view), "drag-data-get", G_CALLBACK (thunar_standard_view_drag_data_get), object); + g_signal_connect (G_OBJECT (view), "drag-end", G_CALLBACK (thunar_standard_view_drag_end), object); /* done, we have a working object */ return object; @@ -395,13 +466,17 @@ thunar_standard_view_dispose (GObject *object) if (G_UNLIKELY (standard_view->loading_binding != NULL)) exo_binding_unbind (standard_view->loading_binding); + /* be sure to cancel any pending drag timer */ + if (G_UNLIKELY (standard_view->priv->drag_timer_id >= 0)) + g_source_remove (standard_view->priv->drag_timer_id); + /* reset the UI manager property */ thunar_view_set_ui_manager (THUNAR_VIEW (standard_view), NULL); /* disconnect the widget from the launcher support */ thunar_launcher_set_widget (standard_view->priv->launcher, NULL); - G_OBJECT_CLASS (thunar_standard_view_parent_class)->dispose (object); + (*G_OBJECT_CLASS (thunar_standard_view_parent_class)->dispose) (object); } @@ -417,6 +492,12 @@ thunar_standard_view_finalize (GObject *object) g_assert (standard_view->ui_manager == NULL); g_assert (standard_view->clipboard == NULL); + /* release the drag URI list (just in case the drag-end wasn't fired before) */ + thunar_vfs_uri_list_free (standard_view->priv->drag_uri_list); + + /* release the drop URI list (just in case the drag-leave wasn't fired before) */ + thunar_vfs_uri_list_free (standard_view->priv->drop_uri_list); + /* release the reference on the name renderer */ g_object_unref (G_OBJECT (standard_view->name_renderer)); @@ -436,7 +517,7 @@ thunar_standard_view_finalize (GObject *object) /* free the statusbar text (if any) */ g_free (standard_view->statusbar_text); - G_OBJECT_CLASS (thunar_standard_view_parent_class)->finalize (object); + (*G_OBJECT_CLASS (thunar_standard_view_parent_class)->finalize) (object); } @@ -567,6 +648,51 @@ thunar_standard_view_grab_focus (GtkWidget *widget) +static gboolean +thunar_standard_view_expose_event (GtkWidget *widget, + GdkEventExpose *event) +{ + gboolean result = FALSE; +#if GTK_CHECK_VERSION(2,7,2) + cairo_t *cr; +#endif + gint x, y, width, height; + + /* let the scrolled window do it's work */ + result = (*GTK_WIDGET_CLASS (thunar_standard_view_parent_class)->expose_event) (widget, event); + + /* render the folder drop shadow */ + if (G_UNLIKELY (THUNAR_STANDARD_VIEW (widget)->priv->drop_highlight)) + { + x = widget->allocation.x; + y = widget->allocation.y; + width = widget->allocation.width; + height = widget->allocation.height; + + gtk_paint_shadow (widget->style, widget->window, + GTK_STATE_NORMAL, GTK_SHADOW_OUT, + NULL, widget, "dnd", + x, y, width, height); + +#if GTK_CHECK_VERSION(2,7,2) + /* the cairo version looks better here, so we use it if possible */ + cr = gdk_cairo_create (widget->window); + cairo_set_source_rgb (cr, 0.0, 0.0, 0.0); + cairo_set_line_width (cr, 1.0); + cairo_rectangle (cr, x + 0.5, y + 0.5, width - 1, height - 1); + cairo_stroke (cr); + cairo_destroy (cr); +#else + gdk_draw_rectangle (widget->window, widget->style->black_gc, + FALSE, x, y, width - 1, height - 1); +#endif + } + + return result; +} + + + static ThunarFile* thunar_standard_view_get_current_directory (ThunarNavigator *navigator) { @@ -763,6 +889,94 @@ thunar_standard_view_set_ui_manager (ThunarView *view, +static GdkDragAction +thunar_standard_view_get_dest_actions (ThunarStandardView *standard_view, + GdkDragContext *context, + gint x, + gint y, + guint time, + ThunarFile **file_return) +{ + GdkDragAction actions = 0; + GdkDragAction action = 0; + GtkTreePath *path; + GtkTreeIter iter; + ThunarFile *file; + + /* determine the path for the given coordinates */ + path = (*THUNAR_STANDARD_VIEW_GET_CLASS (standard_view)->get_path_at_pos) (standard_view, x, y); + if (G_LIKELY (path != NULL)) + { + /* determine the file for the path */ + gtk_tree_model_get_iter (GTK_TREE_MODEL (standard_view->model), &iter, path); + file = thunar_list_model_get_file (standard_view->model, &iter); + } + else + { + /* determine the current directory */ + file = thunar_navigator_get_current_directory (THUNAR_NAVIGATOR (standard_view)); + if (G_LIKELY (file != NULL)) + g_object_ref (G_OBJECT (file)); + } + + /* check if we can drop there */ + if (G_LIKELY (file != NULL)) + { + actions = thunar_file_accepts_uri_drop (file, standard_view->priv->drop_uri_list, context->actions); + if (G_LIKELY (actions != 0)) + { + /* determine a working action */ + if (G_LIKELY ((context->suggested_action & actions) != 0)) + action = context->suggested_action; + else if ((actions & GDK_ACTION_ASK) != 0) + action = GDK_ACTION_ASK; + else if ((actions & GDK_ACTION_COPY) != 0) + action = GDK_ACTION_COPY; + else if ((actions & GDK_ACTION_LINK) != 0) + action = GDK_ACTION_LINK; + else if ((actions & GDK_ACTION_MOVE) != 0) + action = GDK_ACTION_MOVE; + else + action = GDK_ACTION_PRIVATE; + + /* tell the caller about the file (if it's interested) */ + if (G_UNLIKELY (file_return != NULL)) + *file_return = g_object_ref (G_OBJECT (file)); + } + + /* release the file reference */ + g_object_unref (G_OBJECT (file)); + } + + /* reset path if we cannot drop */ + if (G_UNLIKELY (action == 0 && path != NULL)) + { + gtk_tree_path_free (path); + path = NULL; + } + + /* do the view highlighting */ + if (standard_view->priv->drop_highlight != (path == NULL && action != 0)) + { + standard_view->priv->drop_highlight = (path == NULL && action != 0); + gtk_widget_queue_draw (GTK_WIDGET (standard_view)); + } + + /* do the item highlighting */ + (*THUNAR_STANDARD_VIEW_GET_CLASS (standard_view)->highlight_path) (standard_view, path); + + /* tell Gdk whether we can drop here */ + gdk_drag_status (context, action, time); + + /* clean up */ + if (G_LIKELY (path != NULL)) + gtk_tree_path_free (path); + + return actions; +} + + + static GList* thunar_standard_view_get_selected_files (ThunarStandardView *standard_view) { @@ -1034,49 +1248,249 @@ thunar_standard_view_action_show_hidden_files (GtkToggleAction *toggle_action +static gboolean +thunar_standard_view_button_release_event (GtkWidget *view, + GdkEventButton *event, + ThunarStandardView *standard_view) +{ + g_return_val_if_fail (THUNAR_IS_STANDARD_VIEW (standard_view), FALSE); + g_return_val_if_fail (standard_view->priv->drag_timer_id >= 0, FALSE); + + /* cancel the pending drag timer */ + g_source_remove (standard_view->priv->drag_timer_id); + + /* fire up the context menu */ + thunar_standard_view_context_menu (standard_view, event->button, event->time); + + return TRUE; +} + + + +static gboolean +thunar_standard_view_motion_notify_event (GtkWidget *view, + GdkEventMotion *event, + ThunarStandardView *standard_view) +{ + GdkDragContext *context; + GtkTargetList *target_list; + + g_return_val_if_fail (THUNAR_IS_STANDARD_VIEW (standard_view), FALSE); + g_return_val_if_fail (standard_view->priv->drag_timer_id >= 0, FALSE); + + /* check if we passed the DnD threshold */ + if (gtk_drag_check_threshold (view, standard_view->priv->drag_x, standard_view->priv->drag_y, event->x, event->y)) + { + /* cancel the drag timer, as we won't popup the menu anymore */ + g_source_remove (standard_view->priv->drag_timer_id); + + /* allocate the drag context (preferred action is to ask the user) */ + target_list = gtk_target_list_new (drag_targets, G_N_ELEMENTS (drag_targets)); + context = gtk_drag_begin (view, target_list, GDK_ACTION_COPY | GDK_ACTION_MOVE | GDK_ACTION_LINK | GDK_ACTION_ASK, 3, (GdkEvent *) event); + context->suggested_action = GDK_ACTION_ASK; + gtk_target_list_unref (target_list); + + return TRUE; + } + + return FALSE; +} + + + +static gboolean +thunar_standard_view_drag_drop (GtkWidget *view, + GdkDragContext *context, + gint x, + gint y, + guint time, + ThunarStandardView *standard_view) +{ + GdkAtom target; + + target = gtk_drag_dest_find_target (view, context, NULL); + if (G_LIKELY (target != GDK_NONE)) + { + /* set state so the drag-data-received knows that + * this is really a drop this time. + */ + standard_view->priv->drop_occurred = TRUE; + + /* request the drag data from the source */ + gtk_drag_get_data (view, context, target, time); + + /* we'll call gtk_drag_finish() later */ + return TRUE; + } + else + { + /* we cannot handle the drag data */ + return FALSE; + } +} + + + static void -thunar_standard_view_drag_begin (GtkWidget *widget, +thunar_standard_view_drag_data_received (GtkWidget *view, + GdkDragContext *context, + gint x, + gint y, + GtkSelectionData *selection_data, + guint info, + guint time, + ThunarStandardView *standard_view) +{ + GdkDragAction actions; + GdkDragAction action; + ThunarFile *file = NULL; + gboolean succeed = FALSE; + + /* check if we don't already know the drop data */ + if (G_LIKELY (!standard_view->priv->drop_data_ready)) + { + /* extract the URI list from the selection data (if valid) */ + if (info == TEXT_URI_LIST && selection_data->format == 8 && selection_data->length > 0) + standard_view->priv->drop_uri_list = thunar_vfs_uri_list_from_string ((gchar *) selection_data->data, NULL); + + /* reset the state */ + standard_view->priv->drop_data_ready = TRUE; + } + + /* check if the data was dropped */ + if (G_UNLIKELY (standard_view->priv->drop_occurred)) + { + /* reset the state */ + standard_view->priv->drop_occurred = FALSE; + + /* determine the drop position */ + actions = thunar_standard_view_get_dest_actions (standard_view, context, x, y, time, &file); + if (G_LIKELY ((actions & (GDK_ACTION_COPY | GDK_ACTION_MOVE | GDK_ACTION_LINK)) != 0)) + { + /* ask the user what to do with the drop data */ + action = (context->action == GDK_ACTION_ASK) ? thunar_dnd_ask (GTK_WIDGET (standard_view), time, actions) : context->action; + + /* perform the requested action */ + if (G_LIKELY (action != 0)) + succeed = thunar_dnd_perform (GTK_WIDGET (standard_view), file, standard_view->priv->drop_uri_list, action); + } + + /* release the file reference */ + if (G_LIKELY (file != NULL)) + g_object_unref (G_OBJECT (file)); + + /* disable the highlighting and release the drag data */ + thunar_standard_view_drag_leave (view, context, time, standard_view); + + /* tell the peer that we handled the drop */ + gtk_drag_finish (context, succeed, FALSE, time); + } +} + + + +static void +thunar_standard_view_drag_leave (GtkWidget *widget, + GdkDragContext *context, + guint time, + ThunarStandardView *standard_view) +{ + /* disable the drop highlighting around the view */ + if (G_LIKELY (standard_view->priv->drop_highlight)) + { + standard_view->priv->drop_highlight = FALSE; + gtk_widget_queue_draw (GTK_WIDGET (standard_view)); + } + + /* reset the "drop data ready" status and free the URI list */ + if (G_LIKELY (standard_view->priv->drop_data_ready)) + { + thunar_vfs_uri_list_free (standard_view->priv->drop_uri_list); + standard_view->priv->drop_uri_list = NULL; + standard_view->priv->drop_data_ready = FALSE; + } + + /* disable the highlighting of the items in the view */ + (*THUNAR_STANDARD_VIEW_GET_CLASS (standard_view)->highlight_path) (standard_view, NULL); +} + + + +static gboolean +thunar_standard_view_drag_motion (GtkWidget *view, + GdkDragContext *context, + gint x, + gint y, + guint time, + ThunarStandardView *standard_view) +{ + GdkAtom target; + + /* request the drop data on-demand (if we don't have it already) */ + if (G_UNLIKELY (!standard_view->priv->drop_data_ready)) + { + /* check if we can handle that drag data (yet?) */ + target = gtk_drag_dest_find_target (view, context, NULL); + if (G_UNLIKELY (target == GDK_NONE)) + { + /* we cannot handle the drag data */ + gdk_drag_status (context, 0, time); + } + else + { + /* request the drag data from the source */ + gtk_drag_get_data (view, context, target, time); + + /* and deny the drop so far */ + gdk_drag_status (context, 0, time); + } + } + else + { + /* check whether we can drop at (x,y) */ + thunar_standard_view_get_dest_actions (standard_view, context, x, y, time, NULL); + } + + return TRUE; +} + + + +static void +thunar_standard_view_drag_begin (GtkWidget *view, GdkDragContext *context, ThunarStandardView *standard_view) { - GtkTreeIter iter; ThunarFile *file; GdkPixbuf *icon; - GList *selected_items; gint size; - /* query the list of selected items */ - selected_items = (*THUNAR_STANDARD_VIEW_GET_CLASS (standard_view)->get_selected_items) (standard_view); - if (G_LIKELY (selected_items != NULL)) + g_return_if_fail (standard_view->priv->drag_uri_list == NULL); + + /* query the list of selected URIs */ + standard_view->priv->drag_uri_list = thunar_standard_view_get_selected_uris (standard_view); + if (G_LIKELY (standard_view->priv->drag_uri_list != NULL)) { - /* grab the tree iterator for the first selected item */ - if (gtk_tree_model_get_iter (GTK_TREE_MODEL (standard_view->model), &iter, selected_items->data)) + /* determine the first selected file */ + file = thunar_file_get_for_uri (standard_view->priv->drag_uri_list->data, NULL); + if (G_LIKELY (file != NULL)) { - /* determine the first selected file */ - file = thunar_list_model_get_file (THUNAR_LIST_MODEL (standard_view->model), &iter); - if (G_LIKELY (file != NULL)) - { - /* generate an icon based on that file */ - g_object_get (G_OBJECT (standard_view->icon_renderer), "size", &size, NULL); - icon = thunar_file_load_icon (file, standard_view->icon_factory, size); - gtk_drag_set_icon_pixbuf (context, icon, 0, 0); - g_object_unref (G_OBJECT (icon)); - - /* release the file */ - g_object_unref (G_OBJECT (file)); - } + /* generate an icon based on that file */ + g_object_get (G_OBJECT (standard_view->icon_renderer), "size", &size, NULL); + icon = thunar_file_load_icon (file, standard_view->icon_factory, size); + gtk_drag_set_icon_pixbuf (context, icon, 0, 0); + g_object_unref (G_OBJECT (icon)); + + /* release the file */ + g_object_unref (G_OBJECT (file)); } - - /* release the selected items */ - g_list_foreach (selected_items, (GFunc) gtk_tree_path_free, NULL); - g_list_free (selected_items); } } static void -thunar_standard_view_drag_data_get (GtkWidget *widget, +thunar_standard_view_drag_data_get (GtkWidget *view, GdkDragContext *context, GtkSelectionData *selection_data, guint info, @@ -1084,23 +1498,27 @@ thunar_standard_view_drag_data_get (GtkWidget *widget, ThunarStandardView *standard_view) { gchar *uri_string; - GList *uri_list; - - /* transform the list of selected URIs to a string */ - uri_list = thunar_standard_view_get_selected_uris (standard_view); - uri_string = thunar_vfs_uri_list_to_string (uri_list, 0); - thunar_vfs_uri_list_free (uri_list); /* set the URI list for the drag selection */ - gtk_selection_data_set (selection_data, selection_data->target, 8, - (guchar *) uri_string, strlen (uri_string)); - - /* clean up */ + uri_string = thunar_vfs_uri_list_to_string (standard_view->priv->drag_uri_list, 0); + gtk_selection_data_set (selection_data, selection_data->target, 8, (guchar *) uri_string, strlen (uri_string)); g_free (uri_string); } +static void +thunar_standard_view_drag_end (GtkWidget *view, + GdkDragContext *context, + ThunarStandardView *standard_view) +{ + /* release the list of dragged URIs */ + thunar_vfs_uri_list_free (standard_view->priv->drag_uri_list); + standard_view->priv->drag_uri_list = NULL; +} + + + static void thunar_standard_view_renamed (ThunarTextRenderer *text_renderer, const gchar *path_string, @@ -1202,6 +1620,34 @@ thunar_standard_view_loading_unbound (gpointer user_data) +static gboolean +thunar_standard_view_drag_timer (gpointer user_data) +{ + ThunarStandardView *standard_view = THUNAR_STANDARD_VIEW (user_data); + + /* fire up the context menu */ + GDK_THREADS_ENTER (); + thunar_standard_view_context_menu (standard_view, 3, gtk_get_current_event_time ()); + GDK_THREADS_LEAVE (); + + return FALSE; +} + + + +static void +thunar_standard_view_drag_timer_destroy (gpointer user_data) +{ + /* unregister the motion notify and button release event handlers (thread-safe) */ + g_signal_handlers_disconnect_by_func (GTK_BIN (user_data)->child, thunar_standard_view_button_release_event, user_data); + g_signal_handlers_disconnect_by_func (GTK_BIN (user_data)->child, thunar_standard_view_motion_notify_event, user_data); + + /* reset the drag timer source id */ + THUNAR_STANDARD_VIEW (user_data)->priv->drag_timer_id = -1; +} + + + /** * thunar_standard_view_context_menu: * @standard_view : a #ThunarStandardView instance. @@ -1268,6 +1714,59 @@ thunar_standard_view_context_menu (ThunarStandardView *standard_view, +/** + * thunar_standard_view_queue_popup: + * @standard_view : a #ThunarStandardView. + * @event : the right click event. + * + * Schedules a context menu popup in response to + * a right-click button event. Right-click events + * need to be handled in a special way, as the + * user may also start a drag using the right + * mouse button and therefore this function + * schedules a timer, which - once expired - + * opens the context menu. If the user moves + * the mouse prior to expiration, a right-click + * drag (with #GDK_ACTION_ASK) will be started + * instead. + **/ +void +thunar_standard_view_queue_popup (ThunarStandardView *standard_view, + GdkEventButton *event) +{ + GtkSettings *settings; + GtkWidget *view; + gint delay; + + g_return_if_fail (THUNAR_IS_STANDARD_VIEW (standard_view)); + g_return_if_fail (event != NULL); + + /* check if we have already scheduled a drag timer */ + if (G_LIKELY (standard_view->priv->drag_timer_id < 0)) + { + /* remember the new coordinates */ + standard_view->priv->drag_x = event->x; + standard_view->priv->drag_y = event->y; + + /* figure out the real view */ + view = GTK_BIN (standard_view)->child; + + /* we use the menu popup delay here, which should give us good values */ + settings = gtk_settings_get_for_screen (gtk_widget_get_screen (view)); + g_object_get (G_OBJECT (settings), "gtk-menu-popup-delay", &delay, NULL); + + /* schedule the timer */ + standard_view->priv->drag_timer_id = g_timeout_add_full (G_PRIORITY_LOW, delay, thunar_standard_view_drag_timer, + standard_view, thunar_standard_view_drag_timer_destroy); + + /* register the motion notify and the button release events on the real view */ + g_signal_connect (G_OBJECT (view), "button-release-event", G_CALLBACK (thunar_standard_view_button_release_event), standard_view); + g_signal_connect (G_OBJECT (view), "motion-notify-event", G_CALLBACK (thunar_standard_view_motion_notify_event), standard_view); + } +} + + + /** * thunar_standard_view_selection_changed: * @standard_view : a #ThunarStandardView instance. diff --git a/thunar/thunar-standard-view.h b/thunar/thunar-standard-view.h index 8fa549f4c..cdb3efaee 100644 --- a/thunar/thunar-standard-view.h +++ b/thunar/thunar-standard-view.h @@ -44,39 +44,52 @@ struct _ThunarStandardViewClass /* Called by the ThunarStandardView class to let derived classes * connect to and disconnect from the UI manager. */ - void (*connect_ui_manager) (ThunarStandardView *standard_view, - GtkUIManager *ui_manager); - void (*disconnect_ui_manager) (ThunarStandardView *standard_view, - GtkUIManager *ui_manager); + void (*connect_ui_manager) (ThunarStandardView *standard_view, + GtkUIManager *ui_manager); + void (*disconnect_ui_manager) (ThunarStandardView *standard_view, + GtkUIManager *ui_manager); /* Returns the list of currently selected GtkTreePath's, where * both the list and the items are owned by the caller. */ - GList *(*get_selected_items) (ThunarStandardView *standard_view); + GList *(*get_selected_items) (ThunarStandardView *standard_view); /* Selects all items in the view */ - void (*select_all) (ThunarStandardView *standard_view); + void (*select_all) (ThunarStandardView *standard_view); /* Unselects all items in the view */ - void (*unselect_all) (ThunarStandardView *standard_view); + void (*unselect_all) (ThunarStandardView *standard_view); /* Selects the given item */ - void (*select_path) (ThunarStandardView *standard_view, - GtkTreePath *path); + void (*select_path) (ThunarStandardView *standard_view, + GtkTreePath *path); /* Called by the ThunarStandardView class to let derived class * place the cursor on the item/row referred to by path. If * start_editing is TRUE, the derived class should also start * editing that item/row. */ - void (*set_cursor) (ThunarStandardView *standard_view, - GtkTreePath *path, - gboolean start_editing); + void (*set_cursor) (ThunarStandardView *standard_view, + GtkTreePath *path, + gboolean start_editing); /* Called by the ThunarStandardView class to let derived class * scroll the view to the given path. */ - void (*scroll_to_path) (ThunarStandardView *standard_view, - GtkTreePath *path); + void (*scroll_to_path) (ThunarStandardView *standard_view, + GtkTreePath *path); + + /* Returns the path at the given position or NULL if no item/row + * is located at that coordinates. The path is freed by the caller. + */ + GtkTreePath *(*get_path_at_pos) (ThunarStandardView *standard_view, + gint x, + gint y); + + /* Sets the item/row that is highlighted for feedback. NULL is + * passed for path to disable the highlighting. + */ + void (*highlight_path) (ThunarStandardView *standard_view, + GtkTreePath *path); }; struct _ThunarStandardView @@ -106,6 +119,10 @@ GType thunar_standard_view_get_type (void) G_GNUC_CONST; void thunar_standard_view_context_menu (ThunarStandardView *standard_view, guint button, guint32 time); + +void thunar_standard_view_queue_popup (ThunarStandardView *standard_view, + GdkEventButton *event); + void thunar_standard_view_selection_changed (ThunarStandardView *standard_view); G_END_DECLS; -- GitLab