-/* 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;
-
- 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);
-
- 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 common_prefix_len;
- gint pos;
-
- cursor_pos = gtk_editable_get_position (GTK_EDITABLE (chooser_entry));
- common_prefix_len = g_utf8_strlen (common_prefix, -1);
-
- 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)
- {
- gtk_editable_select_region (GTK_EDITABLE (chooser_entry),
- cursor_pos,
- pos); /* equivalent to cursor_pos + common_prefix_len); */
- 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);
-}
-