X-Git-Url: http://pileus.org/git/?a=blobdiff_plain;f=gtk%2Fgtkfilechooserentry.c;h=98ecd24c71d88301a7ae92e3d0f9d9a0b20b7698;hb=b2340254109d8599244c875fe7bc5e234f64da08;hp=94e01d5f8e4502be2ee4b9267953baca7cab7f12;hpb=2186c282634e9ebe6791c5e3dd2e88049b427502;p=~andy%2Fgtk diff --git a/gtk/gtkfilechooserentry.c b/gtk/gtkfilechooserentry.c index 94e01d5f8..98ecd24c7 100644 --- a/gtk/gtkfilechooserentry.c +++ b/gtk/gtkfilechooserentry.c @@ -13,9 +13,7 @@ * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the - * Free Software Foundation, Inc., 59 Temple Place - Suite 330, - * Boston, MA 02111-1307, USA. + * License along with this library. If not, see . */ #include "config.h" @@ -45,22 +43,6 @@ 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; @@ -69,20 +51,13 @@ struct _GtkFileChooserEntry GFile *base_folder; GFile *current_folder_file; + gchar *dir_part; gchar *file_part; - gint file_part_pos; - - LoadCompleteAction load_complete_action; GtkTreeModel *completion_store; - GtkWidget *completion_feedback_window; - GtkWidget *completion_feedback_label; - guint completion_feedback_timeout_id; - guint current_folder_loaded : 1; - guint has_completion : 1; - guint in_change : 1; + guint complete_on_load : 1; guint eat_tabs : 1; guint local_only : 1; }; @@ -90,35 +65,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 (GtkEditableInterface *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, @@ -136,60 +93,66 @@ 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); -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 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); static void finished_loading_cb (GtkFileSystemModel *model, GError *error, GtkFileChooserEntry *chooser_entry); -static void autocomplete (GtkFileChooserEntry *chooser_entry); -static void start_autocompletion (GtkFileChooserEntry *chooser_entry); -static void remove_completion_feedback (GtkFileChooserEntry *chooser_entry); -static void pop_up_completion_feedback (GtkFileChooserEntry *chooser_entry, - const gchar *feedback); -static GtkEditableInterface *parent_editable_iface; +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; -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)) + 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); + + /* 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 (GtkEditableInterface *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 @@ -199,15 +162,19 @@ _gtk_file_chooser_entry_init (GtkFileChooserEntry *chooser_entry) GtkCellRenderer *cell; chooser_entry->local_only = TRUE; - chooser_entry->base_folder = g_file_new_for_path (g_get_home_dir ()); g_object_set (chooser_entry, "truncate-multiline", TRUE, NULL); 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); @@ -223,6 +190,10 @@ _gtk_file_chooser_entry_init (GtkFileChooserEntry *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", @@ -237,39 +208,24 @@ gtk_file_chooser_entry_finalize (GObject *object) { GtkFileChooserEntry *chooser_entry = GTK_FILE_CHOOSER_ENTRY (object); - g_object_unref (chooser_entry->base_folder); + if (chooser_entry->base_folder) + g_object_unref (chooser_entry->base_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_loading_and_current_folder_file (GtkFileChooserEntry *chooser_entry) -{ - if (chooser_entry->current_folder_file) - { - g_object_unref (chooser_entry->current_folder_file); - chooser_entry->current_folder_file = NULL; - } -} - static void gtk_file_chooser_entry_dispose (GObject *object) { GtkFileChooserEntry *chooser_entry = GTK_FILE_CHOOSER_ENTRY (object); - remove_completion_feedback (chooser_entry); - discard_loading_and_current_folder_file (chooser_entry); - - if (chooser_entry->completion_store) - { - g_object_unref (chooser_entry->completion_store); - chooser_entry->completion_store = NULL; - } + set_completion_folder (chooser_entry, NULL); G_OBJECT_CLASS (_gtk_file_chooser_entry_parent_class)->dispose (object); } @@ -277,130 +233,41 @@ 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; - - gtk_tree_model_get (model, iter, - DISPLAY_NAME_COLUMN, &display_name, - FILE_COLUMN, &file, - -1); - - if (!display_name || !file) - { - if (file) - g_object_unref (file); - - g_free (display_name); - return FALSE; - } - 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); + path, + -1, + &pos); - g_object_unref (file); - g_free (display_name); + g_free (path); return TRUE; } -/* 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 = NULL; - gboolean result; - char *norm_file_part; - char *norm_name; - - 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 (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) +set_complete_on_load (GtkFileChooserEntry *chooser_entry, + gboolean complete_on_load) { - chooser_entry->has_completion = FALSE; - chooser_entry->load_complete_action = LOAD_COMPLETE_NOTHING; - - remove_completion_feedback (chooser_entry); -} + /* a completion was triggered, but we couldn't do it. + * So no text was inserted when pressing tab, so we beep */ + if (chooser_entry->complete_on_load && !complete_on_load) + gtk_widget_error_bell (GTK_WIDGET (chooser_entry)); -static void -beep (GtkFileChooserEntry *chooser_entry) -{ - gtk_widget_error_bell (GTK_WIDGET (chooser_entry)); + chooser_entry->complete_on_load = complete_on_load; } static gboolean @@ -426,388 +293,75 @@ has_uri_scheme (const char *str) return (strncmp (p, "://", 3) == 0); } -static gboolean -gtk_file_chooser_entry_parse (GtkFileChooserEntry *chooser_entry, - const gchar *str, - GFile **folder, - gchar **file_part, - GError **error) +static GFile * +gtk_file_chooser_get_file_for_text (GtkFileChooserEntry *chooser_entry, + const gchar *str) { GFile *file; - gboolean result = FALSE; - gboolean is_dir = FALSE; - gchar *last_slash = NULL; - - if (str && *str) - is_dir = (str [strlen (str) - 1] == G_DIR_SEPARATOR); - - last_slash = strrchr (str, G_DIR_SEPARATOR); if (str[0] == '~' || g_path_is_absolute (str) || has_uri_scheme (str)) file = g_file_parse_name (str); - else + else if (chooser_entry->base_folder != NULL) file = g_file_resolve_relative_path (chooser_entry->base_folder, str); - - if (g_file_equal (chooser_entry->base_folder, 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; + file = NULL; - 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; + return file; } -/* 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) +static GFile * +gtk_file_chooser_get_directory_for_text (GtkFileChooserEntry *chooser_entry, + const char * text) { - 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_chooser_entry_parse (chooser_entry, - 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 - && 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 (chooser_entry->completion_store, &iter); - - while (valid) - { - gchar *display_name; - GFile *file; - - gtk_tree_model_get (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 = g_strdup (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 (chooser_entry->completion_store, &iter); - } - - /* Second pass: see if the prefix we found is a complete match */ + GFile *file, *parent; - if (*common_prefix_ret != NULL) - { - valid = gtk_tree_model_get_iter_first (chooser_entry->completion_store, &iter); - - while (valid) - { - gchar *display_name; - int len; - - gtk_tree_model_get (chooser_entry->completion_store, - &iter, - DISPLAY_NAME_COLUMN, &display_name, - -1); - len = strlen (display_name); - g_assert (len > 0); + file = gtk_file_chooser_get_file_for_text (chooser_entry, text); - if (G_IS_DIR_SEPARATOR (display_name[len - 1])) - len--; + if (file == NULL) + return NULL; - if (*unique_file_ret == NULL && strncmp (*common_prefix_ret, display_name, len) == 0) - *is_complete_not_unique_ret = TRUE; + if (text[0] == 0 || text[strlen (text) - 1] == G_DIR_SEPARATOR) + return file; - g_free (display_name); - valid = gtk_tree_model_iter_next (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); + parent = g_file_get_parent (file); + g_object_unref (file); - return TRUE; + return parent; } -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) +static void +explicitly_complete (GtkFileChooserEntry *chooser_entry) { - 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; + chooser_entry->complete_on_load = FALSE; - error = NULL; - if (!find_common_prefix (chooser_entry, &common_prefix, &unique_file, &is_complete_not_unique, &prefix_expands_the_file_part, &error)) - { - g_error_free (error); - - return INVALID_INPUT; - } - - have_result = FALSE; - - if (unique_file) + if (chooser_entry->completion_store) { - g_object_unref (unique_file); - - if (prefix_expands_the_file_part) - result = COMPLETED_UNIQUE; - else - result = NOTHING_INSERTED_UNIQUE; + char *completion, *text; + gsize completion_len, text_len; + + 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; - have_result = TRUE; - } - else - { - if (is_complete_not_unique) + if (completion_len > text_len) { - 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)); + GtkEditable *editable = GTK_EDITABLE (chooser_entry); + int pos = gtk_editable_get_position (editable); - 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, + gtk_editable_insert_text (editable, + completion + text_len, + completion_len - text_len, &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; + gtk_editable_set_position (editable, pos); + return; } - - 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) - start_autocompletion (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); + gtk_widget_error_bell (GTK_WIDGET (chooser_entry)); } static void @@ -817,401 +371,47 @@ gtk_file_chooser_entry_grab_focus (GtkWidget *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_draw_cb (GtkWidget *widget, - cairo_t *cr, - gpointer data) -{ - /* Stolen from gtk_tooltip_paint_window() */ - - GtkFileChooserEntry *chooser_entry = GTK_FILE_CHOOSER_ENTRY (data); - GtkStyleContext *context; - - context = gtk_widget_get_style_context (chooser_entry->completion_feedback_window); - - gtk_render_background (context, cr, 0, 0, - gtk_widget_get_allocated_width (widget), - gtk_widget_get_allocated_height (widget)); - gtk_render_frame (context, cr, 0, 0, - gtk_widget_get_allocated_width (widget), - gtk_widget_get_allocated_height (widget)); - - return FALSE; -} - -static void -set_invisible_mouse_cursor (GdkWindow *window) -{ - 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); - - g_object_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. - */ - set_invisible_mouse_cursor (gtk_widget_get_window (widget)); -} - -static void -create_completion_feedback_window (GtkFileChooserEntry *chooser_entry) -{ - /* Stolen from gtk_tooltip_init() */ - GtkWidget *window, *label; - GtkStyleContext *context; - - window = gtk_window_new (GTK_WINDOW_POPUP); - gtk_window_set_type_hint (GTK_WINDOW (window), GDK_WINDOW_TYPE_HINT_TOOLTIP); - gtk_widget_set_app_paintable (window, TRUE); - gtk_window_set_resizable (GTK_WINDOW (window), FALSE); - gtk_widget_set_name (window, "gtk-tooltip"); - - context = gtk_widget_get_style_context (window); - gtk_style_context_add_class (context, GTK_STYLE_CLASS_TOOLTIP); - - g_signal_connect (window, "draw", - G_CALLBACK (completion_feedback_window_draw_cb), chooser_entry); - g_signal_connect (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 */ - - label = gtk_label_new (NULL); - gtk_widget_set_halign (label, GTK_ALIGN_CENTER); - gtk_widget_set_valign (label, GTK_ALIGN_CENTER); - /* FIXME: don't hardcode this */ - gtk_widget_set_margin_left (label, 6); - gtk_widget_set_margin_right (label, 6); - gtk_widget_set_margin_top (label, 6); - gtk_widget_set_margin_bottom (label, 6); - gtk_container_add (GTK_CONTAINER (window), label); - gtk_widget_show (label); - - chooser_entry->completion_feedback_window = window; - chooser_entry->completion_feedback_label = label; -} - -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; - GtkWidget *widget = GTK_WIDGET (chooser_entry); - gint entry_x, entry_y; - gint cursor_x; - GtkAllocation entry_allocation; - int feedback_x, feedback_y; - - gtk_widget_get_preferred_size (chooser_entry->completion_feedback_window, - &feedback_req, NULL); - - gdk_window_get_origin (gtk_widget_get_window (widget), &entry_x, &entry_y); - gtk_widget_get_allocation (widget, &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) -{ - 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; - } -} - -static void -explicitly_complete (GtkFileChooserEntry *chooser_entry) -{ - CommonPrefixResult result; - - g_assert (chooser_entry->current_folder_loaded); - - /* 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); - - 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; - - case COMPLETED_UNIQUE: - /* Nothing to do */ - break; - - 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; - - default: - g_assert_not_reached (); - } -} - 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; - - switch (status) - { - case REFRESH_OK: - g_assert (chooser_entry->current_folder_file != NULL); - - if (chooser_entry->current_folder_loaded) - explicitly_complete (chooser_entry); - else - { - chooser_entry->load_complete_action = LOAD_COMPLETE_EXPLICIT_COMPLETION; - - /* 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...")); - } - - break; - - 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; - - case REFRESH_INCOMPLETE_HOSTNAME: - is_error = TRUE; - - if (chooser_entry->local_only) - { - /* hostnames in a local_only file chooser? user error */ - - /* 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 */ - - /* 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 '/'"); - } - - break; - - case REFRESH_NONEXISTENT: - is_error = TRUE; - - /* 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; - - case REFRESH_NOT_LOCAL: - is_error = TRUE; - feedback_msg = _("Only local files may be selected"); - break; - - default: - g_assert_not_reached (); - return; - } - - if (is_error) - { - g_assert (chooser_entry->current_folder_file == NULL); - - beep (chooser_entry); - pop_up_completion_feedback (chooser_entry, feedback_msg); - chooser_entry->load_complete_action = LOAD_COMPLETE_NOTHING; - } + 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_KEY_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 @@ -1220,31 +420,33 @@ 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); -} - -static void -gtk_file_chooser_entry_activate (GtkEntry *entry) -{ - GtkFileChooserEntry *chooser_entry = GTK_FILE_CHOOSER_ENTRY (entry); - - commit_completion_and_refresh (chooser_entry); - GTK_ENTRY_CLASS (_gtk_file_chooser_entry_parent_class)->activate (entry); + 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 @@ -1254,6 +456,7 @@ discard_completion_store (GtkFileChooserEntry *chooser_entry) return; 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; } @@ -1266,16 +469,25 @@ completion_store_set (GtkFileSystemModel *model, GValue *value, gpointer data) { + GtkFileChooserEntry *chooser_entry = data; + + const char *prefix = ""; + const char *suffix = ""; + switch (column) { - case FILE_COLUMN: - g_value_set_object (value, file); - break; + case FULL_PATH_COLUMN: + prefix = chooser_entry->dir_part; + /* fall through */ case DISPLAY_NAME_COLUMN: if (_gtk_file_info_consider_as_directory (info)) - g_value_take_string (value, g_strconcat (g_file_info_get_display_name (info), G_DIR_SEPARATOR_S, NULL)); - else - g_value_set_string (value, g_file_info_get_display_name (info)); + suffix = G_DIR_SEPARATOR_S; + + g_value_take_string (value, g_strconcat ( + prefix, + g_file_info_get_display_name (info), + suffix, + NULL)); break; default: g_assert_not_reached (); @@ -1289,8 +501,6 @@ completion_store_set (GtkFileSystemModel *model, static void populate_completion_store (GtkFileChooserEntry *chooser_entry) { - discard_completion_store (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", @@ -1298,10 +508,15 @@ populate_completion_store (GtkFileChooserEntry *chooser_entry) chooser_entry, N_COLUMNS, G_TYPE_STRING, - G_TYPE_FILE)); + 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); @@ -1309,232 +524,113 @@ populate_completion_store (GtkFileChooserEntry *chooser_entry) chooser_entry->completion_store); } -/* 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) - { - case LOAD_COMPLETE_NOTHING: - break; - - case LOAD_COMPLETE_AUTOCOMPLETE: - autocomplete (chooser_entry); - break; - - case LOAD_COMPLETE_EXPLICIT_COMPLETION: - explicitly_complete (chooser_entry); - break; - - default: - g_assert_not_reached (); - } - - chooser_entry->load_complete_action = LOAD_COMPLETE_NOTHING; -} - -static void -finish_folder_load (GtkFileChooserEntry *chooser_entry) -{ - perform_load_complete_action (chooser_entry); - - gtk_widget_set_tooltip_text (GTK_WIDGET (chooser_entry), NULL); -} - /* Callback when the current folder finishes loading */ static void finished_loading_cb (GtkFileSystemModel *model, GError *error, GtkFileChooserEntry *chooser_entry) { + GtkEntryCompletion *completion; + 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); - } - + set_complete_on_load (chooser_entry, FALSE); return; } - finish_folder_load (chooser_entry); + if (chooser_entry->complete_on_load) + explicitly_complete (chooser_entry); + + gtk_widget_set_tooltip_text (GTK_WIDGET (chooser_entry), NULL); + + completion = gtk_entry_get_completion (GTK_ENTRY (chooser_entry)); + update_inline_completion (chooser_entry); + gtk_entry_completion_complete (completion); + gtk_entry_completion_insert_prefix (completion); } -static RefreshStatus -reload_current_folder (GtkFileChooserEntry *chooser_entry, - GFile *folder_file) +static void +set_completion_folder (GtkFileChooserEntry *chooser_entry, + GFile *folder_file) { - g_assert (folder_file != NULL); + if (folder_file && + chooser_entry->local_only + && !g_file_is_native (folder_file)) + folder_file = NULL; - if (chooser_entry->current_folder_file - && g_file_equal (folder_file, chooser_entry->current_folder_file)) - return REFRESH_OK; + 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) + return; if (chooser_entry->current_folder_file) { - discard_loading_and_current_folder_file (chooser_entry); + g_object_unref (chooser_entry->current_folder_file); + chooser_entry->current_folder_file = NULL; } - if (chooser_entry->local_only - && !g_file_is_native (folder_file)) - return REFRESH_NOT_LOCAL; - - chooser_entry->current_folder_file = g_object_ref (folder_file); chooser_entry->current_folder_loaded = FALSE; - populate_completion_store (chooser_entry); + discard_completion_store (chooser_entry); - return REFRESH_OK; + if (folder_file) + { + chooser_entry->current_folder_file = g_object_ref (folder_file); + populate_completion_store (chooser_entry); + } } -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; - - editable = GTK_EDITABLE (chooser_entry); - - 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_editable_get_chars (editable, 0, end_pos); - - error = NULL; - if (!gtk_file_chooser_entry_parse (chooser_entry, - text, &folder_file, &file_part, &error)) - { - folder_file = g_object_ref (chooser_entry->base_folder); + char *text, *last_slash, *old_file_part; - if (g_error_matches (error, GTK_FILE_CHOOSER_ERROR, GTK_FILE_CHOOSER_ERROR_NONEXISTENT)) - result = REFRESH_NONEXISTENT; - else - result = REFRESH_INVALID_INPUT; + old_file_part = chooser_entry->file_part; + g_free (chooser_entry->dir_part); - if (error) - g_error_free (error); + text = gtk_file_chooser_entry_get_completion_text (chooser_entry); - file_part = g_strdup (""); - file_part_pos = -1; - } - else + last_slash = strrchr (text, G_DIR_SEPARATOR); + if (last_slash) { - 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; - } - - g_free (text); - - g_free (chooser_entry->file_part); - - chooser_entry->file_part = file_part; - chooser_entry->file_part_pos = file_part_pos; - - if (result == REFRESH_OK) - { - result = reload_current_folder (chooser_entry, folder_file); + chooser_entry->dir_part = g_strndup (text, last_slash - text + 1); + chooser_entry->file_part = g_strdup (last_slash + 1); } else { - discard_loading_and_current_folder_file (chooser_entry); + chooser_entry->dir_part = g_strdup (""); + chooser_entry->file_part = g_strdup (text); } + folder_file = gtk_file_chooser_get_directory_for_text (chooser_entry, text); + set_completion_folder (chooser_entry, folder_file); 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)) - /* ... 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))); - - return result; -} - -static void -autocomplete (GtkFileChooserEntry *chooser_entry) -{ - if (!(chooser_entry->current_folder_loaded - && 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); - - 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_loaded) - 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; + _gtk_file_system_model_set_filter (GTK_FILE_SYSTEM_MODEL (chooser_entry->completion_store), + 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; - - default: - g_assert_not_reached (); + g_free (pattern); + g_object_unref (filter); } + + g_free (text); + g_free (old_file_part); } #ifdef G_OS_WIN32 @@ -1634,23 +730,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); - else - file = g_file_new_for_path (g_get_home_dir ()); - - if (g_file_equal (chooser_entry->base_folder, file)) - { - g_object_unref (file); - return; - } if (chooser_entry->base_folder) g_object_unref (chooser_entry->base_folder); chooser_entry->base_folder = file; - clear_completions (chooser_entry); + refresh_current_folder_and_file_part (chooser_entry); } /** @@ -1664,14 +760,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))); } /** @@ -1689,30 +787,18 @@ _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 + return text; } - /** * _gtk_file_chooser_entry_set_action: * @chooser_entry: a #GtkFileChooserEntry @@ -1749,6 +835,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); } } @@ -1819,7 +912,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