From 7325f8e2048152974a16107dfa749b171c9fa374 Mon Sep 17 00:00:00 2001 From: Matthias Clasen Date: Wed, 13 Feb 2019 20:16:33 -0500 Subject: [PATCH 01/34] editable: Add more to interface Add all the things to the GtkEditable interface that make sense for most implementations. --- docs/reference/gtk/gtk4-sections.txt | 17 +- gtk/gtkeditable.c | 884 +++++++++++++++++++++------ gtk/gtkeditable.h | 147 +++-- 3 files changed, 798 insertions(+), 250 deletions(-) diff --git a/docs/reference/gtk/gtk4-sections.txt b/docs/reference/gtk/gtk4-sections.txt index 3ac0b3b165..06e6cb7991 100644 --- a/docs/reference/gtk/gtk4-sections.txt +++ b/docs/reference/gtk/gtk4-sections.txt @@ -844,19 +844,24 @@ gtk_drawing_area_get_type gtkeditable GtkEditable GtkEditable -gtk_editable_select_region -gtk_editable_get_selection_bounds +gtk_editable_get_text +gtk_editable_set_text +gtk_editable_get_chars gtk_editable_insert_text gtk_editable_delete_text -gtk_editable_get_chars -gtk_editable_cut_clipboard -gtk_editable_copy_clipboard -gtk_editable_paste_clipboard +gtk_editable_get_selection_bounds +gtk_editable_select_region gtk_editable_delete_selection gtk_editable_set_position gtk_editable_get_position gtk_editable_set_editable gtk_editable_get_editable +gtk_editable_set_alignment +gtk_editable_get_alignment +gtk_editable_get_width_chars +gtk_editable_set_width_chars +gtk_editable_get_max_width_chars +gtk_editable_set_max_width_chars GTK_EDITABLE GTK_IS_EDITABLE diff --git a/gtk/gtkeditable.c b/gtk/gtkeditable.c index 50d0df44ba..3ebc45d159 100644 --- a/gtk/gtkeditable.c +++ b/gtk/gtkeditable.c @@ -44,12 +44,12 @@ * * void * insert_text_handler (GtkEditable *editable, - * const gchar *text, - * gint length, - * gint *position, + * const char *text, + * int length, + * int *position, * gpointer data) * { - * gchar *result = g_utf8_strup (text, length); + * char *result = g_utf8_strup (text, length); * * g_signal_handlers_block_by_func (editable, * (gpointer) insert_text_handler, data); @@ -68,129 +68,267 @@ #include #include "gtkeditable.h" +#include "gtkentrybuffer.h" #include "gtkmarshalers.h" #include "gtkintl.h" +#include "gtkprivate.h" +G_DEFINE_INTERFACE (GtkEditable, gtk_editable, GTK_TYPE_WIDGET) -static void gtk_editable_base_init (gpointer g_class); +static GQuark quark_editable_data; - -GType -gtk_editable_get_type (void) +static GtkEditable * +get_delegate (GtkEditable *editable) { - static GType editable_type = 0; + GtkEditableInterface *iface = GTK_EDITABLE_GET_IFACE (editable); - if (!editable_type) - { - const GTypeInfo editable_info = - { - sizeof (GtkEditableInterface), /* class_size */ - gtk_editable_base_init, /* base_init */ - NULL, /* base_finalize */ - }; + if (iface->get_delegate) + return iface->get_delegate (editable); - editable_type = g_type_register_static (G_TYPE_INTERFACE, I_("GtkEditable"), - &editable_info, 0); - } - - return editable_type; + return NULL; } static void -gtk_editable_base_init (gpointer g_class) +gtk_editable_default_do_insert_text (GtkEditable *editable, + const char *text, + int length, + int *position) { - static gboolean initialized = FALSE; + g_signal_emit_by_name (editable, "insert-text", text, length, position); +} - if (! initialized) - { - /** - * GtkEditable::insert-text: - * @editable: the object which received the signal - * @new_text: the new text to insert - * @new_text_length: the length of the new text, in bytes, - * or -1 if new_text is nul-terminated - * @position: (inout) (type int): the position, in characters, - * at which to insert the new text. this is an in-out - * parameter. After the signal emission is finished, it - * should point after the newly inserted text. - * - * This signal is emitted when text is inserted into - * the widget by the user. The default handler for - * this signal will normally be responsible for inserting - * the text, so by connecting to this signal and then - * stopping the signal with g_signal_stop_emission(), it - * is possible to modify the inserted text, or prevent - * it from being inserted entirely. - */ - g_signal_new (I_("insert-text"), - GTK_TYPE_EDITABLE, - G_SIGNAL_RUN_LAST, - G_STRUCT_OFFSET (GtkEditableInterface, insert_text), - NULL, NULL, - _gtk_marshal_VOID__STRING_INT_POINTER, - G_TYPE_NONE, 3, - G_TYPE_STRING, - G_TYPE_INT, - G_TYPE_POINTER); +#define warn_no_delegate(func) \ + g_critical ("GtkEditable %s: default implementation called without a delegate", func); - /** - * GtkEditable::delete-text: - * @editable: the object which received the signal - * @start_pos: the starting position - * @end_pos: the end position - * - * This signal is emitted when text is deleted from - * the widget by the user. The default handler for - * this signal will normally be responsible for deleting - * the text, so by connecting to this signal and then - * stopping the signal with g_signal_stop_emission(), it - * is possible to modify the range of deleted text, or - * prevent it from being deleted entirely. The @start_pos - * and @end_pos parameters are interpreted as for - * gtk_editable_delete_text(). - */ - g_signal_new (I_("delete-text"), - GTK_TYPE_EDITABLE, - G_SIGNAL_RUN_LAST, - G_STRUCT_OFFSET (GtkEditableInterface, delete_text), - NULL, NULL, - _gtk_marshal_VOID__INT_INT, - G_TYPE_NONE, 2, - G_TYPE_INT, - G_TYPE_INT); - /** - * GtkEditable::changed: - * @editable: the object which received the signal - * - * The ::changed signal is emitted at the end of a single - * user-visible operation on the contents of the #GtkEditable. - * - * E.g., a paste operation that replaces the contents of the - * selection will cause only one signal emission (even though it - * is implemented by first deleting the selection, then inserting - * the new content, and may cause multiple ::notify::text signals - * to be emitted). - */ - g_signal_new (I_("changed"), - GTK_TYPE_EDITABLE, - G_SIGNAL_RUN_LAST, - G_STRUCT_OFFSET (GtkEditableInterface, changed), - NULL, NULL, - NULL, - G_TYPE_NONE, 0); +static void +gtk_editable_default_insert_text (GtkEditable *editable, + const char *text, + int length, + int *position) +{ + GtkEditable *delegate = get_delegate (editable); - initialized = TRUE; - } + if (delegate) + gtk_editable_insert_text (delegate, text, length, position); + else + warn_no_delegate ("insert_text"); +} + +static void +gtk_editable_default_do_delete_text (GtkEditable *editable, + int start_pos, + int end_pos) +{ + g_signal_emit_by_name (editable, "delete-text", start_pos, end_pos); +} + +static void +gtk_editable_default_delete_text (GtkEditable *editable, + int start_pos, + int end_pos) +{ + GtkEditable *delegate = get_delegate (editable); + + if (delegate) + gtk_editable_delete_text (delegate, start_pos, end_pos); + else + warn_no_delegate ("delete_text"); +} + +static const char * +gtk_editable_default_get_text (GtkEditable *editable) +{ + GtkEditable *delegate = get_delegate (editable); + + if (delegate) + return gtk_editable_get_text (delegate); + else + warn_no_delegate ("get_text"); + + return NULL; +} + +static void +gtk_editable_default_set_selection_bounds (GtkEditable *editable, + int start_pos, + int end_pos) +{ + GtkEditable *delegate = get_delegate (editable); + + if (delegate) + gtk_editable_select_region (delegate, start_pos, end_pos); + else + warn_no_delegate ("select_region"); +} + +static gboolean +gtk_editable_default_get_selection_bounds (GtkEditable *editable, + int *start_pos, + int *end_pos) +{ + GtkEditable *delegate = get_delegate (editable); + + if (delegate) + return gtk_editable_get_selection_bounds (delegate, start_pos, end_pos); + else + warn_no_delegate ("select_region"); + + return FALSE; +} + +static void +gtk_editable_default_init (GtkEditableInterface *iface) +{ + quark_editable_data = g_quark_from_static_string ("GtkEditable-data"); + + iface->insert_text = gtk_editable_default_insert_text; + iface->delete_text = gtk_editable_default_delete_text; + iface->get_text = gtk_editable_default_get_text; + iface->do_insert_text = gtk_editable_default_do_insert_text; + iface->do_delete_text = gtk_editable_default_do_delete_text; + iface->get_selection_bounds = gtk_editable_default_get_selection_bounds; + iface->set_selection_bounds = gtk_editable_default_set_selection_bounds; + + /** + * GtkEditable::insert-text: + * @editable: the object which received the signal + * @text: the new text to insert + * @length: the length of the new text, in bytes, + * or -1 if new_text is nul-terminated + * @position: (inout) (type int): the position, in characters, + * at which to insert the new text. this is an in-out + * parameter. After the signal emission is finished, it + * should point after the newly inserted text. + * + * This signal is emitted when text is inserted into + * the widget by the user. The default handler for + * this signal will normally be responsible for inserting + * the text, so by connecting to this signal and then + * stopping the signal with g_signal_stop_emission(), it + * is possible to modify the inserted text, or prevent + * it from being inserted entirely. + */ + g_signal_new (I_("insert-text"), + GTK_TYPE_EDITABLE, + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GtkEditableInterface, insert_text), + NULL, NULL, + _gtk_marshal_VOID__STRING_INT_POINTER, + G_TYPE_NONE, 3, + G_TYPE_STRING, + G_TYPE_INT, + G_TYPE_POINTER); + + /** + * GtkEditable::delete-text: + * @editable: the object which received the signal + * @start_pos: the starting position + * @end_pos: the end position + * + * This signal is emitted when text is deleted from + * the widget by the user. The default handler for + * this signal will normally be responsible for deleting + * the text, so by connecting to this signal and then + * stopping the signal with g_signal_stop_emission(), it + * is possible to modify the range of deleted text, or + * prevent it from being deleted entirely. The @start_pos + * and @end_pos parameters are interpreted as for + * gtk_editable_delete_text(). + */ + g_signal_new (I_("delete-text"), + GTK_TYPE_EDITABLE, + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GtkEditableInterface, delete_text), + NULL, NULL, + _gtk_marshal_VOID__INT_INT, + G_TYPE_NONE, 2, + G_TYPE_INT, + G_TYPE_INT); + + /** + * GtkEditable::changed: + * @editable: the object which received the signal + * + * The ::changed signal is emitted at the end of a single + * user-visible operation on the contents of the #GtkEditable. + * + * E.g., a paste operation that replaces the contents of the + * selection will cause only one signal emission (even though it + * is implemented by first deleting the selection, then inserting + * the new content, and may cause multiple ::notify::text signals + * to be emitted). + */ + g_signal_new (I_("changed"), + GTK_TYPE_EDITABLE, + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GtkEditableInterface, changed), + NULL, NULL, + NULL, + G_TYPE_NONE, 0); + + g_object_interface_install_property (iface, + g_param_spec_string ("text", + P_("Text"), + P_("The contents of the entry"), + "", + GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY)); + + g_object_interface_install_property (iface, + g_param_spec_int ("cursor-position", + P_("Cursor Position"), + P_("The current position of the insertion cursor in chars"), + 0, GTK_ENTRY_BUFFER_MAX_SIZE, + 0, + GTK_PARAM_READABLE)); + + g_object_interface_install_property (iface, + g_param_spec_int ("selection-bound", + P_("Selection Bound"), + P_("The position of the opposite end of the selection from the cursor in chars"), + 0, GTK_ENTRY_BUFFER_MAX_SIZE, + 0, + GTK_PARAM_READABLE)); + + g_object_interface_install_property (iface, + g_param_spec_boolean ("editable", + P_("Editable"), + P_("Whether the entry contents can be edited"), + TRUE, + GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY)); + + g_object_interface_install_property (iface, + g_param_spec_int ("width-chars", + P_("Width in chars"), + P_("Number of characters to leave space for in the entry"), + -1, G_MAXINT, + -1, + GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY)); + + g_object_interface_install_property (iface, + g_param_spec_int ("max-width-chars", + P_("Maximum width in characters"), + P_("The desired maximum width of the entry, in characters"), + -1, G_MAXINT, + -1, + GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY)); + + g_object_interface_install_property (iface, + g_param_spec_float ("xalign", + P_("X align"), + P_("The horizontal alignment, from 0 (left) to 1 (right). Reversed for RTL layouts."), + 0.0, 1.0, + 0.0, + GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY)); } /** * gtk_editable_insert_text: (virtual do_insert_text) * @editable: a #GtkEditable - * @new_text: the text to append - * @new_text_length: the length of the text in bytes, or -1 + * @text: the text to append + * @length: the length of the text in bytes, or -1 * @position: (inout): location of the position text will be inserted at * - * Inserts @new_text_length bytes of @new_text into the contents of the + * Inserts @length bytes of @text into the contents of the * widget, at position @position. * * Note that the position is in characters, not in bytes. @@ -198,17 +336,17 @@ gtk_editable_base_init (gpointer g_class) */ void gtk_editable_insert_text (GtkEditable *editable, - const gchar *new_text, - gint new_text_length, - gint *position) + const char *text, + int length, + int *position) { g_return_if_fail (GTK_IS_EDITABLE (editable)); g_return_if_fail (position != NULL); - if (new_text_length < 0) - new_text_length = strlen (new_text); + if (length < 0) + length = strlen (text); - GTK_EDITABLE_GET_IFACE (editable)->do_insert_text (editable, new_text, new_text_length, position); + GTK_EDITABLE_GET_IFACE (editable)->do_insert_text (editable, text, length, position); } /** @@ -226,8 +364,8 @@ gtk_editable_insert_text (GtkEditable *editable, */ void gtk_editable_delete_text (GtkEditable *editable, - gint start_pos, - gint end_pos) + int start_pos, + int end_pos) { g_return_if_fail (GTK_IS_EDITABLE (editable)); @@ -247,18 +385,73 @@ gtk_editable_delete_text (GtkEditable *editable, * * Note that positions are specified in characters, not bytes. * - * Returns: a pointer to the contents of the widget as a + * Returns: (transfer full): a pointer to the contents of the widget as a * string. This string is allocated by the #GtkEditable * implementation and should be freed by the caller. */ -gchar * +char * gtk_editable_get_chars (GtkEditable *editable, - gint start_pos, - gint end_pos) + int start_pos, + int end_pos) +{ + const char *text; + int length; + int start_index,end_index; + + g_return_val_if_fail (GTK_IS_EDITABLE (editable), NULL); + + text = GTK_EDITABLE_GET_IFACE (editable)->get_text (editable); + length = g_utf8_strlen (text, -1); + + if (end_pos < 0) + end_pos = length; + + start_pos = MIN (length, start_pos); + end_pos = MIN (length, end_pos); + + start_index = g_utf8_offset_to_pointer (text, start_pos) - text; + end_index = g_utf8_offset_to_pointer (text, end_pos) - text; + + return g_strndup (text + start_index, end_index - start_index); +} + +/** + * gtk_editable_get_text: + * @editable: a #GtkEditable + * + * Retrieves the contents of @editable. The returned string is + * owned by GTK and must not be modified or freed. + * + * Returns: (transfer none): a pointer to the contents of the editable. + */ +const char * +gtk_editable_get_text (GtkEditable *editable) { g_return_val_if_fail (GTK_IS_EDITABLE (editable), NULL); - return GTK_EDITABLE_GET_IFACE (editable)->get_chars (editable, start_pos, end_pos); + return GTK_EDITABLE_GET_IFACE (editable)->get_text (editable); +} + +/** + * gtk_editable_set_text: + * @editable: a #GtkEditable + * + * Sets the text in the editable to the given value, + * replacing the current contents. + */ +void +gtk_editable_set_text (GtkEditable *editable, + const char *text) +{ + int pos; + + g_return_if_fail (GTK_IS_EDITABLE (editable)); + + g_object_freeze_notify (G_OBJECT (editable)); + gtk_editable_delete_text (editable, 0, -1); + pos = 0; + gtk_editable_insert_text (editable, text, -1, &pos); + g_object_thaw_notify (G_OBJECT (editable)); } /** @@ -275,12 +468,12 @@ gtk_editable_get_chars (GtkEditable *editable, * of the editable. Note that @position is in characters, not in bytes. */ void -gtk_editable_set_position (GtkEditable *editable, - gint position) +gtk_editable_set_position (GtkEditable *editable, + int position) { g_return_if_fail (GTK_IS_EDITABLE (editable)); - GTK_EDITABLE_GET_IFACE (editable)->set_position (editable, position); + GTK_EDITABLE_GET_IFACE (editable)->set_selection_bounds (editable, position, position); } /** @@ -294,12 +487,16 @@ gtk_editable_set_position (GtkEditable *editable, * * Returns: the cursor position */ -gint +int gtk_editable_get_position (GtkEditable *editable) { + int start, end; + g_return_val_if_fail (GTK_IS_EDITABLE (editable), 0); - return GTK_EDITABLE_GET_IFACE (editable)->get_position (editable); + GTK_EDITABLE_GET_IFACE (editable)->get_selection_bounds (editable, &start, &end); + + return end; } /** @@ -308,20 +505,22 @@ gtk_editable_get_position (GtkEditable *editable) * @start_pos: (out) (allow-none): location to store the starting position, or %NULL * @end_pos: (out) (allow-none): location to store the end position, or %NULL * - * Retrieves the selection bound of the editable. start_pos will be filled - * with the start of the selection and @end_pos with end. If no text was - * selected both will be identical and %FALSE will be returned. + * Retrieves the selection bound of the editable. + * + * @start_pos will be filled with the start of the selection and + * @end_pos with end. If no text was selected both will be identical + * and %FALSE will be returned. * * Note that positions are specified in characters, not bytes. * - * Returns: %TRUE if an area is selected, %FALSE otherwise + * Returns: %TRUE if there is a non-empty selection, %FALSE otherwise */ gboolean gtk_editable_get_selection_bounds (GtkEditable *editable, - gint *start_pos, - gint *end_pos) + int *start_pos, + int *end_pos) { - gint tmp_start, tmp_end; + int tmp_start, tmp_end; gboolean result; g_return_val_if_fail (GTK_IS_EDITABLE (editable), FALSE); @@ -346,7 +545,7 @@ gtk_editable_get_selection_bounds (GtkEditable *editable, void gtk_editable_delete_selection (GtkEditable *editable) { - gint start, end; + int start, end; g_return_if_fail (GTK_IS_EDITABLE (editable)); @@ -360,106 +559,401 @@ gtk_editable_delete_selection (GtkEditable *editable) * @start_pos: start of region * @end_pos: end of region * - * Selects a region of text. The characters that are selected are - * those characters at positions from @start_pos up to, but not - * including @end_pos. If @end_pos is negative, then the - * characters selected are those characters from @start_pos to - * the end of the text. + * Selects a region of text. + * + * The characters that are selected are those characters at positions + * from @start_pos up to, but not including @end_pos. If @end_pos is + * negative, then the characters selected are those characters from + * @start_pos to the end of the text. * * Note that positions are specified in characters, not bytes. */ void gtk_editable_select_region (GtkEditable *editable, - gint start_pos, - gint end_pos) + int start_pos, + int end_pos) { g_return_if_fail (GTK_IS_EDITABLE (editable)); GTK_EDITABLE_GET_IFACE (editable)->set_selection_bounds (editable, start_pos, end_pos); } -/** - * gtk_editable_cut_clipboard: - * @editable: a #GtkEditable - * - * Removes the contents of the currently selected content in the editable and - * puts it on the clipboard. - */ -void -gtk_editable_cut_clipboard (GtkEditable *editable) -{ - g_return_if_fail (GTK_IS_EDITABLE (editable)); - - g_signal_emit_by_name (editable, "cut-clipboard"); -} - -/** - * gtk_editable_copy_clipboard: - * @editable: a #GtkEditable - * - * Copies the contents of the currently selected content in the editable and - * puts it on the clipboard. - */ -void -gtk_editable_copy_clipboard (GtkEditable *editable) -{ - g_return_if_fail (GTK_IS_EDITABLE (editable)); - - g_signal_emit_by_name (editable, "copy-clipboard"); -} - -/** - * gtk_editable_paste_clipboard: - * @editable: a #GtkEditable - * - * Pastes the content of the clipboard to the current position of the - * cursor in the editable. - */ -void -gtk_editable_paste_clipboard (GtkEditable *editable) -{ - g_return_if_fail (GTK_IS_EDITABLE (editable)); - - g_signal_emit_by_name (editable, "paste-clipboard"); -} - /** * gtk_editable_set_editable: * @editable: a #GtkEditable * @is_editable: %TRUE if the user is allowed to edit the text * in the widget * - * Determines if the user can edit the text in the editable - * widget or not. + * Determines if the user can edit the text + * in the editable widget or not. */ void -gtk_editable_set_editable (GtkEditable *editable, - gboolean is_editable) +gtk_editable_set_editable (GtkEditable *editable, + gboolean is_editable) { g_return_if_fail (GTK_IS_EDITABLE (editable)); - g_object_set (editable, - "editable", is_editable != FALSE, - NULL); + g_object_set (editable, "editable", is_editable, NULL); } /** * gtk_editable_get_editable: * @editable: a #GtkEditable * - * Retrieves whether @editable is editable. See - * gtk_editable_set_editable(). + * Retrieves whether @editable is editable. + * See gtk_editable_set_editable(). * * Returns: %TRUE if @editable is editable. */ gboolean gtk_editable_get_editable (GtkEditable *editable) { - gboolean value; + gboolean is_editable; g_return_val_if_fail (GTK_IS_EDITABLE (editable), FALSE); - g_object_get (editable, "editable", &value, NULL); + g_object_get (editable, "editable", &is_editable, NULL); - return value; + return is_editable; } + + +/** + * gtk_editable_get_alignment: + * @editable: a #GtkEditable + * + * Gets the value set by gtk_editable_set_alignment(). + * + * Returns: the alignment + **/ +float +gtk_editable_get_alignment (GtkEditable *editable) +{ + float xalign; + + g_return_val_if_fail (GTK_IS_EDITABLE (editable), 0); + + g_object_get (editable, "xalign", &xalign, NULL); + + return xalign; +} + +/** + * gtk_editable_set_alignment: + * @editable: a #GtkEditable + * @xalign: The horizontal alignment, from 0 (left) to 1 (right). + * Reversed for RTL layouts + * + * Sets the alignment for the contents of the editable. + * + * This controls the horizontal positioning of the contents when + * the displayed text is shorter than the width of the editable. + */ +void +gtk_editable_set_alignment (GtkEditable *editable, + float xalign) +{ + g_return_if_fail (GTK_IS_EDITABLE (editable)); + + g_object_set (editable, "xalign", xalign, NULL); +} + +/** + * gtk_editable_get_width_chars: + * @editable: a #GtkEditable + * + * Gets the value set by gtk_editable_set_width_chars(). + * + * Returns: number of chars to request space for, or negative if unset + **/ +int +gtk_editable_get_width_chars (GtkEditable *editable) +{ + int width_chars; + + g_return_val_if_fail (GTK_IS_EDITABLE (editable), 0); + + g_object_get (editable, "width-chars", &width_chars, NULL); + + return width_chars; +} + +/** + * gtk_editable_set_width_chars: + * @editable: a #GtkEditable + * @n_chars: width in chars + * + * Changes the size request of the editable to be about the + * right size for @n_chars characters. + * + * Note that it changes the size request, the size can still + * be affected by how you pack the widget into containers. + * If @n_chars is -1, the size reverts to the default size. + **/ +void +gtk_editable_set_width_chars (GtkEditable *editable, + int n_chars) +{ + g_return_if_fail (GTK_IS_EDITABLE (editable)); + + g_object_set (editable, "width-chars", n_chars, NULL); +} + +/** + * gtk_editable_get_max_width_chars: + * @editable: a #GtkEditable + * + * Retrieves the desired maximum width of @editable, in characters. + * See gtk_editable_set_max_width_chars(). + * + * Returns: the maximum width of the entry, in characters + */ +int +gtk_editable_get_max_width_chars (GtkEditable *editable) +{ + int max_width_chars; + + g_return_val_if_fail (GTK_IS_EDITABLE (editable), 0); + + g_object_get (editable, "max-width-chars", &max_width_chars, NULL); + + return max_width_chars; +} + +/** + * gtk_editable_set_max_width_chars: + * @editable: a #GtkEditable + * @n_chars: the new desired maximum width, in characters + * + * Sets the desired maximum width in characters of @editable. + */ +void +gtk_editable_set_max_width_chars (GtkEditable *editable, + int n_chars) +{ + g_return_if_fail (GTK_IS_EDITABLE (editable)); + + g_object_set (editable, "max-width-chars", n_chars, NULL); +} + +/** + * gtk_editable_install_properties: + * @object_class: a #GObjectClass + * @first_prop: property ID to use for the first property + * + * Installs the GtkEditable properties for @class. + * + * This is a helper function that should be called in class_init, + * after installing your own properties. + * + * To handle the properties in your set_property and get_property + * functions, you can either use gtk_editable_delegate_set_property() + * and gtk_editable_delegate_get_property() (if you are using a delegate), + * or remember the @first_prop offset and add it to the values in the + * #GtkEditableProperties enumeration to get the property IDs for these + * properties. + * + * Returns: the number of properties that were installed + */ +guint +gtk_editable_install_properties (GObjectClass *object_class, + guint first_prop) +{ + g_type_set_qdata (G_TYPE_FROM_CLASS (object_class), + quark_editable_data, + GUINT_TO_POINTER (first_prop)); + + g_object_class_override_property (object_class, first_prop + GTK_EDITABLE_PROP_TEXT, "text"); + g_object_class_override_property (object_class, first_prop + GTK_EDITABLE_PROP_CURSOR_POSITION, "cursor-position"); + g_object_class_override_property (object_class, first_prop + GTK_EDITABLE_PROP_SELECTION_BOUND, "selection-bound"); + g_object_class_override_property (object_class, first_prop + GTK_EDITABLE_PROP_EDITABLE, "editable"); + g_object_class_override_property (object_class, first_prop + GTK_EDITABLE_PROP_WIDTH_CHARS, "width-chars"); + g_object_class_override_property (object_class, first_prop + GTK_EDITABLE_PROP_MAX_WIDTH_CHARS, "max-width-chars"); + g_object_class_override_property (object_class, first_prop + GTK_EDITABLE_PROP_XALIGN, "xalign"); + + return GTK_EDITABLE_NUM_PROPERTIES; +} + +static void +delegate_changed (GtkEditable *delegate, + gpointer editable) +{ + g_signal_emit_by_name (editable, "changed"); +} + +static void +delegate_notify (GObject *object, + GParamSpec *pspec, + gpointer data) +{ + gpointer iface; + + iface = g_type_interface_peek (g_type_class_peek (G_OBJECT_TYPE (object)), gtk_editable_get_type ()); + if (g_object_interface_find_property (iface, pspec->name)) + g_object_notify (data, pspec->name); +} + +/** + * gtk_editable_init_delegate: + * @editable: a #GtkEditable + * + * Sets up a delegate for #GtkEditable, assuming that the + * get_delegate vfunc in the #GtkEditable interface has been + * set up for the @editable's type. + * + * This is a helper function that should be called in instance init, + * after creating the delegate object. + */ +void +gtk_editable_init_delegate (GtkEditable *editable) +{ + GtkEditable *delegate = get_delegate (editable); + g_signal_connect (delegate, "notify", G_CALLBACK (delegate_notify), editable); + g_signal_connect (delegate, "changed", G_CALLBACK (delegate_changed), editable); +} + +/** + * gtk_editable_finish_delegate: + * @editable: a #GtkEditable + * + * Undoes the setup done by gtk_editable_init_delegate(). + * + * This is a helper function that should be called from dispose, + * before removing the delegate object. + */ +void +gtk_editable_finish_delegate (GtkEditable *editable) +{ + GtkEditable *delegate = get_delegate (editable); + g_signal_handlers_disconnect_by_func (delegate, delegate_notify, editable); + g_signal_handlers_disconnect_by_func (delegate, delegate_changed, editable); +} + +/** + * gtk_editable_set_property: + * @object: a #GObject + * @prop_id: a property ID + * @value: value to set + * @pspec: the #GParamSpec for the property + * + * Sets a property on the #GtkEditable delegate for @object. + * + * This is a helper function that should be called in set_property, + * before handling your own properties. + * + * Returns: %TRUE if the property was found + */ +gboolean +gtk_editable_delegate_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GtkEditable *delegate = get_delegate (GTK_EDITABLE (object)); + GType type = G_TYPE_FROM_INSTANCE (object); + guint first_prop; + + first_prop = GPOINTER_TO_UINT (g_type_get_qdata (type, quark_editable_data)); + + if (prop_id < first_prop) + return FALSE; + + switch (prop_id - first_prop) + { + case GTK_EDITABLE_PROP_TEXT: + gtk_editable_set_text (delegate, g_value_get_string (value)); + break; + + case GTK_EDITABLE_PROP_EDITABLE: + gtk_editable_set_editable (delegate, g_value_get_boolean (value)); + break; + + case GTK_EDITABLE_PROP_WIDTH_CHARS: + gtk_editable_set_width_chars (delegate, g_value_get_int (value)); + break; + + case GTK_EDITABLE_PROP_MAX_WIDTH_CHARS: + gtk_editable_set_max_width_chars (delegate, g_value_get_int (value)); + break; + + case GTK_EDITABLE_PROP_XALIGN: + gtk_editable_set_alignment (delegate, g_value_get_float (value)); + break; + + default: + return FALSE; + } + + return TRUE; +} + +/** + * gtk_editable_get_property: + * @object: a #GObject + * @prop_id: a property ID + * @value: value to set + * @pspec: the #GParamSpec for the property + * + * Gets a property of the #GtkEditable delegate for @object. + * + * This is helper function that should be called in get_property, + * before handling your own properties. + * + * Returns: %TRUE if the property was found + */ +gboolean +gtk_editable_delegate_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GtkEditable *delegate = get_delegate (GTK_EDITABLE (object)); + int cursor_position, selection_bound; + GType type = G_TYPE_FROM_INSTANCE (object); + guint first_prop; + + first_prop = GPOINTER_TO_UINT (g_type_get_qdata (type, quark_editable_data)); + + if (prop_id < first_prop) + return FALSE; + + switch (prop_id - first_prop) + { + case GTK_EDITABLE_PROP_TEXT: + g_value_set_string (value, gtk_editable_get_text (delegate)); + break; + + case GTK_EDITABLE_PROP_CURSOR_POSITION: + gtk_editable_get_selection_bounds (delegate, &cursor_position, &selection_bound); + g_value_set_int (value, cursor_position); + break; + + case GTK_EDITABLE_PROP_SELECTION_BOUND: + gtk_editable_get_selection_bounds (delegate, &cursor_position, &selection_bound); + g_value_set_int (value, selection_bound); + break; + + case GTK_EDITABLE_PROP_EDITABLE: + g_value_set_boolean (value, gtk_editable_get_editable (delegate)); + break; + + case GTK_EDITABLE_PROP_WIDTH_CHARS: + g_value_set_int (value, gtk_editable_get_width_chars (delegate)); + break; + + case GTK_EDITABLE_PROP_MAX_WIDTH_CHARS: + g_value_set_int (value, gtk_editable_get_max_width_chars (delegate)); + break; + + case GTK_EDITABLE_PROP_XALIGN: + g_value_set_float (value, gtk_editable_get_alignment (delegate)); + break; + + default: + return FALSE; + } + + return TRUE; +} + + diff --git a/gtk/gtkeditable.h b/gtk/gtkeditable.h index 88d148919e..2de901fa15 100644 --- a/gtk/gtkeditable.h +++ b/gtk/gtkeditable.h @@ -45,82 +45,131 @@ typedef struct _GtkEditableInterface GtkEditableInterface; struct _GtkEditableInterface { - GTypeInterface base_iface; + GTypeInterface base_iface; /* signals */ void (* insert_text) (GtkEditable *editable, - const gchar *new_text, - gint new_text_length, - gint *position); + const gchar *text, + int length, + int *position); void (* delete_text) (GtkEditable *editable, - gint start_pos, - gint end_pos); + int start_pos, + int end_pos); void (* changed) (GtkEditable *editable); /* vtable */ - void (* do_insert_text) (GtkEditable *editable, - const gchar *new_text, - gint new_text_length, - gint *position); - void (* do_delete_text) (GtkEditable *editable, - gint start_pos, - gint end_pos); + const char * (* get_text) (GtkEditable *editable); + void (* do_insert_text) (GtkEditable *editable, + const char *text, + int length, + int *position); + void (* do_delete_text) (GtkEditable *editable, + int start_pos, + int end_pos); - gchar* (* get_chars) (GtkEditable *editable, - gint start_pos, - gint end_pos); - void (* set_selection_bounds) (GtkEditable *editable, - gint start_pos, - gint end_pos); gboolean (* get_selection_bounds) (GtkEditable *editable, - gint *start_pos, - gint *end_pos); - void (* set_position) (GtkEditable *editable, - gint position); - gint (* get_position) (GtkEditable *editable); + int *start_pos, + int *end_pos); + void (* set_selection_bounds) (GtkEditable *editable, + int start_pos, + int end_pos); + GtkEditable * (* get_delegate) (GtkEditable *editable); }; GDK_AVAILABLE_IN_ALL GType gtk_editable_get_type (void) G_GNUC_CONST; + GDK_AVAILABLE_IN_ALL -void gtk_editable_select_region (GtkEditable *editable, - gint start_pos, - gint end_pos); +const char * gtk_editable_get_text (GtkEditable *editable); GDK_AVAILABLE_IN_ALL -gboolean gtk_editable_get_selection_bounds (GtkEditable *editable, - gint *start_pos, - gint *end_pos); +void gtk_editable_set_text (GtkEditable *editable, + const char *text); +GDK_AVAILABLE_IN_ALL +char * gtk_editable_get_chars (GtkEditable *editable, + int start_pos, + int end_pos); GDK_AVAILABLE_IN_ALL void gtk_editable_insert_text (GtkEditable *editable, - const gchar *new_text, - gint new_text_length, - gint *position); + const char *text, + int length, + int *position); GDK_AVAILABLE_IN_ALL void gtk_editable_delete_text (GtkEditable *editable, - gint start_pos, - gint end_pos); + int start_pos, + int end_pos); + GDK_AVAILABLE_IN_ALL -gchar* gtk_editable_get_chars (GtkEditable *editable, - gint start_pos, - gint end_pos); -GDK_AVAILABLE_IN_ALL -void gtk_editable_cut_clipboard (GtkEditable *editable); -GDK_AVAILABLE_IN_ALL -void gtk_editable_copy_clipboard (GtkEditable *editable); -GDK_AVAILABLE_IN_ALL -void gtk_editable_paste_clipboard (GtkEditable *editable); +gboolean gtk_editable_get_selection_bounds (GtkEditable *editable, + int *start_pos, + int *end_pos); GDK_AVAILABLE_IN_ALL void gtk_editable_delete_selection (GtkEditable *editable); +GDK_AVAILABLE_IN_ALL +void gtk_editable_select_region (GtkEditable *editable, + int start_pos, + int end_pos); + GDK_AVAILABLE_IN_ALL void gtk_editable_set_position (GtkEditable *editable, - gint position); + int position); GDK_AVAILABLE_IN_ALL -gint gtk_editable_get_position (GtkEditable *editable); -GDK_AVAILABLE_IN_ALL -void gtk_editable_set_editable (GtkEditable *editable, - gboolean is_editable); +int gtk_editable_get_position (GtkEditable *editable); + GDK_AVAILABLE_IN_ALL gboolean gtk_editable_get_editable (GtkEditable *editable); +GDK_AVAILABLE_IN_ALL +void gtk_editable_set_editable (GtkEditable *editable, + gboolean is_editable); + +GDK_AVAILABLE_IN_ALL +float gtk_editable_get_alignment (GtkEditable *editable); +GDK_AVAILABLE_IN_ALL +void gtk_editable_set_alignment (GtkEditable *editable, + float xalign); + +GDK_AVAILABLE_IN_ALL +int gtk_editable_get_width_chars (GtkEditable *editable); +GDK_AVAILABLE_IN_ALL +void gtk_editable_set_width_chars (GtkEditable *editable, + int n_chars); + +GDK_AVAILABLE_IN_ALL +int gtk_editable_get_max_width_chars (GtkEditable *editable); +GDK_AVAILABLE_IN_ALL +void gtk_editable_set_max_width_chars (GtkEditable *editable, + int n_chars); + +/* api for implementations */ + +typedef enum { + GTK_EDITABLE_PROP_TEXT, + GTK_EDITABLE_PROP_CURSOR_POSITION, + GTK_EDITABLE_PROP_SELECTION_BOUND, + GTK_EDITABLE_PROP_EDITABLE, + GTK_EDITABLE_PROP_WIDTH_CHARS, + GTK_EDITABLE_PROP_MAX_WIDTH_CHARS, + GTK_EDITABLE_PROP_XALIGN, + GTK_EDITABLE_NUM_PROPERTIES +} GtkEditableProperties; + +GDK_AVAILABLE_IN_ALL +guint gtk_editable_install_properties (GObjectClass *object_class, + guint first_prop); +GDK_AVAILABLE_IN_ALL +void gtk_editable_init_delegate (GtkEditable *editable); +GDK_AVAILABLE_IN_ALL +void gtk_editable_finish_delegate (GtkEditable *editable); +GDK_AVAILABLE_IN_ALL +gboolean gtk_editable_delegate_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec); +GDK_AVAILABLE_IN_ALL +gboolean gtk_editable_delegate_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec); + G_END_DECLS From 32795963c06ba7f6439c263ef421c15b81ef381a Mon Sep 17 00:00:00 2001 From: Matthias Clasen Date: Wed, 13 Feb 2019 23:07:36 -0500 Subject: [PATCH 02/34] entry: Implement new editable iface --- gtk/gtkentry.c | 117 +++++++++++++++++++++++-------------------------- 1 file changed, 56 insertions(+), 61 deletions(-) diff --git a/gtk/gtkentry.c b/gtk/gtkentry.c index 4585df6899..2050cfcdff 100644 --- a/gtk/gtkentry.c +++ b/gtk/gtkentry.c @@ -458,9 +458,8 @@ static void gtk_entry_insert_text (GtkEditable *editable, static void gtk_entry_delete_text (GtkEditable *editable, gint start_pos, gint end_pos); -static gchar * gtk_entry_get_chars (GtkEditable *editable, - gint start_pos, - gint end_pos); +static const char *gtk_entry_real_get_text (GtkEditable *editable); +static int gtk_entry_get_length (GtkEditable *editable); static void gtk_entry_real_set_position (GtkEditable *editable, gint position); static gint gtk_entry_get_position (GtkEditable *editable); @@ -470,6 +469,8 @@ static void gtk_entry_set_selection_bounds (GtkEditable *editable, static gboolean gtk_entry_get_selection_bounds (GtkEditable *editable, gint *start, gint *end); +static void gtk_entry_set_editable (GtkEditable *editable, + gboolean is_editable); /* GtkCellEditable method implementations */ @@ -1884,6 +1885,47 @@ gtk_entry_class_init (GtkEntryClass *class) gtk_widget_class_set_css_name (widget_class, I_("entry")); } +static void +gtk_entry_set_editable (GtkEditable *editable, + gboolean is_editable) +{ + GtkEntry *entry = GTK_ENTRY (editable); + GtkEntryPrivate *priv = gtk_entry_get_instance_private (entry); + GtkStyleContext *context = gtk_widget_get_style_context (GTK_WIDGET (entry)); + + if (is_editable != priv->editable) + { + GtkWidget *widget = GTK_WIDGET (entry); + + if (!is_editable) + { + gtk_entry_reset_im_context (entry); + if (gtk_widget_has_focus (widget)) + gtk_im_context_focus_out (priv->im_context); + + priv->preedit_length = 0; + priv->preedit_cursor = 0; + + gtk_style_context_remove_class (context, GTK_STYLE_CLASS_READ_ONLY); + } + else + { + gtk_style_context_add_class (context, GTK_STYLE_CLASS_READ_ONLY); + } + + priv->editable = is_editable; + + if (is_editable && gtk_widget_has_focus (widget)) + gtk_im_context_focus_in (priv->im_context); + + gtk_event_controller_key_set_im_context (GTK_EVENT_CONTROLLER_KEY (priv->key_controller), + is_editable ? priv->im_context : NULL); + + g_object_notify (G_OBJECT (editable), "editable"); + gtk_widget_queue_draw (widget); + } +} + static void gtk_entry_editable_init (GtkEditableInterface *iface) { @@ -1891,7 +1933,8 @@ gtk_entry_editable_init (GtkEditableInterface *iface) iface->do_delete_text = gtk_entry_delete_text; iface->insert_text = gtk_entry_real_insert_text; iface->delete_text = gtk_entry_real_delete_text; - iface->get_chars = gtk_entry_get_chars; + iface->get_text = gtk_entry_real_get_text; + iface->get_length = gtk_entry_get_length; iface->set_selection_bounds = gtk_entry_set_selection_bounds; iface->get_selection_bounds = gtk_entry_get_selection_bounds; iface->set_position = gtk_entry_real_set_position; @@ -1920,42 +1963,7 @@ gtk_entry_set_property (GObject *object, break; case PROP_EDITABLE: - { - gboolean new_value = g_value_get_boolean (value); - GtkStyleContext *context = gtk_widget_get_style_context (GTK_WIDGET (entry)); - - if (new_value != priv->editable) - { - GtkWidget *widget = GTK_WIDGET (entry); - - if (!new_value) - { - gtk_entry_reset_im_context (entry); - if (gtk_widget_has_focus (widget)) - gtk_im_context_focus_out (priv->im_context); - - priv->preedit_length = 0; - priv->preedit_cursor = 0; - - gtk_style_context_remove_class (context, GTK_STYLE_CLASS_READ_ONLY); - } - else - { - gtk_style_context_add_class (context, GTK_STYLE_CLASS_READ_ONLY); - } - - priv->editable = new_value; - - if (new_value && gtk_widget_has_focus (widget)) - gtk_im_context_focus_in (priv->im_context); - - gtk_event_controller_key_set_im_context (GTK_EVENT_CONTROLLER_KEY (priv->key_controller), - new_value ? priv->im_context : NULL); - - g_object_notify_by_pspec (object, pspec); - gtk_widget_queue_draw (widget); - } - } + gtk_entry_set_editable (GTK_EDITABLE (entry), g_value_get_boolean (value)); break; case PROP_MAX_LENGTH: @@ -4274,29 +4282,16 @@ gtk_entry_delete_text (GtkEditable *editable, g_object_unref (editable); } -static gchar * -gtk_entry_get_chars (GtkEditable *editable, - gint start_pos, - gint end_pos) +static const char * +gtk_entry_real_get_text (GtkEditable *editable) { - GtkEntry *entry = GTK_ENTRY (editable); - const gchar *text; - gint text_length; - gint start_index, end_index; + return gtk_entry_buffer_get_text (gtk_entry_get_buffer (GTK_ENTRY (editable))); +} - text = gtk_entry_buffer_get_text (get_buffer (entry)); - text_length = gtk_entry_buffer_get_length (get_buffer (entry)); - - if (end_pos < 0) - end_pos = text_length; - - start_pos = MIN (text_length, start_pos); - end_pos = MIN (text_length, end_pos); - - start_index = g_utf8_offset_to_pointer (text, start_pos) - text; - end_index = g_utf8_offset_to_pointer (text, end_pos) - text; - - return g_strndup (text + start_index, end_index - start_index); +static int +gtk_entry_get_length (GtkEditable *editable) +{ + return gtk_entry_buffer_get_length (gtk_entry_get_buffer (GTK_ENTRY (editable))); } static void From b9c8dd64b3ee691d517f542117694eb28d9c7a1f Mon Sep 17 00:00:00 2001 From: Matthias Clasen Date: Thu, 14 Feb 2019 00:54:07 -0500 Subject: [PATCH 03/34] spin button: Implement and use editable interface --- gtk/gtkspinbutton.c | 120 +++++++++++++------------------------------- 1 file changed, 35 insertions(+), 85 deletions(-) diff --git a/gtk/gtkspinbutton.c b/gtk/gtkspinbutton.c index f290391964..59c8681bc7 100644 --- a/gtk/gtkspinbutton.c +++ b/gtk/gtkspinbutton.c @@ -35,6 +35,7 @@ #include "gtkbutton.h" #include "gtkcssstylepropertyprivate.h" #include "gtkeditable.h" +#include "gtkeditableprivate.h" #include "gtkentry.h" #include "gtkeventcontrollerkey.h" #include "gtkeventcontrollermotion.h" @@ -221,9 +222,6 @@ enum { PROP_WRAP, PROP_UPDATE_POLICY, PROP_VALUE, - PROP_WIDTH_CHARS, - PROP_MAX_WIDTH_CHARS, - PROP_TEXT, NUM_SPINBUTTON_PROPS, PROP_ORIENTATION, }; @@ -385,33 +383,9 @@ gtk_spin_button_class_init (GtkSpinButtonClass *class) -G_MAXDOUBLE, G_MAXDOUBLE, 0.0, GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY); - spinbutton_props[PROP_WIDTH_CHARS] = - g_param_spec_int ("width-chars", - P_("Width in chars"), - P_("Number of characters to leave space for in the entry"), - -1, G_MAXINT, - 0, - GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY); - - spinbutton_props[PROP_MAX_WIDTH_CHARS] = - g_param_spec_int ("max-width-chars", - P_("Maximum width in characters"), - P_("The desired maximum width of the entry, in characters"), - -1, G_MAXINT, - 0, - GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY); - - spinbutton_props[PROP_TEXT] = - g_param_spec_string ("text", - P_("Text"), - P_("The contents of the entry"), - "0", /* Default value of the default adjustment */ - GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY); - g_object_class_install_properties (gobject_class, NUM_SPINBUTTON_PROPS, spinbutton_props); - g_object_class_override_property (gobject_class, - PROP_ORIENTATION, - "orientation"); + gtk_editable_install_properties (gobject_class); + g_object_class_override_property (gobject_class, PROP_ORIENTATION, "orientation"); /** * GtkSpinButton::input: @@ -552,6 +526,7 @@ gtk_spin_button_class_init (GtkSpinButtonClass *class) static void gtk_spin_button_editable_init (GtkEditableInterface *iface) { + gtk_editable_delegate_iface_init (iface); iface->insert_text = gtk_spin_button_insert_text; } @@ -564,6 +539,9 @@ gtk_spin_button_set_property (GObject *object, GtkSpinButton *spin_button = GTK_SPIN_BUTTON (object); GtkSpinButtonPrivate *priv = gtk_spin_button_get_instance_private (spin_button); + if (gtk_editable_delegate_set_property (object, prop_id, value, pspec)) + return; + switch (prop_id) { GtkAdjustment *adjustment; @@ -602,15 +580,6 @@ gtk_spin_button_set_property (GObject *object, case PROP_ORIENTATION: gtk_spin_button_set_orientation (spin_button, g_value_get_enum (value)); break; - case PROP_WIDTH_CHARS: - gtk_spin_button_set_width_chars (spin_button, g_value_get_int (value)); - break; - case PROP_MAX_WIDTH_CHARS: - gtk_spin_button_set_max_width_chars (spin_button, g_value_get_int (value)); - break; - case PROP_TEXT: - gtk_spin_button_set_text (spin_button, g_value_get_string (value)); - break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -626,6 +595,9 @@ gtk_spin_button_get_property (GObject *object, GtkSpinButton *spin_button = GTK_SPIN_BUTTON (object); GtkSpinButtonPrivate *priv = gtk_spin_button_get_instance_private (spin_button); + if (gtk_editable_delegate_get_property (object, prop_id, value, pspec)) + return; + switch (prop_id) { case PROP_ADJUSTMENT: @@ -655,15 +627,6 @@ gtk_spin_button_get_property (GObject *object, case PROP_ORIENTATION: g_value_set_enum (value, priv->orientation); break; - case PROP_WIDTH_CHARS: - g_value_set_int (value, gtk_spin_button_get_width_chars (spin_button)); - break; - case PROP_MAX_WIDTH_CHARS: - g_value_set_int (value, gtk_spin_button_get_max_width_chars (spin_button)); - break; - case PROP_TEXT: - g_value_set_string (value, gtk_spin_button_get_text (spin_button)); - break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -867,14 +830,14 @@ gtk_spin_button_init (GtkSpinButton *spin_button) gtk_widget_set_parent (priv->box, GTK_WIDGET (spin_button)); priv->entry = gtk_entry_new (); - gtk_entry_set_width_chars (GTK_ENTRY (priv->entry), 0); - gtk_entry_set_max_width_chars (GTK_ENTRY (priv->entry), 0); + gtk_editable_set_delegate (GTK_EDITABLE (spin_button), GTK_EDITABLE (priv->entry)); + gtk_editable_set_width_chars (GTK_EDITABLE (priv->entry), 0); + gtk_editable_set_max_width_chars (GTK_EDITABLE (priv->entry), 0); gtk_widget_set_hexpand (priv->entry, TRUE); gtk_widget_set_vexpand (priv->entry, TRUE); g_signal_connect (priv->entry, "activate", G_CALLBACK (gtk_spin_button_activate), spin_button); gtk_container_add (GTK_CONTAINER (priv->box), priv->entry); - priv->down_button = gtk_button_new (); gtk_container_add (GTK_CONTAINER (priv->down_button), gtk_image_new_from_icon_name ("value-decrease-symbolic")); gtk_style_context_add_class (gtk_widget_get_style_context (priv->down_button), "image-button"); @@ -960,6 +923,7 @@ gtk_spin_button_realize (GtkWidget *widget) GtkSpinButton *spin_button = GTK_SPIN_BUTTON (widget); GtkSpinButtonPrivate *priv = gtk_spin_button_get_instance_private (spin_button); gboolean return_val; + const char *text; GTK_WIDGET_CLASS (gtk_spin_button_parent_class)->realize (widget); @@ -969,8 +933,8 @@ gtk_spin_button_realize (GtkWidget *widget) /* If output wasn't processed explicitly by the method connected to the * 'output' signal; and if we don't have any explicit 'text' set initially, * fallback to the default output. */ - if (!return_val && - (priv->numeric || gtk_entry_get_text (GTK_ENTRY (priv->entry)) == NULL)) + text = gtk_editable_get_text (GTK_EDITABLE (priv->entry)); + if (!return_val && (priv->numeric || text == NULL || *text == '\0')) gtk_spin_button_default_output (spin_button); } @@ -1026,7 +990,7 @@ gtk_spin_button_set_orientation (GtkSpinButton *spin, GtkOrientation orientation) { GtkSpinButtonPrivate *priv = gtk_spin_button_get_instance_private (spin); - GtkEntry *entry = GTK_ENTRY (priv->entry); + GtkEditable *editable = GTK_EDITABLE (priv->entry); if (priv->orientation == orientation) return; @@ -1036,11 +1000,11 @@ gtk_spin_button_set_orientation (GtkSpinButton *spin, /* change alignment if it's the default */ if (priv->orientation == GTK_ORIENTATION_VERTICAL && - gtk_entry_get_alignment (entry) == 0.0) - gtk_entry_set_alignment (entry, 0.5); + gtk_editable_get_alignment (editable) == 0.0) + gtk_editable_set_alignment (editable, 0.5); else if (priv->orientation == GTK_ORIENTATION_HORIZONTAL && - gtk_entry_get_alignment (entry) == 0.5) - gtk_entry_set_alignment (entry, 0.0); + gtk_editable_get_alignment (editable) == 0.5) + gtk_editable_set_alignment (editable, 0.0); if (priv->orientation == GTK_ORIENTATION_HORIZONTAL) { @@ -1348,11 +1312,6 @@ gtk_spin_button_insert_text (GtkEditable *editable, { GtkSpinButton *spin = GTK_SPIN_BUTTON (editable); GtkSpinButtonPrivate *priv = gtk_spin_button_get_instance_private (spin); - GtkEntry *entry = GTK_ENTRY (priv->entry); - GtkEditableInterface *parent_editable_iface; - - parent_editable_iface = g_type_interface_peek (gtk_spin_button_parent_class, - GTK_TYPE_EDITABLE); if (priv->numeric) { @@ -1365,8 +1324,8 @@ gtk_spin_button_insert_text (GtkEditable *editable, gint entry_length; const gchar *entry_text; - entry_length = gtk_entry_get_text_length (entry); - entry_text = gtk_entry_get_text (entry); + entry_text = gtk_editable_get_text (GTK_EDITABLE (priv->entry)); + entry_length = g_utf8_strlen (entry_text, -1); lc = localeconv (); @@ -1444,8 +1403,8 @@ gtk_spin_button_insert_text (GtkEditable *editable, } } - parent_editable_iface->insert_text (editable, new_text, - new_text_length, position); + gtk_editable_insert_text (GTK_EDITABLE (priv->entry), + new_text, new_text_length, position); } static void @@ -1505,8 +1464,9 @@ gtk_spin_button_default_input (GtkSpinButton *spin_button, { GtkSpinButtonPrivate *priv = gtk_spin_button_get_instance_private (spin_button); gchar *err = NULL; + const char *text = gtk_editable_get_text (GTK_EDITABLE (priv->entry)); - *new_val = g_strtod (gtk_entry_get_text (GTK_ENTRY (priv->entry)), &err); + *new_val = g_strtod (text, &err); if (*err) return GTK_INPUT_ERROR; else @@ -1520,8 +1480,8 @@ gtk_spin_button_default_output (GtkSpinButton *spin_button) gchar *buf = gtk_spin_button_format_for_value (spin_button, gtk_adjustment_get_value (priv->adjustment)); - if (strcmp (buf, gtk_entry_get_text (GTK_ENTRY (priv->entry)))) - gtk_entry_set_text (GTK_ENTRY (priv->entry), buf); + if (strcmp (buf, gtk_editable_get_text (GTK_EDITABLE (priv->entry)))) + gtk_editable_set_text (GTK_EDITABLE (priv->entry), buf); g_free (buf); } @@ -2260,7 +2220,7 @@ gtk_spin_button_get_text (GtkSpinButton *spin_button) g_return_val_if_fail (GTK_IS_SPIN_BUTTON (spin_button), NULL); - return gtk_entry_get_text (GTK_ENTRY (priv->entry)); + return gtk_editable_get_text (GTK_EDITABLE (priv->entry)); } /** @@ -2279,9 +2239,7 @@ gtk_spin_button_set_text (GtkSpinButton *spin_button, g_return_if_fail (GTK_IS_SPIN_BUTTON (spin_button)); - gtk_entry_set_text (GTK_ENTRY (priv->entry), text); - - g_object_notify_by_pspec (G_OBJECT (spin_button), spinbutton_props[PROP_TEXT]); + gtk_editable_set_text (GTK_EDITABLE (priv->entry), text); } /** @@ -2303,7 +2261,7 @@ gtk_spin_button_get_max_width_chars (GtkSpinButton *spin_button) g_return_val_if_fail (GTK_IS_SPIN_BUTTON (spin_button), -1); - return gtk_entry_get_max_width_chars (GTK_ENTRY (priv->entry)); + return gtk_editable_get_max_width_chars (GTK_EDITABLE (priv->entry)); } /** @@ -2323,11 +2281,7 @@ gtk_spin_button_set_max_width_chars (GtkSpinButton *spin_button, g_return_if_fail (GTK_IS_SPIN_BUTTON (spin_button)); - if (max_width_chars != gtk_entry_get_max_width_chars (GTK_ENTRY (priv->entry))) - { - gtk_entry_set_max_width_chars (GTK_ENTRY (priv->entry), max_width_chars); - g_object_notify_by_pspec (G_OBJECT (spin_button), spinbutton_props[PROP_MAX_WIDTH_CHARS]); - } + gtk_editable_set_max_width_chars (GTK_EDITABLE (priv->entry), max_width_chars); } /** @@ -2347,7 +2301,7 @@ gtk_spin_button_get_width_chars (GtkSpinButton *spin_button) g_return_val_if_fail (GTK_IS_SPIN_BUTTON (spin_button), -1); - return gtk_entry_get_width_chars (GTK_ENTRY (priv->entry)); + return gtk_editable_get_width_chars (GTK_EDITABLE (priv->entry)); } /** @@ -2366,9 +2320,5 @@ gtk_spin_button_set_width_chars (GtkSpinButton *spin_button, g_return_if_fail (GTK_IS_SPIN_BUTTON (spin_button)); - if (width_chars != gtk_entry_get_width_chars (GTK_ENTRY (priv->entry))) - { - gtk_entry_set_width_chars (GTK_ENTRY (priv->entry), width_chars); - g_object_notify_by_pspec (G_OBJECT (spin_button), spinbutton_props[PROP_WIDTH_CHARS]); - } + gtk_editable_set_width_chars (GTK_EDITABLE (priv->entry), width_chars); } From 1801bf6d8368736b4f3304450a905c36a2eae482 Mon Sep 17 00:00:00 2001 From: Matthias Clasen Date: Wed, 13 Feb 2019 20:16:12 -0500 Subject: [PATCH 04/34] Add a GtkText widget This is a GtkEntry without any of the extras, such as icons, completion, progress, caps-lock warning, emoji icon. --- docs/reference/gtk/gtk4-docs.xml | 5 +- docs/reference/gtk/gtk4-sections.txt | 38 + docs/reference/gtk/gtk4.types.in | 1 + gtk/gtk.h | 1 + gtk/gtktext.c | 6721 ++++++++++++++++++++++++++ gtk/gtktext.h | 223 + gtk/gtktextprivate.h | 46 + gtk/meson.build | 2 + 8 files changed, 7035 insertions(+), 2 deletions(-) create mode 100644 gtk/gtktext.c create mode 100644 gtk/gtktext.h create mode 100644 gtk/gtktextprivate.h diff --git a/docs/reference/gtk/gtk4-docs.xml b/docs/reference/gtk/gtk4-docs.xml index 0611530b29..56d2b8a1a6 100644 --- a/docs/reference/gtk/gtk4-docs.xml +++ b/docs/reference/gtk/gtk4-docs.xml @@ -138,14 +138,15 @@ Numeric and Text Data Entry - + + + - diff --git a/docs/reference/gtk/gtk4-sections.txt b/docs/reference/gtk/gtk4-sections.txt index 06e6cb7991..aa019ef40f 100644 --- a/docs/reference/gtk/gtk4-sections.txt +++ b/docs/reference/gtk/gtk4-sections.txt @@ -871,6 +871,44 @@ GTK_EDITABLE_GET_IFACE gtk_editable_get_type +
+gtktext +GtkText +GtkText +GtkTextClass +gtk_text_new +gtk_text_new_with_buffer +gtk_text_set_buffer +gtk_text_get_buffer +gtk_text_set_visibility +gtk_text_get_visibility +gtk_text_set_invisible_char +gtk_text_get_invisible_char +gtk_text_unset_invisible_char +gtk_text_set_has_frame +gtk_text_get_has_frame +gtk_text_set_overwrite_mode +gtk_text_get_overwrite_mode +gtk_text_set_max_length +gtk_text_get_max_length +gtk_text_get_text_length +gtk_text_set_activates_default +gtk_text_get_activates_default +gtk_text_set_placeholder_text +gtk_text_get_placeholder_text +gtk_text_set_input_purpose +gtk_text_get_input_purpose +gtk_text_set_input_hints +gtk_text_get_input_hints +gtk_text_set_attributes +gtk_text_get_attributes +gtk_text_set_tabs +gtk_text_get_tabs +gtk_text_grab_focus_without_selecting + +gtk_text_get_type +
+
gtkentry GtkEntry diff --git a/docs/reference/gtk/gtk4.types.in b/docs/reference/gtk/gtk4.types.in index 79d73c04a4..5c7bc803f2 100644 --- a/docs/reference/gtk/gtk4.types.in +++ b/docs/reference/gtk/gtk4.types.in @@ -168,6 +168,7 @@ gtk_style_context_get_type gtk_style_provider_get_type gtk_text_buffer_get_type gtk_text_child_anchor_get_type +gtk_text_get_type gtk_text_iter_get_type gtk_text_mark_get_type gtk_text_tag_get_type diff --git a/gtk/gtk.h b/gtk/gtk.h index 368ce0ca69..c500c8de74 100644 --- a/gtk/gtk.h +++ b/gtk/gtk.h @@ -211,6 +211,7 @@ #include #include #include +#include #include #include #include diff --git a/gtk/gtktext.c b/gtk/gtktext.c new file mode 100644 index 0000000000..8409d69e4c --- /dev/null +++ b/gtk/gtktext.c @@ -0,0 +1,6721 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* GTK - The GIMP Toolkit + * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald + * Copyright (C) 2004-2006 Christian Hammond + * Copyright (C) 2008 Cody Russell + * Copyright (C) 2008 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + */ + +#include "config.h" + +#include "gtktextprivate.h" + +#include "gtkadjustment.h" +#include "gtkbindings.h" +#include "gtkbox.h" +#include "gtkbutton.h" +#include "gtkcssnodeprivate.h" +#include "gtkdebug.h" +#include "gtkdnd.h" +#include "gtkdndprivate.h" +#include "gtkeditable.h" +#include "gtkemojichooser.h" +#include "gtkemojicompletion.h" +#include "gtkentrybuffer.h" +#include "gtkeventcontrollerkey.h" +#include "gtkeventcontrollermotion.h" +#include "gtkgesturedrag.h" +#include "gtkgesturemultipress.h" +#include "gtkgesturesingle.h" +#include "gtkimageprivate.h" +#include "gtkimcontextsimple.h" +#include "gtkimmulticontext.h" +#include "gtkintl.h" +#include "gtklabel.h" +#include "gtkmagnifierprivate.h" +#include "gtkmain.h" +#include "gtkmarshalers.h" +#include "gtkmenu.h" +#include "gtkmenuitem.h" +#include "gtkpango.h" +#include "gtkpopover.h" +#include "gtkprivate.h" +#include "gtkseparatormenuitem.h" +#include "gtkselection.h" +#include "gtksettings.h" +#include "gtksnapshot.h" +#include "gtkstylecontextprivate.h" +#include "gtktexthandleprivate.h" +#include "gtktextutil.h" +#include "gtktooltip.h" +#include "gtktreeselection.h" +#include "gtktreeview.h" +#include "gtktypebuiltins.h" +#include "gtkwidgetprivate.h" +#include "gtkwindow.h" + +#include "a11y/gtkentryaccessible.h" + +#include +#include + +#include "fallback-c89.c" + +/** + * SECTION:gtktext + * @Short_description: A simple single-line text entry field + * @Title: GtkText + * @See_also: #GtkEntry, #GtkTextView + * + * The #GtkText widget is a single line text entry widget. + * + * A fairly large set of key bindings are supported by default. If the + * entered text is longer than the allocation of the widget, the widget + * will scroll so that the cursor position is visible. + * + * When using an entry for passwords and other sensitive information, + * it can be put into “password mode” using gtk_text_set_visibility(). + * In this mode, entered text is displayed using a “invisible” character. + * By default, GTK picks the best invisible character that is available + * in the current font, but it can be changed with gtk_text_set_invisible_char(). + * + * If you are looking to add icons or progress display in an entry, look + * at #GtkEntry. There other alternatives for more specialized use cases, + * such as #GtkSearchEntry. + * + * If you need multi-line editable text, look at #GtkTextView. + * + * # CSS nodes + * + * |[ + * text[.read-only][.flat][.warning][.error] + * ├── placeholder + * ├── undershoot.left + * ├── undershoot.right + * ├── [selection] + * ├── [block-cursor] + * ╰── [window.popup] + * ]| + * + * GtkText has a main node with the name text. Depending on the properties + * of the widget, the style classes .read-only and .flat may appear. The style + * classes .warning and .error may also be used with entries. + * + * When the entry has a selection, it adds a subnode with the name selection. + * + * When the entry is in overwrite mode, it adds a subnode with the name block-cursor + * that determines how the block cursor is drawn. + * + * The CSS node for a context menu is added as a subnode below text as well. + * + * The undershoot nodes are used to draw the underflow indication when content + * is scrolled out of view. These nodes get the .left and .right style classes + * added depending on where the indication is drawn. + * + * When touch is used and touch selection handles are shown, they are using + * CSS nodes with name cursor-handle. They get the .top or .bottom style class + * depending on where they are shown in relation to the selection. If there is + * just a single handle for the text cursor, it gets the style class .insertion-cursor. + */ + +#define NAT_ENTRY_WIDTH 150 + +#define UNDERSHOOT_SIZE 20 + +static GQuark quark_password_hint = 0; +static GQuark quark_gtk_signal = 0; + +typedef struct _GtkTextPasswordHint GtkTextPasswordHint; + +typedef struct _GtkTextPrivate GtkTextPrivate; +struct _GtkTextPrivate +{ + GtkEntryBuffer *buffer; + GtkIMContext *im_context; + GtkWidget *popup_menu; + + int text_baseline; + + PangoLayout *cached_layout; + PangoAttrList *attrs; + PangoTabArray *tabs; + + GdkContentProvider *selection_content; + + char *im_module; + + GtkTextHandle *text_handle; + GtkWidget *selection_bubble; + guint selection_bubble_timeout_id; + + GtkWidget *magnifier_popover; + GtkWidget *magnifier; + + GtkWidget *placeholder; + + GtkGesture *drag_gesture; + GtkEventController *key_controller; + + GtkCssNode *selection_node; + GtkCssNode *block_cursor_node; + GtkCssNode *undershoot_node[2]; + + int text_x; + int text_width; + + float xalign; + + int ascent; /* font ascent in pango units */ + int current_pos; + int descent; /* font descent in pango units */ + int dnd_position; /* In chars, -1 == no DND cursor */ + int drag_start_x; + int drag_start_y; + int drop_position; /* where the drop should happen */ + int insert_pos; + int selection_bound; + int scroll_offset; + int start_x; + int start_y; + int width_chars; + int max_width_chars; + + gunichar invisible_char; + + guint blink_time; /* time in msec the cursor has blinked since last user event */ + guint blink_timeout; + + guint16 preedit_length; /* length of preedit string, in bytes */ + guint16 preedit_cursor; /* offset of cursor within preedit string, in chars */ + + gint64 handle_place_time; + + guint editable : 1; + guint enable_emoji_completion : 1; + guint in_drag : 1; + guint overwrite_mode : 1; + guint visible : 1; + + guint activates_default : 1; + guint cache_includes_preedit : 1; + guint change_count : 8; + guint cursor_visible : 1; + guint editing_canceled : 1; /* Only used by GtkCellRendererText */ + guint in_click : 1; /* Flag so we don't select all when clicking in entry to focus in */ + guint invisible_char_set : 1; + guint mouse_cursor_obscured : 1; + guint need_im_reset : 1; + guint real_changed : 1; + guint resolved_dir : 4; /* PangoDirection */ + guint select_words : 1; + guint select_lines : 1; + guint truncate_multiline : 1; + guint cursor_handle_dragged : 1; + guint selection_handle_dragged : 1; + guint populate_all : 1; +}; + +struct _EntryIconInfo +{ + GtkWidget *widget; + gchar *tooltip; + guint nonactivatable : 1; + guint in_drag : 1; + + GdkDragAction actions; + GdkContentFormats *target_list; +}; + +struct _GtkTextPasswordHint +{ + int position; /* Position (in text) of the last password hint */ + guint source_id; /* Timeout source id */ +}; + +enum { + ACTIVATE, + POPULATE_POPUP, + MOVE_CURSOR, + INSERT_AT_CURSOR, + DELETE_FROM_CURSOR, + BACKSPACE, + CUT_CLIPBOARD, + COPY_CLIPBOARD, + PASTE_CLIPBOARD, + TOGGLE_OVERWRITE, + PREEDIT_CHANGED, + INSERT_EMOJI, + LAST_SIGNAL +}; + +enum { + PROP_0, + PROP_BUFFER, + PROP_MAX_LENGTH, + PROP_HAS_FRAME, + PROP_VISIBILITY, + PROP_INVISIBLE_CHAR, + PROP_INVISIBLE_CHAR_SET, + PROP_ACTIVATES_DEFAULT, + PROP_SCROLL_OFFSET, + PROP_TRUNCATE_MULTILINE, + PROP_OVERWRITE_MODE, + PROP_IM_MODULE, + PROP_PLACEHOLDER_TEXT, + PROP_INPUT_PURPOSE, + PROP_INPUT_HINTS, + PROP_ATTRIBUTES, + PROP_POPULATE_ALL, + PROP_TABS, + PROP_ENABLE_EMOJI_COMPLETION, + NUM_PROPERTIES +}; + +static GParamSpec *text_props[NUM_PROPERTIES] = { NULL, }; + +static guint signals[LAST_SIGNAL] = { 0 }; + +typedef enum { + CURSOR_STANDARD, + CURSOR_DND +} CursorType; + +typedef enum +{ + DISPLAY_NORMAL, /* The text is being shown */ + DISPLAY_INVISIBLE, /* In invisible mode, text replaced by (eg) bullets */ + DISPLAY_BLANK /* In invisible mode, nothing shown at all */ +} DisplayMode; + +/* GObject methods + */ +static void gtk_text_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec); +static void gtk_text_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec); +static void gtk_text_finalize (GObject *object); +static void gtk_text_dispose (GObject *object); + +/* GtkWidget methods + */ +static void gtk_text_destroy (GtkWidget *widget); +static void gtk_text_realize (GtkWidget *widget); +static void gtk_text_unrealize (GtkWidget *widget); +static void gtk_text_unmap (GtkWidget *widget); +static void gtk_text_measure (GtkWidget *widget, + GtkOrientation orientation, + int for_size, + int *minimum, + int *natural, + int *minimum_baseline, + int *natural_baseline); +static void gtk_text_size_allocate (GtkWidget *widget, + int width, + int height, + int baseline); +static void gtk_text_snapshot (GtkWidget *widget, + GtkSnapshot *snapshot); +static void gtk_text_focus_in (GtkWidget *widget); +static void gtk_text_focus_out (GtkWidget *widget); +static void gtk_text_grab_focus (GtkWidget *widget); +static void gtk_text_style_updated (GtkWidget *widget); +static void gtk_text_direction_changed (GtkWidget *widget, + GtkTextDirection previous_dir); +static void gtk_text_state_flags_changed (GtkWidget *widget, + GtkStateFlags previous_state); +static void gtk_text_display_changed (GtkWidget *widget, + GdkDisplay *old_display); + +static gboolean gtk_text_drag_drop (GtkWidget *widget, + GdkDrop *drop, + int x, + int y); +static gboolean gtk_text_drag_motion (GtkWidget *widget, + GdkDrop *drop, + int x, + int y); +static void gtk_text_drag_leave (GtkWidget *widget, + GdkDrop *drop); +static void gtk_text_drag_data_received (GtkWidget *widget, + GdkDrop *drop, + GtkSelectionData *selection_data); +static void gtk_text_drag_data_get (GtkWidget *widget, + GdkDrag *drag, + GtkSelectionData *selection_data); +static void gtk_text_drag_data_delete (GtkWidget *widget, + GdkDrag *drag); +static void gtk_text_drag_begin (GtkWidget *widget, + GdkDrag *drag); +static void gtk_text_drag_end (GtkWidget *widget, + GdkDrag *drag); + + +/* GtkEditable method implementations + */ +static void gtk_text_editable_init (GtkEditableInterface *iface); +static void gtk_text_insert_text (GtkText *self, + const char *text, + int length, + int *position); +static void gtk_text_delete_text (GtkText *self, + int start_pos, + int end_pos); +static void gtk_text_set_selection_bounds (GtkText *self, + int start, + int end); +static gboolean gtk_text_get_selection_bounds (GtkText *self, + int *start, + int *end); + +static void gtk_text_set_editable (GtkText *self, + gboolean is_editable); +static void gtk_text_set_text (GtkText *self, + const char *text); +static void gtk_text_set_width_chars (GtkText *self, + int n_chars); +static void gtk_text_set_max_width_chars (GtkText *self, + int n_chars); +static void gtk_text_set_alignment (GtkText *self, + float xalign); + +/* Default signal handlers + */ +static gboolean gtk_text_popup_menu (GtkWidget *widget); +static void gtk_text_move_cursor (GtkText *self, + GtkMovementStep step, + int count, + gboolean extend); +static void gtk_text_insert_at_cursor (GtkText *self, + const char *str); +static void gtk_text_delete_from_cursor (GtkText *self, + GtkDeleteType type, + int count); +static void gtk_text_backspace (GtkText *self); +static void gtk_text_cut_clipboard (GtkText *self); +static void gtk_text_copy_clipboard (GtkText *self); +static void gtk_text_paste_clipboard (GtkText *self); +static void gtk_text_toggle_overwrite (GtkText *self); +static void gtk_text_insert_emoji (GtkText *self); +static void gtk_text_select_all (GtkText *self); +static void gtk_text_real_activate (GtkText *self); + +static void keymap_direction_changed (GdkKeymap *keymap, + GtkText *self); + +/* IM Context Callbacks + */ +static void gtk_text_commit_cb (GtkIMContext *context, + const char *str, + GtkText *self); +static void gtk_text_preedit_changed_cb (GtkIMContext *context, + GtkText *self); +static gboolean gtk_text_retrieve_surrounding_cb (GtkIMContext *context, + GtkText *self); +static gboolean gtk_text_delete_surrounding_cb (GtkIMContext *context, + int offset, + int n_chars, + GtkText *self); + +/* Entry buffer signal handlers + */ +static void buffer_inserted_text (GtkEntryBuffer *buffer, + guint position, + const char *chars, + guint n_chars, + GtkText *self); +static void buffer_deleted_text (GtkEntryBuffer *buffer, + guint position, + guint n_chars, + GtkText *self); +static void buffer_notify_text (GtkEntryBuffer *buffer, + GParamSpec *spec, + GtkText *self); +static void buffer_notify_max_length (GtkEntryBuffer *buffer, + GParamSpec *spec, + GtkText *self); + +/* Event controller callbacks + */ +static void gtk_text_motion_controller_motion (GtkEventControllerMotion *controller, + double x, + double y, + GtkText *self); +static void gtk_text_multipress_gesture_pressed (GtkGestureMultiPress *gesture, + int n_press, + double x, + double y, + GtkText *self); +static void gtk_text_drag_gesture_update (GtkGestureDrag *gesture, + double offset_x, + double offset_y, + GtkText *self); +static void gtk_text_drag_gesture_end (GtkGestureDrag *gesture, + double offset_x, + double offset_y, + GtkText *self); +static gboolean gtk_text_key_controller_key_pressed (GtkEventControllerKey *controller, + guint keyval, + guint keycode, + GdkModifierType state, + GtkText *self); + + +/* GtkTextHandle handlers */ +static void gtk_text_handle_drag_started (GtkTextHandle *handle, + GtkTextHandlePosition pos, + GtkText *self); +static void gtk_text_handle_dragged (GtkTextHandle *handle, + GtkTextHandlePosition pos, + int x, + int y, + GtkText *self); +static void gtk_text_handle_drag_finished (GtkTextHandle *handle, + GtkTextHandlePosition pos, + GtkText *self); + +/* Internal routines + */ +static void gtk_text_draw_text (GtkText *self, + GtkSnapshot *snapshot); +static void gtk_text_draw_cursor (GtkText *self, + GtkSnapshot *snapshot, + CursorType type); +static PangoLayout *gtk_text_ensure_layout (GtkText *self, + gboolean include_preedit); +static void gtk_text_reset_layout (GtkText *self); +static void gtk_text_recompute (GtkText *self); +static int gtk_text_find_position (GtkText *self, + int x); +static void gtk_text_get_cursor_locations (GtkText *self, + int *strong_x, + int *weak_x); +static void gtk_text_adjust_scroll (GtkText *self); +static int gtk_text_move_visually (GtkText *editable, + int start, + int count); +static int gtk_text_move_logically (GtkText *self, + int start, + int count); +static int gtk_text_move_forward_word (GtkText *self, + int start, + gboolean allow_whitespace); +static int gtk_text_move_backward_word (GtkText *self, + int start, + gboolean allow_whitespace); +static void gtk_text_delete_whitespace (GtkText *self); +static void gtk_text_select_word (GtkText *self); +static void gtk_text_select_line (GtkText *self); +static void gtk_text_paste (GtkText *self, + GdkClipboard *clipboard); +static void gtk_text_update_primary_selection (GtkText *self); +static void gtk_text_schedule_im_reset (GtkText *self); +static void gtk_text_do_popup (GtkText *self, + const GdkEvent *event); +static gboolean gtk_text_mnemonic_activate (GtkWidget *widget, + gboolean group_cycling); +static void gtk_text_check_cursor_blink (GtkText *self); +static void gtk_text_pend_cursor_blink (GtkText *self); +static void gtk_text_reset_blink_time (GtkText *self); +static void gtk_text_update_cached_style_values(GtkText *self); +static gboolean get_middle_click_paste (GtkText *self); +static void gtk_text_get_scroll_limits (GtkText *self, + int *min_offset, + int *max_offset); +static GtkEntryBuffer *get_buffer (GtkText *self); +static void set_enable_emoji_completion (GtkText *self, + gboolean value); +static void set_text_cursor (GtkWidget *widget); +static void update_placeholder_visibility (GtkText *self); + +static void buffer_connect_signals (GtkText *self); +static void buffer_disconnect_signals (GtkText *self); + +static void gtk_text_selection_bubble_popup_set (GtkText *self); +static void gtk_text_selection_bubble_popup_unset (GtkText *self); + +static void begin_change (GtkText *self); +static void end_change (GtkText *self); +static void emit_changed (GtkText *self); + + +/* GtkTextContent implementation + */ +#define GTK_TYPE_TEXT_CONTENT (gtk_text_content_get_type ()) +#define GTK_TEXT_CONTENT(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTK_TYPE_TEXT_CONTENT, GtkTextContent)) +#define GTK_IS_TEXT_CONTENT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTK_TYPE_TEXT_CONTENT)) +#define GTK_TEXT_CONTENT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GTK_TYPE_TEXT_CONTENT, GtkTextContentClass)) +#define GTK_IS_TEXT_CONTENT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GTK_TYPE_TEXT_CONTENT)) +#define GTK_TEXT_CONTENT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GTK_TYPE_TEXT_CONTENT, GtkTextContentClass)) + +typedef struct _GtkTextContent GtkTextContent; +typedef struct _GtkTextContentClass GtkTextContentClass; + +struct _GtkTextContent +{ + GdkContentProvider parent; + + GtkText *self; +}; + +struct _GtkTextContentClass +{ + GdkContentProviderClass parent_class; +}; + +GType gtk_text_content_get_type (void) G_GNUC_CONST; + +G_DEFINE_TYPE (GtkTextContent, gtk_text_content, GDK_TYPE_CONTENT_PROVIDER) + +static GdkContentFormats * +gtk_text_content_ref_formats (GdkContentProvider *provider) +{ + return gdk_content_formats_new_for_gtype (G_TYPE_STRING); +} + +static gboolean +gtk_text_content_get_value (GdkContentProvider *provider, + GValue *value, + GError **error) +{ + GtkTextContent *content = GTK_TEXT_CONTENT (provider); + + if (G_VALUE_HOLDS (value, G_TYPE_STRING)) + { + int start, end; + + if (gtk_text_get_selection_bounds (content->self, &start, &end)) + { + char *str = gtk_text_get_display_text (content->self, start, end); + g_value_take_string (value, str); + } + return TRUE; + } + + return GDK_CONTENT_PROVIDER_CLASS (gtk_text_content_parent_class)->get_value (provider, value, error); +} + +static void +gtk_text_content_detach (GdkContentProvider *provider, + GdkClipboard *clipboard) +{ + GtkTextContent *content = GTK_TEXT_CONTENT (provider); + int current_pos, selection_bound; + + gtk_text_get_selection_bounds (content->self, ¤t_pos, &selection_bound); + gtk_text_set_selection_bounds (content->self, current_pos, current_pos); +} + +static void +gtk_text_content_class_init (GtkTextContentClass *class) +{ + GdkContentProviderClass *provider_class = GDK_CONTENT_PROVIDER_CLASS (class); + + provider_class->ref_formats = gtk_text_content_ref_formats; + provider_class->get_value = gtk_text_content_get_value; + provider_class->detach_clipboard = gtk_text_content_detach; +} + +static void +gtk_text_content_init (GtkTextContent *content) +{ +} + +/* GtkText + */ + +G_DEFINE_TYPE_WITH_CODE (GtkText, gtk_text, GTK_TYPE_WIDGET, + G_ADD_PRIVATE (GtkText) + G_IMPLEMENT_INTERFACE (GTK_TYPE_EDITABLE, gtk_text_editable_init)) + +static void +add_move_binding (GtkBindingSet *binding_set, + guint keyval, + guint modmask, + GtkMovementStep step, + int count) +{ + g_return_if_fail ((modmask & GDK_SHIFT_MASK) == 0); + + gtk_binding_entry_add_signal (binding_set, keyval, modmask, + "move-cursor", 3, + G_TYPE_ENUM, step, + G_TYPE_INT, count, + G_TYPE_BOOLEAN, FALSE); + + /* Selection-extending version */ + gtk_binding_entry_add_signal (binding_set, keyval, modmask | GDK_SHIFT_MASK, + "move-cursor", 3, + G_TYPE_ENUM, step, + G_TYPE_INT, count, + G_TYPE_BOOLEAN, TRUE); +} + +static void +gtk_text_class_init (GtkTextClass *class) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (class); + GtkWidgetClass *widget_class; + GtkBindingSet *binding_set; + + widget_class = (GtkWidgetClass*) class; + + gobject_class->dispose = gtk_text_dispose; + gobject_class->finalize = gtk_text_finalize; + gobject_class->set_property = gtk_text_set_property; + gobject_class->get_property = gtk_text_get_property; + + widget_class->destroy = gtk_text_destroy; + widget_class->unmap = gtk_text_unmap; + widget_class->realize = gtk_text_realize; + widget_class->unrealize = gtk_text_unrealize; + widget_class->measure = gtk_text_measure; + widget_class->size_allocate = gtk_text_size_allocate; + widget_class->snapshot = gtk_text_snapshot; + widget_class->grab_focus = gtk_text_grab_focus; + widget_class->style_updated = gtk_text_style_updated; + widget_class->drag_begin = gtk_text_drag_begin; + widget_class->drag_end = gtk_text_drag_end; + widget_class->direction_changed = gtk_text_direction_changed; + widget_class->state_flags_changed = gtk_text_state_flags_changed; + widget_class->display_changed = gtk_text_display_changed; + widget_class->mnemonic_activate = gtk_text_mnemonic_activate; + widget_class->popup_menu = gtk_text_popup_menu; + widget_class->drag_drop = gtk_text_drag_drop; + widget_class->drag_motion = gtk_text_drag_motion; + widget_class->drag_leave = gtk_text_drag_leave; + widget_class->drag_data_received = gtk_text_drag_data_received; + widget_class->drag_data_get = gtk_text_drag_data_get; + widget_class->drag_data_delete = gtk_text_drag_data_delete; + + class->move_cursor = gtk_text_move_cursor; + class->insert_at_cursor = gtk_text_insert_at_cursor; + class->delete_from_cursor = gtk_text_delete_from_cursor; + class->backspace = gtk_text_backspace; + class->cut_clipboard = gtk_text_cut_clipboard; + class->copy_clipboard = gtk_text_copy_clipboard; + class->paste_clipboard = gtk_text_paste_clipboard; + class->toggle_overwrite = gtk_text_toggle_overwrite; + class->insert_emoji = gtk_text_insert_emoji; + class->activate = gtk_text_real_activate; + + quark_password_hint = g_quark_from_static_string ("gtk-entry-password-hint"); + quark_gtk_signal = g_quark_from_static_string ("gtk-signal"); + + text_props[PROP_BUFFER] = + g_param_spec_object ("buffer", + P_("Text Buffer"), + P_("Text buffer object which actually stores self text"), + GTK_TYPE_ENTRY_BUFFER, + GTK_PARAM_READWRITE|G_PARAM_CONSTRUCT|G_PARAM_EXPLICIT_NOTIFY); + + text_props[PROP_MAX_LENGTH] = + g_param_spec_int ("max-length", + P_("Maximum length"), + P_("Maximum number of characters for this self. Zero if no maximum"), + 0, GTK_ENTRY_BUFFER_MAX_SIZE, + 0, + GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY); + + text_props[PROP_HAS_FRAME] = + g_param_spec_boolean ("has-frame", + P_("Has Frame"), + P_("FALSE removes outside bevel from self"), + FALSE, + GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY); + + text_props[PROP_INVISIBLE_CHAR] = + g_param_spec_unichar ("invisible-char", + P_("Invisible character"), + P_("The character to use when masking self contents (in “password mode”)"), + '*', + GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY); + + text_props[PROP_ACTIVATES_DEFAULT] = + g_param_spec_boolean ("activates-default", + P_("Activates default"), + P_("Whether to activate the default widget (such as the default button in a dialog) when Enter is pressed"), + FALSE, + GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY); + + text_props[PROP_SCROLL_OFFSET] = + g_param_spec_int ("scroll-offset", + P_("Scroll offset"), + P_("Number of pixels of the self scrolled off the screen to the left"), + 0, G_MAXINT, + 0, + GTK_PARAM_READABLE|G_PARAM_EXPLICIT_NOTIFY); + + /** + * GtkText:truncate-multiline: + * + * When %TRUE, pasted multi-line text is truncated to the first line. + */ + text_props[PROP_TRUNCATE_MULTILINE] = + g_param_spec_boolean ("truncate-multiline", + P_("Truncate multiline"), + P_("Whether to truncate multiline pastes to one line."), + FALSE, + GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY); + + /** + * GtkText:overwrite-mode: + * + * If text is overwritten when typing in the #GtkText. + */ + text_props[PROP_OVERWRITE_MODE] = + g_param_spec_boolean ("overwrite-mode", + P_("Overwrite mode"), + P_("Whether new text overwrites existing text"), + FALSE, + GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY); + + /** + * GtkText:invisible-char-set: + * + * Whether the invisible char has been set for the #GtkText. + */ + text_props[PROP_INVISIBLE_CHAR_SET] = + g_param_spec_boolean ("invisible-char-set", + P_("Invisible character set"), + P_("Whether the invisible character has been set"), + FALSE, + GTK_PARAM_READWRITE); + + /** + * GtkText:placeholder-text: + * + * The text that will be displayed in the #GtkText when it is empty + * and unfocused. + */ + text_props[PROP_PLACEHOLDER_TEXT] = + g_param_spec_string ("placeholder-text", + P_("Placeholder text"), + P_("Show text in the self when it’s empty and unfocused"), + NULL, + GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY); + + /** + * GtkText:im-module: + * + * Which IM (input method) module should be used for this self. + * See #GtkIMContext. + * + * Setting this to a non-%NULL value overrides the + * system-wide IM module setting. See the GtkSettings + * #GtkSettings:gtk-im-module property. + */ + text_props[PROP_IM_MODULE] = + g_param_spec_string ("im-module", + P_("IM module"), + P_("Which IM module should be used"), + NULL, + GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY); + + /** + * GtkText:input-purpose: + * + * The purpose of this text field. + * + * This property can be used by on-screen keyboards and other input + * methods to adjust their behaviour. + * + * Note that setting the purpose to %GTK_INPUT_PURPOSE_PASSWORD or + * %GTK_INPUT_PURPOSE_PIN is independent from setting + * #GtkText:visibility. + */ + text_props[PROP_INPUT_PURPOSE] = + g_param_spec_enum ("input-purpose", + P_("Purpose"), + P_("Purpose of the text field"), + GTK_TYPE_INPUT_PURPOSE, + GTK_INPUT_PURPOSE_FREE_FORM, + GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY); + + /** + * GtkText:input-hints: + * + * Additional hints (beyond #GtkText:input-purpose) that + * allow input methods to fine-tune their behaviour. + */ + text_props[PROP_INPUT_HINTS] = + g_param_spec_flags ("input-hints", + P_("hints"), + P_("Hints for the text field behaviour"), + GTK_TYPE_INPUT_HINTS, + GTK_INPUT_HINT_NONE, + GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY); + + /** + * GtkText:attributes: + * + * A list of Pango attributes to apply to the text of the self. + * + * This is mainly useful to change the size or weight of the text. + * + * The #PangoAttribute's @start_index and @end_index must refer to the + * #GtkEntryBuffer text, i.e. without the preedit string. + */ + text_props[PROP_ATTRIBUTES] = + g_param_spec_boxed ("attributes", + P_("Attributes"), + P_("A list of style attributes to apply to the text of the self"), + PANGO_TYPE_ATTR_LIST, + GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY); + + /** + * GtkText:populate-all: + * + * If :populate-all is %TRUE, the #GtkText::populate-popup + * signal is also emitted for touch popups. + */ + text_props[PROP_POPULATE_ALL] = + g_param_spec_boolean ("populate-all", + P_("Populate all"), + P_("Whether to emit ::populate-popup for touch popups"), + FALSE, + GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY); + + /** + * GtkText::tabs: + * + * A list of tabstops to apply to the text of the self. + */ + text_props[PROP_TABS] = + g_param_spec_boxed ("tabs", + P_("Tabs"), + P_("A list of tabstop locations to apply to the text of the self"), + PANGO_TYPE_TAB_ARRAY, + GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY); + + text_props[PROP_ENABLE_EMOJI_COMPLETION] = + g_param_spec_boolean ("enable-emoji-completion", + P_("Enable Emoji completion"), + P_("Whether to suggest Emoji replacements"), + FALSE, + GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY); + + text_props[PROP_VISIBILITY] = + g_param_spec_boolean ("visibility", + P_("Visibility"), + P_("FALSE displays the “invisible char” instead of the actual text (password mode)"), + TRUE, + GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY); + + g_object_class_install_properties (gobject_class, NUM_PROPERTIES, text_props); + + gtk_editable_install_properties (gobject_class, NUM_PROPERTIES); + + /** + * GtkText::populate-popup: + * @self: The self on which the signal is emitted + * @widget: the container that is being populated + * + * The ::populate-popup signal gets emitted before showing the + * context menu of the self. + * + * If you need to add items to the context menu, connect + * to this signal and append your items to the @widget, which + * will be a #GtkMenu in this case. + * + * If #GtkText:populate-all is %TRUE, this signal will + * also be emitted to populate touch popups. In this case, + * @widget will be a different container, e.g. a #GtkToolbar. + * The signal handler should not make assumptions about the + * type of @widget. + */ + signals[POPULATE_POPUP] = + g_signal_new (I_("populate-popup"), + G_OBJECT_CLASS_TYPE (gobject_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GtkTextClass, populate_popup), + NULL, NULL, + NULL, + G_TYPE_NONE, 1, + GTK_TYPE_WIDGET); + + /* Action signals */ + + /** + * GtkText::activate: + * @self: The self on which the signal is emitted + * + * The ::activate signal is emitted when the user hits + * the Enter key. + * + * The default bindings for this signal are all forms of the Enter key. + */ + signals[ACTIVATE] = + g_signal_new (I_("activate"), + G_OBJECT_CLASS_TYPE (gobject_class), + G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, + G_STRUCT_OFFSET (GtkTextClass, activate), + NULL, NULL, + NULL, + G_TYPE_NONE, 0); + + /** + * GtkText::move-cursor: + * @self: the object which received the signal + * @step: the granularity of the move, as a #GtkMovementStep + * @count: the number of @step units to move + * @extend: %TRUE if the move should extend the selection + * + * The ::move-cursor signal is a + * [keybinding signal][GtkBindingSignal] + * which gets emitted when the user initiates a cursor movement. + * If the cursor is not visible in @self, this signal causes + * the viewport to be moved instead. + * + * Applications should not connect to it, but may emit it with + * g_signal_emit_by_name() if they need to control the cursor + * programmatically. + * + * The default bindings for this signal come in two variants, + * the variant with the Shift modifier extends the selection, + * the variant without the Shift modifer does not. + * There are too many key combinations to list them all here. + * - Arrow keys move by individual characters/lines + * - Ctrl-arrow key combinations move by words/paragraphs + * - Home/End keys move to the ends of the buffer + */ + signals[MOVE_CURSOR] = + g_signal_new (I_("move-cursor"), + G_OBJECT_CLASS_TYPE (gobject_class), + G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, + G_STRUCT_OFFSET (GtkTextClass, move_cursor), + NULL, NULL, + _gtk_marshal_VOID__ENUM_INT_BOOLEAN, + G_TYPE_NONE, 3, + GTK_TYPE_MOVEMENT_STEP, + G_TYPE_INT, + G_TYPE_BOOLEAN); + + /** + * GtkText::insert-at-cursor: + * @self: the object which received the signal + * @string: the string to insert + * + * The ::insert-at-cursor signal is a + * [keybinding signal][GtkBindingSignal] + * which gets emitted when the user initiates the insertion of a + * fixed string at the cursor. + * + * This signal has no default bindings. + */ + signals[INSERT_AT_CURSOR] = + g_signal_new (I_("insert-at-cursor"), + G_OBJECT_CLASS_TYPE (gobject_class), + G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, + G_STRUCT_OFFSET (GtkTextClass, insert_at_cursor), + NULL, NULL, + NULL, + G_TYPE_NONE, 1, + G_TYPE_STRING); + + /** + * GtkText::delete-from-cursor: + * @self: the object which received the signal + * @type: the granularity of the deletion, as a #GtkDeleteType + * @count: the number of @type units to delete + * + * The ::delete-from-cursor signal is a + * [keybinding signal][GtkBindingSignal] + * which gets emitted when the user initiates a text deletion. + * + * If the @type is %GTK_DELETE_CHARS, GTK deletes the selection + * if there is one, otherwise it deletes the requested number + * of characters. + * + * The default bindings for this signal are + * Delete for deleting a character and Ctrl-Delete for + * deleting a word. + */ + signals[DELETE_FROM_CURSOR] = + g_signal_new (I_("delete-from-cursor"), + G_OBJECT_CLASS_TYPE (gobject_class), + G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, + G_STRUCT_OFFSET (GtkTextClass, delete_from_cursor), + NULL, NULL, + _gtk_marshal_VOID__ENUM_INT, + G_TYPE_NONE, 2, + GTK_TYPE_DELETE_TYPE, + G_TYPE_INT); + + /** + * GtkText::backspace: + * @self: the object which received the signal + * + * The ::backspace signal is a + * [keybinding signal][GtkBindingSignal] + * which gets emitted when the user asks for it. + * + * The default bindings for this signal are + * Backspace and Shift-Backspace. + */ + signals[BACKSPACE] = + g_signal_new (I_("backspace"), + G_OBJECT_CLASS_TYPE (gobject_class), + G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, + G_STRUCT_OFFSET (GtkTextClass, backspace), + NULL, NULL, + NULL, + G_TYPE_NONE, 0); + + /** + * GtkText::cut-clipboard: + * @self: the object which received the signal + * + * The ::cut-clipboard signal is a + * [keybinding signal][GtkBindingSignal] + * which gets emitted to cut the selection to the clipboard. + * + * The default bindings for this signal are + * Ctrl-x and Shift-Delete. + */ + signals[CUT_CLIPBOARD] = + g_signal_new (I_("cut-clipboard"), + G_OBJECT_CLASS_TYPE (gobject_class), + G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, + G_STRUCT_OFFSET (GtkTextClass, cut_clipboard), + NULL, NULL, + NULL, + G_TYPE_NONE, 0); + + /** + * GtkText::copy-clipboard: + * @self: the object which received the signal + * + * The ::copy-clipboard signal is a + * [keybinding signal][GtkBindingSignal] + * which gets emitted to copy the selection to the clipboard. + * + * The default bindings for this signal are + * Ctrl-c and Ctrl-Insert. + */ + signals[COPY_CLIPBOARD] = + g_signal_new (I_("copy-clipboard"), + G_OBJECT_CLASS_TYPE (gobject_class), + G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, + G_STRUCT_OFFSET (GtkTextClass, copy_clipboard), + NULL, NULL, + NULL, + G_TYPE_NONE, 0); + + /** + * GtkText::paste-clipboard: + * @self: the object which received the signal + * + * The ::paste-clipboard signal is a + * [keybinding signal][GtkBindingSignal] + * which gets emitted to paste the contents of the clipboard + * into the text view. + * + * The default bindings for this signal are + * Ctrl-v and Shift-Insert. + */ + signals[PASTE_CLIPBOARD] = + g_signal_new (I_("paste-clipboard"), + G_OBJECT_CLASS_TYPE (gobject_class), + G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, + G_STRUCT_OFFSET (GtkTextClass, paste_clipboard), + NULL, NULL, + NULL, + G_TYPE_NONE, 0); + + /** + * GtkText::toggle-overwrite: + * @self: the object which received the signal + * + * The ::toggle-overwrite signal is a + * [keybinding signal][GtkBindingSignal] + * which gets emitted to toggle the overwrite mode of the self. + * + * The default bindings for this signal is Insert. + */ + signals[TOGGLE_OVERWRITE] = + g_signal_new (I_("toggle-overwrite"), + G_OBJECT_CLASS_TYPE (gobject_class), + G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, + G_STRUCT_OFFSET (GtkTextClass, toggle_overwrite), + NULL, NULL, + NULL, + G_TYPE_NONE, 0); + + /** + * GtkText::preedit-changed: + * @self: the object which received the signal + * @preedit: the current preedit string + * + * If an input method is used, the typed text will not immediately + * be committed to the buffer. So if you are interested in the text, + * connect to this signal. + */ + signals[PREEDIT_CHANGED] = + g_signal_new_class_handler (I_("preedit-changed"), + G_OBJECT_CLASS_TYPE (gobject_class), + G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, + NULL, + NULL, NULL, + NULL, + G_TYPE_NONE, 1, + G_TYPE_STRING); + + + /** + * GtkText::insert-emoji: + * @self: the object which received the signal + * + * The ::insert-emoji signal is a + * [keybinding signal][GtkBindingSignal] + * which gets emitted to present the Emoji chooser for the @self. + * + * The default bindings for this signal are Ctrl-. and Ctrl-; + */ + signals[INSERT_EMOJI] = + g_signal_new (I_("insert-emoji"), + G_OBJECT_CLASS_TYPE (gobject_class), + G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, + G_STRUCT_OFFSET (GtkTextClass, insert_emoji), + NULL, NULL, + NULL, + G_TYPE_NONE, 0); + + /* + * Key bindings + */ + + binding_set = gtk_binding_set_by_class (class); + + /* Moving the insertion point */ + add_move_binding (binding_set, GDK_KEY_Right, 0, + GTK_MOVEMENT_VISUAL_POSITIONS, 1); + + add_move_binding (binding_set, GDK_KEY_Left, 0, + GTK_MOVEMENT_VISUAL_POSITIONS, -1); + + add_move_binding (binding_set, GDK_KEY_KP_Right, 0, + GTK_MOVEMENT_VISUAL_POSITIONS, 1); + + add_move_binding (binding_set, GDK_KEY_KP_Left, 0, + GTK_MOVEMENT_VISUAL_POSITIONS, -1); + + add_move_binding (binding_set, GDK_KEY_Right, GDK_CONTROL_MASK, + GTK_MOVEMENT_WORDS, 1); + + add_move_binding (binding_set, GDK_KEY_Left, GDK_CONTROL_MASK, + GTK_MOVEMENT_WORDS, -1); + + add_move_binding (binding_set, GDK_KEY_KP_Right, GDK_CONTROL_MASK, + GTK_MOVEMENT_WORDS, 1); + + add_move_binding (binding_set, GDK_KEY_KP_Left, GDK_CONTROL_MASK, + GTK_MOVEMENT_WORDS, -1); + + add_move_binding (binding_set, GDK_KEY_Home, 0, + GTK_MOVEMENT_DISPLAY_LINE_ENDS, -1); + + add_move_binding (binding_set, GDK_KEY_End, 0, + GTK_MOVEMENT_DISPLAY_LINE_ENDS, 1); + + add_move_binding (binding_set, GDK_KEY_KP_Home, 0, + GTK_MOVEMENT_DISPLAY_LINE_ENDS, -1); + + add_move_binding (binding_set, GDK_KEY_KP_End, 0, + GTK_MOVEMENT_DISPLAY_LINE_ENDS, 1); + + add_move_binding (binding_set, GDK_KEY_Home, GDK_CONTROL_MASK, + GTK_MOVEMENT_BUFFER_ENDS, -1); + + add_move_binding (binding_set, GDK_KEY_End, GDK_CONTROL_MASK, + GTK_MOVEMENT_BUFFER_ENDS, 1); + + add_move_binding (binding_set, GDK_KEY_KP_Home, GDK_CONTROL_MASK, + GTK_MOVEMENT_BUFFER_ENDS, -1); + + add_move_binding (binding_set, GDK_KEY_KP_End, GDK_CONTROL_MASK, + GTK_MOVEMENT_BUFFER_ENDS, 1); + + /* Select all + */ + gtk_binding_entry_add_signal (binding_set, GDK_KEY_a, GDK_CONTROL_MASK, + "move-cursor", 3, + GTK_TYPE_MOVEMENT_STEP, GTK_MOVEMENT_BUFFER_ENDS, + G_TYPE_INT, -1, + G_TYPE_BOOLEAN, FALSE); + gtk_binding_entry_add_signal (binding_set, GDK_KEY_a, GDK_CONTROL_MASK, + "move-cursor", 3, + GTK_TYPE_MOVEMENT_STEP, GTK_MOVEMENT_BUFFER_ENDS, + G_TYPE_INT, 1, + G_TYPE_BOOLEAN, TRUE); + + gtk_binding_entry_add_signal (binding_set, GDK_KEY_slash, GDK_CONTROL_MASK, + "move-cursor", 3, + GTK_TYPE_MOVEMENT_STEP, GTK_MOVEMENT_BUFFER_ENDS, + G_TYPE_INT, -1, + G_TYPE_BOOLEAN, FALSE); + gtk_binding_entry_add_signal (binding_set, GDK_KEY_slash, GDK_CONTROL_MASK, + "move-cursor", 3, + GTK_TYPE_MOVEMENT_STEP, GTK_MOVEMENT_BUFFER_ENDS, + G_TYPE_INT, 1, + G_TYPE_BOOLEAN, TRUE); + /* Unselect all + */ + gtk_binding_entry_add_signal (binding_set, GDK_KEY_backslash, GDK_CONTROL_MASK, + "move-cursor", 3, + GTK_TYPE_MOVEMENT_STEP, GTK_MOVEMENT_VISUAL_POSITIONS, + G_TYPE_INT, 0, + G_TYPE_BOOLEAN, FALSE); + gtk_binding_entry_add_signal (binding_set, GDK_KEY_a, GDK_SHIFT_MASK | GDK_CONTROL_MASK, + "move-cursor", 3, + GTK_TYPE_MOVEMENT_STEP, GTK_MOVEMENT_VISUAL_POSITIONS, + G_TYPE_INT, 0, + G_TYPE_BOOLEAN, FALSE); + + /* Activate + */ + gtk_binding_entry_add_signal (binding_set, GDK_KEY_Return, 0, + "activate", 0); + gtk_binding_entry_add_signal (binding_set, GDK_KEY_ISO_Enter, 0, + "activate", 0); + gtk_binding_entry_add_signal (binding_set, GDK_KEY_KP_Enter, 0, + "activate", 0); + + /* Deleting text */ + gtk_binding_entry_add_signal (binding_set, GDK_KEY_Delete, 0, + "delete-from-cursor", 2, + G_TYPE_ENUM, GTK_DELETE_CHARS, + G_TYPE_INT, 1); + + gtk_binding_entry_add_signal (binding_set, GDK_KEY_KP_Delete, 0, + "delete-from-cursor", 2, + G_TYPE_ENUM, GTK_DELETE_CHARS, + G_TYPE_INT, 1); + + gtk_binding_entry_add_signal (binding_set, GDK_KEY_BackSpace, 0, + "backspace", 0); + + gtk_binding_entry_add_signal (binding_set, GDK_KEY_u, GDK_CONTROL_MASK, + "delete-from-cursor", 2, + G_TYPE_ENUM, GTK_DELETE_PARAGRAPH_ENDS, + G_TYPE_INT, -1); + + /* Make this do the same as Backspace, to help with mis-typing */ + gtk_binding_entry_add_signal (binding_set, GDK_KEY_BackSpace, GDK_SHIFT_MASK, + "backspace", 0); + + gtk_binding_entry_add_signal (binding_set, GDK_KEY_Delete, GDK_CONTROL_MASK, + "delete-from-cursor", 2, + G_TYPE_ENUM, GTK_DELETE_WORD_ENDS, + G_TYPE_INT, 1); + + gtk_binding_entry_add_signal (binding_set, GDK_KEY_KP_Delete, GDK_CONTROL_MASK, + "delete-from-cursor", 2, + G_TYPE_ENUM, GTK_DELETE_WORD_ENDS, + G_TYPE_INT, 1); + + gtk_binding_entry_add_signal (binding_set, GDK_KEY_BackSpace, GDK_CONTROL_MASK, + "delete-from-cursor", 2, + G_TYPE_ENUM, GTK_DELETE_WORD_ENDS, + G_TYPE_INT, -1); + + /* Cut/copy/paste */ + + gtk_binding_entry_add_signal (binding_set, GDK_KEY_x, GDK_CONTROL_MASK, + "cut-clipboard", 0); + gtk_binding_entry_add_signal (binding_set, GDK_KEY_c, GDK_CONTROL_MASK, + "copy-clipboard", 0); + gtk_binding_entry_add_signal (binding_set, GDK_KEY_v, GDK_CONTROL_MASK, + "paste-clipboard", 0); + + gtk_binding_entry_add_signal (binding_set, GDK_KEY_Delete, GDK_SHIFT_MASK, + "cut-clipboard", 0); + gtk_binding_entry_add_signal (binding_set, GDK_KEY_Insert, GDK_CONTROL_MASK, + "copy-clipboard", 0); + gtk_binding_entry_add_signal (binding_set, GDK_KEY_Insert, GDK_SHIFT_MASK, + "paste-clipboard", 0); + + gtk_binding_entry_add_signal (binding_set, GDK_KEY_KP_Delete, GDK_SHIFT_MASK, + "cut-clipboard", 0); + gtk_binding_entry_add_signal (binding_set, GDK_KEY_KP_Insert, GDK_CONTROL_MASK, + "copy-clipboard", 0); + gtk_binding_entry_add_signal (binding_set, GDK_KEY_KP_Insert, GDK_SHIFT_MASK, + "paste-clipboard", 0); + + /* Overwrite */ + gtk_binding_entry_add_signal (binding_set, GDK_KEY_Insert, 0, + "toggle-overwrite", 0); + gtk_binding_entry_add_signal (binding_set, GDK_KEY_KP_Insert, 0, + "toggle-overwrite", 0); + + /* Emoji */ + gtk_binding_entry_add_signal (binding_set, GDK_KEY_period, GDK_CONTROL_MASK, + "insert-emoji", 0); + gtk_binding_entry_add_signal (binding_set, GDK_KEY_semicolon, GDK_CONTROL_MASK, + "insert-emoji", 0); + + gtk_widget_class_set_accessible_type (widget_class, GTK_TYPE_ENTRY_ACCESSIBLE); + gtk_widget_class_set_css_name (widget_class, I_("text")); +} + +static void +editable_insert_text (GtkEditable *editable, + const char *text, + int length, + int *position) +{ + gtk_text_insert_text (GTK_TEXT (editable), text, length, position); +} + +static void +editable_delete_text (GtkEditable *editable, + int start_pos, + int end_pos) +{ + gtk_text_delete_text (GTK_TEXT (editable), start_pos, end_pos); +} + +static const char * +editable_get_text (GtkEditable *editable) +{ + return gtk_entry_buffer_get_text (get_buffer (GTK_TEXT (editable))); +} + +static void +editable_set_selection_bounds (GtkEditable *editable, + int start_pos, + int end_pos) +{ + gtk_text_set_selection_bounds (GTK_TEXT (editable), start_pos, end_pos); +} + +static gboolean +editable_get_selection_bounds (GtkEditable *editable, + int *start_pos, + int *end_pos) +{ + return gtk_text_get_selection_bounds (GTK_TEXT (editable), start_pos, end_pos); +} + +static void +gtk_text_editable_init (GtkEditableInterface *iface) +{ + iface->insert_text = editable_insert_text; + iface->delete_text = editable_delete_text; + iface->get_text = editable_get_text; + iface->set_selection_bounds = editable_set_selection_bounds; + iface->get_selection_bounds = editable_get_selection_bounds; +} + +static void +gtk_text_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GtkText *self = GTK_TEXT (object); + GtkTextPrivate *priv = gtk_text_get_instance_private (self); + + switch (prop_id) + { + /* GtkEditable properties */ + case NUM_PROPERTIES + GTK_EDITABLE_PROP_EDITABLE: + gtk_text_set_editable (self, g_value_get_boolean (value)); + break; + + case NUM_PROPERTIES + GTK_EDITABLE_PROP_WIDTH_CHARS: + gtk_text_set_width_chars (self, g_value_get_int (value)); + break; + + case NUM_PROPERTIES + GTK_EDITABLE_PROP_MAX_WIDTH_CHARS: + gtk_text_set_max_width_chars (self, g_value_get_int (value)); + break; + + case NUM_PROPERTIES + GTK_EDITABLE_PROP_TEXT: + gtk_text_set_text (self, g_value_get_string (value)); + break; + + case NUM_PROPERTIES + GTK_EDITABLE_PROP_XALIGN: + gtk_text_set_alignment (self, g_value_get_float (value)); + break; + + /* GtkText properties */ + case PROP_BUFFER: + gtk_text_set_buffer (self, g_value_get_object (value)); + break; + + case PROP_MAX_LENGTH: + gtk_text_set_max_length (self, g_value_get_int (value)); + break; + + case PROP_VISIBILITY: + gtk_text_set_visibility (self, g_value_get_boolean (value)); + break; + + case PROP_HAS_FRAME: + gtk_text_set_has_frame (self, g_value_get_boolean (value)); + break; + + case PROP_INVISIBLE_CHAR: + gtk_text_set_invisible_char (self, g_value_get_uint (value)); + break; + + case PROP_ACTIVATES_DEFAULT: + gtk_text_set_activates_default (self, g_value_get_boolean (value)); + break; + + case PROP_TRUNCATE_MULTILINE: + if (priv->truncate_multiline != g_value_get_boolean (value)) + { + priv->truncate_multiline = g_value_get_boolean (value); + g_object_notify_by_pspec (object, pspec); + } + break; + + case PROP_OVERWRITE_MODE: + gtk_text_set_overwrite_mode (self, g_value_get_boolean (value)); + break; + + case PROP_INVISIBLE_CHAR_SET: + if (g_value_get_boolean (value)) + priv->invisible_char_set = TRUE; + else + gtk_text_unset_invisible_char (self); + break; + + case PROP_PLACEHOLDER_TEXT: + gtk_text_set_placeholder_text (self, g_value_get_string (value)); + break; + + case PROP_IM_MODULE: + g_free (priv->im_module); + priv->im_module = g_value_dup_string (value); + if (GTK_IS_IM_MULTICONTEXT (priv->im_context)) + gtk_im_multicontext_set_context_id (GTK_IM_MULTICONTEXT (priv->im_context), priv->im_module); + g_object_notify_by_pspec (object, pspec); + break; + + case PROP_INPUT_PURPOSE: + gtk_text_set_input_purpose (self, g_value_get_enum (value)); + break; + + case PROP_INPUT_HINTS: + gtk_text_set_input_hints (self, g_value_get_flags (value)); + break; + + case PROP_ATTRIBUTES: + gtk_text_set_attributes (self, g_value_get_boxed (value)); + break; + + case PROP_POPULATE_ALL: + if (priv->populate_all != g_value_get_boolean (value)) + { + priv->populate_all = g_value_get_boolean (value); + g_object_notify_by_pspec (object, pspec); + } + break; + + case PROP_TABS: + gtk_text_set_tabs (self, g_value_get_boxed (value)); + break; + + case PROP_ENABLE_EMOJI_COMPLETION: + set_enable_emoji_completion (self, g_value_get_boolean (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gtk_text_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GtkText *self = GTK_TEXT (object); + GtkTextPrivate *priv = gtk_text_get_instance_private (self); + + switch (prop_id) + { + /* GtkEditable properties */ + case NUM_PROPERTIES + GTK_EDITABLE_PROP_CURSOR_POSITION: + g_value_set_int (value, priv->current_pos); + break; + + case NUM_PROPERTIES + GTK_EDITABLE_PROP_SELECTION_BOUND: + g_value_set_int (value, priv->selection_bound); + break; + + case NUM_PROPERTIES + GTK_EDITABLE_PROP_EDITABLE: + g_value_set_boolean (value, priv->editable); + break; + + case NUM_PROPERTIES + GTK_EDITABLE_PROP_WIDTH_CHARS: + g_value_set_int (value, priv->width_chars); + break; + + case NUM_PROPERTIES + GTK_EDITABLE_PROP_MAX_WIDTH_CHARS: + g_value_set_int (value, priv->max_width_chars); + break; + + case NUM_PROPERTIES + GTK_EDITABLE_PROP_TEXT: + g_value_set_string (value, gtk_entry_buffer_get_text (get_buffer (self))); + break; + + case NUM_PROPERTIES + GTK_EDITABLE_PROP_XALIGN: + g_value_set_float (value, priv->xalign); + break; + + /* GtkText properties */ + case PROP_BUFFER: + g_value_set_object (value, get_buffer (self)); + break; + + case PROP_MAX_LENGTH: + g_value_set_int (value, gtk_entry_buffer_get_max_length (get_buffer (self))); + break; + + case PROP_VISIBILITY: + g_value_set_boolean (value, priv->visible); + break; + + case PROP_HAS_FRAME: + g_value_set_boolean (value, gtk_text_get_has_frame (self)); + break; + + case PROP_INVISIBLE_CHAR: + g_value_set_uint (value, priv->invisible_char); + break; + + case PROP_ACTIVATES_DEFAULT: + g_value_set_boolean (value, priv->activates_default); + break; + + case PROP_SCROLL_OFFSET: + g_value_set_int (value, priv->scroll_offset); + break; + + case PROP_TRUNCATE_MULTILINE: + g_value_set_boolean (value, priv->truncate_multiline); + break; + + case PROP_OVERWRITE_MODE: + g_value_set_boolean (value, priv->overwrite_mode); + break; + + case PROP_INVISIBLE_CHAR_SET: + g_value_set_boolean (value, priv->invisible_char_set); + break; + + case PROP_IM_MODULE: + g_value_set_string (value, priv->im_module); + break; + + case PROP_PLACEHOLDER_TEXT: + g_value_set_string (value, gtk_text_get_placeholder_text (self)); + break; + + case PROP_INPUT_PURPOSE: + g_value_set_enum (value, gtk_text_get_input_purpose (self)); + break; + + case PROP_INPUT_HINTS: + g_value_set_flags (value, gtk_text_get_input_hints (self)); + break; + + case PROP_ATTRIBUTES: + g_value_set_boxed (value, priv->attrs); + break; + + case PROP_POPULATE_ALL: + g_value_set_boolean (value, priv->populate_all); + break; + + case PROP_TABS: + g_value_set_boxed (value, priv->tabs); + break; + + case PROP_ENABLE_EMOJI_COMPLETION: + g_value_set_boolean (value, priv->enable_emoji_completion); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gtk_text_init (GtkText *self) +{ + GtkTextPrivate *priv = gtk_text_get_instance_private (self); + GtkCssNode *widget_node; + GtkGesture *gesture; + GtkEventController *controller; + int i; + + gtk_widget_set_can_focus (GTK_WIDGET (self), TRUE); + gtk_widget_set_has_surface (GTK_WIDGET (self), FALSE); + + priv->editable = TRUE; + priv->visible = TRUE; + priv->dnd_position = -1; + priv->width_chars = -1; + priv->max_width_chars = -1; + priv->editing_canceled = FALSE; + priv->truncate_multiline = FALSE; + priv->xalign = 0.0; + priv->insert_pos = -1; + + priv->selection_content = g_object_new (GTK_TYPE_TEXT_CONTENT, NULL); + GTK_TEXT_CONTENT (priv->selection_content)->self = self; + + gtk_drag_dest_set (GTK_WIDGET (self), 0, NULL, + GDK_ACTION_COPY | GDK_ACTION_MOVE); + gtk_drag_dest_add_text_targets (GTK_WIDGET (self)); + + /* This object is completely private. No external entity can gain a reference + * to it; so we create it here and destroy it in finalize(). + */ + priv->im_context = gtk_im_multicontext_new (); + + g_signal_connect (priv->im_context, "commit", + G_CALLBACK (gtk_text_commit_cb), self); + g_signal_connect (priv->im_context, "preedit-changed", + G_CALLBACK (gtk_text_preedit_changed_cb), self); + g_signal_connect (priv->im_context, "retrieve-surrounding", + G_CALLBACK (gtk_text_retrieve_surrounding_cb), self); + g_signal_connect (priv->im_context, "delete-surrounding", + G_CALLBACK (gtk_text_delete_surrounding_cb), self); + + gtk_text_update_cached_style_values (self); + + priv->drag_gesture = gtk_gesture_drag_new (); + g_signal_connect (priv->drag_gesture, "drag-update", + G_CALLBACK (gtk_text_drag_gesture_update), self); + g_signal_connect (priv->drag_gesture, "drag-end", + G_CALLBACK (gtk_text_drag_gesture_end), self); + gtk_gesture_single_set_button (GTK_GESTURE_SINGLE (priv->drag_gesture), 0); + gtk_gesture_single_set_exclusive (GTK_GESTURE_SINGLE (priv->drag_gesture), TRUE); + gtk_widget_add_controller (GTK_WIDGET (self), GTK_EVENT_CONTROLLER (priv->drag_gesture)); + + gesture = gtk_gesture_multi_press_new (); + g_signal_connect (gesture, "pressed", + G_CALLBACK (gtk_text_multipress_gesture_pressed), self); + gtk_gesture_single_set_button (GTK_GESTURE_SINGLE (gesture), 0); + gtk_gesture_single_set_exclusive (GTK_GESTURE_SINGLE (gesture), TRUE); + gtk_widget_add_controller (GTK_WIDGET (self), GTK_EVENT_CONTROLLER (gesture)); + + controller = gtk_event_controller_motion_new (); + g_signal_connect (controller, "motion", + G_CALLBACK (gtk_text_motion_controller_motion), self); + gtk_widget_add_controller (GTK_WIDGET (self), controller); + + priv->key_controller = gtk_event_controller_key_new (); + g_signal_connect (priv->key_controller, "key-pressed", + G_CALLBACK (gtk_text_key_controller_key_pressed), self); + g_signal_connect_swapped (priv->key_controller, "im-update", + G_CALLBACK (gtk_text_schedule_im_reset), self); + g_signal_connect_swapped (priv->key_controller, "focus-in", + G_CALLBACK (gtk_text_focus_in), self); + g_signal_connect_swapped (priv->key_controller, "focus-out", + G_CALLBACK (gtk_text_focus_out), self); + gtk_event_controller_key_set_im_context (GTK_EVENT_CONTROLLER_KEY (priv->key_controller), + priv->im_context); + gtk_widget_add_controller (GTK_WIDGET (self), priv->key_controller); + + widget_node = gtk_widget_get_css_node (GTK_WIDGET (self)); + for (i = 0; i < 2; i++) + { + priv->undershoot_node[i] = gtk_css_node_new (); + gtk_css_node_set_name (priv->undershoot_node[i], I_("undershoot")); + gtk_css_node_add_class (priv->undershoot_node[i], g_quark_from_static_string (i == 0 ? GTK_STYLE_CLASS_LEFT : GTK_STYLE_CLASS_RIGHT)); + gtk_css_node_set_parent (priv->undershoot_node[i], widget_node); + gtk_css_node_set_state (priv->undershoot_node[i], gtk_css_node_get_state (widget_node) & ~GTK_STATE_FLAG_DROP_ACTIVE); + g_object_unref (priv->undershoot_node[i]); + } + + set_text_cursor (GTK_WIDGET (self)); + gtk_text_set_has_frame (self, FALSE); +} + +static void +gtk_text_destroy (GtkWidget *widget) +{ + GtkText *self = GTK_TEXT (widget); + GtkTextPrivate *priv = gtk_text_get_instance_private (self); + + priv->current_pos = priv->selection_bound = 0; + gtk_text_reset_im_context (self); + gtk_text_reset_layout (self); + + if (priv->blink_timeout) + { + g_source_remove (priv->blink_timeout); + priv->blink_timeout = 0; + } + + if (priv->magnifier) + _gtk_magnifier_set_inspected (GTK_MAGNIFIER (priv->magnifier), NULL); + + GTK_WIDGET_CLASS (gtk_text_parent_class)->destroy (widget); +} + +static void +gtk_text_dispose (GObject *object) +{ + GtkText *self = GTK_TEXT (object); + GtkTextPrivate *priv = gtk_text_get_instance_private (self); + GdkKeymap *keymap; + + priv->current_pos = 0; + + if (priv->buffer) + { + buffer_disconnect_signals (self); + g_object_unref (priv->buffer); + priv->buffer = NULL; + } + + keymap = gdk_display_get_keymap (gtk_widget_get_display (GTK_WIDGET (object))); + g_signal_handlers_disconnect_by_func (keymap, keymap_direction_changed, self); + G_OBJECT_CLASS (gtk_text_parent_class)->dispose (object); +} + +static void +gtk_text_finalize (GObject *object) +{ + GtkText *self = GTK_TEXT (object); + GtkTextPrivate *priv = gtk_text_get_instance_private (self); + + g_clear_object (&priv->selection_content); + + g_clear_object (&priv->cached_layout); + g_clear_object (&priv->im_context); + g_clear_pointer (&priv->selection_bubble, gtk_widget_destroy); + g_clear_pointer (&priv->magnifier_popover, gtk_widget_destroy); + g_clear_object (&priv->text_handle); + g_free (priv->im_module); + + g_clear_pointer (&priv->placeholder, gtk_widget_unparent); + + if (priv->blink_timeout) + g_source_remove (priv->blink_timeout); + + if (priv->tabs) + pango_tab_array_free (priv->tabs); + + if (priv->attrs) + pango_attr_list_unref (priv->attrs); + + + G_OBJECT_CLASS (gtk_text_parent_class)->finalize (object); +} + +static void +gtk_text_ensure_magnifier (GtkText *self) +{ + GtkTextPrivate *priv = gtk_text_get_instance_private (self); + + if (priv->magnifier_popover) + return; + + priv->magnifier = _gtk_magnifier_new (GTK_WIDGET (self)); + gtk_widget_set_size_request (priv->magnifier, 100, 60); + _gtk_magnifier_set_magnification (GTK_MAGNIFIER (priv->magnifier), 2.0); + priv->magnifier_popover = gtk_popover_new (GTK_WIDGET (self)); + gtk_style_context_add_class (gtk_widget_get_style_context (priv->magnifier_popover), + "magnifier"); + gtk_popover_set_modal (GTK_POPOVER (priv->magnifier_popover), FALSE); + gtk_container_add (GTK_CONTAINER (priv->magnifier_popover), + priv->magnifier); + gtk_widget_show (priv->magnifier); +} + +static void +gtk_text_ensure_text_handles (GtkText *self) +{ + GtkTextPrivate *priv = gtk_text_get_instance_private (self); + + if (priv->text_handle) + return; + + priv->text_handle = _gtk_text_handle_new (GTK_WIDGET (self)); + g_signal_connect (priv->text_handle, "drag-started", + G_CALLBACK (gtk_text_handle_drag_started), self); + g_signal_connect (priv->text_handle, "handle-dragged", + G_CALLBACK (gtk_text_handle_dragged), self); + g_signal_connect (priv->text_handle, "drag-finished", + G_CALLBACK (gtk_text_handle_drag_finished), self); +} + +static void +begin_change (GtkText *self) +{ + GtkTextPrivate *priv = gtk_text_get_instance_private (self); + + priv->change_count++; + + g_object_freeze_notify (G_OBJECT (self)); +} + +static void +end_change (GtkText *self) +{ + GtkTextPrivate *priv = gtk_text_get_instance_private (self); + + g_return_if_fail (priv->change_count > 0); + + g_object_thaw_notify (G_OBJECT (self)); + + priv->change_count--; + + if (priv->change_count == 0) + { + if (priv->real_changed) + { + g_signal_emit_by_name (self, "changed"); + priv->real_changed = FALSE; + } + } +} + +static void +emit_changed (GtkText *self) +{ + GtkTextPrivate *priv = gtk_text_get_instance_private (self); + + if (priv->change_count == 0) + g_signal_emit_by_name (self, "changed"); + else + priv->real_changed = TRUE; +} + +static DisplayMode +gtk_text_get_display_mode (GtkText *self) +{ + GtkTextPrivate *priv = gtk_text_get_instance_private (self); + + if (priv->visible) + return DISPLAY_NORMAL; + + if (priv->invisible_char == 0 && priv->invisible_char_set) + return DISPLAY_BLANK; + + return DISPLAY_INVISIBLE; +} + +char * +gtk_text_get_display_text (GtkText *self, + int start_pos, + int end_pos) +{ + GtkTextPasswordHint *password_hint; + GtkTextPrivate *priv = gtk_text_get_instance_private (self); + gunichar invisible_char; + const char *start; + const char *end; + const char *text; + char char_str[7]; + int char_len; + GString *str; + guint length; + int i; + + text = gtk_entry_buffer_get_text (get_buffer (self)); + length = gtk_entry_buffer_get_length (get_buffer (self)); + + if (end_pos < 0 || end_pos > length) + end_pos = length; + if (start_pos > length) + start_pos = length; + + if (end_pos <= start_pos) + return g_strdup (""); + else if (priv->visible) + { + start = g_utf8_offset_to_pointer (text, start_pos); + end = g_utf8_offset_to_pointer (start, end_pos - start_pos); + return g_strndup (start, end - start); + } + else + { + str = g_string_sized_new (length * 2); + + /* Figure out what our invisible char is and encode it */ + if (!priv->invisible_char) + invisible_char = priv->invisible_char_set ? ' ' : '*'; + else + invisible_char = priv->invisible_char; + char_len = g_unichar_to_utf8 (invisible_char, char_str); + + /* + * Add hidden characters for each character in the text + * buffer. If there is a password hint, then keep that + * character visible. + */ + + password_hint = g_object_get_qdata (G_OBJECT (self), quark_password_hint); + for (i = start_pos; i < end_pos; ++i) + { + if (password_hint && i == password_hint->position) + { + start = g_utf8_offset_to_pointer (text, i); + g_string_append_len (str, start, g_utf8_next_char (start) - start); + } + else + { + g_string_append_len (str, char_str, char_len); + } + } + + return g_string_free (str, FALSE); + } +} + +static void +update_node_state (GtkText *self) +{ + GtkTextPrivate *priv = gtk_text_get_instance_private (self); + GtkStateFlags state; + + state = gtk_widget_get_state_flags (GTK_WIDGET (self)); + state &= ~GTK_STATE_FLAG_DROP_ACTIVE; + + if (priv->selection_node) + gtk_css_node_set_state (priv->selection_node, state); + + if (priv->block_cursor_node) + gtk_css_node_set_state (priv->block_cursor_node, state); + + gtk_css_node_set_state (priv->undershoot_node[0], state); + gtk_css_node_set_state (priv->undershoot_node[1], state); +} + +static void +gtk_text_unmap (GtkWidget *widget) +{ + GtkText *self = GTK_TEXT (widget); + GtkTextPrivate *priv = gtk_text_get_instance_private (self); + + if (priv->text_handle) + _gtk_text_handle_set_mode (priv->text_handle, + GTK_TEXT_HANDLE_MODE_NONE); + + GTK_WIDGET_CLASS (gtk_text_parent_class)->unmap (widget); +} + +static void +gtk_text_get_text_allocation (GtkText *self, + GdkRectangle *allocation) +{ + allocation->x = 0; + allocation->y = 0; + allocation->width = gtk_widget_get_width (GTK_WIDGET (self)); + allocation->height = gtk_widget_get_height (GTK_WIDGET (self)); +} + +static void +gtk_text_realize (GtkWidget *widget) +{ + GtkText *self = GTK_TEXT (widget); + GtkTextPrivate *priv = gtk_text_get_instance_private (self); + + GTK_WIDGET_CLASS (gtk_text_parent_class)->realize (widget); + + gtk_im_context_set_client_widget (priv->im_context, widget); + + gtk_text_adjust_scroll (self); + gtk_text_update_primary_selection (self); +} + +static void +gtk_text_unrealize (GtkWidget *widget) +{ + GtkText *self = GTK_TEXT (widget); + GtkTextPrivate *priv = gtk_text_get_instance_private (self); + GdkClipboard *clipboard; + + gtk_text_reset_layout (self); + + gtk_im_context_set_client_widget (priv->im_context, NULL); + + clipboard = gtk_widget_get_primary_clipboard (widget); + if (gdk_clipboard_get_content (clipboard) == priv->selection_content) + gdk_clipboard_set_content (clipboard, NULL); + + if (priv->popup_menu) + { + gtk_widget_destroy (priv->popup_menu); + priv->popup_menu = NULL; + } + + GTK_WIDGET_CLASS (gtk_text_parent_class)->unrealize (widget); +} + +static void +gtk_text_measure (GtkWidget *widget, + GtkOrientation orientation, + int for_size, + int *minimum, + int *natural, + int *minimum_baseline, + int *natural_baseline) +{ + GtkText *self = GTK_TEXT (widget); + GtkTextPrivate *priv = gtk_text_get_instance_private (self); + PangoContext *context; + PangoFontMetrics *metrics; + + context = gtk_widget_get_pango_context (widget); + metrics = pango_context_get_metrics (context, + pango_context_get_font_description (context), + pango_context_get_language (context)); + + if (orientation == GTK_ORIENTATION_HORIZONTAL) + { + int min, nat; + int char_width; + int digit_width; + int char_pixels; + + char_width = pango_font_metrics_get_approximate_char_width (metrics); + digit_width = pango_font_metrics_get_approximate_digit_width (metrics); + char_pixels = (MAX (char_width, digit_width) + PANGO_SCALE - 1) / PANGO_SCALE; + + if (priv->width_chars >= 0) + min = char_pixels * priv->width_chars; + else + min = 0; + + if (priv->max_width_chars < 0) + nat = NAT_ENTRY_WIDTH; + else + nat = char_pixels * priv->max_width_chars; + + nat = MAX (min, nat); + + if (priv->placeholder) + { + int pmin, pnat; + + gtk_widget_measure (priv->placeholder, GTK_ORIENTATION_HORIZONTAL, -1, + &pmin, &pnat, NULL, NULL); + min = MAX (min, pmin); + nat = MAX (nat, pnat); + } + + *minimum = min; + *natural = nat; + } + else + { + int height, baseline; + PangoLayout *layout; + + layout = gtk_text_ensure_layout (self, TRUE); + + priv->ascent = pango_font_metrics_get_ascent (metrics); + priv->descent = pango_font_metrics_get_descent (metrics); + + pango_layout_get_pixel_size (layout, NULL, &height); + + height = MAX (height, PANGO_PIXELS (priv->ascent + priv->descent)); + + baseline = pango_layout_get_baseline (layout) / PANGO_SCALE; + + *minimum = *natural = height; + + if (priv->placeholder) + { + int min, nat; + + gtk_widget_measure (priv->placeholder, GTK_ORIENTATION_VERTICAL, -1, + &min, &nat, NULL, NULL); + *minimum = MAX (*minimum, min); + *natural = MAX (*natural, nat); + } + + if (minimum_baseline) + *minimum_baseline = baseline; + if (natural_baseline) + *natural_baseline = baseline; + } + + pango_font_metrics_unref (metrics); +} + +static void +gtk_text_size_allocate (GtkWidget *widget, + int width, + int height, + int baseline) +{ + GtkText *self = GTK_TEXT (widget); + GtkTextPrivate *priv = gtk_text_get_instance_private (self); + + priv->text_baseline = baseline; + priv->text_x = 0; + priv->text_width = width; + + if (priv->placeholder) + { + gtk_widget_size_allocate (priv->placeholder, + &(GtkAllocation) { 0, 0, width, height }, + -1); + } + + /* Do this here instead of gtk_text_size_allocate() so it works + * inside spinbuttons, which don't chain up. + */ + if (gtk_widget_get_realized (widget)) + gtk_text_recompute (self); +} + +static void +gtk_text_draw_undershoot (GtkText *self, + GtkSnapshot *snapshot) +{ + GtkTextPrivate *priv = gtk_text_get_instance_private (self); + GtkStyleContext *context; + int min_offset, max_offset; + GdkRectangle rect; + + context = gtk_widget_get_style_context (GTK_WIDGET (self)); + + gtk_text_get_scroll_limits (self, &min_offset, &max_offset); + + gtk_text_get_text_allocation (self, &rect); + + if (priv->scroll_offset > min_offset) + { + gtk_style_context_save_to_node (context, priv->undershoot_node[0]); + gtk_snapshot_render_background (snapshot, context, rect.x, rect.y, UNDERSHOOT_SIZE, rect.height); + gtk_snapshot_render_frame (snapshot, context, rect.x, rect.y, UNDERSHOOT_SIZE, rect.height); + gtk_style_context_restore (context); + } + + if (priv->scroll_offset < max_offset) + { + gtk_style_context_save_to_node (context, priv->undershoot_node[1]); + gtk_snapshot_render_background (snapshot, context, rect.x + rect.width - UNDERSHOOT_SIZE, rect.y, UNDERSHOOT_SIZE, rect.height); + gtk_snapshot_render_frame (snapshot, context, rect.x + rect.width - UNDERSHOOT_SIZE, rect.y, UNDERSHOOT_SIZE, rect.height); + gtk_style_context_restore (context); + } +} + +static void +gtk_text_snapshot (GtkWidget *widget, + GtkSnapshot *snapshot) +{ + GtkText *self = GTK_TEXT (widget); + GtkTextPrivate *priv = gtk_text_get_instance_private (self); + + gtk_snapshot_push_clip (snapshot, + &GRAPHENE_RECT_INIT ( + priv->text_x, + 0, + priv->text_width, + gtk_widget_get_height (widget))); + + /* Draw text and cursor */ + if (priv->dnd_position != -1) + gtk_text_draw_cursor (GTK_TEXT (widget), snapshot, CURSOR_DND); + + if (priv->placeholder) + gtk_widget_snapshot_child (widget, priv->placeholder, snapshot); + + gtk_text_draw_text (GTK_TEXT (widget), snapshot); + + /* When no text is being displayed at all, don't show the cursor */ + if (gtk_text_get_display_mode (self) != DISPLAY_BLANK && + gtk_widget_has_focus (widget) && + priv->selection_bound == priv->current_pos && priv->cursor_visible) + gtk_text_draw_cursor (GTK_TEXT (widget), snapshot, CURSOR_STANDARD); + + gtk_snapshot_pop (snapshot); + + gtk_text_draw_undershoot (self, snapshot); +} + +static void +gtk_text_get_pixel_ranges (GtkText *self, + int **ranges, + int *n_ranges) +{ + GtkTextPrivate *priv = gtk_text_get_instance_private (self); + + if (priv->selection_bound != priv->current_pos) + { + PangoLayout *layout = gtk_text_ensure_layout (self, TRUE); + PangoLayoutLine *line = pango_layout_get_lines_readonly (layout)->data; + const char *text = pango_layout_get_text (layout); + int start_index = g_utf8_offset_to_pointer (text, priv->selection_bound) - text; + int end_index = g_utf8_offset_to_pointer (text, priv->current_pos) - text; + int real_n_ranges, i; + + pango_layout_line_get_x_ranges (line, start_index, end_index, ranges, &real_n_ranges); + + if (ranges) + { + int *r = *ranges; + + for (i = 0; i < real_n_ranges; ++i) + { + r[2 * i + 1] = (r[2 * i + 1] - r[2 * i]) / PANGO_SCALE; + r[2 * i] = r[2 * i] / PANGO_SCALE; + } + } + + if (n_ranges) + *n_ranges = real_n_ranges; + } + else + { + if (n_ranges) + *n_ranges = 0; + if (ranges) + *ranges = NULL; + } +} + +static gboolean +in_selection (GtkText *self, + int x) +{ + int *ranges; + int n_ranges, i; + int retval = FALSE; + + gtk_text_get_pixel_ranges (self, &ranges, &n_ranges); + + for (i = 0; i < n_ranges; ++i) + { + if (x >= ranges[2 * i] && x < ranges[2 * i] + ranges[2 * i + 1]) + { + retval = TRUE; + break; + } + } + + g_free (ranges); + return retval; +} + +static void +gtk_text_move_handle (GtkText *self, + GtkTextHandlePosition pos, + int x, + int y, + int height) +{ + GtkTextPrivate *priv = gtk_text_get_instance_private (self); + GtkAllocation text_allocation; + + gtk_text_get_text_allocation (self, &text_allocation); + + if (!_gtk_text_handle_get_is_dragged (priv->text_handle, pos) && + (x < 0 || x > text_allocation.width)) + { + /* Hide the handle if it's not being manipulated + * and fell outside of the visible text area. + */ + _gtk_text_handle_set_visible (priv->text_handle, pos, FALSE); + } + else + { + GdkRectangle rect; + + rect.x = x + text_allocation.x; + rect.y = y + text_allocation.y; + rect.width = 1; + rect.height = height; + + _gtk_text_handle_set_visible (priv->text_handle, pos, TRUE); + _gtk_text_handle_set_position (priv->text_handle, pos, &rect); + _gtk_text_handle_set_direction (priv->text_handle, pos, priv->resolved_dir); + } +} + +static int +gtk_text_get_selection_bound_location (GtkText *self) +{ + GtkTextPrivate *priv = gtk_text_get_instance_private (self); + PangoLayout *layout; + PangoRectangle pos; + int x; + const char *text; + int index; + + layout = gtk_text_ensure_layout (self, FALSE); + text = pango_layout_get_text (layout); + index = g_utf8_offset_to_pointer (text, priv->selection_bound) - text; + pango_layout_index_to_pos (layout, index, &pos); + + if (gtk_widget_get_direction (GTK_WIDGET (self)) == GTK_TEXT_DIR_RTL) + x = (pos.x + pos.width) / PANGO_SCALE; + else + x = pos.x / PANGO_SCALE; + + return x; +} + +static void +gtk_text_update_handles (GtkText *self, + GtkTextHandleMode mode) +{ + GtkTextPrivate *priv = gtk_text_get_instance_private (self); + GtkAllocation text_allocation; + int strong_x; + int cursor, bound; + + _gtk_text_handle_set_mode (priv->text_handle, mode); + gtk_text_get_text_allocation (self, &text_allocation); + + gtk_text_get_cursor_locations (self, &strong_x, NULL); + cursor = strong_x - priv->scroll_offset; + + if (mode == GTK_TEXT_HANDLE_MODE_SELECTION) + { + int start, end; + + bound = gtk_text_get_selection_bound_location (self) - priv->scroll_offset; + + if (priv->selection_bound > priv->current_pos) + { + start = cursor; + end = bound; + } + else + { + start = bound; + end = cursor; + } + + /* Update start selection bound */ + gtk_text_move_handle (self, GTK_TEXT_HANDLE_POSITION_SELECTION_START, + start, 0, text_allocation.height); + gtk_text_move_handle (self, GTK_TEXT_HANDLE_POSITION_SELECTION_END, + end, 0, text_allocation.height); + } + else + gtk_text_move_handle (self, GTK_TEXT_HANDLE_POSITION_CURSOR, + cursor, 0, text_allocation.height); +} + +static void +gesture_get_current_point_in_layout (GtkGestureSingle *gesture, + GtkText *self, + int *x, + int *y) +{ + int tx, ty; + GdkEventSequence *sequence; + double px, py; + + sequence = gtk_gesture_single_get_current_sequence (gesture); + gtk_gesture_get_point (GTK_GESTURE (gesture), sequence, &px, &py); + gtk_text_get_layout_offsets (self, &tx, &ty); + + if (x) + *x = px - tx; + if (y) + *y = py - ty; +} + +static void +gtk_text_multipress_gesture_pressed (GtkGestureMultiPress *gesture, + int n_press, + double widget_x, + double widget_y, + GtkText *self) +{ + GtkWidget *widget = GTK_WIDGET (self); + GtkTextPrivate *priv = gtk_text_get_instance_private (self); + GdkEventSequence *current; + const GdkEvent *event; + int x, y, sel_start, sel_end; + guint button; + int tmp_pos; + + button = gtk_gesture_single_get_current_button (GTK_GESTURE_SINGLE (gesture)); + current = gtk_gesture_single_get_current_sequence (GTK_GESTURE_SINGLE (gesture)); + event = gtk_gesture_get_last_event (GTK_GESTURE (gesture), current); + + gtk_gesture_set_sequence_state (GTK_GESTURE (gesture), current, + GTK_EVENT_SEQUENCE_CLAIMED); + gesture_get_current_point_in_layout (GTK_GESTURE_SINGLE (gesture), self, &x, &y); + gtk_text_reset_blink_time (self); + + if (!gtk_widget_has_focus (widget)) + { + priv->in_click = TRUE; + gtk_widget_grab_focus (widget); + priv->in_click = FALSE; + } + + tmp_pos = gtk_text_find_position (self, x); + + if (gdk_event_triggers_context_menu (event)) + { + gtk_text_do_popup (self, event); + } + else if (n_press == 1 && button == GDK_BUTTON_MIDDLE && + get_middle_click_paste (self)) + { + if (priv->editable) + { + priv->insert_pos = tmp_pos; + gtk_text_paste (self, gtk_widget_get_primary_clipboard (widget)); + } + else + { + gtk_widget_error_bell (widget); + } + } + else if (button == GDK_BUTTON_PRIMARY) + { + gboolean have_selection; + GtkTextHandleMode mode; + gboolean is_touchscreen, extend_selection; + GdkDevice *source; + guint state; + + sel_start = priv->selection_bound; + sel_end = priv->current_pos; + have_selection = sel_start != sel_end; + + source = gdk_event_get_source_device (event); + is_touchscreen = gtk_simulate_touchscreen () || + gdk_device_get_source (source) == GDK_SOURCE_TOUCHSCREEN; + + if (!is_touchscreen) + mode = GTK_TEXT_HANDLE_MODE_NONE; + else if (have_selection) + mode = GTK_TEXT_HANDLE_MODE_SELECTION; + else + mode = GTK_TEXT_HANDLE_MODE_CURSOR; + + if (is_touchscreen) + gtk_text_ensure_text_handles (self); + + priv->in_drag = FALSE; + priv->select_words = FALSE; + priv->select_lines = FALSE; + + gdk_event_get_state (event, &state); + + extend_selection = + (state & + gtk_widget_get_modifier_mask (widget, + GDK_MODIFIER_INTENT_EXTEND_SELECTION)); + + if (extend_selection) + gtk_text_reset_im_context (self); + + switch (n_press) + { + case 1: + if (in_selection (self, x)) + { + if (is_touchscreen) + { + if (priv->selection_bubble && + gtk_widget_get_visible (priv->selection_bubble)) + gtk_text_selection_bubble_popup_unset (self); + else + gtk_text_selection_bubble_popup_set (self); + } + else if (extend_selection) + { + /* Truncate current selection, but keep it as big as possible */ + if (tmp_pos - sel_start > sel_end - tmp_pos) + gtk_text_set_positions (self, sel_start, tmp_pos); + else + gtk_text_set_positions (self, tmp_pos, sel_end); + + /* all done, so skip the extend_to_left stuff later */ + extend_selection = FALSE; + } + else + { + /* We'll either start a drag, or clear the selection */ + priv->in_drag = TRUE; + priv->drag_start_x = x; + priv->drag_start_y = y; + } + } + else + { + gtk_text_selection_bubble_popup_unset (self); + + if (!extend_selection) + { + gtk_text_set_selection_bounds (self, tmp_pos, tmp_pos); + priv->handle_place_time = g_get_monotonic_time (); + } + else + { + /* select from the current position to the clicked position */ + if (!have_selection) + sel_start = sel_end = priv->current_pos; + + gtk_text_set_positions (self, tmp_pos, tmp_pos); + } + } + + break; + + case 2: + priv->select_words = TRUE; + gtk_text_select_word (self); + if (is_touchscreen) + mode = GTK_TEXT_HANDLE_MODE_SELECTION; + break; + + case 3: + priv->select_lines = TRUE; + gtk_text_select_line (self); + if (is_touchscreen) + mode = GTK_TEXT_HANDLE_MODE_SELECTION; + break; + + default: + break; + } + + if (extend_selection) + { + gboolean extend_to_left; + int start, end; + + start = MIN (priv->current_pos, priv->selection_bound); + start = MIN (sel_start, start); + + end = MAX (priv->current_pos, priv->selection_bound); + end = MAX (sel_end, end); + + if (tmp_pos == sel_start || tmp_pos == sel_end) + extend_to_left = (tmp_pos == start); + else + extend_to_left = (end == sel_end); + + if (extend_to_left) + gtk_text_set_positions (self, start, end); + else + gtk_text_set_positions (self, end, start); + } + + gtk_gesture_set_state (priv->drag_gesture, + GTK_EVENT_SEQUENCE_CLAIMED); + + if (priv->text_handle) + gtk_text_update_handles (self, mode); + } + + if (n_press >= 3) + gtk_event_controller_reset (GTK_EVENT_CONTROLLER (gesture)); +} + +static char * +_gtk_text_get_selected_text (GtkText *self) +{ + GtkTextPrivate *priv = gtk_text_get_instance_private (self); + + if (priv->selection_bound != priv->current_pos) + { + const char *text = gtk_entry_buffer_get_text (get_buffer (self)); + int start_index = g_utf8_offset_to_pointer (text, priv->selection_bound) - text; + int end_index = g_utf8_offset_to_pointer (text, priv->current_pos) - text; + return g_strndup (text + start_index, end_index - start_index); + } + + return NULL; +} + +static void +gtk_text_show_magnifier (GtkText *self, + int x, + int y) +{ + GtkTextPrivate *priv = gtk_text_get_instance_private (self); + GtkAllocation allocation; + cairo_rectangle_int_t rect; + GtkAllocation text_allocation; + + gtk_text_get_text_allocation (self, &text_allocation); + + gtk_text_ensure_magnifier (self); + + gtk_widget_get_allocation (GTK_WIDGET (self), &allocation); + + rect.x = x + text_allocation.x; + rect.width = 1; + rect.y = text_allocation.y; + rect.height = text_allocation.height; + + _gtk_magnifier_set_coords (GTK_MAGNIFIER (priv->magnifier), rect.x, + rect.y + rect.height / 2); + gtk_popover_set_pointing_to (GTK_POPOVER (priv->magnifier_popover), + &rect); + gtk_popover_popup (GTK_POPOVER (priv->magnifier_popover)); +} + +static void +gtk_text_motion_controller_motion (GtkEventControllerMotion *controller, + double x, + double y, + GtkText *self) +{ + GtkTextPrivate *priv = gtk_text_get_instance_private (self); + + if (priv->mouse_cursor_obscured) + { + set_text_cursor (GTK_WIDGET (self)); + priv->mouse_cursor_obscured = FALSE; + } +} + +static void +gtk_text_drag_gesture_update (GtkGestureDrag *gesture, + double offset_x, + double offset_y, + GtkText *self) +{ + GtkWidget *widget = GTK_WIDGET (self); + GtkTextPrivate *priv = gtk_text_get_instance_private (self); + GdkEventSequence *sequence; + const GdkEvent *event; + int x, y; + + gtk_text_selection_bubble_popup_unset (self); + + gesture_get_current_point_in_layout (GTK_GESTURE_SINGLE (gesture), self, &x, &y); + sequence = gtk_gesture_single_get_current_sequence (GTK_GESTURE_SINGLE (gesture)); + event = gtk_gesture_get_last_event (GTK_GESTURE (gesture), sequence); + + if (priv->mouse_cursor_obscured) + { + set_text_cursor (widget); + priv->mouse_cursor_obscured = FALSE; + } + + if (priv->select_lines) + return; + + if (priv->in_drag) + { + if (gtk_text_get_display_mode (self) == DISPLAY_NORMAL && + gtk_drag_check_threshold (widget, + priv->drag_start_x, priv->drag_start_y, + x, y)) + { + int *ranges; + int n_ranges; + GdkContentFormats *target_list = gdk_content_formats_new (NULL, 0); + guint actions = priv->editable ? GDK_ACTION_COPY | GDK_ACTION_MOVE : GDK_ACTION_COPY; + + target_list = gtk_content_formats_add_text_targets (target_list); + + gtk_text_get_pixel_ranges (self, &ranges, &n_ranges); + + gtk_drag_begin (widget, + gdk_event_get_device ((GdkEvent*) event), + target_list, actions, + priv->drag_start_x + ranges[0], + priv->drag_start_y); + g_free (ranges); + + priv->in_drag = FALSE; + + gdk_content_formats_unref (target_list); + } + } + else + { + GtkAllocation text_allocation; + GdkInputSource input_source; + GdkDevice *source; + guint length; + int tmp_pos; + + length = gtk_entry_buffer_get_length (get_buffer (self)); + gtk_text_get_text_allocation (self, &text_allocation); + + if (y < 0) + tmp_pos = 0; + else if (y >= text_allocation.height) + tmp_pos = length; + else + tmp_pos = gtk_text_find_position (self, x); + + source = gdk_event_get_source_device (event); + input_source = gdk_device_get_source (source); + + if (priv->select_words) + { + int min, max; + int old_min, old_max; + int pos, bound; + + min = gtk_text_move_backward_word (self, tmp_pos, TRUE); + max = gtk_text_move_forward_word (self, tmp_pos, TRUE); + + pos = priv->current_pos; + bound = priv->selection_bound; + + old_min = MIN (priv->current_pos, priv->selection_bound); + old_max = MAX (priv->current_pos, priv->selection_bound); + + if (min < old_min) + { + pos = min; + bound = old_max; + } + else if (old_max < max) + { + pos = max; + bound = old_min; + } + else if (pos == old_min) + { + if (priv->current_pos != min) + pos = max; + } + else + { + if (priv->current_pos != max) + pos = min; + } + + gtk_text_set_positions (self, pos, bound); + } + else + gtk_text_set_positions (self, tmp_pos, -1); + + /* Update touch handles' position */ + if (gtk_simulate_touchscreen () || + input_source == GDK_SOURCE_TOUCHSCREEN) + { + gtk_text_ensure_text_handles (self); + gtk_text_update_handles (self, + (priv->current_pos == priv->selection_bound) ? + GTK_TEXT_HANDLE_MODE_CURSOR : + GTK_TEXT_HANDLE_MODE_SELECTION); + gtk_text_show_magnifier (self, x - priv->scroll_offset, y); + } + } +} + +static void +gtk_text_drag_gesture_end (GtkGestureDrag *gesture, + double offset_x, + double offset_y, + GtkText *self) +{ + GtkTextPrivate *priv = gtk_text_get_instance_private (self); + gboolean in_drag, is_touchscreen; + GdkEventSequence *sequence; + const GdkEvent *event; + GdkDevice *source; + + sequence = gtk_gesture_single_get_current_sequence (GTK_GESTURE_SINGLE (gesture)); + in_drag = priv->in_drag; + priv->in_drag = FALSE; + + if (priv->magnifier_popover) + gtk_popover_popdown (GTK_POPOVER (priv->magnifier_popover)); + + /* Check whether the drag was cancelled rather than finished */ + if (!gtk_gesture_handles_sequence (GTK_GESTURE (gesture), sequence)) + return; + + event = gtk_gesture_get_last_event (GTK_GESTURE (gesture), sequence); + source = gdk_event_get_source_device (event); + is_touchscreen = gtk_simulate_touchscreen () || + gdk_device_get_source (source) == GDK_SOURCE_TOUCHSCREEN; + + if (in_drag) + { + int tmp_pos = gtk_text_find_position (self, priv->drag_start_x); + + gtk_text_set_selection_bounds (self, tmp_pos, tmp_pos); + } + + if (is_touchscreen && priv->selection_bound != priv->current_pos) + gtk_text_update_handles (self, GTK_TEXT_HANDLE_MODE_CURSOR); + + gtk_text_update_primary_selection (self); +} + +static void +gtk_text_obscure_mouse_cursor (GtkText *self) +{ + GtkTextPrivate *priv = gtk_text_get_instance_private (self); + GdkCursor *cursor; + + if (priv->mouse_cursor_obscured) + return; + + cursor = gdk_cursor_new_from_name ("none", NULL); + gtk_widget_set_cursor (GTK_WIDGET (self), cursor); + g_object_unref (cursor); + + priv->mouse_cursor_obscured = TRUE; +} + +static gboolean +gtk_text_key_controller_key_pressed (GtkEventControllerKey *controller, + guint keyval, + guint keycode, + GdkModifierType state, + GtkText *self) +{ + GtkTextPrivate *priv = gtk_text_get_instance_private (self); + gunichar unichar; + + gtk_text_reset_blink_time (self); + gtk_text_pend_cursor_blink (self); + + gtk_text_selection_bubble_popup_unset (self); + + if (priv->text_handle) + _gtk_text_handle_set_mode (priv->text_handle, + GTK_TEXT_HANDLE_MODE_NONE); + + if (keyval == GDK_KEY_Return || + keyval == GDK_KEY_KP_Enter || + keyval == GDK_KEY_ISO_Enter || + keyval == GDK_KEY_Escape) + gtk_text_reset_im_context (self); + + unichar = gdk_keyval_to_unicode (keyval); + + if (!priv->editable && unichar != 0) + gtk_widget_error_bell (GTK_WIDGET (self)); + + gtk_text_obscure_mouse_cursor (self); + + return FALSE; +} + +static void +gtk_text_focus_in (GtkWidget *widget) +{ + GtkText *self = GTK_TEXT (widget); + GtkTextPrivate *priv = gtk_text_get_instance_private (self); + GdkKeymap *keymap; + + gtk_widget_queue_draw (widget); + + keymap = gdk_display_get_keymap (gtk_widget_get_display (widget)); + + if (priv->editable) + { + gtk_text_schedule_im_reset (self); + gtk_im_context_focus_in (priv->im_context); + } + + g_signal_connect (keymap, "direction-changed", + G_CALLBACK (keymap_direction_changed), self); + + gtk_text_reset_blink_time (self); + gtk_text_check_cursor_blink (self); +} + +static void +gtk_text_focus_out (GtkWidget *widget) +{ + GtkText *self = GTK_TEXT (widget); + GtkTextPrivate *priv = gtk_text_get_instance_private (self); + GdkKeymap *keymap; + + gtk_text_selection_bubble_popup_unset (self); + + if (priv->text_handle) + _gtk_text_handle_set_mode (priv->text_handle, + GTK_TEXT_HANDLE_MODE_NONE); + + gtk_widget_queue_draw (widget); + + keymap = gdk_display_get_keymap (gtk_widget_get_display (widget)); + + if (priv->editable) + { + gtk_text_schedule_im_reset (self); + gtk_im_context_focus_out (priv->im_context); + } + + gtk_text_check_cursor_blink (self); + + g_signal_handlers_disconnect_by_func (keymap, keymap_direction_changed, self); +} + +static void +gtk_text_grab_focus (GtkWidget *widget) +{ + GtkText *self = GTK_TEXT (widget); + GtkTextPrivate *priv = gtk_text_get_instance_private (self); + gboolean select_on_focus; + + GTK_WIDGET_CLASS (gtk_text_parent_class)->grab_focus (GTK_WIDGET (self)); + + if (priv->editable && !priv->in_click) + { + g_object_get (gtk_widget_get_settings (widget), + "gtk-entry-select-on-focus", + &select_on_focus, + NULL); + + if (select_on_focus) + gtk_text_set_selection_bounds (self, 0, -1); + } +} + +/** + * gtk_text_grab_focus_without_selecting: + * @self: a #GtkText + * + * Causes @self to have keyboard focus. + * + * It behaves like gtk_widget_grab_focus(), + * except that it doesn't select the contents of the self. + * You only want to call this on some special entries + * which the user usually doesn't want to replace all text in, + * such as search-as-you-type entries. + */ +void +gtk_text_grab_focus_without_selecting (GtkText *self) +{ + g_return_if_fail (GTK_IS_TEXT (self)); + + GTK_WIDGET_CLASS (gtk_text_parent_class)->grab_focus (GTK_WIDGET (self)); +} + +static void +gtk_text_direction_changed (GtkWidget *widget, + GtkTextDirection previous_dir) +{ + GtkText *self = GTK_TEXT (widget); + + gtk_text_recompute (self); + + GTK_WIDGET_CLASS (gtk_text_parent_class)->direction_changed (widget, previous_dir); +} + +static void +gtk_text_state_flags_changed (GtkWidget *widget, + GtkStateFlags previous_state) +{ + GtkText *self = GTK_TEXT (widget); + GtkTextPrivate *priv = gtk_text_get_instance_private (self); + + if (gtk_widget_get_realized (widget)) + { + set_text_cursor (widget); + priv->mouse_cursor_obscured = FALSE; + } + + if (!gtk_widget_is_sensitive (widget)) + { + /* Clear any selection */ + gtk_text_set_selection_bounds (self, priv->current_pos, priv->current_pos); + } + + update_node_state (self); + + gtk_text_update_cached_style_values (self); +} + +static void +gtk_text_display_changed (GtkWidget *widget, + GdkDisplay *old_display) +{ + gtk_text_recompute (GTK_TEXT (widget)); +} + +/* GtkEditable method implementations + */ +static void +gtk_text_insert_text (GtkText *self, + const char *text, + int length, + int *position) +{ + int n_inserted; + int n_chars; + + n_chars = g_utf8_strlen (text, length); + + /* + * The incoming text may a password or other secret. We make sure + * not to copy it into temporary buffers. + */ + begin_change (self); + + n_inserted = gtk_entry_buffer_insert_text (get_buffer (self), *position, text, n_chars); + + end_change (self); + + if (n_inserted != n_chars) + gtk_widget_error_bell (GTK_WIDGET (self)); + + *position += n_inserted; + + update_placeholder_visibility (self); +} + +static void +gtk_text_delete_text (GtkText *self, + int start_pos, + int end_pos) +{ + begin_change (self); + + gtk_entry_buffer_delete_text (get_buffer (self), start_pos, end_pos - start_pos); + + end_change (self); + update_placeholder_visibility (self); +} + +static void +gtk_text_set_selection_bounds (GtkText *self, + int start, + int end) +{ + guint length; + + length = gtk_entry_buffer_get_length (get_buffer (self)); + if (start < 0) + start = length; + if (end < 0) + end = length; + + gtk_text_reset_im_context (self); + + gtk_text_set_positions (self, MIN (end, length), MIN (start, length)); + + gtk_text_update_primary_selection (self); +} + +static gboolean +gtk_text_get_selection_bounds (GtkText *self, + int *start, + int *end) +{ + GtkTextPrivate *priv = gtk_text_get_instance_private (self); + + *start = priv->selection_bound; + *end = priv->current_pos; + + return (priv->selection_bound != priv->current_pos); +} + +static gunichar +find_invisible_char (GtkWidget *widget) +{ + PangoLayout *layout; + PangoAttrList *attr_list; + int i; + gunichar invisible_chars [] = { + 0x25cf, /* BLACK CIRCLE */ + 0x2022, /* BULLET */ + 0x2731, /* HEAVY ASTERISK */ + 0x273a /* SIXTEEN POINTED ASTERISK */ + }; + + layout = gtk_widget_create_pango_layout (widget, NULL); + + attr_list = pango_attr_list_new (); + pango_attr_list_insert (attr_list, pango_attr_fallback_new (FALSE)); + + pango_layout_set_attributes (layout, attr_list); + pango_attr_list_unref (attr_list); + + for (i = 0; i < G_N_ELEMENTS (invisible_chars); i++) + { + char text[7] = { 0, }; + int len, count; + + len = g_unichar_to_utf8 (invisible_chars[i], text); + pango_layout_set_text (layout, text, len); + + count = pango_layout_get_unknown_glyphs_count (layout); + + if (count == 0) + { + g_object_unref (layout); + return invisible_chars[i]; + } + } + + g_object_unref (layout); + + return '*'; +} + +static void +gtk_text_update_cached_style_values (GtkText *self) +{ + GtkTextPrivate *priv = gtk_text_get_instance_private (self); + + if (!priv->invisible_char_set) + { + gunichar ch = find_invisible_char (GTK_WIDGET (self)); + + if (priv->invisible_char != ch) + { + priv->invisible_char = ch; + g_object_notify_by_pspec (G_OBJECT (self), text_props[PROP_INVISIBLE_CHAR]); + } + } +} + +static void +gtk_text_style_updated (GtkWidget *widget) +{ + GtkText *self = GTK_TEXT (widget); + + GTK_WIDGET_CLASS (gtk_text_parent_class)->style_updated (widget); + + gtk_text_update_cached_style_values (self); +} + +static void +gtk_text_password_hint_free (GtkTextPasswordHint *password_hint) +{ + if (password_hint->source_id) + g_source_remove (password_hint->source_id); + + g_slice_free (GtkTextPasswordHint, password_hint); +} + + +static gboolean +gtk_text_remove_password_hint (gpointer data) +{ + GtkTextPasswordHint *password_hint = g_object_get_qdata (data, quark_password_hint); + password_hint->position = -1; + password_hint->source_id = 0; + + /* Force the string to be redrawn, but now without a visible character */ + gtk_text_recompute (GTK_TEXT (data)); + + return G_SOURCE_REMOVE; +} + +static void +update_placeholder_visibility (GtkText *self) +{ + GtkTextPrivate *priv = gtk_text_get_instance_private (self); + + if (priv->placeholder) + gtk_widget_set_child_visible (priv->placeholder, + gtk_entry_buffer_get_length (priv->buffer) == 0); +} + +/* GtkEntryBuffer signal handlers + */ +static void +buffer_inserted_text (GtkEntryBuffer *buffer, + guint position, + const char *chars, + guint n_chars, + GtkText *self) +{ + GtkTextPrivate *priv = gtk_text_get_instance_private (self); + guint password_hint_timeout; + guint current_pos; + int selection_bound; + + current_pos = priv->current_pos; + if (current_pos > position) + current_pos += n_chars; + + selection_bound = priv->selection_bound; + if (selection_bound > position) + selection_bound += n_chars; + + gtk_text_set_positions (self, current_pos, selection_bound); + gtk_text_recompute (self); + + /* Calculate the password hint if it needs to be displayed. */ + if (n_chars == 1 && !priv->visible) + { + g_object_get (gtk_widget_get_settings (GTK_WIDGET (self)), + "gtk-entry-password-hint-timeout", &password_hint_timeout, + NULL); + + if (password_hint_timeout > 0) + { + GtkTextPasswordHint *password_hint = g_object_get_qdata (G_OBJECT (self), + quark_password_hint); + if (!password_hint) + { + password_hint = g_slice_new0 (GtkTextPasswordHint); + g_object_set_qdata_full (G_OBJECT (self), quark_password_hint, password_hint, + (GDestroyNotify)gtk_text_password_hint_free); + } + + password_hint->position = position; + if (password_hint->source_id) + g_source_remove (password_hint->source_id); + password_hint->source_id = g_timeout_add (password_hint_timeout, + (GSourceFunc)gtk_text_remove_password_hint, + self); + g_source_set_name_by_id (password_hint->source_id, "[gtk] gtk_text_remove_password_hint"); + } + } +} + +static void +buffer_deleted_text (GtkEntryBuffer *buffer, + guint position, + guint n_chars, + GtkText *self) +{ + GtkTextPrivate *priv = gtk_text_get_instance_private (self); + guint end_pos = position + n_chars; + int selection_bound; + guint current_pos; + + current_pos = priv->current_pos; + if (current_pos > position) + current_pos -= MIN (current_pos, end_pos) - position; + + selection_bound = priv->selection_bound; + if (selection_bound > position) + selection_bound -= MIN (selection_bound, end_pos) - position; + + gtk_text_set_positions (self, current_pos, selection_bound); + gtk_text_recompute (self); + + /* We might have deleted the selection */ + gtk_text_update_primary_selection (self); + + /* Disable the password hint if one exists. */ + if (!priv->visible) + { + GtkTextPasswordHint *password_hint = g_object_get_qdata (G_OBJECT (self), + quark_password_hint); + if (password_hint) + { + if (password_hint->source_id) + g_source_remove (password_hint->source_id); + password_hint->source_id = 0; + password_hint->position = -1; + } + } +} + +static void +buffer_notify_text (GtkEntryBuffer *buffer, + GParamSpec *spec, + GtkText *self) +{ + emit_changed (self); + g_object_notify (G_OBJECT (self), "text"); +} + +static void +buffer_notify_max_length (GtkEntryBuffer *buffer, + GParamSpec *spec, + GtkText *self) +{ + g_object_notify_by_pspec (G_OBJECT (self), text_props[PROP_MAX_LENGTH]); +} + +static void +buffer_connect_signals (GtkText *self) +{ + g_signal_connect (get_buffer (self), "inserted-text", G_CALLBACK (buffer_inserted_text), self); + g_signal_connect (get_buffer (self), "deleted-text", G_CALLBACK (buffer_deleted_text), self); + g_signal_connect (get_buffer (self), "notify::text", G_CALLBACK (buffer_notify_text), self); + g_signal_connect (get_buffer (self), "notify::max-length", G_CALLBACK (buffer_notify_max_length), self); +} + +static void +buffer_disconnect_signals (GtkText *self) +{ + g_signal_handlers_disconnect_by_func (get_buffer (self), buffer_inserted_text, self); + g_signal_handlers_disconnect_by_func (get_buffer (self), buffer_deleted_text, self); + g_signal_handlers_disconnect_by_func (get_buffer (self), buffer_notify_text, self); + g_signal_handlers_disconnect_by_func (get_buffer (self), buffer_notify_max_length, self); +} + +/* Compute the X position for an offset that corresponds to the "more important + * cursor position for that offset. We use this when trying to guess to which + * end of the selection we should go to when the user hits the left or + * right arrow key. + */ +static int +get_better_cursor_x (GtkText *self, + int offset) +{ + GtkTextPrivate *priv = gtk_text_get_instance_private (self); + GdkKeymap *keymap = gdk_display_get_keymap (gtk_widget_get_display (GTK_WIDGET (self))); + PangoDirection keymap_direction = gdk_keymap_get_direction (keymap); + gboolean split_cursor; + PangoLayout *layout = gtk_text_ensure_layout (self, TRUE); + const char *text = pango_layout_get_text (layout); + int index = g_utf8_offset_to_pointer (text, offset) - text; + PangoRectangle strong_pos, weak_pos; + + g_object_get (gtk_widget_get_settings (GTK_WIDGET (self)), + "gtk-split-cursor", &split_cursor, + NULL); + + pango_layout_get_cursor_pos (layout, index, &strong_pos, &weak_pos); + + if (split_cursor) + return strong_pos.x / PANGO_SCALE; + else + return (keymap_direction == priv->resolved_dir) ? strong_pos.x / PANGO_SCALE : weak_pos.x / PANGO_SCALE; +} + +static void +gtk_text_move_cursor (GtkText *self, + GtkMovementStep step, + int count, + gboolean extend_selection) +{ + GtkTextPrivate *priv = gtk_text_get_instance_private (self); + int new_pos = priv->current_pos; + + gtk_text_reset_im_context (self); + + if (priv->current_pos != priv->selection_bound && !extend_selection) + { + /* If we have a current selection and aren't extending it, move to the + * start/or end of the selection as appropriate + */ + switch (step) + { + case GTK_MOVEMENT_VISUAL_POSITIONS: + { + int current_x = get_better_cursor_x (self, priv->current_pos); + int bound_x = get_better_cursor_x (self, priv->selection_bound); + + if (count <= 0) + new_pos = current_x < bound_x ? priv->current_pos : priv->selection_bound; + else + new_pos = current_x > bound_x ? priv->current_pos : priv->selection_bound; + } + break; + + case GTK_MOVEMENT_WORDS: + if (priv->resolved_dir == PANGO_DIRECTION_RTL) + count *= -1; + /* Fall through */ + + case GTK_MOVEMENT_LOGICAL_POSITIONS: + if (count < 0) + new_pos = MIN (priv->current_pos, priv->selection_bound); + else + new_pos = MAX (priv->current_pos, priv->selection_bound); + + break; + + case GTK_MOVEMENT_DISPLAY_LINE_ENDS: + case GTK_MOVEMENT_PARAGRAPH_ENDS: + case GTK_MOVEMENT_BUFFER_ENDS: + new_pos = count < 0 ? 0 : gtk_entry_buffer_get_length (get_buffer (self)); + break; + + case GTK_MOVEMENT_DISPLAY_LINES: + case GTK_MOVEMENT_PARAGRAPHS: + case GTK_MOVEMENT_PAGES: + case GTK_MOVEMENT_HORIZONTAL_PAGES: + default: + break; + } + } + else + { + switch (step) + { + case GTK_MOVEMENT_LOGICAL_POSITIONS: + new_pos = gtk_text_move_logically (self, new_pos, count); + break; + + case GTK_MOVEMENT_VISUAL_POSITIONS: + new_pos = gtk_text_move_visually (self, new_pos, count); + + if (priv->current_pos == new_pos) + { + if (!extend_selection) + { + if (!gtk_widget_keynav_failed (GTK_WIDGET (self), + count > 0 ? + GTK_DIR_RIGHT : GTK_DIR_LEFT)) + { + GtkWidget *toplevel = gtk_widget_get_toplevel (GTK_WIDGET (self)); + + if (toplevel) + gtk_widget_child_focus (toplevel, + count > 0 ? + GTK_DIR_RIGHT : GTK_DIR_LEFT); + } + } + else + { + gtk_widget_error_bell (GTK_WIDGET (self)); + } + } + break; + + case GTK_MOVEMENT_WORDS: + if (priv->resolved_dir == PANGO_DIRECTION_RTL) + count *= -1; + + while (count > 0) + { + new_pos = gtk_text_move_forward_word (self, new_pos, FALSE); + count--; + } + + while (count < 0) + { + new_pos = gtk_text_move_backward_word (self, new_pos, FALSE); + count++; + } + + if (priv->current_pos == new_pos) + gtk_widget_error_bell (GTK_WIDGET (self)); + + break; + + case GTK_MOVEMENT_DISPLAY_LINE_ENDS: + case GTK_MOVEMENT_PARAGRAPH_ENDS: + case GTK_MOVEMENT_BUFFER_ENDS: + new_pos = count < 0 ? 0 : gtk_entry_buffer_get_length (get_buffer (self)); + + if (priv->current_pos == new_pos) + gtk_widget_error_bell (GTK_WIDGET (self)); + + break; + + case GTK_MOVEMENT_DISPLAY_LINES: + case GTK_MOVEMENT_PARAGRAPHS: + case GTK_MOVEMENT_PAGES: + case GTK_MOVEMENT_HORIZONTAL_PAGES: + default: + break; + } + } + + if (extend_selection) + gtk_text_set_selection_bounds (self, priv->selection_bound, new_pos); + else + gtk_text_set_selection_bounds (self, new_pos, new_pos); + + gtk_text_pend_cursor_blink (self); +} + +static void +gtk_text_insert_at_cursor (GtkText *self, + const char *str) +{ + GtkTextPrivate *priv = gtk_text_get_instance_private (self); + int pos = priv->current_pos; + + if (priv->editable) + { + gtk_text_reset_im_context (self); + gtk_text_insert_text (self, str, -1, &pos); + gtk_text_set_selection_bounds (self, pos, pos); + } +} + +static void +gtk_text_delete_from_cursor (GtkText *self, + GtkDeleteType type, + int count) +{ + GtkTextPrivate *priv = gtk_text_get_instance_private (self); + int start_pos = priv->current_pos; + int end_pos = priv->current_pos; + int old_n_bytes = gtk_entry_buffer_get_bytes (get_buffer (self)); + + gtk_text_reset_im_context (self); + + if (!priv->editable) + { + gtk_widget_error_bell (GTK_WIDGET (self)); + return; + } + + if (priv->selection_bound != priv->current_pos) + { + gtk_text_delete_text (self, priv->selection_bound, priv->current_pos); + return; + } + + switch (type) + { + case GTK_DELETE_CHARS: + end_pos = gtk_text_move_logically (self, priv->current_pos, count); + gtk_text_delete_text (self, MIN (start_pos, end_pos), MAX (start_pos, end_pos)); + break; + + case GTK_DELETE_WORDS: + if (count < 0) + { + /* Move to end of current word, or if not on a word, end of previous word */ + end_pos = gtk_text_move_backward_word (self, end_pos, FALSE); + end_pos = gtk_text_move_forward_word (self, end_pos, FALSE); + } + else if (count > 0) + { + /* Move to beginning of current word, or if not on a word, begining of next word */ + start_pos = gtk_text_move_forward_word (self, start_pos, FALSE); + start_pos = gtk_text_move_backward_word (self, start_pos, FALSE); + } + + /* Fall through */ + case GTK_DELETE_WORD_ENDS: + while (count < 0) + { + start_pos = gtk_text_move_backward_word (self, start_pos, FALSE); + count++; + } + + while (count > 0) + { + end_pos = gtk_text_move_forward_word (self, end_pos, FALSE); + count--; + } + + gtk_text_delete_text (self, start_pos, end_pos); + break; + + case GTK_DELETE_DISPLAY_LINE_ENDS: + case GTK_DELETE_PARAGRAPH_ENDS: + if (count < 0) + gtk_text_delete_text (self, 0, priv->current_pos); + else + gtk_text_delete_text (self, priv->current_pos, -1); + + break; + + case GTK_DELETE_DISPLAY_LINES: + case GTK_DELETE_PARAGRAPHS: + gtk_text_delete_text (self, 0, -1); + break; + + case GTK_DELETE_WHITESPACE: + gtk_text_delete_whitespace (self); + break; + + default: + break; + } + + if (gtk_entry_buffer_get_bytes (get_buffer (self)) == old_n_bytes) + gtk_widget_error_bell (GTK_WIDGET (self)); + + gtk_text_pend_cursor_blink (self); +} + +static void +gtk_text_backspace (GtkText *self) +{ + GtkTextPrivate *priv = gtk_text_get_instance_private (self); + int prev_pos; + + gtk_text_reset_im_context (self); + + if (!priv->editable) + { + gtk_widget_error_bell (GTK_WIDGET (self)); + return; + } + + if (priv->selection_bound != priv->current_pos) + { + gtk_text_delete_text (self, priv->selection_bound, priv->current_pos); + return; + } + + prev_pos = gtk_text_move_logically (self, priv->current_pos, -1); + + if (prev_pos < priv->current_pos) + { + PangoLayout *layout = gtk_text_ensure_layout (self, FALSE); + const PangoLogAttr *log_attrs; + int n_attrs; + + log_attrs = pango_layout_get_log_attrs_readonly (layout, &n_attrs); + + /* Deleting parts of characters */ + if (log_attrs[priv->current_pos].backspace_deletes_character) + { + char *cluster_text; + char *normalized_text; + glong len; + + cluster_text = gtk_text_get_display_text (self, prev_pos, priv->current_pos); + normalized_text = g_utf8_normalize (cluster_text, + strlen (cluster_text), + G_NORMALIZE_NFD); + len = g_utf8_strlen (normalized_text, -1); + + gtk_text_delete_text (self, prev_pos, priv->current_pos); + if (len > 1) + { + int pos = priv->current_pos; + + gtk_text_insert_text (self, normalized_text, + g_utf8_offset_to_pointer (normalized_text, len - 1) - normalized_text, + &pos); + gtk_text_set_selection_bounds (self, pos, pos); + } + + g_free (normalized_text); + g_free (cluster_text); + } + else + { + gtk_text_delete_text (self, prev_pos, priv->current_pos); + } + } + else + { + gtk_widget_error_bell (GTK_WIDGET (self)); + } + + gtk_text_pend_cursor_blink (self); +} + +static void +gtk_text_copy_clipboard (GtkText *self) +{ + GtkTextPrivate *priv = gtk_text_get_instance_private (self); + + if (priv->selection_bound != priv->current_pos) + { + char *str; + + if (!priv->visible) + { + gtk_widget_error_bell (GTK_WIDGET (self)); + return; + } + + str = gtk_text_get_display_text (self, priv->selection_bound, priv->current_pos); + gdk_clipboard_set_text (gtk_widget_get_clipboard (GTK_WIDGET (self)), str); + g_free (str); + } +} + +static void +gtk_text_cut_clipboard (GtkText *self) +{ + GtkTextPrivate *priv = gtk_text_get_instance_private (self); + + if (!priv->visible) + { + gtk_widget_error_bell (GTK_WIDGET (self)); + return; + } + + gtk_text_copy_clipboard (self); + + if (priv->editable) + { + if (priv->selection_bound != priv->current_pos) + gtk_text_delete_text (self, priv->selection_bound, priv->current_pos); + } + else + { + gtk_widget_error_bell (GTK_WIDGET (self)); + } + + gtk_text_selection_bubble_popup_unset (self); + + if (priv->text_handle) + { + GtkTextHandleMode handle_mode; + + handle_mode = _gtk_text_handle_get_mode (priv->text_handle); + + if (handle_mode != GTK_TEXT_HANDLE_MODE_NONE) + gtk_text_update_handles (self, GTK_TEXT_HANDLE_MODE_CURSOR); + } +} + +static void +gtk_text_paste_clipboard (GtkText *self) +{ + GtkTextPrivate *priv = gtk_text_get_instance_private (self); + + if (priv->editable) + gtk_text_paste (self, gtk_widget_get_clipboard (GTK_WIDGET (self))); + else + gtk_widget_error_bell (GTK_WIDGET (self)); + + if (priv->text_handle) + { + GtkTextHandleMode handle_mode; + + handle_mode = _gtk_text_handle_get_mode (priv->text_handle); + + if (handle_mode != GTK_TEXT_HANDLE_MODE_NONE) + gtk_text_update_handles (self, GTK_TEXT_HANDLE_MODE_CURSOR); + } +} + +static void +gtk_text_delete_cb (GtkText *self) +{ + GtkTextPrivate *priv = gtk_text_get_instance_private (self); + + if (priv->editable) + { + if (priv->selection_bound != priv->current_pos) + gtk_text_delete_text (self, priv->selection_bound, priv->current_pos); + } +} + +static void +gtk_text_toggle_overwrite (GtkText *self) +{ + GtkTextPrivate *priv = gtk_text_get_instance_private (self); + + priv->overwrite_mode = !priv->overwrite_mode; + + if (priv->overwrite_mode) + { + if (!priv->block_cursor_node) + { + GtkCssNode *widget_node = gtk_widget_get_css_node (GTK_WIDGET (self)); + + priv->block_cursor_node = gtk_css_node_new (); + gtk_css_node_set_name (priv->block_cursor_node, I_("block-cursor")); + gtk_css_node_set_parent (priv->block_cursor_node, widget_node); + gtk_css_node_set_state (priv->block_cursor_node, gtk_css_node_get_state (widget_node)); + g_object_unref (priv->block_cursor_node); + } + } + else + { + if (priv->block_cursor_node) + { + gtk_css_node_set_parent (priv->block_cursor_node, NULL); + priv->block_cursor_node = NULL; + } + } + + gtk_text_pend_cursor_blink (self); + gtk_widget_queue_draw (GTK_WIDGET (self)); +} + +static void +gtk_text_select_all (GtkText *self) +{ + gtk_text_select_line (self); +} + +static void +gtk_text_real_activate (GtkText *self) +{ + GtkTextPrivate *priv = gtk_text_get_instance_private (self); + GtkWindow *window; + GtkWidget *default_widget, *focus_widget; + GtkWidget *toplevel; + GtkWidget *widget; + + widget = GTK_WIDGET (self); + + if (priv->activates_default) + { + toplevel = gtk_widget_get_toplevel (widget); + if (GTK_IS_WINDOW (toplevel)) + { + window = GTK_WINDOW (toplevel); + + if (window) + { + default_widget = gtk_window_get_default_widget (window); + focus_widget = gtk_window_get_focus (window); + if (widget != default_widget && + !(widget == focus_widget && (!default_widget || !gtk_widget_get_sensitive (default_widget)))) + gtk_window_activate_default (window); + } + } + } +} + +static void +keymap_direction_changed (GdkKeymap *keymap, + GtkText *self) +{ + gtk_text_recompute (self); +} + +/* IM Context Callbacks + */ + +static void +gtk_text_commit_cb (GtkIMContext *context, + const char *str, + GtkText *self) +{ + GtkTextPrivate *priv = gtk_text_get_instance_private (self); + + if (priv->editable) + { + gtk_text_enter_text (self, str); + gtk_text_obscure_mouse_cursor (self); + } +} + +static void +gtk_text_preedit_changed_cb (GtkIMContext *context, + GtkText *self) +{ + GtkTextPrivate *priv = gtk_text_get_instance_private (self); + + if (priv->editable) + { + char *preedit_string; + int cursor_pos; + + gtk_text_obscure_mouse_cursor (self); + + gtk_im_context_get_preedit_string (priv->im_context, + &preedit_string, NULL, + &cursor_pos); + g_signal_emit (self, signals[PREEDIT_CHANGED], 0, preedit_string); + priv->preedit_length = strlen (preedit_string); + cursor_pos = CLAMP (cursor_pos, 0, g_utf8_strlen (preedit_string, -1)); + priv->preedit_cursor = cursor_pos; + g_free (preedit_string); + + gtk_text_recompute (self); + } +} + +static gboolean +gtk_text_retrieve_surrounding_cb (GtkIMContext *context, + GtkText *self) +{ + GtkTextPrivate *priv = gtk_text_get_instance_private (self); + char *text; + + /* XXXX ??? does this even make sense when text is not visible? Should we return FALSE? */ + text = gtk_text_get_display_text (self, 0, -1); + gtk_im_context_set_surrounding (context, text, strlen (text), /* Length in bytes */ + g_utf8_offset_to_pointer (text, priv->current_pos) - text); + g_free (text); + + return TRUE; +} + +static gboolean +gtk_text_delete_surrounding_cb (GtkIMContext *slave, + int offset, + int n_chars, + GtkText *self) +{ + GtkTextPrivate *priv = gtk_text_get_instance_private (self); + + if (priv->editable) + gtk_text_delete_text (self, + priv->current_pos + offset, + priv->current_pos + offset + n_chars); + + return TRUE; +} + +/* Internal functions + */ + +/* Used for im_commit_cb and inserting Unicode chars */ +void +gtk_text_enter_text (GtkText *self, + const char *str) +{ + GtkTextPrivate *priv = gtk_text_get_instance_private (self); + int tmp_pos; + gboolean old_need_im_reset; + guint text_length; + + old_need_im_reset = priv->need_im_reset; + priv->need_im_reset = FALSE; + + if (priv->selection_bound != priv->current_pos) + gtk_text_delete_text (self, priv->selection_bound, priv->current_pos); + else + { + if (priv->overwrite_mode) + { + text_length = gtk_entry_buffer_get_length (get_buffer (self)); + if (priv->current_pos < text_length) + gtk_text_delete_from_cursor (self, GTK_DELETE_CHARS, 1); + } + } + + tmp_pos = priv->current_pos; + gtk_text_insert_text (self, str, strlen (str), &tmp_pos); + gtk_text_set_selection_bounds (self, tmp_pos, tmp_pos); + + priv->need_im_reset = old_need_im_reset; +} + +/* All changes to priv->current_pos and priv->selection_bound + * should go through this function. + */ +void +gtk_text_set_positions (GtkText *self, + int current_pos, + int selection_bound) +{ + GtkTextPrivate *priv = gtk_text_get_instance_private (self); + gboolean changed = FALSE; + + g_object_freeze_notify (G_OBJECT (self)); + + if (current_pos != -1 && + priv->current_pos != current_pos) + { + priv->current_pos = current_pos; + changed = TRUE; + + g_object_notify (G_OBJECT (self), "cursor-position"); + } + + if (selection_bound != -1 && + priv->selection_bound != selection_bound) + { + priv->selection_bound = selection_bound; + changed = TRUE; + + g_object_notify (G_OBJECT (self), "selection-bound"); + } + + g_object_thaw_notify (G_OBJECT (self)); + + if (priv->current_pos != priv->selection_bound) + { + if (!priv->selection_node) + { + GtkCssNode *widget_node = gtk_widget_get_css_node (GTK_WIDGET (self)); + + priv->selection_node = gtk_css_node_new (); + gtk_css_node_set_name (priv->selection_node, I_("selection")); + gtk_css_node_set_parent (priv->selection_node, widget_node); + gtk_css_node_set_state (priv->selection_node, gtk_css_node_get_state (widget_node)); + g_object_unref (priv->selection_node); + } + } + else + { + if (priv->selection_node) + { + gtk_css_node_set_parent (priv->selection_node, NULL); + priv->selection_node = NULL; + } + } + + if (changed) + { + gtk_text_recompute (self); + } +} + +static void +gtk_text_reset_layout (GtkText *self) +{ + GtkTextPrivate *priv = gtk_text_get_instance_private (self); + + if (priv->cached_layout) + { + g_object_unref (priv->cached_layout); + priv->cached_layout = NULL; + } +} + +static void +update_im_cursor_location (GtkText *self) +{ + GtkTextPrivate *priv = gtk_text_get_instance_private (self); + GdkRectangle area; + GtkAllocation text_area; + int strong_x; + int strong_xoffset; + + gtk_text_get_cursor_locations (self, &strong_x, NULL); + gtk_text_get_text_allocation (self, &text_area); + + strong_xoffset = strong_x - priv->scroll_offset; + if (strong_xoffset < 0) + strong_xoffset = 0; + else if (strong_xoffset > text_area.width) + strong_xoffset = text_area.width; + + area.x = strong_xoffset; + area.y = 0; + area.width = 0; + area.height = text_area.height; + + gtk_im_context_set_cursor_location (priv->im_context, &area); +} + +static void +gtk_text_recompute (GtkText *self) +{ + GtkTextPrivate *priv = gtk_text_get_instance_private (self); + GtkTextHandleMode handle_mode; + + gtk_text_reset_layout (self); + gtk_text_check_cursor_blink (self); + + gtk_text_adjust_scroll (self); + + update_im_cursor_location (self); + + if (priv->text_handle) + { + handle_mode = _gtk_text_handle_get_mode (priv->text_handle); + + if (handle_mode != GTK_TEXT_HANDLE_MODE_NONE) + gtk_text_update_handles (self, handle_mode); + } + + gtk_widget_queue_draw (GTK_WIDGET (self)); +} + +static PangoLayout * +gtk_text_create_layout (GtkText *self, + gboolean include_preedit) +{ + GtkTextPrivate *priv = gtk_text_get_instance_private (self); + GtkWidget *widget = GTK_WIDGET (self); + GtkStyleContext *context; + PangoLayout *layout; + PangoAttrList *tmp_attrs; + char *preedit_string = NULL; + int preedit_length = 0; + PangoAttrList *preedit_attrs = NULL; + char *display_text; + guint n_bytes; + + context = gtk_widget_get_style_context (widget); + + layout = gtk_widget_create_pango_layout (widget, NULL); + pango_layout_set_single_paragraph_mode (layout, TRUE); + + tmp_attrs = _gtk_style_context_get_pango_attributes (context); + tmp_attrs = _gtk_pango_attr_list_merge (tmp_attrs, priv->attrs); + if (!tmp_attrs) + tmp_attrs = pango_attr_list_new (); + + display_text = gtk_text_get_display_text (self, 0, -1); + + n_bytes = strlen (display_text); + + if (include_preedit) + { + gtk_im_context_get_preedit_string (priv->im_context, + &preedit_string, &preedit_attrs, NULL); + preedit_length = priv->preedit_length; + } + + if (preedit_length) + { + GString *tmp_string = g_string_new (display_text); + int pos; + + pos = g_utf8_offset_to_pointer (display_text, priv->current_pos) - display_text; + g_string_insert (tmp_string, pos, preedit_string); + pango_layout_set_text (layout, tmp_string->str, tmp_string->len); + pango_attr_list_splice (tmp_attrs, preedit_attrs, pos, preedit_length); + g_string_free (tmp_string, TRUE); + } + else + { + PangoDirection pango_dir; + + if (gtk_text_get_display_mode (self) == DISPLAY_NORMAL) + pango_dir = gdk_find_base_dir (display_text, n_bytes); + else + pango_dir = PANGO_DIRECTION_NEUTRAL; + + if (pango_dir == PANGO_DIRECTION_NEUTRAL) + { + if (gtk_widget_has_focus (widget)) + { + GdkDisplay *display = gtk_widget_get_display (widget); + GdkKeymap *keymap = gdk_display_get_keymap (display); + + if (gdk_keymap_get_direction (keymap) == PANGO_DIRECTION_RTL) + pango_dir = PANGO_DIRECTION_RTL; + else + pango_dir = PANGO_DIRECTION_LTR; + } + else + { + if (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL) + pango_dir = PANGO_DIRECTION_RTL; + else + pango_dir = PANGO_DIRECTION_LTR; + } + } + + pango_context_set_base_dir (gtk_widget_get_pango_context (widget), pango_dir); + + priv->resolved_dir = pango_dir; + + pango_layout_set_text (layout, display_text, n_bytes); + } + + pango_layout_set_attributes (layout, tmp_attrs); + + if (priv->tabs) + pango_layout_set_tabs (layout, priv->tabs); + + g_free (preedit_string); + g_free (display_text); + + if (preedit_attrs) + pango_attr_list_unref (preedit_attrs); + + pango_attr_list_unref (tmp_attrs); + + return layout; +} + +static PangoLayout * +gtk_text_ensure_layout (GtkText *self, + gboolean include_preedit) +{ + GtkTextPrivate *priv = gtk_text_get_instance_private (self); + + if (priv->preedit_length > 0 && + !include_preedit != !priv->cache_includes_preedit) + gtk_text_reset_layout (self); + + if (!priv->cached_layout) + { + priv->cached_layout = gtk_text_create_layout (self, include_preedit); + priv->cache_includes_preedit = include_preedit; + } + + return priv->cached_layout; +} + +static void +get_layout_position (GtkText *self, + int *x, + int *y) +{ + GtkTextPrivate *priv = gtk_text_get_instance_private (self); + PangoLayout *layout; + PangoRectangle logical_rect; + int y_pos, area_height; + PangoLayoutLine *line; + GtkAllocation text_allocation; + + gtk_text_get_text_allocation (self, &text_allocation); + + layout = gtk_text_ensure_layout (self, TRUE); + + area_height = PANGO_SCALE * text_allocation.height; + + line = pango_layout_get_lines_readonly (layout)->data; + pango_layout_line_get_extents (line, NULL, &logical_rect); + + /* Align primarily for locale's ascent/descent */ + if (priv->text_baseline < 0) + y_pos = ((area_height - priv->ascent - priv->descent) / 2 + + priv->ascent + logical_rect.y); + else + y_pos = PANGO_SCALE * priv->text_baseline - pango_layout_get_baseline (layout); + + /* Now see if we need to adjust to fit in actual drawn string */ + if (logical_rect.height > area_height) + y_pos = (area_height - logical_rect.height) / 2; + else if (y_pos < 0) + y_pos = 0; + else if (y_pos + logical_rect.height > area_height) + y_pos = area_height - logical_rect.height; + + y_pos = y_pos / PANGO_SCALE; + + if (x) + *x = priv->text_x - priv->scroll_offset; + + if (y) + *y = y_pos; +} + +#define GRAPHENE_RECT_FROM_RECT(_r) (GRAPHENE_RECT_INIT ((_r)->x, (_r)->y, (_r)->width, (_r)->height)) + +static void +gtk_text_draw_text (GtkText *self, + GtkSnapshot *snapshot) +{ + GtkTextPrivate *priv = gtk_text_get_instance_private (self); + GtkWidget *widget = GTK_WIDGET (self); + GtkStyleContext *context; + PangoLayout *layout; + int x, y; + int width, height; + + /* Nothing to display at all */ + if (gtk_text_get_display_mode (self) == DISPLAY_BLANK) + return; + + context = gtk_widget_get_style_context (widget); + layout = gtk_text_ensure_layout (self, TRUE); + width = gtk_widget_get_width (widget); + height = gtk_widget_get_height (widget); + + gtk_text_get_layout_offsets (self, &x, &y); + + gtk_snapshot_render_layout (snapshot, context, x, y, layout); + + if (priv->selection_bound != priv->current_pos) + { + const char *text = pango_layout_get_text (layout); + int start_index = g_utf8_offset_to_pointer (text, priv->selection_bound) - text; + int end_index = g_utf8_offset_to_pointer (text, priv->current_pos) - text; + cairo_region_t *clip; + cairo_rectangle_int_t clip_extents; + int range[2]; + + range[0] = MIN (start_index, end_index); + range[1] = MAX (start_index, end_index); + + gtk_style_context_save_to_node (context, priv->selection_node); + + clip = gdk_pango_layout_get_clip_region (layout, x, y, range, 1); + cairo_region_get_extents (clip, &clip_extents); + + gtk_snapshot_push_clip (snapshot, &GRAPHENE_RECT_FROM_RECT (&clip_extents)); + gtk_snapshot_render_background (snapshot, context, 0, 0, width, height); + gtk_snapshot_render_layout (snapshot, context, x, y, layout); + gtk_snapshot_pop (snapshot); + + cairo_region_destroy (clip); + + gtk_style_context_restore (context); + } +} + +static void +gtk_text_draw_cursor (GtkText *self, + GtkSnapshot *snapshot, + CursorType type) +{ + GtkTextPrivate *priv = gtk_text_get_instance_private (self); + GtkWidget *widget = GTK_WIDGET (self); + GtkStyleContext *context; + PangoRectangle cursor_rect; + int cursor_index; + gboolean block; + gboolean block_at_line_end; + PangoLayout *layout; + const char *text; + int x, y; + int width, height; + + context = gtk_widget_get_style_context (widget); + + layout = gtk_text_ensure_layout (self, TRUE); + text = pango_layout_get_text (layout); + gtk_text_get_layout_offsets (self, &x, &y); + width = gtk_widget_get_width (widget); + height = gtk_widget_get_height (widget); + + if (type == CURSOR_DND) + cursor_index = g_utf8_offset_to_pointer (text, priv->dnd_position) - text; + else + cursor_index = g_utf8_offset_to_pointer (text, priv->current_pos + priv->preedit_cursor) - text; + + if (!priv->overwrite_mode) + block = FALSE; + else + block = _gtk_text_util_get_block_cursor_location (layout, + cursor_index, &cursor_rect, &block_at_line_end); + if (!block) + { + gtk_snapshot_render_insertion_cursor (snapshot, context, + x, y, + layout, cursor_index, priv->resolved_dir); + } + else /* overwrite_mode */ + { + graphene_rect_t bounds; + + bounds.origin.x = PANGO_PIXELS (cursor_rect.x) + x; + bounds.origin.y = PANGO_PIXELS (cursor_rect.y) + y; + bounds.size.width = PANGO_PIXELS (cursor_rect.width); + bounds.size.height = PANGO_PIXELS (cursor_rect.height); + + gtk_style_context_save_to_node (context, priv->block_cursor_node); + + gtk_snapshot_push_clip (snapshot, &bounds); + gtk_snapshot_render_background (snapshot, context, 0, 0, width, height); + gtk_snapshot_render_layout (snapshot, context, x, y, layout); + gtk_snapshot_pop (snapshot); + + gtk_style_context_restore (context); + } +} + +static void +gtk_text_handle_dragged (GtkTextHandle *handle, + GtkTextHandlePosition pos, + int x, + int y, + GtkText *self) +{ + int cursor_pos, selection_bound_pos, tmp_pos; + GtkTextPrivate *priv = gtk_text_get_instance_private (self); + GtkTextHandleMode mode; + int *min, *max; + + gtk_text_selection_bubble_popup_unset (self); + + cursor_pos = priv->current_pos; + selection_bound_pos = priv->selection_bound; + mode = _gtk_text_handle_get_mode (handle); + + tmp_pos = gtk_text_find_position (self, x + priv->scroll_offset); + + if (mode == GTK_TEXT_HANDLE_MODE_CURSOR || + cursor_pos >= selection_bound_pos) + { + max = &cursor_pos; + min = &selection_bound_pos; + } + else + { + max = &selection_bound_pos; + min = &cursor_pos; + } + + if (pos == GTK_TEXT_HANDLE_POSITION_SELECTION_END) + { + if (mode == GTK_TEXT_HANDLE_MODE_SELECTION) + { + int min_pos; + + min_pos = MAX (*min + 1, 0); + tmp_pos = MAX (tmp_pos, min_pos); + } + + *max = tmp_pos; + } + else + { + if (mode == GTK_TEXT_HANDLE_MODE_SELECTION) + { + int max_pos; + + max_pos = *max - 1; + *min = MIN (tmp_pos, max_pos); + } + } + + if (cursor_pos != priv->current_pos || + selection_bound_pos != priv->selection_bound) + { + if (mode == GTK_TEXT_HANDLE_MODE_CURSOR) + { + priv->cursor_handle_dragged = TRUE; + gtk_text_set_positions (self, cursor_pos, cursor_pos); + } + else + { + priv->selection_handle_dragged = TRUE; + gtk_text_set_positions (self, cursor_pos, selection_bound_pos); + } + + gtk_text_update_handles (self, mode); + } + + gtk_text_show_magnifier (self, x, y); +} + +static void +gtk_text_handle_drag_started (GtkTextHandle *handle, + GtkTextHandlePosition pos, + GtkText *self) +{ + GtkTextPrivate *priv = gtk_text_get_instance_private (self); + + priv->cursor_handle_dragged = FALSE; + priv->selection_handle_dragged = FALSE; +} + +static void +gtk_text_handle_drag_finished (GtkTextHandle *handle, + GtkTextHandlePosition pos, + GtkText *self) +{ + GtkTextPrivate *priv = gtk_text_get_instance_private (self); + + if (!priv->cursor_handle_dragged && !priv->selection_handle_dragged) + { + GtkSettings *settings; + guint double_click_time; + + settings = gtk_widget_get_settings (GTK_WIDGET (self)); + g_object_get (settings, "gtk-double-click-time", &double_click_time, NULL); + if (g_get_monotonic_time() - priv->handle_place_time < double_click_time * 1000) + { + gtk_text_select_word (self); + gtk_text_update_handles (self, GTK_TEXT_HANDLE_MODE_SELECTION); + } + else + gtk_text_selection_bubble_popup_set (self); + } + + if (priv->magnifier_popover) + gtk_popover_popdown (GTK_POPOVER (priv->magnifier_popover)); +} + +static void +gtk_text_schedule_im_reset (GtkText *self) +{ + GtkTextPrivate *priv; + + priv = gtk_text_get_instance_private (self); + + priv->need_im_reset = TRUE; +} + +void +gtk_text_reset_im_context (GtkText *self) +{ + GtkTextPrivate *priv = gtk_text_get_instance_private (self); + + g_return_if_fail (GTK_IS_TEXT (self)); + + if (priv->need_im_reset) + { + priv->need_im_reset = FALSE; + gtk_im_context_reset (priv->im_context); + } +} + +GtkIMContext * +gtk_text_get_im_context (GtkText *self) +{ + GtkTextPrivate *priv = gtk_text_get_instance_private (self); + + return priv->im_context; +} + +static int +gtk_text_find_position (GtkText *self, + int x) +{ + GtkTextPrivate *priv = gtk_text_get_instance_private (self); + PangoLayout *layout; + PangoLayoutLine *line; + int index; + int pos; + int trailing; + const char *text; + int cursor_index; + + layout = gtk_text_ensure_layout (self, TRUE); + text = pango_layout_get_text (layout); + cursor_index = g_utf8_offset_to_pointer (text, priv->current_pos) - text; + + line = pango_layout_get_lines_readonly (layout)->data; + pango_layout_line_x_to_index (line, x * PANGO_SCALE, &index, &trailing); + + if (index >= cursor_index && priv->preedit_length) + { + if (index >= cursor_index + priv->preedit_length) + index -= priv->preedit_length; + else + { + index = cursor_index; + trailing = 0; + } + } + + pos = g_utf8_pointer_to_offset (text, text + index); + pos += trailing; + + return pos; +} + +static void +gtk_text_get_cursor_locations (GtkText *self, + int *strong_x, + int *weak_x) +{ + GtkTextPrivate *priv = gtk_text_get_instance_private (self); + DisplayMode mode = gtk_text_get_display_mode (self); + + /* Nothing to display at all, so no cursor is relevant */ + if (mode == DISPLAY_BLANK) + { + if (strong_x) + *strong_x = 0; + + if (weak_x) + *weak_x = 0; + } + else + { + PangoLayout *layout = gtk_text_ensure_layout (self, TRUE); + const char *text = pango_layout_get_text (layout); + PangoRectangle strong_pos, weak_pos; + int index; + + index = g_utf8_offset_to_pointer (text, priv->current_pos + priv->preedit_cursor) - text; + + pango_layout_get_cursor_pos (layout, index, &strong_pos, &weak_pos); + + if (strong_x) + *strong_x = strong_pos.x / PANGO_SCALE; + + if (weak_x) + *weak_x = weak_pos.x / PANGO_SCALE; + } +} + +static gboolean +gtk_text_get_is_selection_handle_dragged (GtkText *self) +{ + GtkTextPrivate *priv = gtk_text_get_instance_private (self); + GtkTextHandlePosition pos; + + if (!priv->text_handle) + return FALSE; + + if (_gtk_text_handle_get_mode (priv->text_handle) != GTK_TEXT_HANDLE_MODE_SELECTION) + return FALSE; + + if (priv->current_pos >= priv->selection_bound) + pos = GTK_TEXT_HANDLE_POSITION_SELECTION_START; + else + pos = GTK_TEXT_HANDLE_POSITION_SELECTION_END; + + return _gtk_text_handle_get_is_dragged (priv->text_handle, pos); +} + +static void +gtk_text_get_scroll_limits (GtkText *self, + int *min_offset, + int *max_offset) +{ + GtkTextPrivate *priv = gtk_text_get_instance_private (self); + float xalign; + PangoLayout *layout; + PangoLayoutLine *line; + PangoRectangle logical_rect; + int text_width; + + layout = gtk_text_ensure_layout (self, TRUE); + line = pango_layout_get_lines_readonly (layout)->data; + + pango_layout_line_get_extents (line, NULL, &logical_rect); + + /* Display as much text as we can */ + + if (priv->resolved_dir == PANGO_DIRECTION_LTR) + xalign = priv->xalign; + else + xalign = 1.0 - priv->xalign; + + text_width = PANGO_PIXELS(logical_rect.width); + + if (text_width > priv->text_width) + { + *min_offset = 0; + *max_offset = text_width - priv->text_width; + } + else + { + *min_offset = (text_width - priv->text_width) * xalign; + *max_offset = *min_offset; + } +} + +static void +gtk_text_adjust_scroll (GtkText *self) +{ + GtkTextPrivate *priv = gtk_text_get_instance_private (self); + int min_offset, max_offset; + int strong_x, weak_x; + int strong_xoffset, weak_xoffset; + GtkTextHandleMode handle_mode; + GtkAllocation text_allocation; + + if (!gtk_widget_get_realized (GTK_WIDGET (self))) + return; + + gtk_text_get_text_allocation (self, &text_allocation); + + gtk_text_get_scroll_limits (self, &min_offset, &max_offset); + + priv->scroll_offset = CLAMP (priv->scroll_offset, min_offset, max_offset); + + if (gtk_text_get_is_selection_handle_dragged (self)) + { + /* The text handle corresponding to the selection bound is + * being dragged, ensure it stays onscreen even if we scroll + * cursors away, this is so both handles can cause content + * to scroll. + */ + strong_x = weak_x = gtk_text_get_selection_bound_location (self); + } + else + { + /* And make sure cursors are on screen. Note that the cursor is + * actually drawn one pixel into the INNER_BORDER space on + * the right, when the scroll is at the utmost right. This + * looks better to to me than confining the cursor inside the + * border entirely, though it means that the cursor gets one + * pixel closer to the edge of the widget on the right than + * on the left. This might need changing if one changed + * INNER_BORDER from 2 to 1, as one would do on a + * small-screen-real-estate display. + * + * We always make sure that the strong cursor is on screen, and + * put the weak cursor on screen if possible. + */ + gtk_text_get_cursor_locations (self, &strong_x, &weak_x); + } + + strong_xoffset = strong_x - priv->scroll_offset; + + if (strong_xoffset < 0) + { + priv->scroll_offset += strong_xoffset; + strong_xoffset = 0; + } + else if (strong_xoffset > text_allocation.width) + { + priv->scroll_offset += strong_xoffset - text_allocation.width; + strong_xoffset = text_allocation.width; + } + + weak_xoffset = weak_x - priv->scroll_offset; + + if (weak_xoffset < 0 && strong_xoffset - weak_xoffset <= text_allocation.width) + { + priv->scroll_offset += weak_xoffset; + } + else if (weak_xoffset > text_allocation.width && + strong_xoffset - (weak_xoffset - text_allocation.width) >= 0) + { + priv->scroll_offset += weak_xoffset - text_allocation.width; + } + + g_object_notify_by_pspec (G_OBJECT (self), text_props[PROP_SCROLL_OFFSET]); + + if (priv->text_handle) + { + handle_mode = _gtk_text_handle_get_mode (priv->text_handle); + + if (handle_mode != GTK_TEXT_HANDLE_MODE_NONE) + gtk_text_update_handles (self, handle_mode); + } +} + +static int +gtk_text_move_visually (GtkText *self, + int start, + int count) +{ + GtkTextPrivate *priv = gtk_text_get_instance_private (self); + int index; + PangoLayout *layout = gtk_text_ensure_layout (self, FALSE); + const char *text; + + text = pango_layout_get_text (layout); + + index = g_utf8_offset_to_pointer (text, start) - text; + + while (count != 0) + { + int new_index, new_trailing; + gboolean split_cursor; + gboolean strong; + + g_object_get (gtk_widget_get_settings (GTK_WIDGET (self)), + "gtk-split-cursor", &split_cursor, + NULL); + + if (split_cursor) + strong = TRUE; + else + { + GdkKeymap *keymap = gdk_display_get_keymap (gtk_widget_get_display (GTK_WIDGET (self))); + PangoDirection keymap_direction = gdk_keymap_get_direction (keymap); + + strong = keymap_direction == priv->resolved_dir; + } + + if (count > 0) + { + pango_layout_move_cursor_visually (layout, strong, index, 0, 1, &new_index, &new_trailing); + count--; + } + else + { + pango_layout_move_cursor_visually (layout, strong, index, 0, -1, &new_index, &new_trailing); + count++; + } + + if (new_index < 0) + index = 0; + else if (new_index != G_MAXINT) + index = new_index; + + while (new_trailing--) + index = g_utf8_next_char (text + index) - text; + } + + return g_utf8_pointer_to_offset (text, text + index); +} + +static int +gtk_text_move_logically (GtkText *self, + int start, + int count) +{ + int new_pos = start; + guint length; + + length = gtk_entry_buffer_get_length (get_buffer (self)); + + /* Prevent any leak of information */ + if (gtk_text_get_display_mode (self) != DISPLAY_NORMAL) + { + new_pos = CLAMP (start + count, 0, length); + } + else + { + PangoLayout *layout = gtk_text_ensure_layout (self, FALSE); + const PangoLogAttr *log_attrs; + int n_attrs; + + log_attrs = pango_layout_get_log_attrs_readonly (layout, &n_attrs); + + while (count > 0 && new_pos < length) + { + do + new_pos++; + while (new_pos < length && !log_attrs[new_pos].is_cursor_position); + + count--; + } + while (count < 0 && new_pos > 0) + { + do + new_pos--; + while (new_pos > 0 && !log_attrs[new_pos].is_cursor_position); + + count++; + } + } + + return new_pos; +} + +static int +gtk_text_move_forward_word (GtkText *self, + int start, + gboolean allow_whitespace) +{ + int new_pos = start; + guint length; + + length = gtk_entry_buffer_get_length (get_buffer (self)); + + /* Prevent any leak of information */ + if (gtk_text_get_display_mode (self) != DISPLAY_NORMAL) + { + new_pos = length; + } + else if (new_pos < length) + { + PangoLayout *layout = gtk_text_ensure_layout (self, FALSE); + const PangoLogAttr *log_attrs; + int n_attrs; + + log_attrs = pango_layout_get_log_attrs_readonly (layout, &n_attrs); + + /* Find the next word boundary */ + new_pos++; + while (new_pos < n_attrs - 1 && !(log_attrs[new_pos].is_word_end || + (log_attrs[new_pos].is_word_start && allow_whitespace))) + new_pos++; + } + + return new_pos; +} + + +static int +gtk_text_move_backward_word (GtkText *self, + int start, + gboolean allow_whitespace) +{ + int new_pos = start; + + /* Prevent any leak of information */ + if (gtk_text_get_display_mode (self) != DISPLAY_NORMAL) + { + new_pos = 0; + } + else if (start > 0) + { + PangoLayout *layout = gtk_text_ensure_layout (self, FALSE); + const PangoLogAttr *log_attrs; + int n_attrs; + + log_attrs = pango_layout_get_log_attrs_readonly (layout, &n_attrs); + + new_pos = start - 1; + + /* Find the previous word boundary */ + while (new_pos > 0 && !(log_attrs[new_pos].is_word_start || + (log_attrs[new_pos].is_word_end && allow_whitespace))) + new_pos--; + } + + return new_pos; +} + +static void +gtk_text_delete_whitespace (GtkText *self) +{ + GtkTextPrivate *priv = gtk_text_get_instance_private (self); + PangoLayout *layout = gtk_text_ensure_layout (self, FALSE); + const PangoLogAttr *log_attrs; + int n_attrs; + int start, end; + + log_attrs = pango_layout_get_log_attrs_readonly (layout, &n_attrs); + + start = end = priv->current_pos; + + while (start > 0 && log_attrs[start-1].is_white) + start--; + + while (end < n_attrs && log_attrs[end].is_white) + end++; + + if (start != end) + gtk_text_delete_text (self, start, end); +} + + +static void +gtk_text_select_word (GtkText *self) +{ + GtkTextPrivate *priv = gtk_text_get_instance_private (self); + int start_pos = gtk_text_move_backward_word (self, priv->current_pos, TRUE); + int end_pos = gtk_text_move_forward_word (self, priv->current_pos, TRUE); + + gtk_text_set_selection_bounds (self, start_pos, end_pos); +} + +static void +gtk_text_select_line (GtkText *self) +{ + gtk_text_set_selection_bounds (self, 0, -1); +} + +static int +truncate_multiline (const char *text) +{ + int length; + + for (length = 0; + text[length] && text[length] != '\n' && text[length] != '\r'; + length++); + + return length; +} + +static void +paste_received (GObject *clipboard, + GAsyncResult *result, + gpointer data) +{ + GtkText *self = GTK_TEXT (data); + GtkTextPrivate *priv = gtk_text_get_instance_private (self); + char *text; + int pos, start, end; + int length = -1; + + text = gdk_clipboard_read_text_finish (GDK_CLIPBOARD (clipboard), result, NULL); + if (text == NULL) + { + gtk_widget_error_bell (GTK_WIDGET (self)); + return; + } + + if (priv->insert_pos >= 0) + { + pos = priv->insert_pos; + start = priv->selection_bound; + end = priv->current_pos; + if (!((start <= pos && pos <= end) || (end <= pos && pos <= start))) + gtk_text_set_selection_bounds (self, pos, pos); + priv->insert_pos = -1; + } + + if (priv->truncate_multiline) + length = truncate_multiline (text); + + begin_change (self); + if (priv->selection_bound != priv->current_pos) + gtk_text_delete_text (self, priv->selection_bound, priv->current_pos); + + pos = priv->current_pos; + gtk_text_insert_text (self, text, length, &pos); + gtk_text_set_selection_bounds (self, pos, pos); + end_change (self); + + g_free (text); + g_object_unref (self); +} + +static void +gtk_text_paste (GtkText *self, + GdkClipboard *clipboard) +{ + gdk_clipboard_read_text_async (clipboard, NULL, paste_received, g_object_ref (self)); +} + +static void +gtk_text_update_primary_selection (GtkText *self) +{ + GtkTextPrivate *priv = gtk_text_get_instance_private (self); + GdkClipboard *clipboard; + + if (!gtk_widget_get_realized (GTK_WIDGET (self))) + return; + + clipboard = gtk_widget_get_primary_clipboard (GTK_WIDGET (self)); + + if (priv->selection_bound != priv->current_pos) + { + gdk_clipboard_set_content (clipboard, priv->selection_content); + } + else + { + if (gdk_clipboard_get_content (clipboard) == priv->selection_content) + gdk_clipboard_set_content (clipboard, NULL); + } +} + +/* Public API + */ + +/** + * gtk_text_new: + * + * Creates a new self. + * + * Returns: a new #GtkText. + */ +GtkWidget * +gtk_text_new (void) +{ + return g_object_new (GTK_TYPE_TEXT, NULL); +} + +/** + * gtk_text_new_with_buffer: + * @buffer: The buffer to use for the new #GtkText. + * + * Creates a new self with the specified text buffer. + * + * Returns: a new #GtkText + */ +GtkWidget * +gtk_text_new_with_buffer (GtkEntryBuffer *buffer) +{ + g_return_val_if_fail (GTK_IS_ENTRY_BUFFER (buffer), NULL); + + return g_object_new (GTK_TYPE_TEXT, "buffer", buffer, NULL); +} + +static GtkEntryBuffer * +get_buffer (GtkText *self) +{ + GtkTextPrivate *priv = gtk_text_get_instance_private (self); + + if (priv->buffer == NULL) + { + GtkEntryBuffer *buffer; + buffer = gtk_entry_buffer_new (NULL, 0); + gtk_text_set_buffer (self, buffer); + g_object_unref (buffer); + } + + return priv->buffer; +} + +/** + * gtk_text_get_buffer: + * @self: a #GtkText + * + * Get the #GtkEntryBuffer object which holds the text for + * this self. + * + * Returns: (transfer none): A #GtkEntryBuffer object. + */ +GtkEntryBuffer * +gtk_text_get_buffer (GtkText *self) +{ + g_return_val_if_fail (GTK_IS_TEXT (self), NULL); + + return get_buffer (self); +} + +/** + * gtk_text_set_buffer: + * @self: a #GtkText + * @buffer: a #GtkEntryBuffer + * + * Set the #GtkEntryBuffer object which holds the text for + * this widget. + */ +void +gtk_text_set_buffer (GtkText *self, + GtkEntryBuffer *buffer) +{ + GtkTextPrivate *priv = gtk_text_get_instance_private (self); + GObject *obj; + gboolean had_buffer = FALSE; + guint old_length = 0; + guint new_length = 0; + + g_return_if_fail (GTK_IS_TEXT (self)); + + if (buffer) + { + g_return_if_fail (GTK_IS_ENTRY_BUFFER (buffer)); + g_object_ref (buffer); + } + + if (priv->buffer) + { + had_buffer = TRUE; + old_length = gtk_entry_buffer_get_length (priv->buffer); + buffer_disconnect_signals (self); + g_object_unref (priv->buffer); + } + + priv->buffer = buffer; + + if (priv->buffer) + { + new_length = gtk_entry_buffer_get_length (priv->buffer); + buffer_connect_signals (self); + } + + obj = G_OBJECT (self); + g_object_freeze_notify (obj); + g_object_notify_by_pspec (obj, text_props[PROP_BUFFER]); + g_object_notify_by_pspec (obj, text_props[PROP_MAX_LENGTH]); + if (old_length != 0 || new_length != 0) + g_object_notify (obj, "text"); + + if (had_buffer) + { + gtk_text_set_selection_bounds (self, 0, 0); + gtk_text_recompute (self); + } + + g_object_thaw_notify (obj); +} + +static void +gtk_text_set_editable (GtkText *self, + gboolean is_editable) +{ + GtkTextPrivate *priv = gtk_text_get_instance_private (self); + GtkStyleContext *context = gtk_widget_get_style_context (GTK_WIDGET (self)); + + if (is_editable != priv->editable) + { + GtkWidget *widget = GTK_WIDGET (self); + + if (!is_editable) + { + gtk_text_reset_im_context (self); + if (gtk_widget_has_focus (widget)) + gtk_im_context_focus_out (priv->im_context); + + priv->preedit_length = 0; + priv->preedit_cursor = 0; + + gtk_style_context_remove_class (context, GTK_STYLE_CLASS_READ_ONLY); + } + else + { + gtk_style_context_add_class (context, GTK_STYLE_CLASS_READ_ONLY); + } + + priv->editable = is_editable; + + if (is_editable && gtk_widget_has_focus (widget)) + gtk_im_context_focus_in (priv->im_context); + + gtk_event_controller_key_set_im_context (GTK_EVENT_CONTROLLER_KEY (priv->key_controller), + is_editable ? priv->im_context : NULL); + + g_object_notify (G_OBJECT (self), "editable"); + gtk_widget_queue_draw (widget); + } +} + +static void +gtk_text_set_text (GtkText *self, + const char *text) +{ + int tmp_pos; + + g_return_if_fail (GTK_IS_TEXT (self)); + g_return_if_fail (text != NULL); + + /* Actually setting the text will affect the cursor and selection; + * if the contents don't actually change, this will look odd to the user. + */ + if (strcmp (gtk_entry_buffer_get_text (get_buffer (self)), text) == 0) + return; + + begin_change (self); + g_object_freeze_notify (G_OBJECT (self)); + gtk_text_delete_text (self, 0, -1); + tmp_pos = 0; + gtk_text_insert_text (self, text, strlen (text), &tmp_pos); + g_object_thaw_notify (G_OBJECT (self)); + end_change (self); +} + +/** + * gtk_text_set_visibility: + * @self: a #GtkText + * @visible: %TRUE if the contents of the self are displayed + * as plaintext + * + * Sets whether the contents of the self are visible or not. + * When visibility is set to %FALSE, characters are displayed + * as the invisible char, and will also appear that way when + * the text in the self widget is copied to the clipboard. + * + * By default, GTK picks the best invisible character available + * in the current font, but it can be changed with + * gtk_text_set_invisible_char(). + * + * Note that you probably want to set #GtkText:input-purpose + * to %GTK_INPUT_PURPOSE_PASSWORD or %GTK_INPUT_PURPOSE_PIN to + * inform input methods about the purpose of this self, + * in addition to setting visibility to %FALSE. + */ +void +gtk_text_set_visibility (GtkText *self, + gboolean visible) +{ + GtkTextPrivate *priv = gtk_text_get_instance_private (self); + + g_return_if_fail (GTK_IS_TEXT (self)); + + visible = visible != FALSE; + + if (priv->visible != visible) + { + priv->visible = visible; + + g_object_notify (G_OBJECT (self), "visibility"); + gtk_text_recompute (self); + } +} + +/** + * gtk_text_get_visibility: + * @self: a #GtkText + * + * Retrieves whether the text in @self is visible. + * See gtk_text_set_visibility(). + * + * Returns: %TRUE if the text is currently visible + **/ +gboolean +gtk_text_get_visibility (GtkText *self) +{ + GtkTextPrivate *priv = gtk_text_get_instance_private (self); + + g_return_val_if_fail (GTK_IS_TEXT (self), FALSE); + + return priv->visible; +} + +/** + * gtk_text_set_invisible_char: + * @self: a #GtkText + * @ch: a Unicode character + * + * Sets the character to use in place of the actual text when + * gtk_text_set_visibility() has been called to set text visibility + * to %FALSE. i.e. this is the character used in “password mode” to + * show the user how many characters have been typed. + * + * By default, GTK picks the best invisible char available in the + * current font. If you set the invisible char to 0, then the user + * will get no feedback at all; there will be no text on the screen + * as they type. + **/ +void +gtk_text_set_invisible_char (GtkText *self, + gunichar ch) +{ + GtkTextPrivate *priv = gtk_text_get_instance_private (self); + + g_return_if_fail (GTK_IS_TEXT (self)); + + if (!priv->invisible_char_set) + { + priv->invisible_char_set = TRUE; + g_object_notify_by_pspec (G_OBJECT (self), text_props[PROP_INVISIBLE_CHAR_SET]); + } + + if (ch == priv->invisible_char) + return; + + priv->invisible_char = ch; + g_object_notify_by_pspec (G_OBJECT (self), text_props[PROP_INVISIBLE_CHAR]); + gtk_text_recompute (self); +} + +/** + * gtk_text_get_invisible_char: + * @self: a #GtkText + * + * Retrieves the character displayed in place of the real characters + * for entries with visibility set to false. + * See gtk_text_set_invisible_char(). + * + * Returns: the current invisible char, or 0, if the self does not + * show invisible text at all. + **/ +gunichar +gtk_text_get_invisible_char (GtkText *self) +{ + GtkTextPrivate *priv = gtk_text_get_instance_private (self); + + g_return_val_if_fail (GTK_IS_TEXT (self), 0); + + return priv->invisible_char; +} + +/** + * gtk_text_unset_invisible_char: + * @self: a #GtkText + * + * Unsets the invisible char previously set with + * gtk_text_set_invisible_char(). So that the + * default invisible char is used again. + **/ +void +gtk_text_unset_invisible_char (GtkText *self) +{ + GtkTextPrivate *priv = gtk_text_get_instance_private (self); + gunichar ch; + + g_return_if_fail (GTK_IS_TEXT (self)); + + if (!priv->invisible_char_set) + return; + + priv->invisible_char_set = FALSE; + ch = find_invisible_char (GTK_WIDGET (self)); + + if (priv->invisible_char != ch) + { + priv->invisible_char = ch; + g_object_notify_by_pspec (G_OBJECT (self), text_props[PROP_INVISIBLE_CHAR]); + } + + g_object_notify_by_pspec (G_OBJECT (self), text_props[PROP_INVISIBLE_CHAR_SET]); + gtk_text_recompute (self); +} + +/** + * gtk_text_set_overwrite_mode: + * @self: a #GtkText + * @overwrite: new value + * + * Sets whether the text is overwritten when typing in the #GtkText. + **/ +void +gtk_text_set_overwrite_mode (GtkText *self, + gboolean overwrite) +{ + GtkTextPrivate *priv = gtk_text_get_instance_private (self); + + g_return_if_fail (GTK_IS_TEXT (self)); + + if (priv->overwrite_mode == overwrite) + return; + + gtk_text_toggle_overwrite (self); + + g_object_notify_by_pspec (G_OBJECT (self), text_props[PROP_OVERWRITE_MODE]); +} + +/** + * gtk_text_get_overwrite_mode: + * @self: a #GtkText + * + * Gets the value set by gtk_text_set_overwrite_mode(). + * + * Returns: whether the text is overwritten when typing. + **/ +gboolean +gtk_text_get_overwrite_mode (GtkText *self) +{ + GtkTextPrivate *priv = gtk_text_get_instance_private (self); + + g_return_val_if_fail (GTK_IS_TEXT (self), FALSE); + + return priv->overwrite_mode; + +} + +/** + * gtk_text_set_max_length: + * @self: a #GtkText + * @length: the maximum length of the self, or 0 for no maximum. + * (other than the maximum length of entries.) The value passed in will + * be clamped to the range 0-65536. + * + * Sets the maximum allowed length of the contents of the widget. + * + * If the current contents are longer than the given length, then + * they will be truncated to fit. + * + * This is equivalent to getting @self's #GtkEntryBuffer and + * calling gtk_entry_buffer_set_max_length() on it. + * ]| + **/ +void +gtk_text_set_max_length (GtkText *self, + int length) +{ + g_return_if_fail (GTK_IS_TEXT (self)); + gtk_entry_buffer_set_max_length (get_buffer (self), length); +} + +/** + * gtk_text_get_max_length: + * @self: a #GtkText + * + * Retrieves the maximum allowed length of the text in + * @self. See gtk_text_set_max_length(). + * + * This is equivalent to getting @self's #GtkEntryBuffer and + * calling gtk_entry_buffer_get_max_length() on it. + * + * Returns: the maximum allowed number of characters + * in #GtkText, or 0 if there is no maximum. + **/ +int +gtk_text_get_max_length (GtkText *self) +{ + g_return_val_if_fail (GTK_IS_TEXT (self), 0); + + return gtk_entry_buffer_get_max_length (get_buffer (self)); +} + +/** + * gtk_text_get_text_length: + * @self: a #GtkText + * + * Retrieves the current length of the text in + * @self. + * + * This is equivalent to getting @self's #GtkEntryBuffer and + * calling gtk_entry_buffer_get_length() on it. + + * + * Returns: the current number of characters + * in #GtkText, or 0 if there are none. + **/ +guint16 +gtk_text_get_text_length (GtkText *self) +{ + g_return_val_if_fail (GTK_IS_TEXT (self), 0); + + return gtk_entry_buffer_get_length (get_buffer (self)); +} + +/** + * gtk_text_set_activates_default: + * @self: a #GtkText + * @activates: %TRUE to activate window’s default widget on Enter keypress + * + * If @activates is %TRUE, pressing Enter in the @self will activate the default + * widget for the window containing the self. This usually means that + * the dialog box containing the self will be closed, since the default + * widget is usually one of the dialog buttons. + **/ +void +gtk_text_set_activates_default (GtkText *self, + gboolean activates) +{ + GtkTextPrivate *priv = gtk_text_get_instance_private (self); + + g_return_if_fail (GTK_IS_TEXT (self)); + + activates = activates != FALSE; + + if (priv->activates_default != activates) + { + priv->activates_default = activates; + g_object_notify_by_pspec (G_OBJECT (self), text_props[PROP_ACTIVATES_DEFAULT]); + } +} + +/** + * gtk_text_get_activates_default: + * @self: a #GtkText + * + * Retrieves the value set by gtk_text_set_activates_default(). + * + * Returns: %TRUE if the self will activate the default widget + */ +gboolean +gtk_text_get_activates_default (GtkText *self) +{ + GtkTextPrivate *priv = gtk_text_get_instance_private (self); + + g_return_val_if_fail (GTK_IS_TEXT (self), FALSE); + + return priv->activates_default; +} + +static void +gtk_text_set_width_chars (GtkText *self, + int n_chars) +{ + GtkTextPrivate *priv = gtk_text_get_instance_private (self); + + if (priv->width_chars != n_chars) + { + priv->width_chars = n_chars; + g_object_notify (G_OBJECT (self), "width-chars"); + gtk_widget_queue_resize (GTK_WIDGET (self)); + } +} + +static void +gtk_text_set_max_width_chars (GtkText *self, + int n_chars) +{ + GtkTextPrivate *priv = gtk_text_get_instance_private (self); + + if (priv->max_width_chars != n_chars) + { + priv->max_width_chars = n_chars; + g_object_notify (G_OBJECT (self), "max-width-chars"); + gtk_widget_queue_resize (GTK_WIDGET (self)); + } +} + +/** + * gtk_text_set_has_frame: + * @self: a #GtkText + * @has_frame: new value + * + * Sets whether the self has a beveled frame around it. + **/ +void +gtk_text_set_has_frame (GtkText *self, + gboolean has_frame) +{ + GtkStyleContext *context; + + g_return_if_fail (GTK_IS_TEXT (self)); + + has_frame = (has_frame != FALSE); + + if (has_frame == gtk_text_get_has_frame (self)) + return; + + context = gtk_widget_get_style_context (GTK_WIDGET (self)); + if (has_frame) + gtk_style_context_remove_class (context, GTK_STYLE_CLASS_FLAT); + else + gtk_style_context_add_class (context, GTK_STYLE_CLASS_FLAT); + + g_object_notify_by_pspec (G_OBJECT (self), text_props[PROP_HAS_FRAME]); +} + +/** + * gtk_text_get_has_frame: + * @self: a #GtkText + * + * Gets the value set by gtk_text_set_has_frame(). + * + * Returns: whether the self has a beveled frame + **/ +gboolean +gtk_text_get_has_frame (GtkText *self) +{ + GtkStyleContext *context; + + g_return_val_if_fail (GTK_IS_TEXT (self), FALSE); + + context = gtk_widget_get_style_context (GTK_WIDGET (self)); + + return !gtk_style_context_has_class (context, GTK_STYLE_CLASS_FLAT); +} + +PangoLayout * +gtk_text_get_layout (GtkText *self) +{ + PangoLayout *layout; + + g_return_val_if_fail (GTK_IS_TEXT (self), NULL); + + layout = gtk_text_ensure_layout (self, TRUE); + + return layout; +} + +void +gtk_text_get_layout_offsets (GtkText *self, + int *x, + int *y) +{ + g_return_if_fail (GTK_IS_TEXT (self)); + + get_layout_position (self, x, y); +} + +static void +gtk_text_set_alignment (GtkText *self, + float xalign) +{ + GtkTextPrivate *priv = gtk_text_get_instance_private (self); + + if (xalign < 0.0) + xalign = 0.0; + else if (xalign > 1.0) + xalign = 1.0; + + if (xalign != priv->xalign) + { + priv->xalign = xalign; + gtk_text_recompute (self); + g_object_notify (G_OBJECT (self), "xalign"); + } +} + +/* Quick hack of a popup menu + */ +static void +activate_cb (GtkWidget *menuitem, + GtkText *self) +{ + const char *signal; + + signal = g_object_get_qdata (G_OBJECT (menuitem), quark_gtk_signal); + g_signal_emit_by_name (self, signal); +} + + +static gboolean +gtk_text_mnemonic_activate (GtkWidget *widget, + gboolean group_cycling) +{ + gtk_widget_grab_focus (widget); + return GDK_EVENT_STOP; +} + +static void +append_action_signal (GtkText *self, + GtkWidget *menu, + const char *label, + const char *signal, + gboolean sensitive) +{ + GtkWidget *menuitem = gtk_menu_item_new_with_mnemonic (label); + + g_object_set_qdata (G_OBJECT (menuitem), quark_gtk_signal, (char *)signal); + g_signal_connect (menuitem, "activate", + G_CALLBACK (activate_cb), self); + + gtk_widget_set_sensitive (menuitem, sensitive); + + gtk_widget_show (menuitem); + gtk_menu_shell_append (GTK_MENU_SHELL (menu), menuitem); +} + +static void +popup_menu_detach (GtkWidget *attach_widget, + GtkMenu *menu) +{ + GtkText *self_attach = GTK_TEXT (attach_widget); + GtkTextPrivate *priv_attach = gtk_text_get_instance_private (self_attach); + + priv_attach->popup_menu = NULL; +} + +static void +gtk_text_do_popup (GtkText *self, + const GdkEvent *event) +{ + GtkTextPrivate *priv = gtk_text_get_instance_private (self); + GdkEvent *trigger_event; + + /* In order to know what entries we should make sensitive, we + * ask for the current targets of the clipboard, and when + * we get them, then we actually pop up the menu. + */ + trigger_event = event ? gdk_event_copy (event) : gtk_get_current_event (); + + if (gtk_widget_get_realized (GTK_WIDGET (self))) + { + DisplayMode mode; + gboolean clipboard_contains_text; + GtkWidget *menu; + GtkWidget *menuitem; + + clipboard_contains_text = gdk_content_formats_contain_gtype (gdk_clipboard_get_formats (gtk_widget_get_clipboard (GTK_WIDGET (self))), + G_TYPE_STRING); + if (priv->popup_menu) + gtk_widget_destroy (priv->popup_menu); + + priv->popup_menu = menu = gtk_menu_new (); + gtk_style_context_add_class (gtk_widget_get_style_context (menu), + GTK_STYLE_CLASS_CONTEXT_MENU); + + gtk_menu_attach_to_widget (GTK_MENU (menu), GTK_WIDGET (self), popup_menu_detach); + + mode = gtk_text_get_display_mode (self); + append_action_signal (self, menu, _("Cu_t"), "cut-clipboard", + priv->editable && mode == DISPLAY_NORMAL && + priv->current_pos != priv->selection_bound); + + append_action_signal (self, menu, _("_Copy"), "copy-clipboard", + mode == DISPLAY_NORMAL && + priv->current_pos != priv->selection_bound); + + append_action_signal (self, menu, _("_Paste"), "paste-clipboard", + priv->editable && clipboard_contains_text); + + menuitem = gtk_menu_item_new_with_mnemonic (_("_Delete")); + gtk_widget_set_sensitive (menuitem, priv->editable && priv->current_pos != priv->selection_bound); + g_signal_connect_swapped (menuitem, "activate", + G_CALLBACK (gtk_text_delete_cb), self); + gtk_widget_show (menuitem); + gtk_menu_shell_append (GTK_MENU_SHELL (menu), menuitem); + + menuitem = gtk_separator_menu_item_new (); + gtk_widget_show (menuitem); + gtk_menu_shell_append (GTK_MENU_SHELL (menu), menuitem); + + menuitem = gtk_menu_item_new_with_mnemonic (_("Select _All")); + gtk_widget_set_sensitive (menuitem, gtk_entry_buffer_get_length (priv->buffer) > 0); + g_signal_connect_swapped (menuitem, "activate", + G_CALLBACK (gtk_text_select_all), self); + gtk_widget_show (menuitem); + gtk_menu_shell_append (GTK_MENU_SHELL (menu), menuitem); + + if ((gtk_text_get_input_hints (self) & GTK_INPUT_HINT_NO_EMOJI) == 0) + { + menuitem = gtk_menu_item_new_with_mnemonic (_("Insert _Emoji")); + gtk_widget_set_sensitive (menuitem, + mode == DISPLAY_NORMAL && + priv->editable); + g_signal_connect_swapped (menuitem, "activate", + G_CALLBACK (gtk_text_insert_emoji), self); + gtk_widget_show (menuitem); + gtk_menu_shell_append (GTK_MENU_SHELL (menu), menuitem); + } + + g_signal_emit (self, signals[POPULATE_POPUP], 0, menu); + + if (trigger_event && gdk_event_triggers_context_menu (trigger_event)) + gtk_menu_popup_at_pointer (GTK_MENU (menu), trigger_event); + else + { + gtk_menu_popup_at_widget (GTK_MENU (menu), + GTK_WIDGET (self), + GDK_GRAVITY_SOUTH_EAST, + GDK_GRAVITY_NORTH_WEST, + trigger_event); + + gtk_menu_shell_select_first (GTK_MENU_SHELL (menu), FALSE); + } + } + + g_clear_object (&trigger_event); +} + +static gboolean +gtk_text_popup_menu (GtkWidget *widget) +{ + gtk_text_do_popup (GTK_TEXT (widget), NULL); + return GDK_EVENT_STOP; +} + +static void +show_or_hide_handles (GtkWidget *popover, + GParamSpec *pspec, + GtkText *self) +{ + GtkTextPrivate *priv = gtk_text_get_instance_private (self); + gboolean visible; + GtkTextHandle *handle; + GtkTextHandleMode mode; + + visible = gtk_widget_get_visible (popover); + + handle = priv->text_handle; + mode = _gtk_text_handle_get_mode (handle); + + if (mode == GTK_TEXT_HANDLE_MODE_CURSOR) + { + _gtk_text_handle_set_visible (handle, GTK_TEXT_HANDLE_POSITION_CURSOR, !visible); + } + else if (mode == GTK_TEXT_HANDLE_MODE_SELECTION) + { + _gtk_text_handle_set_visible (handle, GTK_TEXT_HANDLE_POSITION_SELECTION_START, !visible); + _gtk_text_handle_set_visible (handle, GTK_TEXT_HANDLE_POSITION_SELECTION_END, !visible); + } +} + +static void +activate_bubble_cb (GtkWidget *item, + GtkText *self) +{ + GtkTextPrivate *priv = gtk_text_get_instance_private (self); + const char *signal; + + signal = g_object_get_qdata (G_OBJECT (item), quark_gtk_signal); + gtk_widget_hide (priv->selection_bubble); + if (strcmp (signal, "select-all") == 0) + gtk_text_select_all (self); + else + g_signal_emit_by_name (self, signal); +} + +static void +append_bubble_action (GtkText *self, + GtkWidget *toolbar, + const char *label, + const char *icon_name, + const char *signal, + gboolean sensitive) +{ + GtkWidget *item, *image; + + item = gtk_button_new (); + gtk_widget_set_focus_on_click (item, FALSE); + image = gtk_image_new_from_icon_name (icon_name); + gtk_widget_show (image); + gtk_container_add (GTK_CONTAINER (item), image); + gtk_widget_set_tooltip_text (item, label); + gtk_style_context_add_class (gtk_widget_get_style_context (item), "image-button"); + g_object_set_qdata (G_OBJECT (item), quark_gtk_signal, (char *)signal); + g_signal_connect (item, "clicked", G_CALLBACK (activate_bubble_cb), self); + gtk_widget_set_sensitive (GTK_WIDGET (item), sensitive); + gtk_widget_show (GTK_WIDGET (item)); + gtk_container_add (GTK_CONTAINER (toolbar), item); +} + +static gboolean +gtk_text_selection_bubble_popup_show (gpointer user_data) +{ + GtkText *self = user_data; + GtkTextPrivate *priv = gtk_text_get_instance_private (self); + cairo_rectangle_int_t rect; + GtkAllocation allocation; + int start_x, end_x; + gboolean has_selection; + gboolean has_clipboard; + gboolean all_selected; + DisplayMode mode; + GtkWidget *box; + GtkWidget *toolbar; + int length; + GtkAllocation text_allocation; + + gtk_text_get_text_allocation (self, &text_allocation); + + has_selection = priv->selection_bound != priv->current_pos; + length = gtk_entry_buffer_get_length (get_buffer (self)); + all_selected = (priv->selection_bound == 0) && (priv->current_pos == length); + + if (!has_selection && !priv->editable) + { + priv->selection_bubble_timeout_id = 0; + return G_SOURCE_REMOVE; + } + + if (priv->selection_bubble) + gtk_widget_destroy (priv->selection_bubble); + + priv->selection_bubble = gtk_popover_new (GTK_WIDGET (self)); + gtk_style_context_add_class (gtk_widget_get_style_context (priv->selection_bubble), + GTK_STYLE_CLASS_TOUCH_SELECTION); + gtk_popover_set_position (GTK_POPOVER (priv->selection_bubble), GTK_POS_BOTTOM); + gtk_popover_set_modal (GTK_POPOVER (priv->selection_bubble), FALSE); + g_signal_connect (priv->selection_bubble, "notify::visible", + G_CALLBACK (show_or_hide_handles), self); + + box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 5); + g_object_set (box, "margin", 10, NULL); + gtk_widget_show (box); + toolbar = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 5); + gtk_widget_show (toolbar); + gtk_container_add (GTK_CONTAINER (priv->selection_bubble), box); + gtk_container_add (GTK_CONTAINER (box), toolbar); + + has_clipboard = gdk_content_formats_contain_gtype (gdk_clipboard_get_formats (gtk_widget_get_clipboard (GTK_WIDGET (self))), + G_TYPE_STRING); + mode = gtk_text_get_display_mode (self); + + if (priv->editable && has_selection && mode == DISPLAY_NORMAL) + append_bubble_action (self, toolbar, _("Select all"), "edit-select-all-symbolic", "select-all", !all_selected); + + if (priv->editable && has_selection && mode == DISPLAY_NORMAL) + append_bubble_action (self, toolbar, _("Cut"), "edit-cut-symbolic", "cut-clipboard", TRUE); + + if (has_selection && mode == DISPLAY_NORMAL) + append_bubble_action (self, toolbar, _("Copy"), "edit-copy-symbolic", "copy-clipboard", TRUE); + + if (priv->editable) + append_bubble_action (self, toolbar, _("Paste"), "edit-paste-symbolic", "paste-clipboard", has_clipboard); + + if (priv->populate_all) + g_signal_emit (self, signals[POPULATE_POPUP], 0, box); + + gtk_widget_get_allocation (GTK_WIDGET (self), &allocation); + + gtk_text_get_cursor_locations (self, &start_x, NULL); + + start_x -= priv->scroll_offset; + start_x = CLAMP (start_x, 0, text_allocation.width); + rect.y = text_allocation.y - allocation.y; + rect.height = text_allocation.height; + + if (has_selection) + { + end_x = gtk_text_get_selection_bound_location (self) - priv->scroll_offset; + end_x = CLAMP (end_x, 0, text_allocation.width); + + rect.x = text_allocation.x - allocation.x + MIN (start_x, end_x); + rect.width = ABS (end_x - start_x); + } + else + { + rect.x = text_allocation.x - allocation.x + start_x; + rect.width = 0; + } + + rect.x -= 5; + rect.y -= 5; + rect.width += 10; + rect.height += 10; + + gtk_popover_set_pointing_to (GTK_POPOVER (priv->selection_bubble), &rect); + gtk_widget_show (priv->selection_bubble); + + priv->selection_bubble_timeout_id = 0; + + return G_SOURCE_REMOVE; +} + +static void +gtk_text_selection_bubble_popup_unset (GtkText *self) +{ + GtkTextPrivate *priv = gtk_text_get_instance_private (self); + + if (priv->selection_bubble) + gtk_widget_hide (priv->selection_bubble); + + if (priv->selection_bubble_timeout_id) + { + g_source_remove (priv->selection_bubble_timeout_id); + priv->selection_bubble_timeout_id = 0; + } +} + +static void +gtk_text_selection_bubble_popup_set (GtkText *self) +{ + GtkTextPrivate *priv = gtk_text_get_instance_private (self); + + if (priv->selection_bubble_timeout_id) + g_source_remove (priv->selection_bubble_timeout_id); + + priv->selection_bubble_timeout_id = + g_timeout_add (50, gtk_text_selection_bubble_popup_show, self); + g_source_set_name_by_id (priv->selection_bubble_timeout_id, "[gtk] gtk_text_selection_bubble_popup_cb"); +} + +static void +gtk_text_drag_begin (GtkWidget *widget, + GdkDrag *drag) +{ + GtkText *self = GTK_TEXT (widget); + GtkTextPrivate *priv = gtk_text_get_instance_private (self); + char *text; + + text = _gtk_text_get_selected_text (self); + + if (text) + { + int *ranges, n_ranges; + GdkPaintable *paintable; + + paintable = gtk_text_util_create_drag_icon (widget, text, -1); + gtk_text_get_pixel_ranges (self, &ranges, &n_ranges); + + gtk_drag_set_icon_paintable (drag, + paintable, + priv->drag_start_x - ranges[0], + priv->drag_start_y); + + g_free (ranges); + g_object_unref (paintable); + g_free (text); + } +} + +static void +gtk_text_drag_end (GtkWidget *widget, + GdkDrag *drag) +{ +} + +static void +gtk_text_drag_leave (GtkWidget *widget, + GdkDrop *drop) +{ + GtkText *self = GTK_TEXT (widget); + GtkTextPrivate *priv = gtk_text_get_instance_private (self); + + gtk_drag_unhighlight (widget); + priv->dnd_position = -1; + gtk_widget_queue_draw (widget); +} + +static gboolean +gtk_text_drag_drop (GtkWidget *widget, + GdkDrop *drop, + int x, + int y) +{ + GtkText *self = GTK_TEXT (widget); + GtkTextPrivate *priv = gtk_text_get_instance_private (self); + GdkAtom target = NULL; + + if (priv->editable) + target = gtk_drag_dest_find_target (widget, drop, NULL); + + if (target != NULL) + { + priv->drop_position = gtk_text_find_position (self, x + priv->scroll_offset); + gtk_drag_get_data (widget, drop, target); + } + else + gdk_drop_finish (drop, 0); + + return TRUE; +} + +static gboolean +gtk_text_drag_motion (GtkWidget *widget, + GdkDrop *drop, + int x, + int y) +{ + GtkText *self = GTK_TEXT (widget); + GtkTextPrivate *priv = gtk_text_get_instance_private (self); + GdkDragAction suggested_action; + int new_position, old_position; + + old_position = priv->dnd_position; + new_position = gtk_text_find_position (self, x + priv->scroll_offset); + + if (priv->editable && + gtk_drag_dest_find_target (widget, drop, NULL) != NULL) + { + suggested_action = GDK_ACTION_COPY | GDK_ACTION_MOVE; + + if (priv->selection_bound == priv->current_pos || + new_position < priv->selection_bound || + new_position > priv->current_pos) + { + priv->dnd_position = new_position; + } + else + { + priv->dnd_position = -1; + } + } + else + { + /* Entry not editable, or no text */ + suggested_action = 0; + priv->dnd_position = -1; + } + + gdk_drop_status (drop, suggested_action); + if (suggested_action == 0) + gtk_drag_unhighlight (widget); + else + gtk_drag_highlight (widget); + + if (priv->dnd_position != old_position) + gtk_widget_queue_draw (widget); + + return TRUE; +} + +static GdkDragAction +gtk_text_get_action (GtkText *self, + GdkDrop *drop) +{ + GtkWidget *widget = GTK_WIDGET (self); + GdkDrag *drag = gdk_drop_get_drag (drop); + GtkWidget *source_widget = gtk_drag_get_source_widget (drag); + GdkDragAction actions; + + actions = gdk_drop_get_actions (drop); + + if (source_widget == widget && + actions & GDK_ACTION_MOVE) + return GDK_ACTION_MOVE; + + if (actions & GDK_ACTION_COPY) + return GDK_ACTION_COPY; + + if (actions & GDK_ACTION_MOVE) + return GDK_ACTION_MOVE; + + return 0; +} + +static void +gtk_text_drag_data_received (GtkWidget *widget, + GdkDrop *drop, + GtkSelectionData *selection_data) +{ + GtkText *self = GTK_TEXT (widget); + GtkTextPrivate *priv = gtk_text_get_instance_private (self); + GdkDragAction action; + char *str; + + str = (char *) gtk_selection_data_get_text (selection_data); + action = gtk_text_get_action (self, drop); + + if (action && str && priv->editable) + { + int length = -1; + int pos; + + if (priv->truncate_multiline) + length = truncate_multiline (str); + + if (priv->selection_bound == priv->current_pos || + priv->drop_position < priv->selection_bound || + priv->drop_position > priv->current_pos) + { + gtk_text_insert_text (self, str, length, &priv->drop_position); + } + else + { + /* Replacing selection */ + begin_change (self); + gtk_text_delete_text (self, priv->selection_bound, priv->current_pos); + pos = priv->selection_bound; + gtk_text_insert_text (self, str, length, &pos); + end_change (self); + } + + gdk_drop_finish (drop, action); + } + else + { + /* Drag and drop didn't happen! */ + gdk_drop_finish (drop, 0); + } + + g_free (str); +} + +static void +gtk_text_drag_data_get (GtkWidget *widget, + GdkDrag *drag, + GtkSelectionData *selection_data) +{ + GtkText *self = GTK_TEXT (widget); + GtkTextPrivate *priv = gtk_text_get_instance_private (self); + + if (priv->selection_bound != priv->current_pos) + { + char *str = gtk_text_get_display_text (self, priv->selection_bound, priv->current_pos); + + gtk_selection_data_set_text (selection_data, str, -1); + + g_free (str); + } +} + +static void +gtk_text_drag_data_delete (GtkWidget *widget, + GdkDrag *drag) +{ + GtkText *self = GTK_TEXT (widget); + GtkTextPrivate *priv = gtk_text_get_instance_private (self); + + if (priv->editable && + priv->selection_bound != priv->current_pos) + gtk_text_delete_text (self, priv->selection_bound, priv->current_pos); +} + +/* We display the cursor when + * + * - the selection is empty, AND + * - the widget has focus + */ + +#define CURSOR_ON_MULTIPLIER 2 +#define CURSOR_OFF_MULTIPLIER 1 +#define CURSOR_PEND_MULTIPLIER 3 +#define CURSOR_DIVIDER 3 + +static gboolean +cursor_blinks (GtkText *self) +{ + GtkTextPrivate *priv = gtk_text_get_instance_private (self); + + if (gtk_widget_has_focus (GTK_WIDGET (self)) && + priv->editable && + priv->selection_bound == priv->current_pos) + { + GtkSettings *settings; + gboolean blink; + + settings = gtk_widget_get_settings (GTK_WIDGET (self)); + g_object_get (settings, "gtk-cursor-blink", &blink, NULL); + + return blink; + } + else + return FALSE; +} + +static gboolean +get_middle_click_paste (GtkText *self) +{ + GtkSettings *settings; + gboolean paste; + + settings = gtk_widget_get_settings (GTK_WIDGET (self)); + g_object_get (settings, "gtk-enable-primary-paste", &paste, NULL); + + return paste; +} + +static int +get_cursor_time (GtkText *self) +{ + GtkSettings *settings = gtk_widget_get_settings (GTK_WIDGET (self)); + int time; + + g_object_get (settings, "gtk-cursor-blink-time", &time, NULL); + + return time; +} + +static int +get_cursor_blink_timeout (GtkText *self) +{ + GtkSettings *settings = gtk_widget_get_settings (GTK_WIDGET (self)); + int timeout; + + g_object_get (settings, "gtk-cursor-blink-timeout", &timeout, NULL); + + return timeout; +} + +static void +show_cursor (GtkText *self) +{ + GtkTextPrivate *priv = gtk_text_get_instance_private (self); + GtkWidget *widget; + + if (!priv->cursor_visible) + { + priv->cursor_visible = TRUE; + + widget = GTK_WIDGET (self); + if (gtk_widget_has_focus (widget) && priv->selection_bound == priv->current_pos) + gtk_widget_queue_draw (widget); + } +} + +static void +hide_cursor (GtkText *self) +{ + GtkTextPrivate *priv = gtk_text_get_instance_private (self); + GtkWidget *widget; + + if (priv->cursor_visible) + { + priv->cursor_visible = FALSE; + + widget = GTK_WIDGET (self); + if (gtk_widget_has_focus (widget) && priv->selection_bound == priv->current_pos) + gtk_widget_queue_draw (widget); + } +} + +/* + * Blink! + */ +static int +blink_cb (gpointer data) +{ + GtkText *self = data; + GtkTextPrivate *priv = gtk_text_get_instance_private (self); + int blink_timeout; + + if (!gtk_widget_has_focus (GTK_WIDGET (self))) + { + g_warning ("GtkText - did not receive a focus-out event.\n" + "If you handle this event, you must return\n" + "GDK_EVENT_PROPAGATE so the self gets the event as well"); + + gtk_text_check_cursor_blink (self); + + return G_SOURCE_REMOVE; + } + + g_assert (priv->selection_bound == priv->current_pos); + + blink_timeout = get_cursor_blink_timeout (self); + if (priv->blink_time > 1000 * blink_timeout && + blink_timeout < G_MAXINT/1000) + { + /* we've blinked enough without the user doing anything, stop blinking */ + show_cursor (self); + priv->blink_timeout = 0; + } + else if (priv->cursor_visible) + { + hide_cursor (self); + priv->blink_timeout = g_timeout_add (get_cursor_time (self) * CURSOR_OFF_MULTIPLIER / CURSOR_DIVIDER, + blink_cb, + self); + g_source_set_name_by_id (priv->blink_timeout, "[gtk] blink_cb"); + } + else + { + show_cursor (self); + priv->blink_time += get_cursor_time (self); + priv->blink_timeout = g_timeout_add (get_cursor_time (self) * CURSOR_ON_MULTIPLIER / CURSOR_DIVIDER, + blink_cb, + self); + g_source_set_name_by_id (priv->blink_timeout, "[gtk] blink_cb"); + } + + return G_SOURCE_REMOVE; +} + +static void +gtk_text_check_cursor_blink (GtkText *self) +{ + GtkTextPrivate *priv = gtk_text_get_instance_private (self); + + if (cursor_blinks (self)) + { + if (!priv->blink_timeout) + { + show_cursor (self); + priv->blink_timeout = g_timeout_add (get_cursor_time (self) * CURSOR_ON_MULTIPLIER / CURSOR_DIVIDER, + blink_cb, + self); + g_source_set_name_by_id (priv->blink_timeout, "[gtk] blink_cb"); + } + } + else + { + if (priv->blink_timeout) + { + g_source_remove (priv->blink_timeout); + priv->blink_timeout = 0; + } + + priv->cursor_visible = TRUE; + } +} + +static void +gtk_text_pend_cursor_blink (GtkText *self) +{ + GtkTextPrivate *priv = gtk_text_get_instance_private (self); + + if (cursor_blinks (self)) + { + if (priv->blink_timeout != 0) + g_source_remove (priv->blink_timeout); + + priv->blink_timeout = g_timeout_add (get_cursor_time (self) * CURSOR_PEND_MULTIPLIER / CURSOR_DIVIDER, + blink_cb, + self); + g_source_set_name_by_id (priv->blink_timeout, "[gtk] blink_cb"); + show_cursor (self); + } +} + +static void +gtk_text_reset_blink_time (GtkText *self) +{ + GtkTextPrivate *priv = gtk_text_get_instance_private (self); + + priv->blink_time = 0; +} + +/** + * gtk_text_set_placeholder_text: + * @self: a #GtkText + * @text: (nullable): a string to be displayed when @self is empty and unfocused, or %NULL + * + * Sets text to be displayed in @self when it is empty. + * + * This can be used to give a visual hint of the expected + * contents of the self. + **/ +void +gtk_text_set_placeholder_text (GtkText *self, + const char *text) +{ + GtkTextPrivate *priv = gtk_text_get_instance_private (self); + + g_return_if_fail (GTK_IS_TEXT (self)); + + if (priv->placeholder == NULL) + { + priv->placeholder = g_object_new (GTK_TYPE_LABEL, + "label", text, + "css-name", "placeholder", + "xalign", 0.0f, + "ellipsize", PANGO_ELLIPSIZE_END, + NULL); + gtk_widget_insert_after (priv->placeholder, GTK_WIDGET (self), NULL); + } + else + { + gtk_label_set_text (GTK_LABEL (priv->placeholder), text); + } + + g_object_notify_by_pspec (G_OBJECT (self), text_props[PROP_PLACEHOLDER_TEXT]); +} + +/** + * gtk_text_get_placeholder_text: + * @self: a #GtkText + * + * Retrieves the text that will be displayed when @self is empty and unfocused + * + * Returns: (nullable) (transfer none):a pointer to the placeholder text as a string. + * This string points to internally allocated storage in the widget and must + * not be freed, modified or stored. If no placeholder text has been set, + * %NULL will be returned. + **/ +const char * +gtk_text_get_placeholder_text (GtkText *self) +{ + GtkTextPrivate *priv = gtk_text_get_instance_private (self); + + g_return_val_if_fail (GTK_IS_TEXT (self), NULL); + + if (!priv->placeholder) + return NULL; + + return gtk_label_get_text (GTK_LABEL (priv->placeholder)); +} + +/** + * gtk_text_set_input_purpose: + * @self: a #GtkText + * @purpose: the purpose + * + * Sets the #GtkText:input-purpose property which + * can be used by on-screen keyboards and other input + * methods to adjust their behaviour. + */ +void +gtk_text_set_input_purpose (GtkText *self, + GtkInputPurpose purpose) +{ + GtkTextPrivate *priv = gtk_text_get_instance_private (self); + + g_return_if_fail (GTK_IS_TEXT (self)); + + if (gtk_text_get_input_purpose (self) != purpose) + { + g_object_set (G_OBJECT (priv->im_context), + "input-purpose", purpose, + NULL); + + g_object_notify_by_pspec (G_OBJECT (self), text_props[PROP_INPUT_PURPOSE]); + } +} + +/** + * gtk_text_get_input_purpose: + * @self: a #GtkText + * + * Gets the value of the #GtkText:input-purpose property. + */ +GtkInputPurpose +gtk_text_get_input_purpose (GtkText *self) +{ + GtkTextPrivate *priv = gtk_text_get_instance_private (self); + GtkInputPurpose purpose; + + g_return_val_if_fail (GTK_IS_TEXT (self), GTK_INPUT_PURPOSE_FREE_FORM); + + g_object_get (G_OBJECT (priv->im_context), + "input-purpose", &purpose, + NULL); + + return purpose; +} + +/** + * gtk_text_set_input_hints: + * @self: a #GtkText + * @hints: the hints + * + * Sets the #GtkText:input-hints property, which + * allows input methods to fine-tune their behaviour. + */ +void +gtk_text_set_input_hints (GtkText *self, + GtkInputHints hints) + +{ + GtkTextPrivate *priv = gtk_text_get_instance_private (self); + + g_return_if_fail (GTK_IS_TEXT (self)); + + if (gtk_text_get_input_hints (self) != hints) + { + g_object_set (G_OBJECT (priv->im_context), + "input-hints", hints, + NULL); + + g_object_notify_by_pspec (G_OBJECT (self), text_props[PROP_INPUT_HINTS]); + } +} + +/** + * gtk_text_get_input_hints: + * @self: a #GtkText + * + * Gets the value of the #GtkText:input-hints property. + */ +GtkInputHints +gtk_text_get_input_hints (GtkText *self) +{ + GtkTextPrivate *priv = gtk_text_get_instance_private (self); + GtkInputHints hints; + + g_return_val_if_fail (GTK_IS_TEXT (self), GTK_INPUT_HINT_NONE); + + g_object_get (G_OBJECT (priv->im_context), + "input-hints", &hints, + NULL); + + return hints; +} + +/** + * gtk_text_set_attributes: + * @self: a #GtkText + * @attrs: a #PangoAttrList + * + * Sets a #PangoAttrList; the attributes in the list are applied to the + * self text. + */ +void +gtk_text_set_attributes (GtkText *self, + PangoAttrList *attrs) +{ + GtkTextPrivate *priv = gtk_text_get_instance_private (self); + g_return_if_fail (GTK_IS_TEXT (self)); + + if (attrs) + pango_attr_list_ref (attrs); + + if (priv->attrs) + pango_attr_list_unref (priv->attrs); + priv->attrs = attrs; + + g_object_notify_by_pspec (G_OBJECT (self), text_props[PROP_ATTRIBUTES]); + + gtk_text_recompute (self); + gtk_widget_queue_resize (GTK_WIDGET (self)); +} + +/** + * gtk_text_get_attributes: + * @self: a #GtkText + * + * Gets the attribute list that was set on the self using + * gtk_text_set_attributes(), if any. + * + * Returns: (transfer none) (nullable): the attribute list, or %NULL + * if none was set. + */ +PangoAttrList * +gtk_text_get_attributes (GtkText *self) +{ + GtkTextPrivate *priv = gtk_text_get_instance_private (self); + + g_return_val_if_fail (GTK_IS_TEXT (self), NULL); + + return priv->attrs; +} + +/** + * gtk_text_set_tabs: + * @self: a #GtkText + * @tabs: (nullable): a #PangoTabArray + * + * Sets a #PangoTabArray; the tabstops in the array are applied to the self + * text. + */ + +void +gtk_text_set_tabs (GtkText *self, + PangoTabArray *tabs) +{ + GtkTextPrivate *priv = gtk_text_get_instance_private (self); + + g_return_if_fail (GTK_IS_TEXT (self)); + + if (priv->tabs) + pango_tab_array_free(priv->tabs); + + if (tabs) + priv->tabs = pango_tab_array_copy (tabs); + else + priv->tabs = NULL; + + g_object_notify_by_pspec (G_OBJECT (self), text_props[PROP_TABS]); + + gtk_text_recompute (self); + gtk_widget_queue_resize (GTK_WIDGET (self)); +} + +/** + * gtk_text_get_tabs: + * @self: a #GtkText + * + * Gets the tabstops that were set on the self using gtk_text_set_tabs(), if + * any. + * + * Returns: (nullable) (transfer none): the tabstops, or %NULL if none was set. + */ +PangoTabArray * +gtk_text_get_tabs (GtkText *self) +{ + GtkTextPrivate *priv = gtk_text_get_instance_private (self); + + g_return_val_if_fail (GTK_IS_TEXT (self), NULL); + + return priv->tabs; +} + +static void +gtk_text_insert_emoji (GtkText *self) +{ + GtkWidget *chooser; + + if (gtk_widget_get_ancestor (GTK_WIDGET (self), GTK_TYPE_EMOJI_CHOOSER) != NULL) + return; + + chooser = GTK_WIDGET (g_object_get_data (G_OBJECT (self), "gtk-emoji-chooser")); + if (!chooser) + { + chooser = gtk_emoji_chooser_new (); + g_object_set_data (G_OBJECT (self), "gtk-emoji-chooser", chooser); + + gtk_popover_set_relative_to (GTK_POPOVER (chooser), GTK_WIDGET (self)); + g_signal_connect_swapped (chooser, "emoji-picked", G_CALLBACK (gtk_text_enter_text), self); + } + + gtk_popover_popup (GTK_POPOVER (chooser)); +} + +static void +set_enable_emoji_completion (GtkText *self, + gboolean value) +{ + GtkTextPrivate *priv = gtk_text_get_instance_private (self); + + if (priv->enable_emoji_completion == value) + return; + + priv->enable_emoji_completion = value; + + if (priv->enable_emoji_completion) + g_object_set_data (G_OBJECT (self), "emoji-completion-popup", + gtk_emoji_completion_new (self)); + else + g_object_set_data (G_OBJECT (self), "emoji-completion-popup", NULL); + + g_object_notify_by_pspec (G_OBJECT (self), text_props[PROP_ENABLE_EMOJI_COMPLETION]); +} + +static void +set_text_cursor (GtkWidget *widget) +{ + gtk_widget_set_cursor_from_name (widget, "text"); +} + +GtkEventController * +gtk_text_get_key_controller (GtkText *self) +{ + GtkTextPrivate *priv = gtk_text_get_instance_private (self); + + return priv->key_controller; +} diff --git a/gtk/gtktext.h b/gtk/gtktext.h new file mode 100644 index 0000000000..c0a1e58f8e --- /dev/null +++ b/gtk/gtktext.h @@ -0,0 +1,223 @@ +/* GTK - The GIMP Toolkit + * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald + * + * Copyright (C) 2004-2006 Christian Hammond + * Copyright (C) 2008 Cody Russell + * Copyright (C) 2008 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + */ + +/* + * Modified by the GTK+ Team and others 1997-2000. See the AUTHORS + * file for a list of people on the GTK+ Team. See the ChangeLog + * files for a list of changes. These files are distributed with + * GTK+ at ftp://ftp.gtk.org/pub/gtk/. + */ + +#ifndef __GTK_TEXT_H__ +#define __GTK_TEXT_H__ + + +#if !defined (__GTK_H_INSIDE__) && !defined (GTK_COMPILATION) +#error "Only can be included directly." +#endif + +#include +#include + + +G_BEGIN_DECLS + +#define GTK_TYPE_TEXT (gtk_text_get_type ()) +#define GTK_TEXT(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTK_TYPE_TEXT, GtkText)) +#define GTK_TEXT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GTK_TYPE_TEXT, GtkTextClass)) +#define GTK_IS_TEXT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTK_TYPE_TEXT)) +#define GTK_IS_TEXT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GTK_TYPE_TEXT)) +#define GTK_TEXT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GTK_TYPE_TEXT, GtkTextClass)) + +typedef struct _GtkText GtkText; +typedef struct _GtkTextClass GtkTextClass; + +struct _GtkText +{ + /*< private >*/ + GtkWidget parent_instance; +}; + +/** + * GtkTextClass: + * @parent_class: The parent class. + * @populate_popup: Class handler for the #GtkText::populate-popup signal. If + * non-%NULL, this will be called to add additional entries to the context + * menu when it is displayed. + * @activate: Class handler for the #GtkText::activate signal. The default + * implementation calls gtk_window_activate_default() on the entry’s top-level + * window. + * @move_cursor: Class handler for the #GtkText::move-cursor signal. The + * default implementation specifies the standard #GtkText cursor movement + * behavior. + * @insert_at_cursor: Class handler for the #GtkText::insert-at-cursor signal. + * The default implementation inserts text at the cursor. + * @delete_from_cursor: Class handler for the #GtkText::delete-from-cursor + * signal. The default implementation deletes the selection or the specified + * number of characters or words. + * @backspace: Class handler for the #GtkText::backspace signal. The default + * implementation deletes the selection or a single character or word. + * @cut_clipboard: Class handler for the #GtkText::cut-clipboard signal. The + * default implementation cuts the selection, if one exists. + * @copy_clipboard: Class handler for the #GtkText::copy-clipboard signal. The + * default implementation copies the selection, if one exists. + * @paste_clipboard: Class handler for the #GtkText::paste-clipboard signal. + * The default implementation pastes at the current cursor position or over + * the current selection if one exists. + * @toggle_overwrite: Class handler for the #GtkText::toggle-overwrite signal. + * The default implementation toggles overwrite mode and blinks the cursor. + * @insert_emoji: Class handler for the #GtkText::insert-emoji signal. + * + * Class structure for #GtkText. All virtual functions have a default + * implementation. Derived classes may set the virtual function pointers for the + * signal handlers to %NULL, but must keep @get_text_area_size and + * @get_frame_size non-%NULL; either use the default implementation, or provide + * a custom one. + */ +struct _GtkTextClass +{ + GtkWidgetClass parent_class; + + /* Hook to customize right-click popup */ + void (* populate_popup) (GtkText *self, + GtkWidget *popup); + + /* Action signals + */ + void (* activate) (GtkText *self); + void (* move_cursor) (GtkText *self, + GtkMovementStep step, + gint count, + gboolean extend); + void (* insert_at_cursor) (GtkText *self, + const gchar *str); + void (* delete_from_cursor) (GtkText *self, + GtkDeleteType type, + gint count); + void (* backspace) (GtkText *self); + void (* cut_clipboard) (GtkText *self); + void (* copy_clipboard) (GtkText *self); + void (* paste_clipboard) (GtkText *self); + void (* toggle_overwrite) (GtkText *self); + void (* insert_emoji) (GtkText *self); + + /*< private >*/ + + /* Padding for future expansion */ + void (*_gtk_reserved1) (void); + void (*_gtk_reserved2) (void); + void (*_gtk_reserved3) (void); + void (*_gtk_reserved4) (void); + void (*_gtk_reserved5) (void); + void (*_gtk_reserved6) (void); +}; + +GDK_AVAILABLE_IN_ALL +GType gtk_text_get_type (void) G_GNUC_CONST; + +GDK_AVAILABLE_IN_ALL +GtkWidget * gtk_text_new (void); +GDK_AVAILABLE_IN_ALL +GtkWidget * gtk_text_new_with_buffer (GtkEntryBuffer *buffer); + +GDK_AVAILABLE_IN_ALL +GtkEntryBuffer *gtk_text_get_buffer (GtkText *self); +GDK_AVAILABLE_IN_ALL +void gtk_text_set_buffer (GtkText *self, + GtkEntryBuffer *buffer); + +GDK_AVAILABLE_IN_ALL +void gtk_text_set_visibility (GtkText *self, + gboolean visible); +GDK_AVAILABLE_IN_ALL +gboolean gtk_text_get_visibility (GtkText *self); + +GDK_AVAILABLE_IN_ALL +void gtk_text_set_invisible_char (GtkText *self, + gunichar ch); +GDK_AVAILABLE_IN_ALL +gunichar gtk_text_get_invisible_char (GtkText *self); +GDK_AVAILABLE_IN_ALL +void gtk_text_unset_invisible_char (GtkText *self); + +GDK_AVAILABLE_IN_ALL +void gtk_text_set_has_frame (GtkText *self, + gboolean has_frame); +GDK_AVAILABLE_IN_ALL +gboolean gtk_text_get_has_frame (GtkText *self); + +GDK_AVAILABLE_IN_ALL +void gtk_text_set_overwrite_mode (GtkText *self, + gboolean overwrite); +GDK_AVAILABLE_IN_ALL +gboolean gtk_text_get_overwrite_mode (GtkText *self); + +GDK_AVAILABLE_IN_ALL +void gtk_text_set_max_length (GtkText *self, + int length); +GDK_AVAILABLE_IN_ALL +gint gtk_text_get_max_length (GtkText *self); +GDK_AVAILABLE_IN_ALL +guint16 gtk_text_get_text_length (GtkText *self); + +GDK_AVAILABLE_IN_ALL +void gtk_text_set_activates_default (GtkText *self, + gboolean activates); +GDK_AVAILABLE_IN_ALL +gboolean gtk_text_get_activates_default (GtkText *self); + +GDK_AVAILABLE_IN_ALL +const char * gtk_text_get_placeholder_text (GtkText *self); +GDK_AVAILABLE_IN_ALL +void gtk_text_set_placeholder_text (GtkText *self, + const char *text); + +GDK_AVAILABLE_IN_ALL +void gtk_text_set_input_purpose (GtkText *self, + GtkInputPurpose purpose); +GDK_AVAILABLE_IN_ALL +GtkInputPurpose gtk_text_get_input_purpose (GtkText *self); + +GDK_AVAILABLE_IN_ALL +void gtk_text_set_input_hints (GtkText *self, + GtkInputHints hints); +GDK_AVAILABLE_IN_ALL +GtkInputHints gtk_text_get_input_hints (GtkText *self); + +GDK_AVAILABLE_IN_ALL +void gtk_text_set_attributes (GtkText *self, + PangoAttrList *attrs); +GDK_AVAILABLE_IN_ALL +PangoAttrList * gtk_text_get_attributes (GtkText *self); + +GDK_AVAILABLE_IN_ALL +void gtk_text_set_tabs (GtkText *self, + PangoTabArray *tabs); + +GDK_AVAILABLE_IN_ALL +PangoTabArray * gtk_text_get_tabs (GtkText *self); + +GDK_AVAILABLE_IN_ALL +void gtk_text_grab_focus_without_selecting (GtkText *self); + +G_END_DECLS + +#endif /* __GTK_TEXT_H__ */ diff --git a/gtk/gtktextprivate.h b/gtk/gtktextprivate.h new file mode 100644 index 0000000000..b1f566af8a --- /dev/null +++ b/gtk/gtktextprivate.h @@ -0,0 +1,46 @@ +/* gtkentryprivate.h + * Copyright (C) 2019 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * 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, see . + */ + +#ifndef __GTK_TEXT_PRIVATE_H__ +#define __GTK_TEXT_PRIVATE_H__ + +#include "gtktext.h" + +#include "gtkeventcontroller.h" +#include "gtkimcontext.h" + +G_BEGIN_DECLS + +char * gtk_text_get_display_text (GtkText *entry, + int start_pos, + int end_pos); +GtkIMContext * gtk_text_get_im_context (GtkText *entry); +void gtk_text_enter_text (GtkText *entry, + const char *text); +void gtk_text_set_positions (GtkText *entry, + int current_pos, + int selection_bound); +PangoLayout * gtk_text_get_layout (GtkText *entry); +void gtk_text_get_layout_offsets (GtkText *entry, + int *x, + int *y); +void gtk_text_reset_im_context (GtkText *entry); +GtkEventController *gtk_text_get_key_controller (GtkText *entry); + +G_END_DECLS + +#endif /* __GTK_TEXT_PRIVATE_H__ */ diff --git a/gtk/meson.build b/gtk/meson.build index 558232b9e7..3eefb1139a 100644 --- a/gtk/meson.build +++ b/gtk/meson.build @@ -352,6 +352,7 @@ gtk_public_sources = files([ 'gtkstyleprovider.c', 'gtkswitch.c', 'gtktestutils.c', + 'gtktext.c', 'gtktextattributes.c', 'gtktextbuffer.c', 'gtktextchild.c', @@ -588,6 +589,7 @@ gtk_public_headers = files([ 'gtkstyleprovider.h', 'gtkswitch.h', 'gtktestutils.h', + 'gtktext.h', 'gtktextbuffer.h', 'gtktextchild.h', 'gtktextiter.h', From fcb58887fa6edbaabce3201f6e4855ece96f9b72 Mon Sep 17 00:00:00 2001 From: Matthias Clasen Date: Fri, 15 Feb 2019 15:46:51 -0500 Subject: [PATCH 05/34] Make emoji completion work for GtkText GtkEntry will stop using it directly in the following commits. --- gtk/gtkemojicompletion.c | 66 +++++++++++++++++++++------------------- gtk/gtkemojicompletion.h | 4 +-- 2 files changed, 36 insertions(+), 34 deletions(-) diff --git a/gtk/gtkemojicompletion.c b/gtk/gtkemojicompletion.c index b3c62278e8..3c8796f920 100644 --- a/gtk/gtkemojicompletion.c +++ b/gtk/gtkemojicompletion.c @@ -19,7 +19,8 @@ #include "gtkemojicompletion.h" -#include "gtkentryprivate.h" +#include "gtktextprivate.h" +#include "gtkeditable.h" #include "gtkbox.h" #include "gtkcssprovider.h" #include "gtklistbox.h" @@ -28,6 +29,7 @@ #include "gtkintl.h" #include "gtkprivate.h" #include "gtkgesturelongpress.h" +#include "gtkeventcontrollerkey.h" #include "gtkflowbox.h" #include "gtkstack.h" @@ -35,7 +37,7 @@ struct _GtkEmojiCompletion { GtkPopover parent_instance; - GtkEntry *entry; + GtkText *entry; char *text; guint length; guint offset; @@ -54,7 +56,7 @@ struct _GtkEmojiCompletionClass { }; static void connect_signals (GtkEmojiCompletion *completion, - GtkEntry *entry); + GtkText *text); static void disconnect_signals (GtkEmojiCompletion *completion); static int populate_completion (GtkEmojiCompletion *completion, const char *text, @@ -86,7 +88,7 @@ update_completion (GtkEmojiCompletion *completion) n_matches = 0; - text = gtk_entry_get_text (GTK_ENTRY (completion->entry)); + text = gtk_editable_get_text (GTK_EDITABLE (completion->entry)); length = strlen (text); if (length > 0) @@ -125,7 +127,8 @@ next: } static void -entry_changed (GtkEntry *entry, GtkEmojiCompletion *completion) +changed_cb (GtkText *text, + GtkEmojiCompletion *completion) { update_completion (completion); } @@ -143,9 +146,9 @@ emoji_activated (GtkWidget *row, g_signal_handler_block (completion->entry, completion->changed_id); - length = g_utf8_strlen (gtk_entry_get_text (completion->entry), -1); - gtk_entry_set_positions (completion->entry, length - completion->length, length); - gtk_entry_enter_text (completion->entry, emoji); + length = g_utf8_strlen (gtk_editable_get_text (GTK_EDITABLE (completion->entry)), -1); + gtk_editable_select_region (GTK_EDITABLE (completion->entry), length - completion->length, length); + gtk_text_enter_text (completion->entry, emoji); g_signal_handler_unblock (completion->entry, completion->changed_id); } @@ -167,7 +170,6 @@ child_activated (GtkFlowBox *box, { GtkEmojiCompletion *completion = data; - g_print ("child activated\n"); emoji_activated (GTK_WIDGET (child), completion); } @@ -300,11 +302,11 @@ move_active_variation (GtkEmojiCompletion *completion, } static gboolean -entry_key_press (GtkEventControllerKey *key, - guint keyval, - guint keycode, - GdkModifierType modifiers, - GtkEmojiCompletion *completion) +key_press_cb (GtkEventControllerKey *key, + guint keyval, + guint keycode, + GdkModifierType modifiers, + GtkEmojiCompletion *completion) { if (!gtk_widget_get_visible (GTK_WIDGET (completion))) return FALSE; @@ -368,27 +370,27 @@ entry_key_press (GtkEventControllerKey *key, } static gboolean -entry_focus_out (GtkWidget *entry, - GParamSpec *pspec, - GtkEmojiCompletion *completion) +focus_out_cb (GtkWidget *text, + GParamSpec *pspec, + GtkEmojiCompletion *completion) { - if (!gtk_widget_has_focus (entry)) + if (!gtk_widget_has_focus (text)) gtk_popover_popdown (GTK_POPOVER (completion)); return FALSE; } static void connect_signals (GtkEmojiCompletion *completion, - GtkEntry *entry) + GtkText *entry) { GtkEventController *key_controller; completion->entry = g_object_ref (entry); - key_controller = gtk_entry_get_key_controller (entry); + key_controller = gtk_text_get_key_controller (entry); - completion->changed_id = g_signal_connect (entry, "changed", G_CALLBACK (entry_changed), completion); - g_signal_connect (key_controller, "key-pressed", G_CALLBACK (entry_key_press), completion); - g_signal_connect (entry, "notify::has-focus", G_CALLBACK (entry_focus_out), completion); + g_signal_connect (key_controller, "key-pressed", G_CALLBACK (key_press_cb), completion); + completion->changed_id = g_signal_connect (entry, "changed", G_CALLBACK (changed_cb), completion); + g_signal_connect (entry, "notify::has-focus", G_CALLBACK (focus_out_cb), completion); } static void @@ -396,11 +398,11 @@ disconnect_signals (GtkEmojiCompletion *completion) { GtkEventController *key_controller; - key_controller = gtk_entry_get_key_controller (completion->entry); + key_controller = gtk_text_get_key_controller (completion->entry); - g_signal_handlers_disconnect_by_func (completion->entry, entry_changed, completion); - g_signal_handlers_disconnect_by_func (key_controller, entry_key_press, completion); - g_signal_handlers_disconnect_by_func (completion->entry, entry_focus_out, completion); + g_signal_handlers_disconnect_by_func (completion->entry, changed_cb, completion); + g_signal_handlers_disconnect_by_func (key_controller, key_press_cb, completion); + g_signal_handlers_disconnect_by_func (completion->entry, focus_out_cb, completion); g_clear_object (&completion->entry); } @@ -554,8 +556,8 @@ add_emoji (GtkWidget *list, static int populate_completion (GtkEmojiCompletion *completion, - const char *text, - guint offset) + const char *text, + guint offset) { GList *children, *l; guint n_matches; @@ -657,15 +659,15 @@ gtk_emoji_completion_class_init (GtkEmojiCompletionClass *klass) } GtkWidget * -gtk_emoji_completion_new (GtkEntry *entry) +gtk_emoji_completion_new (GtkText *text) { GtkEmojiCompletion *completion; completion = GTK_EMOJI_COMPLETION (g_object_new (GTK_TYPE_EMOJI_COMPLETION, - "relative-to", entry, + "relative-to", text, NULL)); - connect_signals (completion, entry); + connect_signals (completion, text); return GTK_WIDGET (completion); } diff --git a/gtk/gtkemojicompletion.h b/gtk/gtkemojicompletion.h index ff7cb1fa63..620c1395a1 100644 --- a/gtk/gtkemojicompletion.h +++ b/gtk/gtkemojicompletion.h @@ -21,7 +21,7 @@ #error "Only can be included directly." #endif -#include +#include G_BEGIN_DECLS @@ -36,6 +36,6 @@ typedef struct _GtkEmojiCompletion GtkEmojiCompletion; typedef struct _GtkEmojiCompletionClass GtkEmojiCompletionClass; GType gtk_emoji_completion_get_type (void) G_GNUC_CONST; -GtkWidget *gtk_emoji_completion_new (GtkEntry *entry); +GtkWidget *gtk_emoji_completion_new (GtkText *text); G_END_DECLS From 936181f3542c43da7c70e8d8ed880ddfc07e9d2f Mon Sep 17 00:00:00 2001 From: Matthias Clasen Date: Fri, 15 Feb 2019 21:30:36 -0500 Subject: [PATCH 06/34] Add a GtkTextAccessible Add an accessible implementation for GtkText. --- gtk/a11y/gtktextaccessible.c | 1030 ++++++++++++++++++++++++++++++++++ gtk/a11y/gtktextaccessible.h | 57 ++ gtk/a11y/meson.build | 2 + gtk/gtktext.c | 15 +- 4 files changed, 1091 insertions(+), 13 deletions(-) create mode 100644 gtk/a11y/gtktextaccessible.c create mode 100644 gtk/a11y/gtktextaccessible.h diff --git a/gtk/a11y/gtktextaccessible.c b/gtk/a11y/gtktextaccessible.c new file mode 100644 index 0000000000..6a0bd453b1 --- /dev/null +++ b/gtk/a11y/gtktextaccessible.c @@ -0,0 +1,1030 @@ +/* GTK+ - accessibility implementations + * Copyright 2001, 2002, 2003 Sun Microsystems Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + */ + +#include "config.h" + +#define GDK_COMPILATION +#include "gdk/gdkeventsprivate.h" + +#include +#include +#include +#include "gtkpango.h" +#include "gtktextaccessible.h" +#include "gtktextprivate.h" +#include "gtkcomboboxaccessible.h" +#include "gtkstylecontextprivate.h" +#include "gtkwidgetprivate.h" + +struct _GtkTextAccessiblePrivate +{ + gint cursor_position; + gint selection_bound; +}; + +/* Callbacks */ + +static void insert_text_cb (GtkEditable *editable, + gchar *new_text, + gint new_text_length, + gint *position); +static void delete_text_cb (GtkEditable *editable, + gint start, + gint end); + +static gboolean check_for_selection_change (GtkTextAccessible *entry, + GtkText *gtk_text); + + +static void atk_editable_text_interface_init (AtkEditableTextIface *iface); +static void atk_text_interface_init (AtkTextIface *iface); +static void atk_action_interface_init (AtkActionIface *iface); + + +G_DEFINE_TYPE_WITH_CODE (GtkTextAccessible, gtk_text_accessible, GTK_TYPE_WIDGET_ACCESSIBLE, + G_ADD_PRIVATE (GtkTextAccessible) + G_IMPLEMENT_INTERFACE (ATK_TYPE_EDITABLE_TEXT, atk_editable_text_interface_init) + G_IMPLEMENT_INTERFACE (ATK_TYPE_TEXT, atk_text_interface_init) + G_IMPLEMENT_INTERFACE (ATK_TYPE_ACTION, atk_action_interface_init)) + + +static AtkStateSet * +gtk_text_accessible_ref_state_set (AtkObject *accessible) +{ + AtkStateSet *state_set; + gboolean value; + GtkWidget *widget; + + widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (accessible)); + if (widget == NULL) + return NULL; + + state_set = ATK_OBJECT_CLASS (gtk_text_accessible_parent_class)->ref_state_set (accessible); + + g_object_get (G_OBJECT (widget), "editable", &value, NULL); + if (value) + atk_state_set_add_state (state_set, ATK_STATE_EDITABLE); + atk_state_set_add_state (state_set, ATK_STATE_SINGLE_LINE); + + return state_set; +} + +static AtkAttributeSet * +gtk_text_accessible_get_attributes (AtkObject *accessible) +{ + GtkWidget *widget; + AtkAttributeSet *attributes; + AtkAttribute *placeholder_text; + const gchar *text; + + attributes = ATK_OBJECT_CLASS (gtk_text_accessible_parent_class)->get_attributes (accessible); + + widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (accessible)); + if (widget == NULL) + return attributes; + + text = gtk_text_get_placeholder_text (GTK_TEXT (widget)); + if (text == NULL) + return attributes; + + placeholder_text = g_malloc (sizeof (AtkAttribute)); + placeholder_text->name = g_strdup ("placeholder-text"); + placeholder_text->value = g_strdup (text); + + attributes = g_slist_append (attributes, placeholder_text); + + return attributes; +} + +static void +gtk_text_accessible_initialize (AtkObject *obj, + gpointer data) +{ + GtkText *entry; + GtkTextAccessible *gtk_text_accessible; + gint start_pos, end_pos; + + ATK_OBJECT_CLASS (gtk_text_accessible_parent_class)->initialize (obj, data); + + gtk_text_accessible = GTK_TEXT_ACCESSIBLE (obj); + + entry = GTK_TEXT (data); + gtk_editable_get_selection_bounds (GTK_EDITABLE (entry), &start_pos, &end_pos); + gtk_text_accessible->priv->cursor_position = end_pos; + gtk_text_accessible->priv->selection_bound = start_pos; + + /* Set up signal callbacks */ + g_signal_connect_after (entry, "insert-text", G_CALLBACK (insert_text_cb), NULL); + g_signal_connect (entry, "delete-text", G_CALLBACK (delete_text_cb), NULL); + + if (gtk_text_get_visibility (entry)) + obj->role = ATK_ROLE_TEXT; + else + obj->role = ATK_ROLE_PASSWORD_TEXT; +} + +static void +gtk_text_accessible_notify_gtk (GObject *obj, + GParamSpec *pspec) +{ + GtkWidget *widget; + AtkObject* atk_obj; + GtkText* gtk_text; + GtkTextAccessible* entry; + + widget = GTK_WIDGET (obj); + atk_obj = gtk_widget_get_accessible (widget); + gtk_text = GTK_TEXT (widget); + entry = GTK_TEXT_ACCESSIBLE (atk_obj); + + if (g_strcmp0 (pspec->name, "cursor-position") == 0) + { + if (check_for_selection_change (entry, gtk_text)) + g_signal_emit_by_name (atk_obj, "text-selection-changed"); + /* + * The entry cursor position has moved so generate the signal. + */ + g_signal_emit_by_name (atk_obj, "text-caret-moved", + entry->priv->cursor_position); + } + else if (g_strcmp0 (pspec->name, "selection-bound") == 0) + { + if (check_for_selection_change (entry, gtk_text)) + g_signal_emit_by_name (atk_obj, "text-selection-changed"); + } + else if (g_strcmp0 (pspec->name, "editable") == 0) + { + gboolean value; + + g_object_get (obj, "editable", &value, NULL); + atk_object_notify_state_change (atk_obj, ATK_STATE_EDITABLE, value); + } + else if (g_strcmp0 (pspec->name, "visibility") == 0) + { + gboolean visibility; + AtkRole new_role; + + visibility = gtk_text_get_visibility (gtk_text); + new_role = visibility ? ATK_ROLE_TEXT : ATK_ROLE_PASSWORD_TEXT; + atk_object_set_role (atk_obj, new_role); + } + else + GTK_WIDGET_ACCESSIBLE_CLASS (gtk_text_accessible_parent_class)->notify_gtk (obj, pspec); +} + +static gint +gtk_text_accessible_get_index_in_parent (AtkObject *accessible) +{ + /* + * If the parent widget is a combo box then the index is 1 + * otherwise do the normal thing. + */ + if (accessible->accessible_parent) + if (GTK_IS_COMBO_BOX_ACCESSIBLE (accessible->accessible_parent)) + return 1; + + return ATK_OBJECT_CLASS (gtk_text_accessible_parent_class)->get_index_in_parent (accessible); +} + +static void +gtk_text_accessible_class_init (GtkTextAccessibleClass *klass) +{ + AtkObjectClass *class = ATK_OBJECT_CLASS (klass); + GtkWidgetAccessibleClass *widget_class = (GtkWidgetAccessibleClass*)klass; + + class->ref_state_set = gtk_text_accessible_ref_state_set; + class->get_index_in_parent = gtk_text_accessible_get_index_in_parent; + class->initialize = gtk_text_accessible_initialize; + class->get_attributes = gtk_text_accessible_get_attributes; + + widget_class->notify_gtk = gtk_text_accessible_notify_gtk; +} + +static void +gtk_text_accessible_init (GtkTextAccessible *entry) +{ + entry->priv = gtk_text_accessible_get_instance_private (entry); + entry->priv->cursor_position = 0; + entry->priv->selection_bound = 0; +} + +static gchar * +gtk_text_accessible_get_text (AtkText *atk_text, + gint start_pos, + gint end_pos) +{ + GtkWidget *widget; + + widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (atk_text)); + if (widget == NULL) + return NULL; + + return gtk_text_get_display_text (GTK_TEXT (widget), start_pos, end_pos); +} + +static gchar * +gtk_text_accessible_get_text_before_offset (AtkText *text, + gint offset, + AtkTextBoundary boundary_type, + gint *start_offset, + gint *end_offset) +{ + GtkWidget *widget; + + widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (text)); + if (widget == NULL) + return NULL; + + return _gtk_pango_get_text_before (gtk_text_get_layout (GTK_TEXT (widget)), + boundary_type, offset, + start_offset, end_offset); +} + +static gchar * +gtk_text_accessible_get_text_at_offset (AtkText *text, + gint offset, + AtkTextBoundary boundary_type, + gint *start_offset, + gint *end_offset) +{ + GtkWidget *widget; + + widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (text)); + if (widget == NULL) + return NULL; + + return _gtk_pango_get_text_at (gtk_text_get_layout (GTK_TEXT (widget)), + boundary_type, offset, + start_offset, end_offset); +} + +static gchar * +gtk_text_accessible_get_text_after_offset (AtkText *text, + gint offset, + AtkTextBoundary boundary_type, + gint *start_offset, + gint *end_offset) +{ + GtkWidget *widget; + + widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (text)); + if (widget == NULL) + return NULL; + + return _gtk_pango_get_text_after (gtk_text_get_layout (GTK_TEXT (widget)), + boundary_type, offset, + start_offset, end_offset); +} + +static gint +gtk_text_accessible_get_character_count (AtkText *atk_text) +{ + GtkWidget *widget; + gchar *text; + glong char_count; + + widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (atk_text)); + if (widget == NULL) + return 0; + + text = gtk_text_get_display_text (GTK_TEXT (widget), 0, -1); + + char_count = 0; + if (text) + { + char_count = g_utf8_strlen (text, -1); + g_free (text); + } + + return char_count; +} + +static gint +gtk_text_accessible_get_caret_offset (AtkText *text) +{ + GtkWidget *widget; + + widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (text)); + if (widget == NULL) + return 0; + + return gtk_editable_get_position (GTK_EDITABLE (widget)); +} + +static gboolean +gtk_text_accessible_set_caret_offset (AtkText *text, + gint offset) +{ + GtkWidget *widget; + + widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (text)); + if (widget == NULL) + return FALSE; + + gtk_editable_set_position (GTK_EDITABLE (widget), offset); + + return TRUE; +} + +static AtkAttributeSet * +add_text_attribute (AtkAttributeSet *attributes, + AtkTextAttribute attr, + gint i) +{ + AtkAttribute *at; + + at = g_new (AtkAttribute, 1); + at->name = g_strdup (atk_text_attribute_get_name (attr)); + at->value = g_strdup (atk_text_attribute_get_value (attr, i)); + + return g_slist_prepend (attributes, at); +} + +static AtkAttributeSet * +gtk_text_accessible_get_run_attributes (AtkText *text, + gint offset, + gint *start_offset, + gint *end_offset) +{ + GtkWidget *widget; + AtkAttributeSet *attributes; + + widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (text)); + if (widget == NULL) + return NULL; + + attributes = NULL; + attributes = add_text_attribute (attributes, ATK_TEXT_ATTR_DIRECTION, + gtk_widget_get_direction (widget)); + attributes = _gtk_pango_get_run_attributes (attributes, + gtk_text_get_layout (GTK_TEXT (widget)), + offset, + start_offset, + end_offset); + + return attributes; +} + +static AtkAttributeSet * +gtk_text_accessible_get_default_attributes (AtkText *text) +{ + GtkWidget *widget; + AtkAttributeSet *attributes; + + widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (text)); + if (widget == NULL) + return NULL; + + attributes = NULL; + attributes = add_text_attribute (attributes, ATK_TEXT_ATTR_DIRECTION, + gtk_widget_get_direction (widget)); + attributes = _gtk_pango_get_default_attributes (attributes, + gtk_text_get_layout (GTK_TEXT (widget))); + attributes = _gtk_style_context_get_attributes (attributes, + gtk_widget_get_style_context (widget)); + + return attributes; +} + +static void +gtk_text_accessible_get_character_extents (AtkText *text, + gint offset, + gint *x, + gint *y, + gint *width, + gint *height, + AtkCoordType coords) +{ + GtkWidget *widget; + GtkText *entry; + PangoRectangle char_rect; + gchar *entry_text; + gint index, x_layout, y_layout; + GdkSurface *surface; + gint x_surface, y_surface; + GtkAllocation allocation; + + widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (text)); + if (widget == NULL) + return; + + entry = GTK_TEXT (widget); + + gtk_text_get_layout_offsets (entry, &x_layout, &y_layout); + entry_text = gtk_text_get_display_text (entry, 0, -1); + index = g_utf8_offset_to_pointer (entry_text, offset) - entry_text; + g_free (entry_text); + + pango_layout_index_to_pos (gtk_text_get_layout (entry), index, &char_rect); + pango_extents_to_pixels (&char_rect, NULL); + + gtk_widget_get_allocation (widget, &allocation); + + surface = gtk_widget_get_surface (widget); + gdk_surface_get_origin (surface, &x_surface, &y_surface); + + *x = x_surface + allocation.x + x_layout + char_rect.x; + *y = y_surface + allocation.y + y_layout + char_rect.y; + *width = char_rect.width; + *height = char_rect.height; + + if (coords == ATK_XY_WINDOW) + { + surface = gdk_surface_get_toplevel (surface); + gdk_surface_get_origin (surface, &x_surface, &y_surface); + + *x -= x_surface; + *y -= y_surface; + } +} + +static gint +gtk_text_accessible_get_offset_at_point (AtkText *atk_text, + gint x, + gint y, + AtkCoordType coords) +{ + GtkWidget *widget; + GtkText *entry; + gchar *text; + gint index, x_layout, y_layout; + gint x_surface, y_surface; + gint x_local, y_local; + GdkSurface *surface; + glong offset; + + widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (atk_text)); + if (widget == NULL) + return -1; + + entry = GTK_TEXT (widget); + + gtk_text_get_layout_offsets (entry, &x_layout, &y_layout); + + surface = gtk_widget_get_surface (widget); + gdk_surface_get_origin (surface, &x_surface, &y_surface); + + x_local = x - x_layout - x_surface; + y_local = y - y_layout - y_surface; + + if (coords == ATK_XY_WINDOW) + { + surface = gdk_surface_get_toplevel (surface); + gdk_surface_get_origin (surface, &x_surface, &y_surface); + + x_local += x_surface; + y_local += y_surface; + } + if (!pango_layout_xy_to_index (gtk_text_get_layout (entry), + x_local * PANGO_SCALE, + y_local * PANGO_SCALE, + &index, NULL)) + { + if (x_local < 0 || y_local < 0) + index = 0; + else + index = -1; + } + + offset = -1; + if (index != -1) + { + text = gtk_text_get_display_text (entry, 0, -1); + offset = g_utf8_pointer_to_offset (text, text + index); + g_free (text); + } + + return offset; +} + +static gint +gtk_text_accessible_get_n_selections (AtkText *text) +{ + GtkWidget *widget; + gint start, end; + + widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (text)); + if (widget == NULL) + return 0; + + if (gtk_editable_get_selection_bounds (GTK_EDITABLE (widget), &start, &end)) + return 1; + + return 0; +} + +static gchar * +gtk_text_accessible_get_selection (AtkText *text, + gint selection_num, + gint *start_pos, + gint *end_pos) +{ + GtkWidget *widget; + + widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (text)); + if (widget == NULL) + return NULL; + + if (selection_num != 0) + return NULL; + + if (gtk_editable_get_selection_bounds (GTK_EDITABLE (widget), start_pos, end_pos)) + return gtk_editable_get_chars (GTK_EDITABLE (widget), *start_pos, *end_pos); + + return NULL; +} + +static gboolean +gtk_text_accessible_add_selection (AtkText *text, + gint start_pos, + gint end_pos) +{ + GtkText *entry; + GtkWidget *widget; + gint start, end; + + widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (text)); + if (widget == NULL) + return FALSE; + + entry = GTK_TEXT (widget); + + if (!gtk_editable_get_selection_bounds (GTK_EDITABLE (entry), &start, &end)) + { + gtk_editable_select_region (GTK_EDITABLE (entry), start_pos, end_pos); + return TRUE; + } + else + return FALSE; +} + +static gboolean +gtk_text_accessible_remove_selection (AtkText *text, + gint selection_num) +{ + GtkWidget *widget; + gint start, end; + + widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (text)); + if (widget == NULL) + return FALSE; + + if (selection_num != 0) + return FALSE; + + if (gtk_editable_get_selection_bounds (GTK_EDITABLE (widget), &start, &end)) + { + gtk_editable_select_region (GTK_EDITABLE (widget), end, end); + return TRUE; + } + else + return FALSE; +} + +static gboolean +gtk_text_accessible_set_selection (AtkText *text, + gint selection_num, + gint start_pos, + gint end_pos) +{ + GtkWidget *widget; + gint start, end; + + widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (text)); + if (widget == NULL) + return FALSE; + + if (selection_num != 0) + return FALSE; + + if (gtk_editable_get_selection_bounds (GTK_EDITABLE (widget), &start, &end)) + { + gtk_editable_select_region (GTK_EDITABLE (widget), start_pos, end_pos); + return TRUE; + } + else + return FALSE; +} + +static gunichar +gtk_text_accessible_get_character_at_offset (AtkText *atk_text, + gint offset) +{ + GtkWidget *widget; + gchar *text; + gchar *index; + gunichar result; + + result = '\0'; + + widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (atk_text)); + if (widget == NULL) + return result; + + if (!gtk_text_get_visibility (GTK_TEXT (widget))) + return result; + + text = gtk_text_get_display_text (GTK_TEXT (widget), 0, -1); + if (offset < g_utf8_strlen (text, -1)) + { + index = g_utf8_offset_to_pointer (text, offset); + result = g_utf8_get_char (index); + g_free (text); + } + + return result; +} + +static void +atk_text_interface_init (AtkTextIface *iface) +{ + iface->get_text = gtk_text_accessible_get_text; + iface->get_character_at_offset = gtk_text_accessible_get_character_at_offset; + iface->get_text_before_offset = gtk_text_accessible_get_text_before_offset; + iface->get_text_at_offset = gtk_text_accessible_get_text_at_offset; + iface->get_text_after_offset = gtk_text_accessible_get_text_after_offset; + iface->get_caret_offset = gtk_text_accessible_get_caret_offset; + iface->set_caret_offset = gtk_text_accessible_set_caret_offset; + iface->get_character_count = gtk_text_accessible_get_character_count; + iface->get_n_selections = gtk_text_accessible_get_n_selections; + iface->get_selection = gtk_text_accessible_get_selection; + iface->add_selection = gtk_text_accessible_add_selection; + iface->remove_selection = gtk_text_accessible_remove_selection; + iface->set_selection = gtk_text_accessible_set_selection; + iface->get_run_attributes = gtk_text_accessible_get_run_attributes; + iface->get_default_attributes = gtk_text_accessible_get_default_attributes; + iface->get_character_extents = gtk_text_accessible_get_character_extents; + iface->get_offset_at_point = gtk_text_accessible_get_offset_at_point; +} + +static void +gtk_text_accessible_set_text_contents (AtkEditableText *text, + const gchar *string) +{ + GtkWidget *widget; + + widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (text)); + if (widget == NULL) + return; + + if (!gtk_editable_get_editable (GTK_EDITABLE (widget))) + return; + + gtk_editable_set_text (GTK_EDITABLE (widget), string); +} + +static void +gtk_text_accessible_insert_text (AtkEditableText *text, + const gchar *string, + gint length, + gint *position) +{ + GtkWidget *widget; + GtkEditable *editable; + + widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (text)); + if (widget == NULL) + return; + + editable = GTK_EDITABLE (widget); + if (!gtk_editable_get_editable (editable)) + return; + + gtk_editable_insert_text (editable, string, length, position); + gtk_editable_set_position (editable, *position); +} + +static void +gtk_text_accessible_copy_text (AtkEditableText *text, + gint start_pos, + gint end_pos) +{ + GtkWidget *widget; + GtkEditable *editable; + gchar *str; + GdkClipboard *clipboard; + + widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (text)); + if (widget == NULL) + return; + + editable = GTK_EDITABLE (widget); + str = gtk_editable_get_chars (editable, start_pos, end_pos); + clipboard = gtk_widget_get_clipboard (widget); + gdk_clipboard_set_text (clipboard, str); + g_free (str); +} + +static void +gtk_text_accessible_cut_text (AtkEditableText *text, + gint start_pos, + gint end_pos) +{ + GtkWidget *widget; + GtkEditable *editable; + gchar *str; + GdkClipboard *clipboard; + + widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (text)); + if (widget == NULL) + return; + + editable = GTK_EDITABLE (widget); + if (!gtk_editable_get_editable (editable)) + return; + + str = gtk_editable_get_chars (editable, start_pos, end_pos); + clipboard = gtk_widget_get_clipboard (widget); + gdk_clipboard_set_text (clipboard, str); + gtk_editable_delete_text (editable, start_pos, end_pos); +} + +static void +gtk_text_accessible_delete_text (AtkEditableText *text, + gint start_pos, + gint end_pos) +{ + GtkWidget *widget; + GtkEditable *editable; + + widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (text)); + if (widget == NULL) + return; + + editable = GTK_EDITABLE (widget); + if (!gtk_editable_get_editable (editable)) + return; + + gtk_editable_delete_text (editable, start_pos, end_pos); +} + +typedef struct +{ + GtkText* entry; + gint position; +} PasteData; + +static void +paste_received_cb (GObject *clipboard, + GAsyncResult *result, + gpointer data) +{ + PasteData *paste = data; + char *text; + + text = gdk_clipboard_read_text_finish (GDK_CLIPBOARD (clipboard), result, NULL); + if (text) + gtk_editable_insert_text (GTK_EDITABLE (paste->entry), text, -1, + &paste->position); + + g_object_unref (paste->entry); + g_free (paste); + g_free (text); +} + +static void +gtk_text_accessible_paste_text (AtkEditableText *text, + gint position) +{ + GtkWidget *widget; + GtkEditable *editable; + PasteData *paste; + GdkClipboard *clipboard; + + widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (text)); + if (widget == NULL) + return; + + editable = GTK_EDITABLE (widget); + if (!gtk_editable_get_editable (editable)) + return; + + paste = g_new0 (PasteData, 1); + paste->entry = GTK_TEXT (widget); + paste->position = position; + + g_object_ref (paste->entry); + clipboard = gtk_widget_get_clipboard (widget); + gdk_clipboard_read_text_async (clipboard, NULL, paste_received_cb, paste); +} + +static void +atk_editable_text_interface_init (AtkEditableTextIface *iface) +{ + iface->set_text_contents = gtk_text_accessible_set_text_contents; + iface->insert_text = gtk_text_accessible_insert_text; + iface->copy_text = gtk_text_accessible_copy_text; + iface->cut_text = gtk_text_accessible_cut_text; + iface->delete_text = gtk_text_accessible_delete_text; + iface->paste_text = gtk_text_accessible_paste_text; + iface->set_run_attributes = NULL; +} + +static void +insert_text_cb (GtkEditable *editable, + gchar *new_text, + gint new_text_length, + gint *position) +{ + GtkTextAccessible *accessible; + gint length; + + if (new_text_length == 0) + return; + + accessible = GTK_TEXT_ACCESSIBLE (gtk_widget_get_accessible (GTK_WIDGET (editable))); + length = g_utf8_strlen (new_text, new_text_length); + + g_signal_emit_by_name (accessible, + "text-changed::insert", + *position - length, + length); +} + +/* We connect to GtkEditable::delete-text, since it carries + * the information we need. But we delay emitting our own + * text_changed::delete signal until the entry has update + * all its internal state and emits GtkText::changed. + */ +static void +delete_text_cb (GtkEditable *editable, + gint start, + gint end) +{ + GtkTextAccessible *accessible; + + accessible = GTK_TEXT_ACCESSIBLE (gtk_widget_get_accessible (GTK_WIDGET (editable))); + + if (end < 0) + { + gchar *text; + + text = gtk_text_get_display_text (GTK_TEXT (editable), 0, -1); + end = g_utf8_strlen (text, -1); + g_free (text); + } + + if (end == start) + return; + + g_signal_emit_by_name (accessible, + "text-changed::delete", + start, + end - start); +} + +static gboolean +check_for_selection_change (GtkTextAccessible *accessible, + GtkText *entry) +{ + gboolean ret_val = FALSE; + gint start, end; + + if (gtk_editable_get_selection_bounds (GTK_EDITABLE (entry), &start, &end)) + { + if (end != accessible->priv->cursor_position || + start != accessible->priv->selection_bound) + /* + * This check is here as this function can be called + * for notification of selection_bound and current_pos. + * The values of current_pos and selection_bound may be the same + * for both notifications and we only want to generate one + * text_selection_changed signal. + */ + ret_val = TRUE; + } + else + { + /* We had a selection */ + ret_val = (accessible->priv->cursor_position != accessible->priv->selection_bound); + } + + accessible->priv->cursor_position = end; + accessible->priv->selection_bound = start; + + return ret_val; +} + +static gboolean +gtk_text_accessible_do_action (AtkAction *action, + gint i) +{ + GtkWidget *widget; + + widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (action)); + if (widget == NULL) + return FALSE; + + if (!gtk_widget_get_sensitive (widget) || !gtk_widget_get_visible (widget)) + return FALSE; + + if (i != 0) + return FALSE; + + gtk_widget_activate (widget); + + return TRUE; +} + +static gint +gtk_text_accessible_get_n_actions (AtkAction *action) +{ + return 1; +} + +static const gchar * +gtk_text_accessible_get_keybinding (AtkAction *action, + gint i) +{ + GtkWidget *widget; + GtkWidget *label; + AtkRelationSet *set; + AtkRelation *relation; + GPtrArray *target; + gpointer target_object; + guint key_val; + + if (i != 0) + return NULL; + + widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (action)); + if (widget == NULL) + return NULL; + + set = atk_object_ref_relation_set (ATK_OBJECT (action)); + if (!set) + return NULL; + + label = NULL; + relation = atk_relation_set_get_relation_by_type (set, ATK_RELATION_LABELLED_BY); + if (relation) + { + target = atk_relation_get_target (relation); + + target_object = g_ptr_array_index (target, 0); + label = gtk_accessible_get_widget (GTK_ACCESSIBLE (target_object)); + } + + g_object_unref (set); + + if (GTK_IS_LABEL (label)) + { + key_val = gtk_label_get_mnemonic_keyval (GTK_LABEL (label)); + if (key_val != GDK_KEY_VoidSymbol) + return gtk_accelerator_name (key_val, GDK_MOD1_MASK); + } + + return NULL; +} + +static const gchar* +gtk_text_accessible_action_get_name (AtkAction *action, + gint i) +{ + if (i == 0) + return "activate"; + return NULL; +} + +static const gchar* +gtk_text_accessible_action_get_localized_name (AtkAction *action, + gint i) +{ + if (i == 0) + return C_("Action name", "Activate"); + return NULL; +} + +static const gchar* +gtk_text_accessible_action_get_description (AtkAction *action, + gint i) +{ + if (i == 0) + return C_("Action description", "Activates the entry"); + return NULL; +} + +static void +atk_action_interface_init (AtkActionIface *iface) +{ + iface->do_action = gtk_text_accessible_do_action; + iface->get_n_actions = gtk_text_accessible_get_n_actions; + iface->get_keybinding = gtk_text_accessible_get_keybinding; + iface->get_name = gtk_text_accessible_action_get_name; + iface->get_localized_name = gtk_text_accessible_action_get_localized_name; + iface->get_description = gtk_text_accessible_action_get_description; +} diff --git a/gtk/a11y/gtktextaccessible.h b/gtk/a11y/gtktextaccessible.h new file mode 100644 index 0000000000..e47be4236d --- /dev/null +++ b/gtk/a11y/gtktextaccessible.h @@ -0,0 +1,57 @@ +/* GTK+ - accessibility implementations + * Copyright 2001 Sun Microsystems Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * 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, see . + */ + +#ifndef __GTK_TEXT_ACCESSIBLE_H__ +#define __GTK_TEXT_ACCESSIBLE_H__ + +#if !defined (__GTK_A11Y_H_INSIDE__) && !defined (GTK_COMPILATION) +#error "Only can be included directly." +#endif + +#include + +G_BEGIN_DECLS + +#define GTK_TYPE_TEXT_ACCESSIBLE (gtk_text_accessible_get_type ()) +#define GTK_TEXT_ACCESSIBLE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTK_TYPE_TEXT_ACCESSIBLE, GtkTextAccessible)) +#define GTK_TEXT_ACCESSIBLE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GTK_TYPE_TEXT_ACCESSIBLE, GtkTextAccessibleClass)) +#define GTK_IS_TEXT_ACCESSIBLE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTK_TYPE_TEXT_ACCESSIBLE)) +#define GTK_IS_TEXT_ACCESSIBLE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GTK_TYPE_TEXT_ACCESSIBLE)) +#define GTK_TEXT_ACCESSIBLE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GTK_TYPE_TEXT_ACCESSIBLE, GtkTextAccessibleClass)) + +typedef struct _GtkTextAccessible GtkTextAccessible; +typedef struct _GtkTextAccessibleClass GtkTextAccessibleClass; +typedef struct _GtkTextAccessiblePrivate GtkTextAccessiblePrivate; + +struct _GtkTextAccessible +{ + GtkWidgetAccessible parent; + + GtkTextAccessiblePrivate *priv; +}; + +struct _GtkTextAccessibleClass +{ + GtkWidgetAccessibleClass parent_class; +}; + +GDK_AVAILABLE_IN_ALL +GType gtk_text_accessible_get_type (void); + +G_END_DECLS + +#endif /* __GTK_TEXT_ACCESSIBLE_H__ */ diff --git a/gtk/a11y/meson.build b/gtk/a11y/meson.build index e1a1f86be9..4ea944d0fd 100644 --- a/gtk/a11y/meson.build +++ b/gtk/a11y/meson.build @@ -46,6 +46,7 @@ a11y_sources = files([ 'gtkstackaccessible.c', 'gtkstatusbaraccessible.c', 'gtkswitchaccessible.c', + 'gtktextaccessible.c', 'gtktextcellaccessible.c', 'gtktextviewaccessible.c', 'gtktogglebuttonaccessible.c', @@ -100,6 +101,7 @@ a11y_headers = files([ 'gtkstackaccessible.h', 'gtkstatusbaraccessible.h', 'gtkswitchaccessible.h', + 'gtktextaccessible.h', 'gtktextcellaccessible.h', 'gtktextviewaccessible.h', 'gtktogglebuttonaccessible.h', diff --git a/gtk/gtktext.c b/gtk/gtktext.c index 8409d69e4c..0d54525daa 100644 --- a/gtk/gtktext.c +++ b/gtk/gtktext.c @@ -67,7 +67,7 @@ #include "gtkwidgetprivate.h" #include "gtkwindow.h" -#include "a11y/gtkentryaccessible.h" +#include "a11y/gtktextaccessible.h" #include #include @@ -228,17 +228,6 @@ struct _GtkTextPrivate guint populate_all : 1; }; -struct _EntryIconInfo -{ - GtkWidget *widget; - gchar *tooltip; - guint nonactivatable : 1; - guint in_drag : 1; - - GdkDragAction actions; - GdkContentFormats *target_list; -}; - struct _GtkTextPasswordHint { int position; /* Position (in text) of the last password hint */ @@ -1370,7 +1359,7 @@ gtk_text_class_init (GtkTextClass *class) gtk_binding_entry_add_signal (binding_set, GDK_KEY_semicolon, GDK_CONTROL_MASK, "insert-emoji", 0); - gtk_widget_class_set_accessible_type (widget_class, GTK_TYPE_ENTRY_ACCESSIBLE); + gtk_widget_class_set_accessible_type (widget_class, GTK_TYPE_TEXT_ACCESSIBLE); gtk_widget_class_set_css_name (widget_class, I_("text")); } From 45fb1d06e3b1c9b578301612eca8d5c54027f0f3 Mon Sep 17 00:00:00 2001 From: Matthias Clasen Date: Sat, 16 Feb 2019 00:50:40 -0500 Subject: [PATCH 07/34] entry: Delegate to GtkText Use a GtkText child, and delegate the editable functionality to it. Also forward all the properties that are provided by GtkText. Some of the more internal APIs, such as layout and im context access and caps-lock warning, are removed here, but we preserve most of the plain GtkEntry API by forwarding it to the GtkText child. --- docs/reference/gtk/gtk4-sections.txt | 5 - gtk/gtkentry.c | 6199 +------------------------- gtk/gtkentry.h | 35 - gtk/gtkentryprivate.h | 8 +- 4 files changed, 165 insertions(+), 6082 deletions(-) diff --git a/docs/reference/gtk/gtk4-sections.txt b/docs/reference/gtk/gtk4-sections.txt index aa019ef40f..f1f7877fa4 100644 --- a/docs/reference/gtk/gtk4-sections.txt +++ b/docs/reference/gtk/gtk4-sections.txt @@ -942,10 +942,6 @@ gtk_entry_set_placeholder_text gtk_entry_get_placeholder_text gtk_entry_set_overwrite_mode gtk_entry_get_overwrite_mode -gtk_entry_get_layout -gtk_entry_get_layout_offsets -gtk_entry_layout_index_to_text_index -gtk_entry_text_index_to_layout_index gtk_entry_set_attributes gtk_entry_get_attributes gtk_entry_set_completion @@ -955,7 +951,6 @@ gtk_entry_get_progress_fraction gtk_entry_set_progress_pulse_step gtk_entry_get_progress_pulse_step gtk_entry_progress_pulse -gtk_entry_im_context_filter_keypress gtk_entry_reset_im_context gtk_entry_set_tabs gtk_entry_get_tabs diff --git a/gtk/gtkentry.c b/gtk/gtkentry.c index 2050cfcdff..035b81d828 100644 --- a/gtk/gtkentry.c +++ b/gtk/gtkentry.c @@ -40,19 +40,16 @@ #include "gtkdebug.h" #include "gtkdnd.h" #include "gtkdndprivate.h" +#include "gtkeditable.h" #include "gtkemojichooser.h" #include "gtkemojicompletion.h" #include "gtkentrybuffer.h" -#include "gtkeventcontrollerkey.h" #include "gtkgesturedrag.h" #include "gtkgesturemultipress.h" -#include "gtkgesturesingle.h" #include "gtkimageprivate.h" #include "gtkimcontextsimple.h" -#include "gtkimmulticontext.h" #include "gtkintl.h" #include "gtklabel.h" -#include "gtkmagnifierprivate.h" #include "gtkmain.h" #include "gtkmarshalers.h" #include "gtkmenu.h" @@ -66,6 +63,7 @@ #include "gtksettings.h" #include "gtksnapshot.h" #include "gtkstylecontextprivate.h" +#include "gtktextprivate.h" #include "gtktexthandleprivate.h" #include "gtktextutil.h" #include "gtktooltip.h" @@ -99,10 +97,7 @@ * In this mode, entered text is displayed using a “invisible” character. * By default, GTK+ picks the best invisible character that is available * in the current font, but it can be changed with - * gtk_entry_set_invisible_char(). GTK+ displays a warning - * when Caps Lock or input methods might interfere with entering text in - * a password entry. The warning can be turned off with the - * #GtkEntry:caps-lock-warning property. + * gtk_entry_set_invisible_char(). * * GtkEntry has the ability to display progress or activity * information behind the text. To make an entry display such information, @@ -127,12 +122,11 @@ * # CSS nodes * * |[ - * entry[.read-only][.flat][.warning][.error] + * entry[.flat][.warning][.error] + * ├── text[.readonly] * ├── placeholder * ├── image.left * ├── image.right - * ├── undershoot.left - * ├── undershoot.right * ├── [selection] * ├── [block-cursor] * ├── [progress[.pulse]] @@ -177,105 +171,19 @@ ((pos) == GTK_ENTRY_ICON_PRIMARY || \ (pos) == GTK_ENTRY_ICON_SECONDARY) -static GQuark quark_password_hint = 0; -static GQuark quark_capslock_feedback = 0; -static GQuark quark_gtk_signal = 0; static GQuark quark_entry_completion = 0; typedef struct _EntryIconInfo EntryIconInfo; -typedef struct _GtkEntryPasswordHint GtkEntryPasswordHint; struct _GtkEntryPrivate { - EntryIconInfo *icons[MAX_ICONS]; - - GtkEntryBuffer *buffer; - GtkIMContext *im_context; - GtkWidget *popup_menu; - - int text_baseline; - - PangoLayout *cached_layout; - PangoAttrList *attrs; - PangoTabArray *tabs; - - GdkContentProvider *selection_content; - - gchar *im_module; - - GtkTextHandle *text_handle; - GtkWidget *selection_bubble; - guint selection_bubble_timeout_id; - - GtkWidget *magnifier_popover; - GtkWidget *magnifier; - - GtkWidget *placeholder; - - GtkGesture *drag_gesture; - GtkEventController *key_controller; + EntryIconInfo *icons[MAX_ICONS]; + GtkWidget *text; GtkWidget *progress_widget; - GtkCssNode *selection_node; - GtkCssNode *block_cursor_node; - GtkCssNode *undershoot_node[2]; - int text_x; - int text_width; - - gfloat xalign; - - gint ascent; /* font ascent in pango units */ - gint current_pos; - gint descent; /* font descent in pango units */ - gint dnd_position; /* In chars, -1 == no DND cursor */ - gint drag_start_x; - gint drag_start_y; - gint drop_position; /* where the drop should happen */ - gint insert_pos; - gint selection_bound; - gint scroll_offset; - gint start_x; - gint start_y; - gint width_chars; - gint max_width_chars; - - gunichar invisible_char; - - guint blink_time; /* time in msec the cursor has blinked since last user event */ - guint blink_timeout; - - guint16 preedit_length; /* length of preedit string, in bytes */ - guint16 preedit_cursor; /* offset of cursor within preedit string, in chars */ - - gint64 handle_place_time; - - guint editable : 1; guint show_emoji_icon : 1; - guint enable_emoji_completion : 1; - guint in_drag : 1; - guint overwrite_mode : 1; - guint visible : 1; - - guint activates_default : 1; - guint cache_includes_preedit : 1; - guint caps_lock_warning : 1; - guint caps_lock_warning_shown : 1; - guint change_count : 8; - guint cursor_visible : 1; guint editing_canceled : 1; /* Only used by GtkCellRendererText */ - guint in_click : 1; /* Flag so we don't select all when clicking in entry to focus in */ - guint invisible_char_set : 1; - guint mouse_cursor_obscured : 1; - guint need_im_reset : 1; - guint real_changed : 1; - guint resolved_dir : 4; /* PangoDirection */ - guint select_words : 1; - guint select_lines : 1; - guint truncate_multiline : 1; - guint cursor_handle_dragged : 1; - guint selection_handle_dragged : 1; - guint populate_all : 1; }; struct _EntryIconInfo @@ -289,51 +197,26 @@ struct _EntryIconInfo GdkContentFormats *target_list; }; -struct _GtkEntryPasswordHint -{ - gint position; /* Position (in text) of the last password hint */ - guint source_id; /* Timeout source id */ -}; - enum { ACTIVATE, - POPULATE_POPUP, - MOVE_CURSOR, - INSERT_AT_CURSOR, - DELETE_FROM_CURSOR, - BACKSPACE, - CUT_CLIPBOARD, - COPY_CLIPBOARD, - PASTE_CLIPBOARD, - TOGGLE_OVERWRITE, ICON_PRESS, ICON_RELEASE, - PREEDIT_CHANGED, - INSERT_EMOJI, LAST_SIGNAL }; enum { PROP_0, PROP_BUFFER, - PROP_CURSOR_POSITION, - PROP_SELECTION_BOUND, - PROP_EDITABLE, PROP_MAX_LENGTH, PROP_VISIBILITY, PROP_HAS_FRAME, PROP_INVISIBLE_CHAR, PROP_ACTIVATES_DEFAULT, - PROP_WIDTH_CHARS, - PROP_MAX_WIDTH_CHARS, PROP_SCROLL_OFFSET, - PROP_TEXT, - PROP_XALIGN, PROP_TRUNCATE_MULTILINE, PROP_OVERWRITE_MODE, PROP_TEXT_LENGTH, PROP_INVISIBLE_CHAR_SET, - PROP_CAPS_LOCK_WARNING, PROP_PROGRESS_FRACTION, PROP_PROGRESS_PULSE_STEP, PROP_PAINTABLE_PRIMARY, @@ -363,7 +246,7 @@ enum { PROP_SHOW_EMOJI_ICON, PROP_ENABLE_EMOJI_COMPLETION, PROP_EDITING_CANCELED, - NUM_PROPERTIES = PROP_EDITING_CANCELED + NUM_PROPERTIES = PROP_EDITING_CANCELED, }; static GParamSpec *entry_props[NUM_PROPERTIES] = { NULL, }; @@ -399,20 +282,12 @@ static void gtk_entry_dispose (GObject *object); /* GtkWidget methods */ -static void gtk_entry_destroy (GtkWidget *widget); -static void gtk_entry_realize (GtkWidget *widget); -static void gtk_entry_unrealize (GtkWidget *widget); -static void gtk_entry_unmap (GtkWidget *widget); static void gtk_entry_size_allocate (GtkWidget *widget, int width, int height, int baseline); static void gtk_entry_snapshot (GtkWidget *widget, GtkSnapshot *snapshot); -static void gtk_entry_focus_in (GtkWidget *widget); -static void gtk_entry_focus_out (GtkWidget *widget); -static void gtk_entry_grab_focus (GtkWidget *widget); -static void gtk_entry_style_updated (GtkWidget *widget); static gboolean gtk_entry_query_tooltip (GtkWidget *widget, gint x, gint y, @@ -420,57 +295,7 @@ static gboolean gtk_entry_query_tooltip (GtkWidget *widget, GtkTooltip *tooltip); static void gtk_entry_direction_changed (GtkWidget *widget, GtkTextDirection previous_dir); -static void gtk_entry_state_flags_changed (GtkWidget *widget, - GtkStateFlags previous_state); -static void gtk_entry_display_changed (GtkWidget *widget, - GdkDisplay *old_display); -static gboolean gtk_entry_drag_drop (GtkWidget *widget, - GdkDrop *drop, - gint x, - gint y); -static gboolean gtk_entry_drag_motion (GtkWidget *widget, - GdkDrop *drop, - gint x, - gint y); -static void gtk_entry_drag_leave (GtkWidget *widget, - GdkDrop *drop); -static void gtk_entry_drag_data_received (GtkWidget *widget, - GdkDrop *drop, - GtkSelectionData *selection_data); -static void gtk_entry_drag_data_get (GtkWidget *widget, - GdkDrag *drag, - GtkSelectionData *selection_data); -static void gtk_entry_drag_data_delete (GtkWidget *widget, - GdkDrag *drag); -static void gtk_entry_drag_begin (GtkWidget *widget, - GdkDrag *drag); -static void gtk_entry_drag_end (GtkWidget *widget, - GdkDrag *drag); - - -/* GtkEditable method implementations - */ -static void gtk_entry_insert_text (GtkEditable *editable, - const gchar *new_text, - gint new_text_length, - gint *position); -static void gtk_entry_delete_text (GtkEditable *editable, - gint start_pos, - gint end_pos); -static const char *gtk_entry_real_get_text (GtkEditable *editable); -static int gtk_entry_get_length (GtkEditable *editable); -static void gtk_entry_real_set_position (GtkEditable *editable, - gint position); -static gint gtk_entry_get_position (GtkEditable *editable); -static void gtk_entry_set_selection_bounds (GtkEditable *editable, - gint start, - gint end); -static gboolean gtk_entry_get_selection_bounds (GtkEditable *editable, - gint *start, - gint *end); -static void gtk_entry_set_editable (GtkEditable *editable, - gboolean is_editable); /* GtkCellEditable method implementations */ @@ -479,165 +304,9 @@ static void gtk_entry_start_editing (GtkCellEditable *cell_editable, /* Default signal handlers */ -static void gtk_entry_real_insert_text (GtkEditable *editable, - const gchar *new_text, - gint new_text_length, - gint *position); -static void gtk_entry_real_delete_text (GtkEditable *editable, - gint start_pos, - gint end_pos); -static void gtk_entry_move_cursor (GtkEntry *entry, - GtkMovementStep step, - gint count, - gboolean extend_selection); -static void gtk_entry_insert_at_cursor (GtkEntry *entry, - const gchar *str); -static void gtk_entry_delete_from_cursor (GtkEntry *entry, - GtkDeleteType type, - gint count); -static void gtk_entry_backspace (GtkEntry *entry); -static void gtk_entry_cut_clipboard (GtkEntry *entry); -static void gtk_entry_copy_clipboard (GtkEntry *entry); -static void gtk_entry_paste_clipboard (GtkEntry *entry); -static void gtk_entry_toggle_overwrite (GtkEntry *entry); -static void gtk_entry_insert_emoji (GtkEntry *entry); -static void gtk_entry_select_all (GtkEntry *entry); -static void gtk_entry_real_activate (GtkEntry *entry); -static gboolean gtk_entry_popup_menu (GtkWidget *widget); - -static void keymap_direction_changed (GdkKeymap *keymap, - GtkEntry *entry); -static void keymap_state_changed (GdkKeymap *keymap, - GtkEntry *entry); -static void remove_capslock_feedback (GtkEntry *entry); - -/* IM Context Callbacks - */ -static void gtk_entry_commit_cb (GtkIMContext *context, - const gchar *str, - GtkEntry *entry); -static void gtk_entry_preedit_changed_cb (GtkIMContext *context, - GtkEntry *entry); -static gboolean gtk_entry_retrieve_surrounding_cb (GtkIMContext *context, - GtkEntry *entry); -static gboolean gtk_entry_delete_surrounding_cb (GtkIMContext *context, - gint offset, - gint n_chars, - GtkEntry *entry); - -/* Event controller callbacks */ -static void gtk_entry_multipress_gesture_pressed (GtkGestureMultiPress *gesture, - gint n_press, - gdouble x, - gdouble y, - GtkEntry *entry); -static void gtk_entry_drag_gesture_update (GtkGestureDrag *gesture, - gdouble offset_x, - gdouble offset_y, - GtkEntry *entry); -static void gtk_entry_drag_gesture_end (GtkGestureDrag *gesture, - gdouble offset_x, - gdouble offset_y, - GtkEntry *entry); -static gboolean gtk_entry_key_controller_key_pressed (GtkEventControllerKey *controller, - guint keyval, - guint keycode, - GdkModifierType state, - GtkWidget *widget); - -/* Internal routines - */ -static void gtk_entry_draw_text (GtkEntry *entry, - GtkSnapshot *snapshot); -static void gtk_entry_draw_cursor (GtkEntry *entry, - GtkSnapshot *snapshot, - CursorType type); -static PangoLayout *gtk_entry_ensure_layout (GtkEntry *entry, - gboolean include_preedit); -static void gtk_entry_reset_layout (GtkEntry *entry); -static void gtk_entry_recompute (GtkEntry *entry); -static gint gtk_entry_find_position (GtkEntry *entry, - gint x); -static void gtk_entry_get_cursor_locations (GtkEntry *entry, - gint *strong_x, - gint *weak_x); -static void gtk_entry_adjust_scroll (GtkEntry *entry); -static gint gtk_entry_move_visually (GtkEntry *editable, - gint start, - gint count); -static gint gtk_entry_move_logically (GtkEntry *entry, - gint start, - gint count); -static gint gtk_entry_move_forward_word (GtkEntry *entry, - gint start, - gboolean allow_whitespace); -static gint gtk_entry_move_backward_word (GtkEntry *entry, - gint start, - gboolean allow_whitespace); -static void gtk_entry_delete_whitespace (GtkEntry *entry); -static void gtk_entry_select_word (GtkEntry *entry); -static void gtk_entry_select_line (GtkEntry *entry); -static void gtk_entry_paste (GtkEntry *entry, - GdkClipboard *clipboard); -static void gtk_entry_update_primary_selection (GtkEntry *entry); -static void gtk_entry_do_popup (GtkEntry *entry, - const GdkEvent *event); -static gboolean gtk_entry_mnemonic_activate (GtkWidget *widget, - gboolean group_cycling); -static void gtk_entry_check_cursor_blink (GtkEntry *entry); -static void gtk_entry_pend_cursor_blink (GtkEntry *entry); -static void gtk_entry_reset_blink_time (GtkEntry *entry); -static void gtk_entry_update_cached_style_values(GtkEntry *entry); -static gboolean get_middle_click_paste (GtkEntry *entry); -static void gtk_entry_get_scroll_limits (GtkEntry *entry, - gint *min_offset, - gint *max_offset); - -/* GtkTextHandle handlers */ -static void gtk_entry_handle_drag_started (GtkTextHandle *handle, - GtkTextHandlePosition pos, - GtkEntry *entry); -static void gtk_entry_handle_dragged (GtkTextHandle *handle, - GtkTextHandlePosition pos, - gint x, - gint y, - GtkEntry *entry); -static void gtk_entry_handle_drag_finished (GtkTextHandle *handle, - GtkTextHandlePosition pos, - GtkEntry *entry); - -static void gtk_entry_selection_bubble_popup_set (GtkEntry *entry); -static void gtk_entry_selection_bubble_popup_unset (GtkEntry *entry); - -static void begin_change (GtkEntry *entry); -static void end_change (GtkEntry *entry); -static void emit_changed (GtkEntry *entry); - -static void buffer_inserted_text (GtkEntryBuffer *buffer, - guint position, - const gchar *chars, - guint n_chars, - GtkEntry *entry); -static void buffer_deleted_text (GtkEntryBuffer *buffer, - guint position, - guint n_chars, - GtkEntry *entry); -static void buffer_notify_text (GtkEntryBuffer *buffer, - GParamSpec *spec, - GtkEntry *entry); -static void buffer_notify_length (GtkEntryBuffer *buffer, - GParamSpec *spec, - GtkEntry *entry); -static void buffer_notify_max_length (GtkEntryBuffer *buffer, - GParamSpec *spec, - GtkEntry *entry); -static void buffer_connect_signals (GtkEntry *entry); -static void buffer_disconnect_signals (GtkEntry *entry); static GtkEntryBuffer *get_buffer (GtkEntry *entry); static void set_show_emoji_icon (GtkEntry *entry, gboolean value); -static void set_enable_emoji_completion (GtkEntry *entry, - gboolean value); static void gtk_entry_measure (GtkWidget *widget, GtkOrientation orientation, @@ -654,107 +323,13 @@ G_DEFINE_TYPE_WITH_CODE (GtkEntry, gtk_entry, GTK_TYPE_WIDGET, G_IMPLEMENT_INTERFACE (GTK_TYPE_CELL_EDITABLE, gtk_entry_cell_editable_init)) -#define GTK_TYPE_ENTRY_CONTENT (gtk_entry_content_get_type ()) -#define GTK_ENTRY_CONTENT(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTK_TYPE_ENTRY_CONTENT, GtkEntryContent)) -#define GTK_IS_ENTRY_CONTENT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTK_TYPE_ENTRY_CONTENT)) -#define GTK_ENTRY_CONTENT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GTK_TYPE_ENTRY_CONTENT, GtkEntryContentClass)) -#define GTK_IS_ENTRY_CONTENT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GTK_TYPE_ENTRY_CONTENT)) -#define GTK_ENTRY_CONTENT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GTK_TYPE_ENTRY_CONTENT, GtkEntryContentClass)) - -typedef struct _GtkEntryContent GtkEntryContent; -typedef struct _GtkEntryContentClass GtkEntryContentClass; - -struct _GtkEntryContent -{ - GdkContentProvider parent; - - GtkEntry *entry; -}; - -struct _GtkEntryContentClass -{ - GdkContentProviderClass parent_class; -}; - -GType gtk_entry_content_get_type (void) G_GNUC_CONST; - -G_DEFINE_TYPE (GtkEntryContent, gtk_entry_content, GDK_TYPE_CONTENT_PROVIDER) - -static GdkContentFormats * -gtk_entry_content_ref_formats (GdkContentProvider *provider) -{ - return gdk_content_formats_new_for_gtype (G_TYPE_STRING); -} - -static gboolean -gtk_entry_content_get_value (GdkContentProvider *provider, - GValue *value, - GError **error) -{ - GtkEntryContent *content = GTK_ENTRY_CONTENT (provider); - - if (G_VALUE_HOLDS (value, G_TYPE_STRING)) - { - int start, end; - - if (gtk_editable_get_selection_bounds (GTK_EDITABLE (content->entry), &start, &end)) - { - gchar *str = _gtk_entry_get_display_text (content->entry, start, end); - g_value_take_string (value, str); - } - return TRUE; - } - - return GDK_CONTENT_PROVIDER_CLASS (gtk_entry_content_parent_class)->get_value (provider, value, error); -} - static void -gtk_entry_content_detach (GdkContentProvider *provider, - GdkClipboard *clipboard) +gtk_entry_grab_focus (GtkWidget *widget) { - GtkEntryContent *content = GTK_ENTRY_CONTENT (provider); - GtkEntry *entry = content->entry; + GtkEntry *entry = GTK_ENTRY (widget); GtkEntryPrivate *priv = gtk_entry_get_instance_private (entry); - gtk_editable_select_region (GTK_EDITABLE (entry), priv->current_pos, priv->current_pos); -} - -static void -gtk_entry_content_class_init (GtkEntryContentClass *class) -{ - GdkContentProviderClass *provider_class = GDK_CONTENT_PROVIDER_CLASS (class); - - provider_class->ref_formats = gtk_entry_content_ref_formats; - provider_class->get_value = gtk_entry_content_get_value; - provider_class->detach_clipboard = gtk_entry_content_detach; -} - -static void -gtk_entry_content_init (GtkEntryContent *content) -{ -} - -static void -add_move_binding (GtkBindingSet *binding_set, - guint keyval, - guint modmask, - GtkMovementStep step, - gint count) -{ - g_return_if_fail ((modmask & GDK_SHIFT_MASK) == 0); - - gtk_binding_entry_add_signal (binding_set, keyval, modmask, - "move-cursor", 3, - G_TYPE_ENUM, step, - G_TYPE_INT, count, - G_TYPE_BOOLEAN, FALSE); - - /* Selection-extending version */ - gtk_binding_entry_add_signal (binding_set, keyval, modmask | GDK_SHIFT_MASK, - "move-cursor", 3, - G_TYPE_ENUM, step, - G_TYPE_INT, count, - G_TYPE_BOOLEAN, TRUE); + gtk_widget_grab_focus (priv->text); } static void @@ -762,7 +337,6 @@ gtk_entry_class_init (GtkEntryClass *class) { GObjectClass *gobject_class = G_OBJECT_CLASS (class); GtkWidgetClass *widget_class; - GtkBindingSet *binding_set; widget_class = (GtkWidgetClass*) class; @@ -771,52 +345,15 @@ gtk_entry_class_init (GtkEntryClass *class) gobject_class->set_property = gtk_entry_set_property; gobject_class->get_property = gtk_entry_get_property; - widget_class->destroy = gtk_entry_destroy; - widget_class->unmap = gtk_entry_unmap; - widget_class->realize = gtk_entry_realize; - widget_class->unrealize = gtk_entry_unrealize; widget_class->measure = gtk_entry_measure; widget_class->size_allocate = gtk_entry_size_allocate; widget_class->snapshot = gtk_entry_snapshot; - widget_class->grab_focus = gtk_entry_grab_focus; - widget_class->style_updated = gtk_entry_style_updated; widget_class->query_tooltip = gtk_entry_query_tooltip; - widget_class->drag_begin = gtk_entry_drag_begin; - widget_class->drag_end = gtk_entry_drag_end; widget_class->direction_changed = gtk_entry_direction_changed; - widget_class->state_flags_changed = gtk_entry_state_flags_changed; - widget_class->display_changed = gtk_entry_display_changed; - widget_class->mnemonic_activate = gtk_entry_mnemonic_activate; - - widget_class->drag_drop = gtk_entry_drag_drop; - widget_class->drag_motion = gtk_entry_drag_motion; - widget_class->drag_leave = gtk_entry_drag_leave; - widget_class->drag_data_received = gtk_entry_drag_data_received; - widget_class->drag_data_get = gtk_entry_drag_data_get; - widget_class->drag_data_delete = gtk_entry_drag_data_delete; - - widget_class->popup_menu = gtk_entry_popup_menu; - - class->move_cursor = gtk_entry_move_cursor; - class->insert_at_cursor = gtk_entry_insert_at_cursor; - class->delete_from_cursor = gtk_entry_delete_from_cursor; - class->backspace = gtk_entry_backspace; - class->cut_clipboard = gtk_entry_cut_clipboard; - class->copy_clipboard = gtk_entry_copy_clipboard; - class->paste_clipboard = gtk_entry_paste_clipboard; - class->toggle_overwrite = gtk_entry_toggle_overwrite; - class->insert_emoji = gtk_entry_insert_emoji; - class->activate = gtk_entry_real_activate; + widget_class->grab_focus = gtk_entry_grab_focus; - quark_password_hint = g_quark_from_static_string ("gtk-entry-password-hint"); - quark_capslock_feedback = g_quark_from_static_string ("gtk-entry-capslock-feedback"); - quark_gtk_signal = g_quark_from_static_string ("gtk-signal"); quark_entry_completion = g_quark_from_static_string ("gtk-entry-completion-key"); - g_object_class_override_property (gobject_class, - PROP_EDITING_CANCELED, - "editing-canceled"); - entry_props[PROP_BUFFER] = g_param_spec_object ("buffer", P_("Text Buffer"), @@ -824,29 +361,6 @@ gtk_entry_class_init (GtkEntryClass *class) GTK_TYPE_ENTRY_BUFFER, GTK_PARAM_READWRITE|G_PARAM_CONSTRUCT|G_PARAM_EXPLICIT_NOTIFY); - entry_props[PROP_CURSOR_POSITION] = - g_param_spec_int ("cursor-position", - P_("Cursor Position"), - P_("The current position of the insertion cursor in chars"), - 0, GTK_ENTRY_BUFFER_MAX_SIZE, - 0, - GTK_PARAM_READABLE); - - entry_props[PROP_SELECTION_BOUND] = - g_param_spec_int ("selection-bound", - P_("Selection Bound"), - P_("The position of the opposite end of the selection from the cursor in chars"), - 0, GTK_ENTRY_BUFFER_MAX_SIZE, - 0, - GTK_PARAM_READABLE); - - entry_props[PROP_EDITABLE] = - g_param_spec_boolean ("editable", - P_("Editable"), - P_("Whether the entry contents can be edited"), - TRUE, - GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY); - entry_props[PROP_MAX_LENGTH] = g_param_spec_int ("max-length", P_("Maximum length"), @@ -883,29 +397,6 @@ gtk_entry_class_init (GtkEntryClass *class) FALSE, GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY); - entry_props[PROP_WIDTH_CHARS] = - g_param_spec_int ("width-chars", - P_("Width in chars"), - P_("Number of characters to leave space for in the entry"), - -1, G_MAXINT, - -1, - GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY); - - /** - * GtkEntry:max-width-chars: - * - * The desired maximum width of the entry, in characters. - * If this property is set to -1, the width will be calculated - * automatically. - */ - entry_props[PROP_MAX_WIDTH_CHARS] = - g_param_spec_int ("max-width-chars", - P_("Maximum width in characters"), - P_("The desired maximum width of the entry, in characters"), - -1, G_MAXINT, - -1, - GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY); - entry_props[PROP_SCROLL_OFFSET] = g_param_spec_int ("scroll-offset", P_("Scroll offset"), @@ -914,27 +405,6 @@ gtk_entry_class_init (GtkEntryClass *class) 0, GTK_PARAM_READABLE|G_PARAM_EXPLICIT_NOTIFY); - entry_props[PROP_TEXT] = - g_param_spec_string ("text", - P_("Text"), - P_("The contents of the entry"), - "", - GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY); - - /** - * GtkEntry:xalign: - * - * The horizontal alignment, from 0 (left) to 1 (right). - * Reversed for RTL layouts. - */ - entry_props[PROP_XALIGN] = - g_param_spec_float ("xalign", - P_("X align"), - P_("The horizontal alignment, from 0 (left) to 1 (right). Reversed for RTL layouts."), - 0.0, 1.0, - 0.0, - GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY); - /** * GtkEntry:truncate-multiline: * @@ -984,22 +454,6 @@ gtk_entry_class_init (GtkEntryClass *class) FALSE, GTK_PARAM_READWRITE); - /** - * GtkEntry:caps-lock-warning: - * - * Whether password entries will show a warning when Caps Lock is on. - * - * Note that the warning is shown using a secondary icon, and thus - * does not work if you are using the secondary icon position for some - * other purpose. - */ - entry_props[PROP_CAPS_LOCK_WARNING] = - g_param_spec_boolean ("caps-lock-warning", - P_("Caps Lock warning"), - P_("Whether password entries will show a warning when Caps Lock is on"), - TRUE, - GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY); - /** * GtkEntry:progress-fraction: * @@ -1396,242 +850,17 @@ gtk_entry_class_init (GtkEntryClass *class) GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY); g_object_class_install_properties (gobject_class, NUM_PROPERTIES, entry_props); + g_object_class_override_property (gobject_class, PROP_EDITING_CANCELED, "editing-canceled"); + gtk_editable_install_properties (gobject_class, PROP_EDITING_CANCELED + 1); - /** - * GtkEntry::populate-popup: - * @entry: The entry on which the signal is emitted - * @widget: the container that is being populated - * - * The ::populate-popup signal gets emitted before showing the - * context menu of the entry. - * - * If you need to add items to the context menu, connect - * to this signal and append your items to the @widget, which - * will be a #GtkMenu in this case. - * - * If #GtkEntry:populate-all is %TRUE, this signal will - * also be emitted to populate touch popups. In this case, - * @widget will be a different container, e.g. a #GtkToolbar. - * The signal handler should not make assumptions about the - * type of @widget. - */ - signals[POPULATE_POPUP] = - g_signal_new (I_("populate-popup"), - G_OBJECT_CLASS_TYPE (gobject_class), - G_SIGNAL_RUN_LAST, - G_STRUCT_OFFSET (GtkEntryClass, populate_popup), - NULL, NULL, - NULL, - G_TYPE_NONE, 1, - GTK_TYPE_WIDGET); - - /* Action signals */ - - /** - * GtkEntry::activate: - * @entry: The entry on which the signal is emitted - * - * The ::activate signal is emitted when the user hits - * the Enter key. - * - * The default bindings for this signal are all forms of the Enter key. - */ signals[ACTIVATE] = g_signal_new (I_("activate"), - G_OBJECT_CLASS_TYPE (gobject_class), - G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, - G_STRUCT_OFFSET (GtkEntryClass, activate), - NULL, NULL, - NULL, - G_TYPE_NONE, 0); - - /** - * GtkEntry::move-cursor: - * @entry: the object which received the signal - * @step: the granularity of the move, as a #GtkMovementStep - * @count: the number of @step units to move - * @extend_selection: %TRUE if the move should extend the selection - * - * The ::move-cursor signal is a - * [keybinding signal][GtkBindingSignal] - * which gets emitted when the user initiates a cursor movement. - * If the cursor is not visible in @entry, this signal causes - * the viewport to be moved instead. - * - * Applications should not connect to it, but may emit it with - * g_signal_emit_by_name() if they need to control the cursor - * programmatically. - * - * The default bindings for this signal come in two variants, - * the variant with the Shift modifier extends the selection, - * the variant without the Shift modifer does not. - * There are too many key combinations to list them all here. - * - Arrow keys move by individual characters/lines - * - Ctrl-arrow key combinations move by words/paragraphs - * - Home/End keys move to the ends of the buffer - */ - signals[MOVE_CURSOR] = - g_signal_new (I_("move-cursor"), - G_OBJECT_CLASS_TYPE (gobject_class), - G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, - G_STRUCT_OFFSET (GtkEntryClass, move_cursor), - NULL, NULL, - _gtk_marshal_VOID__ENUM_INT_BOOLEAN, - G_TYPE_NONE, 3, - GTK_TYPE_MOVEMENT_STEP, - G_TYPE_INT, - G_TYPE_BOOLEAN); - - /** - * GtkEntry::insert-at-cursor: - * @entry: the object which received the signal - * @string: the string to insert - * - * The ::insert-at-cursor signal is a - * [keybinding signal][GtkBindingSignal] - * which gets emitted when the user initiates the insertion of a - * fixed string at the cursor. - * - * This signal has no default bindings. - */ - signals[INSERT_AT_CURSOR] = - g_signal_new (I_("insert-at-cursor"), - G_OBJECT_CLASS_TYPE (gobject_class), - G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, - G_STRUCT_OFFSET (GtkEntryClass, insert_at_cursor), - NULL, NULL, - NULL, - G_TYPE_NONE, 1, - G_TYPE_STRING); - - /** - * GtkEntry::delete-from-cursor: - * @entry: the object which received the signal - * @type: the granularity of the deletion, as a #GtkDeleteType - * @count: the number of @type units to delete - * - * The ::delete-from-cursor signal is a - * [keybinding signal][GtkBindingSignal] - * which gets emitted when the user initiates a text deletion. - * - * If the @type is %GTK_DELETE_CHARS, GTK+ deletes the selection - * if there is one, otherwise it deletes the requested number - * of characters. - * - * The default bindings for this signal are - * Delete for deleting a character and Ctrl-Delete for - * deleting a word. - */ - signals[DELETE_FROM_CURSOR] = - g_signal_new (I_("delete-from-cursor"), - G_OBJECT_CLASS_TYPE (gobject_class), - G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, - G_STRUCT_OFFSET (GtkEntryClass, delete_from_cursor), - NULL, NULL, - _gtk_marshal_VOID__ENUM_INT, - G_TYPE_NONE, 2, - GTK_TYPE_DELETE_TYPE, - G_TYPE_INT); - - /** - * GtkEntry::backspace: - * @entry: the object which received the signal - * - * The ::backspace signal is a - * [keybinding signal][GtkBindingSignal] - * which gets emitted when the user asks for it. - * - * The default bindings for this signal are - * Backspace and Shift-Backspace. - */ - signals[BACKSPACE] = - g_signal_new (I_("backspace"), - G_OBJECT_CLASS_TYPE (gobject_class), - G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, - G_STRUCT_OFFSET (GtkEntryClass, backspace), - NULL, NULL, - NULL, - G_TYPE_NONE, 0); - - /** - * GtkEntry::cut-clipboard: - * @entry: the object which received the signal - * - * The ::cut-clipboard signal is a - * [keybinding signal][GtkBindingSignal] - * which gets emitted to cut the selection to the clipboard. - * - * The default bindings for this signal are - * Ctrl-x and Shift-Delete. - */ - signals[CUT_CLIPBOARD] = - g_signal_new (I_("cut-clipboard"), - G_OBJECT_CLASS_TYPE (gobject_class), - G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, - G_STRUCT_OFFSET (GtkEntryClass, cut_clipboard), - NULL, NULL, - NULL, - G_TYPE_NONE, 0); - - /** - * GtkEntry::copy-clipboard: - * @entry: the object which received the signal - * - * The ::copy-clipboard signal is a - * [keybinding signal][GtkBindingSignal] - * which gets emitted to copy the selection to the clipboard. - * - * The default bindings for this signal are - * Ctrl-c and Ctrl-Insert. - */ - signals[COPY_CLIPBOARD] = - g_signal_new (I_("copy-clipboard"), - G_OBJECT_CLASS_TYPE (gobject_class), - G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, - G_STRUCT_OFFSET (GtkEntryClass, copy_clipboard), - NULL, NULL, - NULL, - G_TYPE_NONE, 0); - - /** - * GtkEntry::paste-clipboard: - * @entry: the object which received the signal - * - * The ::paste-clipboard signal is a - * [keybinding signal][GtkBindingSignal] - * which gets emitted to paste the contents of the clipboard - * into the text view. - * - * The default bindings for this signal are - * Ctrl-v and Shift-Insert. - */ - signals[PASTE_CLIPBOARD] = - g_signal_new (I_("paste-clipboard"), - G_OBJECT_CLASS_TYPE (gobject_class), - G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, - G_STRUCT_OFFSET (GtkEntryClass, paste_clipboard), - NULL, NULL, - NULL, - G_TYPE_NONE, 0); - - /** - * GtkEntry::toggle-overwrite: - * @entry: the object which received the signal - * - * The ::toggle-overwrite signal is a - * [keybinding signal][GtkBindingSignal] - * which gets emitted to toggle the overwrite mode of the entry. - * - * The default bindings for this signal is Insert. - */ - signals[TOGGLE_OVERWRITE] = - g_signal_new (I_("toggle-overwrite"), - G_OBJECT_CLASS_TYPE (gobject_class), - G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, - G_STRUCT_OFFSET (GtkEntryClass, toggle_overwrite), - NULL, NULL, - NULL, - G_TYPE_NONE, 0); + G_OBJECT_CLASS_TYPE (gobject_class), + G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, + G_STRUCT_OFFSET (GtkEntryClass, activate), + NULL, NULL, + NULL, + G_TYPE_NONE, 0); /** * GtkEntry::icon-press: @@ -1669,276 +898,23 @@ gtk_entry_class_init (GtkEntryClass *class) G_TYPE_NONE, 1, GTK_TYPE_ENTRY_ICON_POSITION); - /** - * GtkEntry::preedit-changed: - * @entry: the object which received the signal - * @preedit: the current preedit string - * - * If an input method is used, the typed text will not immediately - * be committed to the buffer. So if you are interested in the text, - * connect to this signal. - */ - signals[PREEDIT_CHANGED] = - g_signal_new_class_handler (I_("preedit-changed"), - G_OBJECT_CLASS_TYPE (gobject_class), - G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, - NULL, - NULL, NULL, - NULL, - G_TYPE_NONE, 1, - G_TYPE_STRING); - - - /** - * GtkEntry::insert-emoji: - * @entry: the object which received the signal - * - * The ::insert-emoji signal is a - * [keybinding signal][GtkBindingSignal] - * which gets emitted to present the Emoji chooser for the @entry. - * - * The default bindings for this signal are Ctrl-. and Ctrl-; - */ - signals[INSERT_EMOJI] = - g_signal_new (I_("insert-emoji"), - G_OBJECT_CLASS_TYPE (gobject_class), - G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, - G_STRUCT_OFFSET (GtkEntryClass, insert_emoji), - NULL, NULL, - NULL, - G_TYPE_NONE, 0); - - /* - * Key bindings - */ - - binding_set = gtk_binding_set_by_class (class); - - /* Moving the insertion point */ - add_move_binding (binding_set, GDK_KEY_Right, 0, - GTK_MOVEMENT_VISUAL_POSITIONS, 1); - - add_move_binding (binding_set, GDK_KEY_Left, 0, - GTK_MOVEMENT_VISUAL_POSITIONS, -1); - - add_move_binding (binding_set, GDK_KEY_KP_Right, 0, - GTK_MOVEMENT_VISUAL_POSITIONS, 1); - - add_move_binding (binding_set, GDK_KEY_KP_Left, 0, - GTK_MOVEMENT_VISUAL_POSITIONS, -1); - - add_move_binding (binding_set, GDK_KEY_Right, GDK_CONTROL_MASK, - GTK_MOVEMENT_WORDS, 1); - - add_move_binding (binding_set, GDK_KEY_Left, GDK_CONTROL_MASK, - GTK_MOVEMENT_WORDS, -1); - - add_move_binding (binding_set, GDK_KEY_KP_Right, GDK_CONTROL_MASK, - GTK_MOVEMENT_WORDS, 1); - - add_move_binding (binding_set, GDK_KEY_KP_Left, GDK_CONTROL_MASK, - GTK_MOVEMENT_WORDS, -1); - - add_move_binding (binding_set, GDK_KEY_Home, 0, - GTK_MOVEMENT_DISPLAY_LINE_ENDS, -1); - - add_move_binding (binding_set, GDK_KEY_End, 0, - GTK_MOVEMENT_DISPLAY_LINE_ENDS, 1); - - add_move_binding (binding_set, GDK_KEY_KP_Home, 0, - GTK_MOVEMENT_DISPLAY_LINE_ENDS, -1); - - add_move_binding (binding_set, GDK_KEY_KP_End, 0, - GTK_MOVEMENT_DISPLAY_LINE_ENDS, 1); - - add_move_binding (binding_set, GDK_KEY_Home, GDK_CONTROL_MASK, - GTK_MOVEMENT_BUFFER_ENDS, -1); - - add_move_binding (binding_set, GDK_KEY_End, GDK_CONTROL_MASK, - GTK_MOVEMENT_BUFFER_ENDS, 1); - - add_move_binding (binding_set, GDK_KEY_KP_Home, GDK_CONTROL_MASK, - GTK_MOVEMENT_BUFFER_ENDS, -1); - - add_move_binding (binding_set, GDK_KEY_KP_End, GDK_CONTROL_MASK, - GTK_MOVEMENT_BUFFER_ENDS, 1); - - /* Select all - */ - gtk_binding_entry_add_signal (binding_set, GDK_KEY_a, GDK_CONTROL_MASK, - "move-cursor", 3, - GTK_TYPE_MOVEMENT_STEP, GTK_MOVEMENT_BUFFER_ENDS, - G_TYPE_INT, -1, - G_TYPE_BOOLEAN, FALSE); - gtk_binding_entry_add_signal (binding_set, GDK_KEY_a, GDK_CONTROL_MASK, - "move-cursor", 3, - GTK_TYPE_MOVEMENT_STEP, GTK_MOVEMENT_BUFFER_ENDS, - G_TYPE_INT, 1, - G_TYPE_BOOLEAN, TRUE); - - gtk_binding_entry_add_signal (binding_set, GDK_KEY_slash, GDK_CONTROL_MASK, - "move-cursor", 3, - GTK_TYPE_MOVEMENT_STEP, GTK_MOVEMENT_BUFFER_ENDS, - G_TYPE_INT, -1, - G_TYPE_BOOLEAN, FALSE); - gtk_binding_entry_add_signal (binding_set, GDK_KEY_slash, GDK_CONTROL_MASK, - "move-cursor", 3, - GTK_TYPE_MOVEMENT_STEP, GTK_MOVEMENT_BUFFER_ENDS, - G_TYPE_INT, 1, - G_TYPE_BOOLEAN, TRUE); - /* Unselect all - */ - gtk_binding_entry_add_signal (binding_set, GDK_KEY_backslash, GDK_CONTROL_MASK, - "move-cursor", 3, - GTK_TYPE_MOVEMENT_STEP, GTK_MOVEMENT_VISUAL_POSITIONS, - G_TYPE_INT, 0, - G_TYPE_BOOLEAN, FALSE); - gtk_binding_entry_add_signal (binding_set, GDK_KEY_a, GDK_SHIFT_MASK | GDK_CONTROL_MASK, - "move-cursor", 3, - GTK_TYPE_MOVEMENT_STEP, GTK_MOVEMENT_VISUAL_POSITIONS, - G_TYPE_INT, 0, - G_TYPE_BOOLEAN, FALSE); - - /* Activate - */ - gtk_binding_entry_add_signal (binding_set, GDK_KEY_Return, 0, - "activate", 0); - gtk_binding_entry_add_signal (binding_set, GDK_KEY_ISO_Enter, 0, - "activate", 0); - gtk_binding_entry_add_signal (binding_set, GDK_KEY_KP_Enter, 0, - "activate", 0); - - /* Deleting text */ - gtk_binding_entry_add_signal (binding_set, GDK_KEY_Delete, 0, - "delete-from-cursor", 2, - G_TYPE_ENUM, GTK_DELETE_CHARS, - G_TYPE_INT, 1); - - gtk_binding_entry_add_signal (binding_set, GDK_KEY_KP_Delete, 0, - "delete-from-cursor", 2, - G_TYPE_ENUM, GTK_DELETE_CHARS, - G_TYPE_INT, 1); - - gtk_binding_entry_add_signal (binding_set, GDK_KEY_BackSpace, 0, - "backspace", 0); - - gtk_binding_entry_add_signal (binding_set, GDK_KEY_u, GDK_CONTROL_MASK, - "delete-from-cursor", 2, - G_TYPE_ENUM, GTK_DELETE_PARAGRAPH_ENDS, - G_TYPE_INT, -1); - - /* Make this do the same as Backspace, to help with mis-typing */ - gtk_binding_entry_add_signal (binding_set, GDK_KEY_BackSpace, GDK_SHIFT_MASK, - "backspace", 0); - - gtk_binding_entry_add_signal (binding_set, GDK_KEY_Delete, GDK_CONTROL_MASK, - "delete-from-cursor", 2, - G_TYPE_ENUM, GTK_DELETE_WORD_ENDS, - G_TYPE_INT, 1); - - gtk_binding_entry_add_signal (binding_set, GDK_KEY_KP_Delete, GDK_CONTROL_MASK, - "delete-from-cursor", 2, - G_TYPE_ENUM, GTK_DELETE_WORD_ENDS, - G_TYPE_INT, 1); - - gtk_binding_entry_add_signal (binding_set, GDK_KEY_BackSpace, GDK_CONTROL_MASK, - "delete-from-cursor", 2, - G_TYPE_ENUM, GTK_DELETE_WORD_ENDS, - G_TYPE_INT, -1); - - /* Cut/copy/paste */ - - gtk_binding_entry_add_signal (binding_set, GDK_KEY_x, GDK_CONTROL_MASK, - "cut-clipboard", 0); - gtk_binding_entry_add_signal (binding_set, GDK_KEY_c, GDK_CONTROL_MASK, - "copy-clipboard", 0); - gtk_binding_entry_add_signal (binding_set, GDK_KEY_v, GDK_CONTROL_MASK, - "paste-clipboard", 0); - - gtk_binding_entry_add_signal (binding_set, GDK_KEY_Delete, GDK_SHIFT_MASK, - "cut-clipboard", 0); - gtk_binding_entry_add_signal (binding_set, GDK_KEY_Insert, GDK_CONTROL_MASK, - "copy-clipboard", 0); - gtk_binding_entry_add_signal (binding_set, GDK_KEY_Insert, GDK_SHIFT_MASK, - "paste-clipboard", 0); - - gtk_binding_entry_add_signal (binding_set, GDK_KEY_KP_Delete, GDK_SHIFT_MASK, - "cut-clipboard", 0); - gtk_binding_entry_add_signal (binding_set, GDK_KEY_KP_Insert, GDK_CONTROL_MASK, - "copy-clipboard", 0); - gtk_binding_entry_add_signal (binding_set, GDK_KEY_KP_Insert, GDK_SHIFT_MASK, - "paste-clipboard", 0); - - /* Overwrite */ - gtk_binding_entry_add_signal (binding_set, GDK_KEY_Insert, 0, - "toggle-overwrite", 0); - gtk_binding_entry_add_signal (binding_set, GDK_KEY_KP_Insert, 0, - "toggle-overwrite", 0); - - /* Emoji */ - gtk_binding_entry_add_signal (binding_set, GDK_KEY_period, GDK_CONTROL_MASK, - "insert-emoji", 0); - gtk_binding_entry_add_signal (binding_set, GDK_KEY_semicolon, GDK_CONTROL_MASK, - "insert-emoji", 0); - gtk_widget_class_set_accessible_type (widget_class, GTK_TYPE_ENTRY_ACCESSIBLE); gtk_widget_class_set_css_name (widget_class, I_("entry")); } -static void -gtk_entry_set_editable (GtkEditable *editable, - gboolean is_editable) +static GtkEditable * +gtk_entry_get_delegate (GtkEditable *editable) { GtkEntry *entry = GTK_ENTRY (editable); GtkEntryPrivate *priv = gtk_entry_get_instance_private (entry); - GtkStyleContext *context = gtk_widget_get_style_context (GTK_WIDGET (entry)); - if (is_editable != priv->editable) - { - GtkWidget *widget = GTK_WIDGET (entry); - - if (!is_editable) - { - gtk_entry_reset_im_context (entry); - if (gtk_widget_has_focus (widget)) - gtk_im_context_focus_out (priv->im_context); - - priv->preedit_length = 0; - priv->preedit_cursor = 0; - - gtk_style_context_remove_class (context, GTK_STYLE_CLASS_READ_ONLY); - } - else - { - gtk_style_context_add_class (context, GTK_STYLE_CLASS_READ_ONLY); - } - - priv->editable = is_editable; - - if (is_editable && gtk_widget_has_focus (widget)) - gtk_im_context_focus_in (priv->im_context); - - gtk_event_controller_key_set_im_context (GTK_EVENT_CONTROLLER_KEY (priv->key_controller), - is_editable ? priv->im_context : NULL); - - g_object_notify (G_OBJECT (editable), "editable"); - gtk_widget_queue_draw (widget); - } + return GTK_EDITABLE (priv->text); } static void gtk_entry_editable_init (GtkEditableInterface *iface) { - iface->do_insert_text = gtk_entry_insert_text; - iface->do_delete_text = gtk_entry_delete_text; - iface->insert_text = gtk_entry_real_insert_text; - iface->delete_text = gtk_entry_real_delete_text; - iface->get_text = gtk_entry_real_get_text; - iface->get_length = gtk_entry_get_length; - iface->set_selection_bounds = gtk_entry_set_selection_bounds; - iface->get_selection_bounds = gtk_entry_get_selection_bounds; - iface->set_position = gtk_entry_real_set_position; - iface->get_position = gtk_entry_get_position; + iface->get_delegate = gtk_entry_get_delegate; } static void @@ -1956,79 +932,34 @@ gtk_entry_set_property (GObject *object, GtkEntry *entry = GTK_ENTRY (object); GtkEntryPrivate *priv = gtk_entry_get_instance_private (entry); + if (gtk_editable_delegate_set_property (object, prop_id, value, pspec)) + return; + switch (prop_id) { case PROP_BUFFER: - gtk_entry_set_buffer (entry, g_value_get_object (value)); - break; - - case PROP_EDITABLE: - gtk_entry_set_editable (GTK_EDITABLE (entry), g_value_get_boolean (value)); - break; - case PROP_MAX_LENGTH: - gtk_entry_set_max_length (entry, g_value_get_int (value)); - break; - case PROP_VISIBILITY: - gtk_entry_set_visibility (entry, g_value_get_boolean (value)); + case PROP_INVISIBLE_CHAR: + case PROP_INVISIBLE_CHAR_SET: + case PROP_ACTIVATES_DEFAULT: + case PROP_TRUNCATE_MULTILINE: + case PROP_OVERWRITE_MODE: + case PROP_PLACEHOLDER_TEXT: + case PROP_IM_MODULE: + case PROP_INPUT_PURPOSE: + case PROP_INPUT_HINTS: + case PROP_ATTRIBUTES: + case PROP_POPULATE_ALL: + case PROP_TABS: + case PROP_ENABLE_EMOJI_COMPLETION: + g_object_set_property (G_OBJECT (priv->text), pspec->name, value); break; case PROP_HAS_FRAME: gtk_entry_set_has_frame (entry, g_value_get_boolean (value)); break; - case PROP_INVISIBLE_CHAR: - gtk_entry_set_invisible_char (entry, g_value_get_uint (value)); - break; - - case PROP_ACTIVATES_DEFAULT: - gtk_entry_set_activates_default (entry, g_value_get_boolean (value)); - break; - - case PROP_WIDTH_CHARS: - gtk_entry_set_width_chars (entry, g_value_get_int (value)); - break; - - case PROP_MAX_WIDTH_CHARS: - gtk_entry_set_max_width_chars (entry, g_value_get_int (value)); - break; - - case PROP_TEXT: - gtk_entry_set_text (entry, g_value_get_string (value)); - break; - - case PROP_XALIGN: - gtk_entry_set_alignment (entry, g_value_get_float (value)); - break; - - case PROP_TRUNCATE_MULTILINE: - if (priv->truncate_multiline != g_value_get_boolean (value)) - { - priv->truncate_multiline = g_value_get_boolean (value); - g_object_notify_by_pspec (object, pspec); - } - break; - - case PROP_OVERWRITE_MODE: - gtk_entry_set_overwrite_mode (entry, g_value_get_boolean (value)); - break; - - case PROP_INVISIBLE_CHAR_SET: - if (g_value_get_boolean (value)) - priv->invisible_char_set = TRUE; - else - gtk_entry_unset_invisible_char (entry); - break; - - case PROP_CAPS_LOCK_WARNING: - if (priv->caps_lock_warning != g_value_get_boolean (value)) - { - priv->caps_lock_warning = g_value_get_boolean (value); - g_object_notify_by_pspec (object, pspec); - } - break; - case PROP_PROGRESS_FRACTION: gtk_entry_set_progress_fraction (entry, g_value_get_double (value)); break; @@ -2037,10 +968,6 @@ gtk_entry_set_property (GObject *object, gtk_entry_set_progress_pulse_step (entry, g_value_get_double (value)); break; - case PROP_PLACEHOLDER_TEXT: - gtk_entry_set_placeholder_text (entry, g_value_get_string (value)); - break; - case PROP_PAINTABLE_PRIMARY: gtk_entry_set_icon_from_paintable (entry, GTK_ENTRY_ICON_PRIMARY, @@ -2125,14 +1052,6 @@ gtk_entry_set_property (GObject *object, g_value_get_string (value)); break; - case PROP_IM_MODULE: - g_free (priv->im_module); - priv->im_module = g_value_dup_string (value); - if (GTK_IS_IM_MULTICONTEXT (priv->im_context)) - gtk_im_multicontext_set_context_id (GTK_IM_MULTICONTEXT (priv->im_context), priv->im_module); - g_object_notify_by_pspec (object, pspec); - break; - case PROP_EDITING_CANCELED: if (priv->editing_canceled != g_value_get_boolean (value)) { @@ -2145,40 +1064,10 @@ gtk_entry_set_property (GObject *object, gtk_entry_set_completion (entry, GTK_ENTRY_COMPLETION (g_value_get_object (value))); break; - case PROP_INPUT_PURPOSE: - gtk_entry_set_input_purpose (entry, g_value_get_enum (value)); - break; - - case PROP_INPUT_HINTS: - gtk_entry_set_input_hints (entry, g_value_get_flags (value)); - break; - - case PROP_ATTRIBUTES: - gtk_entry_set_attributes (entry, g_value_get_boxed (value)); - break; - - case PROP_POPULATE_ALL: - if (priv->populate_all != g_value_get_boolean (value)) - { - priv->populate_all = g_value_get_boolean (value); - g_object_notify_by_pspec (object, pspec); - } - break; - - case PROP_TABS: - gtk_entry_set_tabs (entry, g_value_get_boxed (value)); - break; - case PROP_SHOW_EMOJI_ICON: set_show_emoji_icon (entry, g_value_get_boolean (value)); break; - case PROP_ENABLE_EMOJI_COMPLETION: - set_enable_emoji_completion (entry, g_value_get_boolean (value)); - break; - - case PROP_SCROLL_OFFSET: - case PROP_CURSOR_POSITION: default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -2194,88 +1083,39 @@ gtk_entry_get_property (GObject *object, GtkEntry *entry = GTK_ENTRY (object); GtkEntryPrivate *priv = gtk_entry_get_instance_private (entry); + if (gtk_editable_delegate_get_property (object, prop_id, value, pspec)) + return; + switch (prop_id) { case PROP_BUFFER: - g_value_set_object (value, gtk_entry_get_buffer (entry)); - break; - - case PROP_CURSOR_POSITION: - g_value_set_int (value, priv->current_pos); - break; - - case PROP_SELECTION_BOUND: - g_value_set_int (value, priv->selection_bound); - break; - - case PROP_EDITABLE: - g_value_set_boolean (value, priv->editable); - break; - + case PROP_IM_MODULE: case PROP_MAX_LENGTH: - g_value_set_int (value, gtk_entry_buffer_get_max_length (get_buffer (entry))); - break; - case PROP_VISIBILITY: - g_value_set_boolean (value, priv->visible); + case PROP_INVISIBLE_CHAR: + case PROP_INVISIBLE_CHAR_SET: + case PROP_ACTIVATES_DEFAULT: + case PROP_SCROLL_OFFSET: + case PROP_TRUNCATE_MULTILINE: + case PROP_OVERWRITE_MODE: + case PROP_PLACEHOLDER_TEXT: + case PROP_INPUT_PURPOSE: + case PROP_INPUT_HINTS: + case PROP_ATTRIBUTES: + case PROP_POPULATE_ALL: + case PROP_TABS: + case PROP_ENABLE_EMOJI_COMPLETION: + g_object_get_property (G_OBJECT (priv->text), pspec->name, value); break; case PROP_HAS_FRAME: g_value_set_boolean (value, gtk_entry_get_has_frame (entry)); break; - case PROP_INVISIBLE_CHAR: - g_value_set_uint (value, priv->invisible_char); - break; - - case PROP_ACTIVATES_DEFAULT: - g_value_set_boolean (value, priv->activates_default); - break; - - case PROP_WIDTH_CHARS: - g_value_set_int (value, priv->width_chars); - break; - - case PROP_MAX_WIDTH_CHARS: - g_value_set_int (value, priv->max_width_chars); - break; - - case PROP_SCROLL_OFFSET: - g_value_set_int (value, priv->scroll_offset); - break; - - case PROP_TEXT: - g_value_set_string (value, gtk_entry_get_text (entry)); - break; - - case PROP_XALIGN: - g_value_set_float (value, gtk_entry_get_alignment (entry)); - break; - - case PROP_TRUNCATE_MULTILINE: - g_value_set_boolean (value, priv->truncate_multiline); - break; - - case PROP_OVERWRITE_MODE: - g_value_set_boolean (value, priv->overwrite_mode); - break; - case PROP_TEXT_LENGTH: g_value_set_uint (value, gtk_entry_buffer_get_length (get_buffer (entry))); break; - case PROP_INVISIBLE_CHAR_SET: - g_value_set_boolean (value, priv->invisible_char_set); - break; - - case PROP_IM_MODULE: - g_value_set_string (value, priv->im_module); - break; - - case PROP_CAPS_LOCK_WARNING: - g_value_set_boolean (value, priv->caps_lock_warning); - break; - case PROP_PROGRESS_FRACTION: g_value_set_double (value, gtk_entry_get_progress_fraction (entry)); break; @@ -2284,10 +1124,6 @@ gtk_entry_get_property (GObject *object, g_value_set_double (value, gtk_entry_get_progress_pulse_step (entry)); break; - case PROP_PLACEHOLDER_TEXT: - g_value_set_string (value, gtk_entry_get_placeholder_text (entry)); - break; - case PROP_PAINTABLE_PRIMARY: g_value_set_object (value, gtk_entry_get_icon_paintable (entry, @@ -2385,34 +1221,10 @@ gtk_entry_get_property (GObject *object, g_value_set_object (value, G_OBJECT (gtk_entry_get_completion (entry))); break; - case PROP_INPUT_PURPOSE: - g_value_set_enum (value, gtk_entry_get_input_purpose (entry)); - break; - - case PROP_INPUT_HINTS: - g_value_set_flags (value, gtk_entry_get_input_hints (entry)); - break; - - case PROP_ATTRIBUTES: - g_value_set_boxed (value, priv->attrs); - break; - - case PROP_POPULATE_ALL: - g_value_set_boolean (value, priv->populate_all); - break; - - case PROP_TABS: - g_value_set_boxed (value, priv->tabs); - break; - case PROP_SHOW_EMOJI_ICON: g_value_set_boolean (value, priv->show_emoji_icon); break; - case PROP_ENABLE_EMOJI_COMPLETION: - g_value_set_boolean (value, priv->enable_emoji_completion); - break; - default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -2420,296 +1232,46 @@ gtk_entry_get_property (GObject *object, } static void -set_text_cursor (GtkWidget *widget) +activate_cb (GtkText *text, GtkEntry *entry) { - gtk_widget_set_cursor_from_name (widget, "text"); + g_signal_emit (entry, signals[ACTIVATE], 0); } static void -entry_motion_cb (GtkEventControllerMotion *event_controller, - double x, - double y, - gpointer user_data) +notify_cb (GObject *object, + GParamSpec *pspec, + gpointer data) +{ + gpointer iface; + + /* The editable interface properties are already forwarded by the editable delegate setup */ + iface = g_type_interface_peek (g_type_class_peek (G_OBJECT_TYPE (object)), gtk_editable_get_type ()); + if (!g_object_interface_find_property (iface, pspec->name)) + g_object_notify (data, pspec->name); +} + +static void +connect_text_signals (GtkEntry *entry) { - GtkEntry *entry = user_data; GtkEntryPrivate *priv = gtk_entry_get_instance_private (entry); - if (priv->mouse_cursor_obscured) - { - set_text_cursor (GTK_WIDGET (entry)); - priv->mouse_cursor_obscured = FALSE; - } -} - -static gunichar -find_invisible_char (GtkWidget *widget) -{ - PangoLayout *layout; - PangoAttrList *attr_list; - gint i; - gunichar invisible_chars [] = { - 0x25cf, /* BLACK CIRCLE */ - 0x2022, /* BULLET */ - 0x2731, /* HEAVY ASTERISK */ - 0x273a /* SIXTEEN POINTED ASTERISK */ - }; - - layout = gtk_widget_create_pango_layout (widget, NULL); - - attr_list = pango_attr_list_new (); - pango_attr_list_insert (attr_list, pango_attr_fallback_new (FALSE)); - - pango_layout_set_attributes (layout, attr_list); - pango_attr_list_unref (attr_list); - - for (i = 0; i < G_N_ELEMENTS (invisible_chars); i++) - { - gchar text[7] = { 0, }; - gint len, count; - - len = g_unichar_to_utf8 (invisible_chars[i], text); - pango_layout_set_text (layout, text, len); - - count = pango_layout_get_unknown_glyphs_count (layout); - - if (count == 0) - { - g_object_unref (layout); - return invisible_chars[i]; - } - } - - g_object_unref (layout); - - return '*'; -} - -static void -gtk_entry_schedule_im_reset (GtkEntry *entry) -{ - GtkEntryPrivate *priv; - - priv = gtk_entry_get_instance_private (entry); - - priv->need_im_reset = TRUE; + g_signal_connect (priv->text, "activate", G_CALLBACK (activate_cb), entry); + g_signal_connect (priv->text, "notify", G_CALLBACK (notify_cb), entry); } static void gtk_entry_init (GtkEntry *entry) { GtkEntryPrivate *priv = gtk_entry_get_instance_private (entry); - GtkCssNode *widget_node; - GtkGesture *gesture; - GtkEventController *controller; - gint i; - gtk_widget_set_can_focus (GTK_WIDGET (entry), TRUE); gtk_widget_set_has_surface (GTK_WIDGET (entry), FALSE); - priv->editable = TRUE; - priv->visible = TRUE; - priv->dnd_position = -1; - priv->width_chars = -1; - priv->max_width_chars = -1; + priv->text = gtk_text_new (); + gtk_widget_set_parent (priv->text, GTK_WIDGET (entry)); + gtk_editable_init_delegate (GTK_EDITABLE (entry)); + connect_text_signals (entry); + priv->editing_canceled = FALSE; - priv->truncate_multiline = FALSE; - priv->xalign = 0.0; - priv->caps_lock_warning = TRUE; - priv->caps_lock_warning_shown = FALSE; - priv->insert_pos = -1; - - priv->selection_content = g_object_new (GTK_TYPE_ENTRY_CONTENT, NULL); - GTK_ENTRY_CONTENT (priv->selection_content)->entry = entry; - - gtk_drag_dest_set (GTK_WIDGET (entry), 0, NULL, - GDK_ACTION_COPY | GDK_ACTION_MOVE); - gtk_drag_dest_add_text_targets (GTK_WIDGET (entry)); - - /* This object is completely private. No external entity can gain a reference - * to it; so we create it here and destroy it in finalize(). - */ - priv->im_context = gtk_im_multicontext_new (); - - g_signal_connect (priv->im_context, "commit", - G_CALLBACK (gtk_entry_commit_cb), entry); - g_signal_connect (priv->im_context, "preedit-changed", - G_CALLBACK (gtk_entry_preedit_changed_cb), entry); - g_signal_connect (priv->im_context, "retrieve-surrounding", - G_CALLBACK (gtk_entry_retrieve_surrounding_cb), entry); - g_signal_connect (priv->im_context, "delete-surrounding", - G_CALLBACK (gtk_entry_delete_surrounding_cb), entry); - - gtk_entry_update_cached_style_values (entry); - - priv->drag_gesture = gtk_gesture_drag_new (); - g_signal_connect (priv->drag_gesture, "drag-update", - G_CALLBACK (gtk_entry_drag_gesture_update), entry); - g_signal_connect (priv->drag_gesture, "drag-end", - G_CALLBACK (gtk_entry_drag_gesture_end), entry); - gtk_gesture_single_set_button (GTK_GESTURE_SINGLE (priv->drag_gesture), 0); - gtk_gesture_single_set_exclusive (GTK_GESTURE_SINGLE (priv->drag_gesture), TRUE); - gtk_widget_add_controller (GTK_WIDGET (entry), GTK_EVENT_CONTROLLER (priv->drag_gesture)); - - gesture = gtk_gesture_multi_press_new (); - g_signal_connect (gesture, "pressed", - G_CALLBACK (gtk_entry_multipress_gesture_pressed), entry); - gtk_gesture_single_set_button (GTK_GESTURE_SINGLE (gesture), 0); - gtk_gesture_single_set_exclusive (GTK_GESTURE_SINGLE (gesture), TRUE); - gtk_widget_add_controller (GTK_WIDGET (entry), GTK_EVENT_CONTROLLER (gesture)); - - controller = gtk_event_controller_motion_new (); - g_signal_connect (controller, "motion", - G_CALLBACK (entry_motion_cb), entry); - gtk_widget_add_controller (GTK_WIDGET (entry), controller); - - priv->key_controller = gtk_event_controller_key_new (); - g_signal_connect (priv->key_controller, "key-pressed", - G_CALLBACK (gtk_entry_key_controller_key_pressed), entry); - g_signal_connect_swapped (priv->key_controller, "im-update", - G_CALLBACK (gtk_entry_schedule_im_reset), entry); - g_signal_connect_swapped (priv->key_controller, "focus-in", - G_CALLBACK (gtk_entry_focus_in), entry); - g_signal_connect_swapped (priv->key_controller, "focus-out", - G_CALLBACK (gtk_entry_focus_out), entry); - gtk_event_controller_key_set_im_context (GTK_EVENT_CONTROLLER_KEY (priv->key_controller), - priv->im_context); - gtk_widget_add_controller (GTK_WIDGET (entry), priv->key_controller); - - widget_node = gtk_widget_get_css_node (GTK_WIDGET (entry)); - for (i = 0; i < 2; i++) - { - priv->undershoot_node[i] = gtk_css_node_new (); - gtk_css_node_set_name (priv->undershoot_node[i], I_("undershoot")); - gtk_css_node_add_class (priv->undershoot_node[i], g_quark_from_static_string (i == 0 ? GTK_STYLE_CLASS_LEFT : GTK_STYLE_CLASS_RIGHT)); - gtk_css_node_set_parent (priv->undershoot_node[i], widget_node); - gtk_css_node_set_state (priv->undershoot_node[i], gtk_css_node_get_state (widget_node) & ~GTK_STATE_FLAG_DROP_ACTIVE); - g_object_unref (priv->undershoot_node[i]); - } - - set_text_cursor (GTK_WIDGET (entry)); -} - -static void -gtk_entry_ensure_magnifier (GtkEntry *entry) -{ - GtkEntryPrivate *priv = gtk_entry_get_instance_private (entry); - - if (priv->magnifier_popover) - return; - - priv->magnifier = _gtk_magnifier_new (GTK_WIDGET (entry)); - gtk_widget_set_size_request (priv->magnifier, 100, 60); - _gtk_magnifier_set_magnification (GTK_MAGNIFIER (priv->magnifier), 2.0); - priv->magnifier_popover = gtk_popover_new (GTK_WIDGET (entry)); - gtk_style_context_add_class (gtk_widget_get_style_context (priv->magnifier_popover), - "magnifier"); - gtk_popover_set_modal (GTK_POPOVER (priv->magnifier_popover), FALSE); - gtk_container_add (GTK_CONTAINER (priv->magnifier_popover), - priv->magnifier); - gtk_widget_show (priv->magnifier); -} - -static void -gtk_entry_ensure_text_handles (GtkEntry *entry) -{ - GtkEntryPrivate *priv = gtk_entry_get_instance_private (entry); - - if (priv->text_handle) - return; - - priv->text_handle = _gtk_text_handle_new (GTK_WIDGET (entry)); - g_signal_connect (priv->text_handle, "drag-started", - G_CALLBACK (gtk_entry_handle_drag_started), entry); - g_signal_connect (priv->text_handle, "handle-dragged", - G_CALLBACK (gtk_entry_handle_dragged), entry); - g_signal_connect (priv->text_handle, "drag-finished", - G_CALLBACK (gtk_entry_handle_drag_finished), entry); -} - -static gint -get_icon_width (GtkEntry *entry, - GtkEntryIconPosition icon_pos) -{ - GtkEntryPrivate *priv = gtk_entry_get_instance_private (entry); - EntryIconInfo *icon_info = priv->icons[icon_pos]; - gint width; - - if (!icon_info) - return 0; - - gtk_widget_measure (icon_info->widget, - GTK_ORIENTATION_HORIZONTAL, - -1, - &width, NULL, - NULL, NULL); - - return width; -} - -static void -begin_change (GtkEntry *entry) -{ - GtkEntryPrivate *priv = gtk_entry_get_instance_private (entry); - - priv->change_count++; - - g_object_freeze_notify (G_OBJECT (entry)); -} - -static void -end_change (GtkEntry *entry) -{ - GtkEditable *editable = GTK_EDITABLE (entry); - GtkEntryPrivate *priv = gtk_entry_get_instance_private (entry); - - g_return_if_fail (priv->change_count > 0); - - g_object_thaw_notify (G_OBJECT (entry)); - - priv->change_count--; - - if (priv->change_count == 0) - { - if (priv->real_changed) - { - g_signal_emit_by_name (editable, "changed"); - priv->real_changed = FALSE; - } - } -} - -static void -emit_changed (GtkEntry *entry) -{ - GtkEditable *editable = GTK_EDITABLE (entry); - GtkEntryPrivate *priv = gtk_entry_get_instance_private (entry); - - if (priv->change_count == 0) - g_signal_emit_by_name (editable, "changed"); - else - priv->real_changed = TRUE; -} - -static void -gtk_entry_destroy (GtkWidget *widget) -{ - GtkEntry *entry = GTK_ENTRY (widget); - GtkEntryPrivate *priv = gtk_entry_get_instance_private (entry); - - priv->current_pos = priv->selection_bound = 0; - gtk_entry_reset_im_context (entry); - gtk_entry_reset_layout (entry); - - if (priv->blink_timeout) - { - g_source_remove (priv->blink_timeout); - priv->blink_timeout = 0; - } - - if (priv->magnifier) - _gtk_magnifier_set_inspected (GTK_MAGNIFIER (priv->magnifier), NULL); - - GTK_WIDGET_CLASS (gtk_entry_parent_class)->destroy (widget); } static void @@ -2717,26 +1279,18 @@ gtk_entry_dispose (GObject *object) { GtkEntry *entry = GTK_ENTRY (object); GtkEntryPrivate *priv = gtk_entry_get_instance_private (entry); - GdkKeymap *keymap; + + gtk_entry_set_completion (entry, NULL); + + if (priv->text) + gtk_editable_finish_delegate (GTK_EDITABLE (entry)); + g_clear_pointer (&priv->text, gtk_widget_unparent); gtk_entry_set_icon_from_paintable (entry, GTK_ENTRY_ICON_PRIMARY, NULL); gtk_entry_set_icon_tooltip_markup (entry, GTK_ENTRY_ICON_PRIMARY, NULL); gtk_entry_set_icon_from_paintable (entry, GTK_ENTRY_ICON_SECONDARY, NULL); gtk_entry_set_icon_tooltip_markup (entry, GTK_ENTRY_ICON_SECONDARY, NULL); - gtk_entry_set_completion (entry, NULL); - priv->current_pos = 0; - - if (priv->buffer) - { - buffer_disconnect_signals (entry); - g_object_unref (priv->buffer); - priv->buffer = NULL; - } - - keymap = gdk_display_get_keymap (gtk_widget_get_display (GTK_WIDGET (object))); - g_signal_handlers_disconnect_by_func (keymap, keymap_state_changed, entry); - g_signal_handlers_disconnect_by_func (keymap, keymap_direction_changed, entry); G_OBJECT_CLASS (gtk_entry_parent_class)->dispose (object); } @@ -2748,8 +1302,6 @@ gtk_entry_finalize (GObject *object) EntryIconInfo *icon_info = NULL; gint i; - g_clear_object (&priv->selection_content); - for (i = 0; i < MAX_ICONS; i++) { icon_info = priv->icons[i]; @@ -2764,111 +1316,11 @@ gtk_entry_finalize (GObject *object) g_slice_free (EntryIconInfo, icon_info); } - g_clear_object (&priv->cached_layout); - g_clear_object (&priv->im_context); - g_clear_pointer (&priv->selection_bubble, gtk_widget_destroy); - g_clear_pointer (&priv->magnifier_popover, gtk_widget_destroy); g_clear_pointer (&priv->progress_widget, gtk_widget_unparent); - g_clear_object (&priv->text_handle); - g_free (priv->im_module); - - g_clear_pointer (&priv->placeholder, gtk_widget_unparent); - - if (priv->blink_timeout) - g_source_remove (priv->blink_timeout); - - if (priv->tabs) - pango_tab_array_free (priv->tabs); - - if (priv->attrs) - pango_attr_list_unref (priv->attrs); - G_OBJECT_CLASS (gtk_entry_parent_class)->finalize (object); } -static DisplayMode -gtk_entry_get_display_mode (GtkEntry *entry) -{ - GtkEntryPrivate *priv = gtk_entry_get_instance_private (entry); - - if (priv->visible) - return DISPLAY_NORMAL; - - if (priv->invisible_char == 0 && priv->invisible_char_set) - return DISPLAY_BLANK; - - return DISPLAY_INVISIBLE; -} - -gchar* -_gtk_entry_get_display_text (GtkEntry *entry, - gint start_pos, - gint end_pos) -{ - GtkEntryPasswordHint *password_hint; - GtkEntryPrivate *priv = gtk_entry_get_instance_private (entry); - gunichar invisible_char; - const gchar *start; - const gchar *end; - const gchar *text; - gchar char_str[7]; - gint char_len; - GString *str; - guint length; - gint i; - - text = gtk_entry_buffer_get_text (get_buffer (entry)); - length = gtk_entry_buffer_get_length (get_buffer (entry)); - - if (end_pos < 0 || end_pos > length) - end_pos = length; - if (start_pos > length) - start_pos = length; - - if (end_pos <= start_pos) - return g_strdup (""); - else if (priv->visible) - { - start = g_utf8_offset_to_pointer (text, start_pos); - end = g_utf8_offset_to_pointer (start, end_pos - start_pos); - return g_strndup (start, end - start); - } - else - { - str = g_string_sized_new (length * 2); - - /* Figure out what our invisible char is and encode it */ - if (!priv->invisible_char) - invisible_char = priv->invisible_char_set ? ' ' : '*'; - else - invisible_char = priv->invisible_char; - char_len = g_unichar_to_utf8 (invisible_char, char_str); - - /* - * Add hidden characters for each character in the text - * buffer. If there is a password hint, then keep that - * character visible. - */ - - password_hint = g_object_get_qdata (G_OBJECT (entry), quark_password_hint); - for (i = start_pos; i < end_pos; ++i) - { - if (password_hint && i == password_hint->position) - { - start = g_utf8_offset_to_pointer (text, i); - g_string_append_len (str, start, g_utf8_next_char (start) - start); - } - else - { - g_string_append_len (str, char_str, char_len); - } - } - - return g_string_free (str, FALSE); - } -} - static void update_icon_style (GtkWidget *widget, GtkEntryIconPosition icon_pos) @@ -2890,25 +1342,6 @@ update_icon_style (GtkWidget *widget, gtk_style_context_remove_class (context, sides[1 - icon_pos]); } -static void -update_node_state (GtkEntry *entry) -{ - GtkEntryPrivate *priv = gtk_entry_get_instance_private (entry); - GtkStateFlags state; - - state = gtk_widget_get_state_flags (GTK_WIDGET (entry)); - state &= ~GTK_STATE_FLAG_DROP_ACTIVE; - - if (priv->selection_node) - gtk_css_node_set_state (priv->selection_node, state); - - if (priv->block_cursor_node) - gtk_css_node_set_state (priv->block_cursor_node, state); - - gtk_css_node_set_state (priv->undershoot_node[0], state); - gtk_css_node_set_state (priv->undershoot_node[1], state); -} - static void update_node_ordering (GtkEntry *entry) { @@ -3047,7 +1480,10 @@ construct_icon_info (GtkWidget *widget, icon_info->widget = gtk_image_new (); gtk_widget_set_cursor_from_name (icon_info->widget, "default"); - gtk_widget_insert_after (icon_info->widget, widget, priv->placeholder); + if (icon_pos == GTK_ENTRY_ICON_PRIMARY) + gtk_widget_insert_before (icon_info->widget, widget, priv->text); + else + gtk_widget_insert_after (icon_info->widget, widget, priv->text); update_icon_style (widget, icon_pos); update_node_ordering (entry); @@ -3069,69 +1505,6 @@ construct_icon_info (GtkWidget *widget, return icon_info; } -static void -gtk_entry_unmap (GtkWidget *widget) -{ - GtkEntry *entry = GTK_ENTRY (widget); - GtkEntryPrivate *priv = gtk_entry_get_instance_private (entry); - - if (priv->text_handle) - _gtk_text_handle_set_mode (priv->text_handle, - GTK_TEXT_HANDLE_MODE_NONE); - - GTK_WIDGET_CLASS (gtk_entry_parent_class)->unmap (widget); -} - -static void -gtk_entry_get_text_allocation (GtkEntry *entry, - GdkRectangle *allocation) -{ - GtkEntryPrivate *priv = gtk_entry_get_instance_private (entry); - - allocation->x = priv->text_x; - allocation->y = 0; - allocation->width = priv->text_width; - allocation->height = gtk_widget_get_height (GTK_WIDGET (entry)); -} - -static void -gtk_entry_realize (GtkWidget *widget) -{ - GtkEntry *entry = GTK_ENTRY (widget); - GtkEntryPrivate *priv = gtk_entry_get_instance_private (entry); - - GTK_WIDGET_CLASS (gtk_entry_parent_class)->realize (widget); - - gtk_im_context_set_client_widget (priv->im_context, widget); - - gtk_entry_adjust_scroll (entry); - gtk_entry_update_primary_selection (entry); -} - -static void -gtk_entry_unrealize (GtkWidget *widget) -{ - GtkEntry *entry = GTK_ENTRY (widget); - GtkEntryPrivate *priv = gtk_entry_get_instance_private (entry); - GdkClipboard *clipboard; - - gtk_entry_reset_layout (entry); - - gtk_im_context_set_client_widget (priv->im_context, NULL); - - clipboard = gtk_widget_get_primary_clipboard (widget); - if (gdk_clipboard_get_content (clipboard) == priv->selection_content) - gdk_clipboard_set_content (clipboard, NULL); - - if (priv->popup_menu) - { - gtk_widget_destroy (priv->popup_menu); - priv->popup_menu = NULL; - } - - GTK_WIDGET_CLASS (gtk_entry_parent_class)->unrealize (widget); -} - static void gtk_entry_measure (GtkWidget *widget, GtkOrientation orientation, @@ -3143,113 +1516,12 @@ gtk_entry_measure (GtkWidget *widget, { GtkEntry *entry = GTK_ENTRY (widget); GtkEntryPrivate *priv = gtk_entry_get_instance_private (entry); - PangoContext *context; - PangoFontMetrics *metrics; - context = gtk_widget_get_pango_context (widget); - metrics = pango_context_get_metrics (context, - pango_context_get_font_description (context), - pango_context_get_language (context)); - - if (orientation == GTK_ORIENTATION_HORIZONTAL) - { - gint icon_width, i; - gint min, nat; - gint char_width; - gint digit_width; - gint char_pixels; - - char_width = pango_font_metrics_get_approximate_char_width (metrics); - digit_width = pango_font_metrics_get_approximate_digit_width (metrics); - char_pixels = (MAX (char_width, digit_width) + PANGO_SCALE - 1) / PANGO_SCALE; - - if (priv->width_chars >= 0) - min = char_pixels * priv->width_chars; - else - min = 0; - - if (priv->max_width_chars < 0) - nat = NAT_ENTRY_WIDTH; - else - nat = char_pixels * priv->max_width_chars; - - icon_width = 0; - for (i = 0; i < MAX_ICONS; i++) - icon_width += get_icon_width (entry, i); - - min = MAX (min, icon_width); - nat = MAX (min, nat); - - if (priv->placeholder) - { - int pmin, pnat; - - gtk_widget_measure (priv->placeholder, GTK_ORIENTATION_HORIZONTAL, -1, - &pmin, &pnat, NULL, NULL); - min = MAX (min, pmin); - nat = MAX (nat, pnat); - } - - *minimum = min; - *natural = nat; - } - else - { - gint height, baseline; - gint icon_height, i; - PangoLayout *layout; - - layout = gtk_entry_ensure_layout (entry, TRUE); - - priv->ascent = pango_font_metrics_get_ascent (metrics); - priv->descent = pango_font_metrics_get_descent (metrics); - - pango_layout_get_pixel_size (layout, NULL, &height); - - height = MAX (height, PANGO_PIXELS (priv->ascent + priv->descent)); - - baseline = pango_layout_get_baseline (layout) / PANGO_SCALE; - - icon_height = 0; - for (i = 0; i < MAX_ICONS; i++) - { - EntryIconInfo *icon_info = priv->icons[i]; - gint h; - - if (!icon_info) - continue; - - gtk_widget_measure (icon_info->widget, - GTK_ORIENTATION_VERTICAL, - -1, - NULL, &h, - NULL, NULL); - icon_height = MAX (icon_height, h); - } - - *minimum = MAX (height, icon_height); - *natural = MAX (height, icon_height); - - if (icon_height > height) - baseline += (icon_height - height) / 2; - - if (priv->placeholder) - { - int min, nat; - - gtk_widget_measure (priv->placeholder, GTK_ORIENTATION_VERTICAL, -1, - &min, &nat, NULL, NULL); - *minimum = MAX (*minimum, min); - *natural = MAX (*natural, nat); - } - - if (minimum_baseline) - *minimum_baseline = baseline; - if (natural_baseline) - *natural_baseline = baseline; - } - - pango_font_metrics_unref (metrics); + gtk_widget_measure (priv->text, + orientation, + for_size, + minimum, natural, + minimum_baseline, natural_baseline); if (priv->progress_widget && gtk_widget_get_visible (priv->progress_widget)) { @@ -3275,10 +1547,12 @@ gtk_entry_size_allocate (GtkWidget *widget, GtkEntry *entry = GTK_ENTRY (widget); GtkEntryPrivate *priv = gtk_entry_get_instance_private (entry); gint i; + GtkAllocation text_alloc; - priv->text_baseline = baseline; - priv->text_x = 0; - priv->text_width = width; + text_alloc.x = 0; + text_alloc.y = 0; + text_alloc.width = width; + text_alloc.height = height; for (i = 0; i < MAX_ICONS; i++) { @@ -3297,22 +1571,22 @@ gtk_entry_size_allocate (GtkWidget *widget, if ((gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL && i == GTK_ENTRY_ICON_PRIMARY) || (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_LTR && i == GTK_ENTRY_ICON_SECONDARY)) - { - icon_alloc.x = priv->text_x + priv->text_width - icon_width; - } + icon_alloc.x = width - icon_width; else - { - icon_alloc.x = priv->text_x; - priv->text_x += icon_width; - } + icon_alloc.x = 0; icon_alloc.y = 0; icon_alloc.width = icon_width; icon_alloc.height = height; - priv->text_width -= icon_width; gtk_widget_size_allocate (icon_info->widget, &icon_alloc, baseline); + + text_alloc.width -= icon_width; + if (i == 0) + text_alloc.x += icon_width; } + gtk_widget_size_allocate (priv->text, &text_alloc, baseline); + if (priv->progress_widget && gtk_widget_get_visible (priv->progress_widget)) { GtkAllocation progress_alloc; @@ -3331,15 +1605,6 @@ gtk_entry_size_allocate (GtkWidget *widget, gtk_widget_size_allocate (priv->progress_widget, &progress_alloc, -1); } - if (priv->placeholder) - { - gtk_widget_size_allocate (priv->placeholder, - &(GtkAllocation) { - priv->text_x, 0, - priv->text_width, height - }, -1); - } - /* Do this here instead of gtk_entry_size_allocate() so it works * inside spinbuttons, which don't chain up. */ @@ -3347,69 +1612,12 @@ gtk_entry_size_allocate (GtkWidget *widget, { GtkEntryCompletion *completion; - gtk_entry_recompute (entry); - completion = gtk_entry_get_completion (entry); if (completion) _gtk_entry_completion_resize_popup (completion); } } -static void -gtk_entry_draw_undershoot (GtkEntry *entry, - GtkSnapshot *snapshot) -{ - GtkEntryPrivate *priv = gtk_entry_get_instance_private (entry); - GtkStyleContext *context; - gint min_offset, max_offset; - GdkRectangle rect; - gboolean rtl; - - context = gtk_widget_get_style_context (GTK_WIDGET (entry)); - rtl = gtk_widget_get_direction (GTK_WIDGET (entry)) == GTK_TEXT_DIR_RTL; - - gtk_entry_get_scroll_limits (entry, &min_offset, &max_offset); - - gtk_entry_get_text_allocation (entry, &rect); - - if (priv->scroll_offset > min_offset) - { - int icon_width = 0; - int icon_idx = rtl ? 1 : 0; - if (priv->icons[icon_idx] != NULL) - { - gtk_widget_measure (priv->icons[icon_idx]->widget, - GTK_ORIENTATION_HORIZONTAL, - -1, - &icon_width, NULL, - NULL, NULL); - } - - gtk_style_context_save_to_node (context, priv->undershoot_node[0]); - gtk_snapshot_render_background (snapshot, context, rect.x, rect.y, UNDERSHOOT_SIZE, rect.height); - gtk_snapshot_render_frame (snapshot, context, rect.x, rect.y, UNDERSHOOT_SIZE, rect.height); - gtk_style_context_restore (context); - } - - if (priv->scroll_offset < max_offset) - { - int icon_width = 0; - int icon_idx = rtl ? 0 : 1; - if (priv->icons[icon_idx] != NULL) - { - gtk_widget_measure (priv->icons[icon_idx]->widget, - GTK_ORIENTATION_HORIZONTAL, - -1, - &icon_width, NULL, - NULL, NULL); - } - gtk_style_context_save_to_node (context, priv->undershoot_node[1]); - gtk_snapshot_render_background (snapshot, context, rect.x + rect.width - UNDERSHOOT_SIZE, rect.y, UNDERSHOOT_SIZE, rect.height); - gtk_snapshot_render_frame (snapshot, context, rect.x + rect.width - UNDERSHOOT_SIZE, rect.y, UNDERSHOOT_SIZE, rect.height); - gtk_style_context_restore (context); - } -} - static void gtk_entry_snapshot (GtkWidget *widget, GtkSnapshot *snapshot) @@ -3422,29 +1630,7 @@ gtk_entry_snapshot (GtkWidget *widget, if (priv->progress_widget && gtk_widget_get_visible (priv->progress_widget)) gtk_widget_snapshot_child (widget, priv->progress_widget, snapshot); - gtk_snapshot_push_clip (snapshot, - &GRAPHENE_RECT_INIT ( - priv->text_x, - 0, - priv->text_width, - gtk_widget_get_height (widget))); - - /* Draw text and cursor */ - if (priv->dnd_position != -1) - gtk_entry_draw_cursor (GTK_ENTRY (widget), snapshot, CURSOR_DND); - - if (priv->placeholder) - gtk_widget_snapshot_child (widget, priv->placeholder, snapshot); - - gtk_entry_draw_text (GTK_ENTRY (widget), snapshot); - - /* When no text is being displayed at all, don't show the cursor */ - if (gtk_entry_get_display_mode (entry) != DISPLAY_BLANK && - gtk_widget_has_focus (widget) && - priv->selection_bound == priv->current_pos && priv->cursor_visible) - gtk_entry_draw_cursor (GTK_ENTRY (widget), snapshot, CURSOR_STANDARD); - - gtk_snapshot_pop (snapshot); + gtk_widget_snapshot_child (widget, priv->text, snapshot); /* Draw icons */ for (i = 0; i < MAX_ICONS; i++) @@ -3454,733 +1640,6 @@ gtk_entry_snapshot (GtkWidget *widget, if (icon_info != NULL) gtk_widget_snapshot_child (widget, icon_info->widget, snapshot); } - - gtk_entry_draw_undershoot (entry, snapshot); -} - -static void -gtk_entry_get_pixel_ranges (GtkEntry *entry, - gint **ranges, - gint *n_ranges) -{ - gint start_char, end_char; - - if (gtk_editable_get_selection_bounds (GTK_EDITABLE (entry), &start_char, &end_char)) - { - PangoLayout *layout = gtk_entry_ensure_layout (entry, TRUE); - PangoLayoutLine *line = pango_layout_get_lines_readonly (layout)->data; - const char *text = pango_layout_get_text (layout); - gint start_index = g_utf8_offset_to_pointer (text, start_char) - text; - gint end_index = g_utf8_offset_to_pointer (text, end_char) - text; - gint real_n_ranges, i; - - pango_layout_line_get_x_ranges (line, start_index, end_index, ranges, &real_n_ranges); - - if (ranges) - { - gint *r = *ranges; - - for (i = 0; i < real_n_ranges; ++i) - { - r[2 * i + 1] = (r[2 * i + 1] - r[2 * i]) / PANGO_SCALE; - r[2 * i] = r[2 * i] / PANGO_SCALE; - } - } - - if (n_ranges) - *n_ranges = real_n_ranges; - } - else - { - if (n_ranges) - *n_ranges = 0; - if (ranges) - *ranges = NULL; - } -} - -static gboolean -in_selection (GtkEntry *entry, - gint x) -{ - gint *ranges; - gint n_ranges, i; - gint retval = FALSE; - - gtk_entry_get_pixel_ranges (entry, &ranges, &n_ranges); - - for (i = 0; i < n_ranges; ++i) - { - if (x >= ranges[2 * i] && x < ranges[2 * i] + ranges[2 * i + 1]) - { - retval = TRUE; - break; - } - } - - g_free (ranges); - return retval; -} - -static void -gtk_entry_move_handle (GtkEntry *entry, - GtkTextHandlePosition pos, - gint x, - gint y, - gint height) -{ - GtkEntryPrivate *priv = gtk_entry_get_instance_private (entry); - GtkAllocation text_allocation; - - gtk_entry_get_text_allocation (entry, &text_allocation); - - if (!_gtk_text_handle_get_is_dragged (priv->text_handle, pos) && - (x < 0 || x > text_allocation.width)) - { - /* Hide the handle if it's not being manipulated - * and fell outside of the visible text area. - */ - _gtk_text_handle_set_visible (priv->text_handle, pos, FALSE); - } - else - { - GdkRectangle rect; - - rect.x = x + text_allocation.x; - rect.y = y + text_allocation.y; - rect.width = 1; - rect.height = height; - - _gtk_text_handle_set_visible (priv->text_handle, pos, TRUE); - _gtk_text_handle_set_position (priv->text_handle, pos, &rect); - _gtk_text_handle_set_direction (priv->text_handle, pos, priv->resolved_dir); - } -} - -static gint -gtk_entry_get_selection_bound_location (GtkEntry *entry) -{ - GtkEntryPrivate *priv = gtk_entry_get_instance_private (entry); - PangoLayout *layout; - PangoRectangle pos; - gint x; - const gchar *text; - gint index; - - layout = gtk_entry_ensure_layout (entry, FALSE); - text = pango_layout_get_text (layout); - index = g_utf8_offset_to_pointer (text, priv->selection_bound) - text; - pango_layout_index_to_pos (layout, index, &pos); - - if (gtk_widget_get_direction (GTK_WIDGET (entry)) == GTK_TEXT_DIR_RTL) - x = (pos.x + pos.width) / PANGO_SCALE; - else - x = pos.x / PANGO_SCALE; - - return x; -} - -static void -gtk_entry_update_handles (GtkEntry *entry, - GtkTextHandleMode mode) -{ - GtkEntryPrivate *priv = gtk_entry_get_instance_private (entry); - GtkAllocation text_allocation; - gint strong_x; - gint cursor, bound; - - _gtk_text_handle_set_mode (priv->text_handle, mode); - gtk_entry_get_text_allocation (entry, &text_allocation); - - gtk_entry_get_cursor_locations (entry, &strong_x, NULL); - cursor = strong_x - priv->scroll_offset; - - if (mode == GTK_TEXT_HANDLE_MODE_SELECTION) - { - gint start, end; - - bound = gtk_entry_get_selection_bound_location (entry) - priv->scroll_offset; - - if (priv->selection_bound > priv->current_pos) - { - start = cursor; - end = bound; - } - else - { - start = bound; - end = cursor; - } - - /* Update start selection bound */ - gtk_entry_move_handle (entry, GTK_TEXT_HANDLE_POSITION_SELECTION_START, - start, 0, text_allocation.height); - gtk_entry_move_handle (entry, GTK_TEXT_HANDLE_POSITION_SELECTION_END, - end, 0, text_allocation.height); - } - else - gtk_entry_move_handle (entry, GTK_TEXT_HANDLE_POSITION_CURSOR, - cursor, 0, text_allocation.height); -} - -static void -gesture_get_current_point_in_layout (GtkGestureSingle *gesture, - GtkEntry *entry, - gint *x, - gint *y) -{ - gint tx, ty; - GdkEventSequence *sequence; - gdouble px, py; - - sequence = gtk_gesture_single_get_current_sequence (gesture); - gtk_gesture_get_point (GTK_GESTURE (gesture), sequence, &px, &py); - gtk_entry_get_layout_offsets (entry, &tx, &ty); - - if (x) - *x = px - tx; - if (y) - *y = py - ty; -} - -static void -gtk_entry_multipress_gesture_pressed (GtkGestureMultiPress *gesture, - gint n_press, - gdouble widget_x, - gdouble widget_y, - GtkEntry *entry) -{ - GtkEditable *editable = GTK_EDITABLE (entry); - GtkWidget *widget = GTK_WIDGET (entry); - GtkEntryPrivate *priv = gtk_entry_get_instance_private (entry); - GdkEventSequence *current; - const GdkEvent *event; - gint x, y, sel_start, sel_end; - guint button; - gint tmp_pos; - - button = gtk_gesture_single_get_current_button (GTK_GESTURE_SINGLE (gesture)); - current = gtk_gesture_single_get_current_sequence (GTK_GESTURE_SINGLE (gesture)); - event = gtk_gesture_get_last_event (GTK_GESTURE (gesture), current); - - gtk_gesture_set_sequence_state (GTK_GESTURE (gesture), current, - GTK_EVENT_SEQUENCE_CLAIMED); - gesture_get_current_point_in_layout (GTK_GESTURE_SINGLE (gesture), entry, &x, &y); - gtk_entry_reset_blink_time (entry); - - if (!gtk_widget_has_focus (widget)) - { - priv->in_click = TRUE; - gtk_widget_grab_focus (widget); - priv->in_click = FALSE; - } - - tmp_pos = gtk_entry_find_position (entry, x); - - if (gdk_event_triggers_context_menu (event)) - { - gtk_entry_do_popup (entry, event); - } - else if (n_press == 1 && button == GDK_BUTTON_MIDDLE && - get_middle_click_paste (entry)) - { - if (priv->editable) - { - priv->insert_pos = tmp_pos; - gtk_entry_paste (entry, gtk_widget_get_primary_clipboard (widget)); - } - else - { - gtk_widget_error_bell (widget); - } - } - else if (button == GDK_BUTTON_PRIMARY) - { - gboolean have_selection = gtk_editable_get_selection_bounds (editable, &sel_start, &sel_end); - GtkTextHandleMode mode; - gboolean is_touchscreen, extend_selection; - GdkDevice *source; - guint state; - - source = gdk_event_get_source_device (event); - is_touchscreen = gtk_simulate_touchscreen () || - gdk_device_get_source (source) == GDK_SOURCE_TOUCHSCREEN; - - if (!is_touchscreen) - mode = GTK_TEXT_HANDLE_MODE_NONE; - else if (have_selection) - mode = GTK_TEXT_HANDLE_MODE_SELECTION; - else - mode = GTK_TEXT_HANDLE_MODE_CURSOR; - - if (is_touchscreen) - gtk_entry_ensure_text_handles (entry); - - priv->in_drag = FALSE; - priv->select_words = FALSE; - priv->select_lines = FALSE; - - gdk_event_get_state (event, &state); - - extend_selection = - (state & - gtk_widget_get_modifier_mask (widget, - GDK_MODIFIER_INTENT_EXTEND_SELECTION)); - - if (extend_selection) - gtk_entry_reset_im_context (entry); - - switch (n_press) - { - case 1: - if (in_selection (entry, x)) - { - if (is_touchscreen) - { - if (priv->selection_bubble && - gtk_widget_get_visible (priv->selection_bubble)) - gtk_entry_selection_bubble_popup_unset (entry); - else - gtk_entry_selection_bubble_popup_set (entry); - } - else if (extend_selection) - { - /* Truncate current selection, but keep it as big as possible */ - if (tmp_pos - sel_start > sel_end - tmp_pos) - gtk_entry_set_positions (entry, sel_start, tmp_pos); - else - gtk_entry_set_positions (entry, tmp_pos, sel_end); - - /* all done, so skip the extend_to_left stuff later */ - extend_selection = FALSE; - } - else - { - /* We'll either start a drag, or clear the selection */ - priv->in_drag = TRUE; - priv->drag_start_x = x; - priv->drag_start_y = y; - } - } - else - { - gtk_entry_selection_bubble_popup_unset (entry); - - if (!extend_selection) - { - gtk_editable_set_position (editable, tmp_pos); - priv->handle_place_time = g_get_monotonic_time (); - } - else - { - /* select from the current position to the clicked position */ - if (!have_selection) - sel_start = sel_end = priv->current_pos; - - gtk_entry_set_positions (entry, tmp_pos, tmp_pos); - } - } - - break; - - case 2: - priv->select_words = TRUE; - gtk_entry_select_word (entry); - if (is_touchscreen) - mode = GTK_TEXT_HANDLE_MODE_SELECTION; - break; - - case 3: - priv->select_lines = TRUE; - gtk_entry_select_line (entry); - if (is_touchscreen) - mode = GTK_TEXT_HANDLE_MODE_SELECTION; - break; - - default: - break; - } - - if (extend_selection) - { - gboolean extend_to_left; - gint start, end; - - start = MIN (priv->current_pos, priv->selection_bound); - start = MIN (sel_start, start); - - end = MAX (priv->current_pos, priv->selection_bound); - end = MAX (sel_end, end); - - if (tmp_pos == sel_start || tmp_pos == sel_end) - extend_to_left = (tmp_pos == start); - else - extend_to_left = (end == sel_end); - - if (extend_to_left) - gtk_entry_set_positions (entry, start, end); - else - gtk_entry_set_positions (entry, end, start); - } - - gtk_gesture_set_state (priv->drag_gesture, - GTK_EVENT_SEQUENCE_CLAIMED); - - if (priv->text_handle) - gtk_entry_update_handles (entry, mode); - } - - if (n_press >= 3) - gtk_event_controller_reset (GTK_EVENT_CONTROLLER (gesture)); -} - -static gchar * -_gtk_entry_get_selected_text (GtkEntry *entry) -{ - GtkEditable *editable = GTK_EDITABLE (entry); - gint start_text, end_text; - gchar *text = NULL; - - if (gtk_editable_get_selection_bounds (editable, &start_text, &end_text)) - text = gtk_editable_get_chars (editable, start_text, end_text); - - return text; -} - -static void -gtk_entry_show_magnifier (GtkEntry *entry, - gint x, - gint y) -{ - GtkAllocation allocation; - cairo_rectangle_int_t rect; - GtkEntryPrivate *priv = gtk_entry_get_instance_private (entry); - GtkAllocation text_allocation; - - gtk_entry_get_text_allocation (entry, &text_allocation); - - gtk_entry_ensure_magnifier (entry); - - gtk_widget_get_allocation (GTK_WIDGET (entry), &allocation); - - rect.x = x + text_allocation.x; - rect.width = 1; - rect.y = text_allocation.y; - rect.height = text_allocation.height; - - _gtk_magnifier_set_coords (GTK_MAGNIFIER (priv->magnifier), rect.x, - rect.y + rect.height / 2); - gtk_popover_set_pointing_to (GTK_POPOVER (priv->magnifier_popover), - &rect); - gtk_popover_popup (GTK_POPOVER (priv->magnifier_popover)); -} - -static void -gtk_entry_drag_gesture_update (GtkGestureDrag *gesture, - gdouble offset_x, - gdouble offset_y, - GtkEntry *entry) -{ - GtkWidget *widget = GTK_WIDGET (entry); - GtkEntryPrivate *priv = gtk_entry_get_instance_private (entry); - GdkEventSequence *sequence; - const GdkEvent *event; - gint x, y; - - gtk_entry_selection_bubble_popup_unset (entry); - - gesture_get_current_point_in_layout (GTK_GESTURE_SINGLE (gesture), entry, &x, &y); - sequence = gtk_gesture_single_get_current_sequence (GTK_GESTURE_SINGLE (gesture)); - event = gtk_gesture_get_last_event (GTK_GESTURE (gesture), sequence); - - if (priv->mouse_cursor_obscured) - { - set_text_cursor (widget); - priv->mouse_cursor_obscured = FALSE; - } - - if (priv->select_lines) - return; - - if (priv->in_drag) - { - if (gtk_entry_get_display_mode (entry) == DISPLAY_NORMAL && - gtk_drag_check_threshold (widget, - priv->drag_start_x, priv->drag_start_y, - x, y)) - { - gint *ranges; - gint n_ranges; - GdkContentFormats *target_list = gdk_content_formats_new (NULL, 0); - guint actions = priv->editable ? GDK_ACTION_COPY | GDK_ACTION_MOVE : GDK_ACTION_COPY; - - target_list = gtk_content_formats_add_text_targets (target_list); - - gtk_entry_get_pixel_ranges (entry, &ranges, &n_ranges); - - gtk_drag_begin (widget, - gdk_event_get_device ((GdkEvent*) event), - target_list, actions, - priv->drag_start_x + ranges[0], - priv->drag_start_y); - g_free (ranges); - - priv->in_drag = FALSE; - - gdk_content_formats_unref (target_list); - } - } - else - { - GtkAllocation text_allocation; - GdkInputSource input_source; - GdkDevice *source; - guint length; - gint tmp_pos; - - length = gtk_entry_buffer_get_length (get_buffer (entry)); - gtk_entry_get_text_allocation (entry, &text_allocation); - - if (y < 0) - tmp_pos = 0; - else if (y >= text_allocation.height) - tmp_pos = length; - else - tmp_pos = gtk_entry_find_position (entry, x); - - source = gdk_event_get_source_device (event); - input_source = gdk_device_get_source (source); - - if (priv->select_words) - { - gint min, max; - gint old_min, old_max; - gint pos, bound; - - min = gtk_entry_move_backward_word (entry, tmp_pos, TRUE); - max = gtk_entry_move_forward_word (entry, tmp_pos, TRUE); - - pos = priv->current_pos; - bound = priv->selection_bound; - - old_min = MIN (priv->current_pos, priv->selection_bound); - old_max = MAX (priv->current_pos, priv->selection_bound); - - if (min < old_min) - { - pos = min; - bound = old_max; - } - else if (old_max < max) - { - pos = max; - bound = old_min; - } - else if (pos == old_min) - { - if (priv->current_pos != min) - pos = max; - } - else - { - if (priv->current_pos != max) - pos = min; - } - - gtk_entry_set_positions (entry, pos, bound); - } - else - gtk_entry_set_positions (entry, tmp_pos, -1); - - /* Update touch handles' position */ - if (gtk_simulate_touchscreen () || - input_source == GDK_SOURCE_TOUCHSCREEN) - { - gtk_entry_ensure_text_handles (entry); - gtk_entry_update_handles (entry, - (priv->current_pos == priv->selection_bound) ? - GTK_TEXT_HANDLE_MODE_CURSOR : - GTK_TEXT_HANDLE_MODE_SELECTION); - gtk_entry_show_magnifier (entry, x - priv->scroll_offset, y); - } - } -} - -static void -gtk_entry_drag_gesture_end (GtkGestureDrag *gesture, - gdouble offset_x, - gdouble offset_y, - GtkEntry *entry) -{ - GtkEntryPrivate *priv = gtk_entry_get_instance_private (entry); - gboolean in_drag, is_touchscreen; - GdkEventSequence *sequence; - const GdkEvent *event; - GdkDevice *source; - - sequence = gtk_gesture_single_get_current_sequence (GTK_GESTURE_SINGLE (gesture)); - in_drag = priv->in_drag; - priv->in_drag = FALSE; - - if (priv->magnifier_popover) - gtk_popover_popdown (GTK_POPOVER (priv->magnifier_popover)); - - /* Check whether the drag was cancelled rather than finished */ - if (!gtk_gesture_handles_sequence (GTK_GESTURE (gesture), sequence)) - return; - - event = gtk_gesture_get_last_event (GTK_GESTURE (gesture), sequence); - source = gdk_event_get_source_device (event); - is_touchscreen = gtk_simulate_touchscreen () || - gdk_device_get_source (source) == GDK_SOURCE_TOUCHSCREEN; - - if (in_drag) - { - gint tmp_pos = gtk_entry_find_position (entry, priv->drag_start_x); - - gtk_editable_set_position (GTK_EDITABLE (entry), tmp_pos); - } - - if (is_touchscreen && - !gtk_editable_get_selection_bounds (GTK_EDITABLE (entry), NULL, NULL)) - gtk_entry_update_handles (entry, GTK_TEXT_HANDLE_MODE_CURSOR); - - gtk_entry_update_primary_selection (entry); -} - -static void -gtk_entry_obscure_mouse_cursor (GtkEntry *entry) -{ - GtkEntryPrivate *priv = gtk_entry_get_instance_private (entry); - GdkCursor *cursor; - - if (priv->mouse_cursor_obscured) - return; - - cursor = gdk_cursor_new_from_name ("none", NULL); - gtk_widget_set_cursor (GTK_WIDGET (entry), cursor); - g_object_unref (cursor); - - priv->mouse_cursor_obscured = TRUE; -} - -static gboolean -gtk_entry_key_controller_key_pressed (GtkEventControllerKey *controller, - guint keyval, - guint keycode, - GdkModifierType state, - GtkWidget *widget) -{ - GtkEntry *entry = GTK_ENTRY (widget); - GtkEntryPrivate *priv = gtk_entry_get_instance_private (entry); - gunichar unichar; - - gtk_entry_reset_blink_time (entry); - gtk_entry_pend_cursor_blink (entry); - - gtk_entry_selection_bubble_popup_unset (entry); - - if (priv->text_handle) - _gtk_text_handle_set_mode (priv->text_handle, - GTK_TEXT_HANDLE_MODE_NONE); - - if (keyval == GDK_KEY_Return || - keyval == GDK_KEY_KP_Enter || - keyval == GDK_KEY_ISO_Enter || - keyval == GDK_KEY_Escape) - gtk_entry_reset_im_context (entry); - - unichar = gdk_keyval_to_unicode (keyval); - - if (!priv->editable && unichar != 0) - gtk_widget_error_bell (widget); - - gtk_entry_obscure_mouse_cursor (entry); - - return FALSE; -} - -static void -gtk_entry_focus_in (GtkWidget *widget) -{ - GtkEntry *entry = GTK_ENTRY (widget); - GtkEntryPrivate *priv = gtk_entry_get_instance_private (entry); - GdkKeymap *keymap; - - gtk_widget_queue_draw (widget); - - keymap = gdk_display_get_keymap (gtk_widget_get_display (widget)); - - if (priv->editable) - { - gtk_entry_schedule_im_reset (entry); - gtk_im_context_focus_in (priv->im_context); - keymap_state_changed (keymap, entry); - g_signal_connect (keymap, "state-changed", - G_CALLBACK (keymap_state_changed), entry); - } - - g_signal_connect (keymap, "direction-changed", - G_CALLBACK (keymap_direction_changed), entry); - - gtk_entry_reset_blink_time (entry); - gtk_entry_check_cursor_blink (entry); -} - -static void -gtk_entry_focus_out (GtkWidget *widget) -{ - GtkEntry *entry = GTK_ENTRY (widget); - GtkEntryPrivate *priv = gtk_entry_get_instance_private (entry); - GtkEntryCompletion *completion; - GdkKeymap *keymap; - - gtk_entry_selection_bubble_popup_unset (entry); - - if (priv->text_handle) - _gtk_text_handle_set_mode (priv->text_handle, - GTK_TEXT_HANDLE_MODE_NONE); - - gtk_widget_queue_draw (widget); - - keymap = gdk_display_get_keymap (gtk_widget_get_display (widget)); - - if (priv->editable) - { - gtk_entry_schedule_im_reset (entry); - gtk_im_context_focus_out (priv->im_context); - remove_capslock_feedback (entry); - } - - gtk_entry_check_cursor_blink (entry); - - g_signal_handlers_disconnect_by_func (keymap, keymap_state_changed, entry); - g_signal_handlers_disconnect_by_func (keymap, keymap_direction_changed, entry); - - completion = gtk_entry_get_completion (entry); - if (completion) - _gtk_entry_completion_popdown (completion); -} - -static void -gtk_entry_grab_focus (GtkWidget *widget) -{ - GtkEntry *entry = GTK_ENTRY (widget); - GtkEntryPrivate *priv = gtk_entry_get_instance_private (entry); - gboolean select_on_focus; - - GTK_WIDGET_CLASS (gtk_entry_parent_class)->grab_focus (GTK_WIDGET (entry)); - - if (priv->editable && !priv->in_click) - { - g_object_get (gtk_widget_get_settings (widget), - "gtk-entry-select-on-focus", - &select_on_focus, - NULL); - - if (select_on_focus) - gtk_editable_select_region (GTK_EDITABLE (entry), 0, -1); - } } /** @@ -4199,8 +1658,9 @@ void gtk_entry_grab_focus_without_selecting (GtkEntry *entry) { g_return_if_fail (GTK_IS_ENTRY (entry)); + GtkEntryPrivate *priv = gtk_entry_get_instance_private (entry); - GTK_WIDGET_CLASS (gtk_entry_parent_class)->grab_focus (GTK_WIDGET (entry)); + gtk_text_grab_focus_without_selecting (GTK_TEXT (priv->text)); } static void @@ -4209,8 +1669,6 @@ gtk_entry_direction_changed (GtkWidget *widget, { GtkEntry *entry = GTK_ENTRY (widget); - gtk_entry_recompute (entry); - update_icon_style (widget, GTK_ENTRY_ICON_PRIMARY); update_icon_style (widget, GTK_ENTRY_ICON_SECONDARY); @@ -4219,175 +1677,6 @@ gtk_entry_direction_changed (GtkWidget *widget, GTK_WIDGET_CLASS (gtk_entry_parent_class)->direction_changed (widget, previous_dir); } -static void -gtk_entry_state_flags_changed (GtkWidget *widget, - GtkStateFlags previous_state) -{ - GtkEntry *entry = GTK_ENTRY (widget); - GtkEntryPrivate *priv = gtk_entry_get_instance_private (entry); - - if (gtk_widget_get_realized (widget)) - { - set_text_cursor (widget); - priv->mouse_cursor_obscured = FALSE; - } - - if (!gtk_widget_is_sensitive (widget)) - { - /* Clear any selection */ - gtk_editable_select_region (GTK_EDITABLE (entry), priv->current_pos, priv->current_pos); - } - - update_node_state (entry); - - gtk_entry_update_cached_style_values (entry); -} - -static void -gtk_entry_display_changed (GtkWidget *widget, - GdkDisplay *old_display) -{ - gtk_entry_recompute (GTK_ENTRY (widget)); -} - -/* GtkEditable method implementations - */ -static void -gtk_entry_insert_text (GtkEditable *editable, - const gchar *new_text, - gint new_text_length, - gint *position) -{ - g_object_ref (editable); - - /* - * The incoming text may a password or other secret. We make sure - * not to copy it into temporary buffers. - */ - - g_signal_emit_by_name (editable, "insert-text", new_text, new_text_length, position); - - g_object_unref (editable); -} - -static void -gtk_entry_delete_text (GtkEditable *editable, - gint start_pos, - gint end_pos) -{ - g_object_ref (editable); - - g_signal_emit_by_name (editable, "delete-text", start_pos, end_pos); - - g_object_unref (editable); -} - -static const char * -gtk_entry_real_get_text (GtkEditable *editable) -{ - return gtk_entry_buffer_get_text (gtk_entry_get_buffer (GTK_ENTRY (editable))); -} - -static int -gtk_entry_get_length (GtkEditable *editable) -{ - return gtk_entry_buffer_get_length (gtk_entry_get_buffer (GTK_ENTRY (editable))); -} - -static void -gtk_entry_real_set_position (GtkEditable *editable, - gint position) -{ - GtkEntry *entry = GTK_ENTRY (editable); - GtkEntryPrivate *priv = gtk_entry_get_instance_private (entry); - - guint length; - - length = gtk_entry_buffer_get_length (get_buffer (entry)); - if (position < 0 || position > length) - position = length; - - if (position != priv->current_pos || - position != priv->selection_bound) - { - gtk_entry_reset_im_context (entry); - gtk_entry_set_positions (entry, position, position); - } -} - -static gint -gtk_entry_get_position (GtkEditable *editable) -{ - GtkEntry *entry = GTK_ENTRY (editable); - GtkEntryPrivate *priv = gtk_entry_get_instance_private (entry); - - return priv->current_pos; -} - -static void -gtk_entry_set_selection_bounds (GtkEditable *editable, - gint start, - gint end) -{ - GtkEntry *entry = GTK_ENTRY (editable); - guint length; - - length = gtk_entry_buffer_get_length (get_buffer (entry)); - if (start < 0) - start = length; - if (end < 0) - end = length; - - gtk_entry_reset_im_context (entry); - - gtk_entry_set_positions (entry, - MIN (end, length), - MIN (start, length)); - - gtk_entry_update_primary_selection (entry); -} - -static gboolean -gtk_entry_get_selection_bounds (GtkEditable *editable, - gint *start, - gint *end) -{ - GtkEntry *entry = GTK_ENTRY (editable); - GtkEntryPrivate *priv = gtk_entry_get_instance_private (entry); - - *start = priv->selection_bound; - *end = priv->current_pos; - - return (priv->selection_bound != priv->current_pos); -} - -static void -gtk_entry_update_cached_style_values (GtkEntry *entry) -{ - GtkEntryPrivate *priv = gtk_entry_get_instance_private (entry); - - if (!priv->invisible_char_set) - { - gunichar ch = find_invisible_char (GTK_WIDGET (entry)); - - if (priv->invisible_char != ch) - { - priv->invisible_char = ch; - g_object_notify_by_pspec (G_OBJECT (entry), entry_props[PROP_INVISIBLE_CHAR]); - } - } -} - -static void -gtk_entry_style_updated (GtkWidget *widget) -{ - GtkEntry *entry = GTK_ENTRY (widget); - - GTK_WIDGET_CLASS (gtk_entry_parent_class)->style_updated (widget); - - gtk_entry_update_cached_style_values (entry); -} - /* GtkCellEditable method implementations */ static void @@ -4439,1380 +1728,9 @@ gtk_entry_start_editing (GtkCellEditable *cell_editable, cell_editable); } -static void -gtk_entry_password_hint_free (GtkEntryPasswordHint *password_hint) -{ - if (password_hint->source_id) - g_source_remove (password_hint->source_id); - - g_slice_free (GtkEntryPasswordHint, password_hint); -} - - -static gboolean -gtk_entry_remove_password_hint (gpointer data) -{ - GtkEntryPasswordHint *password_hint = g_object_get_qdata (data, quark_password_hint); - password_hint->position = -1; - - /* Force the string to be redrawn, but now without a visible character */ - gtk_entry_recompute (GTK_ENTRY (data)); - return G_SOURCE_REMOVE; -} - -static void -update_placeholder_visibility (GtkEntry *entry) -{ - GtkEntryPrivate *priv = gtk_entry_get_instance_private (entry); - - if (priv->placeholder) - gtk_widget_set_child_visible (priv->placeholder, - gtk_entry_buffer_get_length (priv->buffer) == 0); -} - -/* Default signal handlers - */ -static void -gtk_entry_real_insert_text (GtkEditable *editable, - const gchar *new_text, - gint new_text_length, - gint *position) -{ - guint n_inserted; - gint n_chars; - - n_chars = g_utf8_strlen (new_text, new_text_length); - - /* - * The actual insertion into the buffer. This will end up firing the - * following signal handlers: buffer_inserted_text(), buffer_notify_display_text(), - * buffer_notify_text(), buffer_notify_length() - */ - begin_change (GTK_ENTRY (editable)); - - n_inserted = gtk_entry_buffer_insert_text (get_buffer (GTK_ENTRY (editable)), *position, new_text, n_chars); - - end_change (GTK_ENTRY (editable)); - - if (n_inserted != n_chars) - gtk_widget_error_bell (GTK_WIDGET (editable)); - - *position += n_inserted; - - update_placeholder_visibility (GTK_ENTRY (editable)); -} - -static void -gtk_entry_real_delete_text (GtkEditable *editable, - gint start_pos, - gint end_pos) -{ - /* - * The actual deletion from the buffer. This will end up firing the - * following signal handlers: buffer_deleted_text(), buffer_notify_display_text(), - * buffer_notify_text(), buffer_notify_length() - */ - - begin_change (GTK_ENTRY (editable)); - - gtk_entry_buffer_delete_text (get_buffer (GTK_ENTRY (editable)), start_pos, end_pos - start_pos); - - end_change (GTK_ENTRY (editable)); - update_placeholder_visibility (GTK_ENTRY (editable)); -} - -/* GtkEntryBuffer signal handlers - */ -static void -buffer_inserted_text (GtkEntryBuffer *buffer, - guint position, - const gchar *chars, - guint n_chars, - GtkEntry *entry) -{ - GtkEntryPrivate *priv = gtk_entry_get_instance_private (entry); - guint password_hint_timeout; - guint current_pos; - gint selection_bound; - - current_pos = priv->current_pos; - if (current_pos > position) - current_pos += n_chars; - - selection_bound = priv->selection_bound; - if (selection_bound > position) - selection_bound += n_chars; - - gtk_entry_set_positions (entry, current_pos, selection_bound); - gtk_entry_recompute (entry); - - /* Calculate the password hint if it needs to be displayed. */ - if (n_chars == 1 && !priv->visible) - { - g_object_get (gtk_widget_get_settings (GTK_WIDGET (entry)), - "gtk-entry-password-hint-timeout", &password_hint_timeout, - NULL); - - if (password_hint_timeout > 0) - { - GtkEntryPasswordHint *password_hint = g_object_get_qdata (G_OBJECT (entry), - quark_password_hint); - if (!password_hint) - { - password_hint = g_slice_new0 (GtkEntryPasswordHint); - g_object_set_qdata_full (G_OBJECT (entry), quark_password_hint, password_hint, - (GDestroyNotify)gtk_entry_password_hint_free); - } - - password_hint->position = position; - if (password_hint->source_id) - g_source_remove (password_hint->source_id); - password_hint->source_id = g_timeout_add (password_hint_timeout, - (GSourceFunc)gtk_entry_remove_password_hint, - entry); - g_source_set_name_by_id (password_hint->source_id, "[gtk] gtk_entry_remove_password_hint"); - } - } -} - -static void -buffer_deleted_text (GtkEntryBuffer *buffer, - guint position, - guint n_chars, - GtkEntry *entry) -{ - GtkEntryPrivate *priv = gtk_entry_get_instance_private (entry); - guint end_pos = position + n_chars; - gint selection_bound; - guint current_pos; - - current_pos = priv->current_pos; - if (current_pos > position) - current_pos -= MIN (current_pos, end_pos) - position; - - selection_bound = priv->selection_bound; - if (selection_bound > position) - selection_bound -= MIN (selection_bound, end_pos) - position; - - gtk_entry_set_positions (entry, current_pos, selection_bound); - gtk_entry_recompute (entry); - - /* We might have deleted the selection */ - gtk_entry_update_primary_selection (entry); - - /* Disable the password hint if one exists. */ - if (!priv->visible) - { - GtkEntryPasswordHint *password_hint = g_object_get_qdata (G_OBJECT (entry), - quark_password_hint); - if (password_hint) - { - if (password_hint->source_id) - g_source_remove (password_hint->source_id); - password_hint->source_id = 0; - password_hint->position = -1; - } - } -} - -static void -buffer_notify_text (GtkEntryBuffer *buffer, - GParamSpec *spec, - GtkEntry *entry) -{ - emit_changed (entry); - g_object_notify_by_pspec (G_OBJECT (entry), entry_props[PROP_TEXT]); -} - -static void -buffer_notify_length (GtkEntryBuffer *buffer, - GParamSpec *spec, - GtkEntry *entry) -{ - g_object_notify_by_pspec (G_OBJECT (entry), entry_props[PROP_TEXT_LENGTH]); -} - -static void -buffer_notify_max_length (GtkEntryBuffer *buffer, - GParamSpec *spec, - GtkEntry *entry) -{ - g_object_notify_by_pspec (G_OBJECT (entry), entry_props[PROP_MAX_LENGTH]); -} - -static void -buffer_connect_signals (GtkEntry *entry) -{ - g_signal_connect (get_buffer (entry), "inserted-text", G_CALLBACK (buffer_inserted_text), entry); - g_signal_connect (get_buffer (entry), "deleted-text", G_CALLBACK (buffer_deleted_text), entry); - g_signal_connect (get_buffer (entry), "notify::text", G_CALLBACK (buffer_notify_text), entry); - g_signal_connect (get_buffer (entry), "notify::length", G_CALLBACK (buffer_notify_length), entry); - g_signal_connect (get_buffer (entry), "notify::max-length", G_CALLBACK (buffer_notify_max_length), entry); -} - -static void -buffer_disconnect_signals (GtkEntry *entry) -{ - g_signal_handlers_disconnect_by_func (get_buffer (entry), buffer_inserted_text, entry); - g_signal_handlers_disconnect_by_func (get_buffer (entry), buffer_deleted_text, entry); - g_signal_handlers_disconnect_by_func (get_buffer (entry), buffer_notify_text, entry); - g_signal_handlers_disconnect_by_func (get_buffer (entry), buffer_notify_length, entry); - g_signal_handlers_disconnect_by_func (get_buffer (entry), buffer_notify_max_length, entry); -} - -/* Compute the X position for an offset that corresponds to the "more important - * cursor position for that offset. We use this when trying to guess to which - * end of the selection we should go to when the user hits the left or - * right arrow key. - */ -static gint -get_better_cursor_x (GtkEntry *entry, - gint offset) -{ - GtkEntryPrivate *priv = gtk_entry_get_instance_private (entry); - GdkKeymap *keymap = gdk_display_get_keymap (gtk_widget_get_display (GTK_WIDGET (entry))); - PangoDirection keymap_direction = gdk_keymap_get_direction (keymap); - gboolean split_cursor; - - PangoLayout *layout = gtk_entry_ensure_layout (entry, TRUE); - const gchar *text = pango_layout_get_text (layout); - gint index = g_utf8_offset_to_pointer (text, offset) - text; - - PangoRectangle strong_pos, weak_pos; - - g_object_get (gtk_widget_get_settings (GTK_WIDGET (entry)), - "gtk-split-cursor", &split_cursor, - NULL); - - pango_layout_get_cursor_pos (layout, index, &strong_pos, &weak_pos); - - if (split_cursor) - return strong_pos.x / PANGO_SCALE; - else - return (keymap_direction == priv->resolved_dir) ? strong_pos.x / PANGO_SCALE : weak_pos.x / PANGO_SCALE; -} - -static void -gtk_entry_move_cursor (GtkEntry *entry, - GtkMovementStep step, - gint count, - gboolean extend_selection) -{ - GtkEntryPrivate *priv = gtk_entry_get_instance_private (entry); - gint new_pos = priv->current_pos; - - gtk_entry_reset_im_context (entry); - - if (priv->current_pos != priv->selection_bound && !extend_selection) - { - /* If we have a current selection and aren't extending it, move to the - * start/or end of the selection as appropriate - */ - switch (step) - { - case GTK_MOVEMENT_VISUAL_POSITIONS: - { - gint current_x = get_better_cursor_x (entry, priv->current_pos); - gint bound_x = get_better_cursor_x (entry, priv->selection_bound); - - if (count <= 0) - new_pos = current_x < bound_x ? priv->current_pos : priv->selection_bound; - else - new_pos = current_x > bound_x ? priv->current_pos : priv->selection_bound; - } - break; - - case GTK_MOVEMENT_WORDS: - if (priv->resolved_dir == PANGO_DIRECTION_RTL) - count *= -1; - /* Fall through */ - - case GTK_MOVEMENT_LOGICAL_POSITIONS: - if (count < 0) - new_pos = MIN (priv->current_pos, priv->selection_bound); - else - new_pos = MAX (priv->current_pos, priv->selection_bound); - - break; - - case GTK_MOVEMENT_DISPLAY_LINE_ENDS: - case GTK_MOVEMENT_PARAGRAPH_ENDS: - case GTK_MOVEMENT_BUFFER_ENDS: - new_pos = count < 0 ? 0 : gtk_entry_buffer_get_length (get_buffer (entry)); - break; - - case GTK_MOVEMENT_DISPLAY_LINES: - case GTK_MOVEMENT_PARAGRAPHS: - case GTK_MOVEMENT_PAGES: - case GTK_MOVEMENT_HORIZONTAL_PAGES: - default: - break; - } - } - else - { - switch (step) - { - case GTK_MOVEMENT_LOGICAL_POSITIONS: - new_pos = gtk_entry_move_logically (entry, new_pos, count); - break; - - case GTK_MOVEMENT_VISUAL_POSITIONS: - new_pos = gtk_entry_move_visually (entry, new_pos, count); - - if (priv->current_pos == new_pos) - { - if (!extend_selection) - { - if (!gtk_widget_keynav_failed (GTK_WIDGET (entry), - count > 0 ? - GTK_DIR_RIGHT : GTK_DIR_LEFT)) - { - GtkWidget *toplevel = gtk_widget_get_toplevel (GTK_WIDGET (entry)); - - if (toplevel) - gtk_widget_child_focus (toplevel, - count > 0 ? - GTK_DIR_RIGHT : GTK_DIR_LEFT); - } - } - else - { - gtk_widget_error_bell (GTK_WIDGET (entry)); - } - } - break; - - case GTK_MOVEMENT_WORDS: - if (priv->resolved_dir == PANGO_DIRECTION_RTL) - count *= -1; - - while (count > 0) - { - new_pos = gtk_entry_move_forward_word (entry, new_pos, FALSE); - count--; - } - - while (count < 0) - { - new_pos = gtk_entry_move_backward_word (entry, new_pos, FALSE); - count++; - } - - if (priv->current_pos == new_pos) - gtk_widget_error_bell (GTK_WIDGET (entry)); - - break; - - case GTK_MOVEMENT_DISPLAY_LINE_ENDS: - case GTK_MOVEMENT_PARAGRAPH_ENDS: - case GTK_MOVEMENT_BUFFER_ENDS: - new_pos = count < 0 ? 0 : gtk_entry_buffer_get_length (get_buffer (entry)); - - if (priv->current_pos == new_pos) - gtk_widget_error_bell (GTK_WIDGET (entry)); - - break; - - case GTK_MOVEMENT_DISPLAY_LINES: - case GTK_MOVEMENT_PARAGRAPHS: - case GTK_MOVEMENT_PAGES: - case GTK_MOVEMENT_HORIZONTAL_PAGES: - default: - break; - } - } - - if (extend_selection) - gtk_editable_select_region (GTK_EDITABLE (entry), priv->selection_bound, new_pos); - else - gtk_editable_set_position (GTK_EDITABLE (entry), new_pos); - - gtk_entry_pend_cursor_blink (entry); -} - -static void -gtk_entry_insert_at_cursor (GtkEntry *entry, - const gchar *str) -{ - GtkEntryPrivate *priv = gtk_entry_get_instance_private (entry); - GtkEditable *editable = GTK_EDITABLE (entry); - gint pos = priv->current_pos; - - if (priv->editable) - { - gtk_entry_reset_im_context (entry); - gtk_editable_insert_text (editable, str, -1, &pos); - gtk_editable_set_position (editable, pos); - } -} - -static void -gtk_entry_delete_from_cursor (GtkEntry *entry, - GtkDeleteType type, - gint count) -{ - GtkEntryPrivate *priv = gtk_entry_get_instance_private (entry); - GtkEditable *editable = GTK_EDITABLE (entry); - gint start_pos = priv->current_pos; - gint end_pos = priv->current_pos; - gint old_n_bytes = gtk_entry_buffer_get_bytes (get_buffer (entry)); - - gtk_entry_reset_im_context (entry); - - if (!priv->editable) - { - gtk_widget_error_bell (GTK_WIDGET (entry)); - return; - } - - if (priv->selection_bound != priv->current_pos) - { - gtk_editable_delete_selection (editable); - return; - } - - switch (type) - { - case GTK_DELETE_CHARS: - end_pos = gtk_entry_move_logically (entry, priv->current_pos, count); - gtk_editable_delete_text (editable, MIN (start_pos, end_pos), MAX (start_pos, end_pos)); - break; - - case GTK_DELETE_WORDS: - if (count < 0) - { - /* Move to end of current word, or if not on a word, end of previous word */ - end_pos = gtk_entry_move_backward_word (entry, end_pos, FALSE); - end_pos = gtk_entry_move_forward_word (entry, end_pos, FALSE); - } - else if (count > 0) - { - /* Move to beginning of current word, or if not on a word, begining of next word */ - start_pos = gtk_entry_move_forward_word (entry, start_pos, FALSE); - start_pos = gtk_entry_move_backward_word (entry, start_pos, FALSE); - } - - /* Fall through */ - case GTK_DELETE_WORD_ENDS: - while (count < 0) - { - start_pos = gtk_entry_move_backward_word (entry, start_pos, FALSE); - count++; - } - - while (count > 0) - { - end_pos = gtk_entry_move_forward_word (entry, end_pos, FALSE); - count--; - } - - gtk_editable_delete_text (editable, start_pos, end_pos); - break; - - case GTK_DELETE_DISPLAY_LINE_ENDS: - case GTK_DELETE_PARAGRAPH_ENDS: - if (count < 0) - gtk_editable_delete_text (editable, 0, priv->current_pos); - else - gtk_editable_delete_text (editable, priv->current_pos, -1); - - break; - - case GTK_DELETE_DISPLAY_LINES: - case GTK_DELETE_PARAGRAPHS: - gtk_editable_delete_text (editable, 0, -1); - break; - - case GTK_DELETE_WHITESPACE: - gtk_entry_delete_whitespace (entry); - break; - - default: - break; - } - - if (gtk_entry_buffer_get_bytes (get_buffer (entry)) == old_n_bytes) - gtk_widget_error_bell (GTK_WIDGET (entry)); - - gtk_entry_pend_cursor_blink (entry); -} - -static void -gtk_entry_backspace (GtkEntry *entry) -{ - GtkEntryPrivate *priv = gtk_entry_get_instance_private (entry); - GtkEditable *editable = GTK_EDITABLE (entry); - gint prev_pos; - - gtk_entry_reset_im_context (entry); - - if (!priv->editable) - { - gtk_widget_error_bell (GTK_WIDGET (entry)); - return; - } - - if (priv->selection_bound != priv->current_pos) - { - gtk_editable_delete_selection (editable); - return; - } - - prev_pos = gtk_entry_move_logically (entry, priv->current_pos, -1); - - if (prev_pos < priv->current_pos) - { - PangoLayout *layout = gtk_entry_ensure_layout (entry, FALSE); - const PangoLogAttr *log_attrs; - gint n_attrs; - - log_attrs = pango_layout_get_log_attrs_readonly (layout, &n_attrs); - - /* Deleting parts of characters */ - if (log_attrs[priv->current_pos].backspace_deletes_character) - { - gchar *cluster_text; - gchar *normalized_text; - glong len; - - cluster_text = _gtk_entry_get_display_text (entry, prev_pos, - priv->current_pos); - normalized_text = g_utf8_normalize (cluster_text, - strlen (cluster_text), - G_NORMALIZE_NFD); - len = g_utf8_strlen (normalized_text, -1); - - gtk_editable_delete_text (editable, prev_pos, priv->current_pos); - if (len > 1) - { - gint pos = priv->current_pos; - - gtk_editable_insert_text (editable, normalized_text, - g_utf8_offset_to_pointer (normalized_text, len - 1) - normalized_text, - &pos); - gtk_editable_set_position (editable, pos); - } - - g_free (normalized_text); - g_free (cluster_text); - } - else - { - gtk_editable_delete_text (editable, prev_pos, priv->current_pos); - } - } - else - { - gtk_widget_error_bell (GTK_WIDGET (entry)); - } - - gtk_entry_pend_cursor_blink (entry); -} - -static void -gtk_entry_copy_clipboard (GtkEntry *entry) -{ - GtkEntryPrivate *priv = gtk_entry_get_instance_private (entry); - GtkEditable *editable = GTK_EDITABLE (entry); - gint start, end; - gchar *str; - - if (gtk_editable_get_selection_bounds (editable, &start, &end)) - { - if (!priv->visible) - { - gtk_widget_error_bell (GTK_WIDGET (entry)); - return; - } - - str = _gtk_entry_get_display_text (entry, start, end); - gdk_clipboard_set_text (gtk_widget_get_clipboard (GTK_WIDGET (entry)), str); - g_free (str); - } -} - -static void -gtk_entry_cut_clipboard (GtkEntry *entry) -{ - GtkEntryPrivate *priv = gtk_entry_get_instance_private (entry); - GtkEditable *editable = GTK_EDITABLE (entry); - gint start, end; - - if (!priv->visible) - { - gtk_widget_error_bell (GTK_WIDGET (entry)); - return; - } - - gtk_entry_copy_clipboard (entry); - - if (priv->editable) - { - if (gtk_editable_get_selection_bounds (editable, &start, &end)) - gtk_editable_delete_text (editable, start, end); - } - else - { - gtk_widget_error_bell (GTK_WIDGET (entry)); - } - - gtk_entry_selection_bubble_popup_unset (entry); - - if (priv->text_handle) - { - GtkTextHandleMode handle_mode; - - handle_mode = _gtk_text_handle_get_mode (priv->text_handle); - - if (handle_mode != GTK_TEXT_HANDLE_MODE_NONE) - gtk_entry_update_handles (entry, GTK_TEXT_HANDLE_MODE_CURSOR); - } -} - -static void -gtk_entry_paste_clipboard (GtkEntry *entry) -{ - GtkEntryPrivate *priv = gtk_entry_get_instance_private (entry); - - if (priv->editable) - gtk_entry_paste (entry, gtk_widget_get_clipboard (GTK_WIDGET (entry))); - else - gtk_widget_error_bell (GTK_WIDGET (entry)); - - if (priv->text_handle) - { - GtkTextHandleMode handle_mode; - - handle_mode = _gtk_text_handle_get_mode (priv->text_handle); - - if (handle_mode != GTK_TEXT_HANDLE_MODE_NONE) - gtk_entry_update_handles (entry, GTK_TEXT_HANDLE_MODE_CURSOR); - } -} - -static void -gtk_entry_delete_cb (GtkEntry *entry) -{ - GtkEntryPrivate *priv = gtk_entry_get_instance_private (entry); - GtkEditable *editable = GTK_EDITABLE (entry); - gint start, end; - - if (priv->editable) - { - if (gtk_editable_get_selection_bounds (editable, &start, &end)) - gtk_editable_delete_text (editable, start, end); - } -} - -static void -gtk_entry_toggle_overwrite (GtkEntry *entry) -{ - GtkEntryPrivate *priv = gtk_entry_get_instance_private (entry); - - priv->overwrite_mode = !priv->overwrite_mode; - - if (priv->overwrite_mode) - { - if (!priv->block_cursor_node) - { - GtkCssNode *widget_node = gtk_widget_get_css_node (GTK_WIDGET (entry)); - - priv->block_cursor_node = gtk_css_node_new (); - gtk_css_node_set_name (priv->block_cursor_node, I_("block-cursor")); - gtk_css_node_set_parent (priv->block_cursor_node, widget_node); - gtk_css_node_set_state (priv->block_cursor_node, gtk_css_node_get_state (widget_node)); - g_object_unref (priv->block_cursor_node); - } - } - else - { - if (priv->block_cursor_node) - { - gtk_css_node_set_parent (priv->block_cursor_node, NULL); - priv->block_cursor_node = NULL; - } - } - - gtk_entry_pend_cursor_blink (entry); - gtk_widget_queue_draw (GTK_WIDGET (entry)); -} - -static void -gtk_entry_select_all (GtkEntry *entry) -{ - gtk_entry_select_line (entry); -} - -static void -gtk_entry_real_activate (GtkEntry *entry) -{ - GtkEntryPrivate *priv = gtk_entry_get_instance_private (entry); - GtkWindow *window; - GtkWidget *default_widget, *focus_widget; - GtkWidget *toplevel; - GtkWidget *widget; - - widget = GTK_WIDGET (entry); - - if (priv->activates_default) - { - toplevel = gtk_widget_get_toplevel (widget); - if (GTK_IS_WINDOW (toplevel)) - { - window = GTK_WINDOW (toplevel); - - if (window) - { - default_widget = gtk_window_get_default_widget (window); - focus_widget = gtk_window_get_focus (window); - if (widget != default_widget && - !(widget == focus_widget && (!default_widget || !gtk_widget_get_sensitive (default_widget)))) - gtk_window_activate_default (window); - } - } - } -} - -static void -keymap_direction_changed (GdkKeymap *keymap, - GtkEntry *entry) -{ - gtk_entry_recompute (entry); -} - -/* IM Context Callbacks - */ - -static void -gtk_entry_commit_cb (GtkIMContext *context, - const gchar *str, - GtkEntry *entry) -{ - GtkEntryPrivate *priv = gtk_entry_get_instance_private (entry); - - if (priv->editable) - { - gtk_entry_enter_text (entry, str); - gtk_entry_obscure_mouse_cursor (entry); - } -} - -static void -gtk_entry_preedit_changed_cb (GtkIMContext *context, - GtkEntry *entry) -{ - GtkEntryPrivate *priv = gtk_entry_get_instance_private (entry); - - if (priv->editable) - { - gchar *preedit_string; - gint cursor_pos; - - gtk_entry_obscure_mouse_cursor (entry); - - gtk_im_context_get_preedit_string (priv->im_context, - &preedit_string, NULL, - &cursor_pos); - g_signal_emit (entry, signals[PREEDIT_CHANGED], 0, preedit_string); - priv->preedit_length = strlen (preedit_string); - cursor_pos = CLAMP (cursor_pos, 0, g_utf8_strlen (preedit_string, -1)); - priv->preedit_cursor = cursor_pos; - g_free (preedit_string); - - gtk_entry_recompute (entry); - } -} - -static gboolean -gtk_entry_retrieve_surrounding_cb (GtkIMContext *context, - GtkEntry *entry) -{ - GtkEntryPrivate *priv = gtk_entry_get_instance_private (entry); - gchar *text; - - /* XXXX ??? does this even make sense when text is not visible? Should we return FALSE? */ - text = _gtk_entry_get_display_text (entry, 0, -1); - gtk_im_context_set_surrounding (context, text, strlen (text), /* Length in bytes */ - g_utf8_offset_to_pointer (text, priv->current_pos) - text); - g_free (text); - - return TRUE; -} - -static gboolean -gtk_entry_delete_surrounding_cb (GtkIMContext *slave, - gint offset, - gint n_chars, - GtkEntry *entry) -{ - GtkEntryPrivate *priv = gtk_entry_get_instance_private (entry); - - if (priv->editable) - gtk_editable_delete_text (GTK_EDITABLE (entry), - priv->current_pos + offset, - priv->current_pos + offset + n_chars); - - return TRUE; -} - /* Internal functions */ -/* Used for im_commit_cb and inserting Unicode chars */ -void -gtk_entry_enter_text (GtkEntry *entry, - const gchar *str) -{ - GtkEntryPrivate *priv = gtk_entry_get_instance_private (entry); - GtkEditable *editable = GTK_EDITABLE (entry); - gint tmp_pos; - gboolean old_need_im_reset; - guint text_length; - - old_need_im_reset = priv->need_im_reset; - priv->need_im_reset = FALSE; - - if (gtk_editable_get_selection_bounds (editable, NULL, NULL)) - gtk_editable_delete_selection (editable); - else - { - if (priv->overwrite_mode) - { - text_length = gtk_entry_buffer_get_length (get_buffer (entry)); - if (priv->current_pos < text_length) - gtk_entry_delete_from_cursor (entry, GTK_DELETE_CHARS, 1); - } - } - - tmp_pos = priv->current_pos; - gtk_editable_insert_text (editable, str, strlen (str), &tmp_pos); - gtk_editable_set_position (editable, tmp_pos); - - priv->need_im_reset = old_need_im_reset; -} - -/* All changes to priv->current_pos and priv->selection_bound - * should go through this function. - */ -void -gtk_entry_set_positions (GtkEntry *entry, - gint current_pos, - gint selection_bound) -{ - GtkEntryPrivate *priv = gtk_entry_get_instance_private (entry); - gboolean changed = FALSE; - - g_object_freeze_notify (G_OBJECT (entry)); - - if (current_pos != -1 && - priv->current_pos != current_pos) - { - priv->current_pos = current_pos; - changed = TRUE; - - g_object_notify_by_pspec (G_OBJECT (entry), entry_props[PROP_CURSOR_POSITION]); - } - - if (selection_bound != -1 && - priv->selection_bound != selection_bound) - { - priv->selection_bound = selection_bound; - changed = TRUE; - - g_object_notify_by_pspec (G_OBJECT (entry), entry_props[PROP_SELECTION_BOUND]); - } - - g_object_thaw_notify (G_OBJECT (entry)); - - if (priv->current_pos != priv->selection_bound) - { - if (!priv->selection_node) - { - GtkCssNode *widget_node = gtk_widget_get_css_node (GTK_WIDGET (entry)); - - priv->selection_node = gtk_css_node_new (); - gtk_css_node_set_name (priv->selection_node, I_("selection")); - gtk_css_node_set_parent (priv->selection_node, widget_node); - gtk_css_node_set_state (priv->selection_node, gtk_css_node_get_state (widget_node)); - g_object_unref (priv->selection_node); - } - } - else - { - if (priv->selection_node) - { - gtk_css_node_set_parent (priv->selection_node, NULL); - priv->selection_node = NULL; - } - } - - if (changed) - { - gtk_entry_recompute (entry); - } -} - -static void -gtk_entry_reset_layout (GtkEntry *entry) -{ - GtkEntryPrivate *priv = gtk_entry_get_instance_private (entry); - - if (priv->cached_layout) - { - g_object_unref (priv->cached_layout); - priv->cached_layout = NULL; - } -} - -static void -update_im_cursor_location (GtkEntry *entry) -{ - GtkEntryPrivate *priv = gtk_entry_get_instance_private (entry); - GdkRectangle area; - GtkAllocation text_area; - gint strong_x; - gint strong_xoffset; - - gtk_entry_get_cursor_locations (entry, &strong_x, NULL); - gtk_entry_get_text_allocation (entry, &text_area); - - strong_xoffset = strong_x - priv->scroll_offset; - if (strong_xoffset < 0) - { - strong_xoffset = 0; - } - else if (strong_xoffset > text_area.width) - { - strong_xoffset = text_area.width; - } - area.x = strong_xoffset; - area.y = 0; - area.width = 0; - area.height = text_area.height; - - gtk_im_context_set_cursor_location (priv->im_context, &area); -} - -static void -gtk_entry_recompute (GtkEntry *entry) -{ - GtkEntryPrivate *priv = gtk_entry_get_instance_private (entry); - GtkTextHandleMode handle_mode; - - gtk_entry_reset_layout (entry); - gtk_entry_check_cursor_blink (entry); - - gtk_entry_adjust_scroll (entry); - - update_im_cursor_location (entry); - - if (priv->text_handle) - { - handle_mode = _gtk_text_handle_get_mode (priv->text_handle); - - if (handle_mode != GTK_TEXT_HANDLE_MODE_NONE) - gtk_entry_update_handles (entry, handle_mode); - } - - gtk_widget_queue_draw (GTK_WIDGET (entry)); -} - -static PangoLayout * -gtk_entry_create_layout (GtkEntry *entry, - gboolean include_preedit) -{ - GtkEntryPrivate *priv = gtk_entry_get_instance_private (entry); - GtkWidget *widget = GTK_WIDGET (entry); - GtkStyleContext *context; - PangoLayout *layout; - PangoAttrList *tmp_attrs; - - gchar *preedit_string = NULL; - gint preedit_length = 0; - PangoAttrList *preedit_attrs = NULL; - - gchar *display_text; - guint n_bytes; - - context = gtk_widget_get_style_context (widget); - - layout = gtk_widget_create_pango_layout (widget, NULL); - pango_layout_set_single_paragraph_mode (layout, TRUE); - - tmp_attrs = _gtk_style_context_get_pango_attributes (context); - tmp_attrs = _gtk_pango_attr_list_merge (tmp_attrs, priv->attrs); - if (!tmp_attrs) - tmp_attrs = pango_attr_list_new (); - - display_text = _gtk_entry_get_display_text (entry, 0, -1); - - n_bytes = strlen (display_text); - - if (include_preedit) - { - gtk_im_context_get_preedit_string (priv->im_context, - &preedit_string, &preedit_attrs, NULL); - preedit_length = priv->preedit_length; - } - - if (preedit_length) - { - GString *tmp_string = g_string_new (display_text); - gint pos; - - pos = g_utf8_offset_to_pointer (display_text, priv->current_pos) - display_text; - g_string_insert (tmp_string, pos, preedit_string); - pango_layout_set_text (layout, tmp_string->str, tmp_string->len); - pango_attr_list_splice (tmp_attrs, preedit_attrs, pos, preedit_length); - g_string_free (tmp_string, TRUE); - } - else - { - PangoDirection pango_dir; - - if (gtk_entry_get_display_mode (entry) == DISPLAY_NORMAL) - pango_dir = gdk_find_base_dir (display_text, n_bytes); - else - pango_dir = PANGO_DIRECTION_NEUTRAL; - - if (pango_dir == PANGO_DIRECTION_NEUTRAL) - { - if (gtk_widget_has_focus (widget)) - { - GdkDisplay *display = gtk_widget_get_display (widget); - GdkKeymap *keymap = gdk_display_get_keymap (display); - if (gdk_keymap_get_direction (keymap) == PANGO_DIRECTION_RTL) - pango_dir = PANGO_DIRECTION_RTL; - else - pango_dir = PANGO_DIRECTION_LTR; - } - else - { - if (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL) - pango_dir = PANGO_DIRECTION_RTL; - else - pango_dir = PANGO_DIRECTION_LTR; - } - } - - pango_context_set_base_dir (gtk_widget_get_pango_context (widget), pango_dir); - - priv->resolved_dir = pango_dir; - - pango_layout_set_text (layout, display_text, n_bytes); - } - - pango_layout_set_attributes (layout, tmp_attrs); - - if (priv->tabs) - pango_layout_set_tabs (layout, priv->tabs); - - g_free (preedit_string); - g_free (display_text); - - if (preedit_attrs) - pango_attr_list_unref (preedit_attrs); - - pango_attr_list_unref (tmp_attrs); - - return layout; -} - -static PangoLayout * -gtk_entry_ensure_layout (GtkEntry *entry, - gboolean include_preedit) -{ - GtkEntryPrivate *priv = gtk_entry_get_instance_private (entry); - - if (priv->preedit_length > 0 && - !include_preedit != !priv->cache_includes_preedit) - gtk_entry_reset_layout (entry); - - if (!priv->cached_layout) - { - priv->cached_layout = gtk_entry_create_layout (entry, include_preedit); - priv->cache_includes_preedit = include_preedit; - } - - return priv->cached_layout; -} - -static void -get_layout_position (GtkEntry *entry, - gint *x, - gint *y) -{ - GtkEntryPrivate *priv = gtk_entry_get_instance_private (entry); - PangoLayout *layout; - PangoRectangle logical_rect; - gint y_pos, area_height; - PangoLayoutLine *line; - GtkAllocation text_allocation; - - gtk_entry_get_text_allocation (entry, &text_allocation); - - layout = gtk_entry_ensure_layout (entry, TRUE); - - area_height = PANGO_SCALE * text_allocation.height; - - line = pango_layout_get_lines_readonly (layout)->data; - pango_layout_line_get_extents (line, NULL, &logical_rect); - - /* Align primarily for locale's ascent/descent */ - if (priv->text_baseline < 0) - y_pos = ((area_height - priv->ascent - priv->descent) / 2 + - priv->ascent + logical_rect.y); - else - y_pos = PANGO_SCALE * priv->text_baseline - pango_layout_get_baseline (layout); - - /* Now see if we need to adjust to fit in actual drawn string */ - if (logical_rect.height > area_height) - y_pos = (area_height - logical_rect.height) / 2; - else if (y_pos < 0) - y_pos = 0; - else if (y_pos + logical_rect.height > area_height) - y_pos = area_height - logical_rect.height; - - y_pos = y_pos / PANGO_SCALE; - - if (x) - *x = priv->text_x - priv->scroll_offset; - - if (y) - *y = y_pos; -} - -#define GRAPHENE_RECT_FROM_RECT(_r) (GRAPHENE_RECT_INIT ((_r)->x, (_r)->y, (_r)->width, (_r)->height)) - -static void -gtk_entry_draw_text (GtkEntry *entry, - GtkSnapshot *snapshot) -{ - GtkEntryPrivate *priv = gtk_entry_get_instance_private (entry); - GtkWidget *widget = GTK_WIDGET (entry); - GtkStyleContext *context; - PangoLayout *layout; - gint x, y; - gint start_pos, end_pos; - int width, height; - - /* Nothing to display at all */ - if (gtk_entry_get_display_mode (entry) == DISPLAY_BLANK) - return; - - context = gtk_widget_get_style_context (widget); - layout = gtk_entry_ensure_layout (entry, TRUE); - width = gtk_widget_get_width (widget); - height = gtk_widget_get_height (widget); - - gtk_entry_get_layout_offsets (entry, &x, &y); - - gtk_snapshot_render_layout (snapshot, context, x, y, layout); - - if (gtk_editable_get_selection_bounds (GTK_EDITABLE (entry), &start_pos, &end_pos)) - { - const char *text = pango_layout_get_text (layout); - gint start_index = g_utf8_offset_to_pointer (text, start_pos) - text; - gint end_index = g_utf8_offset_to_pointer (text, end_pos) - text; - cairo_region_t *clip; - cairo_rectangle_int_t clip_extents; - gint range[2]; - - range[0] = MIN (start_index, end_index); - range[1] = MAX (start_index, end_index); - - gtk_style_context_save_to_node (context, priv->selection_node); - - clip = gdk_pango_layout_get_clip_region (layout, x, y, range, 1); - cairo_region_get_extents (clip, &clip_extents); - - gtk_snapshot_push_clip (snapshot, &GRAPHENE_RECT_FROM_RECT (&clip_extents)); - gtk_snapshot_render_background (snapshot, context, 0, 0, width, height); - gtk_snapshot_render_layout (snapshot, context, x, y, layout); - gtk_snapshot_pop (snapshot); - - cairo_region_destroy (clip); - - gtk_style_context_restore (context); - } -} - -static void -gtk_entry_draw_cursor (GtkEntry *entry, - GtkSnapshot *snapshot, - CursorType type) -{ - GtkEntryPrivate *priv = gtk_entry_get_instance_private (entry); - GtkWidget *widget = GTK_WIDGET (entry); - GtkStyleContext *context; - PangoRectangle cursor_rect; - gint cursor_index; - gboolean block; - gboolean block_at_line_end; - PangoLayout *layout; - const char *text; - gint x, y; - int width, height; - - context = gtk_widget_get_style_context (widget); - - layout = gtk_entry_ensure_layout (entry, TRUE); - text = pango_layout_get_text (layout); - gtk_entry_get_layout_offsets (entry, &x, &y); - width = gtk_widget_get_width (widget); - height = gtk_widget_get_height (widget); - - if (type == CURSOR_DND) - cursor_index = g_utf8_offset_to_pointer (text, priv->dnd_position) - text; - else - cursor_index = g_utf8_offset_to_pointer (text, priv->current_pos + priv->preedit_cursor) - text; - - if (!priv->overwrite_mode) - block = FALSE; - else - block = _gtk_text_util_get_block_cursor_location (layout, - cursor_index, &cursor_rect, &block_at_line_end); - if (!block) - { - gtk_snapshot_render_insertion_cursor (snapshot, context, - x, y, - layout, cursor_index, priv->resolved_dir); - } - else /* overwrite_mode */ - { - graphene_rect_t bounds; - - bounds.origin.x = PANGO_PIXELS (cursor_rect.x) + x; - bounds.origin.y = PANGO_PIXELS (cursor_rect.y) + y; - bounds.size.width = PANGO_PIXELS (cursor_rect.width); - bounds.size.height = PANGO_PIXELS (cursor_rect.height); - - gtk_style_context_save_to_node (context, priv->block_cursor_node); - - gtk_snapshot_push_clip (snapshot, &bounds); - gtk_snapshot_render_background (snapshot, context, 0, 0, width, height); - gtk_snapshot_render_layout (snapshot, context, x, y, layout); - gtk_snapshot_pop (snapshot); - - gtk_style_context_restore (context); - } -} - -static void -gtk_entry_handle_dragged (GtkTextHandle *handle, - GtkTextHandlePosition pos, - gint x, - gint y, - GtkEntry *entry) -{ - gint cursor_pos, selection_bound_pos, tmp_pos; - GtkEntryPrivate *priv = gtk_entry_get_instance_private (entry); - GtkTextHandleMode mode; - gint *min, *max; - - gtk_entry_selection_bubble_popup_unset (entry); - - cursor_pos = priv->current_pos; - selection_bound_pos = priv->selection_bound; - mode = _gtk_text_handle_get_mode (handle); - - tmp_pos = gtk_entry_find_position (entry, x + priv->scroll_offset); - - if (mode == GTK_TEXT_HANDLE_MODE_CURSOR || - cursor_pos >= selection_bound_pos) - { - max = &cursor_pos; - min = &selection_bound_pos; - } - else - { - max = &selection_bound_pos; - min = &cursor_pos; - } - - if (pos == GTK_TEXT_HANDLE_POSITION_SELECTION_END) - { - if (mode == GTK_TEXT_HANDLE_MODE_SELECTION) - { - gint min_pos; - - min_pos = MAX (*min + 1, 0); - tmp_pos = MAX (tmp_pos, min_pos); - } - - *max = tmp_pos; - } - else - { - if (mode == GTK_TEXT_HANDLE_MODE_SELECTION) - { - gint max_pos; - - max_pos = *max - 1; - *min = MIN (tmp_pos, max_pos); - } - } - - if (cursor_pos != priv->current_pos || - selection_bound_pos != priv->selection_bound) - { - if (mode == GTK_TEXT_HANDLE_MODE_CURSOR) - { - priv->cursor_handle_dragged = TRUE; - gtk_entry_set_positions (entry, cursor_pos, cursor_pos); - } - else - { - priv->selection_handle_dragged = TRUE; - gtk_entry_set_positions (entry, cursor_pos, selection_bound_pos); - } - - gtk_entry_update_handles (entry, mode); - } - - gtk_entry_show_magnifier (entry, x, y); -} - -static void -gtk_entry_handle_drag_started (GtkTextHandle *handle, - GtkTextHandlePosition pos, - GtkEntry *entry) -{ - GtkEntryPrivate *priv = gtk_entry_get_instance_private (entry); - - priv->cursor_handle_dragged = FALSE; - priv->selection_handle_dragged = FALSE; -} - -static void -gtk_entry_handle_drag_finished (GtkTextHandle *handle, - GtkTextHandlePosition pos, - GtkEntry *entry) -{ - GtkEntryPrivate *priv = gtk_entry_get_instance_private (entry); - - if (!priv->cursor_handle_dragged && !priv->selection_handle_dragged) - { - GtkSettings *settings; - guint double_click_time; - - settings = gtk_widget_get_settings (GTK_WIDGET (entry)); - g_object_get (settings, "gtk-double-click-time", &double_click_time, NULL); - if (g_get_monotonic_time() - priv->handle_place_time < double_click_time * 1000) - { - gtk_entry_select_word (entry); - gtk_entry_update_handles (entry, GTK_TEXT_HANDLE_MODE_SELECTION); - } - else - gtk_entry_selection_bubble_popup_set (entry); - } - - if (priv->magnifier_popover) - gtk_popover_popdown (GTK_POPOVER (priv->magnifier_popover)); -} - - /** * gtk_entry_reset_im_context: * @entry: a #GtkEntry @@ -5829,578 +1747,7 @@ gtk_entry_reset_im_context (GtkEntry *entry) g_return_if_fail (GTK_IS_ENTRY (entry)); - if (priv->need_im_reset) - { - priv->need_im_reset = FALSE; - gtk_im_context_reset (priv->im_context); - } -} - -/** - * gtk_entry_im_context_filter_keypress: - * @entry: a #GtkEntry - * @event: (type Gdk.EventKey): the key event - * - * Allow the #GtkEntry input method to internally handle key press - * and release events. If this function returns %TRUE, then no further - * processing should be done for this key event. See - * gtk_im_context_filter_keypress(). - * - * Note that you are expected to call this function from your handler - * when overriding key event handling. This is needed in the case when - * you need to insert your own key handling between the input method - * and the default key event handling of the #GtkEntry. - * See gtk_text_view_reset_im_context() for an example of use. - * - * Returns: %TRUE if the input method handled the key event. - */ -gboolean -gtk_entry_im_context_filter_keypress (GtkEntry *entry, - GdkEventKey *event) -{ - GtkEntryPrivate *priv = gtk_entry_get_instance_private (entry); - - g_return_val_if_fail (GTK_IS_ENTRY (entry), FALSE); - - return gtk_im_context_filter_keypress (priv->im_context, event); -} - -GtkIMContext* -_gtk_entry_get_im_context (GtkEntry *entry) -{ - GtkEntryPrivate *priv = gtk_entry_get_instance_private (entry); - - return priv->im_context; -} - -static gint -gtk_entry_find_position (GtkEntry *entry, - gint x) -{ - GtkEntryPrivate *priv = gtk_entry_get_instance_private (entry); - PangoLayout *layout; - PangoLayoutLine *line; - gint index; - gint pos; - gint trailing; - const gchar *text; - gint cursor_index; - - layout = gtk_entry_ensure_layout (entry, TRUE); - text = pango_layout_get_text (layout); - cursor_index = g_utf8_offset_to_pointer (text, priv->current_pos) - text; - - line = pango_layout_get_lines_readonly (layout)->data; - pango_layout_line_x_to_index (line, x * PANGO_SCALE, &index, &trailing); - - if (index >= cursor_index && priv->preedit_length) - { - if (index >= cursor_index + priv->preedit_length) - index -= priv->preedit_length; - else - { - index = cursor_index; - trailing = 0; - } - } - - pos = g_utf8_pointer_to_offset (text, text + index); - pos += trailing; - - return pos; -} - -static void -gtk_entry_get_cursor_locations (GtkEntry *entry, - gint *strong_x, - gint *weak_x) -{ - GtkEntryPrivate *priv = gtk_entry_get_instance_private (entry); - DisplayMode mode = gtk_entry_get_display_mode (entry); - - /* Nothing to display at all, so no cursor is relevant */ - if (mode == DISPLAY_BLANK) - { - if (strong_x) - *strong_x = 0; - - if (weak_x) - *weak_x = 0; - } - else - { - PangoLayout *layout = gtk_entry_ensure_layout (entry, TRUE); - const gchar *text = pango_layout_get_text (layout); - PangoRectangle strong_pos, weak_pos; - gint index; - - index = g_utf8_offset_to_pointer (text, priv->current_pos + priv->preedit_cursor) - text; - - pango_layout_get_cursor_pos (layout, index, &strong_pos, &weak_pos); - - if (strong_x) - *strong_x = strong_pos.x / PANGO_SCALE; - - if (weak_x) - *weak_x = weak_pos.x / PANGO_SCALE; - } -} - -static gboolean -gtk_entry_get_is_selection_handle_dragged (GtkEntry *entry) -{ - GtkEntryPrivate *priv = gtk_entry_get_instance_private (entry); - GtkTextHandlePosition pos; - - if (!priv->text_handle) - return FALSE; - - if (_gtk_text_handle_get_mode (priv->text_handle) != GTK_TEXT_HANDLE_MODE_SELECTION) - return FALSE; - - if (priv->current_pos >= priv->selection_bound) - pos = GTK_TEXT_HANDLE_POSITION_SELECTION_START; - else - pos = GTK_TEXT_HANDLE_POSITION_SELECTION_END; - - return _gtk_text_handle_get_is_dragged (priv->text_handle, pos); -} - -static void -gtk_entry_get_scroll_limits (GtkEntry *entry, - gint *min_offset, - gint *max_offset) -{ - GtkEntryPrivate *priv = gtk_entry_get_instance_private (entry); - gfloat xalign; - PangoLayout *layout; - PangoLayoutLine *line; - PangoRectangle logical_rect; - gint text_width; - - layout = gtk_entry_ensure_layout (entry, TRUE); - line = pango_layout_get_lines_readonly (layout)->data; - - pango_layout_line_get_extents (line, NULL, &logical_rect); - - /* Display as much text as we can */ - - if (priv->resolved_dir == PANGO_DIRECTION_LTR) - xalign = priv->xalign; - else - xalign = 1.0 - priv->xalign; - - text_width = PANGO_PIXELS(logical_rect.width); - - if (text_width > priv->text_width) - { - *min_offset = 0; - *max_offset = text_width - priv->text_width; - } - else - { - *min_offset = (text_width - priv->text_width) * xalign; - *max_offset = *min_offset; - } -} - -static void -gtk_entry_adjust_scroll (GtkEntry *entry) -{ - GtkEntryPrivate *priv = gtk_entry_get_instance_private (entry); - gint min_offset, max_offset; - gint strong_x, weak_x; - gint strong_xoffset, weak_xoffset; - GtkTextHandleMode handle_mode; - GtkAllocation text_allocation; - - if (!gtk_widget_get_realized (GTK_WIDGET (entry))) - return; - - gtk_entry_get_text_allocation (entry, &text_allocation); - - gtk_entry_get_scroll_limits (entry, &min_offset, &max_offset); - - priv->scroll_offset = CLAMP (priv->scroll_offset, min_offset, max_offset); - - if (gtk_entry_get_is_selection_handle_dragged (entry)) - { - /* The text handle corresponding to the selection bound is - * being dragged, ensure it stays onscreen even if we scroll - * cursors away, this is so both handles can cause content - * to scroll. - */ - strong_x = weak_x = gtk_entry_get_selection_bound_location (entry); - } - else - { - /* And make sure cursors are on screen. Note that the cursor is - * actually drawn one pixel into the INNER_BORDER space on - * the right, when the scroll is at the utmost right. This - * looks better to to me than confining the cursor inside the - * border entirely, though it means that the cursor gets one - * pixel closer to the edge of the widget on the right than - * on the left. This might need changing if one changed - * INNER_BORDER from 2 to 1, as one would do on a - * small-screen-real-estate display. - * - * We always make sure that the strong cursor is on screen, and - * put the weak cursor on screen if possible. - */ - gtk_entry_get_cursor_locations (entry, &strong_x, &weak_x); - } - - strong_xoffset = strong_x - priv->scroll_offset; - - if (strong_xoffset < 0) - { - priv->scroll_offset += strong_xoffset; - strong_xoffset = 0; - } - else if (strong_xoffset > text_allocation.width) - { - priv->scroll_offset += strong_xoffset - text_allocation.width; - strong_xoffset = text_allocation.width; - } - - weak_xoffset = weak_x - priv->scroll_offset; - - if (weak_xoffset < 0 && strong_xoffset - weak_xoffset <= text_allocation.width) - { - priv->scroll_offset += weak_xoffset; - } - else if (weak_xoffset > text_allocation.width && - strong_xoffset - (weak_xoffset - text_allocation.width) >= 0) - { - priv->scroll_offset += weak_xoffset - text_allocation.width; - } - - g_object_notify_by_pspec (G_OBJECT (entry), entry_props[PROP_SCROLL_OFFSET]); - - if (priv->text_handle) - { - handle_mode = _gtk_text_handle_get_mode (priv->text_handle); - - if (handle_mode != GTK_TEXT_HANDLE_MODE_NONE) - gtk_entry_update_handles (entry, handle_mode); - } -} - -static gint -gtk_entry_move_visually (GtkEntry *entry, - gint start, - gint count) -{ - GtkEntryPrivate *priv = gtk_entry_get_instance_private (entry); - gint index; - PangoLayout *layout = gtk_entry_ensure_layout (entry, FALSE); - const gchar *text; - - text = pango_layout_get_text (layout); - - index = g_utf8_offset_to_pointer (text, start) - text; - - while (count != 0) - { - int new_index, new_trailing; - gboolean split_cursor; - gboolean strong; - - g_object_get (gtk_widget_get_settings (GTK_WIDGET (entry)), - "gtk-split-cursor", &split_cursor, - NULL); - - if (split_cursor) - strong = TRUE; - else - { - GdkKeymap *keymap = gdk_display_get_keymap (gtk_widget_get_display (GTK_WIDGET (entry))); - PangoDirection keymap_direction = gdk_keymap_get_direction (keymap); - - strong = keymap_direction == priv->resolved_dir; - } - - if (count > 0) - { - pango_layout_move_cursor_visually (layout, strong, index, 0, 1, &new_index, &new_trailing); - count--; - } - else - { - pango_layout_move_cursor_visually (layout, strong, index, 0, -1, &new_index, &new_trailing); - count++; - } - - if (new_index < 0) - index = 0; - else if (new_index != G_MAXINT) - index = new_index; - - while (new_trailing--) - index = g_utf8_next_char (text + index) - text; - } - - return g_utf8_pointer_to_offset (text, text + index); -} - -static gint -gtk_entry_move_logically (GtkEntry *entry, - gint start, - gint count) -{ - gint new_pos = start; - guint length; - - length = gtk_entry_buffer_get_length (get_buffer (entry)); - - /* Prevent any leak of information */ - if (gtk_entry_get_display_mode (entry) != DISPLAY_NORMAL) - { - new_pos = CLAMP (start + count, 0, length); - } - else - { - PangoLayout *layout = gtk_entry_ensure_layout (entry, FALSE); - const PangoLogAttr *log_attrs; - gint n_attrs; - - log_attrs = pango_layout_get_log_attrs_readonly (layout, &n_attrs); - - while (count > 0 && new_pos < length) - { - do - new_pos++; - while (new_pos < length && !log_attrs[new_pos].is_cursor_position); - - count--; - } - while (count < 0 && new_pos > 0) - { - do - new_pos--; - while (new_pos > 0 && !log_attrs[new_pos].is_cursor_position); - - count++; - } - } - - return new_pos; -} - -static gint -gtk_entry_move_forward_word (GtkEntry *entry, - gint start, - gboolean allow_whitespace) -{ - gint new_pos = start; - guint length; - - length = gtk_entry_buffer_get_length (get_buffer (entry)); - - /* Prevent any leak of information */ - if (gtk_entry_get_display_mode (entry) != DISPLAY_NORMAL) - { - new_pos = length; - } - else if (new_pos < length) - { - PangoLayout *layout = gtk_entry_ensure_layout (entry, FALSE); - const PangoLogAttr *log_attrs; - gint n_attrs; - - log_attrs = pango_layout_get_log_attrs_readonly (layout, &n_attrs); - - /* Find the next word boundary */ - new_pos++; - while (new_pos < n_attrs - 1 && !(log_attrs[new_pos].is_word_end || - (log_attrs[new_pos].is_word_start && allow_whitespace))) - new_pos++; - } - - return new_pos; -} - - -static gint -gtk_entry_move_backward_word (GtkEntry *entry, - gint start, - gboolean allow_whitespace) -{ - gint new_pos = start; - - /* Prevent any leak of information */ - if (gtk_entry_get_display_mode (entry) != DISPLAY_NORMAL) - { - new_pos = 0; - } - else if (start > 0) - { - PangoLayout *layout = gtk_entry_ensure_layout (entry, FALSE); - const PangoLogAttr *log_attrs; - gint n_attrs; - - log_attrs = pango_layout_get_log_attrs_readonly (layout, &n_attrs); - - new_pos = start - 1; - - /* Find the previous word boundary */ - while (new_pos > 0 && !(log_attrs[new_pos].is_word_start || - (log_attrs[new_pos].is_word_end && allow_whitespace))) - new_pos--; - } - - return new_pos; -} - -static void -gtk_entry_delete_whitespace (GtkEntry *entry) -{ - GtkEntryPrivate *priv = gtk_entry_get_instance_private (entry); - PangoLayout *layout = gtk_entry_ensure_layout (entry, FALSE); - const PangoLogAttr *log_attrs; - gint n_attrs; - gint start, end; - - log_attrs = pango_layout_get_log_attrs_readonly (layout, &n_attrs); - - start = end = priv->current_pos; - - while (start > 0 && log_attrs[start-1].is_white) - start--; - - while (end < n_attrs && log_attrs[end].is_white) - end++; - - if (start != end) - gtk_editable_delete_text (GTK_EDITABLE (entry), start, end); -} - - -static void -gtk_entry_select_word (GtkEntry *entry) -{ - GtkEntryPrivate *priv = gtk_entry_get_instance_private (entry); - gint start_pos = gtk_entry_move_backward_word (entry, priv->current_pos, TRUE); - gint end_pos = gtk_entry_move_forward_word (entry, priv->current_pos, TRUE); - - gtk_editable_select_region (GTK_EDITABLE (entry), start_pos, end_pos); -} - -static void -gtk_entry_select_line (GtkEntry *entry) -{ - gtk_editable_select_region (GTK_EDITABLE (entry), 0, -1); -} - -static gint -truncate_multiline (const gchar *text) -{ - gint length; - - for (length = 0; - text[length] && text[length] != '\n' && text[length] != '\r'; - length++); - - return length; -} - -static void -paste_received (GObject *clipboard, - GAsyncResult *result, - gpointer data) -{ - GtkEntry *entry = GTK_ENTRY (data); - GtkEditable *editable = GTK_EDITABLE (entry); - GtkEntryPrivate *priv = gtk_entry_get_instance_private (entry); - char *text; - gint pos, start, end; - gint length = -1; - gboolean popup_completion; - GtkEntryCompletion *completion; - - text = gdk_clipboard_read_text_finish (GDK_CLIPBOARD (clipboard), result, NULL); - if (text == NULL) - { - gtk_widget_error_bell (GTK_WIDGET (entry)); - return; - } - - if (priv->insert_pos >= 0) - { - pos = priv->insert_pos; - gtk_editable_get_selection_bounds (editable, &start, &end); - if (!((start <= pos && pos <= end) || (end <= pos && pos <= start))) - gtk_editable_select_region (editable, pos, pos); - priv->insert_pos = -1; - } - - completion = gtk_entry_get_completion (entry); - - if (priv->truncate_multiline) - length = truncate_multiline (text); - - /* only complete if the selection is at the end */ - popup_completion = (gtk_entry_buffer_get_length (get_buffer (entry)) == - MAX (priv->current_pos, priv->selection_bound)); - - if (completion) - { - if (gtk_widget_get_mapped (completion->priv->popup_window)) - _gtk_entry_completion_popdown (completion); - - if (!popup_completion && completion->priv->changed_id > 0) - g_signal_handler_block (entry, completion->priv->changed_id); - } - - begin_change (entry); - if (gtk_editable_get_selection_bounds (editable, &start, &end)) - gtk_editable_delete_text (editable, start, end); - - pos = priv->current_pos; - gtk_editable_insert_text (editable, text, length, &pos); - gtk_editable_set_position (editable, pos); - end_change (entry); - - if (completion && - !popup_completion && completion->priv->changed_id > 0) - g_signal_handler_unblock (entry, completion->priv->changed_id); - - g_free (text); - g_object_unref (entry); -} - -static void -gtk_entry_paste (GtkEntry *entry, - GdkClipboard *clipboard) -{ - g_object_ref (entry); - gdk_clipboard_read_text_async (clipboard, - NULL, - paste_received, - entry); -} - -static void -gtk_entry_update_primary_selection (GtkEntry *entry) -{ - GtkEntryPrivate *priv = gtk_entry_get_instance_private (entry); - GdkClipboard *clipboard; - gint start, end; - - if (!gtk_widget_get_realized (GTK_WIDGET (entry))) - return; - - clipboard = gtk_widget_get_primary_clipboard (GTK_WIDGET (entry)); - - if (gtk_editable_get_selection_bounds (GTK_EDITABLE (entry), &start, &end)) - { - gdk_clipboard_set_content (clipboard, priv->selection_content); - } - else - { - if (gdk_clipboard_get_content (clipboard) == priv->selection_content) - gdk_clipboard_set_content (clipboard, NULL); - } + gtk_text_reset_im_context (GTK_TEXT (priv->text)); } static void @@ -6496,15 +1843,7 @@ get_buffer (GtkEntry *entry) { GtkEntryPrivate *priv = gtk_entry_get_instance_private (entry); - if (priv->buffer == NULL) - { - GtkEntryBuffer *buffer; - buffer = gtk_entry_buffer_new (NULL, 0); - gtk_entry_set_buffer (entry, buffer); - g_object_unref (buffer); - } - - return priv->buffer; + return gtk_text_get_buffer (GTK_TEXT (priv->text)); } /** @@ -6537,45 +1876,10 @@ gtk_entry_set_buffer (GtkEntry *entry, GtkEntryBuffer *buffer) { GtkEntryPrivate *priv = gtk_entry_get_instance_private (entry); - GObject *obj; - gboolean had_buffer = FALSE; g_return_if_fail (GTK_IS_ENTRY (entry)); - if (buffer) - { - g_return_if_fail (GTK_IS_ENTRY_BUFFER (buffer)); - g_object_ref (buffer); - } - - if (priv->buffer) - { - had_buffer = TRUE; - buffer_disconnect_signals (entry); - g_object_unref (priv->buffer); - } - - priv->buffer = buffer; - - if (priv->buffer) - buffer_connect_signals (entry); - - obj = G_OBJECT (entry); - g_object_freeze_notify (obj); - g_object_notify_by_pspec (obj, entry_props[PROP_BUFFER]); - g_object_notify_by_pspec (obj, entry_props[PROP_TEXT]); - g_object_notify_by_pspec (obj, entry_props[PROP_TEXT_LENGTH]); - g_object_notify_by_pspec (obj, entry_props[PROP_MAX_LENGTH]); - g_object_notify_by_pspec (obj, entry_props[PROP_VISIBILITY]); - g_object_notify_by_pspec (obj, entry_props[PROP_INVISIBLE_CHAR]); - g_object_notify_by_pspec (obj, entry_props[PROP_INVISIBLE_CHAR_SET]); - g_object_thaw_notify (obj); - - if (had_buffer) - { - gtk_editable_set_position (GTK_EDITABLE (entry), 0); - gtk_entry_recompute (entry); - } + gtk_text_set_buffer (GTK_TEXT (priv->text), buffer); } /** @@ -6592,30 +1896,9 @@ void gtk_entry_set_text (GtkEntry *entry, const gchar *text) { - gint tmp_pos; - GtkEntryCompletion *completion; + GtkEntryPrivate *priv = gtk_entry_get_instance_private (entry); - g_return_if_fail (GTK_IS_ENTRY (entry)); - g_return_if_fail (text != NULL); - - /* Actually setting the text will affect the cursor and selection; - * if the contents don't actually change, this will look odd to the user. - */ - if (strcmp (gtk_entry_buffer_get_text (get_buffer (entry)), text) == 0) - return; - - completion = gtk_entry_get_completion (entry); - if (completion && completion->priv->changed_id > 0) - g_signal_handler_block (entry, completion->priv->changed_id); - - begin_change (entry); - gtk_editable_delete_text (GTK_EDITABLE (entry), 0, -1); - tmp_pos = 0; - gtk_editable_insert_text (GTK_EDITABLE (entry), text, strlen (text), &tmp_pos); - end_change (entry); - - if (completion && completion->priv->changed_id > 0) - g_signal_handler_unblock (entry, completion->priv->changed_id); + gtk_editable_set_text (GTK_EDITABLE (priv->text), text); } /** @@ -6646,15 +1929,7 @@ gtk_entry_set_visibility (GtkEntry *entry, g_return_if_fail (GTK_IS_ENTRY (entry)); - visible = visible != FALSE; - - if (priv->visible != visible) - { - priv->visible = visible; - - g_object_notify_by_pspec (G_OBJECT (entry), entry_props[PROP_VISIBILITY]); - gtk_entry_recompute (entry); - } + gtk_text_set_visibility (GTK_TEXT (priv->text), visible); } /** @@ -6673,8 +1948,7 @@ gtk_entry_get_visibility (GtkEntry *entry) g_return_val_if_fail (GTK_IS_ENTRY (entry), FALSE); - return priv->visible; - + return gtk_text_get_visibility (GTK_TEXT (priv->text)); } /** @@ -6698,18 +1972,7 @@ gtk_entry_set_invisible_char (GtkEntry *entry, g_return_if_fail (GTK_IS_ENTRY (entry)); - if (!priv->invisible_char_set) - { - priv->invisible_char_set = TRUE; - g_object_notify_by_pspec (G_OBJECT (entry), entry_props[PROP_INVISIBLE_CHAR_SET]); - } - - if (ch == priv->invisible_char) - return; - - priv->invisible_char = ch; - g_object_notify_by_pspec (G_OBJECT (entry), entry_props[PROP_INVISIBLE_CHAR]); - gtk_entry_recompute (entry); + gtk_text_set_invisible_char (GTK_TEXT (priv->text), ch); } /** @@ -6729,7 +1992,7 @@ gtk_entry_get_invisible_char (GtkEntry *entry) g_return_val_if_fail (GTK_IS_ENTRY (entry), 0); - return priv->invisible_char; + return gtk_text_get_invisible_char (GTK_TEXT (priv->text)); } /** @@ -6744,24 +2007,10 @@ void gtk_entry_unset_invisible_char (GtkEntry *entry) { GtkEntryPrivate *priv = gtk_entry_get_instance_private (entry); - gunichar ch; g_return_if_fail (GTK_IS_ENTRY (entry)); - if (!priv->invisible_char_set) - return; - - priv->invisible_char_set = FALSE; - ch = find_invisible_char (GTK_WIDGET (entry)); - - if (priv->invisible_char != ch) - { - priv->invisible_char = ch; - g_object_notify_by_pspec (G_OBJECT (entry), entry_props[PROP_INVISIBLE_CHAR]); - } - - g_object_notify_by_pspec (G_OBJECT (entry), entry_props[PROP_INVISIBLE_CHAR_SET]); - gtk_entry_recompute (entry); + gtk_text_unset_invisible_char (GTK_TEXT (priv->text)); } /** @@ -6779,12 +2028,7 @@ gtk_entry_set_overwrite_mode (GtkEntry *entry, g_return_if_fail (GTK_IS_ENTRY (entry)); - if (priv->overwrite_mode == overwrite) - return; - - gtk_entry_toggle_overwrite (entry); - - g_object_notify_by_pspec (G_OBJECT (entry), entry_props[PROP_OVERWRITE_MODE]); + gtk_text_set_overwrite_mode (GTK_TEXT (priv->text), overwrite); } /** @@ -6802,7 +2046,7 @@ gtk_entry_get_overwrite_mode (GtkEntry *entry) g_return_val_if_fail (GTK_IS_ENTRY (entry), FALSE); - return priv->overwrite_mode; + return gtk_text_get_overwrite_mode (GTK_TEXT (priv->text)); } @@ -6917,13 +2161,7 @@ gtk_entry_set_activates_default (GtkEntry *entry, g_return_if_fail (GTK_IS_ENTRY (entry)); - setting = setting != FALSE; - - if (setting != priv->activates_default) - { - priv->activates_default = setting; - g_object_notify_by_pspec (G_OBJECT (entry), entry_props[PROP_ACTIVATES_DEFAULT]); - } + gtk_text_set_activates_default (GTK_TEXT (priv->text), setting); } /** @@ -6941,7 +2179,7 @@ gtk_entry_get_activates_default (GtkEntry *entry) g_return_val_if_fail (GTK_IS_ENTRY (entry), FALSE); - return priv->activates_default; + return gtk_text_get_activates_default (GTK_TEXT (priv->text)); } /** @@ -6963,12 +2201,7 @@ gtk_entry_set_width_chars (GtkEntry *entry, g_return_if_fail (GTK_IS_ENTRY (entry)); - if (priv->width_chars != n_chars) - { - priv->width_chars = n_chars; - g_object_notify_by_pspec (G_OBJECT (entry), entry_props[PROP_WIDTH_CHARS]); - gtk_widget_queue_resize (GTK_WIDGET (entry)); - } + gtk_editable_set_width_chars (GTK_EDITABLE (priv->text), n_chars); } /** @@ -6986,7 +2219,7 @@ gtk_entry_get_width_chars (GtkEntry *entry) g_return_val_if_fail (GTK_IS_ENTRY (entry), 0); - return priv->width_chars; + return gtk_editable_get_width_chars (GTK_EDITABLE (priv->text)); } /** @@ -7004,12 +2237,7 @@ gtk_entry_set_max_width_chars (GtkEntry *entry, g_return_if_fail (GTK_IS_ENTRY (entry)); - if (priv->max_width_chars != n_chars) - { - priv->max_width_chars = n_chars; - g_object_notify_by_pspec (G_OBJECT (entry), entry_props[PROP_MAX_WIDTH_CHARS]); - gtk_widget_queue_resize (GTK_WIDGET (entry)); - } + gtk_editable_set_max_width_chars (GTK_EDITABLE (priv->text), n_chars); } /** @@ -7028,7 +2256,7 @@ gtk_entry_get_max_width_chars (GtkEntry *entry) g_return_val_if_fail (GTK_IS_ENTRY (entry), 0); - return priv->max_width_chars; + return gtk_editable_get_max_width_chars (GTK_EDITABLE (priv->text)); } /** @@ -7080,144 +2308,6 @@ gtk_entry_get_has_frame (GtkEntry *entry) return !gtk_style_context_has_class (context, GTK_STYLE_CLASS_FLAT); } -/** - * gtk_entry_get_layout: - * @entry: a #GtkEntry - * - * Gets the #PangoLayout used to display the entry. - * The layout is useful to e.g. convert text positions to - * pixel positions, in combination with gtk_entry_get_layout_offsets(). - * The returned layout is owned by the entry and must not be - * modified or freed by the caller. - * - * Keep in mind that the layout text may contain a preedit string, so - * gtk_entry_layout_index_to_text_index() and - * gtk_entry_text_index_to_layout_index() are needed to convert byte - * indices in the layout to byte indices in the entry contents. - * - * Returns: (transfer none): the #PangoLayout for this entry - **/ -PangoLayout* -gtk_entry_get_layout (GtkEntry *entry) -{ - PangoLayout *layout; - - g_return_val_if_fail (GTK_IS_ENTRY (entry), NULL); - - layout = gtk_entry_ensure_layout (entry, TRUE); - - return layout; -} - - -/** - * gtk_entry_layout_index_to_text_index: - * @entry: a #GtkEntry - * @layout_index: byte index into the entry layout text - * - * Converts from a position in the entry’s #PangoLayout (returned by - * gtk_entry_get_layout()) to a position in the entry contents - * (returned by gtk_entry_get_text()). - * - * Returns: byte index into the entry contents - **/ -gint -gtk_entry_layout_index_to_text_index (GtkEntry *entry, - gint layout_index) -{ - GtkEntryPrivate *priv = gtk_entry_get_instance_private (entry); - PangoLayout *layout; - const gchar *text; - gint cursor_index; - - g_return_val_if_fail (GTK_IS_ENTRY (entry), 0); - - layout = gtk_entry_ensure_layout (entry, TRUE); - text = pango_layout_get_text (layout); - cursor_index = g_utf8_offset_to_pointer (text, priv->current_pos) - text; - - if (layout_index >= cursor_index && priv->preedit_length) - { - if (layout_index >= cursor_index + priv->preedit_length) - layout_index -= priv->preedit_length; - else - layout_index = cursor_index; - } - - return layout_index; -} - -/** - * gtk_entry_text_index_to_layout_index: - * @entry: a #GtkEntry - * @text_index: byte index into the entry contents - * - * Converts from a position in the entry contents (returned - * by gtk_entry_get_text()) to a position in the - * entry’s #PangoLayout (returned by gtk_entry_get_layout(), - * with text retrieved via pango_layout_get_text()). - * - * Returns: byte index into the entry layout text - **/ -gint -gtk_entry_text_index_to_layout_index (GtkEntry *entry, - gint text_index) -{ - GtkEntryPrivate *priv = gtk_entry_get_instance_private (entry); - PangoLayout *layout; - const gchar *text; - gint cursor_index; - - g_return_val_if_fail (GTK_IS_ENTRY (entry), 0); - - layout = gtk_entry_ensure_layout (entry, TRUE); - text = pango_layout_get_text (layout); - cursor_index = g_utf8_offset_to_pointer (text, priv->current_pos) - text; - - if (text_index > cursor_index) - text_index += priv->preedit_length; - - return text_index; -} - -/** - * gtk_entry_get_layout_offsets: - * @entry: a #GtkEntry - * @x: (out) (allow-none): location to store X offset of layout, or %NULL - * @y: (out) (allow-none): location to store Y offset of layout, or %NULL - * - * - * Obtains the position of the #PangoLayout used to render text - * in the entry, in widget coordinates. Useful if you want to line - * up the text in an entry with some other text, e.g. when using the - * entry to implement editable cells in a sheet widget. - * - * Also useful to convert mouse events into coordinates inside the - * #PangoLayout, e.g. to take some action if some part of the entry text - * is clicked. - * - * Note that as the user scrolls around in the entry the offsets will - * change; you’ll need to connect to the “notify::scroll-offset” - * signal to track this. Remember when using the #PangoLayout - * functions you need to convert to and from pixels using - * PANGO_PIXELS() or #PANGO_SCALE. - * - * Keep in mind that the layout text may contain a preedit string, so - * gtk_entry_layout_index_to_text_index() and - * gtk_entry_text_index_to_layout_index() are needed to convert byte - * indices in the layout to byte indices in the entry contents. - **/ -void -gtk_entry_get_layout_offsets (GtkEntry *entry, - gint *x, - gint *y) -{ - g_return_if_fail (GTK_IS_ENTRY (entry)); - - get_layout_position (entry, x, y); -} - - /** * gtk_entry_set_alignment: * @entry: a #GtkEntry @@ -7235,17 +2325,7 @@ gtk_entry_set_alignment (GtkEntry *entry, gfloat xalign) g_return_if_fail (GTK_IS_ENTRY (entry)); - if (xalign < 0.0) - xalign = 0.0; - else if (xalign > 1.0) - xalign = 1.0; - - if (xalign != priv->xalign) - { - priv->xalign = xalign; - gtk_entry_recompute (entry); - g_object_notify_by_pspec (G_OBJECT (entry), entry_props[PROP_XALIGN]); - } + gtk_editable_set_alignment (GTK_EDITABLE (priv->text), xalign); } /** @@ -7263,7 +2343,7 @@ gtk_entry_get_alignment (GtkEntry *entry) g_return_val_if_fail (GTK_IS_ENTRY (entry), 0.0); - return priv->xalign; + return gtk_editable_get_alignment (GTK_EDITABLE (priv->text)); } /** @@ -8065,837 +3145,6 @@ gtk_entry_query_tooltip (GtkWidget *widget, tooltip); } - -/* Quick hack of a popup menu - */ -static void -activate_cb (GtkWidget *menuitem, - GtkEntry *entry) -{ - const gchar *signal; - - signal = g_object_get_qdata (G_OBJECT (menuitem), quark_gtk_signal); - g_signal_emit_by_name (entry, signal); -} - - -static gboolean -gtk_entry_mnemonic_activate (GtkWidget *widget, - gboolean group_cycling) -{ - gtk_widget_grab_focus (widget); - return GDK_EVENT_STOP; -} - -static void -append_action_signal (GtkEntry *entry, - GtkWidget *menu, - const gchar *label, - const gchar *signal, - gboolean sensitive) -{ - GtkWidget *menuitem = gtk_menu_item_new_with_mnemonic (label); - - g_object_set_qdata (G_OBJECT (menuitem), quark_gtk_signal, (char *)signal); - g_signal_connect (menuitem, "activate", - G_CALLBACK (activate_cb), entry); - - gtk_widget_set_sensitive (menuitem, sensitive); - - gtk_widget_show (menuitem); - gtk_menu_shell_append (GTK_MENU_SHELL (menu), menuitem); -} - -static void -popup_menu_detach (GtkWidget *attach_widget, - GtkMenu *menu) -{ - GtkEntry *entry_attach = GTK_ENTRY (attach_widget); - GtkEntryPrivate *priv_attach = gtk_entry_get_instance_private (entry_attach); - - priv_attach->popup_menu = NULL; -} - -static void -gtk_entry_do_popup (GtkEntry *entry, - const GdkEvent *event) -{ - GtkEntryPrivate *info_entry_priv = gtk_entry_get_instance_private (entry); - GdkEvent *trigger_event; - - /* In order to know what entries we should make sensitive, we - * ask for the current targets of the clipboard, and when - * we get them, then we actually pop up the menu. - */ - trigger_event = event ? gdk_event_copy (event) : gtk_get_current_event (); - - if (gtk_widget_get_realized (GTK_WIDGET (entry))) - { - DisplayMode mode; - gboolean clipboard_contains_text; - GtkWidget *menu; - GtkWidget *menuitem; - - clipboard_contains_text = gdk_content_formats_contain_gtype (gdk_clipboard_get_formats (gtk_widget_get_clipboard (GTK_WIDGET (entry))), - G_TYPE_STRING); - if (info_entry_priv->popup_menu) - gtk_widget_destroy (info_entry_priv->popup_menu); - - info_entry_priv->popup_menu = menu = gtk_menu_new (); - gtk_style_context_add_class (gtk_widget_get_style_context (menu), - GTK_STYLE_CLASS_CONTEXT_MENU); - - gtk_menu_attach_to_widget (GTK_MENU (menu), GTK_WIDGET (entry), popup_menu_detach); - - mode = gtk_entry_get_display_mode (entry); - append_action_signal (entry, menu, _("Cu_t"), "cut-clipboard", - info_entry_priv->editable && mode == DISPLAY_NORMAL && - info_entry_priv->current_pos != info_entry_priv->selection_bound); - - append_action_signal (entry, menu, _("_Copy"), "copy-clipboard", - mode == DISPLAY_NORMAL && - info_entry_priv->current_pos != info_entry_priv->selection_bound); - - append_action_signal (entry, menu, _("_Paste"), "paste-clipboard", - info_entry_priv->editable && clipboard_contains_text); - - menuitem = gtk_menu_item_new_with_mnemonic (_("_Delete")); - gtk_widget_set_sensitive (menuitem, info_entry_priv->editable && info_entry_priv->current_pos != info_entry_priv->selection_bound); - g_signal_connect_swapped (menuitem, "activate", - G_CALLBACK (gtk_entry_delete_cb), entry); - gtk_widget_show (menuitem); - gtk_menu_shell_append (GTK_MENU_SHELL (menu), menuitem); - - menuitem = gtk_separator_menu_item_new (); - gtk_widget_show (menuitem); - gtk_menu_shell_append (GTK_MENU_SHELL (menu), menuitem); - - menuitem = gtk_menu_item_new_with_mnemonic (_("Select _All")); - gtk_widget_set_sensitive (menuitem, gtk_entry_buffer_get_length (info_entry_priv->buffer) > 0); - g_signal_connect_swapped (menuitem, "activate", - G_CALLBACK (gtk_entry_select_all), entry); - gtk_widget_show (menuitem); - gtk_menu_shell_append (GTK_MENU_SHELL (menu), menuitem); - - if (info_entry_priv->show_emoji_icon || - (gtk_entry_get_input_hints (entry) & GTK_INPUT_HINT_NO_EMOJI) == 0) - { - menuitem = gtk_menu_item_new_with_mnemonic (_("Insert _Emoji")); - gtk_widget_set_sensitive (menuitem, - mode == DISPLAY_NORMAL && - info_entry_priv->editable); - g_signal_connect_swapped (menuitem, "activate", - G_CALLBACK (gtk_entry_insert_emoji), entry); - gtk_widget_show (menuitem); - gtk_menu_shell_append (GTK_MENU_SHELL (menu), menuitem); - } - - g_signal_emit (entry, signals[POPULATE_POPUP], 0, menu); - - if (trigger_event && gdk_event_triggers_context_menu (trigger_event)) - gtk_menu_popup_at_pointer (GTK_MENU (menu), trigger_event); - else - { - gtk_menu_popup_at_widget (GTK_MENU (menu), - GTK_WIDGET (entry), - GDK_GRAVITY_SOUTH_EAST, - GDK_GRAVITY_NORTH_WEST, - trigger_event); - - gtk_menu_shell_select_first (GTK_MENU_SHELL (menu), FALSE); - } - } - - g_clear_object (&trigger_event); -} - -static gboolean -gtk_entry_popup_menu (GtkWidget *widget) -{ - gtk_entry_do_popup (GTK_ENTRY (widget), NULL); - return GDK_EVENT_STOP; -} - -static void -show_or_hide_handles (GtkWidget *popover, - GParamSpec *pspec, - GtkEntry *entry) -{ - GtkEntryPrivate *priv = gtk_entry_get_instance_private (entry); - gboolean visible; - GtkTextHandle *handle; - GtkTextHandleMode mode; - - visible = gtk_widget_get_visible (popover); - - handle = priv->text_handle; - mode = _gtk_text_handle_get_mode (handle); - - if (mode == GTK_TEXT_HANDLE_MODE_CURSOR) - { - _gtk_text_handle_set_visible (handle, GTK_TEXT_HANDLE_POSITION_CURSOR, !visible); - } - else if (mode == GTK_TEXT_HANDLE_MODE_SELECTION) - { - _gtk_text_handle_set_visible (handle, GTK_TEXT_HANDLE_POSITION_SELECTION_START, !visible); - _gtk_text_handle_set_visible (handle, GTK_TEXT_HANDLE_POSITION_SELECTION_END, !visible); - } -} - -static void -activate_bubble_cb (GtkWidget *item, - GtkEntry *entry) -{ - GtkEntryPrivate *priv = gtk_entry_get_instance_private (entry); - const gchar *signal; - - signal = g_object_get_qdata (G_OBJECT (item), quark_gtk_signal); - gtk_widget_hide (priv->selection_bubble); - if (strcmp (signal, "select-all") == 0) - gtk_entry_select_all (entry); - else - g_signal_emit_by_name (entry, signal); -} - -static void -append_bubble_action (GtkEntry *entry, - GtkWidget *toolbar, - const gchar *label, - const gchar *icon_name, - const gchar *signal, - gboolean sensitive) -{ - GtkWidget *item, *image; - - item = gtk_button_new (); - gtk_widget_set_focus_on_click (item, FALSE); - image = gtk_image_new_from_icon_name (icon_name); - gtk_widget_show (image); - gtk_container_add (GTK_CONTAINER (item), image); - gtk_widget_set_tooltip_text (item, label); - gtk_style_context_add_class (gtk_widget_get_style_context (item), "image-button"); - g_object_set_qdata (G_OBJECT (item), quark_gtk_signal, (char *)signal); - g_signal_connect (item, "clicked", G_CALLBACK (activate_bubble_cb), entry); - gtk_widget_set_sensitive (GTK_WIDGET (item), sensitive); - gtk_widget_show (GTK_WIDGET (item)); - gtk_container_add (GTK_CONTAINER (toolbar), item); -} - -static gboolean -gtk_entry_selection_bubble_popup_show (gpointer user_data) -{ - GtkEntry *entry = user_data; - GtkEntryPrivate *priv = gtk_entry_get_instance_private (entry); - cairo_rectangle_int_t rect; - GtkAllocation allocation; - gint start_x, end_x; - gboolean has_selection; - gboolean has_clipboard; - gboolean all_selected; - DisplayMode mode; - GtkWidget *box; - GtkWidget *toolbar; - gint length, start, end; - GtkAllocation text_allocation; - - gtk_entry_get_text_allocation (entry, &text_allocation); - - has_selection = gtk_editable_get_selection_bounds (GTK_EDITABLE (entry), &start, &end); - length = gtk_entry_buffer_get_length (get_buffer (entry)); - all_selected = (start == 0) && (end == length); - - if (!has_selection && !priv->editable) - { - priv->selection_bubble_timeout_id = 0; - return G_SOURCE_REMOVE; - } - - if (priv->selection_bubble) - gtk_widget_destroy (priv->selection_bubble); - - priv->selection_bubble = gtk_popover_new (GTK_WIDGET (entry)); - gtk_style_context_add_class (gtk_widget_get_style_context (priv->selection_bubble), - GTK_STYLE_CLASS_TOUCH_SELECTION); - gtk_popover_set_position (GTK_POPOVER (priv->selection_bubble), GTK_POS_BOTTOM); - gtk_popover_set_modal (GTK_POPOVER (priv->selection_bubble), FALSE); - g_signal_connect (priv->selection_bubble, "notify::visible", - G_CALLBACK (show_or_hide_handles), entry); - - box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 5); - g_object_set (box, "margin", 10, NULL); - gtk_widget_show (box); - toolbar = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 5); - gtk_widget_show (toolbar); - gtk_container_add (GTK_CONTAINER (priv->selection_bubble), box); - gtk_container_add (GTK_CONTAINER (box), toolbar); - - has_clipboard = gdk_content_formats_contain_gtype (gdk_clipboard_get_formats (gtk_widget_get_clipboard (GTK_WIDGET (entry))), - G_TYPE_STRING); - mode = gtk_entry_get_display_mode (entry); - - if (priv->editable && has_selection && mode == DISPLAY_NORMAL) - append_bubble_action (entry, toolbar, _("Select all"), "edit-select-all-symbolic", "select-all", !all_selected); - - if (priv->editable && has_selection && mode == DISPLAY_NORMAL) - append_bubble_action (entry, toolbar, _("Cut"), "edit-cut-symbolic", "cut-clipboard", TRUE); - - if (has_selection && mode == DISPLAY_NORMAL) - append_bubble_action (entry, toolbar, _("Copy"), "edit-copy-symbolic", "copy-clipboard", TRUE); - - if (priv->editable) - append_bubble_action (entry, toolbar, _("Paste"), "edit-paste-symbolic", "paste-clipboard", has_clipboard); - - if (priv->populate_all) - g_signal_emit (entry, signals[POPULATE_POPUP], 0, box); - - gtk_widget_get_allocation (GTK_WIDGET (entry), &allocation); - - gtk_entry_get_cursor_locations (entry, &start_x, NULL); - - start_x -= priv->scroll_offset; - start_x = CLAMP (start_x, 0, text_allocation.width); - rect.y = text_allocation.y - allocation.y; - rect.height = text_allocation.height; - - if (has_selection) - { - end_x = gtk_entry_get_selection_bound_location (entry) - priv->scroll_offset; - end_x = CLAMP (end_x, 0, text_allocation.width); - - rect.x = text_allocation.x - allocation.x + MIN (start_x, end_x); - rect.width = ABS (end_x - start_x); - } - else - { - rect.x = text_allocation.x - allocation.x + start_x; - rect.width = 0; - } - - rect.x -= 5; - rect.y -= 5; - rect.width += 10; - rect.height += 10; - - gtk_popover_set_pointing_to (GTK_POPOVER (priv->selection_bubble), &rect); - gtk_widget_show (priv->selection_bubble); - - priv->selection_bubble_timeout_id = 0; - - return G_SOURCE_REMOVE; -} - -static void -gtk_entry_selection_bubble_popup_unset (GtkEntry *entry) -{ - GtkEntryPrivate *priv = gtk_entry_get_instance_private (entry); - - if (priv->selection_bubble) - gtk_widget_hide (priv->selection_bubble); - - if (priv->selection_bubble_timeout_id) - { - g_source_remove (priv->selection_bubble_timeout_id); - priv->selection_bubble_timeout_id = 0; - } -} - -static void -gtk_entry_selection_bubble_popup_set (GtkEntry *entry) -{ - GtkEntryPrivate *priv = gtk_entry_get_instance_private (entry); - - if (priv->selection_bubble_timeout_id) - g_source_remove (priv->selection_bubble_timeout_id); - - priv->selection_bubble_timeout_id = - g_timeout_add (50, gtk_entry_selection_bubble_popup_show, entry); - g_source_set_name_by_id (priv->selection_bubble_timeout_id, "[gtk] gtk_entry_selection_bubble_popup_cb"); -} - -static void -gtk_entry_drag_begin (GtkWidget *widget, - GdkDrag *drag) -{ - GtkEntry *entry = GTK_ENTRY (widget); - GtkEntryPrivate *priv = gtk_entry_get_instance_private (entry); - gchar *text; - gint i; - - for (i = 0; i < MAX_ICONS; i++) - { - EntryIconInfo *icon_info = priv->icons[i]; - - if (icon_info != NULL) - { - if (icon_info->in_drag) - { - gtk_drag_set_icon_definition (drag, - gtk_image_get_definition (GTK_IMAGE (icon_info->widget)), - -2, -2); - return; - } - } - } - - text = _gtk_entry_get_selected_text (entry); - - if (text) - { - gint *ranges, n_ranges; - GdkPaintable *paintable; - - paintable = gtk_text_util_create_drag_icon (widget, text, -1); - gtk_entry_get_pixel_ranges (entry, &ranges, &n_ranges); - - gtk_drag_set_icon_paintable (drag, - paintable, - priv->drag_start_x - ranges[0], - priv->drag_start_y); - - g_free (ranges); - g_object_unref (paintable); - g_free (text); - } -} - -static void -gtk_entry_drag_end (GtkWidget *widget, - GdkDrag *drag) -{ - GtkEntry *entry = GTK_ENTRY (widget); - GtkEntryPrivate *priv = gtk_entry_get_instance_private (entry); - gint i; - - for (i = 0; i < MAX_ICONS; i++) - { - EntryIconInfo *icon_info = priv->icons[i]; - - if (icon_info != NULL) - icon_info->in_drag = 0; - } -} - -static void -gtk_entry_drag_leave (GtkWidget *widget, - GdkDrop *drop) -{ - GtkEntry *entry = GTK_ENTRY (widget); - GtkEntryPrivate *priv = gtk_entry_get_instance_private (entry); - - gtk_drag_unhighlight (widget); - priv->dnd_position = -1; - gtk_widget_queue_draw (widget); -} - -static gboolean -gtk_entry_drag_drop (GtkWidget *widget, - GdkDrop *drop, - gint x, - gint y) -{ - GtkEntry *entry = GTK_ENTRY (widget); - GtkEntryPrivate *priv = gtk_entry_get_instance_private (entry); - GdkAtom target = NULL; - - if (priv->editable) - target = gtk_drag_dest_find_target (widget, drop, NULL); - - if (target != NULL) - { - priv->drop_position = gtk_entry_find_position (entry, x + priv->scroll_offset); - gtk_drag_get_data (widget, drop, target); - } - else - gdk_drop_finish (drop, 0); - - return TRUE; -} - -static gboolean -gtk_entry_drag_motion (GtkWidget *widget, - GdkDrop *drop, - gint x, - gint y) -{ - GtkEntry *entry = GTK_ENTRY (widget); - GtkEntryPrivate *priv = gtk_entry_get_instance_private (entry); - GdkDragAction suggested_action; - gint new_position, old_position; - gint sel1, sel2; - - old_position = priv->dnd_position; - new_position = gtk_entry_find_position (entry, x + priv->scroll_offset); - - if (priv->editable && - gtk_drag_dest_find_target (widget, drop, NULL) != NULL) - { - suggested_action = GDK_ACTION_COPY | GDK_ACTION_MOVE; - - if (!gtk_editable_get_selection_bounds (GTK_EDITABLE (entry), &sel1, &sel2) || - new_position < sel1 || new_position > sel2) - { - priv->dnd_position = new_position; - } - else - { - priv->dnd_position = -1; - } - } - else - { - /* Entry not editable, or no text */ - suggested_action = 0; - priv->dnd_position = -1; - } - - gdk_drop_status (drop, suggested_action); - if (suggested_action == 0) - gtk_drag_unhighlight (widget); - else - gtk_drag_highlight (widget); - - if (priv->dnd_position != old_position) - gtk_widget_queue_draw (widget); - - return TRUE; -} - -static GdkDragAction -gtk_entry_get_action (GtkEntry *entry, - GdkDrop *drop) -{ - GtkWidget *widget = GTK_WIDGET (entry); - GdkDrag *drag = gdk_drop_get_drag (drop); - GtkWidget *source_widget = gtk_drag_get_source_widget (drag); - GdkDragAction actions; - - actions = gdk_drop_get_actions (drop); - - if (source_widget == widget && - actions & GDK_ACTION_MOVE) - return GDK_ACTION_MOVE; - - if (actions & GDK_ACTION_COPY) - return GDK_ACTION_COPY; - - if (actions & GDK_ACTION_MOVE) - return GDK_ACTION_MOVE; - - return 0; -} - -static void -gtk_entry_drag_data_received (GtkWidget *widget, - GdkDrop *drop, - GtkSelectionData *selection_data) -{ - GtkEntry *entry = GTK_ENTRY (widget); - GtkEntryPrivate *priv = gtk_entry_get_instance_private (entry); - GtkEditable *editable = GTK_EDITABLE (widget); - GdkDragAction action; - gchar *str; - - str = (gchar *) gtk_selection_data_get_text (selection_data); - action = gtk_entry_get_action (entry, drop); - - if (action && str && priv->editable) - { - gint sel1, sel2; - gint length = -1; - - if (priv->truncate_multiline) - length = truncate_multiline (str); - - if (!gtk_editable_get_selection_bounds (editable, &sel1, &sel2) || - priv->drop_position < sel1 || priv->drop_position > sel2) - { - gtk_editable_insert_text (editable, str, length, &priv->drop_position); - } - else - { - /* Replacing selection */ - begin_change (entry); - gtk_editable_delete_text (editable, sel1, sel2); - gtk_editable_insert_text (editable, str, length, &sel1); - end_change (entry); - } - - gdk_drop_finish (drop, action); - } - else - { - /* Drag and drop didn't happen! */ - gdk_drop_finish (drop, 0); - } - - g_free (str); -} - -static void -gtk_entry_drag_data_get (GtkWidget *widget, - GdkDrag *drag, - GtkSelectionData *selection_data) -{ - GtkEntry *entry = GTK_ENTRY (widget); - GtkEntryPrivate *priv = gtk_entry_get_instance_private (entry); - GtkEditable *editable = GTK_EDITABLE (widget); - gint sel_start, sel_end; - gint i; - - /* If there is an icon drag going on, exit early. */ - for (i = 0; i < MAX_ICONS; i++) - { - EntryIconInfo *icon_info = priv->icons[i]; - - if (icon_info != NULL) - { - if (icon_info->in_drag) - return; - } - } - - if (gtk_editable_get_selection_bounds (editable, &sel_start, &sel_end)) - { - gchar *str = _gtk_entry_get_display_text (GTK_ENTRY (widget), sel_start, sel_end); - - gtk_selection_data_set_text (selection_data, str, -1); - - g_free (str); - } - -} - -static void -gtk_entry_drag_data_delete (GtkWidget *widget, - GdkDrag *drag) -{ - GtkEntry *entry = GTK_ENTRY (widget); - GtkEntryPrivate *priv = gtk_entry_get_instance_private (entry); - GtkEditable *editable = GTK_EDITABLE (widget); - gint sel_start, sel_end; - gint i; - - /* If there is an icon drag going on, exit early. */ - for (i = 0; i < MAX_ICONS; i++) - { - EntryIconInfo *icon_info = priv->icons[i]; - - if (icon_info != NULL) - { - if (icon_info->in_drag) - return; - } - } - - if (priv->editable && - gtk_editable_get_selection_bounds (editable, &sel_start, &sel_end)) - gtk_editable_delete_text (editable, sel_start, sel_end); -} - -/* We display the cursor when - * - * - the selection is empty, AND - * - the widget has focus - */ - -#define CURSOR_ON_MULTIPLIER 2 -#define CURSOR_OFF_MULTIPLIER 1 -#define CURSOR_PEND_MULTIPLIER 3 -#define CURSOR_DIVIDER 3 - -static gboolean -cursor_blinks (GtkEntry *entry) -{ - GtkEntryPrivate *priv = gtk_entry_get_instance_private (entry); - - if (gtk_widget_has_focus (GTK_WIDGET (entry)) && - priv->editable && - priv->selection_bound == priv->current_pos) - { - GtkSettings *settings; - gboolean blink; - - settings = gtk_widget_get_settings (GTK_WIDGET (entry)); - g_object_get (settings, "gtk-cursor-blink", &blink, NULL); - - return blink; - } - else - return FALSE; -} - -static gboolean -get_middle_click_paste (GtkEntry *entry) -{ - GtkSettings *settings; - gboolean paste; - - settings = gtk_widget_get_settings (GTK_WIDGET (entry)); - g_object_get (settings, "gtk-enable-primary-paste", &paste, NULL); - - return paste; -} - -static gint -get_cursor_time (GtkEntry *entry) -{ - GtkSettings *settings = gtk_widget_get_settings (GTK_WIDGET (entry)); - gint time; - - g_object_get (settings, "gtk-cursor-blink-time", &time, NULL); - - return time; -} - -static gint -get_cursor_blink_timeout (GtkEntry *entry) -{ - GtkSettings *settings = gtk_widget_get_settings (GTK_WIDGET (entry)); - gint timeout; - - g_object_get (settings, "gtk-cursor-blink-timeout", &timeout, NULL); - - return timeout; -} - -static void -show_cursor (GtkEntry *entry) -{ - GtkEntryPrivate *priv = gtk_entry_get_instance_private (entry); - GtkWidget *widget; - - if (!priv->cursor_visible) - { - priv->cursor_visible = TRUE; - - widget = GTK_WIDGET (entry); - if (gtk_widget_has_focus (widget) && priv->selection_bound == priv->current_pos) - gtk_widget_queue_draw (widget); - } -} - -static void -hide_cursor (GtkEntry *entry) -{ - GtkEntryPrivate *priv = gtk_entry_get_instance_private (entry); - GtkWidget *widget; - - if (priv->cursor_visible) - { - priv->cursor_visible = FALSE; - - widget = GTK_WIDGET (entry); - if (gtk_widget_has_focus (widget) && priv->selection_bound == priv->current_pos) - gtk_widget_queue_draw (widget); - } -} - -/* - * Blink! - */ -static gint -blink_cb (gpointer data) -{ - GtkEntry *entry = data; - GtkEntryPrivate *priv = gtk_entry_get_instance_private (entry); - gint blink_timeout; - - if (!gtk_widget_has_focus (GTK_WIDGET (entry))) - { - g_warning ("GtkEntry - did not receive a focus-out event.\n" - "If you handle this event, you must return\n" - "GDK_EVENT_PROPAGATE so the entry gets the event as well"); - - gtk_entry_check_cursor_blink (entry); - - return G_SOURCE_REMOVE; - } - - g_assert (priv->selection_bound == priv->current_pos); - - blink_timeout = get_cursor_blink_timeout (entry); - if (priv->blink_time > 1000 * blink_timeout && - blink_timeout < G_MAXINT/1000) - { - /* we've blinked enough without the user doing anything, stop blinking */ - show_cursor (entry); - priv->blink_timeout = 0; - } - else if (priv->cursor_visible) - { - hide_cursor (entry); - priv->blink_timeout = g_timeout_add (get_cursor_time (entry) * CURSOR_OFF_MULTIPLIER / CURSOR_DIVIDER, - blink_cb, - entry); - g_source_set_name_by_id (priv->blink_timeout, "[gtk] blink_cb"); - } - else - { - show_cursor (entry); - priv->blink_time += get_cursor_time (entry); - priv->blink_timeout = g_timeout_add (get_cursor_time (entry) * CURSOR_ON_MULTIPLIER / CURSOR_DIVIDER, - blink_cb, - entry); - g_source_set_name_by_id (priv->blink_timeout, "[gtk] blink_cb"); - } - - return G_SOURCE_REMOVE; -} - -static void -gtk_entry_check_cursor_blink (GtkEntry *entry) -{ - GtkEntryPrivate *priv = gtk_entry_get_instance_private (entry); - - if (cursor_blinks (entry)) - { - if (!priv->blink_timeout) - { - show_cursor (entry); - priv->blink_timeout = g_timeout_add (get_cursor_time (entry) * CURSOR_ON_MULTIPLIER / CURSOR_DIVIDER, - blink_cb, - entry); - g_source_set_name_by_id (priv->blink_timeout, "[gtk] blink_cb"); - } - } - else - { - if (priv->blink_timeout) - { - g_source_remove (priv->blink_timeout); - priv->blink_timeout = 0; - } - - priv->cursor_visible = TRUE; - } -} - -static void -gtk_entry_pend_cursor_blink (GtkEntry *entry) -{ - GtkEntryPrivate *priv = gtk_entry_get_instance_private (entry); - - if (cursor_blinks (entry)) - { - if (priv->blink_timeout != 0) - g_source_remove (priv->blink_timeout); - - priv->blink_timeout = g_timeout_add (get_cursor_time (entry) * CURSOR_PEND_MULTIPLIER / CURSOR_DIVIDER, - blink_cb, - entry); - g_source_set_name_by_id (priv->blink_timeout, "[gtk] blink_cb"); - show_cursor (entry); - } -} - -static void -gtk_entry_reset_blink_time (GtkEntry *entry) -{ - GtkEntryPrivate *priv = gtk_entry_get_instance_private (entry); - - priv->blink_time = 0; -} - /** * gtk_entry_set_completion: * @entry: A #GtkEntry @@ -9116,24 +3365,9 @@ void gtk_entry_set_placeholder_text (GtkEntry *entry, const gchar *text) { - GtkEntryPrivate *priv = gtk_entry_get_instance_private (entry); - g_return_if_fail (GTK_IS_ENTRY (entry)); - if (priv->placeholder == NULL) - { - priv->placeholder = g_object_new (GTK_TYPE_LABEL, - "label", text, - "css-name", "placeholder", - "xalign", 0.0f, - "ellipsize", PANGO_ELLIPSIZE_END, - NULL); - gtk_widget_insert_after (priv->placeholder, GTK_WIDGET (entry), NULL); - } - else - { - gtk_label_set_text (GTK_LABEL (priv->placeholder), text); - } + //FIXME g_object_notify_by_pspec (G_OBJECT (entry), entry_props[PROP_PLACEHOLDER_TEXT]); } @@ -9152,68 +3386,10 @@ gtk_entry_set_placeholder_text (GtkEntry *entry, const gchar * gtk_entry_get_placeholder_text (GtkEntry *entry) { - GtkEntryPrivate *priv = gtk_entry_get_instance_private (entry); - g_return_val_if_fail (GTK_IS_ENTRY (entry), NULL); - if (!priv->placeholder) - return NULL; - - return gtk_label_get_text (GTK_LABEL (priv->placeholder)); -} - -/* Caps Lock warning for password entries */ - -static void -show_capslock_feedback (GtkEntry *entry, - const gchar *text) -{ - GtkEntryPrivate *priv = gtk_entry_get_instance_private (entry); - - if (gtk_entry_get_icon_storage_type (entry, GTK_ENTRY_ICON_SECONDARY) == GTK_IMAGE_EMPTY) - { - gtk_entry_set_icon_from_icon_name (entry, GTK_ENTRY_ICON_SECONDARY, "dialog-warning-symbolic"); - gtk_entry_set_icon_activatable (entry, GTK_ENTRY_ICON_SECONDARY, FALSE); - priv->caps_lock_warning_shown = TRUE; - } - - if (priv->caps_lock_warning_shown) - gtk_entry_set_icon_tooltip_text (entry, GTK_ENTRY_ICON_SECONDARY, text); - else - g_warning ("Can't show Caps Lock warning, since secondary icon is set"); -} - -static void -remove_capslock_feedback (GtkEntry *entry) -{ - GtkEntryPrivate *priv = gtk_entry_get_instance_private (entry); - - if (priv->caps_lock_warning_shown) - { - gtk_entry_set_icon_from_icon_name (entry, GTK_ENTRY_ICON_SECONDARY, NULL); - priv->caps_lock_warning_shown = FALSE; - } -} - -static void -keymap_state_changed (GdkKeymap *keymap, - GtkEntry *entry) -{ - GtkEntryPrivate *priv = gtk_entry_get_instance_private (entry); - char *text = NULL; - - if (priv->editable && - gtk_entry_get_display_mode (entry) != DISPLAY_NORMAL && - priv->caps_lock_warning) - { - if (gdk_keymap_get_caps_lock_state (keymap)) - text = _("Caps Lock is on"); - } - - if (text) - show_capslock_feedback (entry, text); - else - remove_capslock_feedback (entry); + //FIXME + return NULL; } /** @@ -9234,14 +3410,7 @@ gtk_entry_set_input_purpose (GtkEntry *entry, g_return_if_fail (GTK_IS_ENTRY (entry)); - if (gtk_entry_get_input_purpose (entry) != purpose) - { - g_object_set (G_OBJECT (priv->im_context), - "input-purpose", purpose, - NULL); - - g_object_notify_by_pspec (G_OBJECT (entry), entry_props[PROP_INPUT_PURPOSE]); - } + gtk_text_set_input_purpose (GTK_TEXT (priv->text), purpose); } /** @@ -9255,15 +3424,9 @@ gtk_entry_get_input_purpose (GtkEntry *entry) { GtkEntryPrivate *priv = gtk_entry_get_instance_private (entry); - GtkInputPurpose purpose; - g_return_val_if_fail (GTK_IS_ENTRY (entry), GTK_INPUT_PURPOSE_FREE_FORM); - g_object_get (G_OBJECT (priv->im_context), - "input-purpose", &purpose, - NULL); - - return purpose; + return gtk_text_get_input_purpose (GTK_TEXT (priv->text)); } /** @@ -9283,14 +3446,7 @@ gtk_entry_set_input_hints (GtkEntry *entry, g_return_if_fail (GTK_IS_ENTRY (entry)); - if (gtk_entry_get_input_hints (entry) != hints) - { - g_object_set (G_OBJECT (priv->im_context), - "input-hints", hints, - NULL); - - g_object_notify_by_pspec (G_OBJECT (entry), entry_props[PROP_INPUT_HINTS]); - } + gtk_text_set_input_hints (GTK_TEXT (priv->text), hints); } /** @@ -9304,15 +3460,9 @@ gtk_entry_get_input_hints (GtkEntry *entry) { GtkEntryPrivate *priv = gtk_entry_get_instance_private (entry); - GtkInputHints hints; - g_return_val_if_fail (GTK_IS_ENTRY (entry), GTK_INPUT_HINT_NONE); - g_object_get (G_OBJECT (priv->im_context), - "input-hints", &hints, - NULL); - - return hints; + return gtk_text_get_input_hints (GTK_TEXT (priv->text)); } /** @@ -9328,19 +3478,10 @@ gtk_entry_set_attributes (GtkEntry *entry, PangoAttrList *attrs) { GtkEntryPrivate *priv = gtk_entry_get_instance_private (entry); + g_return_if_fail (GTK_IS_ENTRY (entry)); - if (attrs) - pango_attr_list_ref (attrs); - - if (priv->attrs) - pango_attr_list_unref (priv->attrs); - priv->attrs = attrs; - - g_object_notify_by_pspec (G_OBJECT (entry), entry_props[PROP_ATTRIBUTES]); - - gtk_entry_recompute (entry); - gtk_widget_queue_resize (GTK_WIDGET (entry)); + gtk_text_set_attributes (GTK_TEXT (priv->text), attrs); } /** @@ -9360,7 +3501,7 @@ gtk_entry_get_attributes (GtkEntry *entry) g_return_val_if_fail (GTK_IS_ENTRY (entry), NULL); - return priv->attrs; + return gtk_text_get_attributes (GTK_TEXT (priv->text)); } /** @@ -9380,18 +3521,7 @@ gtk_entry_set_tabs (GtkEntry *entry, g_return_if_fail (GTK_IS_ENTRY (entry)); - if (priv->tabs) - pango_tab_array_free(priv->tabs); - - if (tabs) - priv->tabs = pango_tab_array_copy (tabs); - else - priv->tabs = NULL; - - g_object_notify_by_pspec (G_OBJECT (entry), entry_props[PROP_TABS]); - - gtk_entry_recompute (entry); - gtk_widget_queue_resize (GTK_WIDGET (entry)); + gtk_text_set_tabs (GTK_TEXT (priv->text), tabs); } /** @@ -9411,7 +3541,16 @@ gtk_entry_get_tabs (GtkEntry *entry) g_return_val_if_fail (GTK_IS_ENTRY (entry), NULL); - return priv->tabs; + return gtk_text_get_tabs (GTK_TEXT (priv->text)); +} + +void +gtk_entry_enter_text (GtkEntry *entry, + const char *text) +{ + GtkEntryPrivate *priv = gtk_entry_get_instance_private (entry); + + gtk_text_enter_text (GTK_TEXT (priv->text), text); } static void @@ -9500,30 +3639,18 @@ set_show_emoji_icon (GtkEntry *entry, gtk_widget_queue_resize (GTK_WIDGET (entry)); } -static void -set_enable_emoji_completion (GtkEntry *entry, - gboolean value) -{ - GtkEntryPrivate *priv = gtk_entry_get_instance_private (entry); - - if (priv->enable_emoji_completion == value) - return; - - priv->enable_emoji_completion = value; - - if (priv->enable_emoji_completion) - g_object_set_data (G_OBJECT (entry), "emoji-completion-popup", - gtk_emoji_completion_new (entry)); - else - g_object_set_data (G_OBJECT (entry), "emoji-completion-popup", NULL); - - g_object_notify_by_pspec (G_OBJECT (entry), entry_props[PROP_ENABLE_EMOJI_COMPLETION]); -} - GtkEventController * gtk_entry_get_key_controller (GtkEntry *entry) { GtkEntryPrivate *priv = gtk_entry_get_instance_private (entry); - return priv->key_controller; + return gtk_text_get_key_controller (GTK_TEXT (priv->text)); +} + +GtkText * +gtk_entry_get_text_widget (GtkEntry *entry) +{ + GtkEntryPrivate *priv = gtk_entry_get_instance_private (entry); + + return GTK_TEXT (priv->text); } diff --git a/gtk/gtkentry.h b/gtk/gtkentry.h index 4debf4ab9c..b6aae2d24f 100644 --- a/gtk/gtkentry.h +++ b/gtk/gtkentry.h @@ -115,28 +115,9 @@ struct _GtkEntryClass { GtkWidgetClass parent_class; - /* Hook to customize right-click popup */ - void (* populate_popup) (GtkEntry *entry, - GtkWidget *popup); - /* Action signals */ void (* activate) (GtkEntry *entry); - void (* move_cursor) (GtkEntry *entry, - GtkMovementStep step, - gint count, - gboolean extend_selection); - void (* insert_at_cursor) (GtkEntry *entry, - const gchar *str); - void (* delete_from_cursor) (GtkEntry *entry, - GtkDeleteType type, - gint count); - void (* backspace) (GtkEntry *entry); - void (* cut_clipboard) (GtkEntry *entry); - void (* copy_clipboard) (GtkEntry *entry); - void (* paste_clipboard) (GtkEntry *entry); - void (* toggle_overwrite) (GtkEntry *entry); - void (* insert_emoji) (GtkEntry *entry); /*< private >*/ @@ -224,12 +205,6 @@ void gtk_entry_set_text (GtkEntry *entry, GDK_AVAILABLE_IN_ALL const gchar* gtk_entry_get_text (GtkEntry *entry); -GDK_AVAILABLE_IN_ALL -PangoLayout* gtk_entry_get_layout (GtkEntry *entry); -GDK_AVAILABLE_IN_ALL -void gtk_entry_get_layout_offsets (GtkEntry *entry, - gint *x, - gint *y); GDK_AVAILABLE_IN_ALL void gtk_entry_set_alignment (GtkEntry *entry, gfloat xalign); @@ -242,13 +217,6 @@ void gtk_entry_set_completion (GtkEntry *entry, GDK_AVAILABLE_IN_ALL GtkEntryCompletion *gtk_entry_get_completion (GtkEntry *entry); -GDK_AVAILABLE_IN_ALL -gint gtk_entry_layout_index_to_text_index (GtkEntry *entry, - gint layout_index); -GDK_AVAILABLE_IN_ALL -gint gtk_entry_text_index_to_layout_index (GtkEntry *entry, - gint text_index); - /* Progress API */ GDK_AVAILABLE_IN_ALL @@ -341,9 +309,6 @@ void gtk_entry_get_icon_area (GtkEntry * GdkRectangle *icon_area); GDK_AVAILABLE_IN_ALL -gboolean gtk_entry_im_context_filter_keypress (GtkEntry *entry, - GdkEventKey *event); -GDK_AVAILABLE_IN_ALL void gtk_entry_reset_im_context (GtkEntry *entry); GDK_AVAILABLE_IN_ALL diff --git a/gtk/gtkentryprivate.h b/gtk/gtkentryprivate.h index 1fc50b7492..5d6c9d8877 100644 --- a/gtk/gtkentryprivate.h +++ b/gtk/gtkentryprivate.h @@ -26,6 +26,7 @@ #include "gtktreemodelfilter.h" #include "gtktreeviewcolumn.h" #include "gtkeventcontrollerkey.h" +#include "gtktextprivate.h" G_BEGIN_DECLS @@ -84,17 +85,12 @@ void _gtk_entry_completion_connect (GtkEntryCompletion *completion, GtkEntry *entry); void _gtk_entry_completion_disconnect (GtkEntryCompletion *completion); -gchar* _gtk_entry_get_display_text (GtkEntry *entry, - gint start_pos, - gint end_pos); GtkIMContext* _gtk_entry_get_im_context (GtkEntry *entry); void gtk_entry_enter_text (GtkEntry *entry, const char *text); -void gtk_entry_set_positions (GtkEntry *entry, - int current_pos, - int selection_bound); GtkEventController * gtk_entry_get_key_controller (GtkEntry *entry); +GtkText *gtk_entry_get_text_widget (GtkEntry *entry); G_END_DECLS From ea098f8d517e7a1f9abe9faeb1c4506954ad57ea Mon Sep 17 00:00:00 2001 From: Matthias Clasen Date: Sat, 16 Feb 2019 00:50:12 -0500 Subject: [PATCH 08/34] entry accessible: Make this work with the text widget --- gtk/a11y/gtkentryaccessible.c | 35 ++++++++++++++++++++--------------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/gtk/a11y/gtkentryaccessible.c b/gtk/a11y/gtkentryaccessible.c index 3b8bcf3b23..db2158b9fe 100644 --- a/gtk/a11y/gtkentryaccessible.c +++ b/gtk/a11y/gtkentryaccessible.c @@ -26,6 +26,7 @@ #include "gtkpango.h" #include "gtkentryaccessible.h" #include "gtkentryprivate.h" +#include "gtktextprivate.h" #include "gtkcomboboxaccessible.h" #include "gtkstylecontextprivate.h" #include "gtkwidgetprivate.h" @@ -762,7 +763,7 @@ gtk_entry_accessible_get_text (AtkText *atk_text, if (widget == NULL) return NULL; - return _gtk_entry_get_display_text (GTK_ENTRY (widget), start_pos, end_pos); + return gtk_text_get_display_text (gtk_entry_get_text_widget (GTK_ENTRY (widget)), start_pos, end_pos); } static gchar * @@ -778,7 +779,7 @@ gtk_entry_accessible_get_text_before_offset (AtkText *text, if (widget == NULL) return NULL; - return _gtk_pango_get_text_before (gtk_entry_get_layout (GTK_ENTRY (widget)), + return _gtk_pango_get_text_before (gtk_text_get_layout (gtk_entry_get_text_widget (GTK_ENTRY (widget))), boundary_type, offset, start_offset, end_offset); } @@ -796,7 +797,7 @@ gtk_entry_accessible_get_text_at_offset (AtkText *text, if (widget == NULL) return NULL; - return _gtk_pango_get_text_at (gtk_entry_get_layout (GTK_ENTRY (widget)), + return _gtk_pango_get_text_at (gtk_text_get_layout (gtk_entry_get_text_widget (GTK_ENTRY (widget))), boundary_type, offset, start_offset, end_offset); } @@ -814,7 +815,7 @@ gtk_entry_accessible_get_text_after_offset (AtkText *text, if (widget == NULL) return NULL; - return _gtk_pango_get_text_after (gtk_entry_get_layout (GTK_ENTRY (widget)), + return _gtk_pango_get_text_after (gtk_text_get_layout (gtk_entry_get_text_widget (GTK_ENTRY (widget))), boundary_type, offset, start_offset, end_offset); } @@ -830,7 +831,7 @@ gtk_entry_accessible_get_character_count (AtkText *atk_text) if (widget == NULL) return 0; - text = _gtk_entry_get_display_text (GTK_ENTRY (widget), 0, -1); + text = gtk_text_get_display_text (gtk_entry_get_text_widget (GTK_ENTRY (widget)), 0, -1); char_count = 0; if (text) @@ -900,7 +901,7 @@ gtk_entry_accessible_get_run_attributes (AtkText *text, attributes = add_text_attribute (attributes, ATK_TEXT_ATTR_DIRECTION, gtk_widget_get_direction (widget)); attributes = _gtk_pango_get_run_attributes (attributes, - gtk_entry_get_layout (GTK_ENTRY (widget)), + gtk_text_get_layout (gtk_entry_get_text_widget (GTK_ENTRY (widget))), offset, start_offset, end_offset); @@ -922,7 +923,7 @@ gtk_entry_accessible_get_default_attributes (AtkText *text) attributes = add_text_attribute (attributes, ATK_TEXT_ATTR_DIRECTION, gtk_widget_get_direction (widget)); attributes = _gtk_pango_get_default_attributes (attributes, - gtk_entry_get_layout (GTK_ENTRY (widget))); + gtk_text_get_layout (gtk_entry_get_text_widget (GTK_ENTRY (widget)))); attributes = _gtk_style_context_get_attributes (attributes, gtk_widget_get_style_context (widget)); @@ -940,6 +941,7 @@ gtk_entry_accessible_get_character_extents (AtkText *text, { GtkWidget *widget; GtkEntry *entry; + GtkText *textw; PangoRectangle char_rect; gchar *entry_text; gint index, x_layout, y_layout; @@ -952,13 +954,14 @@ gtk_entry_accessible_get_character_extents (AtkText *text, return; entry = GTK_ENTRY (widget); + textw = gtk_entry_get_text_widget (entry); - gtk_entry_get_layout_offsets (entry, &x_layout, &y_layout); - entry_text = _gtk_entry_get_display_text (entry, 0, -1); + gtk_text_get_layout_offsets (textw, &x_layout, &y_layout); + entry_text = gtk_text_get_display_text (textw, 0, -1); index = g_utf8_offset_to_pointer (entry_text, offset) - entry_text; g_free (entry_text); - pango_layout_index_to_pos (gtk_entry_get_layout (entry), index, &char_rect); + pango_layout_index_to_pos (gtk_text_get_layout (textw), index, &char_rect); pango_extents_to_pixels (&char_rect, NULL); gtk_widget_get_allocation (widget, &allocation); @@ -989,6 +992,7 @@ gtk_entry_accessible_get_offset_at_point (AtkText *atk_text, { GtkWidget *widget; GtkEntry *entry; + GtkText *textw; gchar *text; gint index, x_layout, y_layout; gint x_surface, y_surface; @@ -1001,8 +1005,9 @@ gtk_entry_accessible_get_offset_at_point (AtkText *atk_text, return -1; entry = GTK_ENTRY (widget); + textw = gtk_entry_get_text_widget (entry); - gtk_entry_get_layout_offsets (entry, &x_layout, &y_layout); + gtk_text_get_layout_offsets (textw, &x_layout, &y_layout); surface = gtk_widget_get_surface (widget); gdk_surface_get_origin (surface, &x_surface, &y_surface); @@ -1018,7 +1023,7 @@ gtk_entry_accessible_get_offset_at_point (AtkText *atk_text, x_local += x_surface; y_local += y_surface; } - if (!pango_layout_xy_to_index (gtk_entry_get_layout (entry), + if (!pango_layout_xy_to_index (gtk_text_get_layout (textw), x_local * PANGO_SCALE, y_local * PANGO_SCALE, &index, NULL)) @@ -1032,7 +1037,7 @@ gtk_entry_accessible_get_offset_at_point (AtkText *atk_text, offset = -1; if (index != -1) { - text = _gtk_entry_get_display_text (entry, 0, -1); + text = gtk_text_get_display_text (textw, 0, -1); offset = g_utf8_pointer_to_offset (text, text + index); g_free (text); } @@ -1167,7 +1172,7 @@ gtk_entry_accessible_get_character_at_offset (AtkText *atk_text, if (!gtk_entry_get_visibility (GTK_ENTRY (widget))) return result; - text = _gtk_entry_get_display_text (GTK_ENTRY (widget), 0, -1); + text = gtk_text_get_display_text (gtk_entry_get_text_widget (GTK_ENTRY (widget)), 0, -1); if (offset < g_utf8_strlen (text, -1)) { index = g_utf8_offset_to_pointer (text, offset); @@ -1402,7 +1407,7 @@ delete_text_cb (GtkEditable *editable, { gchar *text; - text = _gtk_entry_get_display_text (GTK_ENTRY (editable), 0, -1); + text = gtk_text_get_display_text (gtk_entry_get_text_widget (GTK_ENTRY (editable)), 0, -1); end = g_utf8_strlen (text, -1); g_free (text); } From 1930c7473629347405e2127c315a008146390b9a Mon Sep 17 00:00:00 2001 From: Matthias Clasen Date: Fri, 15 Feb 2019 21:01:33 -0500 Subject: [PATCH 09/34] spin button: Use GtkText We already use GtkEditable api throughout. This just means we create a GtkText instead of a GtkEntry. --- gtk/gtkspinbutton.c | 38 ++++++++++++++++++++++++-------------- 1 file changed, 24 insertions(+), 14 deletions(-) diff --git a/gtk/gtkspinbutton.c b/gtk/gtkspinbutton.c index 59c8681bc7..280a324305 100644 --- a/gtk/gtkspinbutton.c +++ b/gtk/gtkspinbutton.c @@ -35,8 +35,8 @@ #include "gtkbutton.h" #include "gtkcssstylepropertyprivate.h" #include "gtkeditable.h" -#include "gtkeditableprivate.h" -#include "gtkentry.h" +#include "gtkimage.h" +#include "gtktext.h" #include "gtkeventcontrollerkey.h" #include "gtkeventcontrollermotion.h" #include "gtkeventcontrollerscroll.h" @@ -95,7 +95,7 @@ * |[ * spinbutton.horizontal * ╰── box.horizontal - * ├── entry + * ├── text * │ ├── undershoot.left * │ ╰── undershoot.right * ├── button.down @@ -106,7 +106,7 @@ * spinbutton.vertical * ╰── box.vertical * ├── button.up - * ├── entry + * ├── text * │ ├── undershoot.left * │ ╰── undershoot.right * ╰── button.down @@ -114,8 +114,8 @@ * * GtkSpinButtons main CSS node has the name spinbutton. It creates subnodes * for the entry and the two buttons, with these names. The button nodes have - * the style classes .up and .down. The GtkEntry subnodes (if present) are put - * below the entry node. The orientation of the spin button is reflected in + * the style classes .up and .down. The GtkText subnodes (if present) are put + * below the text node. The orientation of the spin button is reflected in * the .vertical or .horizontal style class on the main node. * * ## Using a GtkSpinButton to get an integer @@ -223,7 +223,7 @@ enum { PROP_UPDATE_POLICY, PROP_VALUE, NUM_SPINBUTTON_PROPS, - PROP_ORIENTATION, + PROP_ORIENTATION = NUM_SPINBUTTON_PROPS }; /* Signals */ @@ -269,7 +269,7 @@ static gboolean gtk_spin_button_stop_spinning (GtkSpinButton *spin); static void gtk_spin_button_value_changed (GtkAdjustment *adjustment, GtkSpinButton *spin_button); -static void gtk_spin_button_activate (GtkEntry *entry, +static void gtk_spin_button_activate (GtkText *entry, gpointer user_data); static void gtk_spin_button_unset_adjustment (GtkSpinButton *spin_button); static void gtk_spin_button_set_orientation (GtkSpinButton *spin_button, @@ -384,8 +384,8 @@ gtk_spin_button_class_init (GtkSpinButtonClass *class) GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY); g_object_class_install_properties (gobject_class, NUM_SPINBUTTON_PROPS, spinbutton_props); - gtk_editable_install_properties (gobject_class); g_object_class_override_property (gobject_class, PROP_ORIENTATION, "orientation"); + gtk_editable_install_properties (gobject_class, PROP_ORIENTATION + 1); /** * GtkSpinButton::input: @@ -523,10 +523,19 @@ gtk_spin_button_class_init (GtkSpinButtonClass *class) gtk_widget_class_set_css_name (widget_class, I_("spinbutton")); } +static GtkEditable * +gtk_spin_button_get_delegate (GtkEditable *editable) +{ + GtkSpinButton *spin_button = GTK_SPIN_BUTTON (editable); + GtkSpinButtonPrivate *priv = gtk_spin_button_get_instance_private (spin_button); + + return GTK_EDITABLE (priv->entry); +} + static void gtk_spin_button_editable_init (GtkEditableInterface *iface) { - gtk_editable_delegate_iface_init (iface); + iface->get_delegate = gtk_spin_button_get_delegate; iface->insert_text = gtk_spin_button_insert_text; } @@ -824,13 +833,12 @@ gtk_spin_button_init (GtkSpinButton *spin_button) priv->orientation = GTK_ORIENTATION_HORIZONTAL; _gtk_orientable_set_style_classes (GTK_ORIENTABLE (spin_button)); - gtk_widget_set_focus_on_click (GTK_WIDGET (spin_button), TRUE); priv->box = gtk_box_new (priv->orientation, 0); gtk_widget_set_parent (priv->box, GTK_WIDGET (spin_button)); - priv->entry = gtk_entry_new (); - gtk_editable_set_delegate (GTK_EDITABLE (spin_button), GTK_EDITABLE (priv->entry)); + priv->entry = gtk_text_new (); + gtk_editable_init_delegate (GTK_EDITABLE (spin_button)); gtk_editable_set_width_chars (GTK_EDITABLE (priv->entry), 0); gtk_editable_set_max_width_chars (GTK_EDITABLE (priv->entry), 0); gtk_widget_set_hexpand (priv->entry, TRUE); @@ -904,6 +912,8 @@ gtk_spin_button_finalize (GObject *object) gtk_spin_button_unset_adjustment (spin_button); + gtk_editable_finish_delegate (GTK_EDITABLE (spin_button)); + gtk_widget_unparent (priv->box); G_OBJECT_CLASS (gtk_spin_button_parent_class)->finalize (object); @@ -1294,7 +1304,7 @@ gtk_spin_button_snap (GtkSpinButton *spin_button, } static void -gtk_spin_button_activate (GtkEntry *entry, +gtk_spin_button_activate (GtkText *entry, gpointer user_data) { GtkSpinButton *spin_button = user_data; From 8bb6d00ce240286ade023bced18bf0e55e6efb66 Mon Sep 17 00:00:00 2001 From: Matthias Clasen Date: Sat, 16 Feb 2019 00:52:20 -0500 Subject: [PATCH 10/34] search entry: temporarily disable some functions The search entry needs to be rewritten to use a text child until then, keep it limping along. --- gtk/gtksearchentry.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/gtk/gtksearchentry.c b/gtk/gtksearchentry.c index b2368c308a..acbd5e110e 100644 --- a/gtk/gtksearchentry.c +++ b/gtk/gtksearchentry.c @@ -31,6 +31,7 @@ #include "gtkaccessible.h" #include "gtkbindings.h" +#include "gtkentryprivate.h" #include "gtkintl.h" #include "gtkmarshalers.h" #include "gtkstylecontext.h" @@ -164,10 +165,6 @@ gtk_search_entry_class_init (GtkSearchEntryClass *klass) GTK_TYPE_SEARCH_ENTRY, G_CALLBACK (gtk_search_entry_icon_release)); - g_signal_override_class_handler ("preedit-changed", - GTK_TYPE_SEARCH_ENTRY, - G_CALLBACK (gtk_search_entry_preedit_changed)); - /** * GtkSearchEntry::search-changed: * @entry: the entry on which the signal was emitted @@ -368,6 +365,9 @@ gtk_search_entry_init (GtkSearchEntry *entry) atk_object_set_name (atk_obj, _("Search")); gtk_style_context_add_class (gtk_widget_get_style_context (GTK_WIDGET (entry)), I_("search")); + + g_signal_connect (gtk_entry_get_text_widget (GTK_ENTRY (entry)), "preedit-changed", + G_CALLBACK (gtk_search_entry_preedit_changed), NULL); } /** From a31da76f95cfc4af4f9c6b0f066e63f338541f19 Mon Sep 17 00:00:00 2001 From: Matthias Clasen Date: Sat, 16 Feb 2019 00:53:09 -0500 Subject: [PATCH 11/34] treeview: Disable some search entry functionality This needs to be rewritten. Until then, keep it limping along. --- gtk/gtktreeview.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gtk/gtktreeview.c b/gtk/gtktreeview.c index bf0a665c33..5cfd417f81 100644 --- a/gtk/gtktreeview.c +++ b/gtk/gtktreeview.c @@ -10247,7 +10247,7 @@ gtk_tree_view_ensure_interactive_directory (GtkTreeView *tree_view) g_signal_connect (tree_view->priv->search_entry, "activate", G_CALLBACK (gtk_tree_view_search_activate), tree_view); - +#if 0 g_signal_connect (_gtk_entry_get_im_context (GTK_ENTRY (tree_view->priv->search_entry)), "preedit-changed", G_CALLBACK (gtk_tree_view_search_preedit_changed), @@ -10256,7 +10256,7 @@ gtk_tree_view_ensure_interactive_directory (GtkTreeView *tree_view) "commit", G_CALLBACK (gtk_tree_view_search_commit), tree_view); - +#endif gtk_container_add (GTK_CONTAINER (vbox), tree_view->priv->search_entry); From cbbc7dd70facbaa300d496229de3089b6d008cf8 Mon Sep 17 00:00:00 2001 From: Matthias Clasen Date: Sat, 16 Feb 2019 12:29:48 -0500 Subject: [PATCH 12/34] treeview: use a GtkText as search entry --- gtk/gtktreeview.c | 43 ++++++++++++++++++------------------------- 1 file changed, 18 insertions(+), 25 deletions(-) diff --git a/gtk/gtktreeview.c b/gtk/gtktreeview.c index 5cfd417f81..45a8ae2cc3 100644 --- a/gtk/gtktreeview.c +++ b/gtk/gtktreeview.c @@ -46,6 +46,7 @@ #include "gtkmain.h" #include "gtkmarshalers.h" #include "gtkprivate.h" +#include "gtktext.h" #include "gtktreerbtreeprivate.h" #include "gtkrendericonprivate.h" #include "gtkscrollable.h" @@ -799,10 +800,10 @@ static void gtk_tree_view_search_position_func (GtkTreeView *tree_ static void gtk_tree_view_search_disable_popdown (GtkEntry *entry, GtkMenu *menu, gpointer data); -static void gtk_tree_view_search_preedit_changed (GtkIMContext *im_context, +static void gtk_tree_view_search_preedit_changed (GtkText *text, + const char *preedit, GtkTreeView *tree_view); -static void gtk_tree_view_search_commit (GtkIMContext *im_context, - gchar *buf, +static void gtk_tree_view_search_changed (GtkEditable *editable, GtkTreeView *tree_view); static void gtk_tree_view_search_activate (GtkEntry *entry, GtkTreeView *tree_view); @@ -5383,7 +5384,7 @@ gtk_tree_view_key_controller_key_pressed (GtkEventControllerKey *key, } else { - gtk_entry_set_text (GTK_ENTRY (tree_view->priv->search_entry), ""); + gtk_editable_set_text (GTK_EDITABLE (tree_view->priv->search_entry), ""); return FALSE; } } @@ -10239,24 +10240,16 @@ gtk_tree_view_ensure_interactive_directory (GtkTreeView *tree_view) gtk_container_add (GTK_CONTAINER (frame), vbox); /* add entry */ - tree_view->priv->search_entry = gtk_entry_new (); - gtk_widget_show (tree_view->priv->search_entry); + tree_view->priv->search_entry = gtk_text_new (); g_signal_connect (tree_view->priv->search_entry, "populate-popup", - G_CALLBACK (gtk_tree_view_search_disable_popdown), - tree_view); - g_signal_connect (tree_view->priv->search_entry, - "activate", G_CALLBACK (gtk_tree_view_search_activate), - tree_view); -#if 0 - g_signal_connect (_gtk_entry_get_im_context (GTK_ENTRY (tree_view->priv->search_entry)), - "preedit-changed", - G_CALLBACK (gtk_tree_view_search_preedit_changed), - tree_view); - g_signal_connect (_gtk_entry_get_im_context (GTK_ENTRY (tree_view->priv->search_entry)), - "commit", - G_CALLBACK (gtk_tree_view_search_commit), - tree_view); -#endif + G_CALLBACK (gtk_tree_view_search_disable_popdown), tree_view); + g_signal_connect (tree_view->priv->search_entry, "activate", + G_CALLBACK (gtk_tree_view_search_activate), tree_view); + g_signal_connect (tree_view->priv->search_entry, "preedit-changed", + G_CALLBACK (gtk_tree_view_search_preedit_changed), tree_view); + g_signal_connect (tree_view->priv->search_entry, "changed", + G_CALLBACK (gtk_tree_view_search_changed), tree_view); + gtk_container_add (GTK_CONTAINER (vbox), tree_view->priv->search_entry); @@ -13846,7 +13839,8 @@ gtk_tree_view_search_disable_popdown (GtkEntry *entry, * callback. */ static void -gtk_tree_view_search_preedit_changed (GtkIMContext *im_context, +gtk_tree_view_search_preedit_changed (GtkText *text, + const char *predit, GtkTreeView *tree_view) { tree_view->priv->imcontext_changed = 1; @@ -13863,9 +13857,8 @@ gtk_tree_view_search_preedit_changed (GtkIMContext *im_context, } static void -gtk_tree_view_search_commit (GtkIMContext *im_context, - gchar *buf, - GtkTreeView *tree_view) +gtk_tree_view_search_changed (GtkEditable *editable, + GtkTreeView *tree_view) { tree_view->priv->imcontext_changed = 1; } From 39d5f9e07b4afeed2accc6b54ec09d80a016e0dc Mon Sep 17 00:00:00 2001 From: Matthias Clasen Date: Sun, 17 Feb 2019 23:03:27 -0500 Subject: [PATCH 13/34] entry completion: Make this work again Connect to GtkText signals where necessary. --- gtk/gtkentrycompletion.c | 110 +++++++++++++++++---------------------- 1 file changed, 49 insertions(+), 61 deletions(-) diff --git a/gtk/gtkentrycompletion.c b/gtk/gtkentrycompletion.c index 6d43daff98..5657a19d46 100644 --- a/gtk/gtkentrycompletion.c +++ b/gtk/gtkentrycompletion.c @@ -67,6 +67,7 @@ #include "gtkentrycompletion.h" #include "gtkentryprivate.h" +#include "gtktextprivate.h" #include "gtkcelllayout.h" #include "gtkcellareabox.h" @@ -496,8 +497,9 @@ propagate_to_entry (GtkEventControllerKey *key, GtkEntryCompletion *completion) { GtkEntryCompletionPrivate *priv = completion->priv; + GtkText *text = gtk_entry_get_text_widget (GTK_ENTRY (priv->entry)); - return gtk_event_controller_key_forward (key, priv->entry); + return gtk_event_controller_key_forward (key, GTK_WIDGET (text)); } static void @@ -894,6 +896,7 @@ gtk_entry_completion_list_activated (GtkTreeView *treeview, gboolean entry_set; GtkTreeModel *model; GtkTreeIter child_iter; + GtkText *text = gtk_entry_get_text_widget (GTK_ENTRY (completion->priv->entry)); gtk_tree_model_get_iter (GTK_TREE_MODEL (completion->priv->filter_model), &iter, path); gtk_tree_model_filter_convert_iter_to_child_iter (completion->priv->filter_model, @@ -901,12 +904,10 @@ gtk_entry_completion_list_activated (GtkTreeView *treeview, &iter); model = gtk_tree_model_filter_get_model (completion->priv->filter_model); - g_signal_handler_block (completion->priv->entry, - completion->priv->changed_id); + g_signal_handler_block (text, completion->priv->changed_id); g_signal_emit (completion, entry_completion_signals[MATCH_SELECTED], 0, model, &child_iter, &entry_set); - g_signal_handler_unblock (completion->priv->entry, - completion->priv->changed_id); + g_signal_handler_unblock (text, completion->priv->changed_id); _gtk_entry_completion_popdown (completion); } @@ -1493,14 +1494,15 @@ static void gtk_entry_completion_popup (GtkEntryCompletion *completion) { GtkWidget *toplevel; + GtkText *text = gtk_entry_get_text_widget (GTK_ENTRY (completion->priv->entry)); if (gtk_widget_get_mapped (completion->priv->popup_window)) return; - if (!gtk_widget_get_mapped (completion->priv->entry)) + if (!gtk_widget_get_mapped (GTK_WIDGET (text))) return; - if (!gtk_widget_has_focus (completion->priv->entry)) + if (!gtk_widget_has_focus (GTK_WIDGET (text))) return; if (completion->priv->has_grab) @@ -1709,29 +1711,30 @@ gtk_entry_completion_get_completion_prefix (GtkEntryCompletion *completion) static void gtk_entry_completion_insert_completion_text (GtkEntryCompletion *completion, - const gchar *text) + const gchar *new_text) { GtkEntryCompletionPrivate *priv = completion->priv; gint len; + GtkText *text = gtk_entry_get_text_widget (GTK_ENTRY (priv->entry)); priv = completion->priv; if (priv->changed_id > 0) - g_signal_handler_block (priv->entry, priv->changed_id); + g_signal_handler_block (text, priv->changed_id); if (priv->insert_text_id > 0) - g_signal_handler_block (priv->entry, priv->insert_text_id); + g_signal_handler_block (text, priv->insert_text_id); - gtk_entry_set_text (GTK_ENTRY (priv->entry), text); + gtk_editable_set_text (GTK_EDITABLE (priv->entry), new_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); + g_signal_handler_unblock (text, priv->changed_id); if (priv->insert_text_id > 0) - g_signal_handler_unblock (priv->entry, priv->insert_text_id); + g_signal_handler_unblock (text, priv->insert_text_id); } static gboolean @@ -1766,13 +1769,13 @@ gtk_entry_completion_insert_prefix (GtkEntryCompletion *completion) { gboolean done; gchar *prefix; + GtkText *text = gtk_entry_get_text_widget (GTK_ENTRY (completion->priv->entry)); if (completion->priv->insert_text_id > 0) - g_signal_handler_block (completion->priv->entry, - completion->priv->insert_text_id); + g_signal_handler_block (text, completion->priv->insert_text_id); prefix = gtk_entry_completion_compute_prefix (completion, - gtk_entry_get_text (GTK_ENTRY (completion->priv->entry))); + gtk_editable_get_text (GTK_EDITABLE (completion->priv->entry))); if (prefix) { @@ -1782,8 +1785,7 @@ gtk_entry_completion_insert_prefix (GtkEntryCompletion *completion) } if (completion->priv->insert_text_id > 0) - g_signal_handler_unblock (completion->priv->entry, - completion->priv->insert_text_id); + g_signal_handler_unblock (text, completion->priv->insert_text_id); } /** @@ -2066,6 +2068,7 @@ gtk_entry_completion_key_pressed (GtkEventControllerKey *controller, gint matches, actions = 0; GtkEntryCompletion *completion = GTK_ENTRY_COMPLETION (user_data); GtkWidget *widget = completion->priv->entry; + GtkText *text = gtk_entry_get_text_widget (GTK_ENTRY (widget)); if (!completion->priv->popup_completion) return FALSE; @@ -2296,10 +2299,10 @@ keypress_completion_out: { 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_handler_block (text, 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); + g_signal_handler_unblock (text, completion->priv->changed_id); if (!entry_set) { @@ -2349,7 +2352,6 @@ 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) @@ -2362,12 +2364,12 @@ gtk_entry_completion_changed (GtkWidget *widget, completion->priv->completion_timeout = 0; } - if (!gtk_entry_get_text (entry)) + if (!gtk_editable_get_text (GTK_EDITABLE (widget))) return; /* no need to normalize for this test */ if (completion->priv->minimum_key_length > 0 && - strcmp ("", gtk_entry_get_text (entry)) == 0) + strcmp ("", gtk_editable_get_text (GTK_EDITABLE (widget))) == 0) { if (gtk_widget_get_visible (completion->priv->popup_window)) _gtk_entry_completion_popdown (completion); @@ -2401,11 +2403,10 @@ check_completion_callback (GtkEntryCompletion *completion) } static void -clear_completion_callback (GtkEntry *entry, - GParamSpec *pspec) +clear_completion_callback (GObject *text, + GParamSpec *pspec, + GtkEntryCompletion *completion) { - GtkEntryCompletion *completion = gtk_entry_get_completion (entry); - if (!completion->priv->inline_completion) return; @@ -2415,22 +2416,20 @@ clear_completion_callback (GtkEntry *entry, } static gboolean -accept_completion_callback (GtkEntry *entry) +accept_completion_callback (GtkEntryCompletion *completion) { - 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))); + gtk_editable_set_position (GTK_EDITABLE (completion->priv->entry), + gtk_entry_buffer_get_length (gtk_entry_get_buffer (GTK_ENTRY (completion->priv->entry)))); return FALSE; } static void -completion_insert_text_callback (GtkEntry *entry, +completion_insert_text_callback (GtkText *entry, const gchar *text, gint length, gint position, @@ -2457,26 +2456,21 @@ connect_completion_signals (GtkEntryCompletion *completion) { GtkEntryCompletionPrivate *priv = completion->priv; GtkEventController *controller; + GtkText *text = gtk_entry_get_text_widget (GTK_ENTRY (priv->entry)); controller = priv->entry_key_controller = gtk_event_controller_key_new (); g_signal_connect (controller, "key-pressed", G_CALLBACK (gtk_entry_completion_key_pressed), completion); - g_signal_connect_swapped (controller, "focus-out", - G_CALLBACK (accept_completion_callback), - completion->priv->entry); - gtk_widget_add_controller (completion->priv->entry, controller); + g_signal_connect_swapped (controller, "focus-out", G_CALLBACK (accept_completion_callback), completion); + gtk_widget_add_controller (GTK_WIDGET (text), controller); completion->priv->changed_id = - g_signal_connect (completion->priv->entry, "changed", - G_CALLBACK (gtk_entry_completion_changed), completion); + g_signal_connect (text, "changed", G_CALLBACK (gtk_entry_completion_changed), 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 (text, "insert-text", G_CALLBACK (completion_insert_text_callback), completion); + g_signal_connect (text, "notify", G_CALLBACK (clear_completion_callback), completion); + g_signal_connect_swapped (text, "activate", G_CALLBACK (accept_completion_callback), completion); } static void @@ -2512,31 +2506,25 @@ unset_accessible_relation (GtkWidget *window, static void disconnect_completion_signals (GtkEntryCompletion *completion) { - gtk_widget_remove_controller (completion->priv->entry, - completion->priv->entry_key_controller); + GtkText *text = gtk_entry_get_text_widget (GTK_ENTRY (completion->priv->entry)); + + gtk_widget_remove_controller (GTK_WIDGET (text), completion->priv->entry_key_controller); if (completion->priv->changed_id > 0 && - g_signal_handler_is_connected (completion->priv->entry, - completion->priv->changed_id)) + g_signal_handler_is_connected (text, completion->priv->changed_id)) { - g_signal_handler_disconnect (completion->priv->entry, - completion->priv->changed_id); + g_signal_handler_disconnect (text, completion->priv->changed_id); completion->priv->changed_id = 0; } if (completion->priv->insert_text_id > 0 && - g_signal_handler_is_connected (completion->priv->entry, - completion->priv->insert_text_id)) + g_signal_handler_is_connected (text, completion->priv->insert_text_id)) { - g_signal_handler_disconnect (completion->priv->entry, - completion->priv->insert_text_id); + g_signal_handler_disconnect (text, 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); + g_signal_handlers_disconnect_by_func (text, G_CALLBACK (completion_insert_text_callback), completion); + g_signal_handlers_disconnect_by_func (text, G_CALLBACK (clear_completion_callback), completion); + g_signal_handlers_disconnect_by_func (text, G_CALLBACK (accept_completion_callback), completion); } void From c6e6fb19e76f7ede4db75f9271a232b65c645c2b Mon Sep 17 00:00:00 2001 From: Matthias Clasen Date: Sat, 16 Feb 2019 22:21:57 -0500 Subject: [PATCH 14/34] Add GtkPasswordEntry This is a simple editable which hides the entered text and shows a caps-lock warning. --- docs/reference/gtk/gtk4-docs.xml | 1 + docs/reference/gtk/gtk4-sections.txt | 9 + docs/reference/gtk/gtk4.types.in | 1 + gtk/gtk.h | 3 +- gtk/gtkpasswordentry.c | 271 +++++++++++++++++++++++++++ gtk/gtkpasswordentry.h | 60 ++++++ gtk/meson.build | 2 + 7 files changed, 346 insertions(+), 1 deletion(-) create mode 100644 gtk/gtkpasswordentry.c create mode 100644 gtk/gtkpasswordentry.h diff --git a/docs/reference/gtk/gtk4-docs.xml b/docs/reference/gtk/gtk4-docs.xml index 56d2b8a1a6..e8b9810359 100644 --- a/docs/reference/gtk/gtk4-docs.xml +++ b/docs/reference/gtk/gtk4-docs.xml @@ -143,6 +143,7 @@ + diff --git a/docs/reference/gtk/gtk4-sections.txt b/docs/reference/gtk/gtk4-sections.txt index f1f7877fa4..d4925bd3d3 100644 --- a/docs/reference/gtk/gtk4-sections.txt +++ b/docs/reference/gtk/gtk4-sections.txt @@ -996,6 +996,15 @@ GtkEntryPrivate gtk_entry_get_type
+
+gtkpasswordentry +GtkPasswordEntry +GtkPasswordEntry +gtk_password_entry_new + +gtk_password_entry_get_type +
+
gtkentrybuffer GtkEntryBuffer diff --git a/docs/reference/gtk/gtk4.types.in b/docs/reference/gtk/gtk4.types.in index 5c7bc803f2..cede164bee 100644 --- a/docs/reference/gtk/gtk4.types.in +++ b/docs/reference/gtk/gtk4.types.in @@ -118,6 +118,7 @@ gtk_page_setup_get_type @DISABLE_ON_W32@gtk_page_setup_unix_dialog_get_type gtk_paned_get_type gtk_paper_size_get_type +gtk_password_entry_get_type gtk_picture_get_type gtk_popover_get_type gtk_popover_menu_get_type diff --git a/gtk/gtk.h b/gtk/gtk.h index c500c8de74..34fff24a3f 100644 --- a/gtk/gtk.h +++ b/gtk/gtk.h @@ -159,8 +159,9 @@ #include #include #include -#include #include +#include +#include #include #include #include diff --git a/gtk/gtkpasswordentry.c b/gtk/gtkpasswordentry.c new file mode 100644 index 0000000000..16a811a8b3 --- /dev/null +++ b/gtk/gtkpasswordentry.c @@ -0,0 +1,271 @@ +/* GTK - The GIMP Toolkit + * Copyright (C) 2019 Red Hat, Inc. + * + * Authors: + * - Matthias Clasen + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + */ + +#include "config.h" + +#include "gtkpasswordentry.h" + +#include "gtkaccessible.h" +#include "gtkbindings.h" +#include "gtktextprivate.h" +#include "gtkeditable.h" +#include "gtkbox.h" +#include "gtkimage.h" +#include "gtkintl.h" +#include "gtkmarshalers.h" +#include "gtkstylecontext.h" +#include "gtkeventcontrollerkey.h" + +#include "a11y/gtkentryaccessible.h" + +/** + * SECTION:gtkpasswordhentry + * @Short_description: An entry for secrets + * @Title: GtkPasswordEntry + * + * #GtkPasswordEntry is entry that has been tailored for + * entering secrets. + */ + +typedef struct { + GtkWidget *box; + GtkWidget *entry; + GtkWidget *icon; + GdkKeymap *keymap; +} GtkPasswordEntryPrivate; + +static void gtk_password_entry_editable_init (GtkEditableInterface *iface); + +G_DEFINE_TYPE_WITH_CODE (GtkPasswordEntry, gtk_password_entry, GTK_TYPE_WIDGET, + G_ADD_PRIVATE (GtkPasswordEntry) + G_IMPLEMENT_INTERFACE (GTK_TYPE_EDITABLE, gtk_password_entry_editable_init)) + +static void +keymap_state_changed (GdkKeymap *keymap, + GtkWidget *widget) +{ + GtkPasswordEntry *entry = GTK_PASSWORD_ENTRY (widget); + GtkPasswordEntryPrivate *priv = gtk_password_entry_get_instance_private (entry); + + if (gtk_editable_get_editable (GTK_EDITABLE (entry)) && + gtk_widget_has_focus (priv->entry) && + gdk_keymap_get_caps_lock_state (priv->keymap)) + gtk_widget_show (priv->icon); + else + gtk_widget_hide (priv->icon); +} + +static void +focus_changed (GtkWidget *widget) +{ + GtkPasswordEntry *entry = GTK_PASSWORD_ENTRY (widget); + GtkPasswordEntryPrivate *priv = gtk_password_entry_get_instance_private (entry); + + if (priv->keymap) + keymap_state_changed (priv->keymap, widget); +} + +static void +gtk_password_entry_init (GtkPasswordEntry *entry) +{ + GtkPasswordEntryPrivate *priv = gtk_password_entry_get_instance_private (entry); + + gtk_widget_set_has_surface (GTK_WIDGET (entry), FALSE); + + priv->box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0); + gtk_widget_set_parent (priv->box, GTK_WIDGET (entry)); + + priv->entry = gtk_text_new (); + gtk_text_set_visibility (GTK_TEXT (priv->entry), FALSE); + gtk_widget_set_hexpand (priv->entry, TRUE); + gtk_widget_set_vexpand (priv->entry, TRUE); + gtk_container_add (GTK_CONTAINER (priv->box), priv->entry); + gtk_editable_init_delegate (GTK_EDITABLE (entry)); + g_signal_connect_swapped (priv->entry, "notify::has-focus", G_CALLBACK (focus_changed), entry); + + priv->icon = gtk_image_new_from_icon_name ("dialog-warning-symbolic"); + gtk_widget_set_tooltip_text (priv->icon, _("Caps Lock is on")); + gtk_container_add (GTK_CONTAINER (priv->box), priv->icon); + + gtk_style_context_add_class (gtk_widget_get_style_context (GTK_WIDGET (entry)), I_("password")); +} + +static void +gtk_password_entry_realize (GtkWidget *widget) +{ + GtkPasswordEntry *entry = GTK_PASSWORD_ENTRY (widget); + GtkPasswordEntryPrivate *priv = gtk_password_entry_get_instance_private (entry); + + GTK_WIDGET_CLASS (gtk_password_entry_parent_class)->realize (widget); + + priv->keymap = gdk_display_get_keymap (gtk_widget_get_display (widget)); + g_signal_connect (priv->keymap, "state-changed", G_CALLBACK (keymap_state_changed), entry); +} + +static void +gtk_password_entry_dispose (GObject *object) +{ + GtkPasswordEntry *entry = GTK_PASSWORD_ENTRY (object); + GtkPasswordEntryPrivate *priv = gtk_password_entry_get_instance_private (entry); + + if (priv->keymap) + g_signal_handlers_disconnect_by_func (priv->keymap, keymap_state_changed, entry); + + if (priv->entry) + gtk_editable_finish_delegate (GTK_EDITABLE (entry)); + + g_clear_pointer (&priv->entry, gtk_widget_unparent); + g_clear_pointer (&priv->icon, gtk_widget_unparent); + g_clear_pointer (&priv->box, gtk_widget_unparent); + + G_OBJECT_CLASS (gtk_password_entry_parent_class)->dispose (object); +} + +static void +gtk_password_entry_finalize (GObject *object) +{ + G_OBJECT_CLASS (gtk_password_entry_parent_class)->finalize (object); +} + +static void +gtk_password_entry_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + if (gtk_editable_delegate_set_property (object, prop_id, value, pspec)) + return; + + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); +} + +static void +gtk_password_entry_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + if (gtk_editable_delegate_get_property (object, prop_id, value, pspec)) + return; + + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); +} + +static void +gtk_password_entry_measure (GtkWidget *widget, + GtkOrientation orientation, + int for_size, + int *minimum, + int *natural, + int *minimum_baseline, + int *natural_baseline) +{ + GtkPasswordEntry *entry = GTK_PASSWORD_ENTRY (widget); + GtkPasswordEntryPrivate *priv = gtk_password_entry_get_instance_private (entry); + + gtk_widget_measure (priv->box, orientation, for_size, + minimum, natural, + minimum_baseline, natural_baseline); +} + +static void +gtk_password_entry_size_allocate (GtkWidget *widget, + int width, + int height, + int baseline) +{ + GtkPasswordEntry *entry = GTK_PASSWORD_ENTRY (widget); + GtkPasswordEntryPrivate *priv = gtk_password_entry_get_instance_private (entry); + + gtk_widget_size_allocate (priv->box, + &(GtkAllocation) { 0, 0, width, height }, + baseline); +} + +static AtkObject * +gtk_password_entry_get_accessible (GtkWidget *widget) +{ + AtkObject *atk_obj; + + atk_obj = GTK_WIDGET_CLASS (gtk_password_entry_parent_class)->get_accessible (widget); + atk_object_set_name (atk_obj, _("Password")); + + return atk_obj; +} + +static void +gtk_password_entry_grab_focus (GtkWidget *widget) +{ + GtkPasswordEntry *entry = GTK_PASSWORD_ENTRY (widget); + GtkPasswordEntryPrivate *priv = gtk_password_entry_get_instance_private (entry); + + gtk_widget_grab_focus (priv->entry); +} + +static void +gtk_password_entry_class_init (GtkPasswordEntryClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + object_class->dispose = gtk_password_entry_dispose; + object_class->finalize = gtk_password_entry_finalize; + object_class->get_property = gtk_password_entry_get_property; + object_class->set_property = gtk_password_entry_set_property; + + widget_class->realize = gtk_password_entry_realize; + widget_class->measure = gtk_password_entry_measure; + widget_class->size_allocate = gtk_password_entry_size_allocate; + widget_class->get_accessible = gtk_password_entry_get_accessible; + widget_class->grab_focus = gtk_password_entry_grab_focus; + + gtk_editable_install_properties (object_class, 1); + + gtk_widget_class_set_accessible_type (widget_class, GTK_TYPE_ENTRY_ACCESSIBLE); + gtk_widget_class_set_css_name (widget_class, I_("entry")); +} + +static GtkEditable * +gtk_password_entry_get_delegate (GtkEditable *editable) +{ + GtkPasswordEntry *entry = GTK_PASSWORD_ENTRY (editable); + GtkPasswordEntryPrivate *priv = gtk_password_entry_get_instance_private (entry); + + return GTK_EDITABLE (priv->entry); +} + +static void +gtk_password_entry_editable_init (GtkEditableInterface *iface) +{ + iface->get_delegate = gtk_password_entry_get_delegate; +} + +/** + * gtk_password_entry_new: + * + * Creates a #GtkPasswordEntry. + * + * Returns: a new #GtkPasswordEntry + */ +GtkWidget * +gtk_password_entry_new (void) +{ + return GTK_WIDGET (g_object_new (GTK_TYPE_PASSWORD_ENTRY, NULL)); +} diff --git a/gtk/gtkpasswordentry.h b/gtk/gtkpasswordentry.h new file mode 100644 index 0000000000..66de3ee961 --- /dev/null +++ b/gtk/gtkpasswordentry.h @@ -0,0 +1,60 @@ +/* GTK - The GIMP Toolkit + * Copyright (C) 2019 Red Hat, Inc. + * + * Authors: + * - MAtthias Clasen + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + */ + +#ifndef __GTK_PASSWORD_ENTRY_H__ +#define __GTK_PASSWORD_ENTRY_H__ + +#if !defined (__GTK_H_INSIDE__) && !defined (GTK_COMPILATION) +#error "Only can be included directly." +#endif + +#include + +G_BEGIN_DECLS + +#define GTK_TYPE_PASSWORD_ENTRY (gtk_password_entry_get_type ()) +#define GTK_PASSWORD_ENTRY(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTK_TYPE_PASSWORD_ENTRY, GtkPasswordEntry)) +#define GTK_PASSWORD_ENTRY_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GTK_TYPE_PASSWORD_ENTRY, GtkPasswordEntryClass)) +#define GTK_IS_PASSWORD_ENTRY(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTK_TYPE_PASSWORD_ENTRY)) +#define GTK_IS_PASSWORD_ENTRY_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GTK_TYPE_PASSWORD_ENTRY)) +#define GTK_PASSWORD_ENTRY_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GTK_TYPE_PASSWORD_ENTRY, GtkPasswordEntryClass)) + +typedef struct _GtkPasswordEntry GtkPasswordEntry; +typedef struct _GtkPasswordEntryClass GtkPasswordEntryClass; + +struct _GtkPasswordEntry +{ + GtkWidget parent; +}; + +struct _GtkPasswordEntryClass +{ + GtkWidgetClass parent_class; +}; + +GDK_AVAILABLE_IN_ALL +GType gtk_password_entry_get_type (void) G_GNUC_CONST; + +GDK_AVAILABLE_IN_ALL +GtkWidget * gtk_password_entry_new (void); + +G_END_DECLS + +#endif /* __GTK_PASSWORD_ENTRY_H__ */ diff --git a/gtk/meson.build b/gtk/meson.build index 3eefb1139a..2e6c813b4a 100644 --- a/gtk/meson.build +++ b/gtk/meson.build @@ -293,6 +293,7 @@ gtk_public_sources = files([ 'gtkpagesetup.c', 'gtkpaned.c', 'gtkpapersize.c', + 'gtkpasswordentry.c', 'gtkpicture.c', 'gtkpopover.c', 'gtkpopovermenu.c', @@ -538,6 +539,7 @@ gtk_public_headers = files([ 'gtkpagesetup.h', 'gtkpaned.h', 'gtkpapersize.h', + 'gtkpasswordentry.h', 'gtkpicture.h', 'gtkpopover.h', 'gtkpopovermenu.h', From 210c5d88a2b134044a94fb3d931eb1c6ec11c64c Mon Sep 17 00:00:00 2001 From: Matthias Clasen Date: Sat, 16 Feb 2019 22:22:43 -0500 Subject: [PATCH 15/34] Add a test for GtkPassworkEntry --- tests/testentryicons.c | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/tests/testentryicons.c b/tests/testentryicons.c index bbc8eeb4bd..e6381ed045 100644 --- a/tests/testentryicons.c +++ b/tests/testentryicons.c @@ -241,22 +241,9 @@ main (int argc, char **argv) gtk_widget_set_halign (label, GTK_ALIGN_START); gtk_widget_set_valign (label, GTK_ALIGN_CENTER); - entry = gtk_entry_new (); + entry = gtk_password_entry_new (); gtk_widget_set_hexpand (entry, TRUE); gtk_grid_attach (GTK_GRID (grid), entry, 1, 3, 1, 1); - gtk_entry_set_visibility (GTK_ENTRY (entry), FALSE); - - gtk_entry_set_icon_from_icon_name (GTK_ENTRY (entry), - GTK_ENTRY_ICON_PRIMARY, - "dialog-password-symbolic"); - - gtk_entry_set_icon_activatable (GTK_ENTRY (entry), - GTK_ENTRY_ICON_PRIMARY, - FALSE); - - gtk_entry_set_icon_tooltip_text (GTK_ENTRY (entry), - GTK_ENTRY_ICON_PRIMARY, - "The password is hidden for security"); /* Name - Does not set any icons. */ label = gtk_label_new ("Name:"); From 667ea2dd4229307ddacb1d32f18259a15047575f Mon Sep 17 00:00:00 2001 From: Matthias Clasen Date: Sat, 16 Feb 2019 23:57:18 -0500 Subject: [PATCH 16/34] search entry: Use a GtkText --- gtk/gtksearchentry.c | 318 +++++++++++++++++++++++++++--------- gtk/gtksearchentry.h | 14 +- gtk/gtksearchentryprivate.h | 2 + 3 files changed, 246 insertions(+), 88 deletions(-) diff --git a/gtk/gtksearchentry.c b/gtk/gtksearchentry.c index acbd5e110e..b4c47f4c71 100644 --- a/gtk/gtksearchentry.c +++ b/gtk/gtksearchentry.c @@ -31,8 +31,13 @@ #include "gtkaccessible.h" #include "gtkbindings.h" -#include "gtkentryprivate.h" +#include "gtkeditable.h" +#include "gtkbox.h" +#include "gtkgesturemultipress.h" +#include "gtktextprivate.h" +#include "gtkimage.h" #include "gtkintl.h" +#include "gtkprivate.h" #include "gtkmarshalers.h" #include "gtkstylecontext.h" #include "gtkeventcontrollerkey.h" @@ -70,6 +75,7 @@ */ enum { + ACTIVATE, SEARCH_CHANGED, NEXT_MATCH, PREVIOUS_MATCH, @@ -77,25 +83,33 @@ enum { LAST_SIGNAL }; +enum { + PROP_0, + PROP_PLACEHOLDER_TEXT, + PROP_ACTIVATES_DEFAULT, + NUM_PROPERTIES, +}; + static guint signals[LAST_SIGNAL] = { 0 }; +static GParamSpec *props[NUM_PROPERTIES] = { NULL, }; + typedef struct { GtkWidget *capture_widget; GtkEventController *capture_widget_controller; + GtkWidget *box; + GtkWidget *entry; + GtkWidget *icon; + guint delayed_changed_id; gboolean content_changed; gboolean search_stopped; } GtkSearchEntryPrivate; -static void gtk_search_entry_icon_release (GtkEntry *entry, - GtkEntryIconPosition icon_pos); -static void gtk_search_entry_changed (GtkEditable *editable); static void gtk_search_entry_editable_init (GtkEditableInterface *iface); -static GtkEditableInterface *parent_editable_iface; - -G_DEFINE_TYPE_WITH_CODE (GtkSearchEntry, gtk_search_entry, GTK_TYPE_ENTRY, +G_DEFINE_TYPE_WITH_CODE (GtkSearchEntry, gtk_search_entry, GTK_TYPE_WIDGET, G_ADD_PRIVATE (GtkSearchEntry) G_IMPLEMENT_INTERFACE (GTK_TYPE_EDITABLE, gtk_search_entry_editable_init)) @@ -103,36 +117,23 @@ G_DEFINE_TYPE_WITH_CODE (GtkSearchEntry, gtk_search_entry, GTK_TYPE_ENTRY, /* 150 mseconds of delay */ #define DELAYED_TIMEOUT_ID 150 -/* This widget got created without a private structure, meaning - * that we cannot now have one without breaking ABI */ -#define GET_PRIV(e) ((GtkSearchEntryPrivate *) gtk_search_entry_get_instance_private ((GtkSearchEntry *) (e))) - static void -gtk_search_entry_preedit_changed (GtkEntry *entry, - const gchar *preedit) +text_changed (GtkSearchEntry *entry) { - GtkSearchEntryPrivate *priv = GET_PRIV (entry); + GtkSearchEntryPrivate *priv = gtk_search_entry_get_instance_private (entry); priv->content_changed = TRUE; } -static void -gtk_search_entry_notify (GObject *object, - GParamSpec *pspec) -{ - GtkSearchEntryPrivate *priv = GET_PRIV (object); - - if (strcmp (pspec->name, "text") == 0) - priv->content_changed = TRUE; - - if (G_OBJECT_CLASS (gtk_search_entry_parent_class)->notify) - G_OBJECT_CLASS (gtk_search_entry_parent_class)->notify (object, pspec); -} - static void gtk_search_entry_finalize (GObject *object) { - GtkSearchEntryPrivate *priv = GET_PRIV (object); + GtkSearchEntry *entry = GTK_SEARCH_ENTRY (object); + GtkSearchEntryPrivate *priv = gtk_search_entry_get_instance_private (entry); + + gtk_editable_finish_delegate (GTK_EDITABLE (entry)); + + g_clear_pointer (&priv->box, gtk_widget_unparent); if (priv->delayed_changed_id > 0) g_source_remove (priv->delayed_changed_id); @@ -145,25 +146,147 @@ gtk_search_entry_finalize (GObject *object) static void gtk_search_entry_stop_search (GtkSearchEntry *entry) { - GtkSearchEntryPrivate *priv = GET_PRIV (entry); + GtkSearchEntryPrivate *priv = gtk_search_entry_get_instance_private (entry); priv->search_stopped = TRUE; } +static void +gtk_search_entry_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GtkSearchEntry *entry = GTK_SEARCH_ENTRY (object); + GtkSearchEntryPrivate *priv = gtk_search_entry_get_instance_private (entry); + + if (gtk_editable_delegate_set_property (object, prop_id, value, pspec)) + return; + + switch (prop_id) + { + case PROP_PLACEHOLDER_TEXT: + gtk_text_set_placeholder_text (GTK_TEXT (priv->entry), g_value_get_string (value)); + break; + + case PROP_ACTIVATES_DEFAULT: + gtk_text_set_activates_default (GTK_TEXT (priv->entry), g_value_get_boolean (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +gtk_search_entry_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GtkSearchEntry *entry = GTK_SEARCH_ENTRY (object); + GtkSearchEntryPrivate *priv = gtk_search_entry_get_instance_private (entry); + + if (gtk_editable_delegate_get_property (object, prop_id, value, pspec)) + return; + + switch (prop_id) + { + case PROP_PLACEHOLDER_TEXT: + g_value_set_string (value, gtk_text_get_placeholder_text (GTK_TEXT (priv->entry))); + break; + + case PROP_ACTIVATES_DEFAULT: + g_value_set_boolean (value, gtk_text_get_activates_default (GTK_TEXT (priv->entry))); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +gtk_search_entry_measure (GtkWidget *widget, + GtkOrientation orientation, + int for_size, + int *minimum, + int *natural, + int *minimum_baseline, + int *natural_baseline) +{ + GtkSearchEntry *entry = GTK_SEARCH_ENTRY (widget); + GtkSearchEntryPrivate *priv = gtk_search_entry_get_instance_private (entry); + + gtk_widget_measure (priv->box, orientation, for_size, + minimum, natural, + minimum_baseline, natural_baseline); +} + +static void +gtk_search_entry_size_allocate (GtkWidget *widget, + int width, + int height, + int baseline) +{ + GtkSearchEntry *entry = GTK_SEARCH_ENTRY (widget); + GtkSearchEntryPrivate *priv = gtk_search_entry_get_instance_private (entry); + + gtk_widget_size_allocate (priv->box, + &(GtkAllocation) { 0, 0, width, height }, + baseline); +} + +static void +gtk_search_entry_grab_focus (GtkWidget *widget) +{ + GtkSearchEntry *entry = GTK_SEARCH_ENTRY (widget); + GtkSearchEntryPrivate *priv = gtk_search_entry_get_instance_private (entry); + + gtk_text_grab_focus_without_selecting (GTK_TEXT (priv->entry)); +} + static void gtk_search_entry_class_init (GtkSearchEntryClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); GtkBindingSet *binding_set; object_class->finalize = gtk_search_entry_finalize; - object_class->notify = gtk_search_entry_notify; + object_class->get_property = gtk_search_entry_get_property; + object_class->set_property = gtk_search_entry_set_property; + + widget_class->measure = gtk_search_entry_measure; + widget_class->size_allocate = gtk_search_entry_size_allocate; + widget_class->grab_focus = gtk_search_entry_grab_focus; klass->stop_search = gtk_search_entry_stop_search; - g_signal_override_class_handler ("icon-release", - GTK_TYPE_SEARCH_ENTRY, - G_CALLBACK (gtk_search_entry_icon_release)); + props[PROP_PLACEHOLDER_TEXT] = + g_param_spec_string ("placeholder-text", + P_("Placeholder text"), + P_("Show text in the entry when it’s empty and unfocused"), + NULL, + GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY); + + props[PROP_ACTIVATES_DEFAULT] = + g_param_spec_boolean ("activates-default", + P_("Activates default"), + P_("Whether to activate the default widget (such as the default button in a dialog) when Enter is pressed"), + FALSE, + GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY); + + g_object_class_install_properties (object_class, NUM_PROPERTIES, props); + gtk_editable_install_properties (object_class, NUM_PROPERTIES); + + signals[ACTIVATE] = + g_signal_new (I_("activate"), + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, + G_STRUCT_OFFSET (GtkSearchEntryClass, activate), + NULL, NULL, + NULL, + G_TYPE_NONE, 0); /** * GtkSearchEntry::search-changed: @@ -254,37 +377,42 @@ gtk_search_entry_class_init (GtkSearchEntryClass *klass) "previous-match", 0); gtk_binding_entry_add_signal (binding_set, GDK_KEY_Escape, 0, "stop-search", 0); + + gtk_widget_class_set_css_name (widget_class, I_("entry")); +} + +static GtkEditable * +gtk_search_entry_get_delegate (GtkEditable *editable) +{ + GtkSearchEntry *entry = GTK_SEARCH_ENTRY (editable); + GtkSearchEntryPrivate *priv = gtk_search_entry_get_instance_private (entry); + + return GTK_EDITABLE (priv->entry); } static void gtk_search_entry_editable_init (GtkEditableInterface *iface) { - parent_editable_iface = g_type_interface_peek_parent (iface); - iface->do_insert_text = parent_editable_iface->do_insert_text; - iface->do_delete_text = parent_editable_iface->do_delete_text; - iface->insert_text = parent_editable_iface->insert_text; - iface->delete_text = parent_editable_iface->delete_text; - iface->get_chars = parent_editable_iface->get_chars; - iface->set_selection_bounds = parent_editable_iface->set_selection_bounds; - iface->get_selection_bounds = parent_editable_iface->get_selection_bounds; - iface->set_position = parent_editable_iface->set_position; - iface->get_position = parent_editable_iface->get_position; - iface->changed = gtk_search_entry_changed; + iface->get_delegate = gtk_search_entry_get_delegate; } static void -gtk_search_entry_icon_release (GtkEntry *entry, - GtkEntryIconPosition icon_pos) +gtk_search_entry_icon_release (GtkGestureMultiPress *press, + int n_press, + double x, + double y, + GtkSearchEntry *entry) { - if (icon_pos == GTK_ENTRY_ICON_SECONDARY) - gtk_entry_set_text (entry, ""); + GtkSearchEntryPrivate *priv = gtk_search_entry_get_instance_private (entry); + + gtk_editable_set_text (GTK_EDITABLE (priv->entry), ""); } static gboolean gtk_search_entry_changed_timeout_cb (gpointer user_data) { GtkSearchEntry *entry = user_data; - GtkSearchEntryPrivate *priv = GET_PRIV (entry); + GtkSearchEntryPrivate *priv = gtk_search_entry_get_instance_private (entry); g_signal_emit (entry, signals[SEARCH_CHANGED], 0); priv->delayed_changed_id = 0; @@ -295,7 +423,7 @@ gtk_search_entry_changed_timeout_cb (gpointer user_data) static void reset_timeout (GtkSearchEntry *entry) { - GtkSearchEntryPrivate *priv = GET_PRIV (entry); + GtkSearchEntryPrivate *priv = gtk_search_entry_get_instance_private (entry); if (priv->delayed_changed_id > 0) g_source_remove (priv->delayed_changed_id); @@ -306,35 +434,19 @@ reset_timeout (GtkSearchEntry *entry) } static void -gtk_search_entry_changed (GtkEditable *editable) +gtk_search_entry_changed (GtkEditable *editable, + GtkSearchEntry *entry) { - GtkSearchEntry *entry = GTK_SEARCH_ENTRY (editable); - GtkSearchEntryPrivate *priv = GET_PRIV (entry); - const char *str, *icon_name; - gboolean cleared; + GtkSearchEntryPrivate *priv = gtk_search_entry_get_instance_private (entry); + const char *str; /* Update the icons first */ - str = gtk_entry_get_text (GTK_ENTRY (entry)); + str = gtk_editable_get_text (GTK_EDITABLE (priv->entry)); if (str == NULL || *str == '\0') { - icon_name = NULL; - cleared = TRUE; - } - else - { - icon_name = "edit-clear-symbolic"; - cleared = FALSE; - } + gtk_widget_hide (priv->icon); - g_object_set (entry, - "secondary-icon-name", icon_name, - "secondary-icon-activatable", !cleared, - "secondary-icon-sensitive", !cleared, - NULL); - - if (cleared) - { if (priv->delayed_changed_id > 0) { g_source_remove (priv->delayed_changed_id); @@ -344,30 +456,67 @@ gtk_search_entry_changed (GtkEditable *editable) } else { + gtk_widget_show (priv->icon); + /* Queue up the timeout */ reset_timeout (entry); } } +static void +notify_cb (GObject *object, + GParamSpec *pspec, + gpointer data) +{ + /* The editable interface properties are already forwarded by the editable delegate setup */ + if (g_str_equal (pspec->name, "placeholder-text") || + g_str_equal (pspec->name, "activates-default")) + g_object_notify (data, pspec->name); +} + +static void +activate_cb (GtkText *text, + gpointer data) +{ + g_signal_emit (data, signals[ACTIVATE], 0); +} + static void gtk_search_entry_init (GtkSearchEntry *entry) { + GtkSearchEntryPrivate *priv = gtk_search_entry_get_instance_private (entry); AtkObject *atk_obj; + GtkGesture *press; - g_object_set (entry, - "primary-icon-name", "edit-find-symbolic", - "primary-icon-activatable", FALSE, - "primary-icon-sensitive", FALSE, - NULL); + gtk_widget_set_has_surface (GTK_WIDGET (entry), FALSE); + + priv->box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0); + gtk_widget_set_parent (priv->box, GTK_WIDGET (entry)); + + priv->entry = gtk_text_new (); + gtk_widget_set_hexpand (priv->entry, TRUE); + gtk_widget_set_vexpand (priv->entry, TRUE); + gtk_container_add (GTK_CONTAINER (priv->box), GTK_WIDGET (priv->entry)); + gtk_editable_init_delegate (GTK_EDITABLE (entry)); + g_signal_connect_swapped (priv->entry, "changed", G_CALLBACK (text_changed), entry); + g_signal_connect_after (priv->entry, "changed", G_CALLBACK (gtk_search_entry_changed), entry); + g_signal_connect_swapped (priv->entry, "preedit-changed", G_CALLBACK (text_changed), entry); + g_signal_connect (priv->entry, "notify", G_CALLBACK (notify_cb), entry); + g_signal_connect (priv->entry, "activate", G_CALLBACK (activate_cb), entry); + + priv->icon = gtk_image_new_from_icon_name ("edit-clear-symbolic"); + gtk_container_add (GTK_CONTAINER (priv->box), GTK_WIDGET (priv->icon)); + gtk_widget_hide (priv->icon); + + press = gtk_gesture_multi_press_new (); + g_signal_connect (press, "released", G_CALLBACK (gtk_search_entry_icon_release), entry); + gtk_widget_add_controller (priv->icon, GTK_EVENT_CONTROLLER (press)); atk_obj = gtk_widget_get_accessible (GTK_WIDGET (entry)); if (GTK_IS_ACCESSIBLE (atk_obj)) atk_object_set_name (atk_obj, _("Search")); gtk_style_context_add_class (gtk_widget_get_style_context (GTK_WIDGET (entry)), I_("search")); - - g_signal_connect (gtk_entry_get_text_widget (GTK_ENTRY (entry)), "preedit-changed", - G_CALLBACK (gtk_search_entry_preedit_changed), NULL); } /** @@ -431,7 +580,7 @@ gboolean gtk_search_entry_handle_event (GtkSearchEntry *entry, GdkEvent *event) { - GtkSearchEntryPrivate *priv = GET_PRIV (entry); + GtkSearchEntryPrivate *priv = gtk_search_entry_get_instance_private (entry); gboolean handled; guint keyval, state; @@ -544,3 +693,10 @@ gtk_search_entry_get_key_capture_widget (GtkSearchEntry *entry) return priv->capture_widget; } +GtkEventController * +gtk_search_entry_get_key_controller (GtkSearchEntry *entry) +{ + GtkSearchEntryPrivate *priv = gtk_search_entry_get_instance_private (entry); + + return gtk_text_get_key_controller (GTK_TEXT (priv->entry)); +} diff --git a/gtk/gtksearchentry.h b/gtk/gtksearchentry.h index cdabb9fcda..5d65fc2e83 100644 --- a/gtk/gtksearchentry.h +++ b/gtk/gtksearchentry.h @@ -48,17 +48,18 @@ typedef struct _GtkSearchEntryClass GtkSearchEntryClass; struct _GtkSearchEntry { - GtkEntry parent; + GtkWidget parent; }; struct _GtkSearchEntryClass { - GtkEntryClass parent_class; + GtkWidgetClass parent_class; - void (*search_changed) (GtkSearchEntry *entry); - void (*next_match) (GtkSearchEntry *entry); - void (*previous_match) (GtkSearchEntry *entry); - void (*stop_search) (GtkSearchEntry *entry); + void (* activate) (GtkSearchEntry *entry); + void (* search_changed) (GtkSearchEntry *entry); + void (* next_match) (GtkSearchEntry *entry); + void (* previous_match) (GtkSearchEntry *entry); + void (* stop_search) (GtkSearchEntry *entry); }; GDK_AVAILABLE_IN_ALL @@ -78,7 +79,6 @@ GDK_AVAILABLE_IN_ALL GtkWidget* gtk_search_entry_get_key_capture_widget (GtkSearchEntry *entry); - G_END_DECLS #endif /* __GTK_SEARCH_ENTRY_H__ */ diff --git a/gtk/gtksearchentryprivate.h b/gtk/gtksearchentryprivate.h index 1b0706d412..46b5d9f5b9 100644 --- a/gtk/gtksearchentryprivate.h +++ b/gtk/gtksearchentryprivate.h @@ -32,6 +32,8 @@ G_BEGIN_DECLS gboolean gtk_search_entry_is_keynav (guint keyval, GdkModifierType state); +GtkEventController * gtk_search_entry_get_key_controller (GtkSearchEntry *entry); + G_END_DECLS #endif /* __GTK_SEARCH_ENTRY_PRIVATE_H__ */ From fa3d1940bf2b36c165164584f12ef37c0a1c6206 Mon Sep 17 00:00:00 2001 From: Matthias Clasen Date: Sat, 16 Feb 2019 23:58:06 -0500 Subject: [PATCH 17/34] search bar: Work with the new GtkSearchEntry Don't assume a search entry is an entry, and use the editable api as far as possible. --- gtk/gtksearchbar.c | 26 ++++++++++++++------------ gtk/gtksearchbar.h | 2 +- 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/gtk/gtksearchbar.c b/gtk/gtksearchbar.c index 93594d15f2..0b3f916344 100644 --- a/gtk/gtksearchbar.c +++ b/gtk/gtksearchbar.c @@ -148,9 +148,9 @@ gtk_search_bar_handle_event_for_entry (GtkSearchBar *bar, preedit_change_id = g_signal_connect (priv->entry, "preedit-changed", G_CALLBACK (preedit_changed_cb), &preedit_changed); - old_text = g_strdup (gtk_entry_get_text (GTK_ENTRY (priv->entry))); + old_text = g_strdup (gtk_editable_get_text (GTK_EDITABLE (priv->entry))); res = gtk_widget_event (priv->entry, event); - new_text = g_strdup (gtk_entry_get_text (GTK_ENTRY (priv->entry))); + new_text = g_strdup (gtk_editable_get_text (GTK_EDITABLE (priv->entry))); g_signal_handler_disconnect (priv->entry, preedit_change_id); @@ -255,10 +255,12 @@ reveal_child_changed_cb (GObject *object, if (priv->entry) { - if (reveal_child) + if (reveal_child && GTK_IS_ENTRY (priv->entry)) gtk_entry_grab_focus_without_selecting (GTK_ENTRY (priv->entry)); + else if (GTK_IS_SEARCH_ENTRY (priv->entry)) + gtk_widget_grab_focus (priv->entry); else - gtk_entry_set_text (GTK_ENTRY (priv->entry), ""); + gtk_editable_set_text (GTK_EDITABLE (priv->entry), ""); } g_object_notify (G_OBJECT (bar), "search-mode-enabled"); @@ -284,8 +286,8 @@ gtk_search_bar_add (GtkContainer *container, /* If an entry is the only child, save the developer a couple of * lines of code */ - if (GTK_IS_ENTRY (child)) - gtk_search_bar_connect_entry (bar, GTK_ENTRY (child)); + if (GTK_IS_EDITABLE (child)) + gtk_search_bar_connect_entry (bar, GTK_EDITABLE (child)); _gtk_bin_set_child (GTK_BIN (container), child); } @@ -297,7 +299,7 @@ gtk_search_bar_remove (GtkContainer *container, GtkSearchBar *bar = GTK_SEARCH_BAR (container); GtkSearchBarPrivate *priv = gtk_search_bar_get_instance_private (bar); - if (GTK_IS_ENTRY (child)) + if (GTK_IS_EDITABLE (child)) gtk_search_bar_connect_entry (bar, NULL); gtk_center_box_set_center_widget (GTK_CENTER_BOX (priv->box_center), NULL); @@ -350,7 +352,7 @@ gtk_search_bar_get_property (GObject *object, } static void gtk_search_bar_set_entry (GtkSearchBar *bar, - GtkEntry *entry); + GtkEditable *editable); static void gtk_search_bar_dispose (GObject *object) @@ -494,7 +496,7 @@ gtk_search_bar_new (void) static void gtk_search_bar_set_entry (GtkSearchBar *bar, - GtkEntry *entry) + GtkEditable *entry) { GtkSearchBarPrivate *priv = gtk_search_bar_get_instance_private (bar); @@ -527,7 +529,7 @@ gtk_search_bar_set_entry (GtkSearchBar *bar, /** * gtk_search_bar_connect_entry: * @bar: a #GtkSearchBar - * @entry: a #GtkEntry + * @entry: a #GtkEditable * * Connects the #GtkEntry widget passed as the one to be used in * this search bar. The entry should be a descendant of the search bar. @@ -536,10 +538,10 @@ gtk_search_bar_set_entry (GtkSearchBar *bar, */ void gtk_search_bar_connect_entry (GtkSearchBar *bar, - GtkEntry *entry) + GtkEditable *entry) { g_return_if_fail (GTK_IS_SEARCH_BAR (bar)); - g_return_if_fail (entry == NULL || GTK_IS_ENTRY (entry)); + g_return_if_fail (entry == NULL || GTK_IS_EDITABLE (entry)); gtk_search_bar_set_entry (bar, entry); } diff --git a/gtk/gtksearchbar.h b/gtk/gtksearchbar.h index e06e181f7d..ebf31e3bcc 100644 --- a/gtk/gtksearchbar.h +++ b/gtk/gtksearchbar.h @@ -78,7 +78,7 @@ GtkWidget* gtk_search_bar_new (void); GDK_AVAILABLE_IN_ALL void gtk_search_bar_connect_entry (GtkSearchBar *bar, - GtkEntry *entry); + GtkEditable *entry); GDK_AVAILABLE_IN_ALL gboolean gtk_search_bar_get_search_mode (GtkSearchBar *bar); From 3a1a7c43f3353c018a4dbfbf203ca967277c9c9e Mon Sep 17 00:00:00 2001 From: Matthias Clasen Date: Sat, 16 Feb 2019 23:56:15 -0500 Subject: [PATCH 18/34] treeview: make search work Make search work with both GtkEntry and GtkSearchEntry, even if a GtkSearchEntry is not derived from GtkEntry anymore. --- gtk/gtktreeview.c | 40 +++++++++++++++++++++++++++------------- gtk/gtktreeview.h | 4 ++-- 2 files changed, 29 insertions(+), 15 deletions(-) diff --git a/gtk/gtktreeview.c b/gtk/gtktreeview.c index 45a8ae2cc3..9e838c2319 100644 --- a/gtk/gtktreeview.c +++ b/gtk/gtktreeview.c @@ -34,6 +34,7 @@ #include "gtkdragdest.h" #include "gtkdragsource.h" #include "gtkentryprivate.h" +#include "gtksearchentryprivate.h" #include "gtkeventcontrollerkey.h" #include "gtkeventcontrollermotion.h" #include "gtkeventcontrollerscroll.h" @@ -2111,10 +2112,17 @@ gtk_tree_view_destroy (GtkWidget *widget) if (tree_view->priv->search_custom_entry_set) { + GtkEventController *controller; + g_signal_handlers_disconnect_by_func (tree_view->priv->search_entry, G_CALLBACK (gtk_tree_view_search_init), tree_view); - g_signal_handlers_disconnect_by_func (gtk_entry_get_key_controller (GTK_ENTRY (tree_view->priv->search_entry)), + + if (GTK_IS_ENTRY (tree_view->priv->search_entry)) + controller = gtk_entry_get_key_controller (GTK_ENTRY (tree_view->priv->search_entry)); + else + controller = gtk_search_entry_get_key_controller (GTK_SEARCH_ENTRY (tree_view->priv->search_entry)); + g_signal_handlers_disconnect_by_func (controller, G_CALLBACK (gtk_tree_view_search_key_pressed), tree_view); @@ -10310,13 +10318,16 @@ gtk_tree_view_real_start_interactive_search (GtkTreeView *tree_view, gtk_tree_view_ensure_interactive_directory (tree_view); if (keybinding) - gtk_entry_set_text (GTK_ENTRY (tree_view->priv->search_entry), ""); + gtk_editable_set_text (GTK_EDITABLE (tree_view->priv->search_entry), ""); /* done, show it */ tree_view->priv->search_position_func (tree_view, tree_view->priv->search_window, tree_view->priv->search_position_user_data); /* Grab focus without selecting all the text. */ - gtk_entry_grab_focus_without_selecting (GTK_ENTRY (tree_view->priv->search_entry)); + if (GTK_IS_ENTRY (tree_view->priv->search_entry)) + gtk_entry_grab_focus_without_selecting (GTK_ENTRY (tree_view->priv->search_entry)); + else + gtk_widget_grab_focus (tree_view->priv->search_entry); gtk_widget_show (tree_view->priv->search_window); if (tree_view->priv->search_entry_changed_id == 0) @@ -13632,13 +13643,13 @@ gtk_tree_view_set_search_equal_func (GtkTreeView *tree_view, * * Returns: (transfer none): the entry currently in use as search entry. */ -GtkEntry * +GtkEditable * gtk_tree_view_get_search_entry (GtkTreeView *tree_view) { g_return_val_if_fail (GTK_IS_TREE_VIEW (tree_view), NULL); if (tree_view->priv->search_custom_entry_set) - return GTK_ENTRY (tree_view->priv->search_entry); + return GTK_EDITABLE (tree_view->priv->search_entry); return NULL; } @@ -13656,10 +13667,10 @@ gtk_tree_view_get_search_entry (GtkTreeView *tree_view) */ void gtk_tree_view_set_search_entry (GtkTreeView *tree_view, - GtkEntry *entry) + GtkEditable *entry) { g_return_if_fail (GTK_IS_TREE_VIEW (tree_view)); - g_return_if_fail (entry == NULL || GTK_IS_ENTRY (entry)); + g_return_if_fail (entry == NULL || GTK_IS_ENTRY (entry) || GTK_IS_SEARCH_ENTRY (entry)); if (tree_view->priv->search_custom_entry_set) { @@ -13683,6 +13694,8 @@ gtk_tree_view_set_search_entry (GtkTreeView *tree_view, if (entry) { + GtkEventController *controller; + tree_view->priv->search_entry = GTK_WIDGET (g_object_ref (entry)); tree_view->priv->search_custom_entry_set = TRUE; @@ -13694,10 +13707,12 @@ gtk_tree_view_set_search_entry (GtkTreeView *tree_view, tree_view); } - g_signal_connect (gtk_entry_get_key_controller (GTK_ENTRY (tree_view->priv->search_entry)), - "key-pressed", - G_CALLBACK (gtk_tree_view_search_key_pressed), - tree_view); + if (GTK_IS_ENTRY (entry)) + controller = gtk_entry_get_key_controller (GTK_ENTRY (entry)); + else + controller = gtk_search_entry_get_key_controller (GTK_SEARCH_ENTRY (entry)); + g_signal_connect (controller, "key-pressed", + G_CALLBACK (gtk_tree_view_search_key_pressed), tree_view); gtk_tree_view_search_init (tree_view->priv->search_entry, tree_view); } @@ -14266,10 +14281,9 @@ gtk_tree_view_search_init (GtkWidget *entry, GtkTreeModel *model; GtkTreeSelection *selection; - g_return_if_fail (GTK_IS_ENTRY (entry)); g_return_if_fail (GTK_IS_TREE_VIEW (tree_view)); - text = gtk_entry_get_text (GTK_ENTRY (entry)); + text = gtk_editable_get_text (GTK_EDITABLE (entry)); model = gtk_tree_view_get_model (tree_view); selection = gtk_tree_view_get_selection (tree_view); diff --git a/gtk/gtktreeview.h b/gtk/gtktreeview.h index 12fe7e9efd..33c043cccd 100644 --- a/gtk/gtktreeview.h +++ b/gtk/gtktreeview.h @@ -433,10 +433,10 @@ void gtk_tree_view_set_search_equal_func (GtkTreeView GDestroyNotify search_destroy); GDK_AVAILABLE_IN_ALL -GtkEntry *gtk_tree_view_get_search_entry (GtkTreeView *tree_view); +GtkEditable *gtk_tree_view_get_search_entry (GtkTreeView *tree_view); GDK_AVAILABLE_IN_ALL void gtk_tree_view_set_search_entry (GtkTreeView *tree_view, - GtkEntry *entry); + GtkEditable *entry); GDK_AVAILABLE_IN_ALL GtkTreeViewSearchPositionFunc gtk_tree_view_get_search_position_func (GtkTreeView *tree_view); GDK_AVAILABLE_IN_ALL From b8c981fb2b5c8fe26b282b47ef1436312470e624 Mon Sep 17 00:00:00 2001 From: Matthias Clasen Date: Sat, 16 Feb 2019 23:58:54 -0500 Subject: [PATCH 19/34] inspector: Work with new search entry Don't assume a search entry is an entry, and use the editable api as far as possible. --- gtk/inspector/object-tree.c | 6 +++--- gtk/inspector/prop-list.c | 6 +++--- gtk/inspector/resource-list.c | 4 ++-- gtk/inspector/statistics.c | 2 +- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/gtk/inspector/object-tree.c b/gtk/inspector/object-tree.c index f2a2f13e1b..618cf0866f 100644 --- a/gtk/inspector/object-tree.c +++ b/gtk/inspector/object-tree.c @@ -916,7 +916,7 @@ search (GtkInspectorObjectTree *wt, guint i, selected, n, row; const char *text; - text = gtk_entry_get_text (GTK_ENTRY (priv->search_entry)); + text = gtk_editable_get_text (GTK_EDITABLE (priv->search_entry)); if (gtk_list_box_get_selected_row (priv->list)) { selected = gtk_list_box_row_get_index (gtk_list_box_get_selected_row (priv->list)); @@ -998,7 +998,7 @@ static void stop_search (GtkWidget *entry, GtkInspectorObjectTree *wt) { - gtk_entry_set_text (GTK_ENTRY (wt->priv->search_entry), ""); + gtk_editable_set_text (GTK_EDITABLE (wt->priv->search_entry), ""); gtk_search_bar_set_search_mode (GTK_SEARCH_BAR (wt->priv->search_bar), FALSE); } @@ -1155,7 +1155,7 @@ gtk_inspector_object_tree_init (GtkInspectorObjectTree *wt) gtk_widget_init_template (GTK_WIDGET (wt)); gtk_search_bar_connect_entry (GTK_SEARCH_BAR (wt->priv->search_bar), - GTK_ENTRY (wt->priv->search_entry)); + GTK_EDITABLE (wt->priv->search_entry)); root_model = create_root_model (); wt->priv->tree_model = gtk_tree_list_model_new (FALSE, diff --git a/gtk/inspector/prop-list.c b/gtk/inspector/prop-list.c index 87aa1d7548..d12a97bd7e 100644 --- a/gtk/inspector/prop-list.c +++ b/gtk/inspector/prop-list.c @@ -79,7 +79,7 @@ static void search_close_clicked (GtkWidget *button, GtkInspectorPropList *pl) { - gtk_entry_set_text (GTK_ENTRY (pl->priv->search_entry), ""); + gtk_editable_set_text (GTK_EDITABLE (pl->priv->search_entry), ""); gtk_stack_set_visible_child_name (GTK_STACK (pl->priv->search_stack), "title"); } @@ -275,7 +275,7 @@ constructed (GObject *object) pl->priv->search_stack = gtk_widget_get_parent (pl->priv->search_entry); gtk_tree_view_set_search_entry (GTK_TREE_VIEW (pl->priv->tree), - GTK_ENTRY (pl->priv->search_entry)); + GTK_EDITABLE (pl->priv->search_entry)); g_signal_connect (pl->priv->search_entry, "stop-search", G_CALLBACK (search_close_clicked), pl); @@ -525,7 +525,7 @@ gtk_inspector_prop_list_set_object (GtkInspectorPropList *pl, cleanup_object (pl); - gtk_entry_set_text (GTK_ENTRY (pl->priv->search_entry), ""); + gtk_editable_set_text (GTK_EDITABLE (pl->priv->search_entry), ""); gtk_stack_set_visible_child_name (GTK_STACK (pl->priv->search_stack), "title"); if (pl->priv->child_properties) diff --git a/gtk/inspector/resource-list.c b/gtk/inspector/resource-list.c index 78e116a992..b1d99223fd 100644 --- a/gtk/inspector/resource-list.c +++ b/gtk/inspector/resource-list.c @@ -548,7 +548,7 @@ match_row (GtkTreeModel *model, const gchar *text; gboolean match; - text = gtk_entry_get_text (GTK_ENTRY (sl->priv->search_entry)); + text = gtk_editable_get_text (GTK_EDITABLE (sl->priv->search_entry)); gtk_tree_model_get (model, iter, COLUMN_NAME, &name, COLUMN_PATH, &path, @@ -622,7 +622,7 @@ gtk_inspector_resource_list_init (GtkInspectorResourceList *sl) g_signal_connect (sl, "map", G_CALLBACK (on_map), NULL); gtk_search_bar_connect_entry (GTK_SEARCH_BAR (sl->priv->search_bar), - GTK_ENTRY (sl->priv->search_entry)); + GTK_EDITABLE (sl->priv->search_entry)); g_signal_connect (sl->priv->search_bar, "notify::search-mode-enabled", G_CALLBACK (search_mode_changed), sl); diff --git a/gtk/inspector/statistics.c b/gtk/inspector/statistics.c index ad988be72f..e975c45db6 100644 --- a/gtk/inspector/statistics.c +++ b/gtk/inspector/statistics.c @@ -374,7 +374,7 @@ gtk_inspector_statistics_init (GtkInspectorStatistics *sl) GINT_TO_POINTER (COLUMN_CUMULATIVE2), NULL); sl->priv->counts = g_hash_table_new_full (NULL, NULL, NULL, type_data_free); - gtk_tree_view_set_search_entry (sl->priv->view, GTK_ENTRY (sl->priv->search_entry)); + gtk_tree_view_set_search_entry (sl->priv->view, GTK_EDITABLE (sl->priv->search_entry)); gtk_tree_view_set_search_equal_func (sl->priv->view, match_row, sl, NULL); g_signal_connect (sl, "hierarchy-changed", G_CALLBACK (hierarchy_changed), NULL); } From a0454e40253aff0a7a05d6ae3c2d806c27fd4c3e Mon Sep 17 00:00:00 2001 From: Matthias Clasen Date: Sat, 16 Feb 2019 23:59:39 -0500 Subject: [PATCH 20/34] gtk-demo: Make the delayed search demo work Make this demo work with the new search entry. --- demos/gtk-demo/search_entry2.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/demos/gtk-demo/search_entry2.c b/demos/gtk-demo/search_entry2.c index 1b0aa15395..ea4bb62151 100644 --- a/demos/gtk-demo/search_entry2.c +++ b/demos/gtk-demo/search_entry2.c @@ -13,7 +13,7 @@ search_changed_cb (GtkSearchEntry *entry, GtkLabel *result_label) { const char *text; - text = gtk_entry_get_text (GTK_ENTRY (entry)); + text = gtk_editable_get_text (GTK_EDITABLE (entry)); g_message ("search changed: %s", text); gtk_label_set_text (result_label, text ? text : ""); } @@ -22,7 +22,7 @@ static void changed_cb (GtkEditable *editable) { const char *text; - text = gtk_entry_get_text (GTK_ENTRY (editable)); + text = gtk_editable_get_text (GTK_EDITABLE (editable)); g_message ("changed: %s", text); } @@ -85,7 +85,7 @@ do_search_entry2 (GtkWidget *do_widget) gtk_widget_set_halign (container, GTK_ALIGN_CENTER); gtk_container_add (GTK_CONTAINER (container), entry); searchbar = gtk_search_bar_new (); - gtk_search_bar_connect_entry (GTK_SEARCH_BAR (searchbar), GTK_ENTRY (entry)); + gtk_search_bar_connect_entry (GTK_SEARCH_BAR (searchbar), GTK_EDITABLE (entry)); gtk_search_bar_set_show_close_button (GTK_SEARCH_BAR (searchbar), FALSE); gtk_container_add (GTK_CONTAINER (searchbar), container); gtk_container_add (GTK_CONTAINER (vbox), searchbar); From f1012b5623ccb1ff0b2f4592d584b3566f220643 Mon Sep 17 00:00:00 2001 From: Matthias Clasen Date: Sun, 17 Feb 2019 16:13:58 -0500 Subject: [PATCH 21/34] Fix the search bar example More GtkSearchBar entry -> editable fixups. --- examples/search-bar.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/search-bar.c b/examples/search-bar.c index cb84e9ce06..0a42a925ce 100644 --- a/examples/search-bar.c +++ b/examples/search-bar.c @@ -28,7 +28,7 @@ activate_cb (GtkApplication *app, menu_button = gtk_menu_button_new (); gtk_container_add (GTK_CONTAINER (box), menu_button); - gtk_search_bar_connect_entry (GTK_SEARCH_BAR (search_bar), GTK_ENTRY (entry)); + gtk_search_bar_connect_entry (GTK_SEARCH_BAR (search_bar), GTK_EDITABLE (entry)); gtk_search_bar_set_key_capture_widget (GTK_SEARCH_BAR (search_bar), window); } From 7d9e63ae38cc26001e74d7882189958430059623 Mon Sep 17 00:00:00 2001 From: Matthias Clasen Date: Sun, 17 Feb 2019 14:47:53 -0500 Subject: [PATCH 22/34] app chooser: Use editable api on search entry --- gtk/gtkappchooserdialog.c | 2 +- gtk/gtkappchooserprivate.h | 2 +- gtk/gtkappchooserwidget.c | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/gtk/gtkappchooserdialog.c b/gtk/gtkappchooserdialog.c index 80143686ec..ec5ced63b2 100644 --- a/gtk/gtkappchooserdialog.c +++ b/gtk/gtkappchooserdialog.c @@ -362,7 +362,7 @@ construct_appchooser_widget (GtkAppChooserDialog *self) g_object_unref (info); _gtk_app_chooser_widget_set_search_entry (GTK_APP_CHOOSER_WIDGET (self->priv->app_chooser_widget), - GTK_ENTRY (self->priv->search_entry)); + GTK_EDITABLE (self->priv->search_entry)); gtk_search_bar_set_key_capture_widget (GTK_SEARCH_BAR (self->priv->search_bar), GTK_WIDGET (self)); diff --git a/gtk/gtkappchooserprivate.h b/gtk/gtkappchooserprivate.h index b5fe2e9aec..754619adc7 100644 --- a/gtk/gtkappchooserprivate.h +++ b/gtk/gtkappchooserprivate.h @@ -45,7 +45,7 @@ struct _GtkAppChooserIface { void _gtk_app_chooser_widget_set_search_entry (GtkAppChooserWidget *self, - GtkEntry *entry); + GtkEditable *editable); G_END_DECLS diff --git a/gtk/gtkappchooserwidget.c b/gtk/gtkappchooserwidget.c index 62f3566a38..9ff532dc2f 100644 --- a/gtk/gtkappchooserwidget.c +++ b/gtk/gtkappchooserwidget.c @@ -1489,7 +1489,7 @@ gtk_app_chooser_widget_get_default_text (GtkAppChooserWidget *self) void _gtk_app_chooser_widget_set_search_entry (GtkAppChooserWidget *self, - GtkEntry *entry) + GtkEditable *entry) { gtk_tree_view_set_search_entry (GTK_TREE_VIEW (self->priv->program_list), entry); From 37b841b59b0bc4a374f55e31b95368d0135b3338 Mon Sep 17 00:00:00 2001 From: Matthias Clasen Date: Sun, 17 Feb 2019 14:48:08 -0500 Subject: [PATCH 23/34] font chooser: Use editable api on search entry --- gtk/gtkfontchooserwidget.c | 12 ++++++------ gtk/ui/gtkfontchooserwidget.ui | 6 ------ 2 files changed, 6 insertions(+), 12 deletions(-) diff --git a/gtk/gtkfontchooserwidget.c b/gtk/gtkfontchooserwidget.c index afe1b922fb..b378d29a24 100644 --- a/gtk/gtkfontchooserwidget.c +++ b/gtk/gtkfontchooserwidget.c @@ -372,11 +372,11 @@ text_changed_cb (GtkEntry *entry, } static void -stop_search_cb (GtkEntry *entry, +stop_search_cb (GtkSearchEntry *entry, GtkFontChooserWidget *fc) { - if (gtk_entry_get_text (entry)[0] != 0) - gtk_entry_set_text (entry, ""); + if (gtk_editable_get_text (GTK_EDITABLE (entry))[0] != 0) + gtk_editable_set_text (GTK_EDITABLE (entry), ""); else { GtkWidget *dialog; @@ -623,7 +623,7 @@ gtk_font_chooser_widget_map (GtkWidget *widget) GtkFontChooserWidget *fontchooser = GTK_FONT_CHOOSER_WIDGET (widget); GtkFontChooserWidgetPrivate *priv = fontchooser->priv; - gtk_entry_set_text (GTK_ENTRY (priv->search_entry), ""); + gtk_editable_set_text (GTK_EDITABLE (priv->search_entry), ""); gtk_stack_set_visible_child_name (GTK_STACK (priv->stack), "list"); g_simple_action_set_state (G_SIMPLE_ACTION (priv->tweak_action), g_variant_new_boolean (FALSE)); @@ -767,7 +767,7 @@ change_tweak (GSimpleAction *action, } else { - gtk_entry_grab_focus_without_selecting (GTK_ENTRY (fontchooser->priv->search_entry)); + gtk_widget_grab_focus (fontchooser->priv->search_entry); gtk_stack_set_visible_child_name (GTK_STACK (fontchooser->priv->stack), "list"); } @@ -1034,7 +1034,7 @@ visible_func (GtkTreeModel *model, } /* If there's no filter string we show the item */ - search_text = gtk_entry_get_text (GTK_ENTRY (priv->search_entry)); + search_text = gtk_editable_get_text (GTK_EDITABLE (priv->search_entry)); if (strlen (search_text) == 0) return TRUE; diff --git a/gtk/ui/gtkfontchooserwidget.ui b/gtk/ui/gtkfontchooserwidget.ui index 5d06c53cd2..25b1f7083a 100644 --- a/gtk/ui/gtkfontchooserwidget.ui +++ b/gtk/ui/gtkfontchooserwidget.ui @@ -36,14 +36,8 @@ 6 - 1 1 1 - edit-find-symbolic - 0 - 0 - 0 - 0 Search font name From 08bd6904b939e88433dbbe48a9bdad64182ac9e1 Mon Sep 17 00:00:00 2001 From: Matthias Clasen Date: Sun, 17 Feb 2019 14:48:44 -0500 Subject: [PATCH 24/34] file chooser: Use editable api on the search entry --- gtk/gtkfilechooserwidget.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/gtk/gtkfilechooserwidget.c b/gtk/gtkfilechooserwidget.c index 707f40139d..c17eee8f9d 100644 --- a/gtk/gtkfilechooserwidget.c +++ b/gtk/gtkfilechooserwidget.c @@ -3064,7 +3064,7 @@ operation_mode_set_search (GtkFileChooserWidget *impl) gtk_stack_set_visible_child_name (GTK_STACK (priv->browse_files_stack), "list"); } - gtk_entry_grab_focus_without_selecting (GTK_ENTRY (priv->search_entry)); + gtk_widget_grab_focus (priv->search_entry); gtk_stack_set_visible_child_name (GTK_STACK (priv->browse_header_stack), "search"); gtk_revealer_set_reveal_child (GTK_REVEALER (priv->browse_header_revealer), TRUE); location_bar_update (impl); @@ -7043,7 +7043,7 @@ search_engine_finished_cb (GtkSearchEngine *engine, if (gtk_tree_model_iter_n_children (GTK_TREE_MODEL (priv->search_model), NULL) == 0) { gtk_stack_set_visible_child_name (GTK_STACK (priv->browse_files_stack), "empty"); - gtk_entry_grab_focus_without_selecting (GTK_ENTRY (priv->search_entry)); + gtk_widget_grab_focus (priv->search_entry); } } @@ -7084,7 +7084,7 @@ search_stop_searching (GtkFileChooserWidget *impl, if (remove_query && priv->search_entry) { - gtk_entry_set_text (GTK_ENTRY (priv->search_entry), ""); + gtk_editable_set_text (GTK_EDITABLE (priv->search_entry), ""); } if (priv->search_engine) @@ -7218,7 +7218,7 @@ search_entry_activate_cb (GtkFileChooserWidget *impl) if (priv->operation_mode != OPERATION_MODE_SEARCH) return; - text = gtk_entry_get_text (GTK_ENTRY (priv->search_entry)); + text = gtk_editable_get_text (GTK_EDITABLE (priv->search_entry)); /* reset any existing query object */ g_set_object (&priv->search_query, NULL); @@ -7254,7 +7254,7 @@ search_setup_widgets (GtkFileChooserWidget *impl) query = gtk_query_get_text (priv->search_query); if (query) { - gtk_entry_set_text (GTK_ENTRY (priv->search_entry), query); + gtk_editable_set_text (GTK_EDITABLE (priv->search_entry), query); search_start_query (impl, query); } else From 0e1194416693b9192ec5331e757329162406135d Mon Sep 17 00:00:00 2001 From: Matthias Clasen Date: Mon, 18 Feb 2019 14:23:14 -0500 Subject: [PATCH 25/34] widget-factory: Don't set can-focus on entries The focus needs to be on the text inside, now. --- demos/widget-factory/widget-factory.ui | 7 ------- 1 file changed, 7 deletions(-) diff --git a/demos/widget-factory/widget-factory.ui b/demos/widget-factory/widget-factory.ui index 419f1493d8..bf674d07b4 100644 --- a/demos/widget-factory/widget-factory.ui +++ b/demos/widget-factory/widget-factory.ui @@ -474,7 +474,6 @@ Suspendisse feugiat quam quis dolor accumsan cursus. - 1 1 Click icon to change mode @@ -486,7 +485,6 @@ Suspendisse feugiat quam quis dolor accumsan cursus. 0 - 1 entry @@ -498,7 +496,6 @@ Suspendisse feugiat quam quis dolor accumsan cursus. - 1 entry 1 @@ -575,7 +572,6 @@ Suspendisse feugiat quam quis dolor accumsan cursus. - 1 2 2 adjustment2 @@ -583,7 +579,6 @@ Suspendisse feugiat quam quis dolor accumsan cursus. - 1 0 2 2 @@ -3000,14 +2995,12 @@ microphone-sensitivity-medium-symbolic center - 1 Name… name_completion - 1 1 Age… From 613f8ceacf2eb615adb6c4d04810374ba19e0fdb Mon Sep 17 00:00:00 2001 From: Matthias Clasen Date: Sun, 17 Feb 2019 15:18:00 -0500 Subject: [PATCH 26/34] testsuite: update css node tests The entries have changed. Reflect that. --- testsuite/css/nodes/combobox.nodes | 5 +++-- testsuite/css/nodes/entries.nodes | 17 ++++++++++------- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/testsuite/css/nodes/combobox.nodes b/testsuite/css/nodes/combobox.nodes index 368d025459..4db8e2445b 100644 --- a/testsuite/css/nodes/combobox.nodes +++ b/testsuite/css/nodes/combobox.nodes @@ -21,8 +21,9 @@ combobox:dir(ltr) box.linked.horizontal:dir(ltr) entry.combo:dir(ltr) - undershoot.left:dir(ltr) - undershoot.right:dir(ltr) + text.flat:dir(ltr) + undershoot.left:dir(ltr) + undershoot.right:dir(ltr) button.combo:dir(ltr) box.horizontal:dir(ltr) arrow:dir(ltr) diff --git a/testsuite/css/nodes/entries.nodes b/testsuite/css/nodes/entries.nodes index 7339c042fe..36433c6e29 100644 --- a/testsuite/css/nodes/entries.nodes +++ b/testsuite/css/nodes/entries.nodes @@ -5,22 +5,25 @@ label:dir(ltr) label:dir(ltr) entry:dir(ltr) - undershoot.left:dir(ltr) - undershoot.right:dir(ltr) + text.flat:dir(ltr) + undershoot.left:dir(ltr) + undershoot.right:dir(ltr) progress.horizontal:dir(ltr) trough:dir(ltr) progress.left:dir(ltr) entry:dir(ltr) - undershoot.left:dir(ltr) - undershoot.right:dir(ltr) + text.flat:dir(ltr) + undershoot.left:dir(ltr) + undershoot.right:dir(ltr) entry:dir(ltr) image.left:dir(ltr) - undershoot.left:dir(ltr) - undershoot.right:dir(ltr) + text.flat:dir(ltr) + undershoot.left:dir(ltr) + undershoot.right:dir(ltr) image.right:dir(ltr) spinbutton.horizontal:dir(ltr) box.horizontal:dir(ltr) - entry:dir(ltr) + text.flat:dir(ltr) undershoot.left:dir(ltr) undershoot.right:dir(ltr) button.image-button.down:dir(ltr) From 996a4246fb974571523a92c44d4382efb808109c Mon Sep 17 00:00:00 2001 From: Matthias Clasen Date: Sun, 17 Feb 2019 15:50:59 -0500 Subject: [PATCH 27/34] defaultvalue test: Exempt GtkText::buffer It gets created on-demand, so isn't NULL. --- testsuite/gtk/defaultvalue.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/testsuite/gtk/defaultvalue.c b/testsuite/gtk/defaultvalue.c index 9ef83c1aaf..603947d60c 100644 --- a/testsuite/gtk/defaultvalue.c +++ b/testsuite/gtk/defaultvalue.c @@ -228,12 +228,19 @@ G_GNUC_BEGIN_IGNORE_DEPRECATIONS G_GNUC_END_IGNORE_DEPRECATIONS - /* Default invisible char is determined at runtime */ + /* Default invisible char is determined at runtime, + * and buffer gets created on-demand + */ if (g_type_is_a (type, GTK_TYPE_ENTRY) && (strcmp (pspec->name, "invisible-char") == 0 || strcmp (pspec->name, "buffer") == 0)) continue; + if (g_type_is_a (type, GTK_TYPE_TEXT) && + (strcmp (pspec->name, "invisible-char") == 0 || + strcmp (pspec->name, "buffer") == 0)) + continue; + G_GNUC_BEGIN_IGNORE_DEPRECATIONS if (g_type_is_a (type, GTK_TYPE_ENTRY_COMPLETION) && From 102c0ce6966122019484aa2c7fd4c5fa12bab6d5 Mon Sep 17 00:00:00 2001 From: Matthias Clasen Date: Sun, 17 Feb 2019 15:51:31 -0500 Subject: [PATCH 28/34] focus test: Use a GtkText instead Thats where the focus is nowadays. --- testsuite/gtk/focus.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/testsuite/gtk/focus.c b/testsuite/gtk/focus.c index 2884d137b3..0605c8eb53 100644 --- a/testsuite/gtk/focus.c +++ b/testsuite/gtk/focus.c @@ -12,10 +12,10 @@ test_window_focus (void) box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0); gtk_container_add (GTK_CONTAINER (window), box); gtk_container_add (GTK_CONTAINER (box), gtk_label_new ("label1")); - entry1 = gtk_entry_new (); + entry1 = gtk_text_new (); gtk_container_add (GTK_CONTAINER (box), entry1); gtk_container_add (GTK_CONTAINER (box), gtk_label_new ("label2")); - entry2 = gtk_entry_new (); + entry2 = gtk_text_new (); gtk_container_add (GTK_CONTAINER (box), entry2); g_assert_null (gtk_window_get_focus (GTK_WINDOW (window))); From 3422edff6cb88469801d6568ce25fb4d1f338a57 Mon Sep 17 00:00:00 2001 From: Matthias Clasen Date: Sun, 17 Feb 2019 15:52:10 -0500 Subject: [PATCH 29/34] a11y tests: Update expected output GtkText makes an appearance in many places now. --- testsuite/a11y/about.txt | 11 +++++------ testsuite/a11y/combos.txt | 4 ++-- testsuite/a11y/entries.txt | 6 +++--- testsuite/a11y/mnemonic.txt | 2 +- testsuite/a11y/placeholder-text.txt | 3 --- 5 files changed, 11 insertions(+), 15 deletions(-) diff --git a/testsuite/a11y/about.txt b/testsuite/a11y/about.txt index fdb3ab1e2c..165234e97a 100644 --- a/testsuite/a11y/about.txt +++ b/testsuite/a11y/about.txt @@ -60,8 +60,7 @@ window1 text: FancyPants character count: 10 - caret offset: 10 - selection 0: (0, 10) FancyPants + caret offset: 0 default attributes: bg-color: bg-full-height: 0 direction: @@ -365,7 +364,7 @@ See the GNU General Public License, version 3 or later for details. "filler" parent: headerbar1 index: 0 - state: enabled horizontal sensitive + state: enabled horizontal sensitive showing visible toolkit: gtk layer: widget @@ -375,7 +374,7 @@ See the GNU General Public License, version 3 or later for details. parent: stack_switcher index: 0 name: About - state: checked enabled focusable sensitive visible + state: checked enabled focusable sensitive showing visible toolkit: gtk layer: widget @@ -391,7 +390,7 @@ See the GNU General Public License, version 3 or later for details. parent: stack_switcher index: 1 name: Credits - state: enabled focusable sensitive + state: enabled focusable sensitive showing visible toolkit: gtk layer: widget @@ -423,7 +422,7 @@ See the GNU General Public License, version 3 or later for details. parent: stack_switcher index: 3 name: System - state: enabled focusable sensitive + state: enabled focusable sensitive showing visible toolkit: gtk layer: widget diff --git a/testsuite/a11y/combos.txt b/testsuite/a11y/combos.txt index 635ed47318..490ecd390b 100644 --- a/testsuite/a11y/combos.txt +++ b/testsuite/a11y/combos.txt @@ -109,7 +109,7 @@ window1 "text" parent: combo2 index: 1 - state: editable enabled focusable sensitive showing single-line visible + state: editable enabled sensitive showing single-line visible toolkit: gtk layer: widget @@ -196,7 +196,7 @@ window1 "text" parent: combo4 index: 1 - state: editable enabled focusable sensitive showing single-line visible + state: editable enabled sensitive showing single-line visible toolkit: gtk layer: widget diff --git a/testsuite/a11y/entries.txt b/testsuite/a11y/entries.txt index efbe14e90e..6b42082e6f 100644 --- a/testsuite/a11y/entries.txt +++ b/testsuite/a11y/entries.txt @@ -144,7 +144,7 @@ window1 parent: box1 index: 3 labelled-by: label1 - state: editable enabled focusable sensitive showing single-line visible + state: editable enabled sensitive showing single-line visible toolkit: gtk layer: widget @@ -188,7 +188,7 @@ window1 parent: box1 index: 4 labelled-by: label2 - state: editable enabled focusable sensitive showing single-line visible + state: editable enabled sensitive showing single-line visible toolkit: gtk layer: widget @@ -230,7 +230,7 @@ window1 "text" parent: box1 index: 5 - state: editable enabled focusable sensitive showing single-line visible + state: editable enabled sensitive showing single-line visible toolkit: gtk layer: widget diff --git a/testsuite/a11y/mnemonic.txt b/testsuite/a11y/mnemonic.txt index 0243d6d37b..362afca174 100644 --- a/testsuite/a11y/mnemonic.txt +++ b/testsuite/a11y/mnemonic.txt @@ -62,7 +62,7 @@ window1 parent: box1 index: 1 labelled-by: label1 - state: editable enabled focusable sensitive showing single-line visible + state: editable enabled sensitive showing single-line visible toolkit: gtk layer: widget diff --git a/testsuite/a11y/placeholder-text.txt b/testsuite/a11y/placeholder-text.txt index 8079c61f03..04ee21051e 100644 --- a/testsuite/a11y/placeholder-text.txt +++ b/testsuite/a11y/placeholder-text.txt @@ -22,7 +22,6 @@ window1 index: 0 state: editable enabled focusable sensitive showing single-line visible toolkit: gtk - placeholder-text: Subject or Addresses contain layer: widget alpha: 1 @@ -64,7 +63,6 @@ window1 index: 1 state: editable enabled focusable sensitive showing single-line visible toolkit: gtk - placeholder-text: Message contains layer: widget alpha: 1 @@ -106,7 +104,6 @@ window1 index: 2 state: editable enabled focusable sensitive showing single-line visible toolkit: gtk - placeholder-text: Body contains layer: widget alpha: 1 From ac445229d01cece0fa7b4c518b5c2cbcc83f3a05 Mon Sep 17 00:00:00 2001 From: Matthias Clasen Date: Sun, 17 Feb 2019 15:52:33 -0500 Subject: [PATCH 30/34] a11y text test: Use a GtkText instead --- testsuite/a11y/text.c | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/testsuite/a11y/text.c b/testsuite/a11y/text.c index 04db8f1ffd..7a2a31fd08 100644 --- a/testsuite/a11y/text.c +++ b/testsuite/a11y/text.c @@ -27,8 +27,8 @@ set_text (GtkWidget *widget, { if (GTK_IS_LABEL (widget)) gtk_label_set_text (GTK_LABEL (widget), text); - else if (GTK_IS_ENTRY (widget)) - gtk_entry_set_text (GTK_ENTRY (widget), text); + else if (GTK_IS_EDITABLE (widget)) + gtk_editable_set_text (GTK_EDITABLE (widget), text); else if (GTK_IS_TEXT_VIEW (widget)) gtk_text_buffer_set_text (gtk_text_view_get_buffer (GTK_TEXT_VIEW (widget)), text, -1); else @@ -47,12 +47,12 @@ append_text (GtkWidget *widget, gtk_label_set_text (GTK_LABEL (widget), tmp); g_free (tmp); } - else if (GTK_IS_ENTRY (widget)) + else if (GTK_IS_EDITABLE (widget)) { gchar *tmp; - tmp = g_strconcat (gtk_entry_get_text (GTK_ENTRY (widget)), text, NULL); - gtk_entry_set_text (GTK_ENTRY (widget), tmp); + tmp = g_strconcat (gtk_editable_get_text (GTK_EDITABLE (widget)), text, NULL); + gtk_editable_set_text (GTK_EDITABLE (widget), tmp); g_free (tmp); } else if (GTK_IS_TEXT_VIEW (widget)) @@ -134,7 +134,7 @@ test_text_changed (GtkWidget *widget) SignalData insert_data; gboolean cant_append = FALSE; - if (GTK_IS_LABEL (widget) || GTK_IS_ENTRY (widget)) + if (GTK_IS_LABEL (widget) || GTK_IS_TEXT (widget)) cant_append = TRUE; atk_text = ATK_TEXT (gtk_widget_get_accessible (widget)); @@ -874,9 +874,9 @@ test_words (GtkWidget *widget) for (i = 0; expected[i].offset != -1; i++) { - if (GTK_IS_ENTRY (widget)) + if (GTK_IS_TEXT (widget)) { - /* GtkEntry sets single-paragraph mode on its pango layout */ + /* GtkText sets single-paragraph mode on its pango layout */ if (expected[i].boundary == ATK_TEXT_BOUNDARY_LINE_START || expected[i].boundary == ATK_TEXT_BOUNDARY_LINE_END) continue; @@ -1094,7 +1094,7 @@ main (int argc, char *argv[]) g_test_add_func ("/text/bold/GtkLabel", test_bold_label); add_text_tests (gtk_label_new ("")); - add_text_tests (gtk_entry_new ()); + add_text_tests (gtk_text_new ()); add_text_tests (gtk_text_view_new ()); return g_test_run (); From 22ea832eb3574be340a055d2da8edef64dab81e4 Mon Sep 17 00:00:00 2001 From: Matthias Clasen Date: Sun, 17 Feb 2019 18:18:20 -0500 Subject: [PATCH 31/34] Fix testtreesort warnings --- tests/testtreesort.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/testtreesort.c b/tests/testtreesort.c index 35b6de9332..14a1b25e1e 100644 --- a/tests/testtreesort.c +++ b/tests/testtreesort.c @@ -87,7 +87,7 @@ switch_search_method (GtkWidget *button, if (!gtk_tree_view_get_search_entry (GTK_TREE_VIEW (tree_view))) { gpointer data = g_object_get_data (tree_view, "my-search-entry"); - gtk_tree_view_set_search_entry (GTK_TREE_VIEW (tree_view), GTK_ENTRY (data)); + gtk_tree_view_set_search_entry (GTK_TREE_VIEW (tree_view), GTK_EDITABLE (data)); } else gtk_tree_view_set_search_entry (GTK_TREE_VIEW (tree_view), NULL); @@ -145,7 +145,7 @@ main (int argc, char *argv[]) */ tree_view = gtk_tree_view_new_with_model (GTK_TREE_MODEL (model)); - gtk_tree_view_set_search_entry (GTK_TREE_VIEW (tree_view), GTK_ENTRY (entry)); + gtk_tree_view_set_search_entry (GTK_TREE_VIEW (tree_view), GTK_EDITABLE (entry)); g_object_set_data (G_OBJECT (tree_view), "my-search-entry", entry); g_signal_connect (button, "clicked", G_CALLBACK (switch_search_method), tree_view); From b299ac8dfa97c9e57961ac107dbc2e63194a33b7 Mon Sep 17 00:00:00 2001 From: Matthias Clasen Date: Mon, 18 Feb 2019 23:42:06 -0500 Subject: [PATCH 32/34] Add editable delegate api to docs And add a section that explains how to use these functions when setting up a GtkEditable delegate. --- docs/reference/gtk/gtk4-sections.txt | 6 +++ gtk/gtkeditable.c | 81 ++++++++++++++++++++++++++++ 2 files changed, 87 insertions(+) diff --git a/docs/reference/gtk/gtk4-sections.txt b/docs/reference/gtk/gtk4-sections.txt index d4925bd3d3..9ddc0c6c96 100644 --- a/docs/reference/gtk/gtk4-sections.txt +++ b/docs/reference/gtk/gtk4-sections.txt @@ -862,6 +862,12 @@ gtk_editable_get_width_chars gtk_editable_set_width_chars gtk_editable_get_max_width_chars gtk_editable_set_max_width_chars + +gtk_editable_install_properties +gtk_editable_init_delegate +gtk_editable_finish_delegate +gtk_editable_delegate_set_property +gtk_editable_delegate_get_property GTK_EDITABLE GTK_IS_EDITABLE diff --git a/gtk/gtkeditable.c b/gtk/gtkeditable.c index 3ebc45d159..d392846523 100644 --- a/gtk/gtkeditable.c +++ b/gtk/gtkeditable.c @@ -62,6 +62,87 @@ * g_free (result); * } * ]| + * + * ## Implementing GtkEditable + * + * The most likely scenario for implementing GtkEditable on your own widget + * is that you will embed a #GtkText inside a complex widget, and want to + * delegate the editable functionality to that text widget. GtkEditable + * provides some utility functions to make this easy. + * + * In your class_init function, call gtk_editable_install_properties(), + * passing the first available property ID: + * + * |[ + * static void + * my_class_init (MyClass *class) + * { + * ... + * g_object_class_install_properties (object_class, NUM_PROPERTIES, props); + * gtk_editable_install_properties (object_clas, NUM_PROPERTIES); + * ... + * } + * ]| + * + * In your interface_init function for the GtkEditable interface, provide + * an implementation for the get_delegate vfunc that returns your text widget: + * + * |[ + * GtkEditable * + * get_editable_delegate (GtkEditable *editable) + * { + * return GTK_EDITABLE (MY_WIDGET (editable)->text_widget); + * } + * + * static void + * my_editable_init (GtkEditableInterface *iface) + * { + * iface->get_delegate = get_editable_delegate; + * } + * ]| + * + * You don't need to provide any other vfuncs. The default implementations + * work by forwarding to the delegate that the get_delegate() vfunc returns. + * + * In your instance_init function, create your text widget, and then call + * gtk_editable_init_delegate(): + * + * |[ + * static void + * my_widget_init (MyWidget *self) + * { + * ... + * self->text_widget = gtk_text_new (); + * gtk_editable_init_delegate (GTK_EDITABLE (self)); + * ... + * } + * ]| + * + * In your dispose function, call gtk_editable_finish_delegate() before + * destroying your text widget: + * + * |[ + * static void + * my_widget_dispose (GObject *object) + * { + * ... + * gtk_editable_finish_delegate (GTK_EDITABLE (self)); + * g_clear_pointer (&self->text_widget, gtk_widget_unparent); + * ... + * } + * ]| + * + * Finally, use gtk_editable_delegate_set_property() in your set_property + * function (and similar for get_property), to set the editable properties: + * + * |[ + * ... + * if (gtk_editable_delegate_set_property (object, prop_id, value, pspec)) + * return; + * + * switch (prop_id) + * ... + * ]| */ #include "config.h" From a417956054d9eaca8ebe99b33cd11cd49c1c7644 Mon Sep 17 00:00:00 2001 From: Matthias Clasen Date: Tue, 19 Feb 2019 00:10:24 -0500 Subject: [PATCH 33/34] Mention GtkEditable and entries in the migration guide Not a very exhaustive treatment, but at least it gives some hints. --- docs/reference/gtk/migrating-3to4.xml | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/docs/reference/gtk/migrating-3to4.xml b/docs/reference/gtk/migrating-3to4.xml index 53e49c77b2..af5a689a6d 100644 --- a/docs/reference/gtk/migrating-3to4.xml +++ b/docs/reference/gtk/migrating-3to4.xml @@ -601,6 +601,23 @@ to pages. You can easily do that yourself. + +
+ Adapt to changes in the API of GtkEntry and GtkSearchEntry + + The GtkEditable has been made more useful, and the core functionality of + GtkEntry has been broken out as a GtkText widget. GtkEntry, GtkSearchEntry, + GtkSpinButton and the new GtkPasswordEntry now use a GtkText widget internally + and implement GtkEditable. In particular, this means that it is no longer + possible to use GtkEntry API such as gtk_entry_grab_focus_without_selecting() + on a search entry. + + + Use GtkEditable API for editable functionality, and widget-specific APIs for + things that go beyond the common interface. For password entries, use + GtkPasswordEntry. + +
From 0ef6c3afedf4b0eb100077169ff281cd0fd313d7 Mon Sep 17 00:00:00 2001 From: Matthias Clasen Date: Tue, 19 Feb 2019 00:32:35 -0500 Subject: [PATCH 34/34] search entry: Add a tooltip to th clear icon Similar to what we do for the caps-lock warning in the password entry. Closes #1483 --- gtk/gtksearchentry.c | 1 + 1 file changed, 1 insertion(+) diff --git a/gtk/gtksearchentry.c b/gtk/gtksearchentry.c index b4c47f4c71..a73e32963d 100644 --- a/gtk/gtksearchentry.c +++ b/gtk/gtksearchentry.c @@ -505,6 +505,7 @@ gtk_search_entry_init (GtkSearchEntry *entry) g_signal_connect (priv->entry, "activate", G_CALLBACK (activate_cb), entry); priv->icon = gtk_image_new_from_icon_name ("edit-clear-symbolic"); + gtk_widget_set_tooltip_text (priv->icon, _("Clear entry")); gtk_container_add (GTK_CONTAINER (priv->box), GTK_WIDGET (priv->icon)); gtk_widget_hide (priv->icon);