X-Git-Url: http://pileus.org/git/?a=blobdiff_plain;f=gtk%2Fgtkfilechooserdefault.c;h=8987297667fd8402043ffd0074b524b2b8bfa938;hb=72d48463da46cb7abfeceb86289109c4b77ae1cd;hp=d37487adb1e1098331bdd0b4037bbd09f7e55728;hpb=80581c3011871fa000433a881554ffc1e9363468;p=~andy%2Fgtk diff --git a/gtk/gtkfilechooserdefault.c b/gtk/gtkfilechooserdefault.c index d37487adb..898729766 100644 --- a/gtk/gtkfilechooserdefault.c +++ b/gtk/gtkfilechooserdefault.c @@ -39,6 +39,7 @@ #include "gtkframe.h" #include "gtkhbox.h" #include "gtkhpaned.h" +#include "gtkiconfactory.h" #include "gtkicontheme.h" #include "gtkimage.h" #include "gtkintl.h" @@ -52,6 +53,8 @@ #include "gtksizegroup.h" #include "gtkstock.h" #include "gtktable.h" +#include "gtktreednd.h" +#include "gtktreeprivate.h" #include "gtktreeview.h" #include "gtktreemodelsort.h" #include "gtktreeselection.h" @@ -100,19 +103,14 @@ struct _GtkFileChooserDefault /* The file browsing widgets */ GtkWidget *browse_widgets; GtkWidget *browse_shortcuts_tree_view; - GtkWidget *browse_shortcuts_swin; GtkWidget *browse_shortcuts_add_button; GtkWidget *browse_shortcuts_remove_button; - GtkWidget *browse_files_swin; GtkWidget *browse_files_tree_view; - GtkWidget *browse_directories_swin; - GtkWidget *browse_directories_tree_view; GtkWidget *browse_new_folder_button; GtkWidget *browse_path_bar; GtkWidget *browse_extra_align; GtkFileSystemModel *browse_files_model; - GtkFileSystemModel *browse_directories_model; GtkWidget *filter_combo; GtkWidget *preview_box; @@ -146,6 +144,14 @@ struct _GtkFileChooserDefault GtkTreeViewColumn *list_name_column; GtkCellRenderer *list_name_renderer; + guint settings_signal_id; + int icon_size; + +#if 0 + GdkDragContext *shortcuts_drag_context; + GSource *shortcuts_drag_outside_idle; +#endif + /* Flags */ guint local_only : 1; @@ -157,12 +163,17 @@ struct _GtkFileChooserDefault guint changing_folder : 1; guint shortcuts_current_folder_active : 1; guint shortcuts_current_folder_is_volume : 1; + +#if 0 + guint shortcuts_drag_outside : 1; +#endif }; /* Signal IDs */ enum { LOCATION_POPUP, UP_FOLDER, + DOWN_FOLDER, HOME_FOLDER, LAST_SIGNAL }; @@ -189,15 +200,34 @@ enum { /* Identifiers for target types */ enum { + GTK_TREE_MODEL_ROW, TEXT_URI_LIST }; -/* Target types for DnD in the shortcuts list */ -static GtkTargetEntry shortcuts_targets[] = { +/* Target types for dragging from the shortcuts list */ +static GtkTargetEntry shortcuts_source_targets[] = { + { "GTK_TREE_MODEL_ROW", GTK_TARGET_SAME_WIDGET, GTK_TREE_MODEL_ROW } +}; + +static const int num_shortcuts_source_targets = (sizeof (shortcuts_source_targets) + / sizeof (shortcuts_source_targets[0])); + +/* Target types for dropping into the shortcuts list */ +static GtkTargetEntry shortcuts_dest_targets[] = { + { "GTK_TREE_MODEL_ROW", GTK_TARGET_SAME_WIDGET, GTK_TREE_MODEL_ROW }, + { "text/uri-list", 0, TEXT_URI_LIST } +}; + +static const int num_shortcuts_dest_targets = (sizeof (shortcuts_dest_targets) + / sizeof (shortcuts_dest_targets[0])); + +/* Target types for DnD from the file list */ +static GtkTargetEntry file_list_source_targets[] = { { "text/uri-list", 0, TEXT_URI_LIST } }; -static const int num_shortcuts_targets = sizeof (shortcuts_targets) / sizeof (shortcuts_targets[0]); +static const int num_file_list_source_targets = (sizeof (file_list_source_targets) + / sizeof (file_list_source_targets[0])); /* Interesting places in the shortcuts bar */ typedef enum { @@ -211,9 +241,9 @@ typedef enum { SHORTCUTS_CURRENT_FOLDER } ShortcutsIndex; -/* Standard icon size */ -/* FIXME: maybe this should correspond to the font size in the tree views... */ -#define ICON_SIZE 20 +/* Icon size for if we can't get it from the theme */ +#define FALLBACK_ICON_SIZE 20 + #define PREVIEW_HBOX_SPACING 12 #define NUM_LINES 40 #define NUM_CHARS 60 @@ -278,9 +308,11 @@ static void gtk_file_chooser_default_get_resizable_hints (GtkFileCh gboolean *resize_horizontally, gboolean *resize_vertically); static gboolean gtk_file_chooser_default_should_respond (GtkFileChooserEmbed *chooser_embed); +static void gtk_file_chooser_default_initial_focus (GtkFileChooserEmbed *chooser_embed); static void location_popup_handler (GtkFileChooserDefault *impl); static void up_folder_handler (GtkFileChooserDefault *impl); +static void down_folder_handler (GtkFileChooserDefault *impl); static void home_folder_handler (GtkFileChooserDefault *impl); static void update_appearance (GtkFileChooserDefault *impl); @@ -290,9 +322,6 @@ static void check_preview_change (GtkFileChooserDefault *impl); static void filter_combo_changed (GtkComboBox *combo_box, GtkFileChooserDefault *impl); -static void tree_selection_changed (GtkTreeSelection *tree_selection, - GtkFileChooserDefault *impl); - static void shortcuts_row_activated_cb (GtkTreeView *tree_view, GtkTreePath *path, GtkTreeViewColumn *column, @@ -325,11 +354,6 @@ static void add_bookmark_button_clicked_cb (GtkButton *button, static void remove_bookmark_button_clicked_cb (GtkButton *button, GtkFileChooserDefault *impl); -static void tree_name_data_func (GtkTreeViewColumn *tree_column, - GtkCellRenderer *cell, - GtkTreeModel *tree_model, - GtkTreeIter *iter, - gpointer data); static void list_icon_data_func (GtkTreeViewColumn *tree_column, GtkCellRenderer *cell, GtkTreeModel *tree_model, @@ -355,6 +379,37 @@ static void list_mtime_data_func (GtkTreeViewColumn *tree_column, static GObjectClass *parent_class; + + +/* Drag and drop interface declarations */ + +typedef struct { + GtkTreeModelFilter parent; + + GtkFileChooserDefault *impl; +} ShortcutsModelFilter; + +typedef struct { + GtkTreeModelFilterClass parent_class; +} ShortcutsModelFilterClass; + +#define SHORTCUTS_MODEL_FILTER_TYPE (shortcuts_model_filter_get_type ()) +#define SHORTCUTS_MODEL_FILTER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), SHORTCUTS_MODEL_FILTER_TYPE, ShortcutsModelFilter)) + +static void shortcuts_model_filter_drag_source_iface_init (GtkTreeDragSourceIface *iface); + +G_DEFINE_TYPE_WITH_CODE (ShortcutsModelFilter, + shortcuts_model_filter, + GTK_TYPE_TREE_MODEL_FILTER, + G_IMPLEMENT_INTERFACE (GTK_TYPE_TREE_DRAG_SOURCE, + shortcuts_model_filter_drag_source_iface_init)); + +static GtkTreeModel *shortcuts_model_filter_new (GtkFileChooserDefault *impl, + GtkTreeModel *child_model, + GtkTreePath *root); + + + GType _gtk_file_chooser_default_get_type (void) { @@ -438,6 +493,14 @@ gtk_file_chooser_default_class_init (GtkFileChooserDefaultClass *class) NULL, NULL, _gtk_marshal_VOID__VOID, G_TYPE_NONE, 0); + signals[DOWN_FOLDER] = + _gtk_binding_signal_new ("down-folder", + G_OBJECT_CLASS_TYPE (class), + G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION, + G_CALLBACK (down_folder_handler), + NULL, NULL, + _gtk_marshal_VOID__VOID, + G_TYPE_NONE, 0); signals[HOME_FOLDER] = _gtk_binding_signal_new ("home-folder", G_OBJECT_CLASS_TYPE (class), @@ -463,6 +526,15 @@ gtk_file_chooser_default_class_init (GtkFileChooserDefaultClass *class) "up-folder", 0); + gtk_binding_entry_add_signal (binding_set, + GDK_Down, GDK_MOD1_MASK, + "down-folder", + 0); + gtk_binding_entry_add_signal (binding_set, + GDK_KP_Down, GDK_MOD1_MASK, + "down-folder", + 0); + gtk_binding_entry_add_signal (binding_set, GDK_Home, GDK_MOD1_MASK, "home-folder", @@ -508,6 +580,7 @@ gtk_file_chooser_embed_default_iface_init (GtkFileChooserEmbedIface *iface) iface->get_default_size = gtk_file_chooser_default_get_default_size; iface->get_resizable_hints = gtk_file_chooser_default_get_resizable_hints; iface->should_respond = gtk_file_chooser_default_should_respond; + iface->initial_focus = gtk_file_chooser_default_initial_focus; } static void gtk_file_chooser_default_init (GtkFileChooserDefault *impl) @@ -517,7 +590,9 @@ gtk_file_chooser_default_init (GtkFileChooserDefault *impl) impl->use_preview_label = TRUE; impl->select_multiple = FALSE; impl->show_hidden = FALSE; + impl->icon_size = FALLBACK_ICON_SIZE; + gtk_widget_set_redraw_on_allocate (GTK_WIDGET (impl), TRUE); gtk_box_set_spacing (GTK_BOX (impl), 12); } @@ -558,9 +633,6 @@ gtk_file_chooser_default_finalize (GObject *object) if (impl->browse_files_model) g_object_unref (impl->browse_files_model); - if (impl->browse_directories_model) - g_object_unref (impl->browse_directories_model); - if (impl->shortcuts_model) g_object_unref (impl->shortcuts_model); @@ -585,7 +657,7 @@ error_message_with_parent (GtkWindow *parent, dialog = gtk_message_dialog_new (parent, GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_ERROR, - GTK_BUTTONS_CLOSE, + GTK_BUTTONS_OK, "%s", msg); gtk_dialog_run (GTK_DIALOG (dialog)); @@ -614,14 +686,19 @@ error_dialog (GtkFileChooserDefault *impl, const GtkFilePath *path, GError *error) { - char *text; + g_return_if_fail (path != NULL); - text = g_strdup_printf (msg, - gtk_file_path_get_string (path), - error->message); - error_message (impl, text); - g_free (text); - g_error_free (error); + if (error) + { + char *uri = gtk_file_system_path_to_uri (impl->file_system, path); + char *text = g_strdup_printf (msg, + uri, + error->message); + error_message (impl, text); + g_free (text); + g_free (uri); + g_error_free (error); + } } /* Displays an error message about not being able to get information for a file. @@ -655,13 +732,15 @@ error_building_filename_dialog (GtkFileChooserDefault *impl, const char *file_part, GError *error) { + char *uri; char *msg; + uri = gtk_file_system_path_to_uri (impl->file_system, base_path); msg = g_strdup_printf (_("Could not build file name from '%s' and '%s':\n%s"), - gtk_file_path_get_string (base_path), - file_part, + uri, file_part, error->message); error_message (impl, msg); + g_free (uri); g_free (msg); g_error_free (error); } @@ -749,42 +828,128 @@ set_preview_widget (GtkFileChooserDefault *impl, update_preview_widget_visibility (impl); } -/* Clears the selection in the shortcuts tree */ +/* Re-reads all the icons for the shortcuts, used when the theme changes */ static void -shortcuts_unselect_all (GtkFileChooserDefault *impl) +shortcuts_reload_icons (GtkFileChooserDefault *impl) +{ + GtkTreeIter iter; + int i; + int bookmarks_separator_idx; + int current_folder_separator_idx; + int volumes_idx; + + if (!gtk_tree_model_get_iter_first (GTK_TREE_MODEL (impl->shortcuts_model), &iter)) + return; + + bookmarks_separator_idx = shortcuts_get_index (impl, SHORTCUTS_BOOKMARKS_SEPARATOR); + current_folder_separator_idx = shortcuts_get_index (impl, SHORTCUTS_CURRENT_FOLDER_SEPARATOR); + volumes_idx = shortcuts_get_index (impl, SHORTCUTS_VOLUMES); + + i = 0; + + do { + gpointer data; + gboolean pixbuf_visible; + GdkPixbuf *pixbuf; + + gtk_tree_model_get (GTK_TREE_MODEL (impl->shortcuts_model), &iter, + SHORTCUTS_COL_PATH, &data, + SHORTCUTS_COL_PIXBUF_VISIBLE, &pixbuf_visible, + -1); + + if (!pixbuf_visible || !data) + goto next_iter; + + if (i >= volumes_idx && i < volumes_idx + impl->num_volumes) + { + GtkFileSystemVolume *volume; + + volume = data; + pixbuf = gtk_file_system_volume_render_icon (impl->file_system, volume, GTK_WIDGET (impl), + impl->icon_size, NULL); + } + else + { + const GtkFilePath *path; + + path = data; + pixbuf = gtk_file_system_render_icon (impl->file_system, path, GTK_WIDGET (impl), + impl->icon_size, NULL); + } + + gtk_list_store_set (impl->shortcuts_model, &iter, + SHORTCUTS_COL_PIXBUF, pixbuf, + -1); + + next_iter: + i++; + } while (gtk_tree_model_iter_next (GTK_TREE_MODEL (impl->shortcuts_model),&iter)); +} + +/* If a shortcut corresponds to the current folder, selects it */ +static void +shortcuts_find_current_folder (GtkFileChooserDefault *impl) { GtkTreeSelection *selection; + int pos; + GtkTreePath *path; selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (impl->browse_shortcuts_tree_view)); - gtk_tree_selection_unselect_all (selection); + + pos = shortcut_find_position (impl, impl->current_folder); + if (pos == -1) + { + gtk_tree_selection_unselect_all (selection); + return; + } + + path = gtk_tree_path_new_from_indices (pos, -1); + gtk_tree_selection_select_path (selection, path); + gtk_tree_path_free (path); +} + +/* Returns whether a path is a folder */ +static gboolean +check_is_folder (GtkFileSystem *file_system, const GtkFilePath *path, GError **error) +{ + GtkFileFolder *folder; + + folder = gtk_file_system_get_folder (file_system, path, + GTK_FILE_INFO_DISPLAY_NAME, + error); + if (!folder) + return FALSE; + + g_object_unref (folder); + return TRUE; } /* Convenience function to get the display name and icon info for a path */ static GtkFileInfo * -get_file_info (GtkFileSystem *file_system, const GtkFilePath *path, GError **error) +get_file_info (GtkFileSystem *file_system, const GtkFilePath *path, gboolean name_only, GError **error) { GtkFilePath *parent_path; GtkFileFolder *parent_folder; GtkFileInfo *info; + info = NULL; + if (!gtk_file_system_get_parent (file_system, path, &parent_path, error)) return NULL; - parent_folder = gtk_file_system_get_folder (file_system, parent_path, + parent_folder = gtk_file_system_get_folder (file_system, parent_path ? parent_path : path, GTK_FILE_INFO_DISPLAY_NAME -#if 0 - | GTK_FILE_INFO_ICON -#endif - | GTK_FILE_INFO_IS_FOLDER, + | (name_only ? 0 : GTK_FILE_INFO_IS_FOLDER), error); - gtk_file_path_free (parent_path); - if (!parent_folder) - return NULL; + goto out; - info = gtk_file_folder_get_info (parent_folder, path, error); + info = gtk_file_folder_get_info (parent_folder, parent_path ? path : NULL, error); g_object_unref (parent_folder); + out: + + gtk_file_path_free (parent_path); return info; } @@ -810,30 +975,30 @@ shortcuts_insert_path (GtkFileChooserDefault *impl, { data = volume; label_copy = gtk_file_system_volume_get_display_name (impl->file_system, volume); - pixbuf = gtk_file_system_volume_render_icon (impl->file_system, - volume, - GTK_WIDGET (impl), - ICON_SIZE, - NULL); + pixbuf = gtk_file_system_volume_render_icon (impl->file_system, volume, GTK_WIDGET (impl), + impl->icon_size, NULL); } else { - GtkFileInfo *info; - - info = get_file_info (impl->file_system, path, error); - if (!info) + if (!check_is_folder (impl->file_system, path, error)) return FALSE; - data = gtk_file_path_copy (path); - if (label) label_copy = g_strdup (label); else - label_copy = g_strdup (gtk_file_info_get_display_name (info)); + { + GtkFileInfo *info = get_file_info (impl->file_system, path, TRUE, error); + + if (!info) + return FALSE; - pixbuf = gtk_file_system_render_icon (impl->file_system, path, GTK_WIDGET (impl), ICON_SIZE, NULL); + label_copy = g_strdup (gtk_file_info_get_display_name (info)); + gtk_file_info_free (info); + } - gtk_file_info_free (info); + data = gtk_file_path_copy (path); + pixbuf = gtk_file_system_render_icon (impl->file_system, path, GTK_WIDGET (impl), + impl->icon_size, NULL); } if (pos == -1) @@ -916,6 +1081,10 @@ shortcuts_append_paths (GtkFileChooserDefault *impl, path = paths->data; error = NULL; + if (impl->local_only && + !gtk_file_system_path_is_local (impl->file_system, path)) + continue; + /* NULL GError, but we don't really want to show error boxes here */ if (shortcuts_insert_path (impl, start_row + num_inserted, FALSE, NULL, path, NULL, TRUE, NULL)) num_inserted++; @@ -1029,6 +1198,10 @@ shortcuts_add_volumes (GtkFileChooserDefault *impl) int start_row; GSList *list, *l; int n; + gboolean old_changing_folders; + + old_changing_folders = impl->changing_folder; + impl->changing_folder = TRUE; start_row = shortcuts_get_index (impl, SHORTCUTS_VOLUMES); shortcuts_remove_rows (impl, start_row, impl->num_volumes, volume_remove_cb); @@ -1043,8 +1216,24 @@ shortcuts_add_volumes (GtkFileChooserDefault *impl) GtkFileSystemVolume *volume; volume = l->data; - shortcuts_insert_path (impl, start_row + n, TRUE, volume, NULL, NULL, FALSE, NULL); - n++; + + if (impl->local_only) + { + GtkFilePath *base_path = gtk_file_system_volume_get_base_path (impl->file_system, volume); + gboolean is_local = gtk_file_system_path_is_local (impl->file_system, base_path); + gtk_file_path_free (base_path); + + if (!is_local) + { + gtk_file_system_volume_free (impl->file_system, volume); + continue; + } + } + + if (shortcuts_insert_path (impl, start_row + n, TRUE, volume, NULL, NULL, FALSE, NULL)) + n++; + else + gtk_file_system_volume_free (impl->file_system, volume); } impl->num_volumes = n; @@ -1052,6 +1241,8 @@ shortcuts_add_volumes (GtkFileChooserDefault *impl) if (impl->shortcuts_filter_model) gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (impl->shortcuts_filter_model)); + + impl->changing_folder = old_changing_folders; } /* Used from shortcuts_remove_rows() */ @@ -1088,6 +1279,10 @@ static void shortcuts_add_bookmarks (GtkFileChooserDefault *impl) { GSList *bookmarks; + gboolean old_changing_folders; + + old_changing_folders = impl->changing_folder; + impl->changing_folder = TRUE; if (impl->num_bookmarks > 0) { @@ -1108,6 +1303,8 @@ shortcuts_add_bookmarks (GtkFileChooserDefault *impl) } if (impl->shortcuts_filter_model) gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (impl->shortcuts_filter_model)); + + impl->changing_folder = old_changing_folders; } /* Appends a separator and a row to the shortcuts list for the current folder */ @@ -1237,7 +1434,10 @@ shortcuts_model_create (GtkFileChooserDefault *impl) shortcuts_add_bookmarks (impl); } - impl->shortcuts_filter_model = gtk_tree_model_filter_new (GTK_TREE_MODEL (impl->shortcuts_model), NULL); + impl->shortcuts_filter_model = shortcuts_model_filter_new (impl, + GTK_TREE_MODEL (impl->shortcuts_model), + NULL); + gtk_tree_model_filter_set_visible_func (GTK_TREE_MODEL_FILTER (impl->shortcuts_filter_model), shortcuts_filter_cb, impl, @@ -1291,9 +1491,11 @@ renderer_edited_cb (GtkCellRendererText *cell_renderer_text, error = NULL; if (!gtk_file_system_create_folder (impl->file_system, file_path, &error)) - error_dialog (impl, - _("Could not create folder %s:\n%s"), - file_path, error); + { + error_dialog (impl, + _("Could not create folder %s:\n%s"), + file_path, error); + } gtk_file_path_free (file_path); @@ -1360,49 +1562,6 @@ button_new (GtkFileChooserDefault *impl, return button; } -/* Creates the widgets for the folder tree */ -static GtkWidget * -create_folder_tree (GtkFileChooserDefault *impl) -{ - GtkTreeSelection *selection; - - /* Scrolled window */ - - impl->browse_directories_swin = gtk_scrolled_window_new (NULL, NULL); - gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (impl->browse_directories_swin), - GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); - gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (impl->browse_directories_swin), - GTK_SHADOW_IN); - /* Tree */ - - impl->browse_directories_tree_view = gtk_tree_view_new (); - gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (impl->browse_directories_tree_view), FALSE); - - selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (impl->browse_directories_tree_view)); - gtk_tree_view_enable_model_drag_source (GTK_TREE_VIEW (impl->browse_directories_tree_view), - GDK_BUTTON1_MASK, - shortcuts_targets, - num_shortcuts_targets, - GDK_ACTION_COPY); - - g_signal_connect (selection, "changed", - G_CALLBACK (tree_selection_changed), impl); - - gtk_container_add (GTK_CONTAINER (impl->browse_directories_swin), impl->browse_directories_tree_view); - gtk_widget_show (impl->browse_directories_tree_view); - - /* Column */ - - gtk_tree_view_insert_column_with_data_func (GTK_TREE_VIEW (impl->browse_directories_tree_view), 0, - _("Name"), - gtk_cell_renderer_text_new (), - tree_name_data_func, impl, NULL); - gtk_tree_view_set_search_column (GTK_TREE_VIEW (impl->browse_directories_tree_view), - GTK_FILE_SYSTEM_MODEL_DISPLAY_NAME); - - return impl->browse_directories_swin; -} - /* Looks for a path among the shortcuts; returns its index or -1 if it doesn't exist */ static int shortcut_find_position (GtkFileChooserDefault *impl, @@ -1466,51 +1625,35 @@ shortcut_find_position (GtkFileChooserDefault *impl, } /* Tries to add a bookmark from a path name */ -static void +static gboolean shortcuts_add_bookmark_from_path (GtkFileChooserDefault *impl, - const GtkFilePath *path) + const GtkFilePath *path, + int pos) { - GtkFileInfo *info; GError *error; if (shortcut_find_position (impl, path) != -1) - return; + return FALSE; + /* FIXME: this check really belongs in gtk_file_system_insert_bookmark. */ error = NULL; - info = get_file_info (impl->file_system, path, &error); - - if (!info) - error_getting_info_dialog (impl, path, error); - else if (!gtk_file_info_get_is_folder (info)) + if (!check_is_folder (impl->file_system, path, &error)) { - char *msg; - - msg = g_strdup_printf (_("Could not add bookmark for %s because it is not a folder."), - gtk_file_path_get_string (path)); - error_message (impl, msg); - g_free (msg); + error_dialog (impl, + _("Could not add bookmark for %s because it is not a folder."), + path, + error); + return FALSE; } - else + + error = NULL; + if (!gtk_file_system_insert_bookmark (impl->file_system, path, pos, &error)) { - error = NULL; - if (!gtk_file_system_insert_bookmark (impl->file_system, path, -1, &error)) - error_could_not_add_bookmark_dialog (impl, path, error); + error_could_not_add_bookmark_dialog (impl, path, error); + return FALSE; } -} - -/* Returns the GtkTreeSelection that makes sense for the mode which the file chooser is in */ -static GtkTreeSelection * -get_selection (GtkFileChooserDefault *impl) -{ - GtkWidget *tree_view; - if (impl->action == GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER || - impl->action == GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER) - tree_view = impl->browse_directories_tree_view; - else - tree_view = impl->browse_files_tree_view; - - return gtk_tree_view_get_selection (GTK_TREE_VIEW (tree_view)); + return TRUE; } static void @@ -1524,22 +1667,13 @@ add_bookmark_foreach_cb (GtkTreeModel *model, GtkTreeIter child_iter; const GtkFilePath *file_path; - impl = GTK_FILE_CHOOSER_DEFAULT (data); + impl = (GtkFileChooserDefault *) data; - if (impl->action == GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER || - impl->action == GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER) - { - fs_model = impl->browse_directories_model; - child_iter = *iter; - } - else - { - fs_model = impl->browse_files_model; - gtk_tree_model_sort_convert_iter_to_child_iter (impl->sort_model, &child_iter, iter); - } + fs_model = impl->browse_files_model; + gtk_tree_model_sort_convert_iter_to_child_iter (impl->sort_model, &child_iter, iter); file_path = _gtk_file_system_model_get_path (GTK_FILE_SYSTEM_MODEL (fs_model), &child_iter); - shortcuts_add_bookmark_from_path (impl, file_path); + shortcuts_add_bookmark_from_path (impl, file_path, -1); } /* Callback used when the "Add bookmark" button is clicked */ @@ -1549,10 +1683,10 @@ add_bookmark_button_clicked_cb (GtkButton *button, { GtkTreeSelection *selection; - selection = get_selection (impl); + selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (impl->browse_files_tree_view)); if (gtk_tree_selection_count_selected_rows (selection) == 0) - shortcuts_add_bookmark_from_path (impl, impl->current_folder); + shortcuts_add_bookmark_from_path (impl, impl->current_folder, -1); else gtk_tree_selection_selected_foreach (selection, add_bookmark_foreach_cb, @@ -1585,10 +1719,12 @@ remove_bookmark_button_clicked_cb (GtkButton *button, error = NULL; if (!gtk_file_system_remove_bookmark (impl->file_system, path, &error)) - error_dialog (impl, - _("Could not remove bookmark for %s:\n%s"), - path, - error); + { + error_dialog (impl, + _("Could not remove bookmark for %s:\n%s"), + path, + error); + } } } @@ -1633,10 +1769,11 @@ selection_check (GtkFileChooserDefault *impl, struct selection_check_closure closure; GtkTreeSelection *selection; + selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (impl->browse_files_tree_view)); + if (impl->action == GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER || impl->action == GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER) { - selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (impl->browse_directories_tree_view)); if (gtk_tree_selection_count_selected_rows (selection) == 0) closure.empty = TRUE; else @@ -1656,7 +1793,6 @@ selection_check (GtkFileChooserDefault *impl, closure.all_files = TRUE; closure.all_folders = TRUE; - selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (impl->browse_files_tree_view)); gtk_tree_selection_selected_foreach (selection, selection_check_foreach_cb, &closure); @@ -1686,7 +1822,7 @@ bookmarks_check_add_sensitivity (GtkFileChooserDefault *impl) /* Check selection */ - selection = get_selection (impl); + selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (impl->browse_files_tree_view)); if (gtk_tree_selection_count_selected_rows (selection) == 0) active = (shortcut_find_position (impl, impl->current_folder) == -1); @@ -1723,6 +1859,378 @@ bookmarks_check_remove_sensitivity (GtkFileChooserDefault *impl) gtk_widget_set_sensitive (impl->browse_shortcuts_remove_button, removable); } +/* GtkWidget::drag-begin handler for the shortcuts list. */ +static void +shortcuts_drag_begin_cb (GtkWidget *widget, + GdkDragContext *context, + GtkFileChooserDefault *impl) +{ +#if 0 + impl->shortcuts_drag_context = g_object_ref (context); +#endif +} + +#if 0 +/* Removes the idle handler for outside drags */ +static void +shortcuts_cancel_drag_outside_idle (GtkFileChooserDefault *impl) +{ + if (!impl->shortcuts_drag_outside_idle) + return; + + g_source_destroy (impl->shortcuts_drag_outside_idle); + impl->shortcuts_drag_outside_idle = NULL; +} +#endif + +/* GtkWidget::drag-end handler for the shortcuts list. */ +static void +shortcuts_drag_end_cb (GtkWidget *widget, + GdkDragContext *context, + GtkFileChooserDefault *impl) +{ +#if 0 + g_object_unref (impl->shortcuts_drag_context); + + shortcuts_cancel_drag_outside_idle (impl); + + if (!impl->shortcuts_drag_outside) + return; + + gtk_button_clicked (GTK_BUTTON (impl->browse_shortcuts_remove_button)); + + impl->shortcuts_drag_outside = FALSE; +#endif +} + +/* GtkWidget::drag-data-delete handler for the shortcuts list. */ +static void +shortcuts_drag_data_delete_cb (GtkWidget *widget, + GdkDragContext *context, + GtkFileChooserDefault *impl) +{ + g_signal_stop_emission_by_name (widget, "drag-data-delete"); +} + +#if 0 +/* Creates a suitable drag cursor to indicate that the selected bookmark will be + * deleted or not. + */ +static void +shortcuts_drag_set_delete_cursor (GtkFileChooserDefault *impl, + gboolean delete) +{ + GtkTreeView *tree_view; + GtkTreeSelection *selection; + GtkTreeIter iter, child_iter; + GtkTreePath *path; + GdkPixmap *row_pixmap; + GdkBitmap *mask; + int row_pixmap_y; + int cell_y; + + tree_view = GTK_TREE_VIEW (impl->browse_shortcuts_tree_view); + + /* Find the selected path and get its drag pixmap */ + + selection = gtk_tree_view_get_selection (tree_view); + if (!gtk_tree_selection_get_selected (selection, NULL, &iter)) + g_assert_not_reached (); + + gtk_tree_model_filter_convert_iter_to_child_iter (GTK_TREE_MODEL_FILTER (impl->shortcuts_filter_model), + &child_iter, + &iter); + + path = gtk_tree_model_get_path (GTK_TREE_MODEL (impl->shortcuts_model), &child_iter); + + row_pixmap = gtk_tree_view_create_row_drag_icon (tree_view, path); + gtk_tree_path_free (path); + + mask = NULL; + row_pixmap_y = 0; + + if (delete) + { + GdkPixbuf *pixbuf; + + pixbuf = gtk_widget_render_icon (impl->browse_shortcuts_tree_view, + GTK_STOCK_DELETE, + GTK_ICON_SIZE_DND, + NULL); + if (pixbuf) + { + GdkPixmap *composite; + int row_pixmap_width, row_pixmap_height; + int pixbuf_width, pixbuf_height; + int composite_width, composite_height; + int pixbuf_x, pixbuf_y; + GdkGC *gc, *mask_gc; + GdkColor color; + GdkBitmap *pixbuf_mask; + + /* Create pixmap and mask for composite image */ + + gdk_drawable_get_size (row_pixmap, &row_pixmap_width, &row_pixmap_height); + pixbuf_width = gdk_pixbuf_get_width (pixbuf); + pixbuf_height = gdk_pixbuf_get_height (pixbuf); + + composite_width = MAX (row_pixmap_width, pixbuf_width); + composite_height = MAX (row_pixmap_height, pixbuf_height); + + row_pixmap_y = (composite_height - row_pixmap_height) / 2; + + if (gtk_widget_get_direction (impl->browse_shortcuts_tree_view) == GTK_TEXT_DIR_RTL) + pixbuf_x = 0; + else + pixbuf_x = composite_width - pixbuf_width; + + pixbuf_y = (composite_height - pixbuf_height) / 2; + + composite = gdk_pixmap_new (row_pixmap, composite_width, composite_height, -1); + gc = gdk_gc_new (composite); + + mask = gdk_pixmap_new (row_pixmap, composite_width, composite_height, 1); + mask_gc = gdk_gc_new (mask); + color.pixel = 0; + gdk_gc_set_foreground (mask_gc, &color); + gdk_draw_rectangle (mask, mask_gc, TRUE, 0, 0, composite_width, composite_height); + + color.red = 0xffff; + color.green = 0xffff; + color.blue = 0xffff; + gdk_gc_set_rgb_fg_color (gc, &color); + gdk_draw_rectangle (composite, gc, TRUE, 0, 0, composite_width, composite_height); + + /* Composite the row pixmap and the pixbuf */ + + gdk_pixbuf_render_pixmap_and_mask_for_colormap + (pixbuf, + gtk_widget_get_colormap (impl->browse_shortcuts_tree_view), + NULL, &pixbuf_mask, 128); + gdk_draw_drawable (mask, mask_gc, pixbuf_mask, + 0, 0, + pixbuf_x, pixbuf_y, + pixbuf_width, pixbuf_height); + g_object_unref (pixbuf_mask); + + gdk_draw_drawable (composite, gc, row_pixmap, + 0, 0, + 0, row_pixmap_y, + row_pixmap_width, row_pixmap_height); + color.pixel = 1; + gdk_gc_set_foreground (mask_gc, &color); + gdk_draw_rectangle (mask, mask_gc, TRUE, 0, row_pixmap_y, row_pixmap_width, row_pixmap_height); + + gdk_draw_pixbuf (composite, gc, pixbuf, + 0, 0, + pixbuf_x, pixbuf_y, + pixbuf_width, pixbuf_height, + GDK_RGB_DITHER_MAX, + 0, 0); + + g_object_unref (pixbuf); + g_object_unref (row_pixmap); + + row_pixmap = composite; + } + } + + /* The hotspot offsets here are copied from gtk_tree_view_drag_begin(), ugh */ + + gtk_tree_view_get_path_at_pos (tree_view, + tree_view->priv->press_start_x, + tree_view->priv->press_start_y, + NULL, + NULL, + NULL, + &cell_y); + + gtk_drag_set_icon_pixmap (impl->shortcuts_drag_context, + gdk_drawable_get_colormap (row_pixmap), + row_pixmap, + mask, + tree_view->priv->press_start_x + 1, + row_pixmap_y + cell_y + 1); + + g_object_unref (row_pixmap); + if (mask) + g_object_unref (mask); +} + +/* We set the delete cursor and the shortcuts_drag_outside flag in an idle + * handler so that we can tell apart the drag_leave event that comes right + * before a drag_drop, from a normal drag_leave. We don't want to set the + * cursor nor the flag in the latter case. + */ +static gboolean +shortcuts_drag_outside_idle_cb (GtkFileChooserDefault *impl) +{ + shortcuts_drag_set_delete_cursor (impl, TRUE); + impl->shortcuts_drag_outside = TRUE; + + shortcuts_cancel_drag_outside_idle (impl); + return FALSE; +} +#endif + +/* GtkWidget::drag-leave handler for the shortcuts list. We unhighlight the + * drop position. + */ +static void +shortcuts_drag_leave_cb (GtkWidget *widget, + GdkDragContext *context, + guint time_, + GtkFileChooserDefault *impl) +{ +#if 0 + if (gtk_drag_get_source_widget (context) == widget && !impl->shortcuts_drag_outside_idle) + { + impl->shortcuts_drag_outside_idle = g_idle_source_new (); + g_source_set_closure (impl->shortcuts_drag_outside_idle, + g_cclosure_new_object (G_CALLBACK (shortcuts_drag_outside_idle_cb), + G_OBJECT (impl))); + g_source_attach (impl->shortcuts_drag_outside_idle, NULL); + } +#endif + + gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (impl->browse_shortcuts_tree_view), + NULL, + GTK_TREE_VIEW_DROP_BEFORE); + + g_signal_stop_emission_by_name (widget, "drag-leave"); +} + +/* Computes the appropriate row and position for dropping */ +static void +shortcuts_compute_drop_position (GtkFileChooserDefault *impl, + int x, + int y, + GtkTreePath **path, + GtkTreeViewDropPosition *pos) +{ + GtkTreeView *tree_view; + GtkTreeViewColumn *column; + int cell_y; + GdkRectangle cell; + int row; + int bookmarks_index; + + tree_view = GTK_TREE_VIEW (impl->browse_shortcuts_tree_view); + + bookmarks_index = shortcuts_get_index (impl, SHORTCUTS_BOOKMARKS); + + if (!gtk_tree_view_get_path_at_pos (tree_view, + x, + y - TREE_VIEW_HEADER_HEIGHT (tree_view), + path, + &column, + NULL, + &cell_y)) + { + row = bookmarks_index + impl->num_bookmarks - 1; + *path = gtk_tree_path_new_from_indices (row, -1); + *pos = GTK_TREE_VIEW_DROP_AFTER; + return; + } + + row = *gtk_tree_path_get_indices (*path); + gtk_tree_view_get_background_area (tree_view, *path, column, &cell); + gtk_tree_path_free (*path); + + if (row < bookmarks_index) + { + row = bookmarks_index; + *pos = GTK_TREE_VIEW_DROP_BEFORE; + } + else if (row > bookmarks_index + impl->num_bookmarks - 1) + { + row = bookmarks_index + impl->num_bookmarks - 1; + *pos = GTK_TREE_VIEW_DROP_AFTER; + } + else + { + if (cell_y < cell.height / 2) + *pos = GTK_TREE_VIEW_DROP_BEFORE; + else + *pos = GTK_TREE_VIEW_DROP_AFTER; + } + + *path = gtk_tree_path_new_from_indices (row, -1); +} + +/* GtkWidget::drag-motion handler for the shortcuts list. We basically + * implement the destination side of DnD by hand, due to limitations in + * GtkTreeView's DnD API. + */ +static gboolean +shortcuts_drag_motion_cb (GtkWidget *widget, + GdkDragContext *context, + gint x, + gint y, + guint time_, + GtkFileChooserDefault *impl) +{ + GtkTreePath *path; + GtkTreeViewDropPosition pos; + GdkDragAction action; + +#if 0 + if (gtk_drag_get_source_widget (context) == widget) + { + shortcuts_cancel_drag_outside_idle (impl); + + if (impl->shortcuts_drag_outside) + { + shortcuts_drag_set_delete_cursor (impl, FALSE); + impl->shortcuts_drag_outside = FALSE; + } + } +#endif + + if (context->suggested_action == GDK_ACTION_COPY || (context->actions & GDK_ACTION_COPY) != 0) + action = GDK_ACTION_COPY; + else if (context->suggested_action == GDK_ACTION_MOVE || (context->actions & GDK_ACTION_MOVE) != 0) + action = GDK_ACTION_MOVE; + else + { + action = 0; + goto out; + } + + shortcuts_compute_drop_position (impl, x, y, &path, &pos); + gtk_tree_view_set_drag_dest_row (GTK_TREE_VIEW (impl->browse_shortcuts_tree_view), path, pos); + gtk_tree_path_free (path); + + out: + + g_signal_stop_emission_by_name (widget, "drag-motion"); + + if (action != 0) + { + gdk_drag_status (context, action, time_); + return TRUE; + } + else + return FALSE; +} + +/* GtkWidget::drag-drop handler for the shortcuts list. */ +static gboolean +shortcuts_drag_drop_cb (GtkWidget *widget, + GdkDragContext *context, + gint x, + gint y, + guint time_, + GtkFileChooserDefault *impl) +{ +#if 0 + shortcuts_cancel_drag_outside_idle (impl); +#endif + + g_signal_stop_emission_by_name (widget, "drag-drop"); + return TRUE; +} + /* Converts raw selection data from text/uri-list to a list of strings */ static GSList * split_uris (const char *data) @@ -1750,25 +2258,15 @@ split_uris (const char *data) return uris; } -/* Callback used when we get the drag data for the bookmarks list. We add the - * received URIs as bookmarks if they are folders. - */ +/* Parses a "text/uri-list" string and inserts its URIs as bookmarks */ static void -shortcuts_drag_data_received_cb (GtkWidget *widget, - GdkDragContext *context, - gint x, - gint y, - GtkSelectionData *selection_data, - guint info, - guint time_, - gpointer data) +shortcuts_drop_uris (GtkFileChooserDefault *impl, + const char *data, + int position) { - GtkFileChooserDefault *impl; GSList *uris, *l; - impl = GTK_FILE_CHOOSER_DEFAULT (data); - - uris = split_uris (selection_data->data); + uris = split_uris (data); for (l = uris; l; l = l->next) { @@ -1780,7 +2278,9 @@ shortcuts_drag_data_received_cb (GtkWidget *widget, if (path) { - shortcuts_add_bookmark_from_path (impl, path); + if (shortcuts_add_bookmark_from_path (impl, path, position)) + position++; + gtk_file_path_free (path); } else @@ -1799,6 +2299,103 @@ shortcuts_drag_data_received_cb (GtkWidget *widget, g_slist_free (uris); } +/* Reorders the selected bookmark to the specified position */ +static void +shortcuts_reorder (GtkFileChooserDefault *impl, + int new_position) +{ + GtkTreeSelection *selection; + GtkTreeIter iter, child_iter; + GtkTreePath *path; + int old_position; + int bookmarks_index; + const GtkFilePath *file_path; + GtkFilePath *file_path_copy; + GError *error; + + /* Get the selected path */ + + selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (impl->browse_shortcuts_tree_view)); + if (!gtk_tree_selection_get_selected (selection, NULL, &iter)) + g_assert_not_reached (); + + gtk_tree_model_filter_convert_iter_to_child_iter (GTK_TREE_MODEL_FILTER (impl->shortcuts_filter_model), + &child_iter, + &iter); + + path = gtk_tree_model_get_path (GTK_TREE_MODEL (impl->shortcuts_model), &child_iter); + old_position = *gtk_tree_path_get_indices (path); + gtk_tree_path_free (path); + + bookmarks_index = shortcuts_get_index (impl, SHORTCUTS_BOOKMARKS); + old_position -= bookmarks_index; + g_assert (old_position >= 0 && old_position < impl->num_bookmarks); + + gtk_tree_model_get (GTK_TREE_MODEL (impl->shortcuts_model), &child_iter, + SHORTCUTS_COL_PATH, &file_path, + -1); + file_path_copy = gtk_file_path_copy (file_path); /* removal below will free file_path, so we need a copy */ + + /* Remove the path from the old position and insert it in the new one */ + + if (new_position > old_position) + new_position--; + + if (old_position == new_position) + return; + + error = NULL; + if (gtk_file_system_remove_bookmark (impl->file_system, file_path_copy, &error)) + shortcuts_add_bookmark_from_path (impl, file_path_copy, new_position); + else + error_could_not_add_bookmark_dialog (impl, file_path_copy, error); + + gtk_file_path_free (file_path_copy); +} + +/* Callback used when we get the drag data for the bookmarks list. We add the + * received URIs as bookmarks if they are folders. + */ +static void +shortcuts_drag_data_received_cb (GtkWidget *widget, + GdkDragContext *context, + gint x, + gint y, + GtkSelectionData *selection_data, + guint info, + guint time_, + gpointer data) +{ + GtkFileChooserDefault *impl; + GtkTreePath *tree_path; + GtkTreeViewDropPosition tree_pos; + int position; + int bookmarks_index; + + impl = GTK_FILE_CHOOSER_DEFAULT (data); + + /* Compute position */ + + bookmarks_index = shortcuts_get_index (impl, SHORTCUTS_BOOKMARKS); + + shortcuts_compute_drop_position (impl, x, y, &tree_path, &tree_pos); + position = *gtk_tree_path_get_indices (tree_path); + gtk_tree_path_free (tree_path); + + if (tree_pos == GTK_TREE_VIEW_DROP_AFTER) + position++; + + g_assert (position >= bookmarks_index); + position -= bookmarks_index; + + if (selection_data->target == gdk_atom_intern ("text/uri-list", FALSE)) + shortcuts_drop_uris (impl, selection_data->data, position); + else if (selection_data->target == gdk_atom_intern ("GTK_TREE_MODEL_ROW", FALSE)) + shortcuts_reorder (impl, position); + + g_signal_stop_emission_by_name (widget, "drag-data-received"); +} + /* Callback used when the selection in the shortcuts tree changes */ static void shortcuts_selection_changed_cb (GtkTreeSelection *selection, @@ -1811,18 +2408,19 @@ shortcuts_selection_changed_cb (GtkTreeSelection *selection, static GtkWidget * shortcuts_list_create (GtkFileChooserDefault *impl) { + GtkWidget *swin; GtkTreeSelection *selection; GtkTreeViewColumn *column; GtkCellRenderer *renderer; /* Scrolled window */ - impl->browse_shortcuts_swin = gtk_scrolled_window_new (NULL, NULL); - gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (impl->browse_shortcuts_swin), + swin = gtk_scrolled_window_new (NULL, NULL); + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (swin), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); - gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (impl->browse_shortcuts_swin), + gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (swin), GTK_SHADOW_IN); - gtk_widget_show (impl->browse_shortcuts_swin); + gtk_widget_show (swin); /* Tree */ @@ -1831,11 +2429,17 @@ shortcuts_list_create (GtkFileChooserDefault *impl) gtk_tree_view_set_model (GTK_TREE_VIEW (impl->browse_shortcuts_tree_view), impl->shortcuts_filter_model); + gtk_tree_view_enable_model_drag_source (GTK_TREE_VIEW (impl->browse_shortcuts_tree_view), + GDK_BUTTON1_MASK, + shortcuts_source_targets, + num_shortcuts_source_targets, + GDK_ACTION_MOVE); + gtk_drag_dest_set (impl->browse_shortcuts_tree_view, GTK_DEST_DEFAULT_ALL, - shortcuts_targets, - num_shortcuts_targets, - GDK_ACTION_COPY); + shortcuts_dest_targets, + num_shortcuts_dest_targets, + GDK_ACTION_COPY | GDK_ACTION_MOVE); selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (impl->browse_shortcuts_tree_view)); gtk_tree_selection_set_mode (selection, GTK_SELECTION_BROWSE); @@ -1849,10 +2453,23 @@ shortcuts_list_create (GtkFileChooserDefault *impl) g_signal_connect (impl->browse_shortcuts_tree_view, "row-activated", G_CALLBACK (shortcuts_row_activated_cb), impl); + g_signal_connect (impl->browse_shortcuts_tree_view, "drag-begin", + G_CALLBACK (shortcuts_drag_begin_cb), impl); + g_signal_connect (impl->browse_shortcuts_tree_view, "drag-end", + G_CALLBACK (shortcuts_drag_end_cb), impl); + g_signal_connect (impl->browse_shortcuts_tree_view, "drag-data-delete", + G_CALLBACK (shortcuts_drag_data_delete_cb), impl); + + g_signal_connect (impl->browse_shortcuts_tree_view, "drag-leave", + G_CALLBACK (shortcuts_drag_leave_cb), impl); + g_signal_connect (impl->browse_shortcuts_tree_view, "drag-motion", + G_CALLBACK (shortcuts_drag_motion_cb), impl); + g_signal_connect (impl->browse_shortcuts_tree_view, "drag-drop", + G_CALLBACK (shortcuts_drag_drop_cb), impl); g_signal_connect (impl->browse_shortcuts_tree_view, "drag-data-received", G_CALLBACK (shortcuts_drag_data_received_cb), impl); - gtk_container_add (GTK_CONTAINER (impl->browse_shortcuts_swin), impl->browse_shortcuts_tree_view); + gtk_container_add (GTK_CONTAINER (swin), impl->browse_shortcuts_tree_view); gtk_widget_show (impl->browse_shortcuts_tree_view); /* Column */ @@ -1875,7 +2492,7 @@ shortcuts_list_create (GtkFileChooserDefault *impl) gtk_tree_view_append_column (GTK_TREE_VIEW (impl->browse_shortcuts_tree_view), column); - return impl->browse_shortcuts_swin; + return swin; } /* Creates the widgets for the shortcuts/bookmarks pane */ @@ -1925,36 +2542,73 @@ shortcuts_pane_create (GtkFileChooserDefault *impl, return vbox; } +static gboolean +trap_activate_cb (GtkWidget *widget, + GdkEventKey *event, + gpointer data) +{ + GtkFileChooserDefault *impl; + + impl = (GtkFileChooserDefault *) data; + + if (event->keyval == GDK_Return + || event->keyval == GDK_ISO_Enter + || event->keyval == GDK_KP_Enter + || event->keyval == GDK_space) + { + GtkWidget *toplevel; + + toplevel = gtk_widget_get_toplevel (widget); + if (GTK_IS_WINDOW (toplevel)) + { + GtkWindow *window; + + window = GTK_WINDOW (toplevel); + + if (window && + widget != window->default_widget && + !(widget == window->focus_widget && + (!window->default_widget || !GTK_WIDGET_SENSITIVE (window->default_widget)))) + gtk_window_activate_default (window); + } + return TRUE; + } + return FALSE; +} + + /* Creates the widgets for the file list */ static GtkWidget * create_file_list (GtkFileChooserDefault *impl) { + GtkWidget *swin; GtkTreeSelection *selection; GtkTreeViewColumn *column; GtkCellRenderer *renderer; /* Scrolled window */ - impl->browse_files_swin = gtk_scrolled_window_new (NULL, NULL); - gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (impl->browse_files_swin), + swin = gtk_scrolled_window_new (NULL, NULL); + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (swin), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); - gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (impl->browse_files_swin), + gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (swin), GTK_SHADOW_IN); /* Tree/list view */ impl->browse_files_tree_view = gtk_tree_view_new (); gtk_tree_view_set_rules_hint (GTK_TREE_VIEW (impl->browse_files_tree_view), TRUE); - gtk_container_add (GTK_CONTAINER (impl->browse_files_swin), impl->browse_files_tree_view); + gtk_container_add (GTK_CONTAINER (swin), impl->browse_files_tree_view); g_signal_connect (impl->browse_files_tree_view, "row-activated", G_CALLBACK (list_row_activated), impl); - gtk_widget_show (impl->browse_files_tree_view); + g_signal_connect (impl->browse_files_tree_view, "key-press-event", + G_CALLBACK (trap_activate_cb), impl); selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (impl->browse_files_tree_view)); gtk_tree_view_enable_model_drag_source (GTK_TREE_VIEW (impl->browse_files_tree_view), GDK_BUTTON1_MASK, - shortcuts_targets, - num_shortcuts_targets, + file_list_source_targets, + num_file_list_source_targets, GDK_ACTION_COPY); g_signal_connect (selection, "changed", @@ -2006,8 +2660,9 @@ create_file_list (GtkFileChooserDefault *impl) list_mtime_data_func, impl, NULL); gtk_tree_view_column_set_sort_column_id (column, FILE_LIST_COL_MTIME); gtk_tree_view_append_column (GTK_TREE_VIEW (impl->browse_files_tree_view), column); + gtk_widget_show_all (swin); - return impl->browse_files_swin; + return swin; } static GtkWidget * @@ -2027,6 +2682,17 @@ create_filename_entry_and_filter_combo (GtkFileChooserDefault *impl) return hbox; } +static GtkWidget * +create_path_bar (GtkFileChooserDefault *impl) +{ + GtkWidget *path_bar; + + path_bar = g_object_new (GTK_TYPE_PATH_BAR, NULL); + _gtk_path_bar_set_file_system (GTK_PATH_BAR (path_bar), impl->file_system); + + return path_bar; +} + /* Creates the widgets for the files/folders pane */ static GtkWidget * file_pane_create (GtkFileChooserDefault *impl, @@ -2042,7 +2708,7 @@ file_pane_create (GtkFileChooserDefault *impl, /* The path bar and 'Create Folder' button */ hbox = gtk_hbox_new (FALSE, 12); gtk_widget_show (hbox); - impl->browse_path_bar = g_object_new (GTK_TYPE_PATH_BAR, NULL); + impl->browse_path_bar = create_path_bar (impl); g_signal_connect (impl->browse_path_bar, "path-clicked", G_CALLBACK (path_bar_clicked), impl); gtk_widget_show_all (impl->browse_path_bar); gtk_box_pack_start (GTK_BOX (hbox), impl->browse_path_bar, TRUE, TRUE, 0); @@ -2060,11 +2726,6 @@ file_pane_create (GtkFileChooserDefault *impl, gtk_box_pack_start (GTK_BOX (vbox), hbox, TRUE, TRUE, 0); gtk_widget_show (hbox); - /* Folder tree */ - - widget = create_folder_tree (impl); - gtk_box_pack_start (GTK_BOX (hbox), widget, TRUE, TRUE, 0); - /* File list */ widget = create_file_list (impl); @@ -2179,7 +2840,7 @@ save_widgets_create (GtkFileChooserDefault *impl) gtk_label_set_mnemonic_widget (GTK_LABEL (widget), impl->save_file_name_entry); /* Folder combo */ - impl->save_folder_label = gtk_label_new_with_mnemonic (_("Save in _Folder:")); + impl->save_folder_label = gtk_label_new (NULL); gtk_misc_set_alignment (GTK_MISC (impl->save_folder_label), 0.0, 0.5); gtk_table_attach (GTK_TABLE (table), impl->save_folder_label, 0, 1, 1, 2, @@ -2236,6 +2897,8 @@ browse_widgets_create (GtkFileChooserDefault *impl) widget = file_pane_create (impl, size_group); gtk_paned_pack2 (GTK_PANED (hpaned), widget, TRUE, FALSE); + g_object_unref (size_group); + /* Alignment to hold custom widget */ impl->browse_extra_align = gtk_alignment_new (0.0, .5, 1.0, 1.0); gtk_box_pack_start (GTK_BOX (vbox), impl->browse_extra_align, FALSE, FALSE, 0); @@ -2296,6 +2959,37 @@ set_extra_widget (GtkFileChooserDefault *impl, impl->extra_widget = extra_widget; } +static void +set_local_only (GtkFileChooserDefault *impl, + gboolean local_only) +{ + if (local_only != impl->local_only) + { + impl->local_only = local_only; + + if (impl->shortcuts_model && impl->file_system) + { + shortcuts_add_volumes (impl); + shortcuts_add_bookmarks (impl); + } + + if (local_only && + !gtk_file_system_path_is_local (impl->file_system, impl->current_folder)) + { + /* If we are pointing to a non-local folder, make an effort to change + * back to a local folder, but it's really up to the app to not cause + * such a situation, so we ignore errors. + */ + const gchar *home = g_get_home_dir (); + GtkFilePath *home_path = gtk_file_system_filename_to_path (impl->file_system, home); + + _gtk_file_chooser_set_current_folder_path (GTK_FILE_CHOOSER (impl), home_path, NULL); + + gtk_file_path_free (home_path); + } + } +} + static void volumes_changed_cb (GtkFileSystem *file_system, GtkFileChooserDefault *impl) @@ -2328,9 +3022,6 @@ set_select_multiple (GtkFileChooserDefault *impl, mode = select_multiple ? GTK_SELECTION_MULTIPLE : GTK_SELECTION_BROWSE; - selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (impl->browse_directories_tree_view)); - gtk_tree_selection_set_mode (selection, mode); - selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (impl->browse_files_tree_view)); gtk_tree_selection_set_mode (selection, mode); @@ -2405,8 +3096,17 @@ update_appearance (GtkFileChooserDefault *impl) if (impl->action == GTK_FILE_CHOOSER_ACTION_SAVE || impl->action == GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER) { + const char *text; + gtk_widget_show (impl->save_widgets); + if (impl->action == GTK_FILE_CHOOSER_ACTION_SAVE) + text = _("Save in _folder:"); + else + text = _("Create in _folder:"); + + gtk_label_set_text_with_mnemonic (GTK_LABEL (impl->save_folder_label), text); + if (gtk_expander_get_expanded (GTK_EXPANDER (impl->save_expander))) { gtk_widget_set_sensitive (impl->save_folder_label, FALSE); @@ -2439,13 +3139,13 @@ update_appearance (GtkFileChooserDefault *impl) if (impl->action == GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER || impl->action == GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER) { - gtk_widget_hide (impl->browse_files_swin); - gtk_widget_show (impl->browse_directories_swin); + if (impl->browse_files_model) + _gtk_file_system_model_set_show_files (impl->browse_files_model, FALSE); } else { - gtk_widget_hide (impl->browse_directories_swin); - gtk_widget_show (impl->browse_files_swin); + if (impl->browse_files_model) + _gtk_file_system_model_set_show_files (impl->browse_files_model, TRUE); } if (impl->action == GTK_FILE_CHOOSER_ACTION_OPEN) @@ -2458,7 +3158,8 @@ update_appearance (GtkFileChooserDefault *impl) GtkWidget *align; GtkWidget *unused_align; - if (impl->action == GTK_FILE_CHOOSER_ACTION_SAVE) + if (impl->action == GTK_FILE_CHOOSER_ACTION_SAVE + || impl->action == GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER) { align = impl->save_extra_align; unused_align = impl->browse_extra_align; @@ -2539,7 +3240,7 @@ gtk_file_chooser_default_set_property (GObject *object, set_current_filter (impl, g_value_get_object (value)); break; case GTK_FILE_CHOOSER_PROP_LOCAL_ONLY: - impl->local_only = g_value_get_boolean (value); + set_local_only (impl, g_value_get_boolean (value)); break; case GTK_FILE_CHOOSER_PROP_PREVIEW_WIDGET: set_preview_widget (impl, g_value_get_object (value)); @@ -2574,8 +3275,6 @@ gtk_file_chooser_default_set_property (GObject *object, if (show_hidden != impl->show_hidden) { impl->show_hidden = show_hidden; - _gtk_file_system_model_set_show_hidden (GTK_FILE_SYSTEM_MODEL (impl->browse_directories_model), - show_hidden); _gtk_file_system_model_set_show_hidden (GTK_FILE_SYSTEM_MODEL (impl->browse_files_model), show_hidden); } @@ -2630,6 +3329,21 @@ gtk_file_chooser_default_get_property (GObject *object, } } +/* Removes the settings signal handler. It's safe to call multiple times */ +static void +remove_settings_signal (GtkFileChooserDefault *impl, + GdkScreen *screen) +{ + if (impl->settings_signal_id) + { + GtkSettings *settings; + + settings = gtk_settings_get_for_screen (screen); + g_signal_handler_disconnect (settings, + impl->settings_signal_id); + impl->settings_signal_id = 0; + } +} static void gtk_file_chooser_default_dispose (GObject *object) @@ -2641,6 +3355,9 @@ gtk_file_chooser_default_dispose (GObject *object) g_object_unref (impl->extra_widget); impl->extra_widget = NULL; } + + remove_settings_signal (impl, gtk_widget_get_screen (GTK_WIDGET (impl))); + G_OBJECT_CLASS (parent_class)->dispose (object); } @@ -2659,13 +3376,73 @@ gtk_file_chooser_default_show_all (GtkWidget *widget) gtk_widget_show_all (impl->extra_widget); } +/* Changes the icons wherever it is needed */ +static void +change_icon_theme (GtkFileChooserDefault *impl) +{ + GtkSettings *settings; + gint width, height; + + settings = gtk_settings_get_for_screen (gtk_widget_get_screen (GTK_WIDGET (impl))); + + if (gtk_icon_size_lookup_for_settings (settings, GTK_ICON_SIZE_SMALL_TOOLBAR, &width, &height)) + impl->icon_size = MAX (width, height); + else + impl->icon_size = FALLBACK_ICON_SIZE; + + shortcuts_reload_icons (impl); + gtk_widget_queue_resize (impl->browse_files_tree_view); +} + +/* Callback used when a GtkSettings value changes */ +static void +settings_notify_cb (GObject *object, + GParamSpec *pspec, + GtkFileChooserDefault *impl) +{ + const char *name; + + name = g_param_spec_get_name (pspec); + + if (strcmp (name, "gtk-icon-theme-name") == 0 + || strcmp (name, "gtk-icon-sizes") == 0) + change_icon_theme (impl); +} + +/* Installs a signal handler for GtkSettings so that we can monitor changes in + * the icon theme. + */ +static void +check_icon_theme (GtkFileChooserDefault *impl) +{ + GtkSettings *settings; + + if (impl->settings_signal_id) + return; + + if (gtk_widget_has_screen (GTK_WIDGET (impl))) + { + settings = gtk_settings_get_for_screen (gtk_widget_get_screen (GTK_WIDGET (impl))); + impl->settings_signal_id = g_signal_connect (settings, "notify", + G_CALLBACK (settings_notify_cb), impl); + + change_icon_theme (impl); + } +} + static void gtk_file_chooser_default_style_set (GtkWidget *widget, GtkStyle *previous_style) { + GtkFileChooserDefault *impl; + + impl = GTK_FILE_CHOOSER_DEFAULT (widget); + if (GTK_WIDGET_CLASS (parent_class)->style_set) GTK_WIDGET_CLASS (parent_class)->style_set (widget, previous_style); + check_icon_theme (impl); + g_signal_emit_by_name (widget, "default-size-changed"); } @@ -2673,30 +3450,17 @@ static void gtk_file_chooser_default_screen_changed (GtkWidget *widget, GdkScreen *previous_screen) { - if (GTK_WIDGET_CLASS (parent_class)->screen_changed) - GTK_WIDGET_CLASS (parent_class)->screen_changed (widget, previous_screen); + GtkFileChooserDefault *impl; - g_signal_emit_by_name (widget, "default-size-changed"); -} + impl = GTK_FILE_CHOOSER_DEFAULT (widget); -static void -expand_and_select_func (GtkFileSystemModel *model, - GtkTreePath *path, - GtkTreeIter *iter, - gpointer user_data) -{ - GtkFileChooserDefault *impl = user_data; - GtkTreeView *tree_view; + if (GTK_WIDGET_CLASS (parent_class)->screen_changed) + GTK_WIDGET_CLASS (parent_class)->screen_changed (widget, previous_screen); - if (model == impl->browse_directories_model) - tree_view = GTK_TREE_VIEW (impl->browse_directories_tree_view); - else - tree_view = GTK_TREE_VIEW (impl->browse_files_tree_view); + remove_settings_signal (impl, previous_screen); + check_icon_theme (impl); - gtk_tree_view_expand_to_path (tree_view, path); - gtk_tree_view_expand_row (tree_view, path, FALSE); - gtk_tree_view_set_cursor (tree_view, path, NULL, FALSE); - gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (impl->browse_directories_tree_view), path, NULL, TRUE, 0.3, 0.5); + g_signal_emit_by_name (widget, "default-size-changed"); } static gboolean @@ -2849,9 +3613,22 @@ set_list_model (GtkFileChooserDefault *impl) } impl->browse_files_model = _gtk_file_system_model_new (impl->file_system, - impl->current_folder, 0, - GTK_FILE_INFO_ALL); + impl->current_folder, 0, + GTK_FILE_INFO_ALL); _gtk_file_system_model_set_show_hidden (impl->browse_files_model, impl->show_hidden); + switch (impl->action) + { + case GTK_FILE_CHOOSER_ACTION_OPEN: + case GTK_FILE_CHOOSER_ACTION_SAVE: + _gtk_file_system_model_set_show_files (impl->browse_files_model, TRUE); + break; + case GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER: + case GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER: + _gtk_file_system_model_set_show_files (impl->browse_files_model, FALSE); + break; + default: + g_assert_not_reached (); + } install_list_model_filter (impl); impl->sort_model = (GtkTreeModelSort *)gtk_tree_model_sort_new_with_model (GTK_TREE_MODEL (impl->browse_files_model)); @@ -2872,61 +3649,6 @@ set_list_model (GtkFileChooserDefault *impl) GTK_FILE_SYSTEM_MODEL_DISPLAY_NAME); } -/* Gets rid of the old folder tree model and creates a new one for the volume - * corresponding to the specified path. - */ -static void -set_tree_model (GtkFileChooserDefault *impl, const GtkFilePath *path) -{ - GtkFileSystemVolume *volume; - GtkFilePath *base_path, *parent_path; - - base_path = NULL; - - volume = gtk_file_system_get_volume_for_path (impl->file_system, path); - - if (volume) - base_path = gtk_file_system_volume_get_base_path (impl->file_system, volume); - - if (base_path == NULL) - { - base_path = gtk_file_path_copy (path); - while (gtk_file_system_get_parent (impl->file_system, - base_path, - &parent_path, - NULL) && - parent_path != NULL) - { - gtk_file_path_free (base_path); - base_path = parent_path; - } - } - - if (impl->current_volume_path && gtk_file_path_compare (base_path, impl->current_volume_path) == 0) - goto out; - - if (impl->browse_directories_model) - g_object_unref (impl->browse_directories_model); - - impl->current_volume_path = gtk_file_path_copy (base_path); - - impl->browse_directories_model = _gtk_file_system_model_new (impl->file_system, impl->current_volume_path, -1, - GTK_FILE_INFO_DISPLAY_NAME); - _gtk_file_system_model_set_show_files (GTK_FILE_SYSTEM_MODEL (impl->browse_directories_model), - FALSE); - _gtk_file_system_model_set_show_hidden (GTK_FILE_SYSTEM_MODEL (impl->browse_directories_model), - impl->show_hidden); - - gtk_tree_view_set_model (GTK_TREE_VIEW (impl->browse_directories_tree_view), - GTK_TREE_MODEL (impl->browse_directories_model)); - - out: - - gtk_file_path_free (base_path); - if (volume) - gtk_file_system_volume_free (impl->file_system, volume); -} - static void update_chooser_entry (GtkFileChooserDefault *impl) { @@ -2961,15 +3683,25 @@ gtk_file_chooser_default_set_current_folder (GtkFileChooser *chooser, GError **error) { GtkFileChooserDefault *impl = GTK_FILE_CHOOSER_DEFAULT (chooser); - GError *err; - err = NULL; - if (!_gtk_path_bar_set_path (GTK_PATH_BAR (impl->browse_path_bar), path, impl->file_system, &err)) + /* Test validity of path here. */ + if (!check_is_folder (impl->file_system, path, error)) + return FALSE; + + if (impl->local_only && + !gtk_file_system_path_is_local (impl->file_system, path)) { - g_propagate_error (error, err); + g_set_error (error, + GTK_FILE_SYSTEM_ERROR, + GTK_FILE_SYSTEM_ERROR_FAILED, + _("Can't change to folder because it isn't local")); + return FALSE; } + if (!_gtk_path_bar_set_path (GTK_PATH_BAR (impl->browse_path_bar), path, error)) + return FALSE; + if (impl->current_folder != path) { if (impl->current_folder) @@ -2978,16 +3710,12 @@ gtk_file_chooser_default_set_current_folder (GtkFileChooser *chooser, impl->current_folder = gtk_file_path_copy (path); } - /* Update the widgets that may trigger a folder chnage themselves */ + /* Update the widgets that may trigger a folder change themselves. */ if (!impl->changing_folder) { impl->changing_folder = TRUE; - set_tree_model (impl, impl->current_folder); - _gtk_file_system_model_path_do (GTK_FILE_SYSTEM_MODEL (impl->browse_directories_model), - path, expand_and_select_func, impl); - shortcuts_update_current_folder (impl); impl->changing_folder = FALSE; @@ -2998,7 +3726,7 @@ gtk_file_chooser_default_set_current_folder (GtkFileChooser *chooser, /* Refresh controls */ - shortcuts_unselect_all (impl); + shortcuts_find_current_folder (impl); g_signal_emit_by_name (impl, "current-folder-changed", 0); @@ -3024,7 +3752,8 @@ gtk_file_chooser_default_set_current_name (GtkFileChooser *chooser, { GtkFileChooserDefault *impl = GTK_FILE_CHOOSER_DEFAULT (chooser); - g_return_if_fail (impl->action == GTK_FILE_CHOOSER_ACTION_SAVE); + g_return_if_fail (impl->action == GTK_FILE_CHOOSER_ACTION_SAVE + || impl->action == GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER); gtk_entry_set_text (GTK_ENTRY (impl->save_file_name_entry), name); } @@ -3041,7 +3770,6 @@ select_func (GtkFileSystemModel *model, sorted_path = gtk_tree_model_sort_convert_child_path_to_path (impl->sort_model, path); gtk_tree_view_set_cursor (tree_view, sorted_path, NULL, FALSE); - gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (impl->browse_directories_tree_view), sorted_path, NULL, TRUE, 0.3, 0.0); gtk_tree_path_free (sorted_path); } @@ -3176,20 +3904,12 @@ get_paths_foreach (GtkTreeModel *model, GtkTreeIter sel_iter; info = data; - - if (info->impl->action == GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER || - info->impl->action == GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER) - { - fs_model = info->impl->browse_directories_model; - sel_iter = *iter; - } - else - { - fs_model = info->impl->browse_files_model; - gtk_tree_model_sort_convert_iter_to_child_iter (info->impl->sort_model, &sel_iter, iter); - } + fs_model = info->impl->browse_files_model; + gtk_tree_model_sort_convert_iter_to_child_iter (info->impl->sort_model, &sel_iter, iter); file_path = _gtk_file_system_model_get_path (GTK_FILE_SYSTEM_MODEL (fs_model), &sel_iter); + if (!file_path) + return; /* We are on the editable row */ if (!info->path_from_entry || gtk_file_path_compare (info->path_from_entry, file_path) != 0) @@ -3220,13 +3940,21 @@ gtk_file_chooser_default_get_paths (GtkFileChooser *chooser) { GtkTreeSelection *selection; - selection = get_selection (impl); + selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (impl->browse_files_tree_view)); gtk_tree_selection_selected_foreach (selection, get_paths_foreach, &info); } if (info.path_from_entry) info.result = g_slist_prepend (info.result, info.path_from_entry); + /* If there's no folder selected, and we're in SELECT_FOLDER mode, then we + * fall back to the current directory */ + if (impl->action == GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER && + info.result == NULL) + { + info.result = g_slist_prepend (info.result, gtk_file_path_copy (impl->current_folder)); + } + return g_slist_reverse (info.result); } @@ -3352,6 +4080,10 @@ gtk_file_chooser_default_add_shortcut_folder (GtkFileChooser *chooser, gboolean result; int pos; + /* Test validity of path here. */ + if (!check_is_folder (impl->file_system, path, error)) + return FALSE; + pos = shortcuts_get_pos_for_shortcut_folder (impl, impl->num_shortcuts); result = shortcuts_insert_path (impl, pos, FALSE, NULL, path, NULL, FALSE, error); @@ -3373,6 +4105,7 @@ gtk_file_chooser_default_remove_shortcut_folder (GtkFileChooser *chooser, GtkFileChooserDefault *impl = GTK_FILE_CHOOSER_DEFAULT (chooser); int pos; GtkTreeIter iter; + char *uri; int i; if (impl->num_shortcuts == 0) @@ -3404,11 +4137,13 @@ gtk_file_chooser_default_remove_shortcut_folder (GtkFileChooser *chooser, out: + uri = gtk_file_system_path_to_uri (impl->file_system, path); g_set_error (error, GTK_FILE_CHOOSER_ERROR, GTK_FILE_CHOOSER_ERROR_NONEXISTENT, _("shortcut %s does not exist"), - gtk_file_path_get_string (path)); + uri); + g_free (uri); return FALSE; } @@ -3606,12 +4341,15 @@ gtk_file_chooser_default_should_respond (GtkFileChooserEmbed *chooser_embed) return FALSE; } - /* Second, do we have an empty selection? */ - - selection = get_selection (impl); - num_selected = gtk_tree_selection_count_selected_rows (selection); - if (num_selected == 0) - return FALSE; + /* Second, do we have an empty selection */ + if (impl->action == GTK_FILE_CHOOSER_ACTION_OPEN + || impl->action == GTK_FILE_CHOOSER_ACTION_SAVE) + { + selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (impl->browse_files_tree_view)); + num_selected = gtk_tree_selection_count_selected_rows (selection); + if (num_selected == 0) + return FALSE; + } /* Third, should we return file names or folder names? */ @@ -3646,6 +4384,38 @@ gtk_file_chooser_default_should_respond (GtkFileChooserEmbed *chooser_embed) return FALSE; } +/* Implementation for GtkFileChooserEmbed::initial_focus() */ +static void +gtk_file_chooser_default_initial_focus (GtkFileChooserEmbed *chooser_embed) +{ + GtkFileChooserDefault *impl; + GtkWidget *widget; + + impl = GTK_FILE_CHOOSER_DEFAULT (chooser_embed); + + if (impl->action == GTK_FILE_CHOOSER_ACTION_OPEN + || impl->action == GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER) + { + GtkTreePath *path; + + path = gtk_tree_path_new_from_indices (0, -1); + gtk_tree_view_set_cursor (GTK_TREE_VIEW (impl->browse_files_tree_view), path, NULL, FALSE); + gtk_tree_path_free (path); + + widget = impl->browse_files_tree_view; + } + else if (impl->action == GTK_FILE_CHOOSER_ACTION_SAVE + || impl->action == GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER) + widget = impl->save_file_name_entry; + else + { + g_assert_not_reached (); + widget = NULL; + } + + gtk_widget_grab_focus (widget); +} + static void set_current_filter (GtkFileChooserDefault *impl, GtkFileFilter *filter) @@ -3679,63 +4449,6 @@ set_current_filter (GtkFileChooserDefault *impl, } } -static void -open_and_close (GtkTreeView *tree_view, - GtkTreePath *target_path) -{ - GtkTreeModel *model = gtk_tree_view_get_model (tree_view); - GtkTreeIter iter; - GtkTreePath *path; - - path = gtk_tree_path_new (); - gtk_tree_path_append_index (path, 0); - - gtk_tree_model_get_iter (model, &iter, path); - - while (TRUE) - { - if (gtk_tree_path_is_ancestor (path, target_path) || - gtk_tree_path_compare (path, target_path) == 0) - { - GtkTreeIter child_iter; - gtk_tree_view_expand_row (tree_view, path, FALSE); - if (gtk_tree_model_iter_children (model, &child_iter, &iter)) - { - iter = child_iter; - gtk_tree_path_down (path); - goto next; - } - } - else - gtk_tree_view_collapse_row (tree_view, path); - - while (TRUE) - { - GtkTreeIter parent_iter; - GtkTreeIter next_iter; - - next_iter = iter; - if (gtk_tree_model_iter_next (model, &next_iter)) - { - iter = next_iter; - gtk_tree_path_next (path); - goto next; - } - - if (!gtk_tree_model_iter_parent (model, &parent_iter, &iter)) - goto out; - - iter = parent_iter; - gtk_tree_path_up (path); - } - next: - ; - } - - out: - gtk_tree_path_free (path); -} - static void filter_combo_changed (GtkComboBox *combo_box, GtkFileChooserDefault *impl) @@ -3803,39 +4516,6 @@ check_preview_change (GtkFileChooserDefault *impl) } } -static void -tree_selection_changed (GtkTreeSelection *selection, - GtkFileChooserDefault *impl) -{ - GtkTreeIter iter; - const GtkFilePath *file_path; - GtkTreePath *path; - - /* FIXME #132255: Fixing this for multiple selection involves getting the full - * selection and diffing to find out what the most recently selected file is; - * there is logic in GtkFileSelection that probably can be copied; - * check_preview_change() is similar. - */ - if (impl->select_multiple - || !gtk_tree_selection_get_selected (selection, NULL, &iter)) - return; - - file_path = _gtk_file_system_model_get_path (GTK_FILE_SYSTEM_MODEL (impl->browse_directories_model), - &iter); - if (impl->current_folder && gtk_file_path_compare (file_path, impl->current_folder) == 0) - return; - - /* Close the tree up to only the parents of the newly selected - * node and it's immediate children are visible. - */ - path = gtk_tree_model_get_path (GTK_TREE_MODEL (impl->browse_directories_model), &iter); - open_and_close (GTK_TREE_VIEW (impl->browse_directories_tree_view), path); - gtk_tree_path_free (path); - - if (!impl->changing_folder) - change_folder_and_display_error (impl, file_path); -} - /* Activates a volume by mounting it if necessary and then switching to its * base path. */ @@ -4035,27 +4715,6 @@ get_list_file_info (GtkFileChooserDefault *impl, return _gtk_file_system_model_get_info (impl->browse_files_model, &child_iter); } -static void -tree_name_data_func (GtkTreeViewColumn *tree_column, - GtkCellRenderer *cell, - GtkTreeModel *tree_model, - GtkTreeIter *iter, - gpointer data) -{ - GtkFileChooserDefault *impl = data; - const GtkFileInfo *info; - - info = _gtk_file_system_model_get_info (GTK_FILE_SYSTEM_MODEL (impl->browse_directories_model), - iter); - - if (info) - { - g_object_set (cell, - "text", gtk_file_info_get_display_name (info), - NULL); - } -} - static void list_icon_data_func (GtkTreeViewColumn *tree_column, GtkCellRenderer *cell, @@ -4076,7 +4735,8 @@ list_icon_data_func (GtkTreeViewColumn *tree_column, return; /* FIXME: NULL GError */ - pixbuf = gtk_file_system_render_icon (impl->file_system, path, GTK_WIDGET (impl), ICON_SIZE, NULL); + pixbuf = gtk_file_system_render_icon (impl->file_system, path, GTK_WIDGET (impl), + impl->icon_size, NULL); g_object_set (cell, "pixbuf", pixbuf, NULL); @@ -4188,8 +4848,7 @@ list_mtime_data_func (GtkTreeViewColumn *tree_column, if (days_diff > 1 && days_diff < 7) format = "%A"; /* Days from last week */ else - /* FIXME: Get the right format for the locale */ - format = _("%d/%b/%Y"); /* Any other date */ + format = "%x"; /* Any other date */ if (g_date_strftime (buf, sizeof (buf), format, &mtime) == 0) strcpy (buf, _("Unknown")); @@ -4215,7 +4874,7 @@ location_entry_create (GtkFileChooserDefault *impl) entry = _gtk_file_chooser_entry_new (); /* Pick a good width for the entry */ - gtk_entry_set_width_chars (GTK_ENTRY (entry), 25); + gtk_entry_set_width_chars (GTK_ENTRY (entry), 30); gtk_entry_set_activates_default (GTK_ENTRY (entry), TRUE); _gtk_file_chooser_entry_set_file_system (GTK_FILE_CHOOSER_ENTRY (entry), impl->file_system); _gtk_file_chooser_entry_set_base_folder (GTK_FILE_CHOOSER_ENTRY (entry), impl->current_folder); @@ -4223,7 +4882,7 @@ location_entry_create (GtkFileChooserDefault *impl) return GTK_WIDGET (entry); } -static void +static gboolean update_from_entry (GtkFileChooserDefault *impl, GtkWindow *parent, GtkFileChooserEntry *chooser_entry) @@ -4238,20 +4897,20 @@ update_from_entry (GtkFileChooserDefault *impl, { error_message_with_parent (parent, _("Cannot change to the folder you specified as it is an invalid path.")); - return; + return FALSE; } if (file_part[0] == '\0') - { - change_folder_and_display_error (impl, folder_path); - return; - } + return change_folder_and_display_error (impl, folder_path); else { GtkFileFolder *folder = NULL; GtkFilePath *subfolder_path = NULL; GtkFileInfo *info = NULL; GError *error; + gboolean result; + + result = FALSE; /* If the file part is non-empty, we need to figure out if it refers to a * folder within folder. We could optimize the case here where the folder @@ -4264,7 +4923,7 @@ update_from_entry (GtkFileChooserDefault *impl, if (!folder) { error_getting_info_dialog (impl, folder_path, error); - return; + goto out; } error = NULL; @@ -4273,15 +4932,16 @@ update_from_entry (GtkFileChooserDefault *impl, if (!subfolder_path) { char *msg; + char *uri; + uri = gtk_file_system_path_to_uri (impl->file_system, folder_path); msg = g_strdup_printf (_("Could not build file name from '%s' and '%s':\n%s"), - gtk_file_path_get_string (folder_path), - file_part, + uri, file_part, error->message); error_message (impl, msg); + g_free (uri); g_free (msg); - g_object_unref (folder); - return; + goto out; } error = NULL; @@ -4289,38 +4949,48 @@ update_from_entry (GtkFileChooserDefault *impl, if (!info) { -#if 0 - if (impl->action == GTK_FILE_CHOOSER_ACTION_SAVE) + if (impl->action == GTK_FILE_CHOOSER_ACTION_SAVE + || impl->action == GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER) { - g_object_unref (folder); - gtk_file_path_free (subfolder_path); - return; + if (!change_folder_and_display_error (impl, folder_path)) + goto out; + + gtk_file_chooser_default_set_current_name (GTK_FILE_CHOOSER (impl), file_part); } -#endif - error_getting_info_dialog (impl, subfolder_path, error); - g_object_unref (folder); - gtk_file_path_free (subfolder_path); - return; + else + error_getting_info_dialog (impl, subfolder_path, error); + + goto out; } if (gtk_file_info_get_is_folder (info)) - change_folder_and_display_error (impl, subfolder_path); + result = change_folder_and_display_error (impl, subfolder_path); else { GError *error; error = NULL; - if (!_gtk_file_chooser_select_path (GTK_FILE_CHOOSER (impl), subfolder_path, &error)) + result = _gtk_file_chooser_select_path (GTK_FILE_CHOOSER (impl), subfolder_path, &error); + if (!result) error_dialog (impl, _("Could not select %s:\n%s"), - subfolder_path, - error); + subfolder_path, error); } - g_object_unref (folder); + out: + + if (folder) + g_object_unref (folder); + gtk_file_path_free (subfolder_path); - gtk_file_info_free (info); + + if (info) + gtk_file_info_free (info); + + return result; } + + g_assert_not_reached (); } static void @@ -4331,6 +5001,8 @@ location_popup_handler (GtkFileChooserDefault *impl) GtkWidget *hbox; GtkWidget *label; GtkWidget *entry; + gboolean refocus; + char *title; /* Create dialog */ @@ -4338,7 +5010,19 @@ location_popup_handler (GtkFileChooserDefault *impl) if (!GTK_WIDGET_TOPLEVEL (toplevel)) toplevel = NULL; - dialog = gtk_dialog_new_with_buttons (_("Open Location"), + if (impl->action == GTK_FILE_CHOOSER_ACTION_OPEN + || impl->action == GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER) + { + title = _("Open Location"); + } + else + { + g_assert (impl->action == GTK_FILE_CHOOSER_ACTION_SAVE + || impl->action == GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER); + title = ""; /* FIXME: #137272, fix for 2.4.1 */ + } + + dialog = gtk_dialog_new_with_buttons (title, GTK_WINDOW (toplevel), GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT | GTK_DIALOG_NO_SEPARATOR, GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, @@ -4363,8 +5047,36 @@ location_popup_handler (GtkFileChooserDefault *impl) /* Run */ gtk_widget_show_all (dialog); + + refocus = TRUE; + if (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_ACCEPT) - update_from_entry (impl, GTK_WINDOW (dialog), GTK_FILE_CHOOSER_ENTRY (entry)); + { + if (update_from_entry (impl, GTK_WINDOW (dialog), GTK_FILE_CHOOSER_ENTRY (entry))) + { + if (impl->action == GTK_FILE_CHOOSER_ACTION_OPEN + || impl->action == GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER) + { + gtk_widget_grab_focus (impl->browse_files_tree_view); + } + else + { + g_assert (impl->action == GTK_FILE_CHOOSER_ACTION_SAVE + || impl->action == GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER); + gtk_widget_grab_focus (impl->save_file_name_entry); + } + refocus = FALSE; + } + } + + if (refocus) + { + GtkWidget *toplevel; + + toplevel = gtk_widget_get_toplevel (GTK_WIDGET (impl)); + if (GTK_WIDGET_TOPLEVEL (toplevel) && GTK_WINDOW (toplevel)->focus_widget) + gtk_widget_grab_focus (GTK_WINDOW (toplevel)->focus_widget); + } gtk_widget_destroy (dialog); } @@ -4373,23 +5085,14 @@ location_popup_handler (GtkFileChooserDefault *impl) static void up_folder_handler (GtkFileChooserDefault *impl) { - GtkFilePath *parent_path; - GError *error; + _gtk_path_bar_up (GTK_PATH_BAR (impl->browse_path_bar)); +} - error = NULL; - if (gtk_file_system_get_parent (impl->file_system, impl->current_folder, &parent_path, &error)) - { - if (parent_path) /* If we were on a root, parent_path will be NULL */ - { - change_folder_and_display_error (impl, parent_path); - gtk_file_path_free (parent_path); - } - } - else - error_dialog (impl, - _("Could not go to the parent folder of %s:\n%s"), - impl->current_folder, - error); +/* Handler for the "down-folder" keybinding signal */ +static void +down_folder_handler (GtkFileChooserDefault *impl) +{ + _gtk_path_bar_down (GTK_PATH_BAR (impl->browse_path_bar)); } /* Handler for the "home-folder" keybinding signal */ @@ -4412,3 +5115,85 @@ home_folder_handler (GtkFileChooserDefault *impl) change_folder_and_display_error (impl, path); } + + + +/* Drag and drop interfaces */ + +static void +shortcuts_model_filter_class_init (ShortcutsModelFilterClass *class) +{ +} + +static void +shortcuts_model_filter_init (ShortcutsModelFilter *model) +{ + model->impl = NULL; +} + +/* GtkTreeDragSource::row_draggable implementation for the shortcuts filter model */ +static gboolean +shortcuts_model_filter_row_draggable (GtkTreeDragSource *drag_source, + GtkTreePath *path) +{ + ShortcutsModelFilter *model; + int pos; + int bookmarks_pos; + + model = SHORTCUTS_MODEL_FILTER (drag_source); + + pos = *gtk_tree_path_get_indices (path); + bookmarks_pos = shortcuts_get_index (model->impl, SHORTCUTS_BOOKMARKS); + + return (pos >= bookmarks_pos && pos < bookmarks_pos + model->impl->num_bookmarks); +} + +/* GtkTreeDragSource::drag_data_get implementation for the shortcuts filter model */ +static gboolean +shortcuts_model_filter_drag_data_get (GtkTreeDragSource *drag_source, + GtkTreePath *path, + GtkSelectionData *selection_data) +{ + ShortcutsModelFilter *model; + + model = SHORTCUTS_MODEL_FILTER (drag_source); + + /* FIXME */ + + return FALSE; +} + +/* Fill the GtkTreeDragSourceIface vtable */ +static void +shortcuts_model_filter_drag_source_iface_init (GtkTreeDragSourceIface *iface) +{ + iface->row_draggable = shortcuts_model_filter_row_draggable; + iface->drag_data_get = shortcuts_model_filter_drag_data_get; +} + +#if 0 +/* Fill the GtkTreeDragDestIface vtable */ +static void +shortcuts_model_filter_drag_dest_iface_init (GtkTreeDragDestIface *iface) +{ + iface->drag_data_received = shortcuts_model_filter_drag_data_received; + iface->row_drop_possible = shortcuts_model_filter_row_drop_possible; +} +#endif + +static GtkTreeModel * +shortcuts_model_filter_new (GtkFileChooserDefault *impl, + GtkTreeModel *child_model, + GtkTreePath *root) +{ + ShortcutsModelFilter *model; + + model = g_object_new (SHORTCUTS_MODEL_FILTER_TYPE, + "child_model", child_model, + "virtual_root", root, + NULL); + + model->impl = impl; + + return GTK_TREE_MODEL (model); +}