Support inline autocompletion in entries (#135953)

2004-07-19  Matthias Clasen  <mclasen@redhat.com>

	Support inline autocompletion in entries  (#135953)

	* gtk/gtkentryprivate.h:
	* gtk/gtkentrycompletion.h:
	* gtk/gtkentrycompletion.c (gtk_entry_completion_class_init):
	Add a new signal ::insert-prefix which can be used to override
	the default inline-completion behaviour. Add two new boolean
	properties, :popup_completion and :inline_completion which
	determine how the possible completions should be presented.
	(gtk_entry_completion_insert_prefix): New function to request
	a prefix insertion.

	* gtk/gtkentry.c: Add the necessary glue for inline completion.
This commit is contained in:
Matthias Clasen 2004-07-19 18:15:48 +00:00 committed by Matthias Clasen
parent 23b6bd5378
commit 5351a4e444
8 changed files with 511 additions and 22 deletions

View File

@ -1,3 +1,19 @@
2004-07-19 Matthias Clasen <mclasen@redhat.com>
Support inline autocompletion in entries (#135953)
* gtk/gtkentryprivate.h:
* gtk/gtkentrycompletion.h:
* gtk/gtkentrycompletion.c (gtk_entry_completion_class_init):
Add a new signal ::insert-prefix which can be used to override
the default inline-completion behaviour. Add two new boolean
properties, :popup_completion and :inline_completion which
determine how the possible completions should be presented.
(gtk_entry_completion_insert_prefix): New function to request
a prefix insertion.
* gtk/gtkentry.c: Add the necessary glue for inline completion.
Sun Jul 18 15:28:24 2004 Soeren Sandmann <sandmann@daimi.au.dk>
* gtk/gtkdnd.c (gtk_drag_source_set, gtk_drag_dest_set): Create an

View File

@ -1,3 +1,19 @@
2004-07-19 Matthias Clasen <mclasen@redhat.com>
Support inline autocompletion in entries (#135953)
* gtk/gtkentryprivate.h:
* gtk/gtkentrycompletion.h:
* gtk/gtkentrycompletion.c (gtk_entry_completion_class_init):
Add a new signal ::insert-prefix which can be used to override
the default inline-completion behaviour. Add two new boolean
properties, :popup_completion and :inline_completion which
determine how the possible completions should be presented.
(gtk_entry_completion_insert_prefix): New function to request
a prefix insertion.
* gtk/gtkentry.c: Add the necessary glue for inline completion.
Sun Jul 18 15:28:24 2004 Soeren Sandmann <sandmann@daimi.au.dk>
* gtk/gtkdnd.c (gtk_drag_source_set, gtk_drag_dest_set): Create an

View File

@ -1,3 +1,19 @@
2004-07-19 Matthias Clasen <mclasen@redhat.com>
Support inline autocompletion in entries (#135953)
* gtk/gtkentryprivate.h:
* gtk/gtkentrycompletion.h:
* gtk/gtkentrycompletion.c (gtk_entry_completion_class_init):
Add a new signal ::insert-prefix which can be used to override
the default inline-completion behaviour. Add two new boolean
properties, :popup_completion and :inline_completion which
determine how the possible completions should be presented.
(gtk_entry_completion_insert_prefix): New function to request
a prefix insertion.
* gtk/gtkentry.c: Add the necessary glue for inline completion.
Sun Jul 18 15:28:24 2004 Soeren Sandmann <sandmann@daimi.au.dk>
* gtk/gtkdnd.c (gtk_drag_source_set, gtk_drag_dest_set): Create an

View File

@ -1,3 +1,19 @@
2004-07-19 Matthias Clasen <mclasen@redhat.com>
Support inline autocompletion in entries (#135953)
* gtk/gtkentryprivate.h:
* gtk/gtkentrycompletion.h:
* gtk/gtkentrycompletion.c (gtk_entry_completion_class_init):
Add a new signal ::insert-prefix which can be used to override
the default inline-completion behaviour. Add two new boolean
properties, :popup_completion and :inline_completion which
determine how the possible completions should be presented.
(gtk_entry_completion_insert_prefix): New function to request
a prefix insertion.
* gtk/gtkentry.c: Add the necessary glue for inline completion.
Sun Jul 18 15:28:24 2004 Soeren Sandmann <sandmann@daimi.au.dk>
* gtk/gtkdnd.c (gtk_drag_source_set, gtk_drag_dest_set): Create an

View File

@ -322,6 +322,31 @@ static void get_widget_window_size (GtkEntry *entry,
gint *width,
gint *height);
/* Completion */
static gint gtk_entry_completion_timeout (gpointer data);
static gboolean gtk_entry_completion_key_press (GtkWidget *widget,
GdkEventKey *event,
gpointer user_data);
static void gtk_entry_completion_changed (GtkWidget *entry,
gpointer user_data);
static gboolean check_completion_callback (GtkEntryCompletion *completion);
static void clear_completion_callback (GtkEntry *entry,
GParamSpec *pspec);
static gboolean accept_completion_callback (GtkEntry *entry);
static void completion_insert_text_callback (GtkEntry *entry,
const gchar *text,
gint length,
gint position,
GtkEntryCompletion *completion);
static void completion_changed (GtkEntryCompletion *completion,
GParamSpec *pspec,
gpointer data);
static void disconnect_completion_signals (GtkEntry *entry,
GtkEntryCompletion *completion);
static void connect_completion_signals (GtkEntry *entry,
GtkEntryCompletion *completion);
static GtkWidgetClass *parent_class = NULL;
GType
@ -4999,6 +5024,122 @@ gtk_entry_completion_changed (GtkWidget *entry,
completion);
}
static gboolean
check_completion_callback (GtkEntryCompletion *completion)
{
completion->priv->check_completion_idle = NULL;
gtk_entry_completion_insert_prefix (completion);
return FALSE;
}
static void
clear_completion_callback (GtkEntry *entry,
GParamSpec *pspec)
{
GtkEntryCompletion *completion = gtk_entry_get_completion (entry);
completion->priv->has_completion = FALSE;
}
static gboolean
accept_completion_callback (GtkEntry *entry)
{
GtkEntryCompletion *completion = gtk_entry_get_completion (entry);
if (completion->priv->has_completion)
gtk_editable_set_position (GTK_EDITABLE (entry),
entry->text_length);
return FALSE;
}
static void
completion_insert_text_callback (GtkEntry *entry,
const gchar *text,
gint length,
gint position,
GtkEntryCompletion *completion)
{
/* 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
completion_changed (GtkEntryCompletion *completion,
GParamSpec *pspec,
gpointer data)
{
GtkEntry *entry = GTK_ENTRY (data);
disconnect_completion_signals (entry, completion);
connect_completion_signals (entry, completion);
}
static void
disconnect_completion_signals (GtkEntry *entry,
GtkEntryCompletion *completion)
{
g_signal_handlers_disconnect_by_func (completion,
G_CALLBACK (completion_changed), entry);
if (completion->priv->changed_id > 0 &&
g_signal_handler_is_connected (entry, completion->priv->changed_id))
g_signal_handler_disconnect (entry, completion->priv->changed_id);
g_signal_handlers_disconnect_by_func (entry,
G_CALLBACK (gtk_entry_completion_key_press), completion);
if (completion->priv->insert_text_id > 0 &&
g_signal_handler_is_connected (entry, completion->priv->insert_text_id))
g_signal_handler_disconnect (entry, completion->priv->insert_text_id);
g_signal_handlers_disconnect_by_func (entry,
G_CALLBACK (completion_insert_text_callback), completion);
g_signal_handlers_disconnect_by_func (entry,
G_CALLBACK (clear_completion_callback), completion);
g_signal_handlers_disconnect_by_func (entry,
G_CALLBACK (accept_completion_callback), completion);
}
static void
connect_completion_signals (GtkEntry *entry,
GtkEntryCompletion *completion)
{
if (completion->priv->popup_completion)
{
completion->priv->changed_id =
g_signal_connect (entry, "changed",
G_CALLBACK (gtk_entry_completion_changed), completion);
g_signal_connect (entry, "key_press_event",
G_CALLBACK (gtk_entry_completion_key_press), completion);
}
if (completion->priv->inline_completion)
{
completion->priv->insert_text_id =
g_signal_connect (entry, "insert_text",
G_CALLBACK (completion_insert_text_callback), completion);
g_signal_connect (entry, "notify::cursor-position",
G_CALLBACK (clear_completion_callback), completion);
g_signal_connect (entry, "notify::selection-bound",
G_CALLBACK (clear_completion_callback), completion);
g_signal_connect (entry, "activate",
G_CALLBACK (accept_completion_callback), completion);
g_signal_connect (entry, "focus_out_event",
G_CALLBACK (accept_completion_callback), completion);
}
g_signal_connect (completion, "notify::popup_completion",
G_CALLBACK (completion_changed), entry);
g_signal_connect (completion, "notify::inline_completion",
G_CALLBACK (completion_changed), entry);
}
/**
* gtk_entry_set_completion:
* @entry: A #GtkEntry.
@ -5035,14 +5176,7 @@ gtk_entry_set_completion (GtkEntry *entry,
if (GTK_WIDGET_MAPPED (old->priv->popup_window))
_gtk_entry_completion_popdown (old);
gtk_cell_layout_clear (GTK_CELL_LAYOUT (old));
old->priv->text_column = -1;
if (g_signal_handler_is_connected (entry, old->priv->changed_id))
g_signal_handler_disconnect (entry, old->priv->changed_id);
if (g_signal_handler_is_connected (entry, old->priv->key_press_id))
g_signal_handler_disconnect (entry, old->priv->key_press_id);
disconnect_completion_signals (entry, old);
old->priv->entry = NULL;
g_object_unref (old);
@ -5057,14 +5191,7 @@ gtk_entry_set_completion (GtkEntry *entry,
/* hook into the entry */
g_object_ref (completion);
completion->priv->changed_id =
g_signal_connect (entry, "changed",
G_CALLBACK (gtk_entry_completion_changed), completion);
completion->priv->key_press_id =
g_signal_connect (entry, "key_press_event",
G_CALLBACK (gtk_entry_completion_key_press), completion);
connect_completion_signals (entry, completion);
completion->priv->entry = GTK_WIDGET (entry);
g_object_set_data (G_OBJECT (entry), GTK_ENTRY_COMPLETION_KEY, completion);
}

View File

@ -41,6 +41,7 @@
/* signals */
enum
{
INSERT_PREFIX,
MATCH_SELECTED,
ACTION_ACTIVATED,
LAST_SIGNAL
@ -52,7 +53,9 @@ enum
PROP_0,
PROP_MODEL,
PROP_MINIMUM_KEY_LENGTH,
PROP_TEXT_COLUMN
PROP_TEXT_COLUMN,
PROP_INLINE_COMPLETION,
PROP_POPUP_COMPLETION
};
#define GTK_ENTRY_COMPLETION_GET_PRIVATE(obj)(G_TYPE_INSTANCE_GET_PRIVATE ((obj), GTK_TYPE_ENTRY_COMPLETION, GtkEntryCompletionPrivate))
@ -128,6 +131,8 @@ static void gtk_entry_completion_action_data_func (GtkTreeViewColumn
static gboolean gtk_entry_completion_match_selected (GtkEntryCompletion *completion,
GtkTreeModel *model,
GtkTreeIter *iter);
static gboolean gtk_entry_completion_real_insert_prefix (GtkEntryCompletion *completion,
gchar *key);
static GObjectClass *parent_class = NULL;
static guint entry_completion_signals[LAST_SIGNAL] = { 0 };
@ -185,6 +190,35 @@ gtk_entry_completion_class_init (GtkEntryCompletionClass *klass)
object_class->finalize = gtk_entry_completion_finalize;
klass->match_selected = gtk_entry_completion_match_selected;
klass->insert_prefix = gtk_entry_completion_real_insert_prefix;
/**
* GtkEntryCompletion::insert-prefix:
* @widget: the object which received the signal
* @prefix: the common prefix of all possible completions
*
* The ::insert-prefix signal is 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
* next '/'.
*
* Return value: %TRUE if the signal has been handled
*
* Since: 2.6
*/
entry_completion_signals[INSERT_PREFIX] =
g_signal_new ("insert_prefix",
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET (GtkEntryCompletionClass, insert_prefix),
_gtk_boolean_handled_accumulator, NULL,
_gtk_marshal_BOOLEAN__STRING,
G_TYPE_BOOLEAN, 1,
G_TYPE_STRING);
/**
* GtkEntryCompletion::match-selected:
@ -198,6 +232,8 @@ gtk_entry_completion_class_init (GtkEntryCompletionClass *klass)
* @iter.
*
* Return value: %TRUE if the signal has been handled
*
* Since: 2.4
*/
entry_completion_signals[MATCH_SELECTED] =
g_signal_new ("match_selected",
@ -217,6 +253,8 @@ gtk_entry_completion_class_init (GtkEntryCompletionClass *klass)
*
* The ::action-activated signal is emitted when an action
* is activated.
*
* Since: 2.4
*/
entry_completion_signals[ACTION_ACTIVATED] =
g_signal_new ("action_activated",
@ -245,7 +283,7 @@ gtk_entry_completion_class_init (GtkEntryCompletionClass *klass)
1,
G_PARAM_READWRITE));
/**
* GtkEntryCompletion::text-column:
* GtkEntryCompletion:text-column:
*
* The column of the model containing the strings.
*
@ -261,6 +299,38 @@ gtk_entry_completion_class_init (GtkEntryCompletionClass *klass)
-1,
G_PARAM_READWRITE));
/**
* GtkEntryCompletion:inline_completion:
*
* The boolean :inline_completion property determines whether the
* common prefix of the possible completions should be inserted
* automatically in the entry.
*
* 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,
G_PARAM_READWRITE));
/**
* GtkEntryCompletion:popup_completion:
*
* The boolean :popup_completion property 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,
G_PARAM_READWRITE));
g_type_class_add_private (object_class, sizeof (GtkEntryCompletionPrivate));
}
@ -289,6 +359,9 @@ gtk_entry_completion_init (GtkEntryCompletion *completion)
priv->minimum_key_length = 1;
priv->text_column = -1;
priv->has_completion = FALSE;
priv->inline_completion = FALSE;
priv->popup_completion = TRUE;
/* completions */
priv->filter_model = NULL;
@ -410,6 +483,14 @@ gtk_entry_completion_set_property (GObject *object,
priv->text_column = g_value_get_int (value);
break;
case PROP_INLINE_COMPLETION:
priv->inline_completion = g_value_get_boolean (value);
break;
case PROP_POPUP_COMPLETION:
priv->popup_completion = g_value_get_boolean (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
@ -439,6 +520,14 @@ gtk_entry_completion_get_property (GObject *object,
g_value_set_int (value, gtk_entry_completion_get_text_column (completion));
break;
case PROP_INLINE_COMPLETION:
g_value_set_boolean (value, gtk_entry_completion_get_inline_completion (completion));
break;
case PROP_POPUP_COMPLETION:
g_value_set_boolean (value, gtk_entry_completion_get_popup_completion (completion));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
@ -1320,3 +1409,199 @@ gtk_entry_completion_match_selected (GtkEntryCompletion *completion,
return TRUE;
}
static gchar *
gtk_entry_completion_compute_prefix (GtkEntryCompletion *completion)
{
GtkTreeIter iter;
gchar *prefix = NULL;
gboolean valid;
const gchar *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);
while (valid)
{
gchar *text;
gtk_tree_model_get (GTK_TREE_MODEL (completion->priv->filter_model),
&iter, completion->priv->text_column, &text,
-1);
if (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';
}
}
g_free (text);
valid = gtk_tree_model_iter_next (GTK_TREE_MODEL (completion->priv->filter_model),
&iter);
}
return prefix;
}
static gboolean
gtk_entry_completion_real_insert_prefix (GtkEntryCompletion *completion,
gchar *prefix)
{
if (prefix)
{
gint key_len;
gint prefix_len;
gint pos;
const gchar *key;
prefix_len = g_utf8_strlen (prefix, -1);
key = gtk_entry_get_text (GTK_ENTRY (completion->priv->entry));
key_len = g_utf8_strlen (key, -1);
if (prefix_len > key_len)
{
gtk_editable_insert_text (GTK_EDITABLE (completion->priv->entry),
prefix + key_len, -1, &pos);
gtk_editable_select_region (GTK_EDITABLE (completion->priv->entry),
key_len, prefix_len);
completion->priv->has_completion = TRUE;
}
}
return TRUE;
}
/**
* gtk_entry_completion_insert_prefix:
* @completion: a #GtkEntryCompletion
*
* 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 (prefix)
{
g_signal_emit (completion, entry_completion_signals[INSERT_PREFIX],
0, prefix, &done);
g_free (prefix);
}
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
gtk_entry_completion_set_inline_completion (GtkEntryCompletion *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)
{
completion->priv->inline_completion = inline_completion;
g_object_notify (G_OBJECT (completion), "inline_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;
}
/**
* gtk_entry_completion_set_popup_completion:
* @completion: a #GtkEntryCompletion
* @inline_completion: %TRUE to do popup completion
*
* Sets whether the completions should be presented in a popup window.
*
* Since: 2.6
**/
void
gtk_entry_completion_set_popup_completion (GtkEntryCompletion *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)
{
completion->priv->popup_completion = popup_completion;
g_object_notify (G_OBJECT (completion), "popup_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;
}

View File

@ -63,12 +63,13 @@ struct _GtkEntryCompletionClass
GtkTreeIter *iter);
void (* action_activated) (GtkEntryCompletion *completion,
gint index_);
gboolean (* insert_prefix) (GtkEntryCompletion *completion,
gchar *prefix);
/* Padding for future expansion */
void (*_gtk_reserved0) (void);
void (*_gtk_reserved1) (void);
void (*_gtk_reserved2) (void);
void (*_gtk_reserved3) (void);
};
/* core */
@ -89,6 +90,7 @@ void gtk_entry_completion_set_minimum_key_length (GtkEntryComplet
gint length);
gint gtk_entry_completion_get_minimum_key_length (GtkEntryCompletion *completion);
void gtk_entry_completion_complete (GtkEntryCompletion *completion);
void gtk_entry_completion_insert_prefix (GtkEntryCompletion *completion);
void gtk_entry_completion_insert_action_text (GtkEntryCompletion *completion,
gint index_,
@ -99,6 +101,13 @@ void gtk_entry_completion_insert_action_markup (GtkEntryComplet
void gtk_entry_completion_delete_action (GtkEntryCompletion *completion,
gint index_);
void gtk_entry_completion_set_inline_completion (GtkEntryCompletion *completion,
gboolean inline_completion);
gboolean gtk_entry_completion_get_inline_completion (GtkEntryCompletion *completion);
void gtk_entry_completion_set_popup_completion (GtkEntryCompletion *completion,
gboolean popup_completion);
gboolean gtk_entry_completion_get_popup_completion (GtkEntryCompletion *completion);
/* convenience */
void gtk_entry_completion_set_text_column (GtkEntryCompletion *completion,
gint column);

View File

@ -55,10 +55,14 @@ struct _GtkEntryCompletionPrivate
gulong completion_timeout;
gulong changed_id;
gulong key_press_id;
gulong key_release_id;
gulong insert_text_id;
gboolean ignore_enter;
guint ignore_enter : 1;
guint has_completion : 1;
guint inline_completion : 1;
guint popup_completion : 1;
GSource *check_completion_idle;
};
gboolean _gtk_entry_completion_resize_popup (GtkEntryCompletion *completion);