X-Git-Url: http://pileus.org/git/?a=blobdiff_plain;f=gtk%2Fgtkentrycompletion.c;h=b0bf1c8695fa4a988ca89aebc4166bbe8c1ec0f4;hb=HEAD;hp=474b65764fb3a46ce9821e1b3ad5c1fbf3541477;hpb=07e7719441f5927bc3c482717fc53f8529186e3a;p=~andy%2Fgtk diff --git a/gtk/gtkentrycompletion.c b/gtk/gtkentrycompletion.c index 474b65764..b0bf1c869 100644 --- a/gtk/gtkentrycompletion.c +++ b/gtk/gtkentrycompletion.c @@ -12,15 +12,63 @@ * Library General Public License for more details. * * You should have received a copy of the GNU Library 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 +/** + * SECTION:gtkentrycompletion + * @Short_description: Completion functionality for GtkEntry + * @Title: GtkEntryCompletion + * + * #GtkEntryCompletion is an auxiliary object to be used in conjunction with + * #GtkEntry to provide the completion functionality. It implements the + * #GtkCellLayout interface, to allow the user to add extra cells to the + * #GtkTreeView with completion matches. + * + * "Completion functionality" means that when the user modifies the text + * in the entry, #GtkEntryCompletion checks which rows in the model match + * the current content of the entry, and displays a list of matches. + * By default, the matching is done by comparing the entry text + * case-insensitively against the text column of the model (see + * gtk_entry_completion_set_text_column()), but this can be overridden + * with a custom match function (see gtk_entry_completion_set_match_func()). + * + * When the user selects a completion, the content of the entry is + * updated. By default, the content of the entry is replaced by the + * text column of the model, but this can be overridden by connecting + * to the #GtkEntryCompletion::match-selected signal and updating the + * entry in the signal handler. Note that you should return %TRUE from + * the signal handler to suppress the default behaviour. + * + * To add completion functionality to an entry, use gtk_entry_set_completion(). + * + * In addition to regular completion matches, which will be inserted into the + * entry when they are selected, #GtkEntryCompletion also allows to display + * "actions" in the popup window. Their appearance is similar to menuitems, + * to differentiate them clearly from completion strings. When an action is + * selected, the #GtkEntryCompletion::action-activated signal is emitted. + * + * GtkEntryCompletion uses a #GtkTreeModelFilter model to represent the + * subset of the entire model that is currently matching. While the + * GtkEntryCompletion signals #GtkEntryCompletion::match-selected and + * #GtkEntryCompletion::cursor-on-match take the original model and an + * iter pointing to that model as arguments, other callbacks and signals + * (such as #GtkCellLayoutDataFuncs or #GtkCellArea::apply-attributes) + * will generally take the filter model as argument. As long as you are + * only calling gtk_tree_model_get(), this will make no difference to + * you. If for some reason, you need the original model, use + * gtk_tree_model_filter_get_model(). Don't forget to use + * gtk_tree_model_filter_convert_iter_to_child_iter() to obtain a + * matching iter. + */ + +#include "config.h" + #include "gtkentrycompletion.h" + #include "gtkentryprivate.h" #include "gtkcelllayout.h" +#include "gtkcellareabox.h" #include "gtkintl.h" #include "gtkcellrenderertext.h" @@ -28,18 +76,19 @@ #include "gtktreeselection.h" #include "gtktreeview.h" #include "gtkscrolledwindow.h" -#include "gtkvbox.h" +#include "gtksizerequest.h" +#include "gtkbox.h" #include "gtkwindow.h" #include "gtkentry.h" #include "gtkmain.h" -#include "gtksignal.h" #include "gtkmarshalers.h" #include "gtkprivate.h" -#include "gtkalias.h" #include +#define PAGE_STEP 14 +#define COMPLETION_TIMEOUT 300 /* signals */ enum @@ -47,6 +96,7 @@ enum INSERT_PREFIX, MATCH_SELECTED, ACTION_ACTIVATED, + CURSOR_ON_MATCH, LAST_SIGNAL }; @@ -60,88 +110,90 @@ enum PROP_INLINE_COMPLETION, PROP_POPUP_COMPLETION, PROP_POPUP_SET_WIDTH, - PROP_POPUP_SINGLE_MATCH + PROP_POPUP_SINGLE_MATCH, + PROP_INLINE_SELECTION, + PROP_CELL_AREA }; -#define GTK_ENTRY_COMPLETION_GET_PRIVATE(obj)(G_TYPE_INSTANCE_GET_PRIVATE ((obj), GTK_TYPE_ENTRY_COMPLETION, GtkEntryCompletionPrivate)) static void gtk_entry_completion_cell_layout_init (GtkCellLayoutIface *iface); -static void gtk_entry_completion_set_property (GObject *object, - guint prop_id, - const GValue *value, - GParamSpec *pspec); -static void gtk_entry_completion_get_property (GObject *object, - guint prop_id, - GValue *value, - GParamSpec *pspec); -static void gtk_entry_completion_finalize (GObject *object); - -static void gtk_entry_completion_pack_start (GtkCellLayout *cell_layout, - GtkCellRenderer *cell, - gboolean expand); -static void gtk_entry_completion_pack_end (GtkCellLayout *cell_layout, - GtkCellRenderer *cell, - gboolean expand); -static void gtk_entry_completion_clear (GtkCellLayout *cell_layout); -static void gtk_entry_completion_add_attribute (GtkCellLayout *cell_layout, - GtkCellRenderer *cell, - const char *attribute, - gint column); -static void gtk_entry_completion_set_cell_data_func (GtkCellLayout *cell_layout, - GtkCellRenderer *cell, - GtkCellLayoutDataFunc func, - gpointer func_data, - GDestroyNotify destroy); -static void gtk_entry_completion_clear_attributes (GtkCellLayout *cell_layout, - GtkCellRenderer *cell); -static void gtk_entry_completion_reorder (GtkCellLayout *cell_layout, - GtkCellRenderer *cell, - gint position); - -static gboolean gtk_entry_completion_visible_func (GtkTreeModel *model, - GtkTreeIter *iter, - gpointer data); -static gboolean gtk_entry_completion_popup_key_event (GtkWidget *widget, - GdkEventKey *event, - gpointer user_data); -static gboolean gtk_entry_completion_popup_button_press (GtkWidget *widget, - GdkEventButton *event, - gpointer user_data); -static gboolean gtk_entry_completion_list_button_press (GtkWidget *widget, - GdkEventButton *event, - gpointer user_data); -static gboolean gtk_entry_completion_action_button_press (GtkWidget *widget, - GdkEventButton *event, - gpointer user_data); -static void gtk_entry_completion_selection_changed (GtkTreeSelection *selection, - gpointer data); -static gboolean gtk_entry_completion_list_enter_notify (GtkWidget *widget, - GdkEventCrossing *event, - gpointer data); -static gboolean gtk_entry_completion_list_motion_notify (GtkWidget *widget, - GdkEventMotion *event, - gpointer data); -static void gtk_entry_completion_insert_action (GtkEntryCompletion *completion, - gint index, - const gchar *string, - gboolean markup); -static void gtk_entry_completion_action_data_func (GtkTreeViewColumn *tree_column, - GtkCellRenderer *cell, - GtkTreeModel *model, - GtkTreeIter *iter, - gpointer data); +static GtkCellArea* gtk_entry_completion_get_area (GtkCellLayout *cell_layout); + +static GObject *gtk_entry_completion_constructor (GType type, + guint n_construct_properties, + GObjectConstructParam *construct_properties); +static void gtk_entry_completion_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec); +static void gtk_entry_completion_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec); +static void gtk_entry_completion_finalize (GObject *object); +static void gtk_entry_completion_dispose (GObject *object); + +static gboolean gtk_entry_completion_visible_func (GtkTreeModel *model, + GtkTreeIter *iter, + gpointer data); +static gboolean gtk_entry_completion_popup_key_event (GtkWidget *widget, + GdkEventKey *event, + gpointer user_data); +static gboolean gtk_entry_completion_popup_button_press (GtkWidget *widget, + GdkEventButton *event, + gpointer user_data); +static gboolean gtk_entry_completion_list_button_press (GtkWidget *widget, + GdkEventButton *event, + gpointer user_data); +static gboolean gtk_entry_completion_action_button_press (GtkWidget *widget, + GdkEventButton *event, + gpointer user_data); +static void gtk_entry_completion_selection_changed (GtkTreeSelection *selection, + gpointer data); +static gboolean gtk_entry_completion_list_enter_notify (GtkWidget *widget, + GdkEventCrossing *event, + gpointer data); +static gboolean gtk_entry_completion_list_motion_notify (GtkWidget *widget, + GdkEventMotion *event, + gpointer data); +static void gtk_entry_completion_insert_action (GtkEntryCompletion *completion, + gint index, + const gchar *string, + gboolean markup); +static void gtk_entry_completion_action_data_func (GtkTreeViewColumn *tree_column, + GtkCellRenderer *cell, + GtkTreeModel *model, + GtkTreeIter *iter, + gpointer data); static gboolean gtk_entry_completion_match_selected (GtkEntryCompletion *completion, - GtkTreeModel *model, - GtkTreeIter *iter); + GtkTreeModel *model, + GtkTreeIter *iter); static gboolean gtk_entry_completion_real_insert_prefix (GtkEntryCompletion *completion, - const gchar *prefix); + const gchar *prefix); +static gboolean gtk_entry_completion_cursor_on_match (GtkEntryCompletion *completion, + GtkTreeModel *model, + GtkTreeIter *iter); +static gboolean gtk_entry_completion_insert_completion (GtkEntryCompletion *completion, + GtkTreeModel *model, + GtkTreeIter *iter); +static void gtk_entry_completion_insert_completion_text (GtkEntryCompletion *completion, + const gchar *text); +static void connect_completion_signals (GtkEntryCompletion *completion); +static void disconnect_completion_signals (GtkEntryCompletion *completion); + static guint entry_completion_signals[LAST_SIGNAL] = { 0 }; +/* GtkBuildable */ +static void gtk_entry_completion_buildable_init (GtkBuildableIface *iface); + G_DEFINE_TYPE_WITH_CODE (GtkEntryCompletion, gtk_entry_completion, G_TYPE_OBJECT, - G_IMPLEMENT_INTERFACE (GTK_TYPE_CELL_LAYOUT, - gtk_entry_completion_cell_layout_init)) + G_IMPLEMENT_INTERFACE (GTK_TYPE_CELL_LAYOUT, + gtk_entry_completion_cell_layout_init) + G_IMPLEMENT_INTERFACE (GTK_TYPE_BUILDABLE, + gtk_entry_completion_buildable_init)) + static void gtk_entry_completion_class_init (GtkEntryCompletionClass *klass) @@ -150,33 +202,36 @@ gtk_entry_completion_class_init (GtkEntryCompletionClass *klass) object_class = (GObjectClass *)klass; + object_class->constructor = gtk_entry_completion_constructor; object_class->set_property = gtk_entry_completion_set_property; object_class->get_property = gtk_entry_completion_get_property; + object_class->dispose = gtk_entry_completion_dispose; object_class->finalize = gtk_entry_completion_finalize; klass->match_selected = gtk_entry_completion_match_selected; klass->insert_prefix = gtk_entry_completion_real_insert_prefix; + klass->cursor_on_match = gtk_entry_completion_cursor_on_match; /** * 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_signal_new (I_("insert-prefix"), G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GtkEntryCompletionClass, insert_prefix), @@ -190,18 +245,21 @@ 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. * + * Note that @model is the model that was passed to + * gtk_entry_completion_set_model(). + * * Return value: %TRUE if the signal has been handled - * + * * Since: 2.4 - */ + */ entry_completion_signals[MATCH_SELECTED] = - g_signal_new (I_("match_selected"), + g_signal_new (I_("match-selected"), G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GtkEntryCompletionClass, match_selected), @@ -210,23 +268,52 @@ 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 + * pointed to by @iter. + * + * Note that @model is the model that was passed to + * gtk_entry_completion_set_model(). + * + * 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), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GtkEntryCompletionClass, cursor_on_match), + _gtk_boolean_handled_accumulator, NULL, + _gtk_marshal_BOOLEAN__OBJECT_BOXED, + G_TYPE_BOOLEAN, 2, + GTK_TYPE_TREE_MODEL, + GTK_TYPE_TREE_ITER); + /** * GtkEntryCompletion::action-activated: * @widget: the object which received the signal * @index: the index of the activated action * * Gets emitted when an action is activated. - * + * * Since: 2.4 */ entry_completion_signals[ACTION_ACTIVATED] = - g_signal_new (I_("action_activated"), + g_signal_new (I_("action-activated"), G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GtkEntryCompletionClass, action_activated), NULL, NULL, - _gtk_marshal_NONE__INT, + _gtk_marshal_VOID__INT, G_TYPE_NONE, 1, G_TYPE_INT); @@ -250,6 +337,7 @@ gtk_entry_completion_class_init (GtkEntryCompletionClass *klass) * GtkEntryCompletion:text-column: * * The column of the model containing the strings. + * Note that the strings must be UTF-8. * * Since: 2.6 */ @@ -265,8 +353,8 @@ gtk_entry_completion_class_init (GtkEntryCompletionClass *klass) /** * GtkEntryCompletion:inline-completion: - * - * Determines whether the common prefix of the possible completions + * + * Determines whether the common prefix of the possible completions * should be inserted automatically in the entry. Note that this * requires text-column to be set, even if you are using a custom * match function. @@ -274,88 +362,136 @@ gtk_entry_completion_class_init (GtkEntryCompletionClass *klass) * Since: 2.6 **/ g_object_class_install_property (object_class, - PROP_INLINE_COMPLETION, - g_param_spec_boolean ("inline-completion", - P_("Inline completion"), - P_("Whether the common prefix should be inserted automatically"), - FALSE, - GTK_PARAM_READWRITE)); + PROP_INLINE_COMPLETION, + g_param_spec_boolean ("inline-completion", + P_("Inline completion"), + P_("Whether the common prefix should be inserted automatically"), + FALSE, + GTK_PARAM_READWRITE)); /** * GtkEntryCompletion:popup-completion: - * - * Determines whether the possible completions should be - * shown in a popup window. + * + * Determines whether the possible completions should be + * shown in a popup window. * * Since: 2.6 **/ g_object_class_install_property (object_class, - PROP_POPUP_COMPLETION, - g_param_spec_boolean ("popup-completion", - P_("Popup completion"), - P_("Whether the completions should be shown in a popup window"), - TRUE, - GTK_PARAM_READWRITE)); + PROP_POPUP_COMPLETION, + g_param_spec_boolean ("popup-completion", + P_("Popup completion"), + P_("Whether the completions should be shown in a popup window"), + TRUE, + GTK_PARAM_READWRITE)); /** * GtkEntryCompletion:popup-set-width: - * + * * Determines whether the completions popup window will be * resized to the width of the entry. * * Since: 2.8 */ g_object_class_install_property (object_class, - PROP_POPUP_SET_WIDTH, - g_param_spec_boolean ("popup-set-width", - P_("Popup set width"), - P_("If TRUE, the popup window will have the same size as the entry"), - TRUE, - GTK_PARAM_READWRITE)); + PROP_POPUP_SET_WIDTH, + g_param_spec_boolean ("popup-set-width", + P_("Popup set width"), + P_("If TRUE, the popup window will have the same size as the entry"), + TRUE, + GTK_PARAM_READWRITE)); /** * GtkEntryCompletion:popup-single-match: - * + * * Determines whether the completions popup window will shown * for a single possible completion. You probably want to set - * this to %FALSE if you are using - * inline + * this to %FALSE if you are using + * inline * completion. * * Since: 2.8 */ g_object_class_install_property (object_class, - PROP_POPUP_SINGLE_MATCH, - g_param_spec_boolean ("popup-single-match", - P_("Popup single match"), - P_("If TRUE, the popup window will appear for a single match."), - TRUE, - GTK_PARAM_READWRITE)); + PROP_POPUP_SINGLE_MATCH, + g_param_spec_boolean ("popup-single-match", + P_("Popup single match"), + P_("If TRUE, the popup window will appear for a single match."), + TRUE, + GTK_PARAM_READWRITE)); + /** + * GtkEntryCompletion:inline-selection: + * + * Determines whether the possible completions on the popup + * will appear in the entry as you navigate through them. + * + * Since: 2.12 + */ + g_object_class_install_property (object_class, + PROP_INLINE_SELECTION, + g_param_spec_boolean ("inline-selection", + P_("Inline selection"), + P_("Your description here"), + FALSE, + GTK_PARAM_READWRITE)); + + + /** + * GtkEntryCompletion:cell-area: + * + * The #GtkCellArea used to layout cell renderers in the treeview column. + * + * If no area is specified when creating the entry completion with gtk_entry_completion_new_with_area() + * a horizontally oriented #GtkCellAreaBox will be used. + * + * Since: 3.0 + */ + g_object_class_install_property (object_class, + PROP_CELL_AREA, + g_param_spec_object ("cell-area", + P_("Cell Area"), + P_("The GtkCellArea used to layout cells"), + GTK_TYPE_CELL_AREA, + GTK_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); g_type_class_add_private (object_class, sizeof (GtkEntryCompletionPrivate)); } + +static void +gtk_entry_completion_buildable_custom_tag_end (GtkBuildable *buildable, + GtkBuilder *builder, + GObject *child, + const gchar *tagname, + gpointer *data) +{ + /* Just ignore the boolean return from here */ + _gtk_cell_layout_buildable_custom_tag_end (buildable, builder, child, tagname, data); +} + +static void +gtk_entry_completion_buildable_init (GtkBuildableIface *iface) +{ + iface->add_child = _gtk_cell_layout_buildable_add_child; + iface->custom_tag_start = _gtk_cell_layout_buildable_custom_tag_start; + iface->custom_tag_end = gtk_entry_completion_buildable_custom_tag_end; +} + static void gtk_entry_completion_cell_layout_init (GtkCellLayoutIface *iface) { - iface->pack_start = gtk_entry_completion_pack_start; - iface->pack_end = gtk_entry_completion_pack_end; - iface->clear = gtk_entry_completion_clear; - iface->add_attribute = gtk_entry_completion_add_attribute; - iface->set_cell_data_func = gtk_entry_completion_set_cell_data_func; - iface->clear_attributes = gtk_entry_completion_clear_attributes; - iface->reorder = gtk_entry_completion_reorder; + iface->get_area = gtk_entry_completion_get_area; } static void gtk_entry_completion_init (GtkEntryCompletion *completion) { - GtkCellRenderer *cell; - GtkTreeSelection *sel; GtkEntryCompletionPrivate *priv; - GtkWidget *popup_frame; /* yes, also priv, need to keep the code readable */ - priv = completion->priv = GTK_ENTRY_COMPLETION_GET_PRIVATE (completion); + completion->priv = G_TYPE_INSTANCE_GET_PRIVATE (completion, + GTK_TYPE_ENTRY_COMPLETION, + GtkEntryCompletionPrivate); + priv = completion->priv; priv->minimum_key_length = 1; priv->text_column = -1; @@ -364,20 +500,47 @@ gtk_entry_completion_init (GtkEntryCompletion *completion) priv->popup_completion = TRUE; priv->popup_set_width = TRUE; priv->popup_single_match = TRUE; + priv->inline_selection = FALSE; - /* completions */ priv->filter_model = NULL; +} + +static GObject * +gtk_entry_completion_constructor (GType type, + guint n_construct_properties, + GObjectConstructParam *construct_properties) +{ + GtkEntryCompletion *completion; + GtkEntryCompletionPrivate *priv; + GObject *object; + GtkCellRenderer *cell; + GtkTreeSelection *sel; + GtkWidget *popup_frame; + + object = G_OBJECT_CLASS (gtk_entry_completion_parent_class)->constructor + (type, n_construct_properties, construct_properties); + + completion = (GtkEntryCompletion *) object; + priv = completion->priv; + if (!priv->cell_area) + { + priv->cell_area = gtk_cell_area_box_new (); + g_object_ref_sink (priv->cell_area); + } + + /* completions */ priv->tree_view = gtk_tree_view_new (); - g_signal_connect (priv->tree_view, "button_press_event", + g_signal_connect (priv->tree_view, "button-press-event", G_CALLBACK (gtk_entry_completion_list_button_press), completion); - g_signal_connect (priv->tree_view, "enter_notify_event", - G_CALLBACK (gtk_entry_completion_list_enter_notify), - completion); - g_signal_connect (priv->tree_view, "motion_notify_event", - G_CALLBACK (gtk_entry_completion_list_motion_notify), - completion); + g_signal_connect (priv->tree_view, "enter-notify-event", + G_CALLBACK (gtk_entry_completion_list_enter_notify), + completion); + g_signal_connect (priv->tree_view, "motion-notify-event", + G_CALLBACK (gtk_entry_completion_list_motion_notify), + completion); + gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (priv->tree_view), FALSE); gtk_tree_view_set_hover_selection (GTK_TREE_VIEW (priv->tree_view), TRUE); @@ -389,7 +552,7 @@ gtk_entry_completion_init (GtkEntryCompletion *completion) completion); priv->first_sel_changed = TRUE; - priv->column = gtk_tree_view_column_new (); + priv->column = gtk_tree_view_column_new_with_area (priv->cell_area); gtk_tree_view_append_column (GTK_TREE_VIEW (priv->tree_view), priv->column); priv->scrolled_window = gtk_scrolled_window_new (NULL, NULL); @@ -400,7 +563,8 @@ gtk_entry_completion_init (GtkEntryCompletion *completion) GTK_SHADOW_NONE); /* a nasty hack to get the completions treeview to size nicely */ - gtk_widget_set_size_request (GTK_SCROLLED_WINDOW (priv->scrolled_window)->vscrollbar, -1, 0); + gtk_widget_set_size_request (gtk_scrolled_window_get_vscrollbar (GTK_SCROLLED_WINDOW (priv->scrolled_window)), + -1, 0); /* actions */ priv->actions = gtk_list_store_new (2, G_TYPE_STRING, G_TYPE_BOOLEAN); @@ -408,15 +572,15 @@ gtk_entry_completion_init (GtkEntryCompletion *completion) priv->action_view = gtk_tree_view_new_with_model (GTK_TREE_MODEL (priv->actions)); g_object_ref_sink (priv->action_view); - g_signal_connect (priv->action_view, "button_press_event", + g_signal_connect (priv->action_view, "button-press-event", G_CALLBACK (gtk_entry_completion_action_button_press), completion); - g_signal_connect (priv->action_view, "enter_notify_event", - G_CALLBACK (gtk_entry_completion_list_enter_notify), - completion); - g_signal_connect (priv->action_view, "motion_notify_event", - G_CALLBACK (gtk_entry_completion_list_motion_notify), - completion); + g_signal_connect (priv->action_view, "enter-notify-event", + G_CALLBACK (gtk_entry_completion_list_enter_notify), + completion); + g_signal_connect (priv->action_view, "motion-notify-event", + G_CALLBACK (gtk_entry_completion_list_motion_notify), + completion); gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (priv->action_view), FALSE); gtk_tree_view_set_hover_selection (GTK_TREE_VIEW (priv->action_view), TRUE); @@ -435,23 +599,25 @@ gtk_entry_completion_init (GtkEntryCompletion *completion) /* pack it all */ priv->popup_window = gtk_window_new (GTK_WINDOW_POPUP); gtk_window_set_resizable (GTK_WINDOW (priv->popup_window), FALSE); - g_signal_connect (priv->popup_window, "key_press_event", + gtk_window_set_type_hint (GTK_WINDOW(priv->popup_window), + GDK_WINDOW_TYPE_HINT_COMBO); + g_signal_connect (priv->popup_window, "key-press-event", G_CALLBACK (gtk_entry_completion_popup_key_event), completion); - g_signal_connect (priv->popup_window, "key_release_event", + g_signal_connect (priv->popup_window, "key-release-event", G_CALLBACK (gtk_entry_completion_popup_key_event), completion); - g_signal_connect (priv->popup_window, "button_press_event", + g_signal_connect (priv->popup_window, "button-press-event", G_CALLBACK (gtk_entry_completion_popup_button_press), completion); popup_frame = gtk_frame_new (NULL); gtk_frame_set_shadow_type (GTK_FRAME (popup_frame), - GTK_SHADOW_ETCHED_IN); + GTK_SHADOW_ETCHED_IN); gtk_widget_show (popup_frame); gtk_container_add (GTK_CONTAINER (priv->popup_window), popup_frame); - - priv->vbox = gtk_vbox_new (FALSE, 0); + + priv->vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0); gtk_container_add (GTK_CONTAINER (popup_frame), priv->vbox); gtk_container_add (GTK_CONTAINER (priv->scrolled_window), priv->tree_view); @@ -462,8 +628,11 @@ gtk_entry_completion_init (GtkEntryCompletion *completion) * been inserted, so we pack the action treeview after the first * action has been added */ + + return object; } + static void gtk_entry_completion_set_property (GObject *object, guint prop_id, @@ -472,6 +641,7 @@ gtk_entry_completion_set_property (GObject *object, { GtkEntryCompletion *completion = GTK_ENTRY_COMPLETION (object); GtkEntryCompletionPrivate *priv = completion->priv; + GtkCellArea *area; switch (prop_id) { @@ -486,23 +656,48 @@ gtk_entry_completion_set_property (GObject *object, break; case PROP_TEXT_COLUMN: - priv->text_column = g_value_get_int (value); + priv->text_column = g_value_get_int (value); break; case PROP_INLINE_COMPLETION: - priv->inline_completion = g_value_get_boolean (value); + gtk_entry_completion_set_inline_completion (completion, + g_value_get_boolean (value)); break; case PROP_POPUP_COMPLETION: - priv->popup_completion = g_value_get_boolean (value); + gtk_entry_completion_set_popup_completion (completion, + g_value_get_boolean (value)); break; case PROP_POPUP_SET_WIDTH: - priv->popup_set_width = g_value_get_boolean (value); + gtk_entry_completion_set_popup_set_width (completion, + g_value_get_boolean (value)); break; case PROP_POPUP_SINGLE_MATCH: - priv->popup_single_match = g_value_get_boolean (value); + gtk_entry_completion_set_popup_single_match (completion, + g_value_get_boolean (value)); + break; + + case PROP_INLINE_SELECTION: + gtk_entry_completion_set_inline_selection (completion, + g_value_get_boolean (value)); + break; + + case PROP_CELL_AREA: + /* Construct-only, can only be assigned once */ + area = g_value_get_object (value); + if (area) + { + if (priv->cell_area != NULL) + { + g_warning ("cell-area has already been set, ignoring construct property"); + g_object_ref_sink (area); + g_object_unref (area); + } + else + priv->cell_area = g_object_ref_sink (area); + } break; default: @@ -550,6 +745,14 @@ gtk_entry_completion_get_property (GObject *object, g_value_set_boolean (value, gtk_entry_completion_get_popup_single_match (completion)); break; + case PROP_INLINE_SELECTION: + g_value_set_boolean (value, gtk_entry_completion_get_inline_selection (completion)); + break; + + case PROP_CELL_AREA: + g_value_set_object (value, completion->priv->cell_area); + break; + default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -562,22 +765,8 @@ gtk_entry_completion_finalize (GObject *object) GtkEntryCompletion *completion = GTK_ENTRY_COMPLETION (object); GtkEntryCompletionPrivate *priv = completion->priv; - if (priv->tree_view) - gtk_widget_destroy (priv->tree_view); - - if (priv->entry) - gtk_entry_set_completion (GTK_ENTRY (priv->entry), NULL); - - if (priv->actions) - g_object_unref (priv->actions); - if (priv->action_view) - g_object_unref (priv->action_view); - - if (priv->case_normalized_key) - g_free (priv->case_normalized_key); - - if (priv->popup_window) - gtk_widget_destroy (priv->popup_window); + g_free (priv->case_normalized_key); + g_free (priv->completion_prefix); if (priv->match_notify) (* priv->match_notify) (priv->match_data); @@ -585,104 +774,63 @@ gtk_entry_completion_finalize (GObject *object) G_OBJECT_CLASS (gtk_entry_completion_parent_class)->finalize (object); } -/* implement cell layout interface */ -static void -gtk_entry_completion_pack_start (GtkCellLayout *cell_layout, - GtkCellRenderer *cell, - gboolean expand) -{ - GtkEntryCompletionPrivate *priv; - - g_return_if_fail (GTK_IS_ENTRY_COMPLETION (cell_layout)); - - priv = GTK_ENTRY_COMPLETION_GET_PRIVATE (cell_layout); - - gtk_tree_view_column_pack_start (priv->column, cell, expand); -} - -static void -gtk_entry_completion_pack_end (GtkCellLayout *cell_layout, - GtkCellRenderer *cell, - gboolean expand) -{ - GtkEntryCompletionPrivate *priv; - - g_return_if_fail (GTK_IS_ENTRY_COMPLETION (cell_layout)); - - priv = GTK_ENTRY_COMPLETION_GET_PRIVATE (cell_layout); - - gtk_tree_view_column_pack_end (priv->column, cell, expand); -} - -static void -gtk_entry_completion_clear (GtkCellLayout *cell_layout) -{ - GtkEntryCompletionPrivate *priv; - - g_return_if_fail (GTK_IS_ENTRY_COMPLETION (cell_layout)); - - priv = GTK_ENTRY_COMPLETION_GET_PRIVATE (cell_layout); - - gtk_tree_view_column_clear (priv->column); -} - -static void -gtk_entry_completion_add_attribute (GtkCellLayout *cell_layout, - GtkCellRenderer *cell, - const gchar *attribute, - gint column) -{ - GtkEntryCompletionPrivate *priv; - - g_return_if_fail (GTK_IS_ENTRY_COMPLETION (cell_layout)); - - priv = GTK_ENTRY_COMPLETION_GET_PRIVATE (cell_layout); - - gtk_tree_view_column_add_attribute (priv->column, cell, attribute, column); -} - static void -gtk_entry_completion_set_cell_data_func (GtkCellLayout *cell_layout, - GtkCellRenderer *cell, - GtkCellLayoutDataFunc func, - gpointer func_data, - GDestroyNotify destroy) +gtk_entry_completion_dispose (GObject *object) { - GtkEntryCompletionPrivate *priv; + GtkEntryCompletion *completion = GTK_ENTRY_COMPLETION (object); + GtkEntryCompletionPrivate *priv = completion->priv; - g_return_if_fail (GTK_IS_ENTRY_COMPLETION (cell_layout)); + if (priv->tree_view) + { + gtk_widget_destroy (priv->tree_view); + priv->tree_view = NULL; + } - priv = GTK_ENTRY_COMPLETION_GET_PRIVATE (cell_layout); + if (priv->entry) + gtk_entry_set_completion (GTK_ENTRY (priv->entry), NULL); - gtk_cell_layout_set_cell_data_func (GTK_CELL_LAYOUT (priv->column), - cell, func, func_data, destroy); -} + if (priv->actions) + { + g_object_unref (priv->actions); + priv->actions = NULL; + } -static void -gtk_entry_completion_clear_attributes (GtkCellLayout *cell_layout, - GtkCellRenderer *cell) -{ - GtkEntryCompletionPrivate *priv; + if (priv->action_view) + { + g_object_unref (priv->action_view); + priv->action_view = NULL; + } - g_return_if_fail (GTK_IS_ENTRY_COMPLETION (cell_layout)); + if (priv->popup_window) + { + gtk_widget_destroy (priv->popup_window); + priv->popup_window = NULL; + } - priv = GTK_ENTRY_COMPLETION_GET_PRIVATE (cell_layout); + if (priv->cell_area) + { + g_object_unref (priv->cell_area); + priv->cell_area = NULL; + } - gtk_tree_view_column_clear_attributes (priv->column, cell); + G_OBJECT_CLASS (gtk_entry_completion_parent_class)->dispose (object); } -static void -gtk_entry_completion_reorder (GtkCellLayout *cell_layout, - GtkCellRenderer *cell, - gint position) +/* implement cell layout interface (only need to return the underlying cell area) */ +static GtkCellArea* +gtk_entry_completion_get_area (GtkCellLayout *cell_layout) { GtkEntryCompletionPrivate *priv; - g_return_if_fail (GTK_IS_ENTRY_COMPLETION (cell_layout)); + priv = GTK_ENTRY_COMPLETION (cell_layout)->priv; - priv = GTK_ENTRY_COMPLETION_GET_PRIVATE (cell_layout); + if (G_UNLIKELY (!priv->cell_area)) + { + priv->cell_area = gtk_cell_area_box_new (); + g_object_ref_sink (priv->cell_area); + } - gtk_cell_layout_reorder (GTK_CELL_LAYOUT (priv->column), cell, position); + return priv->cell_area; } /* all those callbacks */ @@ -702,8 +850,8 @@ gtk_entry_completion_default_completion_func (GtkEntryCompletion *completion, model = gtk_tree_model_filter_get_model (completion->priv->filter_model); - g_return_val_if_fail (gtk_tree_model_get_column_type (model, completion->priv->text_column) == G_TYPE_STRING, - FALSE); + g_return_val_if_fail (gtk_tree_model_get_column_type (model, completion->priv->text_column) == G_TYPE_STRING, + FALSE); gtk_tree_model_get (model, iter, completion->priv->text_column, &item, @@ -712,15 +860,19 @@ gtk_entry_completion_default_completion_func (GtkEntryCompletion *completion, if (item != NULL) { normalized_string = g_utf8_normalize (item, -1, G_NORMALIZE_ALL); - case_normalized_string = g_utf8_casefold (normalized_string, -1); - - if (!strncmp (key, case_normalized_string, strlen (key))) - ret = TRUE; - - g_free (item); + + if (normalized_string != NULL) + { + case_normalized_string = g_utf8_casefold (normalized_string, -1); + + if (!strncmp (key, case_normalized_string, strlen (key))) + ret = TRUE; + + g_free (case_normalized_string); + } g_free (normalized_string); - g_free (case_normalized_string); } + g_free (item); return ret; } @@ -758,7 +910,7 @@ gtk_entry_completion_popup_key_event (GtkWidget *widget, { GtkEntryCompletion *completion = GTK_ENTRY_COMPLETION (user_data); - if (!GTK_WIDGET_MAPPED (completion->priv->popup_window)) + if (!gtk_widget_get_mapped (completion->priv->popup_window)) return FALSE; /* propagate event to the entry */ @@ -774,7 +926,7 @@ gtk_entry_completion_popup_button_press (GtkWidget *widget, { GtkEntryCompletion *completion = GTK_ENTRY_COMPLETION (user_data); - if (!GTK_WIDGET_MAPPED (completion->priv->popup_window)) + if (!gtk_widget_get_mapped (completion->priv->popup_window)) return FALSE; /* if we come here, it's usually time to popdown */ @@ -791,7 +943,7 @@ gtk_entry_completion_list_button_press (GtkWidget *widget, GtkEntryCompletion *completion = GTK_ENTRY_COMPLETION (user_data); GtkTreePath *path = NULL; - if (!GTK_WIDGET_MAPPED (completion->priv->popup_window)) + if (!gtk_widget_get_mapped (completion->priv->popup_window)) return FALSE; if (gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (widget), @@ -800,18 +952,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); @@ -829,10 +986,10 @@ gtk_entry_completion_action_button_press (GtkWidget *widget, GtkEntryCompletion *completion = GTK_ENTRY_COMPLETION (user_data); GtkTreePath *path = NULL; - if (!GTK_WIDGET_MAPPED (completion->priv->popup_window)) + if (!gtk_widget_get_mapped (completion->priv->popup_window)) return FALSE; - _gtk_entry_reset_im_context (GTK_ENTRY (completion->priv->entry)); + gtk_entry_reset_im_context (GTK_ENTRY (completion->priv->entry)); if (gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (widget), event->x, event->y, @@ -902,7 +1059,7 @@ gtk_entry_completion_selection_changed (GtkTreeSelection *selection, * * Creates a new #GtkEntryCompletion object. * - * Return value: A newly created #GtkEntryCompletion object. + * Return value: A newly created #GtkEntryCompletion object * * Since: 2.4 */ @@ -916,13 +1073,35 @@ gtk_entry_completion_new (void) return completion; } +/** + * gtk_entry_completion_new_with_area: + * @area: the #GtkCellArea used to layout cells + * + * Creates a new #GtkEntryCompletion object using the + * specified @area to layout cells in the underlying + * #GtkTreeViewColumn for the drop-down menu. + * + * Return value: A newly created #GtkEntryCompletion object + * + * Since: 3.0 + */ +GtkEntryCompletion * +gtk_entry_completion_new_with_area (GtkCellArea *area) +{ + GtkEntryCompletion *completion; + + completion = g_object_new (GTK_TYPE_ENTRY_COMPLETION, "cell-area", area, NULL); + + return completion; +} + /** * gtk_entry_completion_get_entry: - * @completion: A #GtkEntryCompletion. + * @completion: a #GtkEntryCompletion * * Gets the entry @completion has been attached to. * - * Return value: The entry @completion has been attached to. + * Return value: (transfer none): The entry @completion has been attached to * * Since: 2.4 */ @@ -936,8 +1115,8 @@ gtk_entry_completion_get_entry (GtkEntryCompletion *completion) /** * gtk_entry_completion_set_model: - * @completion: A #GtkEntryCompletion. - * @model: The #GtkTreeModel. + * @completion: a #GtkEntryCompletion + * @model: (allow-none): the #GtkTreeModel * * Sets the model for a #GtkEntryCompletion. If @completion already has * a model set, it will remove it before setting the new model. @@ -955,12 +1134,12 @@ gtk_entry_completion_set_model (GtkEntryCompletion *completion, if (!model) { gtk_tree_view_set_model (GTK_TREE_VIEW (completion->priv->tree_view), - NULL); + NULL); _gtk_entry_completion_popdown (completion); completion->priv->filter_model = NULL; return; } - + /* code will unref the old filter model (if any) */ completion->priv->filter_model = GTK_TREE_MODEL_FILTER (gtk_tree_model_filter_new (model, NULL)); @@ -975,18 +1154,19 @@ gtk_entry_completion_set_model (GtkEntryCompletion *completion, g_object_notify (G_OBJECT (completion), "model"); - if (GTK_WIDGET_VISIBLE (completion->priv->popup_window)) + if (gtk_widget_get_visible (completion->priv->popup_window)) _gtk_entry_completion_resize_popup (completion); } /** * gtk_entry_completion_get_model: - * @completion: A #GtkEntryCompletion. + * @completion: a #GtkEntryCompletion * * Returns the model the #GtkEntryCompletion is using as data source. * Returns %NULL if the model is unset. * - * Return value: A #GtkTreeModel, or %NULL if none is currently being used. + * Return value: (transfer none): A #GtkTreeModel, or %NULL if none + * is currently being used * * Since: 2.4 */ @@ -997,16 +1177,16 @@ gtk_entry_completion_get_model (GtkEntryCompletion *completion) if (!completion->priv->filter_model) return NULL; - + return gtk_tree_model_filter_get_model (completion->priv->filter_model); } /** * gtk_entry_completion_set_match_func: - * @completion: A #GtkEntryCompletion. - * @func: The #GtkEntryCompletionMatchFunc to use. - * @func_data: The user data for @func. - * @func_notify: Destroy notifier for @func_data. + * @completion: a #GtkEntryCompletion + * @func: the #GtkEntryCompletionMatchFunc to use + * @func_data: user data for @func + * @func_notify: destroy notify for @func_data. * * Sets the match function for @completion to be @func. The match function * is used to determine if a row should or should not be in the completion @@ -1032,8 +1212,8 @@ gtk_entry_completion_set_match_func (GtkEntryCompletion *completion, /** * gtk_entry_completion_set_minimum_key_length: - * @completion: A #GtkEntryCompletion. - * @length: The minimum length of the key in order to start completing. + * @completion: a #GtkEntryCompletion + * @length: the minimum length of the key in order to start completing * * Requires the length of the search key for @completion to be at least * @length. This is useful for long lists, where completing using a small @@ -1052,18 +1232,18 @@ gtk_entry_completion_set_minimum_key_length (GtkEntryCompletion *completion, if (completion->priv->minimum_key_length != length) { completion->priv->minimum_key_length = length; - + g_object_notify (G_OBJECT (completion), "minimum-key-length"); } } /** * gtk_entry_completion_get_minimum_key_length: - * @completion: A #GtkEntryCompletion. + * @completion: a #GtkEntryCompletion * * Returns the minimum key length as set for @completion. * - * Return value: The currently used minimum key length. + * Return value: The currently used minimum key length * * Since: 2.4 */ @@ -1077,7 +1257,7 @@ gtk_entry_completion_get_minimum_key_length (GtkEntryCompletion *completion) /** * gtk_entry_completion_complete: - * @completion: A #GtkEntryCompletion. + * @completion: a #GtkEntryCompletion * * Requests a completion operation, or in other words a refiltering of the * current list with completions, using the current key. The completion list @@ -1091,12 +1271,12 @@ gtk_entry_completion_complete (GtkEntryCompletion *completion) gchar *tmp; g_return_if_fail (GTK_IS_ENTRY_COMPLETION (completion)); + g_return_if_fail (GTK_IS_ENTRY (completion->priv->entry)); if (!completion->priv->filter_model) return; - - if (completion->priv->case_normalized_key) - g_free (completion->priv->case_normalized_key); + + g_free (completion->priv->case_normalized_key); tmp = g_utf8_normalize (gtk_entry_get_text (GTK_ENTRY (completion->priv->entry)), -1, G_NORMALIZE_ALL); @@ -1105,7 +1285,7 @@ gtk_entry_completion_complete (GtkEntryCompletion *completion) gtk_tree_model_filter_refilter (completion->priv->filter_model); - if (GTK_WIDGET_VISIBLE (completion->priv->popup_window)) + if (gtk_widget_get_visible (completion->priv->popup_window)) _gtk_entry_completion_resize_popup (completion); } @@ -1123,7 +1303,7 @@ gtk_entry_completion_insert_action (GtkEntryCompletion *completion, 1, markup, -1); - if (!completion->priv->action_view->parent) + if (!gtk_widget_get_parent (completion->priv->action_view)) { GtkTreePath *path = gtk_tree_path_new_from_indices (0, -1); @@ -1139,9 +1319,9 @@ gtk_entry_completion_insert_action (GtkEntryCompletion *completion, /** * gtk_entry_completion_insert_action_text: - * @completion: A #GtkEntryCompletion. - * @index_: The index of the item to insert. - * @text: Text of the item to insert. + * @completion: a #GtkEntryCompletion + * @index_: the index of the item to insert + * @text: text of the item to insert * * Inserts an action in @completion's action item list at position @index_ * with text @text. If you want the action item to have markup, use @@ -1162,9 +1342,9 @@ gtk_entry_completion_insert_action_text (GtkEntryCompletion *completion, /** * gtk_entry_completion_insert_action_markup: - * @completion: A #GtkEntryCompletion. - * @index_: The index of the item to insert. - * @markup: Markup of the item to insert. + * @completion: a #GtkEntryCompletion + * @index_: the index of the item to insert + * @markup: markup of the item to insert * * Inserts an action in @completion's action item list at position @index_ * with markup @markup. @@ -1184,8 +1364,8 @@ gtk_entry_completion_insert_action_markup (GtkEntryCompletion *completion, /** * gtk_entry_completion_delete_action: - * @completion: A #GtkEntryCompletion. - * @index_: The index of the item to Delete. + * @completion: a #GtkEntryCompletion + * @index_: the index of the item to delete * * Deletes the action at @index_ from @completion's action list. * @@ -1207,18 +1387,19 @@ gtk_entry_completion_delete_action (GtkEntryCompletion *completion, /** * gtk_entry_completion_set_text_column: - * @completion: A #GtkEntryCompletion. - * @column: The column in the model of @completion to get strings from. + * @completion: a #GtkEntryCompletion + * @column: the column in the model of @completion to get strings from * * Convenience function for setting up the most used case of this code: a * completion list with just strings. This function will set up @completion * to have a list displaying all (and just) strings in the completion list, * and to get those strings from @column in the model of @completion. * - * This functions creates and adds a #GtkCellRendererText for the selected - * column. If you need to set the text column, but don't want the cell - * renderer, use g_object_set() to set the ::text_column property directly. - * + * This functions creates and adds a #GtkCellRendererText for the selected + * column. If you need to set the text column, but don't want the cell + * renderer, use g_object_set() to set the #GtkEntryCompletion:text-column + * property directly. + * * Since: 2.4 */ void @@ -1245,42 +1426,42 @@ gtk_entry_completion_set_text_column (GtkEntryCompletion *completion, /** * gtk_entry_completion_get_text_column: * @completion: a #GtkEntryCompletion - * + * * Returns the column in the model of @completion to get strings from. - * + * * Return value: the column containing the strings * * Since: 2.6 - **/ + */ gint gtk_entry_completion_get_text_column (GtkEntryCompletion *completion) { g_return_val_if_fail (GTK_IS_ENTRY_COMPLETION (completion), -1); - return completion->priv->text_column; + return completion->priv->text_column; } /* private */ static gboolean gtk_entry_completion_list_enter_notify (GtkWidget *widget, - GdkEventCrossing *event, - gpointer data) + GdkEventCrossing *event, + gpointer data) { GtkEntryCompletion *completion = GTK_ENTRY_COMPLETION (data); - + return completion->priv->ignore_enter; } static gboolean gtk_entry_completion_list_motion_notify (GtkWidget *widget, - GdkEventMotion *event, - gpointer data) + GdkEventMotion *event, + gpointer data) { GtkEntryCompletion *completion = GTK_ENTRY_COMPLETION (data); - completion->priv->ignore_enter = FALSE; - + completion->priv->ignore_enter = FALSE; + return FALSE; } @@ -1289,78 +1470,94 @@ gtk_entry_completion_list_motion_notify (GtkWidget *widget, gboolean _gtk_entry_completion_resize_popup (GtkEntryCompletion *completion) { + GtkAllocation allocation; gint x, y; - gint matches, items, height, x_border, y_border; + gint matches, actions, items, height; GdkScreen *screen; gint monitor_num; gint vertical_separator; GdkRectangle monitor; + GdkWindow *window; GtkRequisition popup_req; GtkRequisition entry_req; GtkTreePath *path; gboolean above; gint width; + GtkTreeViewColumn *action_column; + gint action_height; + + window = gtk_widget_get_window (completion->priv->entry); - if (!completion->priv->entry->window) + if (!window) return FALSE; - gdk_window_get_origin (completion->priv->entry->window, &x, &y); - _gtk_entry_get_borders (GTK_ENTRY (completion->priv->entry), &x_border, &y_border); + if (!completion->priv->filter_model) + return FALSE; - matches = gtk_tree_model_iter_n_children (GTK_TREE_MODEL (completion->priv->filter_model), NULL); + gtk_widget_get_allocation (completion->priv->entry, &allocation); + gtk_widget_get_preferred_size (completion->priv->entry, + &entry_req, NULL); + + gdk_window_get_origin (window, &x, &y); + x += allocation.x; + y += allocation.y + (allocation.height - entry_req.height) / 2; - items = MIN (matches, 15); + matches = gtk_tree_model_iter_n_children (GTK_TREE_MODEL (completion->priv->filter_model), NULL); + actions = gtk_tree_model_iter_n_children (GTK_TREE_MODEL (completion->priv->actions), NULL); + action_column = gtk_tree_view_get_column (GTK_TREE_VIEW (completion->priv->action_view), 0); gtk_tree_view_column_cell_get_size (completion->priv->column, NULL, NULL, NULL, NULL, &height); + gtk_tree_view_column_cell_get_size (action_column, NULL, + NULL, NULL, NULL, &action_height); gtk_widget_style_get (GTK_WIDGET (completion->priv->tree_view), "vertical-separator", &vertical_separator, NULL); height += vertical_separator; - + gtk_widget_realize (completion->priv->tree_view); + screen = gtk_widget_get_screen (GTK_WIDGET (completion->priv->entry)); + monitor_num = gdk_screen_get_monitor_at_window (screen, window); + gdk_screen_get_monitor_workarea (screen, monitor_num, &monitor); + + if (height == 0) + items = 0; + else if (y > monitor.height / 2) + items = MIN (matches, (((monitor.y + y) - (actions * action_height)) / height) - 1); + else + items = MIN (matches, (((monitor.height - y) - (actions * action_height)) / height) - 1); + if (items <= 0) gtk_widget_hide (completion->priv->scrolled_window); else gtk_widget_show (completion->priv->scrolled_window); - screen = gtk_widget_get_screen (GTK_WIDGET (completion->priv->entry)); - monitor_num = gdk_screen_get_monitor_at_window (screen, - GTK_WIDGET (completion->priv->entry)->window); - gdk_screen_get_monitor_geometry (screen, monitor_num, &monitor); - if (completion->priv->popup_set_width) - width = MIN (completion->priv->entry->allocation.width, monitor.width) - 2 * x_border; + width = MIN (allocation.width, monitor.width); else width = -1; gtk_tree_view_columns_autosize (GTK_TREE_VIEW (completion->priv->tree_view)); - gtk_widget_set_size_request (completion->priv->tree_view, width, items * height); - - /* default on no match */ - completion->priv->current_selected = -1; + gtk_scrolled_window_set_min_content_width (GTK_SCROLLED_WINDOW (completion->priv->scrolled_window), width); + gtk_widget_set_size_request (completion->priv->popup_window, width, -1); + gtk_scrolled_window_set_min_content_height (GTK_SCROLLED_WINDOW (completion->priv->scrolled_window), items * height); - items = gtk_tree_model_iter_n_children (GTK_TREE_MODEL (completion->priv->actions), NULL); - - if (items) - { - gtk_widget_show (completion->priv->action_view); - gtk_widget_set_size_request (completion->priv->action_view, width, -1); - } + if (actions) + gtk_widget_show (completion->priv->action_view); else gtk_widget_hide (completion->priv->action_view); - gtk_widget_size_request (completion->priv->popup_window, &popup_req); - gtk_widget_size_request (completion->priv->entry, &entry_req); - + gtk_widget_get_preferred_size (completion->priv->popup_window, + &popup_req, NULL); + if (x < monitor.x) x = monitor.x; else if (x + popup_req.width > monitor.x + monitor.width) x = monitor.x + monitor.width - popup_req.width; - + if (y + entry_req.height + popup_req.height <= monitor.y + monitor.height || y - monitor.y < (monitor.y + monitor.height) - (y + entry_req.height)) { @@ -1372,12 +1569,12 @@ _gtk_entry_completion_resize_popup (GtkEntryCompletion *completion) y -= popup_req.height; above = TRUE; } - - if (matches > 0) + + if (matches > 0) { path = gtk_tree_path_new_from_indices (above ? matches - 1 : 0, -1); - gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (completion->priv->tree_view), path, - NULL, FALSE, 0.0, 0.0); + gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (completion->priv->tree_view), path, + NULL, FALSE, 0.0, 0.0); gtk_tree_path_free (path); } @@ -1386,137 +1583,193 @@ _gtk_entry_completion_resize_popup (GtkEntryCompletion *completion) return above; } -void -_gtk_entry_completion_popup (GtkEntryCompletion *completion) +static void +gtk_entry_completion_popup (GtkEntryCompletion *completion) { GtkTreeViewColumn *column; + GtkStyleContext *context; + GdkRGBA color; GList *renderers; GtkWidget *toplevel; - if (GTK_WIDGET_MAPPED (completion->priv->popup_window)) + if (gtk_widget_get_mapped (completion->priv->popup_window)) return; - if (!GTK_WIDGET_MAPPED (completion->priv->entry)) + if (!gtk_widget_get_mapped (completion->priv->entry)) return; - if (!GTK_WIDGET_HAS_FOCUS (completion->priv->entry)) + if (!gtk_widget_has_focus (completion->priv->entry)) + return; + + if (completion->priv->has_grab) return; completion->priv->ignore_enter = TRUE; - + column = gtk_tree_view_get_column (GTK_TREE_VIEW (completion->priv->action_view), 0); - renderers = gtk_tree_view_column_get_cell_renderers (column); - gtk_widget_ensure_style (completion->priv->tree_view); - g_object_set (GTK_CELL_RENDERER (renderers->data), "cell-background-gdk", - &completion->priv->tree_view->style->bg[GTK_STATE_NORMAL], + renderers = gtk_cell_layout_get_cells (GTK_CELL_LAYOUT (column)); + + context = gtk_widget_get_style_context (completion->priv->tree_view); + gtk_style_context_get_background_color (context, 0, &color); + + g_object_set (GTK_CELL_RENDERER (renderers->data), + "cell-background-rgba", &color, NULL); g_list_free (renderers); gtk_widget_show_all (completion->priv->vbox); + /* default on no match */ + completion->priv->current_selected = -1; + _gtk_entry_completion_resize_popup (completion); toplevel = gtk_widget_get_toplevel (completion->priv->entry); if (GTK_IS_WINDOW (toplevel)) - gtk_window_group_add_window (gtk_window_get_group (GTK_WINDOW (toplevel)), - GTK_WINDOW (completion->priv->popup_window)); + gtk_window_group_add_window (gtk_window_get_group (GTK_WINDOW (toplevel)), + GTK_WINDOW (completion->priv->popup_window)); /* prevent the first row being focused */ gtk_widget_grab_focus (completion->priv->tree_view); gtk_tree_selection_unselect_all (gtk_tree_view_get_selection (GTK_TREE_VIEW (completion->priv->tree_view))); gtk_tree_selection_unselect_all (gtk_tree_view_get_selection (GTK_TREE_VIEW (completion->priv->action_view))); - + + gtk_window_set_screen (GTK_WINDOW (completion->priv->popup_window), + gtk_widget_get_screen (completion->priv->entry)); gtk_widget_show (completion->priv->popup_window); - - gtk_grab_add (completion->priv->popup_window); - gdk_pointer_grab (completion->priv->popup_window->window, TRUE, - GDK_BUTTON_PRESS_MASK | - GDK_BUTTON_RELEASE_MASK | - GDK_POINTER_MOTION_MASK, - NULL, NULL, GDK_CURRENT_TIME); + + gtk_device_grab_add (completion->priv->popup_window, completion->priv->device, TRUE); + gdk_device_grab (completion->priv->device, gtk_widget_get_window (completion->priv->popup_window), + GDK_OWNERSHIP_WINDOW, TRUE, + GDK_BUTTON_PRESS_MASK | + GDK_BUTTON_RELEASE_MASK | + GDK_POINTER_MOTION_MASK, + NULL, GDK_CURRENT_TIME); + + completion->priv->has_grab = TRUE; } void _gtk_entry_completion_popdown (GtkEntryCompletion *completion) { - if (!GTK_WIDGET_MAPPED (completion->priv->popup_window)) + if (!gtk_widget_get_mapped (completion->priv->popup_window)) return; completion->priv->ignore_enter = FALSE; - - gdk_pointer_ungrab (GDK_CURRENT_TIME); - gtk_grab_remove (completion->priv->popup_window); + + if (completion->priv->has_grab) + { + gdk_device_ungrab (completion->priv->device, GDK_CURRENT_TIME); + gtk_device_grab_remove (completion->priv->popup_window, + completion->priv->device); + completion->priv->has_grab = FALSE; + } gtk_widget_hide (completion->priv->popup_window); } -static gboolean +static gboolean gtk_entry_completion_match_selected (GtkEntryCompletion *completion, - GtkTreeModel *model, - GtkTreeIter *iter) + GtkTreeModel *model, + GtkTreeIter *iter) { gchar *str = NULL; gtk_tree_model_get (model, iter, completion->priv->text_column, &str, -1); gtk_entry_set_text (GTK_ENTRY (completion->priv->entry), str ? str : ""); - + /* move cursor to the end */ gtk_editable_set_position (GTK_EDITABLE (completion->priv->entry), -1); - + g_free (str); return TRUE; } +static gboolean +gtk_entry_completion_cursor_on_match (GtkEntryCompletion *completion, + GtkTreeModel *model, + GtkTreeIter *iter) +{ + gtk_entry_completion_insert_completion (completion, model, iter); + + return TRUE; +} -static gchar * -gtk_entry_completion_compute_prefix (GtkEntryCompletion *completion) +/** + * gtk_entry_completion_compute_prefix: + * @completion: the entry completion + * @key: The text to complete for + * + * Computes the common prefix that is shared by all rows in @completion + * that start with @key. If no row matches @key, %NULL will be returned. + * Note that a text column must have been set for this function to work, + * see gtk_entry_completion_set_text_column() for details. + * + * Returns: (transfer full): The common prefix all rows starting with @key + * or %NULL if no row matches @key. + * + * Since: 3.4 + **/ +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); - + &iter); + while (valid) { gchar *text; - + gtk_tree_model_get (GTK_TREE_MODEL (completion->priv->filter_model), - &iter, completion->priv->text_column, &text, - -1); + &iter, completion->priv->text_column, &text, + -1); if (text && g_str_has_prefix (text, key)) - { - if (!prefix) - prefix = g_strdup (text); - else - { - gchar *p = prefix; - const gchar *q = text; - - while (*p && *p == *q) - { - p++; - q++; - } - - *p = '\0'; - } - } - + { + if (!prefix) + prefix = g_strdup (text); + else + { + gchar *p = prefix; + gchar *q = text; + + while (*p && *p == *q) + { + p++; + q++; + } + + *p = '\0'; + + if (p > prefix) + { + /* strip a partial multibyte character */ + q = g_utf8_find_prev_char (prefix, p); + switch (g_utf8_get_char_validated (q, p - q)) + { + case (gunichar)-2: + case (gunichar)-1: + *q = 0; + default: ; + } + } + } + } + g_free (text); valid = gtk_tree_model_iter_next (GTK_TREE_MODEL (completion->priv->filter_model), - &iter); + &iter); } return prefix; @@ -1525,7 +1778,7 @@ gtk_entry_completion_compute_prefix (GtkEntryCompletion *completion) static gboolean gtk_entry_completion_real_insert_prefix (GtkEntryCompletion *completion, - const gchar *prefix) + const gchar *prefix) { if (prefix) { @@ -1539,64 +1792,137 @@ gtk_entry_completion_real_insert_prefix (GtkEntryCompletion *completion, key_len = g_utf8_strlen (key, -1); if (prefix_len > key_len) - { - gint pos = prefix_len; + { + gint pos = prefix_len; - gtk_editable_insert_text (GTK_EDITABLE (completion->priv->entry), - prefix + strlen (key), -1, &pos); - gtk_editable_select_region (GTK_EDITABLE (completion->priv->entry), - key_len, prefix_len); + gtk_editable_insert_text (GTK_EDITABLE (completion->priv->entry), + prefix + strlen (key), -1, &pos); + gtk_editable_select_region (GTK_EDITABLE (completion->priv->entry), + key_len, prefix_len); - completion->priv->has_completion = TRUE; - } + completion->priv->has_completion = TRUE; + } } return TRUE; } +/** + * gtk_entry_completion_get_completion_prefix: + * @completion: a #GtkEntryCompletion + * + * Get the original text entered by the user that triggered + * the completion or %NULL if there's no completion ongoing. + * + * Returns: the prefix for the current completion + * + * Since: 2.12 + */ +const gchar* +gtk_entry_completion_get_completion_prefix (GtkEntryCompletion *completion) +{ + g_return_val_if_fail (GTK_IS_ENTRY_COMPLETION (completion), NULL); + + return completion->priv->completion_prefix; +} + +static void +gtk_entry_completion_insert_completion_text (GtkEntryCompletion *completion, + const gchar *text) +{ + GtkEntryCompletionPrivate *priv = completion->priv; + gint len; + + priv = completion->priv; + + if (priv->changed_id > 0) + g_signal_handler_block (priv->entry, priv->changed_id); + + if (priv->insert_text_id > 0) + g_signal_handler_block (priv->entry, priv->insert_text_id); + + gtk_entry_set_text (GTK_ENTRY (priv->entry), text); + + len = strlen (priv->completion_prefix); + gtk_editable_select_region (GTK_EDITABLE (priv->entry), len, -1); + + if (priv->changed_id > 0) + g_signal_handler_unblock (priv->entry, priv->changed_id); + + if (priv->insert_text_id > 0) + g_signal_handler_unblock (priv->entry, priv->insert_text_id); +} + +static gboolean +gtk_entry_completion_insert_completion (GtkEntryCompletion *completion, + GtkTreeModel *model, + GtkTreeIter *iter) +{ + gchar *str = NULL; + + if (completion->priv->text_column < 0) + return FALSE; + + gtk_tree_model_get (model, iter, + completion->priv->text_column, &str, + -1); + + gtk_entry_completion_insert_completion_text (completion, str); + + g_free (str); + + return TRUE; +} + /** * gtk_entry_completion_insert_prefix: * @completion: a #GtkEntryCompletion - * - * Requests a prefix insertion. - * + * + * Requests a prefix insertion. + * * Since: 2.6 - **/ + */ void gtk_entry_completion_insert_prefix (GtkEntryCompletion *completion) { gboolean done; gchar *prefix; - g_signal_handler_block (completion->priv->entry, - completion->priv->insert_text_id); - prefix = gtk_entry_completion_compute_prefix (completion); + if (completion->priv->insert_text_id > 0) + g_signal_handler_block (completion->priv->entry, + completion->priv->insert_text_id); + + 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], - 0, prefix, &done); + 0, prefix, &done); g_free (prefix); } - g_signal_handler_unblock (completion->priv->entry, - completion->priv->insert_text_id); + + if (completion->priv->insert_text_id > 0) + g_signal_handler_unblock (completion->priv->entry, + completion->priv->insert_text_id); } /** * gtk_entry_completion_set_inline_completion: * @completion: a #GtkEntryCompletion * @inline_completion: %TRUE to do inline completion - * + * * Sets whether the common prefix of the possible completions should * be automatically inserted in the entry. - * + * * Since: 2.6 - **/ -void + */ +void gtk_entry_completion_set_inline_completion (GtkEntryCompletion *completion, - gboolean inline_completion) + gboolean inline_completion) { g_return_if_fail (GTK_IS_ENTRY_COMPLETION (completion)); - + inline_completion = inline_completion != FALSE; if (completion->priv->inline_completion != inline_completion) @@ -1607,23 +1933,22 @@ gtk_entry_completion_set_inline_completion (GtkEntryCompletion *completion, } } - /** * gtk_entry_completion_get_inline_completion: * @completion: a #GtkEntryCompletion - * + * * Returns whether the common prefix of the possible completions should * be automatically inserted in the entry. - * + * * Return value: %TRUE if inline completion is turned on - * + * * Since: 2.6 - **/ + */ gboolean gtk_entry_completion_get_inline_completion (GtkEntryCompletion *completion) { g_return_val_if_fail (GTK_IS_ENTRY_COMPLETION (completion), FALSE); - + return completion->priv->inline_completion; } @@ -1631,17 +1956,17 @@ gtk_entry_completion_get_inline_completion (GtkEntryCompletion *completion) * gtk_entry_completion_set_popup_completion: * @completion: a #GtkEntryCompletion * @popup_completion: %TRUE to do popup completion - * + * * Sets whether the completions should be presented in a popup window. - * + * * Since: 2.6 - **/ -void + */ +void gtk_entry_completion_set_popup_completion (GtkEntryCompletion *completion, - gboolean popup_completion) + gboolean popup_completion) { g_return_if_fail (GTK_IS_ENTRY_COMPLETION (completion)); - + popup_completion = popup_completion != FALSE; if (completion->priv->popup_completion != popup_completion) @@ -1656,18 +1981,18 @@ gtk_entry_completion_set_popup_completion (GtkEntryCompletion *completion, /** * gtk_entry_completion_get_popup_completion: * @completion: a #GtkEntryCompletion - * + * * Returns whether the completions should be presented in a popup window. - * + * * Return value: %TRUE if popup completion is turned on - * + * * Since: 2.6 - **/ + */ gboolean gtk_entry_completion_get_popup_completion (GtkEntryCompletion *completion) { g_return_val_if_fail (GTK_IS_ENTRY_COMPLETION (completion), TRUE); - + return completion->priv->popup_completion; } @@ -1681,12 +2006,12 @@ gtk_entry_completion_get_popup_completion (GtkEntryCompletion *completion) * * Since: 2.8 */ -void +void gtk_entry_completion_set_popup_set_width (GtkEntryCompletion *completion, - gboolean popup_set_width) + gboolean popup_set_width) { g_return_if_fail (GTK_IS_ENTRY_COMPLETION (completion)); - + popup_set_width = popup_set_width != FALSE; if (completion->priv->popup_set_width != popup_set_width) @@ -1700,20 +2025,20 @@ gtk_entry_completion_set_popup_set_width (GtkEntryCompletion *completion, /** * gtk_entry_completion_get_popup_set_width: * @completion: a #GtkEntryCompletion - * - * Returns whether the completion popup window will be resized to the + * + * Returns whether the completion popup window will be resized to the * width of the entry. - * - * Return value: %TRUE if the popup window will be resized to the width of + * + * Return value: %TRUE if the popup window will be resized to the width of * the entry - * + * * Since: 2.8 - **/ + */ gboolean gtk_entry_completion_get_popup_set_width (GtkEntryCompletion *completion) { g_return_val_if_fail (GTK_IS_ENTRY_COMPLETION (completion), TRUE); - + return completion->priv->popup_set_width; } @@ -1722,7 +2047,7 @@ gtk_entry_completion_get_popup_set_width (GtkEntryCompletion *completion) * gtk_entry_completion_set_popup_single_match: * @completion: a #GtkEntryCompletion * @popup_single_match: %TRUE if the popup should appear even for a single - * match + * match * * Sets whether the completion popup window will appear even if there is * only a single match. You may want to set this to %FALSE if you @@ -1731,12 +2056,12 @@ gtk_entry_completion_get_popup_set_width (GtkEntryCompletion *completion) * * Since: 2.8 */ -void +void gtk_entry_completion_set_popup_single_match (GtkEntryCompletion *completion, - gboolean popup_single_match) + gboolean popup_single_match) { g_return_if_fail (GTK_IS_ENTRY_COMPLETION (completion)); - + popup_single_match = popup_single_match != FALSE; if (completion->priv->popup_single_match != popup_single_match) @@ -1750,23 +2075,629 @@ gtk_entry_completion_set_popup_single_match (GtkEntryCompletion *completion, /** * gtk_entry_completion_get_popup_single_match: * @completion: a #GtkEntryCompletion - * + * * Returns whether the completion popup window will appear even if there is - * only a single match. - * + * only a single match. + * * Return value: %TRUE if the popup window will appear regardless of the - * number of matches. - * + * number of matches + * * Since: 2.8 - **/ + */ gboolean gtk_entry_completion_get_popup_single_match (GtkEntryCompletion *completion) { g_return_val_if_fail (GTK_IS_ENTRY_COMPLETION (completion), TRUE); - + return completion->priv->popup_single_match; } +/** + * gtk_entry_completion_set_inline_selection: + * @completion: a #GtkEntryCompletion + * @inline_selection: %TRUE to do inline selection + * + * Sets whether it is possible to cycle through the possible completions + * inside the entry. + * + * Since: 2.12 + */ +void +gtk_entry_completion_set_inline_selection (GtkEntryCompletion *completion, + gboolean inline_selection) +{ + g_return_if_fail (GTK_IS_ENTRY_COMPLETION (completion)); + + inline_selection = inline_selection != FALSE; + + if (completion->priv->inline_selection != inline_selection) + { + completion->priv->inline_selection = inline_selection; + + g_object_notify (G_OBJECT (completion), "inline-selection"); + } +} + +/** + * gtk_entry_completion_get_inline_selection: + * @completion: a #GtkEntryCompletion + * + * Returns %TRUE if inline-selection mode is turned on. + * + * Returns: %TRUE if inline-selection mode is on + * + * Since: 2.12 + */ +gboolean +gtk_entry_completion_get_inline_selection (GtkEntryCompletion *completion) +{ + g_return_val_if_fail (GTK_IS_ENTRY_COMPLETION (completion), FALSE); + + return completion->priv->inline_selection; +} + + +static gint +gtk_entry_completion_timeout (gpointer data) +{ + GtkEntryCompletion *completion = GTK_ENTRY_COMPLETION (data); + + completion->priv->completion_timeout = 0; + + if (completion->priv->filter_model && + g_utf8_strlen (gtk_entry_get_text (GTK_ENTRY (completion->priv->entry)), -1) + >= completion->priv->minimum_key_length) + { + gint matches; + gint actions; + GtkTreeSelection *s; + gboolean popup_single; + + gtk_entry_completion_complete (completion); + matches = gtk_tree_model_iter_n_children (GTK_TREE_MODEL (completion->priv->filter_model), NULL); + gtk_tree_selection_unselect_all (gtk_tree_view_get_selection (GTK_TREE_VIEW (completion->priv->tree_view))); + + s = gtk_tree_view_get_selection (GTK_TREE_VIEW (completion->priv->action_view)); + + gtk_tree_selection_unselect_all (s); + + actions = gtk_tree_model_iter_n_children (GTK_TREE_MODEL (completion->priv->actions), NULL); + + g_object_get (completion, "popup-single-match", &popup_single, NULL); + if ((matches > (popup_single ? 0: 1)) || actions > 0) + { + if (gtk_widget_get_visible (completion->priv->popup_window)) + _gtk_entry_completion_resize_popup (completion); + else + gtk_entry_completion_popup (completion); + } + else + _gtk_entry_completion_popdown (completion); + } + else if (gtk_widget_get_visible (completion->priv->popup_window)) + _gtk_entry_completion_popdown (completion); + + return FALSE; +} + +static inline gboolean +keyval_is_cursor_move (guint keyval) +{ + if (keyval == GDK_KEY_Up || keyval == GDK_KEY_KP_Up) + return TRUE; + + if (keyval == GDK_KEY_Down || keyval == GDK_KEY_KP_Down) + return TRUE; + + if (keyval == GDK_KEY_Page_Up) + return TRUE; + + if (keyval == GDK_KEY_Page_Down) + return TRUE; + + return FALSE; +} + +static gboolean +gtk_entry_completion_key_press (GtkWidget *widget, + GdkEventKey *event, + gpointer user_data) +{ + gint matches, actions = 0; + GtkEntryCompletion *completion = GTK_ENTRY_COMPLETION (user_data); + + if (!completion->priv->popup_completion) + return FALSE; + + if (event->keyval == GDK_KEY_Return || + event->keyval == GDK_KEY_KP_Enter || + event->keyval == GDK_KEY_ISO_Enter || + event->keyval == GDK_KEY_Escape) + { + if (completion && completion->priv->completion_timeout) + { + g_source_remove (completion->priv->completion_timeout); + completion->priv->completion_timeout = 0; + } + } + + if (!gtk_widget_get_mapped (completion->priv->popup_window)) + return FALSE; + + matches = gtk_tree_model_iter_n_children (GTK_TREE_MODEL (completion->priv->filter_model), NULL); + + if (completion->priv->actions) + actions = gtk_tree_model_iter_n_children (GTK_TREE_MODEL (completion->priv->actions), NULL); + + if (keyval_is_cursor_move (event->keyval)) + { + GtkTreePath *path = NULL; + + if (event->keyval == GDK_KEY_Up || event->keyval == GDK_KEY_KP_Up) + { + if (completion->priv->current_selected < 0) + completion->priv->current_selected = matches + actions - 1; + else + completion->priv->current_selected--; + } + else if (event->keyval == GDK_KEY_Down || event->keyval == GDK_KEY_KP_Down) + { + if (completion->priv->current_selected < matches + actions - 1) + completion->priv->current_selected++; + else + completion->priv->current_selected = -1; + } + else if (event->keyval == GDK_KEY_Page_Up) + { + if (completion->priv->current_selected < 0) + completion->priv->current_selected = matches + actions - 1; + else if (completion->priv->current_selected == 0) + completion->priv->current_selected = -1; + else if (completion->priv->current_selected < matches) + { + completion->priv->current_selected -= PAGE_STEP; + if (completion->priv->current_selected < 0) + completion->priv->current_selected = 0; + } + else + { + completion->priv->current_selected -= PAGE_STEP; + if (completion->priv->current_selected < matches - 1) + completion->priv->current_selected = matches - 1; + } + } + else if (event->keyval == GDK_KEY_Page_Down) + { + if (completion->priv->current_selected < 0) + completion->priv->current_selected = 0; + else if (completion->priv->current_selected < matches - 1) + { + completion->priv->current_selected += PAGE_STEP; + if (completion->priv->current_selected > matches - 1) + completion->priv->current_selected = matches - 1; + } + else if (completion->priv->current_selected == matches + actions - 1) + { + completion->priv->current_selected = -1; + } + else + { + completion->priv->current_selected += PAGE_STEP; + if (completion->priv->current_selected > matches + actions - 1) + completion->priv->current_selected = matches + actions - 1; + } + } + + if (completion->priv->current_selected < 0) + { + gtk_tree_selection_unselect_all (gtk_tree_view_get_selection (GTK_TREE_VIEW (completion->priv->tree_view))); + gtk_tree_selection_unselect_all (gtk_tree_view_get_selection (GTK_TREE_VIEW (completion->priv->action_view))); + + if (completion->priv->inline_selection && + completion->priv->completion_prefix) + { + gtk_entry_set_text (GTK_ENTRY (completion->priv->entry), + completion->priv->completion_prefix); + gtk_editable_set_position (GTK_EDITABLE (widget), -1); + } + } + else if (completion->priv->current_selected < matches) + { + gtk_tree_selection_unselect_all (gtk_tree_view_get_selection (GTK_TREE_VIEW (completion->priv->action_view))); + + path = gtk_tree_path_new_from_indices (completion->priv->current_selected, -1); + gtk_tree_view_set_cursor (GTK_TREE_VIEW (completion->priv->tree_view), + path, NULL, FALSE); + + if (completion->priv->inline_selection) + { + + GtkTreeIter iter; + GtkTreeIter child_iter; + GtkTreeModel *model = NULL; + GtkTreeSelection *sel; + gboolean entry_set; + + 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, + &child_iter, &entry_set); + } + } + else if (completion->priv->current_selected - matches >= 0) + { + gtk_tree_selection_unselect_all (gtk_tree_view_get_selection (GTK_TREE_VIEW (completion->priv->tree_view))); + + path = gtk_tree_path_new_from_indices (completion->priv->current_selected - matches, -1); + gtk_tree_view_set_cursor (GTK_TREE_VIEW (completion->priv->action_view), + path, NULL, FALSE); + + if (completion->priv->inline_selection && + completion->priv->completion_prefix) + { + gtk_entry_set_text (GTK_ENTRY (completion->priv->entry), + completion->priv->completion_prefix); + gtk_editable_set_position (GTK_EDITABLE (widget), -1); + } + } + + gtk_tree_path_free (path); + + return TRUE; + } + else if (event->keyval == GDK_KEY_Escape || + event->keyval == GDK_KEY_Left || + event->keyval == GDK_KEY_KP_Left || + event->keyval == GDK_KEY_Right || + event->keyval == GDK_KEY_KP_Right) + { + gboolean retval = TRUE; + + gtk_entry_reset_im_context (GTK_ENTRY (widget)); + _gtk_entry_completion_popdown (completion); + + if (completion->priv->current_selected < 0) + { + retval = FALSE; + goto keypress_completion_out; + } + else if (completion->priv->inline_selection) + { + /* Escape rejects the tentative completion */ + if (event->keyval == GDK_KEY_Escape) + { + if (completion->priv->completion_prefix) + gtk_entry_set_text (GTK_ENTRY (completion->priv->entry), + completion->priv->completion_prefix); + else + gtk_entry_set_text (GTK_ENTRY (completion->priv->entry), ""); + } + + /* Move the cursor to the end for Right/Esc */ + if (event->keyval == GDK_KEY_Right || + event->keyval == GDK_KEY_KP_Right || + event->keyval == GDK_KEY_Escape) + gtk_editable_set_position (GTK_EDITABLE (widget), -1); + /* Let the default keybindings run for Left, i.e. either move to the + * * previous character or select word if a modifier is used */ + else + retval = FALSE; + } + +keypress_completion_out: + if (completion->priv->inline_selection) + { + g_free (completion->priv->completion_prefix); + completion->priv->completion_prefix = NULL; + } + + return retval; + } + else if (event->keyval == GDK_KEY_Tab || + event->keyval == GDK_KEY_KP_Tab || + event->keyval == GDK_KEY_ISO_Left_Tab) + { + gtk_entry_reset_im_context (GTK_ENTRY (widget)); + _gtk_entry_completion_popdown (completion); + + g_free (completion->priv->completion_prefix); + completion->priv->completion_prefix = NULL; + + return FALSE; + } + else if (event->keyval == GDK_KEY_ISO_Enter || + event->keyval == GDK_KEY_KP_Enter || + event->keyval == GDK_KEY_Return) + { + GtkTreeIter iter; + GtkTreeModel *model = NULL; + GtkTreeModel *child_model; + GtkTreeIter child_iter; + GtkTreeSelection *sel; + gboolean retval = TRUE; + + gtk_entry_reset_im_context (GTK_ENTRY (widget)); + _gtk_entry_completion_popdown (completion); + + if (completion->priv->current_selected < matches) + { + gboolean entry_set; + + 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", + child_model, &child_iter, &entry_set); + g_signal_handler_unblock (widget, completion->priv->changed_id); + + if (!entry_set) + { + gchar *str = NULL; + + gtk_tree_model_get (model, &iter, + completion->priv->text_column, &str, + -1); + + gtk_entry_set_text (GTK_ENTRY (widget), str); + + /* move the cursor to the end */ + gtk_editable_set_position (GTK_EDITABLE (widget), -1); + g_free (str); + } + } + else + retval = FALSE; + } + else if (completion->priv->current_selected - matches >= 0) + { + sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (completion->priv->action_view)); + if (gtk_tree_selection_get_selected (sel, &model, &iter)) + { + GtkTreePath *path; + + path = gtk_tree_path_new_from_indices (completion->priv->current_selected - matches, -1); + g_signal_emit_by_name (completion, "action-activated", + gtk_tree_path_get_indices (path)[0]); + gtk_tree_path_free (path); + } + else + retval = FALSE; + } + + g_free (completion->priv->completion_prefix); + completion->priv->completion_prefix = NULL; + + return retval; + } + + return FALSE; +} + +static void +gtk_entry_completion_changed (GtkWidget *widget, + gpointer user_data) +{ + GtkEntryCompletion *completion = GTK_ENTRY_COMPLETION (user_data); + GtkEntry *entry = GTK_ENTRY (widget); + GdkDevice *device; + + if (!completion->priv->popup_completion) + return; + + /* (re)install completion timeout */ + if (completion->priv->completion_timeout) + g_source_remove (completion->priv->completion_timeout); + + if (!gtk_entry_get_text (entry)) + return; + + /* no need to normalize for this test */ + if (completion->priv->minimum_key_length > 0 && + strcmp ("", gtk_entry_get_text (entry)) == 0) + { + if (gtk_widget_get_visible (completion->priv->popup_window)) + _gtk_entry_completion_popdown (completion); + return; + } + + device = gtk_get_current_event_device (); + + if (device && gdk_device_get_source (device) == GDK_SOURCE_KEYBOARD) + device = gdk_device_get_associated_device (device); + + if (device) + completion->priv->device = device; + + completion->priv->completion_timeout = + gdk_threads_add_timeout (COMPLETION_TIMEOUT, + gtk_entry_completion_timeout, + completion); +} + +static gboolean +check_completion_callback (GtkEntryCompletion *completion) +{ + completion->priv->check_completion_idle = NULL; + + gtk_entry_completion_complete (completion); + gtk_entry_completion_insert_prefix (completion); + + return FALSE; +} -#define __GTK_ENTRY_COMPLETION_C__ -#include "gtkaliasdef.c" +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")) + completion->priv->has_completion = FALSE; +} + +static gboolean +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 (gtk_entry_get_buffer (entry))); + + return FALSE; +} + +static void +completion_insert_text_callback (GtkEntry *entry, + const gchar *text, + gint length, + 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) + { + completion->priv->check_completion_idle = g_idle_source_new (); + g_source_set_priority (completion->priv->check_completion_idle, G_PRIORITY_HIGH); + g_source_set_closure (completion->priv->check_completion_idle, + g_cclosure_new_object (G_CALLBACK (check_completion_callback), + G_OBJECT (completion))); + g_source_attach (completion->priv->check_completion_idle, NULL); + } +} + +static void +connect_completion_signals (GtkEntryCompletion *completion) +{ + completion->priv->changed_id = + g_signal_connect (completion->priv->entry, "changed", + G_CALLBACK (gtk_entry_completion_changed), completion); + g_signal_connect (completion->priv->entry, "key-press-event", + G_CALLBACK (gtk_entry_completion_key_press), completion); + + completion->priv->insert_text_id = + g_signal_connect (completion->priv->entry, "insert-text", + G_CALLBACK (completion_insert_text_callback), completion); + g_signal_connect (completion->priv->entry, "notify", + G_CALLBACK (clear_completion_callback), completion); + g_signal_connect (completion->priv->entry, "activate", + G_CALLBACK (accept_completion_callback), completion); + g_signal_connect (completion->priv->entry, "focus-out-event", + G_CALLBACK (accept_completion_callback), completion); +} + +static void +set_accessible_relation (GtkWidget *window, + GtkWidget *entry) +{ + AtkObject *window_accessible; + AtkObject *entry_accessible; + + window_accessible = gtk_widget_get_accessible (window); + entry_accessible = gtk_widget_get_accessible (entry); + + atk_object_add_relationship (window_accessible, + ATK_RELATION_POPUP_FOR, + entry_accessible); +} + +static void +unset_accessible_relation (GtkWidget *window, + GtkWidget *entry) +{ + AtkObject *window_accessible; + AtkObject *entry_accessible; + + window_accessible = gtk_widget_get_accessible (window); + entry_accessible = gtk_widget_get_accessible (entry); + + atk_object_remove_relationship (window_accessible, + ATK_RELATION_POPUP_FOR, + entry_accessible); +} + +static void +disconnect_completion_signals (GtkEntryCompletion *completion) +{ + if (completion->priv->changed_id > 0 && + g_signal_handler_is_connected (completion->priv->entry, + completion->priv->changed_id)) + { + g_signal_handler_disconnect (completion->priv->entry, + completion->priv->changed_id); + completion->priv->changed_id = 0; + } + g_signal_handlers_disconnect_by_func (completion->priv->entry, + G_CALLBACK (gtk_entry_completion_key_press), completion); + if (completion->priv->insert_text_id > 0 && + g_signal_handler_is_connected (completion->priv->entry, + completion->priv->insert_text_id)) + { + g_signal_handler_disconnect (completion->priv->entry, + completion->priv->insert_text_id); + completion->priv->insert_text_id = 0; + } + g_signal_handlers_disconnect_by_func (completion->priv->entry, + G_CALLBACK (completion_insert_text_callback), completion); + g_signal_handlers_disconnect_by_func (completion->priv->entry, + G_CALLBACK (clear_completion_callback), completion); + g_signal_handlers_disconnect_by_func (completion->priv->entry, + G_CALLBACK (accept_completion_callback), completion); +} + +void +_gtk_entry_completion_disconnect (GtkEntryCompletion *completion) +{ + if (completion->priv->completion_timeout) + { + g_source_remove (completion->priv->completion_timeout); + completion->priv->completion_timeout = 0; + } + if (completion->priv->check_completion_idle) + { + g_source_destroy (completion->priv->check_completion_idle); + completion->priv->check_completion_idle = NULL; + } + + if (gtk_widget_get_mapped (completion->priv->popup_window)) + _gtk_entry_completion_popdown (completion); + + disconnect_completion_signals (completion); + + unset_accessible_relation (completion->priv->popup_window, + completion->priv->entry); + + completion->priv->entry = NULL; +} + +void +_gtk_entry_completion_connect (GtkEntryCompletion *completion, + GtkEntry *entry) +{ + completion->priv->entry = GTK_WIDGET (entry); + + set_accessible_relation (completion->priv->popup_window, + completion->priv->entry); + + connect_completion_signals (completion); +}