X-Git-Url: http://pileus.org/git/?a=blobdiff_plain;f=gtk%2Fgtkfilechooserentry.c;h=c1d96616084b9e4d687176235d5e6241dd0b5daa;hb=a6e744da9d779bb24b069391ba2aaf1e23cbd1c7;hp=6bc9d8a9a37e64c6bec3015fb5a5800379d176c4;hpb=215cabd938150ecfa32d50ac48ac43d00819e596;p=~andy%2Fgtk diff --git a/gtk/gtkfilechooserentry.c b/gtk/gtkfilechooserentry.c index 6bc9d8a9a..c1d966160 100644 --- a/gtk/gtkfilechooserentry.c +++ b/gtk/gtkfilechooserentry.c @@ -26,6 +26,8 @@ #include "gtkentry.h" #include "gtkfilechooserentry.h" #include "gtkmain.h" +#include "gtkintl.h" +#include "gtkalias.h" typedef struct _GtkFileChooserEntryClass GtkFileChooserEntryClass; @@ -42,6 +44,8 @@ struct _GtkFileChooserEntry { GtkEntry parent_instance; + GtkFileChooserAction action; + GtkFileSystem *file_system; GtkFilePath *base_folder; GtkFilePath *current_folder_path; @@ -51,11 +55,13 @@ struct _GtkFileChooserEntry GSource *load_directory_idle; GtkFileFolder *current_folder; + GtkFileSystemHandle *load_folder_handle; GtkListStore *completion_store; guint has_completion : 1; guint in_change : 1; + guint eat_tabs : 1; }; enum @@ -65,11 +71,10 @@ enum N_COLUMNS }; -static void gtk_file_chooser_entry_class_init (GtkFileChooserEntryClass *class); -static void gtk_file_chooser_entry_iface_init (GtkEditableClass *iface); -static void gtk_file_chooser_entry_init (GtkFileChooserEntry *chooser_entry); +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 gboolean gtk_file_chooser_entry_focus (GtkWidget *widget, GtkDirectionType direction); static void gtk_file_chooser_entry_activate (GtkEntry *entry); @@ -99,58 +104,21 @@ static char *maybe_append_separator_to_path (GtkFileChooserEntry *chooser_ent GtkFilePath *path, gchar *display_name); -static GObjectClass *parent_class; static GtkEditableClass *parent_editable_iface; -GType -_gtk_file_chooser_entry_get_type (void) -{ - static GType file_chooser_entry_type = 0; - - if (!file_chooser_entry_type) - { - static const GTypeInfo file_chooser_entry_info = - { - sizeof (GtkFileChooserEntryClass), - NULL, /* base_init */ - NULL, /* base_finalize */ - (GClassInitFunc) gtk_file_chooser_entry_class_init, - NULL, /* class_finalize */ - NULL, /* class_data */ - sizeof (GtkFileChooserEntry), - 0, /* n_preallocs */ - (GInstanceInitFunc) gtk_file_chooser_entry_init, - }; - - static const GInterfaceInfo editable_info = - { - (GInterfaceInitFunc) gtk_file_chooser_entry_iface_init, /* interface_init */ - NULL, /* interface_finalize */ - NULL /* interface_data */ - }; - - - file_chooser_entry_type = g_type_register_static (GTK_TYPE_ENTRY, "GtkFileChooserEntry", - &file_chooser_entry_info, 0); - g_type_add_interface_static (file_chooser_entry_type, - GTK_TYPE_EDITABLE, - &editable_info); - } - - - return file_chooser_entry_type; -} +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 -gtk_file_chooser_entry_class_init (GtkFileChooserEntryClass *class) +_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); - parent_class = g_type_class_peek_parent (class); - gobject_class->finalize = gtk_file_chooser_entry_finalize; + gobject_class->dispose = gtk_file_chooser_entry_dispose; widget_class->focus = gtk_file_chooser_entry_focus; @@ -167,12 +135,16 @@ gtk_file_chooser_entry_iface_init (GtkEditableClass *iface) } static void -gtk_file_chooser_entry_init (GtkFileChooserEntry *chooser_entry) +_gtk_file_chooser_entry_init (GtkFileChooserEntry *chooser_entry) { GtkEntryCompletion *comp; GtkCellRenderer *cell; + 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_match_func (comp, completion_match_func, chooser_entry, @@ -202,8 +174,29 @@ gtk_file_chooser_entry_finalize (GObject *object) { GtkFileChooserEntry *chooser_entry = GTK_FILE_CHOOSER_ENTRY (object); + gtk_file_path_free (chooser_entry->base_folder); + gtk_file_path_free (chooser_entry->current_folder_path); + g_free (chooser_entry->file_part); + + G_OBJECT_CLASS (_gtk_file_chooser_entry_parent_class)->finalize (object); +} + +static void +gtk_file_chooser_entry_dispose (GObject *object) +{ + GtkFileChooserEntry *chooser_entry = GTK_FILE_CHOOSER_ENTRY (object); + if (chooser_entry->completion_store) - g_object_unref (chooser_entry->completion_store); + { + g_object_unref (chooser_entry->completion_store); + chooser_entry->completion_store = NULL; + } + + if (chooser_entry->load_folder_handle) + { + gtk_file_system_cancel_operation (chooser_entry->load_folder_handle); + chooser_entry->load_folder_handle = NULL; + } if (chooser_entry->current_folder) { @@ -212,16 +205,16 @@ gtk_file_chooser_entry_finalize (GObject *object) g_signal_handlers_disconnect_by_func (chooser_entry->current_folder, G_CALLBACK (files_deleted_cb), chooser_entry); g_object_unref (chooser_entry->current_folder); + chooser_entry->current_folder = NULL; } if (chooser_entry->file_system) - g_object_unref (chooser_entry->file_system); - - gtk_file_path_free (chooser_entry->base_folder); - gtk_file_path_free (chooser_entry->current_folder_path); - g_free (chooser_entry->file_part); + { + g_object_unref (chooser_entry->file_system); + chooser_entry->file_system = NULL; + } - parent_class->finalize (object); + G_OBJECT_CLASS (_gtk_file_chooser_entry_parent_class)->dispose (object); } /* Match functions for the GtkEntryCompletion */ @@ -378,26 +371,25 @@ maybe_append_separator_to_path (GtkFileChooserEntry *chooser_entry, return display_name; } -static gboolean -check_completion_callback (GtkFileChooserEntry *chooser_entry) +/* 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 void +find_common_prefix (GtkFileChooserEntry *chooser_entry, + gchar **common_prefix_ret, + GtkFilePath **unique_path_ret) { GtkTreeIter iter; - gchar *common_prefix = NULL; - GtkFilePath *unique_path = NULL; gboolean valid; - g_assert (chooser_entry->file_part); - - chooser_entry->check_completion_idle = NULL; - - if (strcmp (chooser_entry->file_part, "") == 0) - return FALSE; + *common_prefix_ret = NULL; + *unique_path_ret = NULL; if (chooser_entry->completion_store == NULL) - return FALSE; + return; - valid = gtk_tree_model_get_iter_first (GTK_TREE_MODEL (chooser_entry->completion_store), - &iter); + valid = gtk_tree_model_get_iter_first (GTK_TREE_MODEL (chooser_entry->completion_store), &iter); while (valid) { @@ -412,14 +404,14 @@ check_completion_callback (GtkFileChooserEntry *chooser_entry) if (g_str_has_prefix (display_name, chooser_entry->file_part)) { - if (!common_prefix) + if (!*common_prefix_ret) { - common_prefix = g_strdup (display_name); - unique_path = gtk_file_path_copy (path); + *common_prefix_ret = g_strdup (display_name); + *unique_path_ret = gtk_file_path_copy (path); } else { - gchar *p = common_prefix; + gchar *p = *common_prefix_ret; const gchar *q = display_name; while (*p && *p == *q) @@ -430,16 +422,26 @@ check_completion_callback (GtkFileChooserEntry *chooser_entry) *p = '\0'; - gtk_file_path_free (unique_path); - unique_path = NULL; + gtk_file_path_free (*unique_path_ret); + *unique_path_ret = NULL; } } g_free (display_name); gtk_file_path_free (path); - valid = gtk_tree_model_iter_next (GTK_TREE_MODEL (chooser_entry->completion_store), - &iter); + valid = gtk_tree_model_iter_next (GTK_TREE_MODEL (chooser_entry->completion_store), &iter); } +} + +/* Finds a common prefix based on the contents of the entry and mandatorily appends it */ +static void +append_common_prefix (GtkFileChooserEntry *chooser_entry, + gboolean highlight) +{ + gchar *common_prefix; + GtkFilePath *unique_path; + + find_common_prefix (chooser_entry, &common_prefix, &unique_path); if (unique_path) { @@ -468,16 +470,44 @@ check_completion_callback (GtkFileChooserEntry *chooser_entry) gtk_editable_insert_text (GTK_EDITABLE (chooser_entry), common_prefix, -1, &pos); - gtk_editable_select_region (GTK_EDITABLE (chooser_entry), - chooser_entry->file_part_pos + file_part_len, - chooser_entry->file_part_pos + common_prefix_len); chooser_entry->in_change = FALSE; - chooser_entry->has_completion = TRUE; + if (highlight) + { + gtk_editable_select_region (GTK_EDITABLE (chooser_entry), + chooser_entry->file_part_pos + file_part_len, + chooser_entry->file_part_pos + common_prefix_len); + chooser_entry->has_completion = TRUE; + } } - + g_free (common_prefix); } +} + +static gboolean +check_completion_callback (GtkFileChooserEntry *chooser_entry) +{ + GDK_THREADS_ENTER (); + + g_assert (chooser_entry->file_part); + + chooser_entry->check_completion_idle = NULL; + + if (strcmp (chooser_entry->file_part, "") == 0) + goto done; + + /* We only insert the common prefix without requiring the user to hit Tab in + * the "open" modes. For "save" modes, the user must hit Tab to cause the prefix + * to be inserted. That happens in gtk_file_chooser_entry_focus(). + */ + if (chooser_entry->action == GTK_FILE_CHOOSER_ACTION_OPEN + || chooser_entry->action == GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER) + append_common_prefix (chooser_entry, TRUE); + + done: + + GDK_THREADS_LEAVE (); return FALSE; } @@ -555,58 +585,77 @@ files_deleted_cb (GtkFileSystem *file_system, /* FIXME: gravy... */ } +static void +load_directory_get_folder_callback (GtkFileSystemHandle *handle, + GtkFileFolder *folder, + const GError *error, + gpointer data) +{ + gboolean cancelled = handle->cancelled; + GtkFileChooserEntry *chooser_entry = data; + + if (handle != chooser_entry->load_folder_handle) + goto out; + + chooser_entry->load_folder_handle = NULL; + + if (cancelled || error) + goto out; + + chooser_entry->current_folder = folder; + g_signal_connect (chooser_entry->current_folder, "files-added", + G_CALLBACK (files_added_cb), chooser_entry); + g_signal_connect (chooser_entry->current_folder, "files-removed", + G_CALLBACK (files_deleted_cb), chooser_entry); + + chooser_entry->completion_store = gtk_list_store_new (N_COLUMNS, + G_TYPE_STRING, + GTK_TYPE_FILE_PATH); + + gtk_entry_completion_set_model (gtk_entry_get_completion (GTK_ENTRY (chooser_entry)), + GTK_TREE_MODEL (chooser_entry->completion_store)); + +out: + g_object_unref (chooser_entry); + g_object_unref (handle); +} + static gboolean load_directory_callback (GtkFileChooserEntry *chooser_entry) { GSList *child_paths = NULL; + GDK_THREADS_ENTER (); + chooser_entry->load_directory_idle = NULL; /* guard against bogus settings*/ if (chooser_entry->current_folder_path == NULL || chooser_entry->file_system == NULL) - return FALSE; + goto done; if (chooser_entry->current_folder != NULL) { g_warning ("idle activate multiple times without clearing the folder object first."); - return FALSE; + goto done; } g_assert (chooser_entry->completion_store == NULL); /* Load the folder */ - chooser_entry->current_folder = gtk_file_system_get_folder (chooser_entry->file_system, - chooser_entry->current_folder_path, - GTK_FILE_INFO_DISPLAY_NAME | GTK_FILE_INFO_IS_FOLDER, - NULL); /* NULL-GError */ - - /* There is no folder by that name */ - if (!chooser_entry->current_folder) - return FALSE; - g_signal_connect (chooser_entry->current_folder, "files-added", - G_CALLBACK (files_added_cb), chooser_entry); - g_signal_connect (chooser_entry->current_folder, "files-removed", - G_CALLBACK (files_deleted_cb), chooser_entry); - - chooser_entry->completion_store = gtk_list_store_new (N_COLUMNS, - G_TYPE_STRING, - GTK_TYPE_FILE_PATH); + if (chooser_entry->load_folder_handle) + gtk_file_system_cancel_operation (chooser_entry->load_folder_handle); - if (chooser_entry->file_part_pos != -1) - { - gtk_file_folder_list_children (chooser_entry->current_folder, - &child_paths, - NULL); /* NULL-GError */ - if (child_paths) - { - update_current_folder_files (chooser_entry, child_paths); - add_completion_idle (chooser_entry); - gtk_file_paths_free (child_paths); - } - } + chooser_entry->load_folder_handle = + gtk_file_system_get_folder (chooser_entry->file_system, + chooser_entry->current_folder_path, + GTK_FILE_INFO_DISPLAY_NAME | GTK_FILE_INFO_IS_FOLDER, + load_directory_get_folder_callback, + g_object_ref (chooser_entry)); + + done: + + GDK_THREADS_LEAVE (); - gtk_entry_completion_set_model (gtk_entry_get_completion (GTK_ENTRY (chooser_entry)), - GTK_TREE_MODEL (chooser_entry->completion_store)); return FALSE; } @@ -628,9 +677,20 @@ static gboolean gtk_file_chooser_entry_focus (GtkWidget *widget, GtkDirectionType direction) { - GtkFileChooserEntry *chooser_entry = GTK_FILE_CHOOSER_ENTRY (widget); + GtkFileChooserEntry *chooser_entry; + GtkEditable *editable; + GtkEntry *entry; GdkModifierType state; - gboolean control_pressed = FALSE; + gboolean control_pressed; + + 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)->focus (widget, direction); + + control_pressed = FALSE; if (gtk_get_current_event_state (&state)) { @@ -644,15 +704,23 @@ gtk_file_chooser_entry_focus (GtkWidget *widget, (GTK_WIDGET_HAS_FOCUS (widget)) && (! control_pressed)) { - if (chooser_entry->has_completion) - { - gtk_editable_set_position (GTK_EDITABLE (widget), - GTK_ENTRY (widget)->text_length); - } + gint pos = 0; + + if (!chooser_entry->has_completion + && gtk_editable_get_position (editable) == entry->text_length) + append_common_prefix (chooser_entry, FALSE); + + gtk_editable_set_position (editable, entry->text_length); + + /* Trigger the completion window to pop up again by a + * zero-length insertion, a bit of a hack. + */ + gtk_editable_insert_text (editable, "", -1, &pos); + return TRUE; } else - return GTK_WIDGET_CLASS (parent_class)->focus (widget, direction); + return GTK_WIDGET_CLASS (_gtk_file_chooser_entry_parent_class)->focus (widget, direction); } static void @@ -666,7 +734,7 @@ gtk_file_chooser_entry_activate (GtkEntry *entry) entry->text_length); } - GTK_ENTRY_CLASS (parent_class)->activate (entry); + GTK_ENTRY_CLASS (_gtk_file_chooser_entry_parent_class)->activate (entry); } /* This will see if a path typed by the user is new, and installs the loading @@ -698,10 +766,7 @@ gtk_file_chooser_entry_maybe_update_directory (GtkFileChooserEntry *chooser_entr } if (chooser_entry->completion_store) { - gtk_list_store_clear (GTK_LIST_STORE (chooser_entry->completion_store)); - /* FIXME: Uncomment this line and get rid of the _clear above - * after #137211 is fixed */ - /* gtk_entry_completion_set_model (gtk_entry_get_completion (GTK_ENTRY (chooser_entry)), NULL);*/ + 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; } @@ -788,6 +853,7 @@ clear_completion_callback (GtkFileChooserEntry *chooser_entry, /** * _gtk_file_chooser_entry_new: + * @eat_tabs: If %FALSE, allow focus navigation with the tab key. * * Creates a new #GtkFileChooserEntry object. #GtkFileChooserEntry * is an internal implementation widget for the GTK+ file chooser @@ -797,9 +863,14 @@ clear_completion_callback (GtkFileChooserEntry *chooser_entry, * Return value: the newly created #GtkFileChooserEntry **/ GtkWidget * -_gtk_file_chooser_entry_new (void) +_gtk_file_chooser_entry_new (gboolean eat_tabs) { - return g_object_new (GTK_TYPE_FILE_CHOOSER_ENTRY, NULL); + GtkFileChooserEntry *chooser_entry; + + chooser_entry = g_object_new (GTK_TYPE_FILE_CHOOSER_ENTRY, NULL); + chooser_entry->eat_tabs = (eat_tabs != FALSE); + + return GTK_WIDGET (chooser_entry); } /** @@ -841,6 +912,7 @@ _gtk_file_chooser_entry_set_base_folder (GtkFileChooserEntry *chooser_entry, chooser_entry->base_folder = gtk_file_path_copy (path); + gtk_file_chooser_entry_changed (GTK_EDITABLE (chooser_entry)); gtk_editable_select_region (GTK_EDITABLE (chooser_entry), 0, -1); } @@ -862,6 +934,11 @@ _gtk_file_chooser_entry_set_base_folder (GtkFileChooserEntry *chooser_entry, const GtkFilePath * _gtk_file_chooser_entry_get_current_folder (GtkFileChooserEntry *chooser_entry) { + if (chooser_entry->has_completion) + { + gtk_editable_set_position (GTK_EDITABLE (chooser_entry), + GTK_ENTRY (chooser_entry)->text_length); + } return chooser_entry->current_folder_path; } @@ -880,6 +957,11 @@ _gtk_file_chooser_entry_get_current_folder (GtkFileChooserEntry *chooser_entry) const gchar * _gtk_file_chooser_entry_get_file_part (GtkFileChooserEntry *chooser_entry) { + if (chooser_entry->has_completion) + { + gtk_editable_set_position (GTK_EDITABLE (chooser_entry), + GTK_ENTRY (chooser_entry)->text_length); + } return chooser_entry->file_part; } @@ -898,3 +980,84 @@ _gtk_file_chooser_entry_set_file_part (GtkFileChooserEntry *chooser_entry, gtk_entry_set_text (GTK_ENTRY (chooser_entry), file_part); } + + +/** + * _gtk_file_chooser_entry_set_action: + * @chooser_entry: a #GtkFileChooserEntry + * @action: the action which is performed by the file selector using this entry + * + * Sets action which is performed by the file selector using this entry. + * The #GtkFileChooserEntry will use different completion strategies for + * different actions. + **/ +void +_gtk_file_chooser_entry_set_action (GtkFileChooserEntry *chooser_entry, + GtkFileChooserAction action) +{ + g_return_if_fail (GTK_IS_FILE_CHOOSER_ENTRY (chooser_entry)); + + if (chooser_entry->action != action) + { + GtkEntryCompletion *comp; + + chooser_entry->action = action; + + comp = gtk_entry_get_completion (GTK_ENTRY (chooser_entry)); + + switch (action) + { + case GTK_FILE_CHOOSER_ACTION_OPEN: + case GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER: + gtk_entry_completion_set_popup_single_match (comp, FALSE); + break; + case GTK_FILE_CHOOSER_ACTION_SAVE: + case GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER: + gtk_entry_completion_set_popup_single_match (comp, TRUE); + break; + } + } +} + + +/** + * _gtk_file_chooser_entry_get_action: + * @chooser_entry: a #GtkFileChooserEntry + * + * Gets the action for this entry. + * + * Returns: the action + **/ +GtkFileChooserAction +_gtk_file_chooser_entry_get_action (GtkFileChooserEntry *chooser_entry) +{ + g_return_val_if_fail (GTK_IS_FILE_CHOOSER_ENTRY (chooser_entry), + GTK_FILE_CHOOSER_ACTION_OPEN); + + return chooser_entry->action; +} + +gboolean +_gtk_file_chooser_entry_get_is_folder (GtkFileChooserEntry *chooser_entry, + const GtkFilePath *path) +{ + gboolean retval = FALSE; + + if (chooser_entry->current_folder) + { + GtkFileInfo *file_info; + + file_info = gtk_file_folder_get_info (chooser_entry->current_folder, + path, NULL); + if (file_info) + { + retval = gtk_file_info_get_is_folder (file_info); + gtk_file_info_free (file_info); + } + } + + return retval; +} + +#define __GTK_FILE_CHOOSER_ENTRY_C__ +#include "gtkaliasdef.c"