Sign Up
Log In
Log In
or
Sign Up
Places
All Projects
Status Monitor
Collapse sidebar
SUSE:SLE-12:Update
gtk2.4054
gtk2-bgo957400-filechooser-update.patch
Overview
Repositories
Revisions
Requests
Users
Attributes
Meta
File gtk2-bgo957400-filechooser-update.patch of Package gtk2.4054
commit 7a1f6e15982d407f12d64c2f305ef413d546568c Author: Federico Mena Quintero <federico@suse.com> Date: Thu Jan 19 13:05:42 2017 -0600 Update to filechooser and GtkEntry code from GTK+ 2.24.31 This fixes Tab completion in GtkFileChooser, and some drag-and-drop crashes. https://bugzilla.suse.com/show_bug.cgi?id=957400 diff --git a/gtk/gtkentry.c b/gtk/gtkentry.c index b55bd89..ae7a0d3 100644 --- a/gtk/gtkentry.c +++ b/gtk/gtkentry.c @@ -490,9 +490,6 @@ static void completion_insert_text_callback (GtkEntry *entr gint length, gint position, GtkEntryCompletion *completion); -static void completion_changed (GtkEntryCompletion *completion, - GParamSpec *pspec, - gpointer data); static void disconnect_completion_signals (GtkEntry *entry, GtkEntryCompletion *completion); static void connect_completion_signals (GtkEntry *entry, @@ -2382,6 +2379,8 @@ begin_change (GtkEntry *entry) GtkEntryPrivate *priv = GTK_ENTRY_GET_PRIVATE (entry); priv->change_count++; + + g_object_freeze_notify (G_OBJECT (entry)); } static void @@ -2392,6 +2391,8 @@ end_change (GtkEntry *entry) g_return_if_fail (priv->change_count > 0); + g_object_thaw_notify (G_OBJECT (entry)); + priv->change_count--; if (priv->change_count == 0) @@ -4537,8 +4538,12 @@ gtk_entry_real_insert_text (GtkEditable *editable, * following signal handlers: buffer_inserted_text(), buffer_notify_display_text(), * buffer_notify_text(), buffer_notify_length() */ + begin_change (GTK_ENTRY (editable)); + n_inserted = gtk_entry_buffer_insert_text (get_buffer (GTK_ENTRY (editable)), *position, new_text, n_chars); + end_change (GTK_ENTRY (editable)); + if (n_inserted != n_chars) gtk_widget_error_bell (GTK_WIDGET (editable)); @@ -4556,7 +4561,11 @@ gtk_entry_real_delete_text (GtkEditable *editable, * buffer_notify_text(), buffer_notify_length() */ + begin_change (GTK_ENTRY (editable)); + gtk_entry_buffer_delete_text (get_buffer (GTK_ENTRY (editable)), start_pos, end_pos - start_pos); + + end_change (GTK_ENTRY (editable)); } /* GtkEntryBuffer signal handlers @@ -4569,12 +4578,18 @@ buffer_inserted_text (GtkEntryBuffer *buffer, GtkEntry *entry) { guint password_hint_timeout; + guint current_pos; + gint selection_bound; - if (entry->current_pos > position) - entry->current_pos += n_chars; + current_pos = entry->current_pos; + if (current_pos > position) + current_pos += n_chars; - if (entry->selection_bound > position) - entry->selection_bound += n_chars; + selection_bound = entry->selection_bound; + if (selection_bound > position) + selection_bound += n_chars; + + gtk_entry_set_positions (entry, current_pos, selection_bound); /* Calculate the password hint if it needs to be displayed. */ if (n_chars == 1 && !entry->visible) @@ -6312,14 +6327,12 @@ paste_received (GtkClipboard *clipboard, } begin_change (entry); - g_object_freeze_notify (G_OBJECT (entry)); if (gtk_editable_get_selection_bounds (editable, &start, &end)) gtk_editable_delete_text (editable, start, end); pos = entry->current_pos; gtk_editable_insert_text (editable, text, length, &pos); gtk_editable_set_position (editable, pos); - g_object_thaw_notify (G_OBJECT (entry)); end_change (entry); if (completion && @@ -6797,11 +6810,9 @@ gtk_entry_set_text (GtkEntry *entry, g_signal_handler_block (entry, completion->priv->changed_id); begin_change (entry); - g_object_freeze_notify (G_OBJECT (entry)); gtk_editable_delete_text (GTK_EDITABLE (entry), 0, -1); tmp_pos = 0; gtk_editable_insert_text (GTK_EDITABLE (entry), text, strlen (text), &tmp_pos); - g_object_thaw_notify (G_OBJECT (entry)); end_change (entry); if (completion && completion->priv->changed_id > 0) @@ -8995,10 +9006,8 @@ gtk_entry_drag_data_received (GtkWidget *widget, { /* Replacing selection */ begin_change (entry); - g_object_freeze_notify (G_OBJECT (entry)); gtk_editable_delete_text (editable, sel1, sel2); gtk_editable_insert_text (editable, str, length, &sel1); - g_object_thaw_notify (G_OBJECT (entry)); end_change (entry); } @@ -9431,6 +9440,7 @@ gtk_entry_completion_key_press (GtkWidget *widget, { GtkTreeIter iter; + GtkTreeIter child_iter; GtkTreeModel *model = NULL; GtkTreeSelection *sel; gboolean entry_set; @@ -9438,12 +9448,15 @@ gtk_entry_completion_key_press (GtkWidget *widget, sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (completion->priv->tree_view)); if (!gtk_tree_selection_get_selected (sel, &model, &iter)) return FALSE; + + gtk_tree_model_filter_convert_iter_to_child_iter (GTK_TREE_MODEL_FILTER (model), &child_iter, &iter); + model = gtk_tree_model_filter_get_model (GTK_TREE_MODEL_FILTER (model)); if (completion->priv->completion_prefix == NULL) completion->priv->completion_prefix = g_strdup (gtk_entry_get_text (GTK_ENTRY (completion->priv->entry))); g_signal_emit_by_name (completion, "cursor-on-match", model, - &iter, &entry_set); + &child_iter, &entry_set); } } else if (completion->priv->current_selected - matches >= 0) @@ -9518,18 +9531,13 @@ keypress_completion_out: event->keyval == GDK_KP_Tab || event->keyval == GDK_ISO_Left_Tab) { - GtkDirectionType dir = event->keyval == GDK_ISO_Left_Tab ? - GTK_DIR_TAB_BACKWARD : GTK_DIR_TAB_FORWARD; - _gtk_entry_reset_im_context (GTK_ENTRY (widget)); _gtk_entry_completion_popdown (completion); g_free (completion->priv->completion_prefix); completion->priv->completion_prefix = NULL; - gtk_widget_child_focus (gtk_widget_get_toplevel (widget), dir); - - return TRUE; + return FALSE; } else if (event->keyval == GDK_ISO_Enter || event->keyval == GDK_KP_Enter || @@ -9537,6 +9545,8 @@ keypress_completion_out: { GtkTreeIter iter; GtkTreeModel *model = NULL; + GtkTreeModel *child_model; + GtkTreeIter child_iter; GtkTreeSelection *sel; gboolean retval = TRUE; @@ -9550,9 +9560,11 @@ keypress_completion_out: sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (completion->priv->tree_view)); if (gtk_tree_selection_get_selected (sel, &model, &iter)) { + gtk_tree_model_filter_convert_iter_to_child_iter (GTK_TREE_MODEL_FILTER (model), &child_iter, &iter); + child_model = gtk_tree_model_filter_get_model (GTK_TREE_MODEL_FILTER (model)); g_signal_handler_block (widget, completion->priv->changed_id); g_signal_emit_by_name (completion, "match-selected", - model, &iter, &entry_set); + child_model, &child_iter, &entry_set); g_signal_handler_unblock (widget, completion->priv->changed_id); if (!entry_set) @@ -9605,6 +9617,9 @@ gtk_entry_completion_changed (GtkWidget *entry, { GtkEntryCompletion *completion = GTK_ENTRY_COMPLETION (user_data); + if (!completion->priv->popup_completion) + return; + /* (re)install completion timeout */ if (completion->priv->completion_timeout) g_source_remove (completion->priv->completion_timeout); @@ -9642,13 +9657,14 @@ static void clear_completion_callback (GtkEntry *entry, GParamSpec *pspec) { + GtkEntryCompletion *completion = gtk_entry_get_completion (entry); + + if (!completion->priv->inline_completion) + return; + if (pspec->name == I_("cursor-position") || pspec->name == I_("selection-bound")) - { - GtkEntryCompletion *completion = gtk_entry_get_completion (entry); - - completion->priv->has_completion = FALSE; - } + completion->priv->has_completion = FALSE; } static gboolean @@ -9656,6 +9672,9 @@ accept_completion_callback (GtkEntry *entry) { GtkEntryCompletion *completion = gtk_entry_get_completion (entry); + if (!completion->priv->inline_completion) + return FALSE; + if (completion->priv->has_completion) gtk_editable_set_position (GTK_EDITABLE (entry), gtk_entry_buffer_get_length (get_buffer (entry))); @@ -9670,6 +9689,9 @@ completion_insert_text_callback (GtkEntry *entry, gint position, GtkEntryCompletion *completion) { + if (!completion->priv->inline_completion) + return; + /* idle to update the selection based on the file list */ if (completion->priv->check_completion_idle == NULL) { @@ -9683,26 +9705,9 @@ completion_insert_text_callback (GtkEntry *entry, } static void -completion_changed (GtkEntryCompletion *completion, - GParamSpec *pspec, - gpointer data) -{ - GtkEntry *entry = GTK_ENTRY (data); - - if (pspec->name == I_("popup-completion") || - pspec->name == I_("inline-completion")) - { - disconnect_completion_signals (entry, completion); - connect_completion_signals (entry, completion); - } -} - -static void disconnect_completion_signals (GtkEntry *entry, GtkEntryCompletion *completion) { - g_signal_handlers_disconnect_by_func (completion, - G_CALLBACK (completion_changed), entry); if (completion->priv->changed_id > 0 && g_signal_handler_is_connected (entry, completion->priv->changed_id)) { @@ -9729,30 +9734,21 @@ static void connect_completion_signals (GtkEntry *entry, GtkEntryCompletion *completion) { - if (completion->priv->popup_completion) - { - completion->priv->changed_id = - g_signal_connect (entry, "changed", - G_CALLBACK (gtk_entry_completion_changed), completion); - g_signal_connect (entry, "key-press-event", - G_CALLBACK (gtk_entry_completion_key_press), completion); - } - - if (completion->priv->inline_completion) - { - completion->priv->insert_text_id = - g_signal_connect (entry, "insert-text", - G_CALLBACK (completion_insert_text_callback), completion); - g_signal_connect (entry, "notify", - G_CALLBACK (clear_completion_callback), completion); - g_signal_connect (entry, "activate", - G_CALLBACK (accept_completion_callback), completion); - g_signal_connect (entry, "focus-out-event", - G_CALLBACK (accept_completion_callback), completion); - } + completion->priv->changed_id = + g_signal_connect (entry, "changed", + G_CALLBACK (gtk_entry_completion_changed), completion); + g_signal_connect (entry, "key-press-event", + G_CALLBACK (gtk_entry_completion_key_press), completion); - g_signal_connect (completion, "notify", - G_CALLBACK (completion_changed), entry); + completion->priv->insert_text_id = + g_signal_connect (entry, "insert-text", + G_CALLBACK (completion_insert_text_callback), completion); + g_signal_connect (entry, "notify", + G_CALLBACK (clear_completion_callback), completion); + g_signal_connect (entry, "activate", + G_CALLBACK (accept_completion_callback), completion); + g_signal_connect (entry, "focus-out-event", + G_CALLBACK (accept_completion_callback), completion); } /** @@ -9789,6 +9785,12 @@ gtk_entry_set_completion (GtkEntry *entry, old->priv->completion_timeout = 0; } + if (old->priv->check_completion_idle) + { + g_source_destroy (old->priv->check_completion_idle); + old->priv->check_completion_idle = NULL; + } + if (gtk_widget_get_mapped (old->priv->popup_window)) _gtk_entry_completion_popdown (old); diff --git a/gtk/gtkentrycompletion.c b/gtk/gtkentrycompletion.c index 2fa7b56..df5f6d8 100644 --- a/gtk/gtkentrycompletion.c +++ b/gtk/gtkentrycompletion.c @@ -178,20 +178,20 @@ gtk_entry_completion_class_init (GtkEntryCompletionClass *klass) * GtkEntryCompletion::insert-prefix: * @widget: the object which received the signal * @prefix: the common prefix of all possible completions - * - * Gets emitted when the inline autocompletion is triggered. - * The default behaviour is to make the entry display the + * + * Gets emitted when the inline autocompletion is triggered. + * The default behaviour is to make the entry display the * whole prefix and select the newly inserted part. * * Applications may connect to this signal in order to insert only a * smaller part of the @prefix into the entry - e.g. the entry used in - * the #GtkFileChooser inserts only the part of the prefix up to the + * the #GtkFileChooser inserts only the part of the prefix up to the * next '/'. * * Return value: %TRUE if the signal has been handled - * + * * Since: 2.6 - */ + */ entry_completion_signals[INSERT_PREFIX] = g_signal_new (I_("insert-prefix"), G_TYPE_FROM_CLASS (klass), @@ -207,16 +207,16 @@ gtk_entry_completion_class_init (GtkEntryCompletionClass *klass) * @widget: the object which received the signal * @model: the #GtkTreeModel containing the matches * @iter: a #GtkTreeIter positioned at the selected match - * - * Gets emitted when a match from the list is selected. - * The default behaviour is to replace the contents of the - * entry with the contents of the text column in the row + * + * Gets emitted when a match from the list is selected. + * The default behaviour is to replace the contents of the + * entry with the contents of the text column in the row * pointed to by @iter. * * Return value: %TRUE if the signal has been handled - * + * * Since: 2.4 - */ + */ entry_completion_signals[MATCH_SELECTED] = g_signal_new (I_("match-selected"), G_TYPE_FROM_CLASS (klass), @@ -227,22 +227,22 @@ gtk_entry_completion_class_init (GtkEntryCompletionClass *klass) G_TYPE_BOOLEAN, 2, GTK_TYPE_TREE_MODEL, GTK_TYPE_TREE_ITER); + /** * GtkEntryCompletion::cursor-on-match: * @widget: the object which received the signal * @model: the #GtkTreeModel containing the matches * @iter: a #GtkTreeIter positioned at the selected match - * + * * Gets emitted when a match from the cursor is on a match - * of the list.The default behaviour is to replace the contents - * of the entry with the contents of the text column in the row + * of the list. The default behaviour is to replace the contents + * of the entry with the contents of the text column in the row * pointed to by @iter. * * Return value: %TRUE if the signal has been handled - * + * * Since: 2.12 - */ - + */ entry_completion_signals[CURSOR_ON_MATCH] = g_signal_new (I_("cursor-on-match"), G_TYPE_FROM_CLASS (klass), @@ -253,7 +253,7 @@ gtk_entry_completion_class_init (GtkEntryCompletionClass *klass) G_TYPE_BOOLEAN, 2, GTK_TYPE_TREE_MODEL, GTK_TYPE_TREE_ITER); - + /** * GtkEntryCompletion::action-activated: * @widget: the object which received the signal @@ -880,18 +880,23 @@ gtk_entry_completion_list_button_press (GtkWidget *widget, { GtkTreeIter iter; gboolean entry_set; + GtkTreeModel *model; + GtkTreeIter child_iter; gtk_tree_model_get_iter (GTK_TREE_MODEL (completion->priv->filter_model), &iter, path); gtk_tree_path_free (path); + gtk_tree_model_filter_convert_iter_to_child_iter (completion->priv->filter_model, + &child_iter, + &iter); + model = gtk_tree_model_filter_get_model (completion->priv->filter_model); g_signal_handler_block (completion->priv->entry, - completion->priv->changed_id); + completion->priv->changed_id); g_signal_emit (completion, entry_completion_signals[MATCH_SELECTED], - 0, GTK_TREE_MODEL (completion->priv->filter_model), - &iter, &entry_set); + 0, model, &child_iter, &entry_set); g_signal_handler_unblock (completion->priv->entry, - completion->priv->changed_id); + completion->priv->changed_id); _gtk_entry_completion_popdown (completion); @@ -1571,19 +1576,17 @@ gtk_entry_completion_cursor_on_match (GtkEntryCompletion *completion, return TRUE; } -static gchar * -gtk_entry_completion_compute_prefix (GtkEntryCompletion *completion) +gchar * +_gtk_entry_completion_compute_prefix (GtkEntryCompletion *completion, + const char *key) { GtkTreeIter iter; gchar *prefix = NULL; gboolean valid; - const gchar *key; if (completion->priv->text_column < 0) return NULL; - key = gtk_entry_get_text (GTK_ENTRY (completion->priv->entry)); - valid = gtk_tree_model_get_iter_first (GTK_TREE_MODEL (completion->priv->filter_model), &iter); @@ -1753,7 +1756,8 @@ gtk_entry_completion_insert_prefix (GtkEntryCompletion *completion) g_signal_handler_block (completion->priv->entry, completion->priv->insert_text_id); - prefix = gtk_entry_completion_compute_prefix (completion); + prefix = _gtk_entry_completion_compute_prefix (completion, + gtk_entry_get_text (GTK_ENTRY (completion->priv->entry))); if (prefix) { g_signal_emit (completion, entry_completion_signals[INSERT_PREFIX], diff --git a/gtk/gtkentryprivate.h b/gtk/gtkentryprivate.h index a767800..bdb41cb 100644 --- a/gtk/gtkentryprivate.h +++ b/gtk/gtkentryprivate.h @@ -74,6 +74,8 @@ struct _GtkEntryCompletionPrivate gboolean _gtk_entry_completion_resize_popup (GtkEntryCompletion *completion); void _gtk_entry_completion_popup (GtkEntryCompletion *completion); void _gtk_entry_completion_popdown (GtkEntryCompletion *completion); +gchar * _gtk_entry_completion_compute_prefix (GtkEntryCompletion *completion, + const char *key); void _gtk_entry_get_borders (GtkEntry *entry, gint *xborder, diff --git a/gtk/gtkfilechooser.c b/gtk/gtkfilechooser.c index 8dd0dec..62dd01e 100644 --- a/gtk/gtkfilechooser.c +++ b/gtk/gtkfilechooser.c @@ -1498,10 +1498,18 @@ gtk_file_chooser_get_uri (GtkFileChooser *chooser) if (file) { if (gtk_file_chooser_get_local_only (chooser)) - result = file_to_uri_with_native_path (file); + { + gchar *local = g_file_get_path (file); + if (local) + { + result = g_filename_to_uri (local, NULL, NULL); + g_free (local); + } + } else + { result = g_file_get_uri (file); - + } g_object_unref (file); } diff --git a/gtk/gtkfilechooserdefault.c b/gtk/gtkfilechooserdefault.c index 2a75365..db097d4 100644 --- a/gtk/gtkfilechooserdefault.c +++ b/gtk/gtkfilechooserdefault.c @@ -205,6 +205,7 @@ enum { MODEL_COL_FILE, MODEL_COL_NAME_COLLATED, MODEL_COL_IS_FOLDER, + MODEL_COL_IS_SENSITIVE, MODEL_COL_PIXBUF, MODEL_COL_SIZE_TEXT, MODEL_COL_MTIME_TEXT, @@ -221,6 +222,7 @@ enum { G_TYPE_FILE, /* MODEL_COL_FILE */ \ G_TYPE_STRING, /* MODEL_COL_NAME_COLLATED */ \ G_TYPE_BOOLEAN, /* MODEL_COL_IS_FOLDER */ \ + G_TYPE_BOOLEAN, /* MODEL_COL_IS_SENSITIVE */ \ GDK_TYPE_PIXBUF, /* MODEL_COL_PIXBUF */ \ G_TYPE_STRING, /* MODEL_COL_SIZE_TEXT */ \ G_TYPE_STRING, /* MODEL_COL_MTIME_TEXT */ \ @@ -3098,9 +3100,11 @@ shortcuts_drop_uris (GtkFileChooserDefault *impl, /* Reorders the selected bookmark to the specified position */ static void shortcuts_reorder (GtkFileChooserDefault *impl, + GtkSelectionData *selection_data, int new_position) { GtkTreeIter iter; + GtkTreeIter filter_iter; gpointer col_data; ShortcutType shortcut_type; GtkTreePath *path; @@ -3109,13 +3113,23 @@ shortcuts_reorder (GtkFileChooserDefault *impl, GFile *file; GError *error; gchar *name; + GtkTreeModel *model; /* Get the selected path */ - if (!shortcuts_get_selected (impl, &iter)) - g_assert_not_reached (); + if (!gtk_tree_get_row_drag_data (selection_data, &model, &path)) + return; + + g_assert (model == impl->shortcuts_pane_filter_model); + + gtk_tree_model_get_iter (model, &filter_iter, path); + gtk_tree_path_free (path); + gtk_tree_model_filter_convert_iter_to_child_iter (GTK_TREE_MODEL_FILTER (impl->shortcuts_pane_filter_model), + &iter, + &filter_iter); path = gtk_tree_model_get_path (GTK_TREE_MODEL (impl->shortcuts_model), &iter); + old_position = *gtk_tree_path_get_indices (path); gtk_tree_path_free (path); @@ -3197,7 +3211,7 @@ shortcuts_drag_data_received_cb (GtkWidget *widget, if (gtk_targets_include_uri (&target, 1)) shortcuts_drop_uris (impl, selection_data, position); else if (target == gdk_atom_intern_static_string ("GTK_TREE_MODEL_ROW")) - shortcuts_reorder (impl, position); + shortcuts_reorder (impl, selection_data, position); g_signal_stop_emission_by_name (widget, "drag-data-received"); } @@ -4414,8 +4428,6 @@ location_entry_create (GtkFileChooserDefault *impl) if (!impl->location_entry) impl->location_entry = _gtk_file_chooser_entry_new (TRUE); - _gtk_file_chooser_entry_set_file_system (GTK_FILE_CHOOSER_ENTRY (impl->location_entry), - impl->file_system); _gtk_file_chooser_entry_set_local_only (GTK_FILE_CHOOSER_ENTRY (impl->location_entry), impl->local_only); _gtk_file_chooser_entry_set_action (GTK_FILE_CHOOSER_ENTRY (impl->location_entry), impl->action); gtk_entry_set_width_chars (GTK_ENTRY (impl->location_entry), 45); @@ -6653,6 +6665,7 @@ file_system_model_got_thumbnail (GObject *object, GAsyncResult *res, gpointer da /* file was deleted */ if (!_gtk_file_system_model_get_iter_for_file (model, &iter, file)) { + g_object_unref (queried); GDK_THREADS_LEAVE (); return; } @@ -6663,9 +6676,10 @@ file_system_model_got_thumbnail (GObject *object, GAsyncResult *res, gpointer da copy_attribute (info, queried, G_FILE_ATTRIBUTE_THUMBNAILING_FAILED); copy_attribute (info, queried, G_FILE_ATTRIBUTE_STANDARD_ICON); - _gtk_file_system_model_update_file (model, file, info, FALSE); + _gtk_file_system_model_update_file (model, file, info); g_object_unref (info); + g_object_unref (queried); GDK_THREADS_LEAVE (); } @@ -6700,6 +6714,33 @@ file_system_model_set (GtkFileSystemModel *model, case MODEL_COL_IS_FOLDER: g_value_set_boolean (value, info == NULL || _gtk_file_info_consider_as_directory (info)); break; + case MODEL_COL_IS_SENSITIVE: + if (info) + { + gboolean sensitive = TRUE; + + if (impl->action != GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER && + impl->action != GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER) + { + sensitive = TRUE; + } + else if (!_gtk_file_info_consider_as_directory (info)) + { + sensitive = FALSE; + } + else + { + GtkTreeIter iter; + if (!_gtk_file_system_model_get_iter_for_file (model, &iter, file)) + g_assert_not_reached (); + sensitive = !_gtk_file_system_model_iter_is_filtered_out (model, &iter); + } + + g_value_set_boolean (value, sensitive); + } + else + g_value_set_boolean (value, TRUE); + break; case MODEL_COL_PIXBUF: if (info) { @@ -6934,7 +6975,7 @@ update_chooser_entry (GtkFileChooserDefault *impl) if (change_entry) { - _gtk_file_chooser_entry_set_file_part (GTK_FILE_CHOOSER_ENTRY (impl->location_entry), impl->browse_files_last_selected_name); + gtk_entry_set_text (GTK_ENTRY (impl->location_entry), impl->browse_files_last_selected_name); if (impl->action == GTK_FILE_CHOOSER_ACTION_SAVE) _gtk_file_chooser_entry_select_filename (GTK_FILE_CHOOSER_ENTRY (impl->location_entry)); @@ -6966,7 +7007,7 @@ update_chooser_entry (GtkFileChooserDefault *impl) g_free (impl->browse_files_last_selected_name); impl->browse_files_last_selected_name = NULL; - _gtk_file_chooser_entry_set_file_part (GTK_FILE_CHOOSER_ENTRY (impl->location_entry), ""); + gtk_entry_set_text (GTK_ENTRY (impl->location_entry), ""); return; } @@ -7001,7 +7042,7 @@ update_chooser_entry (GtkFileChooserDefault *impl) clear_entry = FALSE; if (clear_entry) - _gtk_file_chooser_entry_set_file_part (GTK_FILE_CHOOSER_ENTRY (impl->location_entry), ""); + gtk_entry_set_text (GTK_ENTRY (impl->location_entry), ""); } } @@ -7198,7 +7239,7 @@ update_current_folder_get_info_cb (GCancellable *cancellable, impl->current_folder); if (data->clear_entry) - _gtk_file_chooser_entry_set_file_part (GTK_FILE_CHOOSER_ENTRY (impl->location_entry), ""); + gtk_entry_set_text (GTK_ENTRY (impl->location_entry), ""); } /* Create a new list model. This is slightly evil; we store the result value @@ -7303,7 +7344,7 @@ gtk_file_chooser_default_set_current_name (GtkFileChooser *chooser, impl->action == GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER); pending_select_files_free (impl); - _gtk_file_chooser_entry_set_file_part (GTK_FILE_CHOOSER_ENTRY (impl->location_entry), name); + gtk_entry_set_text (GTK_ENTRY (impl->location_entry), name); } static gboolean @@ -7389,16 +7430,19 @@ maybe_select (GtkTreeModel *model, { GtkFileChooserDefault *impl = GTK_FILE_CHOOSER_DEFAULT (data); GtkTreeSelection *selection; + gboolean is_sensitive; gboolean is_folder; selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (impl->browse_files_tree_view)); gtk_tree_model_get (model, iter, MODEL_COL_IS_FOLDER, &is_folder, + MODEL_COL_IS_SENSITIVE, &is_sensitive, -1); - if ((is_folder && impl->action == GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER) || - (!is_folder && impl->action == GTK_FILE_CHOOSER_ACTION_OPEN)) + if (is_sensitive && + ((is_folder && impl->action == GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER) || + (!is_folder && impl->action == GTK_FILE_CHOOSER_ACTION_OPEN))) gtk_tree_selection_select_iter (selection, iter); else gtk_tree_selection_unselect_iter (selection, iter); @@ -7495,7 +7539,7 @@ check_save_entry (GtkFileChooserDefault *impl, if (!file_part || file_part[0] == '\0') { - *file_ret = g_object_ref (current_folder); + *file_ret = current_folder; *is_well_formed_ret = TRUE; *is_file_part_empty_ret = TRUE; *is_folder = TRUE; @@ -7507,6 +7551,7 @@ check_save_entry (GtkFileChooserDefault *impl, error = NULL; file = g_file_get_child_for_display_name (current_folder, file_part, &error); + g_object_unref (current_folder); if (!file) { @@ -8806,7 +8851,7 @@ gtk_file_chooser_default_should_respond (GtkFileChooserEmbed *chooser_embed) data = g_new0 (struct FileExistsData, 1); data->impl = g_object_ref (impl); data->file = g_object_ref (file); - data->parent_file = g_object_ref (_gtk_file_chooser_entry_get_current_folder (entry)); + data->parent_file = _gtk_file_chooser_entry_get_current_folder (entry); if (impl->file_exists_get_info_cancellable) g_cancellable_cancel (impl->file_exists_get_info_cancellable); @@ -9768,7 +9813,7 @@ shortcuts_activate_iter (GtkFileChooserDefault *impl, if (impl->location_entry && !(impl->action == GTK_FILE_CHOOSER_ACTION_SAVE || impl->action == GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER)) - _gtk_file_chooser_entry_set_file_part (GTK_FILE_CHOOSER_ENTRY (impl->location_entry), ""); + gtk_entry_set_text (GTK_ENTRY (impl->location_entry), ""); gtk_tree_model_get (GTK_TREE_MODEL (impl->shortcuts_model), iter, SHORTCUTS_COL_DATA, &col_data, @@ -9906,14 +9951,16 @@ list_select_func (GtkTreeSelection *selection, impl->action == GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER) { GtkTreeIter iter; + gboolean is_sensitive; gboolean is_folder; if (!gtk_tree_model_get_iter (model, &iter, path)) return FALSE; gtk_tree_model_get (model, &iter, + MODEL_COL_IS_SENSITIVE, &is_sensitive, MODEL_COL_IS_FOLDER, &is_folder, -1); - if (!is_folder) + if (!is_sensitive || !is_folder) return FALSE; } @@ -9963,6 +10010,7 @@ list_row_activated (GtkTreeView *tree_view, GtkTreeIter iter; GtkTreeModel *model; gboolean is_folder; + gboolean is_sensitive; model = gtk_tree_view_get_model (tree_view); @@ -9972,9 +10020,10 @@ list_row_activated (GtkTreeView *tree_view, gtk_tree_model_get (model, &iter, MODEL_COL_FILE, &file, MODEL_COL_IS_FOLDER, &is_folder, + MODEL_COL_IS_SENSITIVE, &is_sensitive, -1); - if (is_folder && file) + if (is_sensitive && is_folder && file) { change_folder_and_display_error (impl, file, FALSE); return; @@ -10015,10 +10064,6 @@ update_cell_renderer_attributes (GtkFileChooserDefault *impl) GtkTreeViewColumn *column; GtkCellRenderer *renderer; GList *walk, *list; - gboolean always_sensitive; - - always_sensitive = impl->action != GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER && - impl->action != GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER; /* Keep the following column numbers in sync with create_file_list() */ @@ -10041,10 +10086,8 @@ update_cell_renderer_attributes (GtkFileChooserDefault *impl) "ellipsize", MODEL_COL_ELLIPSIZE, NULL); } - if (always_sensitive) - g_object_set (renderer, "sensitive", TRUE, NULL); - else - gtk_tree_view_column_add_attribute (column, renderer, "sensitive", MODEL_COL_IS_FOLDER); + + gtk_tree_view_column_add_attribute (column, renderer, "sensitive", MODEL_COL_IS_SENSITIVE); } g_list_free (list); @@ -10055,10 +10098,8 @@ update_cell_renderer_attributes (GtkFileChooserDefault *impl) gtk_tree_view_column_set_attributes (column, renderer, "text", MODEL_COL_SIZE_TEXT, NULL); - if (always_sensitive) - g_object_set (renderer, "sensitive", TRUE, NULL); - else - gtk_tree_view_column_add_attribute (column, renderer, "sensitive", MODEL_COL_IS_FOLDER); + + gtk_tree_view_column_add_attribute (column, renderer, "sensitive", MODEL_COL_IS_SENSITIVE); g_list_free (list); /* mtime */ @@ -10068,10 +10109,7 @@ update_cell_renderer_attributes (GtkFileChooserDefault *impl) gtk_tree_view_column_set_attributes (column, renderer, "text", MODEL_COL_MTIME_TEXT, NULL); - if (always_sensitive) - g_object_set (renderer, "sensitive", TRUE, NULL); - else - gtk_tree_view_column_add_attribute (column, renderer, "sensitive", MODEL_COL_IS_FOLDER); + gtk_tree_view_column_add_attribute (column, renderer, "sensitive", MODEL_COL_IS_SENSITIVE); g_list_free (list); } @@ -10085,7 +10123,7 @@ static void location_set_user_text (GtkFileChooserDefault *impl, const gchar *path) { - _gtk_file_chooser_entry_set_file_part (GTK_FILE_CHOOSER_ENTRY (impl->location_entry), path); + gtk_entry_set_text (GTK_ENTRY (impl->location_entry), path); gtk_editable_set_position (GTK_EDITABLE (impl->location_entry), -1); } diff --git a/gtk/gtkfilechooserentry.c b/gtk/gtkfilechooserentry.c index 3caa7b8..7350793 100644 --- a/gtk/gtkfilechooserentry.c +++ b/gtk/gtkfilechooserentry.c @@ -19,13 +19,16 @@ */ #include "config.h" + +#include "gtkfilechooserentry.h" + #include <string.h> #include "gtkalignment.h" #include "gtkcelllayout.h" #include "gtkcellrenderertext.h" -#include "gtkentry.h" -#include "gtkfilechooserentry.h" +#include "gtkentryprivate.h" +#include "gtkfilesystemmodel.h" #include "gtklabel.h" #include "gtkmain.h" #include "gtkwindow.h" @@ -45,50 +48,21 @@ struct _GtkFileChooserEntryClass GtkEntryClass parent_class; }; -/* Action to take when the current folder finishes loading (for explicit or automatic completion) */ -typedef enum { - LOAD_COMPLETE_NOTHING, - LOAD_COMPLETE_AUTOCOMPLETE, - LOAD_COMPLETE_EXPLICIT_COMPLETION -} LoadCompleteAction; - -typedef enum -{ - REFRESH_OK, - REFRESH_INVALID_INPUT, - REFRESH_INCOMPLETE_HOSTNAME, - REFRESH_NONEXISTENT, - REFRESH_NOT_LOCAL -} RefreshStatus; - struct _GtkFileChooserEntry { GtkEntry parent_instance; GtkFileChooserAction action; - GtkFileSystem *file_system; GFile *base_folder; GFile *current_folder_file; + gchar *dir_part; gchar *file_part; - gint file_part_pos; - /* Folder being loaded or already loaded */ - GtkFolder *current_folder; - GCancellable *load_folder_cancellable; + GtkTreeModel *completion_store; - LoadCompleteAction load_complete_action; - - GtkListStore *completion_store; - - guint start_autocompletion_idle_id; - - GtkWidget *completion_feedback_window; - GtkWidget *completion_feedback_label; - guint completion_feedback_timeout_id; - - guint has_completion : 1; - guint in_change : 1; + guint current_folder_loaded : 1; + guint complete_on_load : 1; guint eat_tabs : 1; guint local_only : 1; }; @@ -96,35 +70,17 @@ struct _GtkFileChooserEntry enum { DISPLAY_NAME_COLUMN, - FILE_COLUMN, + FULL_PATH_COLUMN, N_COLUMNS }; -#define COMPLETION_FEEDBACK_TIMEOUT_MS 2000 - -static void gtk_file_chooser_entry_iface_init (GtkEditableClass *iface); - static void gtk_file_chooser_entry_finalize (GObject *object); static void gtk_file_chooser_entry_dispose (GObject *object); static void gtk_file_chooser_entry_grab_focus (GtkWidget *widget); -static void gtk_file_chooser_entry_unmap (GtkWidget *widget); -static gboolean gtk_file_chooser_entry_key_press_event (GtkWidget *widget, - GdkEventKey *event); +static gboolean gtk_file_chooser_entry_tab_handler (GtkWidget *widget, + GdkEventKey *event); static gboolean gtk_file_chooser_entry_focus_out_event (GtkWidget *widget, GdkEventFocus *event); -static void gtk_file_chooser_entry_activate (GtkEntry *entry); -static void gtk_file_chooser_entry_do_insert_text (GtkEditable *editable, - const gchar *new_text, - gint new_text_length, - gint *position); -static void gtk_file_chooser_entry_do_delete_text (GtkEditable *editable, - gint start_pos, - gint end_pos); -static void gtk_file_chooser_entry_set_position (GtkEditable *editable, - gint position); -static void gtk_file_chooser_entry_set_selection_bounds (GtkEditable *editable, - gint start_pos, - gint end_pos); #ifdef G_OS_WIN32 static gint insert_text_callback (GtkFileChooserEntry *widget, @@ -142,63 +98,71 @@ static gboolean match_selected_callback (GtkEntryCompletion *completion, GtkTreeModel *model, GtkTreeIter *iter, GtkFileChooserEntry *chooser_entry); -static gboolean completion_match_func (GtkEntryCompletion *comp, - const char *key, - GtkTreeIter *iter, - gpointer data); -static char *maybe_append_separator_to_file (GtkFileChooserEntry *chooser_entry, - GFile *file, - gchar *display_name, - gboolean *appended); - -typedef enum { - REFRESH_UP_TO_CURSOR_POSITION, - REFRESH_WHOLE_TEXT -} RefreshMode; - -static RefreshStatus refresh_current_folder_and_file_part (GtkFileChooserEntry *chooser_entry, - RefreshMode refresh_mode); -static void finished_loading_cb (GtkFolder *folder, - gpointer data); -static void autocomplete (GtkFileChooserEntry *chooser_entry); -static void install_start_autocompletion_idle (GtkFileChooserEntry *chooser_entry); -static void remove_completion_feedback (GtkFileChooserEntry *chooser_entry); -static void pop_up_completion_feedback (GtkFileChooserEntry *chooser_entry, - const gchar *feedback); - -static GtkEditableClass *parent_editable_iface; - -G_DEFINE_TYPE_WITH_CODE (GtkFileChooserEntry, _gtk_file_chooser_entry, GTK_TYPE_ENTRY, - G_IMPLEMENT_INTERFACE (GTK_TYPE_EDITABLE, - gtk_file_chooser_entry_iface_init)) + +static void set_complete_on_load (GtkFileChooserEntry *chooser_entry, + gboolean complete_on_load); +static void refresh_current_folder_and_file_part (GtkFileChooserEntry *chooser_entry); +static void set_completion_folder (GtkFileChooserEntry *chooser_entry, + GFile *folder, + char *dir_part); +static void finished_loading_cb (GtkFileSystemModel *model, + GError *error, + GtkFileChooserEntry *chooser_entry); + +G_DEFINE_TYPE (GtkFileChooserEntry, _gtk_file_chooser_entry, GTK_TYPE_ENTRY) + +static char * +gtk_file_chooser_entry_get_completion_text (GtkFileChooserEntry *chooser_entry) +{ + GtkEditable *editable = GTK_EDITABLE (chooser_entry); + int start, end; + + gtk_editable_get_selection_bounds (editable, &start, &end); + return gtk_editable_get_chars (editable, 0, MIN (start, end)); +} + +static void +gtk_file_chooser_entry_dispatch_properties_changed (GObject *object, + guint n_pspecs, + GParamSpec **pspecs) +{ + GtkFileChooserEntry *chooser_entry = GTK_FILE_CHOOSER_ENTRY (object); + guint i; + + G_OBJECT_CLASS (_gtk_file_chooser_entry_parent_class)->dispatch_properties_changed (object, n_pspecs, pspecs); + + /* Don't do this during or after disposal */ + if (gtk_widget_get_parent (GTK_WIDGET (object)) != NULL) + { + /* What we are after: The text in front of the cursor was modified. + * Unfortunately, there's no other way to catch this. + */ + for (i = 0; i < n_pspecs; i++) + { + if (pspecs[i]->name == I_("cursor-position") || + pspecs[i]->name == I_("selection-bound") || + pspecs[i]->name == I_("text")) + { + set_complete_on_load (chooser_entry, FALSE); + refresh_current_folder_and_file_part (chooser_entry); + break; + } + } + } +} static void _gtk_file_chooser_entry_class_init (GtkFileChooserEntryClass *class) { GObjectClass *gobject_class = G_OBJECT_CLASS (class); GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class); - GtkEntryClass *entry_class = GTK_ENTRY_CLASS (class); gobject_class->finalize = gtk_file_chooser_entry_finalize; gobject_class->dispose = gtk_file_chooser_entry_dispose; + gobject_class->dispatch_properties_changed = gtk_file_chooser_entry_dispatch_properties_changed; widget_class->grab_focus = gtk_file_chooser_entry_grab_focus; - widget_class->unmap = gtk_file_chooser_entry_unmap; - widget_class->key_press_event = gtk_file_chooser_entry_key_press_event; widget_class->focus_out_event = gtk_file_chooser_entry_focus_out_event; - - entry_class->activate = gtk_file_chooser_entry_activate; -} - -static void -gtk_file_chooser_entry_iface_init (GtkEditableClass *iface) -{ - parent_editable_iface = g_type_interface_peek_parent (iface); - - iface->do_insert_text = gtk_file_chooser_entry_do_insert_text; - iface->do_delete_text = gtk_file_chooser_entry_do_delete_text; - iface->set_position = gtk_file_chooser_entry_set_position; - iface->set_selection_bounds = gtk_file_chooser_entry_set_selection_bounds; } static void @@ -213,9 +177,14 @@ _gtk_file_chooser_entry_init (GtkFileChooserEntry *chooser_entry) comp = gtk_entry_completion_new (); gtk_entry_completion_set_popup_single_match (comp, FALSE); + gtk_entry_completion_set_minimum_key_length (comp, 0); + /* see docs for gtk_entry_completion_set_text_column() */ + g_object_set (comp, "text-column", FULL_PATH_COLUMN, NULL); + /* Need a match func here or entry completion uses a wrong one. + * We do our own filtering after all. */ gtk_entry_completion_set_match_func (comp, - completion_match_func, + (GtkEntryCompletionMatchFunc) gtk_true, chooser_entry, NULL); @@ -224,13 +193,17 @@ _gtk_file_chooser_entry_init (GtkFileChooserEntry *chooser_entry) cell, TRUE); gtk_cell_layout_add_attribute (GTK_CELL_LAYOUT (comp), cell, - "text", 0); + "text", DISPLAY_NAME_COLUMN); g_signal_connect (comp, "match-selected", G_CALLBACK (match_selected_callback), chooser_entry); gtk_entry_set_completion (GTK_ENTRY (chooser_entry), comp); g_object_unref (comp); + /* NB: This needs to happen after the completion is set, so this handler + * runs before the handler installed by entrycompletion */ + g_signal_connect (chooser_entry, "key-press-event", + G_CALLBACK (gtk_file_chooser_entry_tab_handler), NULL); #ifdef G_OS_WIN32 g_signal_connect (chooser_entry, "insert-text", @@ -248,78 +221,21 @@ gtk_file_chooser_entry_finalize (GObject *object) if (chooser_entry->base_folder) g_object_unref (chooser_entry->base_folder); - if (chooser_entry->current_folder) - g_object_unref (chooser_entry->current_folder); - if (chooser_entry->current_folder_file) g_object_unref (chooser_entry->current_folder_file); + g_free (chooser_entry->dir_part); g_free (chooser_entry->file_part); G_OBJECT_CLASS (_gtk_file_chooser_entry_parent_class)->finalize (object); } static void -discard_current_folder (GtkFileChooserEntry *chooser_entry) -{ - if (chooser_entry->current_folder) - { - g_signal_handlers_disconnect_by_func (chooser_entry->current_folder, - G_CALLBACK (finished_loading_cb), chooser_entry); - g_object_unref (chooser_entry->current_folder); - chooser_entry->current_folder = NULL; - } -} - -static void -discard_loading_and_current_folder_file (GtkFileChooserEntry *chooser_entry) -{ - if (chooser_entry->load_folder_cancellable) - { - g_cancellable_cancel (chooser_entry->load_folder_cancellable); - chooser_entry->load_folder_cancellable = NULL; - } - - if (chooser_entry->current_folder_file) - { - g_object_unref (chooser_entry->current_folder_file); - chooser_entry->current_folder_file = NULL; - } -} - -static void -discard_completion_store (GtkFileChooserEntry *chooser_entry) -{ - if (!chooser_entry->completion_store) - return; - - gtk_entry_completion_set_model (gtk_entry_get_completion (GTK_ENTRY (chooser_entry)), NULL); - g_object_unref (chooser_entry->completion_store); - chooser_entry->completion_store = NULL; -} - -static void gtk_file_chooser_entry_dispose (GObject *object) { GtkFileChooserEntry *chooser_entry = GTK_FILE_CHOOSER_ENTRY (object); - remove_completion_feedback (chooser_entry); - discard_current_folder (chooser_entry); - discard_loading_and_current_folder_file (chooser_entry); - - if (chooser_entry->start_autocompletion_idle_id != 0) - { - g_source_remove (chooser_entry->start_autocompletion_idle_id); - chooser_entry->start_autocompletion_idle_id = 0; - } - - discard_completion_store (chooser_entry); - - if (chooser_entry->file_system) - { - g_object_unref (chooser_entry->file_system); - chooser_entry->file_system = NULL; - } + set_completion_folder (chooser_entry, NULL, NULL); G_OBJECT_CLASS (_gtk_file_chooser_entry_parent_class)->dispose (object); } @@ -327,948 +243,197 @@ gtk_file_chooser_entry_dispose (GObject *object) /* Match functions for the GtkEntryCompletion */ static gboolean match_selected_callback (GtkEntryCompletion *completion, - GtkTreeModel *model, - GtkTreeIter *iter, - GtkFileChooserEntry *chooser_entry) + GtkTreeModel *model, + GtkTreeIter *iter, + GtkFileChooserEntry *chooser_entry) { - char *display_name; - GFile *file; + char *path; gint pos; - gboolean dummy; - - gtk_tree_model_get (model, iter, - DISPLAY_NAME_COLUMN, &display_name, - FILE_COLUMN, &file, - -1); - - if (!display_name || !file) - { - /* these shouldn't complain if passed NULL */ - g_object_unref (file); - g_free (display_name); - return FALSE; - } - - display_name = maybe_append_separator_to_file (chooser_entry, file, display_name, &dummy); - pos = chooser_entry->file_part_pos; + gtk_tree_model_get (model, iter, + FULL_PATH_COLUMN, &path, + -1); - /* We don't set in_change here as we want to update the current_folder - * variable */ gtk_editable_delete_text (GTK_EDITABLE (chooser_entry), - pos, -1); + 0, + gtk_editable_get_position (GTK_EDITABLE (chooser_entry))); + pos = 0; gtk_editable_insert_text (GTK_EDITABLE (chooser_entry), - display_name, -1, - &pos); - gtk_editable_set_position (GTK_EDITABLE (chooser_entry), -1); - - g_object_unref (file); - g_free (display_name); - - return TRUE; -} + path, + -1, + &pos); -/* Match function for the GtkEntryCompletion */ -static gboolean -completion_match_func (GtkEntryCompletion *comp, - const char *key_unused, - GtkTreeIter *iter, - gpointer data) -{ - GtkFileChooserEntry *chooser_entry; - char *name; - gboolean result; - char *norm_file_part; - char *norm_name; + gtk_editable_set_position (GTK_EDITABLE (chooser_entry), pos); - chooser_entry = GTK_FILE_CHOOSER_ENTRY (data); - - /* We ignore the key because it is the contents of the entry. Instead, we - * just use our precomputed file_part. - */ - if (!chooser_entry->file_part) - { - return FALSE; - } - - gtk_tree_model_get (GTK_TREE_MODEL (chooser_entry->completion_store), iter, DISPLAY_NAME_COLUMN, &name, -1); - if (!name) - { - return FALSE; /* Uninitialized row, ugh */ - } - - /* If we have an empty file_part, then we're at the root of a directory. In - * that case, we want to match all non-dot files. We might want to match - * dot_files too if show_hidden is TRUE on the fileselector in the future. - */ - /* Additionally, support for gnome .hidden files would be sweet, too */ - if (chooser_entry->file_part[0] == '\000') - { - if (name[0] == '.') - result = FALSE; - else - result = TRUE; - g_free (name); - - return result; - } - - - norm_file_part = g_utf8_normalize (chooser_entry->file_part, -1, G_NORMALIZE_ALL); - norm_name = g_utf8_normalize (name, -1, G_NORMALIZE_ALL); - -#ifdef G_PLATFORM_WIN32 - { - gchar *temp; - - temp = norm_file_part; - norm_file_part = g_utf8_casefold (norm_file_part, -1); - g_free (temp); - - temp = norm_name; - norm_name = g_utf8_casefold (norm_name, -1); - g_free (temp); - } -#endif - - result = (strncmp (norm_file_part, norm_name, strlen (norm_file_part)) == 0); - - g_free (norm_file_part); - g_free (norm_name); - g_free (name); - - return result; -} - -static void -clear_completions (GtkFileChooserEntry *chooser_entry) -{ - chooser_entry->has_completion = FALSE; - chooser_entry->load_complete_action = LOAD_COMPLETE_NOTHING; - - remove_completion_feedback (chooser_entry); -} - -static void -beep (GtkFileChooserEntry *chooser_entry) -{ - gtk_widget_error_bell (GTK_WIDGET (chooser_entry)); -} - -/* This function will append a directory separator to paths to - * display_name iff the path associated with it is a directory. - * maybe_append_separator_to_file will g_free the display_name and - * return a new one if needed. Otherwise, it will return the old one. - * You should be safe calling - * - * display_name = maybe_append_separator_to_file (entry, file, display_name, &appended); - * ... - * g_free (display_name); - */ -static char * -maybe_append_separator_to_file (GtkFileChooserEntry *chooser_entry, - GFile *file, - gchar *display_name, - gboolean *appended) -{ - *appended = FALSE; - - if (!g_str_has_suffix (display_name, G_DIR_SEPARATOR_S) && file) - { - GFileInfo *info; - - info = _gtk_folder_get_info (chooser_entry->current_folder, file); - - if (info) - { - if (_gtk_file_info_consider_as_directory (info)) - { - gchar *tmp = display_name; - display_name = g_strconcat (tmp, G_DIR_SEPARATOR_S, NULL); - *appended = TRUE; - g_free (tmp); - } - - g_object_unref (info); - } - } - - return display_name; -} - -static char * -trim_dir_separator_suffix (const char *str) -{ - int len; - - len = strlen (str); - if (len > 0 && G_IS_DIR_SEPARATOR (str[len - 1])) - return g_strndup (str, len - 1); - else - return g_strdup (str); -} - -/* Determines if the completion model has entries with a common prefix relative - * to the current contents of the entry. Also, if there's one and only one such - * path, stores it in unique_path_ret. - */ -static gboolean -find_common_prefix (GtkFileChooserEntry *chooser_entry, - gchar **common_prefix_ret, - GFile **unique_file_ret, - gboolean *is_complete_not_unique_ret, - gboolean *prefix_expands_the_file_part_ret, - GError **error) -{ - GtkEditable *editable; - GtkTreeIter iter; - gboolean parsed; - gboolean valid; - char *text_up_to_cursor; - GFile *parsed_folder_file; - char *parsed_file_part; - - *common_prefix_ret = NULL; - *unique_file_ret = NULL; - *is_complete_not_unique_ret = FALSE; - *prefix_expands_the_file_part_ret = FALSE; - - editable = GTK_EDITABLE (chooser_entry); - - text_up_to_cursor = gtk_editable_get_chars (editable, 0, gtk_editable_get_position (editable)); - - parsed = _gtk_file_system_parse (chooser_entry->file_system, - chooser_entry->base_folder, - text_up_to_cursor, - &parsed_folder_file, - &parsed_file_part, - error); - - g_free (text_up_to_cursor); - - if (!parsed) - return FALSE; - - g_assert (parsed_folder_file != NULL - && chooser_entry->current_folder != NULL - && g_file_equal (parsed_folder_file, chooser_entry->current_folder_file)); - - g_object_unref (parsed_folder_file); - - /* First pass: find the common prefix */ - - valid = gtk_tree_model_get_iter_first (GTK_TREE_MODEL (chooser_entry->completion_store), &iter); - - while (valid) - { - gchar *display_name; - GFile *file; - - gtk_tree_model_get (GTK_TREE_MODEL (chooser_entry->completion_store), - &iter, - DISPLAY_NAME_COLUMN, &display_name, - FILE_COLUMN, &file, - -1); - - if (g_str_has_prefix (display_name, parsed_file_part)) - { - if (!*common_prefix_ret) - { - *common_prefix_ret = trim_dir_separator_suffix (display_name); - *unique_file_ret = g_object_ref (file); - } - else - { - gchar *p = *common_prefix_ret; - const gchar *q = display_name; - - while (*p && *p == *q) - { - p++; - q++; - } - - *p = '\0'; - - if (*unique_file_ret) - { - g_object_unref (*unique_file_ret); - *unique_file_ret = NULL; - } - } - } - - g_free (display_name); - g_object_unref (file); - valid = gtk_tree_model_iter_next (GTK_TREE_MODEL (chooser_entry->completion_store), &iter); - } - - /* Second pass: see if the prefix we found is a complete match */ - - if (*common_prefix_ret != NULL) - { - valid = gtk_tree_model_get_iter_first (GTK_TREE_MODEL (chooser_entry->completion_store), &iter); - - while (valid) - { - gchar *display_name; - int len; - - gtk_tree_model_get (GTK_TREE_MODEL (chooser_entry->completion_store), - &iter, - DISPLAY_NAME_COLUMN, &display_name, - -1); - len = strlen (display_name); - g_assert (len > 0); - - if (G_IS_DIR_SEPARATOR (display_name[len - 1])) - len--; - - if (*unique_file_ret == NULL && strncmp (*common_prefix_ret, display_name, len) == 0) - *is_complete_not_unique_ret = TRUE; - - g_free (display_name); - valid = gtk_tree_model_iter_next (GTK_TREE_MODEL (chooser_entry->completion_store), &iter); - } - - /* Finally: Did we generate a new completion, or was the user's input already completed as far as it could go? */ - - *prefix_expands_the_file_part_ret = g_utf8_strlen (*common_prefix_ret, -1) > g_utf8_strlen (parsed_file_part, -1); - } - - g_free (parsed_file_part); + g_free (path); return TRUE; } -static gboolean -char_after_cursor_is_directory_separator (GtkFileChooserEntry *chooser_entry) -{ - int cursor_pos; - gboolean result; - - result = FALSE; - - cursor_pos = gtk_editable_get_position (GTK_EDITABLE (chooser_entry)); - if (cursor_pos < gtk_entry_get_text_length (GTK_ENTRY (chooser_entry))) - { - char *next_char_str; - - next_char_str = gtk_editable_get_chars (GTK_EDITABLE (chooser_entry), cursor_pos, cursor_pos + 1); - if (G_IS_DIR_SEPARATOR (*next_char_str)) - result = TRUE; - - g_free (next_char_str); - } - - return result; -} - -typedef enum { - INVALID_INPUT, /* what the user typed is bogus */ - NO_MATCH, /* no matches based on what the user typed */ - NOTHING_INSERTED_COMPLETE, /* what the user typed is already completed as far as it will go */ - NOTHING_INSERTED_UNIQUE, /* what the user typed is already completed, and is a unique match */ - COMPLETED, /* completion inserted (ambiguous suffix) */ - COMPLETED_UNIQUE, /* completion inserted, and it is a complete name and a unique match */ - COMPLETE_BUT_NOT_UNIQUE /* completion inserted, it is a complete name but not unique */ -} CommonPrefixResult; - -/* Finds a common prefix based on the contents of the entry - * and mandatorily appends it - */ -static CommonPrefixResult -append_common_prefix (GtkFileChooserEntry *chooser_entry, - gboolean highlight, - gboolean show_errors) -{ - gchar *common_prefix; - GFile *unique_file; - gboolean is_complete_not_unique; - gboolean prefix_expands_the_file_part; - GError *error; - CommonPrefixResult result = NO_MATCH; - gboolean have_result; - - clear_completions (chooser_entry); - - if (chooser_entry->completion_store == NULL) - return NO_MATCH; - - error = NULL; - if (!find_common_prefix (chooser_entry, &common_prefix, &unique_file, &is_complete_not_unique, &prefix_expands_the_file_part, &error)) - { - /* If the user types an incomplete hostname ("http://foo" without - * a slash after that), it's not an error. We just don't want to - * pop up a meaningless completion window in that state. - */ - if (!g_error_matches (error, GTK_FILE_CHOOSER_ERROR, GTK_FILE_CHOOSER_ERROR_INCOMPLETE_HOSTNAME) - && show_errors) - { - beep (chooser_entry); - pop_up_completion_feedback (chooser_entry, _("Invalid path")); - } - - g_error_free (error); - - return INVALID_INPUT; - } - - have_result = FALSE; - - if (unique_file) - { - if (!char_after_cursor_is_directory_separator (chooser_entry)) - { - gboolean appended; - - common_prefix = maybe_append_separator_to_file (chooser_entry, - unique_file, - common_prefix, - &appended); - if (appended) - prefix_expands_the_file_part = TRUE; - } - - g_object_unref (unique_file); - - if (prefix_expands_the_file_part) - result = COMPLETED_UNIQUE; - else - result = NOTHING_INSERTED_UNIQUE; - - have_result = TRUE; - } - else - { - if (is_complete_not_unique) - { - result = COMPLETE_BUT_NOT_UNIQUE; - have_result = TRUE; - } - } - - if (common_prefix) - { - gint cursor_pos; - gint pos; - - cursor_pos = gtk_editable_get_position (GTK_EDITABLE (chooser_entry)); - - pos = chooser_entry->file_part_pos; - - if (prefix_expands_the_file_part) - { - chooser_entry->in_change = TRUE; - gtk_editable_delete_text (GTK_EDITABLE (chooser_entry), - pos, cursor_pos); - gtk_editable_insert_text (GTK_EDITABLE (chooser_entry), - common_prefix, -1, - &pos); - chooser_entry->in_change = FALSE; - - if (highlight) - { - /* equivalent to cursor_pos + common_prefix_len); */ - gtk_editable_select_region (GTK_EDITABLE (chooser_entry), - cursor_pos, - pos); - chooser_entry->has_completion = TRUE; - } - else - gtk_editable_set_position (GTK_EDITABLE (chooser_entry), pos); - } - else if (!have_result) - { - result = NOTHING_INSERTED_COMPLETE; - have_result = TRUE; - } - - g_free (common_prefix); - - if (have_result) - return result; - else - return COMPLETED; - } - else - { - if (have_result) - return result; - else - return NO_MATCH; - } -} - -static void -gtk_file_chooser_entry_do_insert_text (GtkEditable *editable, - const gchar *new_text, - gint new_text_length, - gint *position) -{ - GtkFileChooserEntry *chooser_entry = GTK_FILE_CHOOSER_ENTRY (editable); - gint old_text_len; - gint insert_pos; - - old_text_len = gtk_entry_get_text_length (GTK_ENTRY (chooser_entry)); - insert_pos = *position; - - parent_editable_iface->do_insert_text (editable, new_text, new_text_length, position); - - if (chooser_entry->in_change) - return; - - remove_completion_feedback (chooser_entry); - - if ((chooser_entry->action == GTK_FILE_CHOOSER_ACTION_OPEN - || chooser_entry->action == GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER) - && insert_pos == old_text_len) - install_start_autocompletion_idle (chooser_entry); -} - -static void -clear_completions_if_not_in_change (GtkFileChooserEntry *chooser_entry) -{ - if (chooser_entry->in_change) - return; - - clear_completions (chooser_entry); -} - -static void -gtk_file_chooser_entry_do_delete_text (GtkEditable *editable, - gint start_pos, - gint end_pos) -{ - GtkFileChooserEntry *chooser_entry = GTK_FILE_CHOOSER_ENTRY (editable); - - parent_editable_iface->do_delete_text (editable, start_pos, end_pos); - - clear_completions_if_not_in_change (chooser_entry); -} - -static void -gtk_file_chooser_entry_set_position (GtkEditable *editable, - gint position) -{ - GtkFileChooserEntry *chooser_entry = GTK_FILE_CHOOSER_ENTRY (editable); - - parent_editable_iface->set_position (editable, position); - - clear_completions_if_not_in_change (chooser_entry); -} - -static void -gtk_file_chooser_entry_set_selection_bounds (GtkEditable *editable, - gint start_pos, - gint end_pos) -{ - GtkFileChooserEntry *chooser_entry = GTK_FILE_CHOOSER_ENTRY (editable); - - parent_editable_iface->set_selection_bounds (editable, start_pos, end_pos); - - clear_completions_if_not_in_change (chooser_entry); -} - -static void -gtk_file_chooser_entry_grab_focus (GtkWidget *widget) -{ - GTK_WIDGET_CLASS (_gtk_file_chooser_entry_parent_class)->grab_focus (widget); - _gtk_file_chooser_entry_select_filename (GTK_FILE_CHOOSER_ENTRY (widget)); -} - -static void -gtk_file_chooser_entry_unmap (GtkWidget *widget) -{ - GtkFileChooserEntry *chooser_entry = GTK_FILE_CHOOSER_ENTRY (widget); - - remove_completion_feedback (chooser_entry); - - GTK_WIDGET_CLASS (_gtk_file_chooser_entry_parent_class)->unmap (widget); -} - -static gboolean -completion_feedback_window_expose_event_cb (GtkWidget *widget, - GdkEventExpose *event, - gpointer data) -{ - /* Stolen from gtk_tooltip_paint_window() */ - - GtkFileChooserEntry *chooser_entry = GTK_FILE_CHOOSER_ENTRY (data); - - gtk_paint_flat_box (chooser_entry->completion_feedback_window->style, - chooser_entry->completion_feedback_window->window, - GTK_STATE_NORMAL, - GTK_SHADOW_OUT, - NULL, - chooser_entry->completion_feedback_window, - "tooltip", - 0, 0, - chooser_entry->completion_feedback_window->allocation.width, - chooser_entry->completion_feedback_window->allocation.height); - - return FALSE; -} - static void -set_invisible_mouse_cursor (GdkWindow *window) +set_complete_on_load (GtkFileChooserEntry *chooser_entry, + gboolean complete_on_load) { - GdkDisplay *display; - GdkCursor *cursor; - - display = gdk_window_get_display (window); - cursor = gdk_cursor_new_for_display (display, GDK_BLANK_CURSOR); - - gdk_window_set_cursor (window, cursor); - - gdk_cursor_unref (cursor); -} - -static void -completion_feedback_window_realize_cb (GtkWidget *widget, - gpointer data) -{ - /* We hide the mouse cursor inside the completion feedback window, since - * GtkEntry hides the cursor when the user types. We don't want the cursor to - * come back if the completion feedback ends up where the mouse is. + /* a completion was triggered, but we couldn't do it. + * So no text was inserted when pressing tab, so we beep */ - set_invisible_mouse_cursor (gtk_widget_get_window (widget)); -} + if (chooser_entry->complete_on_load && !complete_on_load) + gtk_widget_error_bell (GTK_WIDGET (chooser_entry)); -static void -create_completion_feedback_window (GtkFileChooserEntry *chooser_entry) -{ - /* Stolen from gtk_tooltip_init() */ - - GtkWidget *alignment; - - chooser_entry->completion_feedback_window = gtk_window_new (GTK_WINDOW_POPUP); - gtk_window_set_type_hint (GTK_WINDOW (chooser_entry->completion_feedback_window), - GDK_WINDOW_TYPE_HINT_TOOLTIP); - gtk_widget_set_app_paintable (chooser_entry->completion_feedback_window, TRUE); - gtk_window_set_resizable (GTK_WINDOW (chooser_entry->completion_feedback_window), FALSE); - gtk_widget_set_name (chooser_entry->completion_feedback_window, "gtk-tooltip"); - - alignment = gtk_alignment_new (0.5, 0.5, 1.0, 1.0); - gtk_alignment_set_padding (GTK_ALIGNMENT (alignment), - chooser_entry->completion_feedback_window->style->ythickness, - chooser_entry->completion_feedback_window->style->ythickness, - chooser_entry->completion_feedback_window->style->xthickness, - chooser_entry->completion_feedback_window->style->xthickness); - gtk_container_add (GTK_CONTAINER (chooser_entry->completion_feedback_window), alignment); - gtk_widget_show (alignment); - - g_signal_connect (chooser_entry->completion_feedback_window, "expose-event", - G_CALLBACK (completion_feedback_window_expose_event_cb), chooser_entry); - g_signal_connect (chooser_entry->completion_feedback_window, "realize", - G_CALLBACK (completion_feedback_window_realize_cb), chooser_entry); - /* FIXME: connect to motion-notify-event, and *show* the cursor when the mouse moves */ - - chooser_entry->completion_feedback_label = gtk_label_new (NULL); - gtk_container_add (GTK_CONTAINER (alignment), chooser_entry->completion_feedback_label); - gtk_widget_show (chooser_entry->completion_feedback_label); + chooser_entry->complete_on_load = complete_on_load; } static gboolean -completion_feedback_timeout_cb (gpointer data) -{ - GtkFileChooserEntry *chooser_entry = GTK_FILE_CHOOSER_ENTRY (data); - - chooser_entry->completion_feedback_timeout_id = 0; - - remove_completion_feedback (chooser_entry); - return FALSE; -} - -static void -install_completion_feedback_timer (GtkFileChooserEntry *chooser_entry) -{ - if (chooser_entry->completion_feedback_timeout_id != 0) - g_source_remove (chooser_entry->completion_feedback_timeout_id); - - chooser_entry->completion_feedback_timeout_id = gdk_threads_add_timeout (COMPLETION_FEEDBACK_TIMEOUT_MS, - completion_feedback_timeout_cb, - chooser_entry); -} - -/* Gets the x position of the text cursor in the entry, in widget coordinates */ -static void -get_entry_cursor_x (GtkFileChooserEntry *chooser_entry, - gint *x_ret) -{ - /* FIXME: see the docs for gtk_entry_get_layout_offsets(). As an exercise for - * the reader, you have to implement support for the entry's scroll offset and - * RTL layouts and all the fancy Pango stuff. - */ - - PangoLayout *layout; - gint layout_x, layout_y; - gint layout_index; - PangoRectangle strong_pos; - gint start_pos, end_pos; - - layout = gtk_entry_get_layout (GTK_ENTRY (chooser_entry)); - - gtk_entry_get_layout_offsets (GTK_ENTRY (chooser_entry), &layout_x, &layout_y); - - gtk_editable_get_selection_bounds (GTK_EDITABLE (chooser_entry), &start_pos, &end_pos); - layout_index = gtk_entry_text_index_to_layout_index (GTK_ENTRY (chooser_entry), - end_pos); - - - pango_layout_get_cursor_pos (layout, layout_index, &strong_pos, NULL); - - *x_ret = layout_x + strong_pos.x / PANGO_SCALE; -} - -static void -show_completion_feedback_window (GtkFileChooserEntry *chooser_entry) -{ - /* More or less stolen from gtk_tooltip_position() */ - - GtkRequisition feedback_req; - gint entry_x, entry_y; - gint cursor_x; - GtkAllocation *entry_allocation; - int feedback_x, feedback_y; - - gtk_widget_size_request (chooser_entry->completion_feedback_window, &feedback_req); - - gdk_window_get_origin (GTK_WIDGET (chooser_entry)->window, &entry_x, &entry_y); - entry_allocation = &(GTK_WIDGET (chooser_entry)->allocation); - - get_entry_cursor_x (chooser_entry, &cursor_x); - - /* FIXME: fit to the screen if we bump on the screen's edge */ - /* cheap "half M-width", use height as approximation of character em-size */ - feedback_x = entry_x + cursor_x + entry_allocation->height / 2; - feedback_y = entry_y + (entry_allocation->height - feedback_req.height) / 2; - - gtk_window_move (GTK_WINDOW (chooser_entry->completion_feedback_window), feedback_x, feedback_y); - gtk_widget_show (chooser_entry->completion_feedback_window); - - install_completion_feedback_timer (chooser_entry); -} - -static void -pop_up_completion_feedback (GtkFileChooserEntry *chooser_entry, - const gchar *feedback) -{ - if (chooser_entry->completion_feedback_window == NULL) - create_completion_feedback_window (chooser_entry); - - gtk_label_set_text (GTK_LABEL (chooser_entry->completion_feedback_label), feedback); - - show_completion_feedback_window (chooser_entry); -} - -static void -remove_completion_feedback (GtkFileChooserEntry *chooser_entry) +is_valid_scheme_character (char c) { - if (chooser_entry->completion_feedback_window) - gtk_widget_destroy (chooser_entry->completion_feedback_window); - - chooser_entry->completion_feedback_window = NULL; - chooser_entry->completion_feedback_label = NULL; - - if (chooser_entry->completion_feedback_timeout_id != 0) - { - g_source_remove (chooser_entry->completion_feedback_timeout_id); - chooser_entry->completion_feedback_timeout_id = 0; - } + return g_ascii_isalnum (c) || c == '+' || c == '-' || c == '.'; } -static void -explicitly_complete (GtkFileChooserEntry *chooser_entry) +static gboolean +has_uri_scheme (const char *str) { - CommonPrefixResult result; - - g_assert (chooser_entry->current_folder != NULL); - g_assert (_gtk_folder_is_finished_loading (chooser_entry->current_folder)); - - /* FIXME: see what Emacs does in case there is no common prefix, or there is more than one match: - * - * - If there is a common prefix, insert it (done) - * - If there is no common prefix, pop up the suggestion window - * - If there are no matches at all, beep and bring up a tooltip (done) - * - If the suggestion window is already up, scroll it - */ - result = append_common_prefix (chooser_entry, FALSE, TRUE); + const char *p; - switch (result) - { - case INVALID_INPUT: - /* We already beeped in append_common_prefix(); do nothing here */ - break; - - case NO_MATCH: - beep (chooser_entry); - /* translators: this text is shown when there are no completions - * for something the user typed in a file chooser entry - */ - pop_up_completion_feedback (chooser_entry, _("No match")); - break; - - case NOTHING_INSERTED_COMPLETE: - /* FIXME: pop up the suggestion window or scroll it */ - break; - - case NOTHING_INSERTED_UNIQUE: - /* translators: this text is shown when there is exactly one completion - * for something the user typed in a file chooser entry - */ - pop_up_completion_feedback (chooser_entry, _("Sole completion")); - break; - - case COMPLETED: - /* Nothing to do */ - break; + p = str; - case COMPLETED_UNIQUE: - /* Nothing to do */ - break; + if (!is_valid_scheme_character (*p)) + return FALSE; - case COMPLETE_BUT_NOT_UNIQUE: - /* translators: this text is shown when the text in a file chooser - * entry is a complete filename, but could be continued to find - * a longer match - */ - pop_up_completion_feedback (chooser_entry, _("Complete, but not unique")); - break; + do + p++; + while (is_valid_scheme_character (*p)); - default: - g_assert_not_reached (); - } + return (strncmp (p, "://", 3) == 0); } -static void -start_explicit_completion (GtkFileChooserEntry *chooser_entry) -{ - RefreshStatus status; - gboolean is_error; - char *feedback_msg; - - status = refresh_current_folder_and_file_part (chooser_entry, REFRESH_UP_TO_CURSOR_POSITION); - - is_error = FALSE; +static GFile * +gtk_file_chooser_get_file_for_text (GtkFileChooserEntry *chooser_entry, + const gchar *str) +{ + GFile *file; - switch (status) - { - case REFRESH_OK: - g_assert (chooser_entry->current_folder_file != NULL); + if (str[0] == '~' || g_path_is_absolute (str) || has_uri_scheme (str)) + file = g_file_parse_name (str); + else if (chooser_entry->base_folder != NULL) + file = g_file_resolve_relative_path (chooser_entry->base_folder, str); + else + file = NULL; - if (chooser_entry->current_folder && _gtk_folder_is_finished_loading (chooser_entry->current_folder)) - explicitly_complete (chooser_entry); - else - { - chooser_entry->load_complete_action = LOAD_COMPLETE_EXPLICIT_COMPLETION; + return file; +} - /* Translators: this text is shown while the system is searching - * for possible completions for filenames in a file chooser entry. */ - pop_up_completion_feedback (chooser_entry, _("Completing...")); - } +static gboolean +is_directory_shortcut (const char *text) +{ + return strcmp (text, ".") == 0 || + strcmp (text, "..") == 0 || + strcmp (text, "~" ) == 0; +} - break; +static GFile * +gtk_file_chooser_get_directory_for_text (GtkFileChooserEntry *chooser_entry, + const char * text) +{ + GFile *file, *parent; - case REFRESH_INVALID_INPUT: - is_error = TRUE; - /* Translators: this is shown in the feedback for Tab-completion in a file - * chooser's text entry, when the user enters an invalid path. */ - feedback_msg = _("Invalid path"); - break; + file = gtk_file_chooser_get_file_for_text (chooser_entry, text); - case REFRESH_INCOMPLETE_HOSTNAME: - is_error = TRUE; + if (file == NULL) + return NULL; - if (chooser_entry->local_only) - { - /* hostnames in a local_only file chooser? user error */ + if (text[0] == 0 || text[strlen (text) - 1] == G_DIR_SEPARATOR || + is_directory_shortcut (text)) + return file; - /* Translators: this is shown in the feedback for Tab-completion in a - * file chooser's text entry when the user enters something like - * "sftp://blahblah" in an app that only supports local filenames. */ - feedback_msg = _("Only local files may be selected"); - } - else - { - /* Another option is to complete the hostname based on the remote volumes that are mounted */ + parent = g_file_get_parent (file); + g_object_unref (file); - /* Translators: this is shown in the feedback for Tab-completion in a - * file chooser's text entry when the user hasn't entered the first '/' - * after a hostname and yet hits Tab (such as "sftp://blahblah[Tab]") */ - feedback_msg = _("Incomplete hostname; end it with '/'"); - } + return parent; +} - break; +/* Finds a common prefix based on the contents of the entry + * and mandatorily appends it + */ +static void +explicitly_complete (GtkFileChooserEntry *chooser_entry) +{ + chooser_entry->complete_on_load = FALSE; - case REFRESH_NONEXISTENT: - is_error = TRUE; + if (chooser_entry->completion_store) + { + char *completion, *text; + gsize completion_len, text_len; - /* Translators: this is shown in the feedback for Tab-completion in a file - * chooser's text entry when the user enters a path that does not exist - * and then hits Tab */ - feedback_msg = _("Path does not exist"); - break; + text = gtk_file_chooser_entry_get_completion_text (chooser_entry); + text_len = strlen (text); + completion = _gtk_entry_completion_compute_prefix (gtk_entry_get_completion (GTK_ENTRY (chooser_entry)), text); + completion_len = completion ? strlen (completion) : 0; - case REFRESH_NOT_LOCAL: - is_error = TRUE; - feedback_msg = _("Only local files may be selected"); - break; + if (completion_len > text_len) + { + GtkEditable *editable = GTK_EDITABLE (chooser_entry); + int pos = gtk_editable_get_position (editable); - default: - g_assert_not_reached (); - return; + gtk_editable_insert_text (editable, + completion + text_len, + completion_len - text_len, + &pos); + gtk_editable_set_position (editable, pos); + return; + } } - if (is_error) - { - g_assert (chooser_entry->current_folder_file == NULL); + gtk_widget_error_bell (GTK_WIDGET (chooser_entry)); +} - beep (chooser_entry); - pop_up_completion_feedback (chooser_entry, feedback_msg); - chooser_entry->load_complete_action = LOAD_COMPLETE_NOTHING; - } +static void +gtk_file_chooser_entry_grab_focus (GtkWidget *widget) +{ + GTK_WIDGET_CLASS (_gtk_file_chooser_entry_parent_class)->grab_focus (widget); + _gtk_file_chooser_entry_select_filename (GTK_FILE_CHOOSER_ENTRY (widget)); +} + +static void +start_explicit_completion (GtkFileChooserEntry *chooser_entry) +{ + if (chooser_entry->current_folder_loaded) + explicitly_complete (chooser_entry); + else + set_complete_on_load (chooser_entry, TRUE); } static gboolean -gtk_file_chooser_entry_key_press_event (GtkWidget *widget, - GdkEventKey *event) +gtk_file_chooser_entry_tab_handler (GtkWidget *widget, + GdkEventKey *event) { GtkFileChooserEntry *chooser_entry; GtkEditable *editable; - GtkEntry *entry; GdkModifierType state; - gboolean control_pressed; + gint start, end; chooser_entry = GTK_FILE_CHOOSER_ENTRY (widget); editable = GTK_EDITABLE (widget); - entry = GTK_ENTRY (widget); if (!chooser_entry->eat_tabs) - return GTK_WIDGET_CLASS (_gtk_file_chooser_entry_parent_class)->key_press_event (widget, event); + return FALSE; - control_pressed = FALSE; + if (event->keyval != GDK_KEY_Tab) + return FALSE; - if (gtk_get_current_event_state (&state)) - { - if ((state & GDK_CONTROL_MASK) == GDK_CONTROL_MASK) - control_pressed = TRUE; - } + if (gtk_get_current_event_state (&state) && + (state & GDK_CONTROL_MASK) == GDK_CONTROL_MASK) + return FALSE; /* This is a bit evil -- it makes Tab never leave the entry. It basically * makes it 'safe' for people to hit. */ - if (event->keyval == GDK_Tab && !control_pressed) - { - if (chooser_entry->has_completion) - gtk_editable_set_position (editable, gtk_entry_get_text_length (entry)); - else - start_explicit_completion (chooser_entry); - - return TRUE; - } - - return GTK_WIDGET_CLASS (_gtk_file_chooser_entry_parent_class)->key_press_event (widget, event); + gtk_editable_get_selection_bounds (editable, &start, &end); + + if (start != end) + gtk_editable_set_position (editable, MAX (start, end)); + else + start_explicit_completion (chooser_entry); + return TRUE; } static gboolean @@ -1277,420 +442,234 @@ gtk_file_chooser_entry_focus_out_event (GtkWidget *widget, { GtkFileChooserEntry *chooser_entry = GTK_FILE_CHOOSER_ENTRY (widget); - chooser_entry->load_complete_action = LOAD_COMPLETE_NOTHING; + set_complete_on_load (chooser_entry, FALSE); return GTK_WIDGET_CLASS (_gtk_file_chooser_entry_parent_class)->focus_out_event (widget, event); } static void -commit_completion_and_refresh (GtkFileChooserEntry *chooser_entry) +update_inline_completion (GtkFileChooserEntry *chooser_entry) { - if (chooser_entry->has_completion) + GtkEntryCompletion *completion = gtk_entry_get_completion (GTK_ENTRY (chooser_entry)); + + if (!chooser_entry->current_folder_loaded) { - gtk_editable_set_position (GTK_EDITABLE (chooser_entry), - gtk_entry_get_text_length (GTK_ENTRY (chooser_entry))); + gtk_entry_completion_set_inline_completion (completion, FALSE); + return; } - /* Here we ignore the result of refresh_current_folder_and_file_part(); there is nothing we can do with it */ - refresh_current_folder_and_file_part (chooser_entry, REFRESH_WHOLE_TEXT); + switch (chooser_entry->action) + { + case GTK_FILE_CHOOSER_ACTION_OPEN: + case GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER: + gtk_entry_completion_set_inline_completion (completion, TRUE); + break; + case GTK_FILE_CHOOSER_ACTION_SAVE: + case GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER: + gtk_entry_completion_set_inline_completion (completion, FALSE); + break; + } } static void -gtk_file_chooser_entry_activate (GtkEntry *entry) +discard_completion_store (GtkFileChooserEntry *chooser_entry) { - GtkFileChooserEntry *chooser_entry = GTK_FILE_CHOOSER_ENTRY (entry); + if (!chooser_entry->completion_store) + return; - commit_completion_and_refresh (chooser_entry); - GTK_ENTRY_CLASS (_gtk_file_chooser_entry_parent_class)->activate (entry); + gtk_entry_completion_set_model (gtk_entry_get_completion (GTK_ENTRY (chooser_entry)), NULL); + update_inline_completion (chooser_entry); + g_object_unref (chooser_entry->completion_store); + chooser_entry->completion_store = NULL; } -/* Fills the completion store from the contents of the current folder */ -static void -populate_completion_store (GtkFileChooserEntry *chooser_entry) +static gboolean +completion_store_set (GtkFileSystemModel *model, + GFile *file, + GFileInfo *info, + int column, + GValue *value, + gpointer data) { - GSList *files; - GSList *tmp_list; - - discard_completion_store (chooser_entry); - - files = _gtk_folder_list_children (chooser_entry->current_folder); - - chooser_entry->completion_store = gtk_list_store_new (N_COLUMNS, - G_TYPE_STRING, - G_TYPE_FILE); - - for (tmp_list = files; tmp_list; tmp_list = tmp_list->next) - { - GFileInfo *info; - GFile *file; - - file = tmp_list->data; - - info = _gtk_folder_get_info (chooser_entry->current_folder, file); - - if (info) - { - gchar *display_name = g_strdup (g_file_info_get_display_name (info)); - GtkTreeIter iter; - gboolean dummy; - - display_name = maybe_append_separator_to_file (chooser_entry, file, display_name, &dummy); - - gtk_list_store_append (chooser_entry->completion_store, &iter); - gtk_list_store_set (chooser_entry->completion_store, &iter, - DISPLAY_NAME_COLUMN, display_name, - FILE_COLUMN, file, - -1); - - g_object_unref (info); - g_free (display_name); - } - } - - g_slist_foreach (files, (GFunc) g_object_unref, NULL); - g_slist_free (files); - - gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (chooser_entry->completion_store), - DISPLAY_NAME_COLUMN, GTK_SORT_ASCENDING); + GtkFileChooserEntry *chooser_entry = data; - gtk_entry_completion_set_model (gtk_entry_get_completion (GTK_ENTRY (chooser_entry)), - GTK_TREE_MODEL (chooser_entry->completion_store)); -} + const char *prefix = ""; + const char *suffix = ""; -/* When we finish loading the current folder, this function should get called to - * perform the deferred autocompletion or explicit completion. - */ -static void -perform_load_complete_action (GtkFileChooserEntry *chooser_entry) -{ - switch (chooser_entry->load_complete_action) + switch (column) { - case LOAD_COMPLETE_NOTHING: - break; - - case LOAD_COMPLETE_AUTOCOMPLETE: - autocomplete (chooser_entry); - break; + case FULL_PATH_COLUMN: + prefix = chooser_entry->dir_part; + /* fall through */ + case DISPLAY_NAME_COLUMN: + if (_gtk_file_info_consider_as_directory (info)) + suffix = G_DIR_SEPARATOR_S; - case LOAD_COMPLETE_EXPLICIT_COMPLETION: - explicitly_complete (chooser_entry); + g_value_take_string (value, + g_strconcat (prefix, + g_file_info_get_display_name (info), + suffix, + NULL)); break; - default: g_assert_not_reached (); + break; } - chooser_entry->load_complete_action = LOAD_COMPLETE_NOTHING; + return TRUE; } +/* Fills the completion store from the contents of the current folder */ static void -finish_folder_load (GtkFileChooserEntry *chooser_entry) +populate_completion_store (GtkFileChooserEntry *chooser_entry) { - populate_completion_store (chooser_entry); - perform_load_complete_action (chooser_entry); + chooser_entry->completion_store = GTK_TREE_MODEL ( + _gtk_file_system_model_new_for_directory (chooser_entry->current_folder_file, + "standard::name,standard::display-name,standard::type", + completion_store_set, + chooser_entry, + N_COLUMNS, + G_TYPE_STRING, + G_TYPE_STRING)); + g_signal_connect (chooser_entry->completion_store, "finished-loading", + G_CALLBACK (finished_loading_cb), chooser_entry); + + _gtk_file_system_model_set_filter_folders (GTK_FILE_SYSTEM_MODEL (chooser_entry->completion_store), + TRUE); + _gtk_file_system_model_set_show_files (GTK_FILE_SYSTEM_MODEL (chooser_entry->completion_store), + chooser_entry->action == GTK_FILE_CHOOSER_ACTION_OPEN || + chooser_entry->action == GTK_FILE_CHOOSER_ACTION_SAVE); + gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (chooser_entry->completion_store), + DISPLAY_NAME_COLUMN, GTK_SORT_ASCENDING); - gtk_widget_set_tooltip_text (GTK_WIDGET (chooser_entry), NULL); + gtk_entry_completion_set_model (gtk_entry_get_completion (GTK_ENTRY (chooser_entry)), + chooser_entry->completion_store); } /* Callback when the current folder finishes loading */ static void -finished_loading_cb (GtkFolder *folder, - gpointer data) -{ - GtkFileChooserEntry *chooser_entry = GTK_FILE_CHOOSER_ENTRY (data); - - finish_folder_load (chooser_entry); -} - -/* Callback when the current folder's handle gets obtained (not necessarily loaded completely) */ -static void -load_directory_get_folder_callback (GCancellable *cancellable, - GtkFolder *folder, - const GError *error, - gpointer data) +finished_loading_cb (GtkFileSystemModel *model, + GError *error, + GtkFileChooserEntry *chooser_entry) { - gboolean cancelled = g_cancellable_is_cancelled (cancellable); - GtkFileChooserEntry *chooser_entry = data; - - if (cancellable != chooser_entry->load_folder_cancellable) - goto out; + GtkEntryCompletion *completion; - chooser_entry->load_folder_cancellable = NULL; + chooser_entry->current_folder_loaded = TRUE; if (error) { - LoadCompleteAction old_load_complete_action; - - old_load_complete_action = chooser_entry->load_complete_action; - discard_completion_store (chooser_entry); - clear_completions (chooser_entry); - - if (old_load_complete_action == LOAD_COMPLETE_EXPLICIT_COMPLETION) - { - /* Since this came from explicit user action (Tab completion), we'll present errors visually */ - - beep (chooser_entry); - pop_up_completion_feedback (chooser_entry, error->message); - } - - discard_current_folder (chooser_entry); + set_complete_on_load (chooser_entry, FALSE); + return; } - if (cancelled || error) - goto out; + if (chooser_entry->complete_on_load) + explicitly_complete (chooser_entry); - g_assert (folder != NULL); - chooser_entry->current_folder = g_object_ref (folder); - - discard_completion_store (chooser_entry); + gtk_widget_set_tooltip_text (GTK_WIDGET (chooser_entry), NULL); - if (_gtk_folder_is_finished_loading (chooser_entry->current_folder)) - finish_folder_load (chooser_entry); - else - g_signal_connect (chooser_entry->current_folder, "finished-loading", - G_CALLBACK (finished_loading_cb), chooser_entry); + completion = gtk_entry_get_completion (GTK_ENTRY (chooser_entry)); + update_inline_completion (chooser_entry); -out: - g_object_unref (chooser_entry); - g_object_unref (cancellable); + if (gtk_widget_has_focus (GTK_WIDGET (chooser_entry))) + { + gtk_entry_completion_complete (completion); + gtk_entry_completion_insert_prefix (completion); + } } -static RefreshStatus -start_loading_current_folder (GtkFileChooserEntry *chooser_entry) +static void +set_completion_folder (GtkFileChooserEntry *chooser_entry, + GFile *folder_file, + char *dir_part) { - if (chooser_entry->file_system == NULL) - return REFRESH_OK; + if (folder_file && + chooser_entry->local_only + && !_gtk_file_has_native_path (folder_file)) + folder_file = NULL; - g_assert (chooser_entry->current_folder_file != NULL); - g_assert (chooser_entry->current_folder == NULL); - g_assert (chooser_entry->load_folder_cancellable == NULL); + if (((chooser_entry->current_folder_file + && folder_file + && g_file_equal (folder_file, chooser_entry->current_folder_file)) + || chooser_entry->current_folder_file == folder_file) + && g_strcmp0 (dir_part, chooser_entry->dir_part) == 0) + { + return; + } - if (chooser_entry->local_only - && !_gtk_file_has_native_path (chooser_entry->current_folder_file)) + if (chooser_entry->current_folder_file) { g_object_unref (chooser_entry->current_folder_file); chooser_entry->current_folder_file = NULL; - - return REFRESH_NOT_LOCAL; } - chooser_entry->load_folder_cancellable = - _gtk_file_system_get_folder (chooser_entry->file_system, - chooser_entry->current_folder_file, - "standard::name,standard::display-name,standard::type", - load_directory_get_folder_callback, - g_object_ref (chooser_entry)); - - return REFRESH_OK; -} - -static RefreshStatus -reload_current_folder (GtkFileChooserEntry *chooser_entry, - GFile *folder_file, - gboolean force_reload) -{ - gboolean reload = FALSE; - - g_assert (folder_file != NULL); - - if (chooser_entry->current_folder_file) - { - if ((!(g_file_equal (folder_file, chooser_entry->current_folder_file) - && chooser_entry->load_folder_cancellable)) - || force_reload) - { - reload = TRUE; + g_free (chooser_entry->dir_part); + chooser_entry->dir_part = g_strdup (dir_part); + + chooser_entry->current_folder_loaded = FALSE; - discard_current_folder (chooser_entry); - discard_loading_and_current_folder_file (chooser_entry); + discard_completion_store (chooser_entry); - chooser_entry->current_folder_file = g_object_ref (folder_file); - } - } - else + if (folder_file) { chooser_entry->current_folder_file = g_object_ref (folder_file); - reload = TRUE; + populate_completion_store (chooser_entry); } - - if (reload) - return start_loading_current_folder (chooser_entry); - else - return REFRESH_OK; } -static RefreshStatus -refresh_current_folder_and_file_part (GtkFileChooserEntry *chooser_entry, - RefreshMode refresh_mode) +static void +refresh_current_folder_and_file_part (GtkFileChooserEntry *chooser_entry) { - GtkEditable *editable; - gint end_pos; - gchar *text; GFile *folder_file; - gchar *file_part; - gsize total_len, file_part_len; - gint file_part_pos; - GError *error; - RefreshStatus result; + char *text, *last_slash, *old_file_part; + char *dir_part; - editable = GTK_EDITABLE (chooser_entry); + old_file_part = chooser_entry->file_part; - switch (refresh_mode) - { - case REFRESH_UP_TO_CURSOR_POSITION: - end_pos = gtk_editable_get_position (editable); - break; - - case REFRESH_WHOLE_TEXT: - end_pos = gtk_entry_get_text_length (GTK_ENTRY (chooser_entry)); - break; - - default: - g_assert_not_reached (); - return REFRESH_INVALID_INPUT; - } + text = gtk_file_chooser_entry_get_completion_text (chooser_entry); - text = gtk_editable_get_chars (editable, 0, end_pos); - - error = NULL; - if (!chooser_entry->file_system || - !_gtk_file_system_parse (chooser_entry->file_system, - chooser_entry->base_folder, text, - &folder_file, &file_part, &error)) + last_slash = strrchr (text, G_DIR_SEPARATOR); + if (last_slash) { - if (g_error_matches (error, GTK_FILE_CHOOSER_ERROR, GTK_FILE_CHOOSER_ERROR_INCOMPLETE_HOSTNAME)) - { - folder_file = NULL; - result = REFRESH_INCOMPLETE_HOSTNAME; - } - else - { - folder_file = (chooser_entry->base_folder) ? g_object_ref (chooser_entry->base_folder) : NULL; - - if (g_error_matches (error, GTK_FILE_CHOOSER_ERROR, GTK_FILE_CHOOSER_ERROR_NONEXISTENT)) - result = REFRESH_NONEXISTENT; - else - result = REFRESH_INVALID_INPUT; - } - - if (error) - g_error_free (error); - - file_part = g_strdup (""); - file_part_pos = -1; + dir_part = g_strndup (text, last_slash - text + 1); + chooser_entry->file_part = g_strdup (last_slash + 1); } else { - g_assert (folder_file != NULL); - - file_part_len = strlen (file_part); - total_len = strlen (text); - if (total_len > file_part_len) - file_part_pos = g_utf8_strlen (text, total_len - file_part_len); - else - file_part_pos = 0; - - result = REFRESH_OK; + dir_part = g_strdup (""); + chooser_entry->file_part = g_strdup (text); } - g_free (text); - - g_free (chooser_entry->file_part); - - chooser_entry->file_part = file_part; - chooser_entry->file_part_pos = file_part_pos; + folder_file = gtk_file_chooser_get_directory_for_text (chooser_entry, text); - if (result == REFRESH_OK) - { - result = reload_current_folder (chooser_entry, folder_file, file_part_pos == -1); - } - else - { - discard_current_folder (chooser_entry); - discard_loading_and_current_folder_file (chooser_entry); - } + set_completion_folder (chooser_entry, folder_file, dir_part); if (folder_file) g_object_unref (folder_file); - g_assert (/* we are OK and we have a current folder file and (loading process or folder handle)... */ - ((result == REFRESH_OK) - && (chooser_entry->current_folder_file != NULL) - && (((chooser_entry->load_folder_cancellable != NULL) && (chooser_entry->current_folder == NULL)) - || ((chooser_entry->load_folder_cancellable == NULL) && (chooser_entry->current_folder != NULL)))) - /* ... OR we have an error, and we don't have a current folder file nor a loading process nor a folder handle */ - || ((result != REFRESH_OK) - && (chooser_entry->current_folder_file == NULL) - && (chooser_entry->load_folder_cancellable == NULL) - && (chooser_entry->current_folder == NULL))); - - return result; -} - -static void -autocomplete (GtkFileChooserEntry *chooser_entry) -{ - if (!(chooser_entry->current_folder != NULL - && _gtk_folder_is_finished_loading (chooser_entry->current_folder) - && gtk_editable_get_position (GTK_EDITABLE (chooser_entry)) == gtk_entry_get_text_length (GTK_ENTRY (chooser_entry)))) - return; - - append_common_prefix (chooser_entry, TRUE, FALSE); -} - -static void -start_autocompletion (GtkFileChooserEntry *chooser_entry) -{ - RefreshStatus status; - - status = refresh_current_folder_and_file_part (chooser_entry, REFRESH_UP_TO_CURSOR_POSITION); + g_free (dir_part); - switch (status) + if (chooser_entry->completion_store && + (g_strcmp0 (old_file_part, chooser_entry->file_part) != 0)) { - case REFRESH_OK: - g_assert (chooser_entry->current_folder_file != NULL); + GtkFileFilter *filter; + char *pattern; - if (chooser_entry->current_folder && _gtk_folder_is_finished_loading (chooser_entry->current_folder)) - autocomplete (chooser_entry); - else - chooser_entry->load_complete_action = LOAD_COMPLETE_AUTOCOMPLETE; + filter = gtk_file_filter_new (); + pattern = g_strconcat (chooser_entry->file_part, "*", NULL); + gtk_file_filter_add_pattern (filter, pattern); - break; + g_object_ref_sink (filter); - case REFRESH_INVALID_INPUT: - case REFRESH_INCOMPLETE_HOSTNAME: - case REFRESH_NONEXISTENT: - case REFRESH_NOT_LOCAL: - /* We don't beep or anything, since this is autocompletion - the user - * didn't request any action explicitly. - */ - break; + _gtk_file_system_model_set_filter (GTK_FILE_SYSTEM_MODEL (chooser_entry->completion_store), + filter); - default: - g_assert_not_reached (); + g_free (pattern); + g_object_unref (filter); } -} - -static gboolean -start_autocompletion_idle_handler (gpointer data) -{ - GtkFileChooserEntry *chooser_entry = GTK_FILE_CHOOSER_ENTRY (data); - - start_autocompletion (chooser_entry); - chooser_entry->start_autocompletion_idle_id = 0; - - return FALSE; -} - -static void -install_start_autocompletion_idle (GtkFileChooserEntry *chooser_entry) -{ - if (chooser_entry->start_autocompletion_idle_id != 0) - return; - - chooser_entry->start_autocompletion_idle_id = gdk_threads_add_idle (start_autocompletion_idle_handler, chooser_entry); + g_free (text); + g_free (old_file_part); } #ifdef G_OS_WIN32 @@ -1769,7 +748,7 @@ delete_text_callback (GtkFileChooserEntry *chooser_entry, * Return value: the newly created #GtkFileChooserEntry **/ GtkWidget * -_gtk_file_chooser_entry_new (gboolean eat_tabs) +_gtk_file_chooser_entry_new (gboolean eat_tabs) { GtkFileChooserEntry *chooser_entry; @@ -1780,29 +759,6 @@ _gtk_file_chooser_entry_new (gboolean eat_tabs) } /** - * _gtk_file_chooser_entry_set_file_system: - * @chooser_entry: a #GtkFileChooser - * @file_system: an object implementing #GtkFileSystem - * - * Sets the file system for @chooser_entry. - **/ -void -_gtk_file_chooser_entry_set_file_system (GtkFileChooserEntry *chooser_entry, - GtkFileSystem *file_system) -{ - g_return_if_fail (GTK_IS_FILE_CHOOSER_ENTRY (chooser_entry)); - g_return_if_fail (GTK_IS_FILE_SYSTEM (file_system)); - - if (file_system != chooser_entry->file_system) - { - if (chooser_entry->file_system) - g_object_unref (chooser_entry->file_system); - - chooser_entry->file_system = g_object_ref (file_system); - } -} - -/** * _gtk_file_chooser_entry_set_base_folder: * @chooser_entry: a #GtkFileChooserEntry * @file: file for a folder in the chooser entries current file system. @@ -1813,15 +769,23 @@ void _gtk_file_chooser_entry_set_base_folder (GtkFileChooserEntry *chooser_entry, GFile *file) { + g_return_if_fail (GTK_IS_FILE_CHOOSER_ENTRY (chooser_entry)); + g_return_if_fail (file == NULL || G_IS_FILE (file)); + + if (chooser_entry->base_folder == file || + (file != NULL && chooser_entry->base_folder != NULL + && g_file_equal (chooser_entry->base_folder, file))) + return; + + if (file) + g_object_ref (file); + if (chooser_entry->base_folder) g_object_unref (chooser_entry->base_folder); chooser_entry->base_folder = file; - if (chooser_entry->base_folder) - g_object_ref (chooser_entry->base_folder); - - clear_completions (chooser_entry); + refresh_current_folder_and_file_part (chooser_entry); } /** @@ -1835,14 +799,16 @@ _gtk_file_chooser_entry_set_base_folder (GtkFileChooserEntry *chooser_entry, * be different. If the user has entered unparsable text, or text which * the entry cannot handle, this will return %NULL. * - * Return value: the file for the current folder - this value is owned by the - * chooser entry and must not be modified or freed. + * Return value: the file for the current folder - you must g_object_unref() + * the value after use. **/ GFile * _gtk_file_chooser_entry_get_current_folder (GtkFileChooserEntry *chooser_entry) { - commit_completion_and_refresh (chooser_entry); - return chooser_entry->current_folder_file; + g_return_val_if_fail (GTK_IS_FILE_CHOOSER_ENTRY (chooser_entry), NULL); + + return gtk_file_chooser_get_directory_for_text (chooser_entry, + gtk_entry_get_text (GTK_ENTRY (chooser_entry))); } /** @@ -1860,30 +826,20 @@ _gtk_file_chooser_entry_get_current_folder (GtkFileChooserEntry *chooser_entry) const gchar * _gtk_file_chooser_entry_get_file_part (GtkFileChooserEntry *chooser_entry) { - commit_completion_and_refresh (chooser_entry); - return chooser_entry->file_part; -} + const char *last_slash, *text; -/** - * _gtk_file_chooser_entry_set_file_part: - * @chooser_entry: a #GtkFileChooserEntry - * @file_part: text to display in the entry, in UTF-8 - * - * Sets the current text shown in the file chooser entry. - **/ -void -_gtk_file_chooser_entry_set_file_part (GtkFileChooserEntry *chooser_entry, - const gchar *file_part) -{ - g_return_if_fail (GTK_IS_FILE_CHOOSER_ENTRY (chooser_entry)); + g_return_val_if_fail (GTK_IS_FILE_CHOOSER_ENTRY (chooser_entry), NULL); - chooser_entry->in_change = TRUE; - clear_completions (chooser_entry); - gtk_entry_set_text (GTK_ENTRY (chooser_entry), file_part); - chooser_entry->in_change = FALSE; + text = gtk_entry_get_text (GTK_ENTRY (chooser_entry)); + last_slash = strrchr (text, G_DIR_SEPARATOR); + if (last_slash) + return last_slash + 1; + else if (is_directory_shortcut (text)) + return ""; + else + return text; } - /** * _gtk_file_chooser_entry_set_action: * @chooser_entry: a #GtkFileChooserEntry @@ -1920,6 +876,13 @@ _gtk_file_chooser_entry_set_action (GtkFileChooserEntry *chooser_entry, gtk_entry_completion_set_popup_single_match (comp, TRUE); break; } + + if (chooser_entry->completion_store) + _gtk_file_system_model_set_show_files (GTK_FILE_SYSTEM_MODEL (chooser_entry->completion_store), + action == GTK_FILE_CHOOSER_ACTION_OPEN || + action == GTK_FILE_CHOOSER_ACTION_SAVE); + + update_inline_completion (chooser_entry); } } @@ -1945,22 +908,19 @@ gboolean _gtk_file_chooser_entry_get_is_folder (GtkFileChooserEntry *chooser_entry, GFile *file) { - gboolean retval = FALSE; - - if (chooser_entry->current_folder) - { - GFileInfo *file_info; + GtkTreeIter iter; + GFileInfo *info; - file_info = _gtk_folder_get_info (chooser_entry->current_folder, file); + if (chooser_entry->completion_store == NULL || + !_gtk_file_system_model_get_iter_for_file (GTK_FILE_SYSTEM_MODEL (chooser_entry->completion_store), + &iter, + file)) + return FALSE; - if (file_info) - { - retval = _gtk_file_info_consider_as_directory (file_info); - g_object_unref (file_info); - } - } + info = _gtk_file_system_model_get_info (GTK_FILE_SYSTEM_MODEL (chooser_entry->completion_store), + &iter); - return retval; + return _gtk_file_info_consider_as_directory (info); } @@ -1993,7 +953,7 @@ _gtk_file_chooser_entry_set_local_only (GtkFileChooserEntry *chooser_entry, gboolean local_only) { chooser_entry->local_only = local_only; - clear_completions (chooser_entry); + refresh_current_folder_and_file_part (chooser_entry); } gboolean diff --git a/gtk/gtkfilechooserentry.h b/gtk/gtkfilechooserentry.h index a9c9f83..216b8ae 100644 --- a/gtk/gtkfilechooserentry.h +++ b/gtk/gtkfilechooserentry.h @@ -33,16 +33,12 @@ G_BEGIN_DECLS typedef struct _GtkFileChooserEntry GtkFileChooserEntry; GType _gtk_file_chooser_entry_get_type (void) G_GNUC_CONST; -GtkWidget * _gtk_file_chooser_entry_new (gboolean eat_tab); +GtkWidget * _gtk_file_chooser_entry_new (gboolean eat_tab); void _gtk_file_chooser_entry_set_action (GtkFileChooserEntry *chooser_entry, GtkFileChooserAction action); GtkFileChooserAction _gtk_file_chooser_entry_get_action (GtkFileChooserEntry *chooser_entry); -void _gtk_file_chooser_entry_set_file_system (GtkFileChooserEntry *chooser_entry, - GtkFileSystem *file_system); void _gtk_file_chooser_entry_set_base_folder (GtkFileChooserEntry *chooser_entry, GFile *folder); -void _gtk_file_chooser_entry_set_file_part (GtkFileChooserEntry *chooser_entry, - const gchar *file_part); GFile * _gtk_file_chooser_entry_get_current_folder (GtkFileChooserEntry *chooser_entry); const gchar * _gtk_file_chooser_entry_get_file_part (GtkFileChooserEntry *chooser_entry); gboolean _gtk_file_chooser_entry_get_is_folder (GtkFileChooserEntry *chooser_entry, diff --git a/gtk/gtkfilesystem.c b/gtk/gtkfilesystem.c index d0ec4b4..5f9e054 100644 --- a/gtk/gtkfilesystem.c +++ b/gtk/gtkfilesystem.c @@ -680,146 +680,6 @@ _gtk_file_system_list_bookmarks (GtkFileSystem *file_system) return g_slist_reverse (files); } -static gboolean -is_valid_scheme_character (char c) -{ - return g_ascii_isalnum (c) || c == '+' || c == '-' || c == '.'; -} - -static gboolean -has_uri_scheme (const char *str) -{ - const char *p; - - p = str; - - if (!is_valid_scheme_character (*p)) - return FALSE; - - do - p++; - while (is_valid_scheme_character (*p)); - - return (strncmp (p, "://", 3) == 0); -} - -gboolean -_gtk_file_system_parse (GtkFileSystem *file_system, - GFile *base_file, - const gchar *str, - GFile **folder, - gchar **file_part, - GError **error) -{ - GFile *file; - gboolean result = FALSE; - gboolean is_dir = FALSE; - gchar *last_slash = NULL; - gboolean is_uri; - - DEBUG ("parse"); - - if (str && *str) - is_dir = (str [strlen (str) - 1] == G_DIR_SEPARATOR); - - last_slash = strrchr (str, G_DIR_SEPARATOR); - - is_uri = has_uri_scheme (str); - - if (is_uri) - { - const char *colon; - const char *slash_after_hostname; - - colon = strchr (str, ':'); - g_assert (colon != NULL); - g_assert (strncmp (colon, "://", 3) == 0); - - slash_after_hostname = strchr (colon + 3, '/'); - - if (slash_after_hostname == NULL) - { - /* We don't have a full hostname yet. So, don't switch the folder - * until we have seen a full hostname. Otherwise, completion will - * happen for every character the user types for the hostname. - */ - - *folder = NULL; - *file_part = NULL; - g_set_error (error, - GTK_FILE_CHOOSER_ERROR, - GTK_FILE_CHOOSER_ERROR_INCOMPLETE_HOSTNAME, - "Incomplete hostname"); - return FALSE; - } - } - - if (str[0] == '~' || g_path_is_absolute (str) || is_uri) - file = g_file_parse_name (str); - else - { - if (base_file) - file = g_file_resolve_relative_path (base_file, str); - else - { - *folder = NULL; - *file_part = NULL; - g_set_error (error, - GTK_FILE_CHOOSER_ERROR, - GTK_FILE_CHOOSER_ERROR_BAD_FILENAME, - _("Invalid path")); - return FALSE; - } - } - - if (base_file && g_file_equal (base_file, file)) - { - /* this is when user types '.', could be the - * beginning of a hidden file, ./ or ../ - */ - *folder = g_object_ref (file); - *file_part = g_strdup (str); - result = TRUE; - } - else if (is_dir) - { - /* it's a dir, or at least it ends with the dir separator */ - *folder = g_object_ref (file); - *file_part = g_strdup (""); - result = TRUE; - } - else - { - GFile *parent_file; - - parent_file = g_file_get_parent (file); - - if (!parent_file) - { - g_set_error (error, - GTK_FILE_CHOOSER_ERROR, - GTK_FILE_CHOOSER_ERROR_NONEXISTENT, - "Could not get parent file"); - *folder = NULL; - *file_part = NULL; - } - else - { - *folder = parent_file; - result = TRUE; - - if (last_slash) - *file_part = g_strdup (last_slash + 1); - else - *file_part = g_strdup (str); - } - } - - g_object_unref (file); - - return result; -} - static void free_async_data (AsyncFuncData *async_data) { @@ -832,79 +692,6 @@ free_async_data (AsyncFuncData *async_data) } static void -enumerate_children_callback (GObject *source_object, - GAsyncResult *result, - gpointer user_data) -{ - GFileEnumerator *enumerator; - AsyncFuncData *async_data; - GtkFolder *folder = NULL; - GFile *file; - GError *error = NULL; - - file = G_FILE (source_object); - async_data = (AsyncFuncData *) user_data; - enumerator = g_file_enumerate_children_finish (file, result, &error); - - if (enumerator) - { - folder = g_object_new (GTK_TYPE_FOLDER, - "file", source_object, - "enumerator", enumerator, - "attributes", async_data->attributes, - NULL); - g_object_unref (enumerator); - } - - gdk_threads_enter (); - ((GtkFileSystemGetFolderCallback) async_data->callback) (async_data->cancellable, - folder, error, async_data->data); - gdk_threads_leave (); - - free_async_data (async_data); - - if (folder) - g_object_unref (folder); - - if (error) - g_error_free (error); -} - -GCancellable * -_gtk_file_system_get_folder (GtkFileSystem *file_system, - GFile *file, - const gchar *attributes, - GtkFileSystemGetFolderCallback callback, - gpointer data) -{ - GCancellable *cancellable; - AsyncFuncData *async_data; - - g_return_val_if_fail (GTK_IS_FILE_SYSTEM (file_system), NULL); - g_return_val_if_fail (G_IS_FILE (file), NULL); - - cancellable = g_cancellable_new (); - - async_data = g_new0 (AsyncFuncData, 1); - async_data->file_system = g_object_ref (file_system); - async_data->file = g_object_ref (file); - async_data->cancellable = g_object_ref (cancellable); - async_data->attributes = g_strdup (attributes); - - async_data->callback = callback; - async_data->data = data; - - g_file_enumerate_children_async (file, - attributes, - G_FILE_QUERY_INFO_NONE, - G_PRIORITY_DEFAULT, - cancellable, - enumerate_children_callback, - async_data); - return cancellable; -} - -static void query_info_callback (GObject *source_object, GAsyncResult *result, gpointer user_data) diff --git a/gtk/gtkfilesystem.h b/gtk/gtkfilesystem.h index 6f3be36..7fc539e 100644 --- a/gtk/gtkfilesystem.h +++ b/gtk/gtkfilesystem.h @@ -100,18 +100,6 @@ GtkFileSystem * _gtk_file_system_new (void); GSList * _gtk_file_system_list_volumes (GtkFileSystem *file_system); GSList * _gtk_file_system_list_bookmarks (GtkFileSystem *file_system); -gboolean _gtk_file_system_parse (GtkFileSystem *file_system, - GFile *base_file, - const gchar *str, - GFile **folder, - gchar **file_part, - GError **error); - -GCancellable * _gtk_file_system_get_folder (GtkFileSystem *file_system, - GFile *file, - const gchar *attributes, - GtkFileSystemGetFolderCallback callback, - gpointer data); GCancellable * _gtk_file_system_get_info (GtkFileSystem *file_system, GFile *file, const gchar *attributes, diff --git a/gtk/gtkfilesystemmodel.c b/gtk/gtkfilesystemmodel.c index 1ba14d3..840f1e8 100644 --- a/gtk/gtkfilesystemmodel.c +++ b/gtk/gtkfilesystemmodel.c @@ -45,6 +45,9 @@ * the special kind of usage for "search" and "recent-files", where the file chooser gives the model the * files to be displayed. * + * Internal data structure + * ----------------------- + * * Each file is kept in a FileModelNode structure. Each FileModelNode holds a GFile* and other data. All the * node structures have the same size, determined at runtime, depending on the number of columns that were passed * to _gtk_file_system_model_new() or _gtk_file_system_model_new_for_directory() (that is, the size of a node is @@ -69,7 +72,14 @@ * * Each FileModelNode has a node->visible field, which indicates whether the node is visible in the GtkTreeView. * A node may be invisible if, for example, it corresponds to a hidden file and the file chooser is not showing - * hidden files. + * hidden files. Also, a file filter may be explicitly set onto the model, for example, to only show files that + * match "*.jpg". In this case, node->filtered_out says whether the node failed the filter. The ultimate + * decision on whether a node is visible or not in the treeview is distilled into the node->visible field. + * The reason for having a separate node->filtered_out field is so that the file chooser can query whether + * a (filtered-out) folder should be made sensitive in the GUI. + * + * Visible rows vs. possibly-invisible nodes + * ----------------------------------------- * * Since not all nodes in the model->files array may be visible, we need a way to map visible row indexes from * the treeview to array indexes in our array of files. And thus we introduce a bit of terminology: @@ -98,6 +108,16 @@ * * You never access a node->row directly. Instead, call node_get_tree_row(). That function will validate the nodes * up to the sought one if the node is not valid yet, and it will return a proper 0-based row. + * + * Sorting + * ------- + * + * The model implements the GtkTreeSortable interface. To avoid re-sorting + * every time a node gets added (which would lead to O(n^2) performance during + * the initial population of the model), the model can freeze itself (with + * freeze_updates()) during the intial population process. When the model is + * frozen, sorting will not happen. The model will sort itself when the freeze + * count goes back to zero, via corresponding calls to thaw_updates(). */ /*** DEFINES ***/ @@ -123,6 +143,7 @@ struct _FileModelNode */ guint visible :1; /* if the file is currently visible */ + guint filtered_out :1;/* if the file is currently filtered out (i.e. it didn't pass the filters) */ guint frozen_add :1; /* true if the model was frozen and the entry has not been added yet */ GValue values[1]; /* actually n_columns values */ @@ -170,6 +191,7 @@ struct _GtkFileSystemModel guint show_hidden :1; /* whether to show hidden files */ guint show_folders :1;/* whether to show folders */ guint show_files :1; /* whether to show files */ + guint filter_folders :1;/* whether filter applies to folders */ }; #define GTK_FILE_SYSTEM_MODEL_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GTK_TYPE_FILE_SYSTEM_MODEL, GtkFileSystemModelClass)) @@ -185,6 +207,12 @@ struct _GtkFileSystemModelClass void (*finished_loading) (GtkFileSystemModel *model, GError *error); }; +static void freeze_updates (GtkFileSystemModel *model); +static void thaw_updates (GtkFileSystemModel *model); + +static guint node_get_for_file (GtkFileSystemModel *model, + GFile *file); + static void add_file (GtkFileSystemModel *model, GFile *file, GFileInfo *info); @@ -264,13 +292,13 @@ node_invalidate_index (GtkFileSystemModel *model, guint id) } static GtkTreePath * -gtk_tree_path_new_from_node (GtkFileSystemModel *model, guint id) +tree_path_new_from_node (GtkFileSystemModel *model, guint id) { - guint i = node_get_tree_row (model, id); + guint r = node_get_tree_row (model, id); - g_assert (i < model->files->len); + g_assert (r < model->files->len); - return gtk_tree_path_new_from_indices (i, -1); + return gtk_tree_path_new_from_indices (r, -1); } static void @@ -279,7 +307,7 @@ emit_row_inserted_for_node (GtkFileSystemModel *model, guint id) GtkTreePath *path; GtkTreeIter iter; - path = gtk_tree_path_new_from_node (model, id); + path = tree_path_new_from_node (model, id); ITER_INIT_FROM_INDEX (model, &iter, id); gtk_tree_model_row_inserted (GTK_TREE_MODEL (model), path, &iter); gtk_tree_path_free (path); @@ -291,7 +319,7 @@ emit_row_changed_for_node (GtkFileSystemModel *model, guint id) GtkTreePath *path; GtkTreeIter iter; - path = gtk_tree_path_new_from_node (model, id); + path = tree_path_new_from_node (model, id); ITER_INIT_FROM_INDEX (model, &iter, id); gtk_tree_model_row_changed (GTK_TREE_MODEL (model), path, &iter); gtk_tree_path_free (path); @@ -308,10 +336,21 @@ emit_row_deleted_for_row (GtkFileSystemModel *model, guint row) } static void -node_set_visible (GtkFileSystemModel *model, guint id, gboolean visible) +node_set_visible_and_filtered_out (GtkFileSystemModel *model, guint id, gboolean visible, gboolean filtered_out) { FileModelNode *node = get_node (model, id); + /* Filteredness */ + + if (node->filtered_out != filtered_out) + { + node->filtered_out = filtered_out; + if (node->visible && visible) + emit_row_changed_for_node (model, id); + } + + /* Visibility */ + if (node->visible == visible || node->frozen_add) return; @@ -336,35 +375,21 @@ node_set_visible (GtkFileSystemModel *model, guint id, gboolean visible) } static gboolean -node_should_be_visible (GtkFileSystemModel *model, guint id) +node_should_be_filtered_out (GtkFileSystemModel *model, guint id) { FileModelNode *node = get_node (model, id); GtkFileFilterInfo filter_info = { 0, }; GtkFileFilterFlags required; - gboolean is_folder, result; + gboolean result; char *mime_type = NULL; char *filename = NULL; char *uri = NULL; if (node->info == NULL) - return FALSE; - - if (!model->show_hidden && - (g_file_info_get_is_hidden (node->info) || g_file_info_get_is_backup (node->info))) - return FALSE; - - is_folder = _gtk_file_info_consider_as_directory (node->info); - - /* wtf? */ - if (model->show_folders != model->show_files && - model->show_folders != is_folder) - return FALSE; - - if (is_folder) return TRUE; if (model->filter == NULL) - return TRUE; + return FALSE; /* fill info */ required = gtk_file_filter_get_needed (model->filter); @@ -406,7 +431,7 @@ node_should_be_visible (GtkFileSystemModel *model, guint id) } } - result = gtk_file_filter_filter (model->filter, &filter_info); + result = !gtk_file_filter_filter (model->filter, &filter_info); g_free (mime_type); g_free (filename); @@ -415,6 +440,50 @@ node_should_be_visible (GtkFileSystemModel *model, guint id) return result; } +static gboolean +node_should_be_visible (GtkFileSystemModel *model, guint id, gboolean filtered_out) +{ + FileModelNode *node = get_node (model, id); + gboolean result; + + if (node->info == NULL) + return FALSE; + + if (!model->show_hidden && + (g_file_info_get_is_hidden (node->info) || g_file_info_get_is_backup (node->info))) + return FALSE; + + if (_gtk_file_info_consider_as_directory (node->info)) + { + if (!model->show_folders) + return FALSE; + + if (!model->filter_folders) + return TRUE; + } + else + { + if (!model->show_files) + return FALSE; + } + + result = !filtered_out; + + return result; +} + +static void +node_compute_visibility_and_filters (GtkFileSystemModel *model, guint id) +{ + gboolean filtered_out; + gboolean visible; + + filtered_out = node_should_be_filtered_out (model, id); + visible = node_should_be_visible (model, id, filtered_out); + + node_set_visible_and_filtered_out (model, id, visible, filtered_out); +} + /*** GtkTreeModel ***/ static GtkTreeModelFlags @@ -513,6 +582,9 @@ gtk_file_system_model_get_iter (GtkTreeModel *tree_model, { g_return_val_if_fail (gtk_tree_path_get_depth (path) > 0, FALSE); + if (gtk_tree_path_get_depth (path) > 1) + return FALSE; + return gtk_file_system_model_iter_nth_child (tree_model, iter, NULL, @@ -527,7 +599,7 @@ gtk_file_system_model_get_path (GtkTreeModel *tree_model, g_return_val_if_fail (ITER_IS_VALID (model, iter), NULL); - return gtk_tree_path_new_from_node (model, ITER_INDEX (iter)); + return tree_path_new_from_node (model, ITER_INDEX (iter)); } static void @@ -1033,6 +1105,7 @@ _gtk_file_system_model_init (GtkFileSystemModel *model) model->show_files = TRUE; model->show_folders = TRUE; model->show_hidden = FALSE; + model->filter_folders = FALSE; model->sort_column_id = GTK_TREE_SORTABLE_UNSORTED_SORT_COLUMN_ID; @@ -1053,7 +1126,7 @@ thaw_func (gpointer data) { GtkFileSystemModel *model = data; - _gtk_file_system_model_thaw_updates (model); + thaw_updates (model); model->dir_thaw_source = 0; return FALSE; @@ -1075,7 +1148,7 @@ gtk_file_system_model_got_files (GObject *object, GAsyncResult *res, gpointer da { if (model->dir_thaw_source == 0) { - _gtk_file_system_model_freeze_updates (model); + freeze_updates (model); model->dir_thaw_source = gdk_threads_add_timeout_full (IO_PRIORITY + 1, 50, thaw_func, @@ -1124,7 +1197,7 @@ gtk_file_system_model_got_files (GObject *object, GAsyncResult *res, gpointer da { g_source_remove (model->dir_thaw_source); model->dir_thaw_source = 0; - _gtk_file_system_model_thaw_updates (model); + thaw_updates (model); } g_signal_emit (model, file_system_model_signals[FINISHED_LOADING], 0, error); @@ -1145,13 +1218,21 @@ gtk_file_system_model_query_done (GObject * object, GtkFileSystemModel *model = data; /* only a valid pointer if not cancelled */ GFile *file = G_FILE (object); GFileInfo *info; + guint id; info = g_file_query_info_finish (file, res, NULL); if (info == NULL) return; gdk_threads_enter (); - _gtk_file_system_model_update_file (model, file, info, TRUE); + + _gtk_file_system_model_update_file (model, file, info); + + id = node_get_for_file (model, file); + gtk_file_system_model_sort_node (model, id); + + g_object_unref (info); + gdk_threads_leave (); } @@ -1389,16 +1470,14 @@ gtk_file_system_model_refilter_all (GtkFileSystemModel *model) return; } - _gtk_file_system_model_freeze_updates (model); + freeze_updates (model); /* start at index 1, don't change the editable */ for (i = 1; i < model->files->len; i++) - { - node_set_visible (model, i, node_should_be_visible (model, i)); - } + node_compute_visibility_and_filters (model, i); model->filter_on_thaw = FALSE; - _gtk_file_system_model_thaw_updates (model); + thaw_updates (model); } /** @@ -1472,6 +1551,30 @@ _gtk_file_system_model_set_show_files (GtkFileSystemModel *model, } /** + * _gtk_file_system_model_set_filter_folders: + * @model: a #GtkFileSystemModel + * @filter_folders: whether the filter applies to folders + * + * Sets whether the filter set by _gtk_file_system_model_set_filter() + * applies to folders. By default, it does not and folders are always + * visible. + **/ +void +_gtk_file_system_model_set_filter_folders (GtkFileSystemModel *model, + gboolean filter_folders) +{ + g_return_if_fail (GTK_IS_FILE_SYSTEM_MODEL (model)); + + filter_folders = filter_folders != FALSE; + + if (filter_folders != model->filter_folders) + { + model->filter_folders = filter_folders; + gtk_file_system_model_refilter_all (model); + } +} + +/** * _gtk_file_system_model_get_cancellable: * @model: the model * @@ -1498,7 +1601,7 @@ _gtk_file_system_model_get_cancellable (GtkFileSystemModel *model) * Checks if the iterator is visible. A visible iterator references * a row that is currently exposed using the #GtkTreeModel API. If * the iterator is invisible, it references a file that is not shown - * for some reason, such as being filtered by the current filter or + * for some reason, such as being filtered out by the current filter or * being a hidden file. * * Returns: %TRUE if the iterator is visible @@ -1517,6 +1620,32 @@ _gtk_file_system_model_iter_is_visible (GtkFileSystemModel *model, } /** + * _gtk_file_system_model_iter_is_filtered_out: + * @model: the model + * @iter: a valid iterator + * + * Checks if the iterator is filtered out. This is only useful for rows + * that refer to folders, as those are always visible regardless + * of what the current filter says. This function lets you see + * the results of the filter. + * + * Returns: %TRUE if the iterator passed the current filter; %FALSE if the + * filter would not have let the row pass. + **/ +gboolean +_gtk_file_system_model_iter_is_filtered_out (GtkFileSystemModel *model, + GtkTreeIter *iter) +{ + FileModelNode *node; + + g_return_val_if_fail (GTK_IS_FILE_SYSTEM_MODEL (model), FALSE); + g_return_val_if_fail (iter != NULL, FALSE); + + node = get_node (model, ITER_INDEX (iter)); + return node->filtered_out; +} + +/** * _gtk_file_system_model_get_info: * @model: a #GtkFileSystemModel * @iter: a #GtkTreeIter pointing to a row of @model @@ -1683,6 +1812,33 @@ _gtk_file_system_model_get_iter_for_file (GtkFileSystemModel *model, return TRUE; } +/* When an element is added or removed to the model->files array, we need to + * update the model->file_lookup mappings of (node, index), as the indexes + * change. This function adds the specified increment to the index in that pair + * if the index is equal or after the specified id. We use this to slide the + * mappings up or down when a node is added or removed, respectively. + */ +static void +adjust_file_lookup (GtkFileSystemModel *model, guint id, int increment) +{ + GHashTableIter iter; + gpointer key; + gpointer value; + + g_hash_table_iter_init (&iter, model->file_lookup); + + while (g_hash_table_iter_next (&iter, &key, &value)) + { + guint index = GPOINTER_TO_UINT (value); + + if (index >= id) + { + index += increment; + g_hash_table_iter_replace (&iter, GUINT_TO_POINTER (index)); + } + } +} + /** * add_file: * @model: the model @@ -1713,8 +1869,8 @@ add_file (GtkFileSystemModel *model, g_slice_free1 (model->node_size, node); if (!model->frozen) - node_set_visible (model, model->files->len -1, - node_should_be_visible (model, model->files->len - 1)); + node_compute_visibility_and_filters (model, model->files->len -1); + gtk_file_system_model_sort_node (model, model->files->len -1); } @@ -1732,7 +1888,9 @@ remove_file (GtkFileSystemModel *model, GFile *file) { FileModelNode *node; + gboolean was_visible; guint id; + guint row; g_return_if_fail (GTK_IS_FILE_SYSTEM_MODEL (model)); g_return_if_fail (G_IS_FILE (file)); @@ -1742,17 +1900,24 @@ remove_file (GtkFileSystemModel *model, return; node = get_node (model, id); - node_set_visible (model, id, FALSE); + was_visible = node->visible; + row = node_get_tree_row (model, id); + + node_invalidate_index (model, id); g_hash_table_remove (model->file_lookup, file); g_object_unref (node->file); + adjust_file_lookup (model, id, -1); if (node->info) g_object_unref (node->info); g_array_remove_index (model->files, id); - g_hash_table_remove_all (model->file_lookup); - /* We don't need to resort, as removing a row doesn't change the sorting order */ + + /* We don't need to resort, as removing a row doesn't change the sorting order of the other rows */ + + if (was_visible) + emit_row_deleted_for_row (model, row); } /** @@ -1760,7 +1925,6 @@ remove_file (GtkFileSystemModel *model, * @model: the model * @file: the file * @info: the new file info - * @requires_resort: FIXME: get rid of this argument * * Tells the file system model that the file changed and that the * new @info should be used for it now. If the file is not part of @@ -1769,8 +1933,7 @@ remove_file (GtkFileSystemModel *model, void _gtk_file_system_model_update_file (GtkFileSystemModel *model, GFile *file, - GFileInfo *info, - gboolean requires_resort) + GFileInfo *info) { FileModelNode *node; guint i, id; @@ -1802,9 +1965,6 @@ _gtk_file_system_model_update_file (GtkFileSystemModel *model, if (node->visible) emit_row_changed_for_node (model, id); - - if (requires_resort) - gtk_file_system_model_sort_node (model, id); } /** @@ -1813,7 +1973,8 @@ _gtk_file_system_model_update_file (GtkFileSystemModel *model, * @filter: (allow-none): %NULL or filter to use * * Sets a filter to be used for deciding if a row should be visible or not. - * Directories are always visible. + * Whether this filter applies to directories can be toggled with + * _gtk_file_system_model_set_filter_folders(). **/ void _gtk_file_system_model_set_filter (GtkFileSystemModel *model, @@ -1837,54 +1998,16 @@ _gtk_file_system_model_set_filter (GtkFileSystemModel *model, } /** - * _gtk_file_system_model_add_editable: - * @model: a #GtkFileSystemModel - * @iter: Location to return the iter corresponding to the editable row - * - * Adds an "empty" row at the beginning of the model. This does not refer to - * any file, but is a temporary placeholder for a file name that the user will - * type when a corresponding cell is made editable. When your code is done - * using this temporary row, call _gtk_file_system_model_remove_editable(). - **/ -void -_gtk_file_system_model_add_editable (GtkFileSystemModel *model, GtkTreeIter *iter) -{ - g_return_if_fail (GTK_IS_FILE_SYSTEM_MODEL (model)); - g_return_if_fail (!get_node (model, 0)->visible); - - node_set_visible (model, 0, TRUE); - ITER_INIT_FROM_INDEX (model, iter, 0); -} - -/** - * _gtk_file_system_model_remove_editable: - * @model: a #GtkFileSystemModel - * - * Removes the "empty" row at the beginning of the model that was - * created with _gtk_file_system_model_add_editable(). You should call - * this function when your code is finished editing this temporary row. - **/ -void -_gtk_file_system_model_remove_editable (GtkFileSystemModel *model) -{ - g_return_if_fail (GTK_IS_FILE_SYSTEM_MODEL (model)); - g_return_if_fail (get_node (model, 0)->visible); - - node_set_visible (model, 0, FALSE); -} - -/** - * _gtk_file_system_model_freeze_updates: + * freeze_updates: * @model: a #GtkFileSystemModel * - * Freezes most updates on the model, so that performing multiple - * operations on the files in the model do not cause any events. - * Use _gtk_file_system_model_thaw_updates() to resume proper - * operations. It is fine to call this function multiple times as - * long as freeze and thaw calls are balanced. + * Freezes most updates on the model, so that performing multiple operations on + * the files in the model do not cause any events. Use thaw_updates() to resume + * proper operations. It is fine to call this function multiple times as long as + * freeze and thaw calls are balanced. **/ -void -_gtk_file_system_model_freeze_updates (GtkFileSystemModel *model) +static void +freeze_updates (GtkFileSystemModel *model) { g_return_if_fail (GTK_IS_FILE_SYSTEM_MODEL (model)); @@ -1892,14 +2015,13 @@ _gtk_file_system_model_freeze_updates (GtkFileSystemModel *model) } /** - * _gtk_file_system_model_thaw_updates: + * thaw_updates: * @model: a #GtkFileSystemModel * - * Undoes the effect of a previous call to - * _gtk_file_system_model_freeze_updates() + * Undoes the effect of a previous call to freeze_updates() **/ -void -_gtk_file_system_model_thaw_updates (GtkFileSystemModel *model) +static void +thaw_updates (GtkFileSystemModel *model) { gboolean stuff_added; @@ -1927,7 +2049,7 @@ _gtk_file_system_model_thaw_updates (GtkFileSystemModel *model) if (!node->frozen_add) continue; node->frozen_add = FALSE; - node_set_visible (model, i, node_should_be_visible (model, i)); + node_compute_visibility_and_filters (model, i); } } } @@ -2012,3 +2134,47 @@ _gtk_file_system_model_add_and_query_file (GtkFileSystemModel *model, gtk_file_system_model_query_done, model); } + +/** + * _gtk_file_system_model_add_editable: + * @model: a #GtkFileSystemModel + * @iter: Location to return the iter corresponding to the editable row + * + * Adds an “empty” row at the beginning of the model. This does not refer to + * any file, but is a temporary placeholder for a file name that the user will + * type when a corresponding cell is made editable. When your code is done + * using this temporary row, call _gtk_file_system_model_remove_editable(). + **/ +void +_gtk_file_system_model_add_editable (GtkFileSystemModel *model, GtkTreeIter *iter) +{ + g_return_if_fail (GTK_IS_FILE_SYSTEM_MODEL (model)); + g_return_if_fail (!get_node (model, 0)->visible); + + node_set_visible_and_filtered_out (model, 0, TRUE, FALSE); + ITER_INIT_FROM_INDEX (model, iter, 0); + + /* we don't want file system changes to affect the model while + * editing is in place + */ + freeze_updates (model); +} + +/** + * _gtk_file_system_model_remove_editable: + * @model: a #GtkFileSystemModel + * + * Removes the “empty” row at the beginning of the model that was + * created with _gtk_file_system_model_add_editable(). You should call + * this function when your code is finished editing this temporary row. + **/ +void +_gtk_file_system_model_remove_editable (GtkFileSystemModel *model) +{ + g_return_if_fail (GTK_IS_FILE_SYSTEM_MODEL (model)); + g_return_if_fail (get_node (model, 0)->visible); + + thaw_updates (model); + + node_set_visible_and_filtered_out (model, 0, FALSE, FALSE); +} diff --git a/gtk/gtkfilesystemmodel.h b/gtk/gtkfilesystemmodel.h index a6fbab9..46743f1 100644 --- a/gtk/gtkfilesystemmodel.h +++ b/gtk/gtkfilesystemmodel.h @@ -55,6 +55,8 @@ GtkFileSystemModel *_gtk_file_system_model_new_for_directory(GFile * GCancellable * _gtk_file_system_model_get_cancellable (GtkFileSystemModel *model); gboolean _gtk_file_system_model_iter_is_visible (GtkFileSystemModel *model, GtkTreeIter *iter); +gboolean _gtk_file_system_model_iter_is_filtered_out (GtkFileSystemModel *model, + GtkTreeIter *iter); GFileInfo * _gtk_file_system_model_get_info (GtkFileSystemModel *model, GtkTreeIter *iter); gboolean _gtk_file_system_model_get_iter_for_file(GtkFileSystemModel *model, @@ -71,8 +73,7 @@ void _gtk_file_system_model_add_and_query_file (GtkFileSystemMode const char *attributes); void _gtk_file_system_model_update_file (GtkFileSystemModel *model, GFile *file, - GFileInfo *info, - gboolean requires_resort); + GFileInfo *info); void _gtk_file_system_model_set_show_hidden (GtkFileSystemModel *model, gboolean show_hidden); @@ -80,8 +81,8 @@ void _gtk_file_system_model_set_show_folders (GtkFileSystemModel gboolean show_folders); void _gtk_file_system_model_set_show_files (GtkFileSystemModel *model, gboolean show_files); -void _gtk_file_system_model_freeze_updates (GtkFileSystemModel *model); -void _gtk_file_system_model_thaw_updates (GtkFileSystemModel *model); +void _gtk_file_system_model_set_filter_folders (GtkFileSystemModel *model, + gboolean show_folders); void _gtk_file_system_model_clear_cache (GtkFileSystemModel *model, int column);
Locations
Projects
Search
Status Monitor
Help
OpenBuildService.org
Documentation
API Documentation
Code of Conduct
Contact
Support
@OBShq
Terms
openSUSE Build Service is sponsored by
The Open Build Service is an
openSUSE project
.
Sign Up
Log In
Places
Places
All Projects
Status Monitor