X-Git-Url: http://pileus.org/git/?a=blobdiff_plain;f=gtk%2Fgtkentry.c;h=e3553cc6ca3b085646be8c912a4b6a29dc33a80e;hb=a731cd1ef62b8e460373e660f6f77fce6d4d9ca4;hp=3d76e8c7288d179f7cce44ef535d67611cb27769;hpb=754eabeb19aef2561199647e9e044fa6edeb985c;p=~andy%2Fgtk diff --git a/gtk/gtkentry.c b/gtk/gtkentry.c index 3d76e8c72..e3553cc6c 100644 --- a/gtk/gtkentry.c +++ b/gtk/gtkentry.c @@ -16,9 +16,7 @@ * 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, write to the - * Free Software Foundation, Inc., 59 Temple Place - Suite 330, - * Boston, MA 02111-1307, USA. + * License along with this library. If not, see . */ /* @@ -67,6 +65,9 @@ #include "gtkicontheme.h" #include "gtkwidgetprivate.h" #include "gtkstylecontextprivate.h" +#include "gtktexthandleprivate.h" +#include "gtkbubblewindowprivate.h" +#include "gtktoolbar.h" #include "a11y/gtkentryaccessible.h" @@ -118,9 +119,7 @@ #define MIN_ENTRY_WIDTH 150 #define DRAW_TIMEOUT 20 -#define COMPLETION_TIMEOUT 300 #define PASSWORD_HINT_MAX 8 -#define PAGE_STEP 14 #define MAX_ICONS 2 @@ -144,13 +143,14 @@ struct _GtkEntryPrivate GtkEntryBuffer *buffer; GtkIMContext *im_context; - GtkShadowType shadow_type; GtkWidget *popup_menu; - GdkDevice *completion_device; + GdkDevice *device; + GdkWindow *text_area; PangoLayout *cached_layout; + PangoAttrList *attrs; gchar *im_module; @@ -160,6 +160,11 @@ struct _GtkEntryPrivate gchar *placeholder_text; + GtkBubbleWindow *bubble_window; + GtkTextHandle *text_handle; + GtkWidget *selection_bubble; + guint selection_bubble_timeout_id; + gfloat xalign; gint ascent; /* font ascent in pango units */ @@ -169,7 +174,6 @@ struct _GtkEntryPrivate gint drag_start_x; gint drag_start_y; gint focus_width; - gint icon_margin; gint insert_pos; gint selection_bound; gint scroll_offset; @@ -190,6 +194,7 @@ struct _GtkEntryPrivate guint16 preedit_length; /* length of preedit string, in bytes */ guint16 preedit_cursor; /* offset of cursor within preedit string, in chars */ + guint shadow_type : 4; guint editable : 1; guint in_drag : 1; guint overwrite_mode : 1; @@ -216,6 +221,9 @@ struct _GtkEntryPrivate 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 @@ -309,10 +317,15 @@ enum { PROP_IM_MODULE, PROP_EDITING_CANCELED, PROP_PLACEHOLDER_TEXT, - PROP_COMPLETION + PROP_COMPLETION, + PROP_INPUT_PURPOSE, + PROP_INPUT_HINTS, + PROP_ATTRIBUTES, + PROP_POPULATE_ALL }; static guint signals[LAST_SIGNAL] = { 0 }; +static gboolean test_touchscreen = FALSE; typedef enum { CURSOR_STANDARD, @@ -548,6 +561,8 @@ static void gtk_entry_do_popup (GtkEntry *entry, GdkEventButton *event); static gboolean gtk_entry_mnemonic_activate (GtkWidget *widget, gboolean group_cycling); +static void gtk_entry_grab_notify (GtkWidget *widget, + gboolean was_grabbed); 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); @@ -556,6 +571,11 @@ static void gtk_entry_get_text_area_size (GtkEntry *entry, gint *y, gint *width, gint *height); +static void gtk_entry_get_frame_size (GtkEntry *entry, + gint *x, + gint *y, + gint *width, + gint *height); static void get_text_area_size (GtkEntry *entry, gint *x, gint *y, @@ -571,30 +591,20 @@ static void gtk_entry_move_adjustments (GtkEntry *en static GdkPixbuf * gtk_entry_ensure_pixbuf (GtkEntry *entry, GtkEntryIconPosition icon_pos); static void gtk_entry_update_cached_style_values(GtkEntry *entry); +static gboolean get_middle_click_paste (GtkEntry *entry); -/* Completion */ -static gint gtk_entry_completion_timeout (gpointer data); -static gboolean gtk_entry_completion_key_press (GtkWidget *widget, - GdkEventKey *event, - gpointer user_data); -static void gtk_entry_completion_changed (GtkWidget *entry, - gpointer user_data); -static gboolean check_completion_callback (GtkEntryCompletion *completion); -static void clear_completion_callback (GtkEntry *entry, - GParamSpec *pspec); -static gboolean accept_completion_callback (GtkEntry *entry); -static void completion_insert_text_callback (GtkEntry *entry, - const gchar *text, - gint length, - gint position, - GtkEntryCompletion *completion); -static void completion_changed (GtkEntryCompletion *completion, - GParamSpec *pspec, - gpointer data); -static void disconnect_completion_signals (GtkEntry *entry, - GtkEntryCompletion *completion); -static void connect_completion_signals (GtkEntry *entry, - GtkEntryCompletion *completion); +/* GtkTextHandle handlers */ +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); @@ -692,6 +702,7 @@ gtk_entry_class_init (GtkEntryClass *class) widget_class->state_flags_changed = gtk_entry_state_flags_changed; widget_class->screen_changed = gtk_entry_screen_changed; widget_class->mnemonic_activate = gtk_entry_mnemonic_activate; + widget_class->grab_notify = gtk_entry_grab_notify; widget_class->drag_drop = gtk_entry_drag_drop; widget_class->drag_motion = gtk_entry_drag_motion; @@ -712,6 +723,7 @@ gtk_entry_class_init (GtkEntryClass *class) class->toggle_overwrite = gtk_entry_toggle_overwrite; class->activate = gtk_entry_real_activate; class->get_text_area_size = gtk_entry_get_text_area_size; + class->get_frame_size = gtk_entry_get_frame_size; quark_inner_border = g_quark_from_static_string ("gtk-entry-inner-border"); quark_password_hint = g_quark_from_static_string ("gtk-entry-password-hint"); @@ -783,13 +795,23 @@ gtk_entry_class_init (GtkEntryClass *class) TRUE, GTK_PARAM_READWRITE)); + /** + * GtkEntry:inner-border: + * + * Sets the text area's border between the text and the frame. + * + * Deprecated: 3.4: Use the standard border and padding CSS properties + * (through objects like #GtkStyleContext and #GtkCssProvider); the value + * of this style property is ignored. + */ g_object_class_install_property (gobject_class, PROP_INNER_BORDER, g_param_spec_boxed ("inner-border", P_("Inner Border"), P_("Border between text and frame. Overrides the inner-border style property"), GTK_TYPE_BORDER, - GTK_PARAM_READWRITE)); + GTK_PARAM_READWRITE | + G_PARAM_DEPRECATED)); g_object_class_install_property (gobject_class, PROP_INVISIBLE_CHAR, @@ -931,7 +953,7 @@ gtk_entry_class_init (GtkEntryClass *class) GTK_PARAM_READWRITE)); /** - * GtkEntry:caps-lock-warning + * GtkEntry:caps-lock-warning: * * Whether password entries will show a warning when Caps Lock is on. * @@ -1346,6 +1368,78 @@ gtk_entry_class_init (GtkEntryClass *class) GTK_TYPE_ENTRY_COMPLETION, GTK_PARAM_READWRITE)); + /** + * GtkEntry: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 + * #GtkEntry:visibility. + * + * Since: 3.6 + */ + g_object_class_install_property (gobject_class, + 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, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + /** + * GtkEntry:input-hints: + * + * Additional hints (beyond #GtkEntry:input-purpose) that + * allow input methods to fine-tune their behaviour. + * + * Since: 3.6 + */ + g_object_class_install_property (gobject_class, + 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, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + /** + * GtkEntry:attributes: + * + * A list of Pango attributes to apply to the text of the entry. + * + * This is mainly useful to change the size or weight of the text. + * + * Since: 3.6 + */ + g_object_class_install_property (gobject_class, + PROP_ATTRIBUTES, + g_param_spec_boxed ("attributes", + P_("Attributes"), + P_("A list of style attributes to apply to the text of the label"), + PANGO_TYPE_ATTR_LIST, + GTK_PARAM_READWRITE)); + + /** GtkEntry:populate-all: + * + * If ::populate-all is %TRUE, the #GtkEntry::populate-popup + * signal is also emitted for touch popups. + * + * Since: 3.8 + */ + g_object_class_install_property (gobject_class, + 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)); + /** * GtkEntry:icon-prelight: * @@ -1367,20 +1461,25 @@ gtk_entry_class_init (GtkEntryClass *class) * The border around the progress bar in the entry. * * Since: 2.16 + * + * Deprecated: 3.4: Use the standard margin CSS property (through objects + * like #GtkStyleContext and #GtkCssProvider); the value of this style + * property is ignored. */ gtk_widget_class_install_style_property (widget_class, g_param_spec_boxed ("progress-border", P_("Progress Border"), P_("Border around the progress bar"), GTK_TYPE_BORDER, - GTK_PARAM_READABLE)); + GTK_PARAM_READABLE | + G_PARAM_DEPRECATED)); /** * GtkEntry:invisible-char: * * The invisible character is used when masking entry contents (in * \"password mode\")"). When it is not explicitly set with the - * #GtkEntry::invisible-char property, GTK+ determines the character + * #GtkEntry:invisible-char property, GTK+ determines the character * to use from a list of possible candidates, depending on availability * in the current font. * @@ -1399,13 +1498,20 @@ gtk_entry_class_init (GtkEntryClass *class) /** * GtkEntry::populate-popup: * @entry: The entry on which the signal is emitted - * @menu: the menu that is being populated + * @popup: the container that is being populated * - * The ::populate-popup signal gets emitted before showing the - * context menu of the entry. + * 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 menuitems to the @menu. + * 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"), @@ -1415,7 +1521,7 @@ gtk_entry_class_init (GtkEntryClass *class) NULL, NULL, _gtk_marshal_VOID__OBJECT, G_TYPE_NONE, 1, - GTK_TYPE_MENU); + GTK_TYPE_WIDGET); /* Action signals */ @@ -1423,12 +1529,13 @@ gtk_entry_class_init (GtkEntryClass *class) * GtkEntry::activate: * @entry: The entry on which the signal is emitted * - * A keybinding signal - * which gets emitted when the user activates the entry. - * - * Applications should not connect to it, but may emit it with - * g_signal_emit_by_name() if they need to control activation - * programmatically. + * The ::activate signal is emitted when the user hits + * the Enter key. + * + * While this signal is used as a + * keybinding signal, + * it is also commonly used by applications to intercept + * activation of entries. * * The default bindings for this signal are all forms of the Enter key. */ @@ -1859,15 +1966,21 @@ gtk_entry_class_init (GtkEntryClass *class) * Sets the text area's border between the text and the frame. * * Since: 2.10 + * + * Deprecated: 3.4: Use the standard border and padding CSS properties + * (through objects like #GtkStyleContext and #GtkCssProvider); the value + * of this style property is ignored. */ gtk_widget_class_install_style_property (widget_class, g_param_spec_boxed ("inner-border", P_("Inner Border"), P_("Border between text and frame."), GTK_TYPE_BORDER, - GTK_PARAM_READABLE)); + GTK_PARAM_READABLE | + G_PARAM_DEPRECATED)); g_type_class_add_private (gobject_class, sizeof (GtkEntryPrivate)); + test_touchscreen = g_getenv ("GTK_TEST_TOUCHSCREEN") != NULL; gtk_widget_class_set_accessible_type (widget_class, GTK_TYPE_ENTRY_ACCESSIBLE); } @@ -1892,6 +2005,27 @@ gtk_entry_cell_editable_init (GtkCellEditableIface *iface) iface->start_editing = gtk_entry_start_editing; } +/* for deprecated properties */ +static void +gtk_entry_do_set_inner_border (GtkEntry *entry, + const GtkBorder *border) +{ + if (border) + g_object_set_qdata_full (G_OBJECT (entry), quark_inner_border, + gtk_border_copy (border), + (GDestroyNotify) gtk_border_free); + else + g_object_set_qdata (G_OBJECT (entry), quark_inner_border, NULL); + + g_object_notify (G_OBJECT (entry), "inner-border"); +} + +static const GtkBorder * +gtk_entry_do_get_inner_border (GtkEntry *entry) +{ + return g_object_get_qdata (G_OBJECT (entry), quark_inner_border); +} + static void gtk_entry_set_property (GObject *object, guint prop_id, @@ -1917,7 +2051,7 @@ gtk_entry_set_property (GObject *object, if (!new_value) { - _gtk_entry_reset_im_context (entry); + gtk_entry_reset_im_context (entry); if (gtk_widget_has_focus (widget)) gtk_im_context_focus_out (priv->im_context); @@ -1948,7 +2082,7 @@ gtk_entry_set_property (GObject *object, break; case PROP_INNER_BORDER: - gtk_entry_set_inner_border (entry, g_value_get_boxed (value)); + gtk_entry_do_set_inner_border (entry, g_value_get_boxed (value)); break; case PROP_INVISIBLE_CHAR: @@ -2117,6 +2251,22 @@ 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: + entry->priv->populate_all = g_value_get_boolean (value); + break; + case PROP_SCROLL_OFFSET: case PROP_CURSOR_POSITION: default: @@ -2165,7 +2315,7 @@ gtk_entry_get_property (GObject *object, break; case PROP_INNER_BORDER: - g_value_set_boxed (value, gtk_entry_get_inner_border (entry)); + g_value_set_boxed (value, gtk_entry_do_get_inner_border (entry)); break; case PROP_INVISIBLE_CHAR: @@ -2341,6 +2491,22 @@ 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; + default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -2448,6 +2614,12 @@ gtk_entry_init (GtkEntry *entry) gtk_style_context_add_class (context, GTK_STYLE_CLASS_ENTRY); gtk_entry_update_cached_style_values (entry); + + priv->text_handle = _gtk_text_handle_new (GTK_WIDGET (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 void @@ -2474,6 +2646,21 @@ gtk_entry_prepare_context_for_icon (GtkEntry *entry, gtk_style_context_set_state (context, state); gtk_style_context_add_class (context, GTK_STYLE_CLASS_IMAGE); + + if (gtk_widget_get_direction (GTK_WIDGET (entry)) == GTK_TEXT_DIR_RTL) + { + if (icon_pos == GTK_ENTRY_ICON_PRIMARY) + gtk_style_context_add_class (context, GTK_STYLE_CLASS_RIGHT); + else + gtk_style_context_add_class (context, GTK_STYLE_CLASS_LEFT); + } + else + { + if (icon_pos == GTK_ENTRY_ICON_PRIMARY) + gtk_style_context_add_class (context, GTK_STYLE_CLASS_LEFT); + else + gtk_style_context_add_class (context, GTK_STYLE_CLASS_RIGHT); + } } static gint @@ -2483,6 +2670,7 @@ get_icon_width (GtkEntry *entry, GtkEntryPrivate *priv = entry->priv; EntryIconInfo *icon_info = priv->icons[icon_pos]; GtkStyleContext *context; + GtkBorder padding; gint width; if (!icon_info) @@ -2490,10 +2678,15 @@ get_icon_width (GtkEntry *entry, context = gtk_widget_get_style_context (GTK_WIDGET (entry)); gtk_entry_prepare_context_for_icon (entry, context, icon_pos); + gtk_style_context_get_padding (context, 0, &padding); + _gtk_icon_helper_get_size (icon_info->icon_helper, context, &width, NULL); gtk_style_context_restore (context); + if (width > 0) + width += padding.left + padding.right; + return width; } @@ -2514,14 +2707,10 @@ get_icon_allocations (GtkEntry *entry, primary->y = y; primary->height = height; primary->width = get_icon_width (entry, GTK_ENTRY_ICON_PRIMARY); - if (primary->width > 0) - primary->width += 2 * priv->icon_margin; secondary->y = y; secondary->height = height; secondary->width = get_icon_width (entry, GTK_ENTRY_ICON_SECONDARY); - if (secondary->width > 0) - secondary->width += 2 * priv->icon_margin; if (gtk_widget_get_direction (GTK_WIDGET (entry)) == GTK_TEXT_DIR_RTL) { @@ -2587,7 +2776,7 @@ gtk_entry_destroy (GtkWidget *widget) GtkEntryPrivate *priv = entry->priv; priv->current_pos = priv->selection_bound = 0; - _gtk_entry_reset_im_context (entry); + gtk_entry_reset_im_context (entry); gtk_entry_reset_layout (entry); if (priv->blink_timeout) @@ -2616,6 +2805,7 @@ gtk_entry_dispose (GObject *object) gtk_entry_set_icon_tooltip_markup (entry, GTK_ENTRY_ICON_PRIMARY, NULL); gtk_entry_set_icon_from_pixbuf (entry, GTK_ENTRY_ICON_SECONDARY, NULL); gtk_entry_set_icon_tooltip_markup (entry, GTK_ENTRY_ICON_SECONDARY, NULL); + gtk_entry_set_completion (entry, NULL); if (priv->buffer) { @@ -2655,8 +2845,6 @@ gtk_entry_finalize (GObject *object) } } - gtk_entry_set_completion (entry, NULL); - if (priv->cached_layout) g_object_unref (priv->cached_layout); @@ -2668,6 +2856,10 @@ gtk_entry_finalize (GObject *object) if (priv->recompute_idle) g_source_remove (priv->recompute_idle); + if (priv->selection_bubble) + gtk_widget_destroy (priv->selection_bubble); + + g_object_unref (priv->text_handle); g_free (priv->placeholder_text); g_free (priv->im_module); @@ -2688,10 +2880,10 @@ gtk_entry_get_display_mode (GtkEntry *entry) return DISPLAY_INVISIBLE; } -static gchar* -gtk_entry_get_display_text (GtkEntry *entry, - gint start_pos, - gint end_pos) +gchar* +_gtk_entry_get_display_text (GtkEntry *entry, + gint start_pos, + gint end_pos) { GtkEntryPasswordHint *password_hint; GtkEntryPrivate *priv; @@ -2709,7 +2901,7 @@ gtk_entry_get_display_text (GtkEntry *entry, text = gtk_entry_buffer_get_text (get_buffer (entry)); length = gtk_entry_buffer_get_length (get_buffer (entry)); - if (end_pos < 0) + if (end_pos < 0 || end_pos > length) end_pos = length; if (start_pos > length) start_pos = length; @@ -2829,7 +3021,7 @@ realize_icon_info (GtkWidget *widget, icon_info->window = gdk_window_new (gtk_widget_get_window (widget), &attributes, attributes_mask); - gdk_window_set_user_data (icon_info->window, widget); + gtk_widget_register_window (widget, icon_info->window); gtk_widget_queue_resize (widget); } @@ -2848,6 +3040,7 @@ construct_icon_info (GtkWidget *widget, priv->icons[icon_pos] = icon_info; icon_info->icon_helper = _gtk_icon_helper_new (); + _gtk_icon_helper_set_force_scale_pixbuf (icon_info->icon_helper, TRUE); if (gtk_widget_get_realized (widget)) realize_icon_info (widget, icon_pos); @@ -2888,6 +3081,9 @@ gtk_entry_unmap (GtkWidget *widget) EntryIconInfo *icon_info = NULL; gint i; + _gtk_text_handle_set_mode (priv->text_handle, + GTK_TEXT_HANDLE_MODE_NONE); + for (i = 0; i < MAX_ICONS; i++) { if ((icon_info = priv->icons[i]) != NULL) @@ -2952,7 +3148,7 @@ gtk_entry_realize (GtkWidget *widget) &attributes, attributes_mask); - gdk_window_set_user_data (priv->text_area, entry); + gtk_widget_register_window (widget, priv->text_area); if (attributes_mask & GDK_WA_CURSOR) g_object_unref (attributes.cursor); @@ -2961,7 +3157,7 @@ gtk_entry_realize (GtkWidget *widget) gtk_entry_adjust_scroll (entry); gtk_entry_update_primary_selection (entry); - + _gtk_text_handle_set_relative_to (priv->text_handle, priv->text_area); /* If the icon positions are already setup, create their windows. * Otherwise if they don't exist yet, then construct_icon_info() @@ -2989,6 +3185,7 @@ gtk_entry_unrealize (GtkWidget *widget) gtk_entry_reset_layout (entry); gtk_im_context_set_client_window (priv->im_context, NULL); + _gtk_text_handle_set_relative_to (priv->text_handle, NULL); clipboard = gtk_widget_get_clipboard (widget, GDK_SELECTION_PRIMARY); if (gtk_clipboard_get_owner (clipboard) == G_OBJECT (entry)) @@ -2996,7 +3193,7 @@ gtk_entry_unrealize (GtkWidget *widget) if (priv->text_area) { - gdk_window_set_user_data (priv->text_area, NULL); + gtk_widget_unregister_window (widget, priv->text_area); gdk_window_destroy (priv->text_area); priv->text_area = NULL; } @@ -3015,6 +3212,7 @@ gtk_entry_unrealize (GtkWidget *widget) { if (icon_info->window != NULL) { + gtk_widget_unregister_window (widget, icon_info->window); gdk_window_destroy (icon_info->window); icon_info->window = NULL; } @@ -3024,34 +3222,37 @@ gtk_entry_unrealize (GtkWidget *widget) void _gtk_entry_get_borders (GtkEntry *entry, - gint *xborder, - gint *yborder) + GtkBorder *border_out) { GtkEntryPrivate *priv = entry->priv; GtkWidget *widget = GTK_WIDGET (entry); + GtkBorder tmp = { 0, 0, 0, 0 }; + GtkStyleContext *context; + + context = gtk_widget_get_style_context (widget); + gtk_style_context_get_padding (context, 0, &tmp); if (priv->has_frame) { - GtkStyleContext *context; - GtkBorder padding; - - context = gtk_widget_get_style_context (widget); - gtk_style_context_get_padding (context, 0, &padding); + GtkBorder border; - *xborder = padding.left; - *yborder = padding.top; - } - else - { - *xborder = 0; - *yborder = 0; + gtk_style_context_get_border (context, 0, &border); + tmp.top += border.top; + tmp.right += border.right; + tmp.bottom += border.bottom; + tmp.left += border.left; } if (!priv->interior_focus) { - *xborder += priv->focus_width; - *yborder += priv->focus_width; + tmp.top += priv->focus_width; + tmp.right += priv->focus_width; + tmp.bottom += priv->focus_width; + tmp.left += priv->focus_width; } + + if (border_out != NULL) + *border_out = tmp; } static void @@ -3062,43 +3263,36 @@ gtk_entry_get_preferred_width (GtkWidget *widget, GtkEntry *entry = GTK_ENTRY (widget); GtkEntryPrivate *priv = entry->priv; PangoFontMetrics *metrics; - gint xborder, yborder; - GtkBorder inner_border; + GtkBorder borders; PangoContext *context; - GtkStyleContext *style_context; - GtkStateFlags state; gint icon_widths = 0; gint icon_width, i; gint width; context = gtk_widget_get_pango_context (widget); - style_context = gtk_widget_get_style_context (widget); - state = gtk_widget_get_state_flags (widget); - metrics = pango_context_get_metrics (context, - gtk_style_context_get_font (style_context, state), + pango_context_get_font_description (context), pango_context_get_language (context)); - _gtk_entry_get_borders (entry, &xborder, &yborder); - _gtk_entry_effective_inner_border (entry, &inner_border); + _gtk_entry_get_borders (entry, &borders); if (priv->width_chars < 0) - width = MIN_ENTRY_WIDTH + xborder * 2 + inner_border.left + inner_border.right; + width = MIN_ENTRY_WIDTH + borders.left + borders.right; else { gint char_width = pango_font_metrics_get_approximate_char_width (metrics); gint digit_width = pango_font_metrics_get_approximate_digit_width (metrics); gint char_pixels = (MAX (char_width, digit_width) + PANGO_SCALE - 1) / PANGO_SCALE; - width = char_pixels * priv->width_chars + xborder * 2 + inner_border.left + inner_border.right; + width = char_pixels * priv->width_chars + borders.left + borders.right; } for (i = 0; i < MAX_ICONS; i++) { icon_width = get_icon_width (entry, i); if (icon_width > 0) - icon_widths += icon_width + 2 * priv->icon_margin; + icon_widths += icon_width; } if (icon_widths > width) @@ -3118,31 +3312,26 @@ gtk_entry_get_preferred_height (GtkWidget *widget, GtkEntry *entry = GTK_ENTRY (widget); GtkEntryPrivate *priv = entry->priv; PangoFontMetrics *metrics; - gint xborder, yborder; - GtkBorder inner_border; - GtkStyleContext *style_context; - GtkStateFlags state; + GtkBorder borders; PangoContext *context; gint height; + PangoLayout *layout; + layout = gtk_entry_ensure_layout (entry, TRUE); context = gtk_widget_get_pango_context (widget); - style_context = gtk_widget_get_style_context (widget); - state = gtk_widget_get_state_flags (widget); - metrics = pango_context_get_metrics (context, - gtk_style_context_get_font (style_context, state), + pango_context_get_font_description (context), pango_context_get_language (context)); priv->ascent = pango_font_metrics_get_ascent (metrics); priv->descent = pango_font_metrics_get_descent (metrics); + pango_font_metrics_unref (metrics); - _gtk_entry_get_borders (entry, &xborder, &yborder); - _gtk_entry_effective_inner_border (entry, &inner_border); - - height = PANGO_PIXELS (priv->ascent + priv->descent) + yborder * 2 + inner_border.top + inner_border.bottom; + _gtk_entry_get_borders (entry, &borders); + pango_layout_get_pixel_size (layout, NULL, &height); - pango_font_metrics_unref (metrics); + height += borders.top + borders.bottom; *minimum = height; *natural = height; @@ -3205,13 +3394,13 @@ gtk_entry_get_text_area_size (GtkEntry *entry, GtkRequisition requisition; gint req_height; gint frame_height; - gint xborder, yborder; + GtkBorder borders; gtk_widget_get_preferred_size (widget, &requisition, NULL); req_height = requisition.height - gtk_widget_get_margin_top (widget) - gtk_widget_get_margin_bottom (widget); gtk_widget_get_allocation (widget, &allocation); - _gtk_entry_get_borders (entry, &xborder, &yborder); + _gtk_entry_get_borders (entry, &borders); if (gtk_widget_get_realized (widget)) get_frame_size (entry, TRUE, NULL, NULL, NULL, &frame_height); @@ -3222,16 +3411,16 @@ gtk_entry_get_text_area_size (GtkEntry *entry, frame_height -= 2 * priv->focus_width; if (x) - *x = xborder; + *x = borders.left; if (y) - *y = frame_height / 2 - (req_height - yborder * 2) / 2; + *y = floor ((frame_height - req_height) / 2) + borders.top; if (width) - *width = allocation.width - xborder * 2; + *width = allocation.width - borders.left - borders.right; if (height) - *height = req_height - yborder * 2; + *height = req_height - borders.top - borders.bottom; } static void @@ -3253,12 +3442,11 @@ get_text_area_size (GtkEntry *entry, static void -get_frame_size (GtkEntry *entry, - gboolean relative_to_window, - gint *x, - gint *y, - gint *width, - gint *height) +gtk_entry_get_frame_size (GtkEntry *entry, + gint *x, + gint *y, + gint *width, + gint *height) { GtkEntryPrivate *priv = entry->priv; GtkAllocation allocation; @@ -3273,7 +3461,7 @@ get_frame_size (GtkEntry *entry, gtk_widget_get_allocation (widget, &allocation); if (x) - *x = relative_to_window ? allocation.x : 0; + *x = allocation.x; if (y) { @@ -3282,8 +3470,7 @@ get_frame_size (GtkEntry *entry, else *y = (allocation.height - req_height) / 2; - if (relative_to_window) - *y += allocation.y; + *y += allocation.y; } if (width) @@ -3298,30 +3485,34 @@ get_frame_size (GtkEntry *entry, } } -void -_gtk_entry_effective_inner_border (GtkEntry *entry, - GtkBorder *border) +static void +get_frame_size (GtkEntry *entry, + gboolean relative_to_window, + gint *x, + gint *y, + gint *width, + gint *height) { - GtkBorder *tmp_border; + GtkEntryClass *class; + GtkWidget *widget = GTK_WIDGET (entry); - tmp_border = g_object_get_qdata (G_OBJECT (entry), quark_inner_border); + g_return_if_fail (GTK_IS_ENTRY (entry)); - if (tmp_border) - { - *border = *tmp_border; - return; - } + class = GTK_ENTRY_GET_CLASS (entry); - gtk_widget_style_get (GTK_WIDGET (entry), "inner-border", &tmp_border, NULL); + if (class->get_frame_size) + class->get_frame_size (entry, x, y, width, height); - if (tmp_border) + if (!relative_to_window) { - *border = *tmp_border; - gtk_border_free (tmp_border); - return; - } + GtkAllocation allocation; + gtk_widget_get_allocation (widget, &allocation); - *border = default_inner_border; + if (x) + *x -= allocation.x; + if (y) + *y -= allocation.y; + } } static void @@ -3340,7 +3531,7 @@ gtk_entry_size_allocate (GtkWidget *widget, gtk_entry_recompute (entry); completion = gtk_entry_get_completion (entry); - if (completion && gtk_widget_get_mapped (completion->priv->popup_window)) + if (completion) _gtk_entry_completion_resize_popup (completion); } } @@ -3379,6 +3570,7 @@ draw_icon (GtkWidget *widget, EntryIconInfo *icon_info = priv->icons[icon_pos]; gint x, y, width, height, pix_width, pix_height; GtkStyleContext *context; + GtkBorder padding; if (!icon_info) return; @@ -3396,9 +3588,11 @@ draw_icon (GtkWidget *widget, context = gtk_widget_get_style_context (widget); gtk_entry_prepare_context_for_icon (entry, context, icon_pos); - _gtk_icon_helper_get_size (icon_info->icon_helper, context, &pix_width, &pix_height); + _gtk_icon_helper_get_size (icon_info->icon_helper, context, + &pix_width, &pix_height); + gtk_style_context_get_padding (context, 0, &padding); - x = MAX (0, (width - pix_width) / 2); + x = MAX (0, padding.left); y = MAX (0, (height - pix_height) / 2); _gtk_icon_helper_draw (icon_info->icon_helper, @@ -3431,13 +3625,13 @@ gtk_entry_draw_frame (GtkWidget *widget, * http://bugzilla.gnome.org/show_bug.cgi?id=466000 */ if (GTK_IS_SPIN_BUTTON (widget)) { - gint xborder, yborder; + GtkBorder borders; gtk_entry_get_text_area_size (GTK_ENTRY (widget), &x, NULL, &width, NULL); - _gtk_entry_get_borders (GTK_ENTRY (widget), &xborder, &yborder); + _gtk_entry_get_borders (GTK_ENTRY (widget), &borders); - x -= xborder; - width += xborder * 2; + x -= borders.left; + width += borders.left + borders.right; } if (gtk_widget_has_focus (widget) && !priv->interior_focus) @@ -3471,6 +3665,18 @@ gtk_entry_draw_frame (GtkWidget *widget, cairo_restore (cr); } +static void +gtk_entry_prepare_context_for_progress (GtkEntry *entry, + GtkStyleContext *context) +{ + GtkEntryPrivate *private = entry->priv; + + gtk_style_context_save (context); + gtk_style_context_add_class (context, GTK_STYLE_CLASS_PROGRESSBAR); + if (private->progress_pulse_mode) + gtk_style_context_add_class (context, GTK_STYLE_CLASS_PULSE); +} + static void get_progress_area (GtkWidget *widget, gint *x, @@ -3480,29 +3686,51 @@ get_progress_area (GtkWidget *widget, { GtkEntry *entry = GTK_ENTRY (widget); GtkEntryPrivate *private = entry->priv; - GtkBorder *progress_border; + GtkStyleContext *context; + GtkBorder margin, border, entry_borders; + gint frame_width, text_area_width, text_area_height; + + context = gtk_widget_get_style_context (widget); + _gtk_entry_get_borders (entry, &entry_borders); + get_text_area_size (entry, + NULL, NULL, + &text_area_width, &text_area_height); + get_frame_size (entry, FALSE, + NULL, NULL, + &frame_width, NULL); - get_text_area_size (entry, x, y, width, height); + *x = 0; + *y = 0; + *width = text_area_width + entry_borders.left + entry_borders.right; + *height = text_area_height + entry_borders.top + entry_borders.bottom; - if (!private->interior_focus) + /* if the text area got resized by a subclass, subtract the left/right + * border width, so that the progress bar won't extend over the resized + * text area. + */ + if (frame_width > *width) { - *x -= private->focus_width; - *y -= private->focus_width; - *width += 2 * private->focus_width; - *height += 2 * private->focus_width; + gtk_style_context_get_border (context, 0, &border); + if (gtk_widget_get_direction (GTK_WIDGET (entry)) == GTK_TEXT_DIR_RTL) + { + *x = (frame_width - *width) + border.left; + *width -= border.left; + } + else + { + *width -= border.right; + } } - gtk_widget_style_get (widget, "progress-border", &progress_border, NULL); + gtk_entry_prepare_context_for_progress (entry, context); + gtk_style_context_get_margin (context, 0, &margin); - if (progress_border) - { - *x += progress_border->left; - *y += progress_border->top; - *width -= progress_border->left + progress_border->right; - *height -= progress_border->top + progress_border->bottom; + gtk_style_context_restore (context); - gtk_border_free (progress_border); - } + *x += margin.left; + *y += margin.top; + *width -= margin.left + margin.right; + *height -= margin.top + margin.bottom; if (private->progress_pulse_mode) { @@ -3541,19 +3769,14 @@ gtk_entry_draw_progress (GtkWidget *widget, cairo_t *cr) { GtkEntry *entry = GTK_ENTRY (widget); - GtkEntryPrivate *private = entry->priv; gint x, y, width, height; get_progress_area (widget, &x, &y, &width, &height); if ((width <= 0) || (height <= 0)) return; - - gtk_style_context_save (context); - gtk_style_context_add_class (context, GTK_STYLE_CLASS_PROGRESSBAR); - if (private->progress_pulse_mode) - gtk_style_context_add_class (context, GTK_STYLE_CLASS_PULSE); - + + gtk_entry_prepare_context_for_progress (entry, context); gtk_render_activity (context, cr, x, y, width, height); @@ -3569,36 +3792,40 @@ gtk_entry_draw (GtkWidget *widget, GtkEntryPrivate *priv = entry->priv; int i; - context = gtk_widget_get_style_context (widget); + if (gtk_cairo_should_draw_window (cr, + gtk_widget_get_window (widget))) + { + context = gtk_widget_get_style_context (widget); - /* Draw entry_bg, shadow, progress and focus */ - gtk_entry_draw_frame (widget, context, cr); + /* Draw entry_bg, shadow, progress and focus */ + gtk_entry_draw_frame (widget, context, cr); - /* Draw text and cursor */ - cairo_save (cr); + /* Draw text and cursor */ + cairo_save (cr); - gtk_cairo_transform_to_window (cr, widget, priv->text_area); + gtk_cairo_transform_to_window (cr, widget, priv->text_area); - if (priv->dnd_position != -1) - gtk_entry_draw_cursor (GTK_ENTRY (widget), cr, CURSOR_DND); - - gtk_entry_draw_text (GTK_ENTRY (widget), cr); + if (priv->dnd_position != -1) + gtk_entry_draw_cursor (GTK_ENTRY (widget), cr, CURSOR_DND); - /* 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), cr, CURSOR_STANDARD); + gtk_entry_draw_text (GTK_ENTRY (widget), cr); - cairo_restore (cr); + /* 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), cr, CURSOR_STANDARD); - /* Draw icons */ - for (i = 0; i < MAX_ICONS; i++) - { - EntryIconInfo *icon_info = priv->icons[i]; + cairo_restore (cr); - if (icon_info != NULL) - draw_icon (widget, cr, i); + /* Draw icons */ + for (i = 0; i < MAX_ICONS; i++) + { + EntryIconInfo *icon_info = priv->icons[i]; + + if (icon_info != NULL) + draw_icon (widget, cr, i); + } } return FALSE; @@ -3725,53 +3952,157 @@ in_selection (GtkEntry *entry, g_free (ranges); return retval; } - -static gint -gtk_entry_button_press (GtkWidget *widget, - GdkEventButton *event) + +static void +gtk_entry_move_handle (GtkEntry *entry, + GtkTextHandlePosition pos, + gint x, + gint y, + gint height) { - GtkEntry *entry = GTK_ENTRY (widget); - GtkEditable *editable = GTK_EDITABLE (widget); GtkEntryPrivate *priv = entry->priv; - EntryIconInfo *icon_info = NULL; - gint tmp_pos; - gint sel_start, sel_end; - gint i; - for (i = 0; i < MAX_ICONS; i++) + if (!_gtk_text_handle_get_is_dragged (priv->text_handle, pos) && + (x < 0 || x > gdk_window_get_width (priv->text_area))) { - icon_info = priv->icons[i]; - - if (!icon_info || icon_info->insensitive) - continue; + /* 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; - if (event->window == icon_info->window) - { - if (should_prelight (entry, i)) - { - icon_info->prelight = FALSE; - gtk_widget_queue_draw (widget); - } + rect.x = CLAMP (x, 0, gdk_window_get_width (priv->text_area)); + rect.y = y; + rect.width = 1; + rect.height = height; - priv->start_x = event->x; - priv->start_y = event->y; - icon_info->pressed = TRUE; + _gtk_text_handle_set_visible (priv->text_handle, pos, TRUE); + _gtk_text_handle_set_position (priv->text_handle, pos, &rect); + } +} - if (!icon_info->nonactivatable) - g_signal_emit (entry, signals[ICON_PRESS], 0, i, event); +static gint +gtk_entry_get_selection_bound_location (GtkEntry *entry) +{ + GtkEntryPrivate *priv = entry->priv; + PangoLayout *layout; + PangoRectangle pos; + gint x; + const gchar *text; + gint index; - return TRUE; - } - } + 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 (event->window != priv->text_area || - (priv->button && event->button != priv->button)) - return FALSE; + 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; - gtk_entry_reset_blink_time (entry); + return x; +} + +static void +gtk_entry_update_handles (GtkEntry *entry, + GtkTextHandleMode mode) +{ + GtkEntryPrivate *priv = entry->priv; + gint strong_x, height; + gint cursor, bound; + + _gtk_text_handle_set_mode (priv->text_handle, mode); + + /* Wait for recomputation before repositioning */ + if (priv->recompute_idle != 0) + return; + + height = gdk_window_get_height (priv->text_area); + + gtk_entry_get_cursor_locations (entry, CURSOR_STANDARD, &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, height); + gtk_entry_move_handle (entry, GTK_TEXT_HANDLE_POSITION_SELECTION_END, + end, 0, height); + } + else + gtk_entry_move_handle (entry, GTK_TEXT_HANDLE_POSITION_CURSOR, + cursor, 0, height); +} + +static gint +gtk_entry_button_press (GtkWidget *widget, + GdkEventButton *event) +{ + GtkEntry *entry = GTK_ENTRY (widget); + GtkEditable *editable = GTK_EDITABLE (widget); + GtkEntryPrivate *priv = entry->priv; + EntryIconInfo *icon_info = NULL; + gint tmp_pos; + gint sel_start, sel_end; + gint i; + + gtk_entry_selection_bubble_popup_unset (entry); + + for (i = 0; i < MAX_ICONS; i++) + { + icon_info = priv->icons[i]; + + if (!icon_info || icon_info->insensitive) + continue; + + if (event->window == icon_info->window) + { + if (should_prelight (entry, i)) + { + icon_info->prelight = FALSE; + gtk_widget_queue_draw (widget); + } + + priv->start_x = event->x; + priv->start_y = event->y; + icon_info->pressed = TRUE; + + if (!icon_info->nonactivatable) + g_signal_emit (entry, signals[ICON_PRESS], 0, i, event); + + return TRUE; + } + } + + if (event->window != priv->text_area || + (priv->button && event->button != priv->button)) + return FALSE; + + gtk_entry_reset_blink_time (entry); priv->button = event->button; - + priv->device = gdk_event_get_device ((GdkEvent *) event); + if (!gtk_widget_has_focus (widget)) { priv->in_click = TRUE; @@ -3785,12 +4116,19 @@ gtk_entry_button_press (GtkWidget *widget, { gtk_entry_do_popup (entry, event); priv->button = 0; /* Don't wait for release, since the menu will gtk_grab_add */ + priv->device = NULL; return TRUE; } - else if (event->button == 1) + else if (event->button == GDK_BUTTON_PRIMARY) { gboolean have_selection = gtk_editable_get_selection_bounds (editable, &sel_start, &sel_end); + gboolean is_touchscreen; + GdkDevice *source; + + source = gdk_event_get_source_device ((GdkEvent *) event); + is_touchscreen = test_touchscreen || + gdk_device_get_source (source) == GDK_SOURCE_TOUCHSCREEN; priv->select_words = FALSE; priv->select_lines = FALSE; @@ -3799,11 +4137,11 @@ gtk_entry_button_press (GtkWidget *widget, gtk_widget_get_modifier_mask (widget, GDK_MODIFIER_INTENT_EXTEND_SELECTION)) { - _gtk_entry_reset_im_context (entry); + gtk_entry_reset_im_context (entry); if (!have_selection) /* select from the current position to the clicked position */ sel_start = sel_end = priv->current_pos; - + if (tmp_pos > sel_start && tmp_pos < sel_end) { /* Truncate current selection, but keep it as big as possible */ @@ -3869,7 +4207,12 @@ gtk_entry_button_press (GtkWidget *widget, priv->drag_start_y = event->y; } else - gtk_editable_set_position (editable, tmp_pos); + { + gtk_editable_set_position (editable, tmp_pos); + + if (is_touchscreen) + gtk_entry_update_handles (entry, GTK_TEXT_HANDLE_MODE_CURSOR); + } break; case GDK_2BUTTON_PRESS: @@ -3880,6 +4223,9 @@ gtk_entry_button_press (GtkWidget *widget, priv->in_drag = FALSE; priv->select_words = TRUE; gtk_entry_select_word (entry); + + if (is_touchscreen) + gtk_entry_update_handles (entry, GTK_TEXT_HANDLE_MODE_SELECTION); break; case GDK_3BUTTON_PRESS: @@ -3890,6 +4236,8 @@ gtk_entry_button_press (GtkWidget *widget, priv->in_drag = FALSE; priv->select_lines = TRUE; gtk_entry_select_line (entry); + if (is_touchscreen) + gtk_entry_update_handles (entry, GTK_TEXT_HANDLE_MODE_SELECTION); break; default: @@ -3898,7 +4246,9 @@ gtk_entry_button_press (GtkWidget *widget, return TRUE; } - else if (event->button == 2 && event->type == GDK_BUTTON_PRESS) + else if (event->type == GDK_BUTTON_PRESS && + event->button == GDK_BUTTON_MIDDLE && + get_middle_click_paste (entry)) { if (priv->editable) { @@ -3922,6 +4272,8 @@ gtk_entry_button_release (GtkWidget *widget, GtkEntry *entry = GTK_ENTRY (widget); GtkEntryPrivate *priv = entry->priv; EntryIconInfo *icon_info = NULL; + gboolean is_touchscreen; + GdkDevice *source; gint i; for (i = 0; i < MAX_ICONS; i++) @@ -3954,17 +4306,27 @@ gtk_entry_button_release (GtkWidget *widget, if (event->window != priv->text_area || priv->button != event->button) return FALSE; + source = gdk_event_get_source_device ((GdkEvent *) event); + is_touchscreen = (test_touchscreen || + gdk_device_get_source (source) == GDK_SOURCE_TOUCHSCREEN); + if (priv->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_entry_update_handles (entry, GTK_TEXT_HANDLE_MODE_CURSOR); + priv->in_drag = 0; } - + else if (is_touchscreen) + gtk_entry_selection_bubble_popup_set (entry); + priv->button = 0; - + priv->device = NULL; + gtk_entry_update_primary_selection (entry); return TRUE; @@ -4033,7 +4395,7 @@ gtk_entry_motion_notify (GtkWidget *widget, priv->mouse_cursor_obscured = FALSE; } - if (event->window != priv->text_area || priv->button != 1) + if (event->window != priv->text_area || priv->button != GDK_BUTTON_PRIMARY) return FALSE; if (priv->select_lines) @@ -4073,19 +4435,29 @@ gtk_entry_motion_notify (GtkWidget *widget, priv->in_drag = FALSE; priv->button = 0; - + priv->device = NULL; + gtk_target_list_unref (target_list); } } else { + GdkInputSource input_source; + GdkDevice *source; + guint length; + + length = gtk_entry_buffer_get_length (get_buffer (entry)); + if (event->y < 0) tmp_pos = 0; else if (event->y >= gdk_window_get_height (priv->text_area)) - tmp_pos = gtk_entry_buffer_get_length (get_buffer (entry)); + tmp_pos = length; else tmp_pos = gtk_entry_find_position (entry, event->x + priv->scroll_offset); + source = gdk_event_get_source_device ((GdkEvent *) event); + input_source = gdk_device_get_source (source); + if (priv->select_words) { gint min, max; @@ -4121,13 +4493,20 @@ gtk_entry_motion_notify (GtkWidget *widget, 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 (test_touchscreen || input_source == GDK_SOURCE_TOUCHSCREEN) + gtk_entry_update_handles (entry, + (priv->current_pos == priv->selection_bound) ? + GTK_TEXT_HANDLE_MODE_CURSOR : + GTK_TEXT_HANDLE_MODE_SELECTION); } - + return TRUE; } @@ -4168,6 +4547,12 @@ gtk_entry_key_press (GtkWidget *widget, gtk_entry_reset_blink_time (entry); gtk_entry_pend_cursor_blink (entry); + gtk_entry_selection_bubble_popup_unset (entry); + + if (!event->send_event) + _gtk_text_handle_set_mode (priv->text_handle, + GTK_TEXT_HANDLE_MODE_NONE); + if (priv->editable) { if (gtk_im_context_filter_keypress (priv->im_context, event)) @@ -4178,21 +4563,11 @@ gtk_entry_key_press (GtkWidget *widget, } } - if (event->keyval == GDK_KEY_Return || - event->keyval == GDK_KEY_KP_Enter || - event->keyval == GDK_KEY_ISO_Enter || + if (event->keyval == GDK_KEY_Return || + event->keyval == GDK_KEY_KP_Enter || + event->keyval == GDK_KEY_ISO_Enter || event->keyval == GDK_KEY_Escape) - { - GtkEntryCompletion *completion = gtk_entry_get_completion (entry); - - if (completion && completion->priv->completion_timeout) - { - g_source_remove (completion->priv->completion_timeout); - completion->priv->completion_timeout = 0; - } - - _gtk_entry_reset_im_context (entry); - } + gtk_entry_reset_im_context (entry); if (GTK_WIDGET_CLASS (gtk_entry_parent_class)->key_press_event (widget, event)) /* Activate key bindings @@ -4271,6 +4646,10 @@ gtk_entry_focus_out (GtkWidget *widget, GtkEntryCompletion *completion; GdkKeymap *keymap; + gtk_entry_selection_bubble_popup_unset (entry); + _gtk_text_handle_set_mode (priv->text_handle, + GTK_TEXT_HANDLE_MODE_NONE); + gtk_widget_queue_draw (widget); keymap = gdk_keymap_get_for_display (gtk_widget_get_display (widget)); @@ -4448,7 +4827,7 @@ gtk_entry_real_set_position (GtkEditable *editable, if (position != priv->current_pos || position != priv->selection_bound) { - _gtk_entry_reset_im_context (entry); + gtk_entry_reset_im_context (entry); gtk_entry_set_positions (entry, position, position); } } @@ -4475,8 +4854,8 @@ gtk_entry_set_selection_bounds (GtkEditable *editable, start = length; if (end < 0) end = length; - - _gtk_entry_reset_im_context (entry); + + gtk_entry_reset_im_context (entry); gtk_entry_set_positions (entry, MIN (end, length), @@ -4515,17 +4894,6 @@ icon_theme_changed (GtkEntry *entry) gtk_widget_queue_draw (GTK_WIDGET (entry)); } -static void -icon_margin_changed (GtkEntry *entry) -{ - GtkEntryPrivate *priv = entry->priv; - GtkBorder border; - - _gtk_entry_effective_inner_border (GTK_ENTRY (entry), &border); - - priv->icon_margin = border.left; -} - static void gtk_entry_update_cached_style_values (GtkEntry *entry) { @@ -4561,10 +4929,7 @@ gtk_entry_style_updated (GtkWidget *widget) gtk_entry_update_cached_style_values (entry); - gtk_entry_recompute (entry); - icon_theme_changed (entry); - icon_margin_changed (entry); } /* GtkCellEditable method implementations @@ -4865,7 +5230,7 @@ gtk_entry_move_cursor (GtkEntry *entry, GtkEntryPrivate *priv = entry->priv; gint new_pos = priv->current_pos; - _gtk_entry_reset_im_context (entry); + gtk_entry_reset_im_context (entry); if (priv->current_pos != priv->selection_bound && !extend_selection) { @@ -4982,8 +5347,7 @@ gtk_entry_insert_at_cursor (GtkEntry *entry, if (priv->editable) { - _gtk_entry_reset_im_context (entry); - + gtk_entry_reset_im_context (entry); gtk_editable_insert_text (editable, str, -1, &pos); gtk_editable_set_position (editable, pos); } @@ -4999,8 +5363,8 @@ gtk_entry_delete_from_cursor (GtkEntry *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); + + gtk_entry_reset_im_context (entry); if (!priv->editable) { @@ -5077,7 +5441,7 @@ gtk_entry_backspace (GtkEntry *entry) GtkEditable *editable = GTK_EDITABLE (entry); gint prev_pos; - _gtk_entry_reset_im_context (entry); + gtk_entry_reset_im_context (entry); if (!priv->editable) { @@ -5108,8 +5472,8 @@ gtk_entry_backspace (GtkEntry *entry) gchar *normalized_text; glong len; - cluster_text = gtk_entry_get_display_text (entry, prev_pos, - priv->current_pos); + 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); @@ -5160,7 +5524,7 @@ gtk_entry_copy_clipboard (GtkEntry *entry) return; } - str = gtk_entry_get_display_text (entry, start, end); + str = _gtk_entry_get_display_text (entry, start, end); gtk_clipboard_set_text (gtk_widget_get_clipboard (GTK_WIDGET (entry), GDK_SELECTION_CLIPBOARD), str, -1); @@ -5192,6 +5556,8 @@ gtk_entry_cut_clipboard (GtkEntry *entry) { gtk_widget_error_bell (GTK_WIDGET (entry)); } + + gtk_entry_selection_bubble_popup_unset (entry); } static void @@ -5318,7 +5684,7 @@ gtk_entry_retrieve_surrounding_cb (GtkIMContext *context, 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); + 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); @@ -5464,10 +5830,17 @@ recompute_idle_func (gpointer data) if (gtk_widget_has_screen (GTK_WIDGET (entry))) { + GtkTextHandleMode handle_mode; + gtk_entry_adjust_scroll (entry); gtk_widget_queue_draw (GTK_WIDGET (entry)); update_im_cursor_location (entry); + + 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); } return FALSE; @@ -5523,9 +5896,9 @@ gtk_entry_create_layout (GtkEntry *entry, { GtkEntryPrivate *priv = entry->priv; GtkWidget *widget = GTK_WIDGET (entry); - PangoLayout *layout = gtk_widget_create_pango_layout (widget, NULL); - PangoAttrList *tmp_attrs = pango_attr_list_new (); - gboolean placeholder_layout = show_placeholder_text (entry); + PangoLayout *layout; + PangoAttrList *tmp_attrs; + gboolean placeholder_layout; gchar *preedit_string = NULL; gint preedit_length = 0; @@ -5534,9 +5907,14 @@ gtk_entry_create_layout (GtkEntry *entry, gchar *display; guint n_bytes; + layout = gtk_widget_create_pango_layout (widget, NULL); pango_layout_set_single_paragraph_mode (layout, TRUE); - display = placeholder_layout ? g_strdup (priv->placeholder_text) : gtk_entry_get_display_text (entry, 0, -1); + tmp_attrs = priv->attrs ? pango_attr_list_ref (priv->attrs) + : pango_attr_list_new (); + + placeholder_layout = show_placeholder_text (entry); + display = placeholder_layout ? g_strdup (priv->placeholder_text) : _gtk_entry_get_display_text (entry, 0, -1); n_bytes = strlen (display); if (!placeholder_layout && include_preedit) @@ -5563,12 +5941,9 @@ gtk_entry_create_layout (GtkEntry *entry, gint cursor_index = g_utf8_offset_to_pointer (display, priv->current_pos) - display; g_string_insert (tmp_string, cursor_index, preedit_string); - pango_layout_set_text (layout, tmp_string->str, tmp_string->len); - pango_attr_list_splice (tmp_attrs, preedit_attrs, cursor_index, preedit_length); - g_string_free (tmp_string, TRUE); } else @@ -5649,16 +6024,13 @@ get_layout_position (GtkEntry *entry, PangoLayout *layout; PangoRectangle logical_rect; gint area_width, area_height; - GtkBorder inner_border; gint y_pos; PangoLayoutLine *line; layout = gtk_entry_ensure_layout (entry, TRUE); - gtk_entry_get_text_area_size (entry, NULL, NULL, &area_width, &area_height); - _gtk_entry_effective_inner_border (entry, &inner_border); - - area_height = PANGO_SCALE * (area_height - inner_border.top - inner_border.bottom); + get_text_area_size (entry, NULL, NULL, &area_width, &area_height); + area_height = PANGO_SCALE * area_height; line = pango_layout_get_lines_readonly (layout)->data; pango_layout_line_get_extents (line, NULL, &logical_rect); @@ -5675,10 +6047,10 @@ get_layout_position (GtkEntry *entry, else if (y_pos + logical_rect.height > area_height) y_pos = area_height - logical_rect.height; - y_pos = inner_border.top + y_pos / PANGO_SCALE; + y_pos = y_pos / PANGO_SCALE; if (x) - *x = inner_border.left - priv->scroll_offset; + *x = - priv->scroll_offset; if (y) *y = y_pos; @@ -5711,7 +6083,6 @@ draw_text_with_color (GtkEntry *entry, gint n_ranges, i; PangoRectangle logical_rect; GdkRGBA selection_color, text_color; - GtkBorder inner_border; GtkStyleContext *context; GtkStateFlags state; @@ -5725,11 +6096,9 @@ draw_text_with_color (GtkEntry *entry, gtk_style_context_get_background_color (context, state, &selection_color); gtk_style_context_get_color (context, state, &text_color); - _gtk_entry_effective_inner_border (entry, &inner_border); - for (i = 0; i < n_ranges; ++i) cairo_rectangle (cr, - inner_border.left - priv->scroll_offset + ranges[2 * i], + - priv->scroll_offset + ranges[2 * i], y, ranges[2 * i + 1], logical_rect.height); @@ -5771,8 +6140,7 @@ gtk_entry_draw_text (GtkEntry *entry, gtk_style_context_get_color (context, state, &text_color); /* Get foreground color for progressbars */ - gtk_style_context_save (context); - gtk_style_context_add_class (context, GTK_STYLE_CLASS_PROGRESSBAR); + gtk_entry_prepare_context_for_progress (entry, context); gtk_style_context_get_color (context, state, &bar_text_color); gtk_style_context_restore (context); @@ -5849,12 +6217,14 @@ gtk_entry_draw_cursor (GtkEntry *entry, gboolean block_at_line_end; PangoLayout *layout; const char *text; + gint x, y; context = gtk_widget_get_style_context (widget); layout = gtk_entry_ensure_layout (entry, TRUE); text = pango_layout_get_text (layout); cursor_index = g_utf8_offset_to_pointer (text, priv->current_pos + priv->preedit_cursor) - text; + get_layout_position (entry, &x, &y); if (!priv->overwrite_mode) block = FALSE; @@ -5864,24 +6234,17 @@ gtk_entry_draw_cursor (GtkEntry *entry, if (!block) { - GtkBorder inner_border; - - _gtk_entry_effective_inner_border (entry, &inner_border); - gtk_render_insertion_cursor (context, cr, - inner_border.left - priv->scroll_offset, inner_border.top, + x, y, layout, cursor_index, priv->resolved_dir); } else /* overwrite_mode */ { GdkRGBA cursor_color; GdkRectangle rect; - gint x, y; cairo_save (cr); - get_layout_position (entry, &x, &y); - rect.x = PANGO_PIXELS (cursor_rect.x) + x; rect.y = PANGO_PIXELS (cursor_rect.y) + y; rect.width = PANGO_PIXELS (cursor_rect.width); @@ -5911,18 +6274,81 @@ gtk_entry_draw_cursor (GtkEntry *entry, } } -void -_gtk_entry_reset_im_context (GtkEntry *entry) +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 = entry->priv; + GtkTextHandleMode mode; + gint *min, *max; - if (priv->need_im_reset) + 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) { - priv->need_im_reset = FALSE; - gtk_im_context_reset (priv->im_context); + 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) + gtk_entry_set_positions (entry, cursor_pos, cursor_pos); + else + gtk_entry_set_positions (entry, cursor_pos, selection_bound_pos); + + gtk_entry_update_handles (entry, mode); } } +static void +gtk_entry_handle_drag_finished (GtkTextHandle *handle, + GtkTextHandlePosition pos, + GtkEntry *entry) +{ + gtk_entry_selection_bubble_popup_set (entry); +} + + /** * gtk_entry_reset_im_context: * @entry: a #GtkEntry @@ -5937,9 +6363,15 @@ _gtk_entry_reset_im_context (GtkEntry *entry) void gtk_entry_reset_im_context (GtkEntry *entry) { + GtkEntryPrivate *priv = entry->priv; + g_return_if_fail (GTK_IS_ENTRY (entry)); - _gtk_entry_reset_im_context (entry); + if (priv->need_im_reset) + { + priv->need_im_reset = FALSE; + gtk_im_context_reset (priv->im_context); + } } /** @@ -6073,27 +6505,42 @@ gtk_entry_get_cursor_locations (GtkEntry *entry, } } +static gboolean +gtk_entry_get_is_selection_handle_dragged (GtkEntry *entry) +{ + GtkEntryPrivate *priv = entry->priv; + GtkTextHandlePosition pos; + + 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_adjust_scroll (GtkEntry *entry) { GtkEntryPrivate *priv = entry->priv; gint min_offset, max_offset; gint text_area_width, text_width; - GtkBorder inner_border; gint strong_x, weak_x; gint strong_xoffset, weak_xoffset; gfloat xalign; PangoLayout *layout; PangoLayoutLine *line; PangoRectangle logical_rect; + GtkTextHandleMode handle_mode; if (!gtk_widget_get_realized (GTK_WIDGET (entry))) return; - _gtk_entry_effective_inner_border (entry, &inner_border); - text_area_width = gdk_window_get_width (priv->text_area); - text_area_width -= inner_border.left + inner_border.right; + if (text_area_width < 0) text_area_width = 0; @@ -6124,22 +6571,33 @@ gtk_entry_adjust_scroll (GtkEntry *entry) priv->scroll_offset = CLAMP (priv->scroll_offset, min_offset, max_offset); - /* 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. - */ + 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, CURSOR_STANDARD, &strong_x, &weak_x); + } - gtk_entry_get_cursor_locations (entry, CURSOR_STANDARD, &strong_x, &weak_x); - strong_xoffset = strong_x - priv->scroll_offset; if (strong_xoffset < 0) @@ -6166,6 +6624,11 @@ gtk_entry_adjust_scroll (GtkEntry *entry) } g_object_notify (G_OBJECT (entry), "scroll-offset"); + + 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 void @@ -6176,9 +6639,8 @@ gtk_entry_move_adjustments (GtkEntry *entry) GtkAdjustment *adjustment; PangoContext *context; PangoFontMetrics *metrics; - GtkStyleContext *style_context; - GtkStateFlags state; - gint x, layout_x, border_x, border_y; + GtkBorder borders; + gint x, layout_x; gint char_width; adjustment = g_object_get_qdata (G_OBJECT (entry), quark_cursor_hadjustment); @@ -6187,19 +6649,17 @@ gtk_entry_move_adjustments (GtkEntry *entry) gtk_widget_get_allocation (widget, &allocation); - /* Cursor position, layout offset, border width, and widget allocation */ + /* Cursor/char position, layout offset, border width, and widget allocation */ gtk_entry_get_cursor_locations (entry, CURSOR_STANDARD, &x, NULL); get_layout_position (entry, &layout_x, NULL); - _gtk_entry_get_borders (entry, &border_x, &border_y); - x += allocation.x + layout_x + border_x; + _gtk_entry_get_borders (entry, &borders); + x += allocation.x + layout_x + borders.left; /* Approximate width of a char, so user can see what is ahead/behind */ context = gtk_widget_get_pango_context (widget); - style_context = gtk_widget_get_style_context (widget); - state = gtk_widget_get_state_flags (widget); metrics = pango_context_get_metrics (context, - gtk_style_context_get_font (style_context, state), + pango_context_get_font_description (context), pango_context_get_language (context)); char_width = pango_font_metrics_get_approximate_char_width (metrics) / PANGO_SCALE; @@ -6444,7 +6904,7 @@ paste_received (GtkClipboard *clipboard, GtkEditable *editable = GTK_EDITABLE (entry); GtkEntryPrivate *priv = entry->priv; - if (priv->button == 2) + if (priv->button == GDK_BUTTON_MIDDLE) { gint pos, start, end; pos = priv->insert_pos; @@ -6515,7 +6975,7 @@ primary_get_cb (GtkClipboard *clipboard, if (gtk_editable_get_selection_bounds (GTK_EDITABLE (entry), &start, &end)) { - gchar *str = gtk_entry_get_display_text (entry, start, end); + gchar *str = _gtk_entry_get_display_text (entry, start, end); gtk_selection_data_set_text (selection_data, str, -1); g_free (str); } @@ -6856,14 +7316,19 @@ gtk_entry_set_text (GtkEntry *entry, * @visible: %TRUE if the contents of the entry are displayed * as plaintext * - * Sets whether the contents of the entry are visible or not. - * When visibility is set to %FALSE, characters are displayed - * as the invisible char, and will also appear that way when + * Sets whether the contents of the entry 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 entry widget is copied elsewhere. * * By default, GTK+ picks the best invisible character available * in the current font, but it can be changed with * gtk_entry_set_invisible_char(). + * + * Note that you probably want to set #GtkEntry:input-purpose + * to %GTK_INPUT_PURPOSE_PASSWORD or %GTK_INPUT_PURPOSE_PIN to + * inform input methods about the purpose of this entry, + * in addition to setting visibility to %FALSE. */ void gtk_entry_set_visibility (GtkEntry *entry, @@ -7150,7 +7615,7 @@ gtk_entry_get_text_length (GtkEntry *entry) * * (For experts: if @setting is %TRUE, the entry calls * gtk_window_activate_default() on the window containing the entry, in - * the default handler for the #GtkWidget::activate signal.) + * the default handler for the #GtkEntry::activate signal.) **/ void gtk_entry_set_activates_default (GtkEntry *entry, @@ -7290,6 +7755,10 @@ gtk_entry_get_has_frame (GtkEntry *entry) * pixel-exact positioning of the entry is important. * * Since: 2.10 + * + * Deprecated: 3.4: Use the standard border and padding CSS properties (through + * objects like #GtkStyleContext and #GtkCssProvider); the value set with + * this function is ignored by #GtkEntry. **/ void gtk_entry_set_inner_border (GtkEntry *entry, @@ -7297,16 +7766,7 @@ gtk_entry_set_inner_border (GtkEntry *entry, { g_return_if_fail (GTK_IS_ENTRY (entry)); - gtk_widget_queue_resize (GTK_WIDGET (entry)); - - if (border) - g_object_set_qdata_full (G_OBJECT (entry), quark_inner_border, - gtk_border_copy (border), - (GDestroyNotify) gtk_border_free); - else - g_object_set_qdata (G_OBJECT (entry), quark_inner_border, NULL); - - g_object_notify (G_OBJECT (entry), "inner-border"); + gtk_entry_do_set_inner_border (entry, border); } /** @@ -7319,13 +7779,17 @@ gtk_entry_set_inner_border (GtkEntry *entry, * Return value: (transfer none): the entry's #GtkBorder, or %NULL if none was set. * * Since: 2.10 + * + * Deprecated: 3.4: Use the standard border and padding CSS properties (through + * objects like #GtkStyleContext and #GtkCssProvider); the value returned by + * this function is ignored by #GtkEntry. **/ const GtkBorder * gtk_entry_get_inner_border (GtkEntry *entry) { g_return_val_if_fail (GTK_IS_ENTRY (entry), NULL); - return g_object_get_qdata (G_OBJECT (entry), quark_inner_border); + return gtk_entry_do_get_inner_border (entry); } /** @@ -7574,6 +8038,8 @@ gtk_entry_set_icon_from_pixbuf (GtkEntry *entry, if (pixbuf) { _gtk_icon_helper_set_pixbuf (icon_info->icon_helper, pixbuf); + _gtk_icon_helper_set_icon_size (icon_info->icon_helper, + GTK_ICON_SIZE_MENU); if (icon_pos == GTK_ENTRY_ICON_PRIMARY) { @@ -7900,7 +8366,8 @@ gtk_entry_get_icon_pixbuf (GtkEntry *entry, * the icon helper's cache ref directly. */ pixbuf = gtk_entry_ensure_pixbuf (entry, icon_pos); - g_object_unref (pixbuf); + if (pixbuf) + g_object_unref (pixbuf); return pixbuf; } @@ -8156,7 +8623,7 @@ gtk_entry_get_icon_at_pos (GtkEntry *entry, /** * gtk_entry_set_icon_drag_source: - * @entry: a #GtkIconEntry + * @entry: a #GtkEntry * @icon_pos: icon position * @target_list: the targets (data formats) in which the data can be provided * @actions: a bitmask of the allowed drag actions @@ -8401,6 +8868,9 @@ gtk_entry_set_icon_tooltip_text (GtkEntry *entry, icon_info->tooltip = tooltip ? g_markup_escape_text (tooltip, -1) : NULL; ensure_has_tooltip (entry); + + g_object_notify (G_OBJECT (entry), + icon_pos == GTK_ENTRY_ICON_PRIMARY ? "primary-icon-tooltip-text" : "secondary-icon-tooltip-text"); } /** @@ -8449,7 +8919,7 @@ gtk_entry_get_icon_tooltip_markup (GtkEntry *entry, * Use %NULL for @tooltip to remove an existing tooltip. * * See also gtk_widget_set_tooltip_markup() and - * gtk_enty_set_icon_tooltip_text(). + * gtk_entry_set_icon_tooltip_text(). * * Since: 2.16 */ @@ -8542,6 +9012,26 @@ gtk_entry_mnemonic_activate (GtkWidget *widget, return TRUE; } +static void +gtk_entry_grab_notify (GtkWidget *widget, + gboolean was_grabbed) +{ + GtkEntryPrivate *priv; + + priv = GTK_ENTRY (widget)->priv; + + if (priv->device && + gtk_widget_device_is_shadowed (widget, priv->device)) + { + /* Unset button so we don't expect + * a button release anymore + */ + priv->button = 0; + priv->device = NULL; + priv->in_drag = FALSE; + } +} + static void append_action_signal (GtkEntry *entry, GtkWidget *menu, @@ -8584,7 +9074,7 @@ popup_position_func (GtkMenu *menu, GdkScreen *screen; GtkRequisition menu_req; GdkRectangle monitor; - GtkBorder inner_border; + GtkBorder borders; gint monitor_num, strong_x, height; g_return_if_fail (gtk_widget_get_realized (widget)); @@ -8602,9 +9092,9 @@ popup_position_func (GtkMenu *menu, &menu_req, NULL); height = gdk_window_get_height (priv->text_area); gtk_entry_get_cursor_locations (entry, CURSOR_STANDARD, &strong_x, NULL); - _gtk_entry_effective_inner_border (entry, &inner_border); + _gtk_entry_get_borders (entry, &borders); - *x += inner_border.left + strong_x - priv->scroll_offset; + *x += borders.left + strong_x - priv->scroll_offset; if (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL) *x -= menu_req.width; @@ -8800,20 +9290,165 @@ gtk_entry_popup_menu (GtkWidget *widget) } static void -gtk_entry_drag_begin (GtkWidget *widget, - GdkDragContext *context) +activate_bubble_cb (GtkWidget *item, + GtkEntry *entry) { - GtkEntry *entry = GTK_ENTRY (widget); + const gchar *signal = g_object_get_data (G_OBJECT (item), "gtk-signal"); + g_signal_emit_by_name (entry, signal); + _gtk_bubble_window_popdown (GTK_BUBBLE_WINDOW (entry->priv->selection_bubble)); +} + +static void +append_bubble_action (GtkEntry *entry, + GtkWidget *toolbar, + const gchar *stock_id, + const gchar *signal, + gboolean sensitive) +{ + GtkToolItem *item = gtk_tool_button_new_from_stock (stock_id); + g_object_set_data (G_OBJECT (item), I_("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_toolbar_insert (GTK_TOOLBAR (toolbar), item, -1); +} + +static void +bubble_targets_received (GtkClipboard *clipboard, + GtkSelectionData *data, + gpointer user_data) +{ + GtkEntry *entry = user_data; GtkEntryPrivate *priv = entry->priv; - gint i; + cairo_rectangle_int_t rect; + GtkAllocation allocation; + gint start_x, end_x; + gboolean has_selection; + gboolean has_clipboard; + DisplayMode mode; + GtkWidget *toolbar; - for (i = 0; i < MAX_ICONS; i++) + has_selection = gtk_editable_get_selection_bounds (GTK_EDITABLE (entry), + NULL, NULL); + if (!has_selection && !priv->editable) { - EntryIconInfo *icon_info = priv->icons[i]; - - if (icon_info != NULL) - { - if (icon_info->in_drag) + priv->selection_bubble_timeout_id = 0; + return; + } + + if (priv->selection_bubble) + gtk_widget_destroy (priv->selection_bubble); + + priv->selection_bubble = _gtk_bubble_window_new (); + toolbar = GTK_WIDGET (gtk_toolbar_new ()); + gtk_toolbar_set_style (GTK_TOOLBAR (toolbar), GTK_TOOLBAR_TEXT); + gtk_toolbar_set_show_arrow (GTK_TOOLBAR (toolbar), FALSE); + gtk_widget_show (toolbar); + gtk_container_add (GTK_CONTAINER (priv->selection_bubble), toolbar); + + has_clipboard = gtk_selection_data_targets_include_text (data); + mode = gtk_entry_get_display_mode (entry); + + append_bubble_action (entry, toolbar, GTK_STOCK_CUT, "cut-clipboard", + priv->editable && has_selection && mode == DISPLAY_NORMAL); + + append_bubble_action (entry, toolbar, GTK_STOCK_COPY, "copy-clipboard", + has_selection && mode == DISPLAY_NORMAL); + + append_bubble_action (entry, toolbar, GTK_STOCK_PASTE, "paste-clipboard", + priv->editable && has_clipboard); + + if (priv->populate_all) + g_signal_emit (entry, signals[POPULATE_POPUP], 0, toolbar); + + gtk_widget_get_allocation (GTK_WIDGET (entry), &allocation); + + gtk_entry_get_cursor_locations (entry, CURSOR_STANDARD, &start_x, NULL); + + start_x -= priv->scroll_offset; + start_x = CLAMP (start_x, 0, gdk_window_get_width (priv->text_area)); + + rect.y = 0; + rect.height = gdk_window_get_height (priv->text_area); + + if (has_selection) + { + end_x = gtk_entry_get_selection_bound_location (entry) - priv->scroll_offset; + end_x = CLAMP (end_x, 0, gdk_window_get_width (priv->text_area)); + + rect.x = MIN (start_x, end_x); + rect.width = MAX (start_x, end_x) - rect.x; + } + else + { + rect.x = start_x; + rect.width = 0; + } + + _gtk_bubble_window_popup (GTK_BUBBLE_WINDOW (priv->selection_bubble), + priv->text_area, &rect, GTK_POS_TOP); + + priv->selection_bubble_timeout_id = 0; +} + +static gboolean +gtk_entry_selection_bubble_popup_cb (gpointer user_data) +{ + GtkEntry *entry = user_data; + + gtk_clipboard_request_contents (gtk_widget_get_clipboard (GTK_WIDGET (entry), GDK_SELECTION_CLIPBOARD), + gdk_atom_intern_static_string ("TARGETS"), + bubble_targets_received, + entry); + return G_SOURCE_REMOVE; +} + +static void +gtk_entry_selection_bubble_popup_unset (GtkEntry *entry) +{ + GtkEntryPrivate *priv; + + priv = entry->priv; + + if (priv->selection_bubble) + _gtk_bubble_window_popdown (GTK_BUBBLE_WINDOW (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; + + priv = entry->priv; + + if (priv->selection_bubble_timeout_id) + g_source_remove (priv->selection_bubble_timeout_id); + + priv->selection_bubble_timeout_id = + gdk_threads_add_timeout (1000, gtk_entry_selection_bubble_popup_cb, entry); +} + +static void +gtk_entry_drag_begin (GtkWidget *widget, + GdkDragContext *context) +{ + GtkEntry *entry = GTK_ENTRY (widget); + GtkEntryPrivate *priv = entry->priv; + gint i; + + for (i = 0; i < MAX_ICONS; i++) + { + EntryIconInfo *icon_info = priv->icons[i]; + + if (icon_info != NULL) + { + if (icon_info->in_drag) { GdkPixbuf *pix; @@ -9032,7 +9667,7 @@ gtk_entry_drag_data_get (GtkWidget *widget, 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); + gchar *str = _gtk_entry_get_display_text (GTK_ENTRY (widget), sel_start, sel_end); gtk_selection_data_set_text (selection_data, str, -1); @@ -9100,6 +9735,18 @@ cursor_blinks (GtkEntry *entry) 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) { @@ -9260,509 +9907,6 @@ gtk_entry_reset_blink_time (GtkEntry *entry) priv->blink_time = 0; } - -/* completion */ -static gint -gtk_entry_completion_timeout (gpointer data) -{ - GtkEntryCompletion *completion = GTK_ENTRY_COMPLETION (data); - GtkEntryPrivate *completion_entry_priv = GTK_ENTRY (completion->priv->entry)->priv; - - completion->priv->completion_timeout = 0; - - if (completion->priv->filter_model && - g_utf8_strlen (gtk_entry_get_text (GTK_ENTRY (completion->priv->entry)), -1) - >= completion->priv->minimum_key_length) - { - gint matches; - gint actions; - GtkTreeSelection *s; - gboolean popup_single; - - gtk_entry_completion_complete (completion); - matches = gtk_tree_model_iter_n_children (GTK_TREE_MODEL (completion->priv->filter_model), NULL); - - gtk_tree_selection_unselect_all (gtk_tree_view_get_selection (GTK_TREE_VIEW (completion->priv->tree_view))); - - s = gtk_tree_view_get_selection (GTK_TREE_VIEW (completion->priv->action_view)); - - gtk_tree_selection_unselect_all (s); - - actions = gtk_tree_model_iter_n_children (GTK_TREE_MODEL (completion->priv->actions), NULL); - - g_object_get (completion, "popup-single-match", &popup_single, NULL); - if ((matches > (popup_single ? 0: 1)) || actions > 0) - { - if (gtk_widget_get_visible (completion->priv->popup_window)) - _gtk_entry_completion_resize_popup (completion); - else - _gtk_entry_completion_popup (completion, completion_entry_priv->completion_device); - } - else - _gtk_entry_completion_popdown (completion); - } - else if (gtk_widget_get_visible (completion->priv->popup_window)) - _gtk_entry_completion_popdown (completion); - - return FALSE; -} - -static inline gboolean -keyval_is_cursor_move (guint keyval) -{ - if (keyval == GDK_KEY_Up || keyval == GDK_KEY_KP_Up) - return TRUE; - - if (keyval == GDK_KEY_Down || keyval == GDK_KEY_KP_Down) - return TRUE; - - if (keyval == GDK_KEY_Page_Up) - return TRUE; - - if (keyval == GDK_KEY_Page_Down) - return TRUE; - - return FALSE; -} - -static gboolean -gtk_entry_completion_key_press (GtkWidget *widget, - GdkEventKey *event, - gpointer user_data) -{ - gint matches, actions = 0; - GtkEntryCompletion *completion = GTK_ENTRY_COMPLETION (user_data); - - if (!gtk_widget_get_mapped (completion->priv->popup_window)) - return FALSE; - - matches = gtk_tree_model_iter_n_children (GTK_TREE_MODEL (completion->priv->filter_model), NULL); - - if (completion->priv->actions) - actions = gtk_tree_model_iter_n_children (GTK_TREE_MODEL (completion->priv->actions), NULL); - - if (keyval_is_cursor_move (event->keyval)) - { - GtkTreePath *path = NULL; - - if (event->keyval == GDK_KEY_Up || event->keyval == GDK_KEY_KP_Up) - { - if (completion->priv->current_selected < 0) - completion->priv->current_selected = matches + actions - 1; - else - completion->priv->current_selected--; - } - else if (event->keyval == GDK_KEY_Down || event->keyval == GDK_KEY_KP_Down) - { - if (completion->priv->current_selected < matches + actions - 1) - completion->priv->current_selected++; - else - completion->priv->current_selected = -1; - } - else if (event->keyval == GDK_KEY_Page_Up) - { - if (completion->priv->current_selected < 0) - completion->priv->current_selected = matches + actions - 1; - else if (completion->priv->current_selected == 0) - completion->priv->current_selected = -1; - else if (completion->priv->current_selected < matches) - { - completion->priv->current_selected -= PAGE_STEP; - if (completion->priv->current_selected < 0) - completion->priv->current_selected = 0; - } - else - { - completion->priv->current_selected -= PAGE_STEP; - if (completion->priv->current_selected < matches - 1) - completion->priv->current_selected = matches - 1; - } - } - else if (event->keyval == GDK_KEY_Page_Down) - { - if (completion->priv->current_selected < 0) - completion->priv->current_selected = 0; - else if (completion->priv->current_selected < matches - 1) - { - completion->priv->current_selected += PAGE_STEP; - if (completion->priv->current_selected > matches - 1) - completion->priv->current_selected = matches - 1; - } - else if (completion->priv->current_selected == matches + actions - 1) - { - completion->priv->current_selected = -1; - } - else - { - completion->priv->current_selected += PAGE_STEP; - if (completion->priv->current_selected > matches + actions - 1) - completion->priv->current_selected = matches + actions - 1; - } - } - - if (completion->priv->current_selected < 0) - { - gtk_tree_selection_unselect_all (gtk_tree_view_get_selection (GTK_TREE_VIEW (completion->priv->tree_view))); - gtk_tree_selection_unselect_all (gtk_tree_view_get_selection (GTK_TREE_VIEW (completion->priv->action_view))); - - if (completion->priv->inline_selection && - completion->priv->completion_prefix) - { - gtk_entry_set_text (GTK_ENTRY (completion->priv->entry), - completion->priv->completion_prefix); - gtk_editable_set_position (GTK_EDITABLE (widget), -1); - } - } - else if (completion->priv->current_selected < matches) - { - gtk_tree_selection_unselect_all (gtk_tree_view_get_selection (GTK_TREE_VIEW (completion->priv->action_view))); - - path = gtk_tree_path_new_from_indices (completion->priv->current_selected, -1); - gtk_tree_view_set_cursor (GTK_TREE_VIEW (completion->priv->tree_view), - path, NULL, FALSE); - - if (completion->priv->inline_selection) - { - - GtkTreeIter iter; - GtkTreeIter child_iter; - GtkTreeModel *model = NULL; - GtkTreeSelection *sel; - gboolean entry_set; - - sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (completion->priv->tree_view)); - if (!gtk_tree_selection_get_selected (sel, &model, &iter)) - return FALSE; - - gtk_tree_model_filter_convert_iter_to_child_iter (GTK_TREE_MODEL_FILTER (model), &child_iter, &iter); - model = gtk_tree_model_filter_get_model (GTK_TREE_MODEL_FILTER (model)); - - if (completion->priv->completion_prefix == NULL) - completion->priv->completion_prefix = g_strdup (gtk_entry_get_text (GTK_ENTRY (completion->priv->entry))); - - g_signal_emit_by_name (completion, "cursor-on-match", model, - &child_iter, &entry_set); - } - } - else if (completion->priv->current_selected - matches >= 0) - { - gtk_tree_selection_unselect_all (gtk_tree_view_get_selection (GTK_TREE_VIEW (completion->priv->tree_view))); - - path = gtk_tree_path_new_from_indices (completion->priv->current_selected - matches, -1); - gtk_tree_view_set_cursor (GTK_TREE_VIEW (completion->priv->action_view), - path, NULL, FALSE); - - if (completion->priv->inline_selection && - completion->priv->completion_prefix) - { - gtk_entry_set_text (GTK_ENTRY (completion->priv->entry), - completion->priv->completion_prefix); - gtk_editable_set_position (GTK_EDITABLE (widget), -1); - } - } - - gtk_tree_path_free (path); - - return TRUE; - } - else if (event->keyval == GDK_KEY_Escape || - event->keyval == GDK_KEY_Left || - event->keyval == GDK_KEY_KP_Left || - event->keyval == GDK_KEY_Right || - event->keyval == GDK_KEY_KP_Right) - { - gboolean retval = TRUE; - - _gtk_entry_reset_im_context (GTK_ENTRY (widget)); - _gtk_entry_completion_popdown (completion); - - if (completion->priv->current_selected < 0) - { - retval = FALSE; - goto keypress_completion_out; - } - else if (completion->priv->inline_selection) - { - /* Escape rejects the tentative completion */ - if (event->keyval == GDK_KEY_Escape) - { - if (completion->priv->completion_prefix) - gtk_entry_set_text (GTK_ENTRY (completion->priv->entry), - completion->priv->completion_prefix); - else - gtk_entry_set_text (GTK_ENTRY (completion->priv->entry), ""); - } - - /* Move the cursor to the end for Right/Esc, to the - beginning for Left */ - if (event->keyval == GDK_KEY_Right || - event->keyval == GDK_KEY_KP_Right || - event->keyval == GDK_KEY_Escape) - gtk_editable_set_position (GTK_EDITABLE (widget), -1); - else - gtk_editable_set_position (GTK_EDITABLE (widget), 0); - } - -keypress_completion_out: - if (completion->priv->inline_selection) - { - g_free (completion->priv->completion_prefix); - completion->priv->completion_prefix = NULL; - } - - return retval; - } - else if (event->keyval == GDK_KEY_Tab || - event->keyval == GDK_KEY_KP_Tab || - event->keyval == GDK_KEY_ISO_Left_Tab) - { - _gtk_entry_reset_im_context (GTK_ENTRY (widget)); - _gtk_entry_completion_popdown (completion); - - g_free (completion->priv->completion_prefix); - completion->priv->completion_prefix = NULL; - - return FALSE; - } - else if (event->keyval == GDK_KEY_ISO_Enter || - event->keyval == GDK_KEY_KP_Enter || - event->keyval == GDK_KEY_Return) - { - GtkTreeIter iter; - GtkTreeModel *model = NULL; - GtkTreeModel *child_model; - GtkTreeIter child_iter; - GtkTreeSelection *sel; - gboolean retval = TRUE; - - _gtk_entry_reset_im_context (GTK_ENTRY (widget)); - _gtk_entry_completion_popdown (completion); - - if (completion->priv->current_selected < matches) - { - gboolean entry_set; - - sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (completion->priv->tree_view)); - if (gtk_tree_selection_get_selected (sel, &model, &iter)) - { - gtk_tree_model_filter_convert_iter_to_child_iter (GTK_TREE_MODEL_FILTER (model), &child_iter, &iter); - child_model = gtk_tree_model_filter_get_model (GTK_TREE_MODEL_FILTER (model)); - g_signal_handler_block (widget, completion->priv->changed_id); - g_signal_emit_by_name (completion, "match-selected", - child_model, &child_iter, &entry_set); - g_signal_handler_unblock (widget, completion->priv->changed_id); - - if (!entry_set) - { - gchar *str = NULL; - - gtk_tree_model_get (model, &iter, - completion->priv->text_column, &str, - -1); - - gtk_entry_set_text (GTK_ENTRY (widget), str); - - /* move the cursor to the end */ - gtk_editable_set_position (GTK_EDITABLE (widget), -1); - - g_free (str); - } - } - else - retval = FALSE; - } - else if (completion->priv->current_selected - matches >= 0) - { - sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (completion->priv->action_view)); - if (gtk_tree_selection_get_selected (sel, &model, &iter)) - { - GtkTreePath *path; - - path = gtk_tree_path_new_from_indices (completion->priv->current_selected - matches, -1); - g_signal_emit_by_name (completion, "action-activated", - gtk_tree_path_get_indices (path)[0]); - gtk_tree_path_free (path); - } - else - retval = FALSE; - } - - g_free (completion->priv->completion_prefix); - completion->priv->completion_prefix = NULL; - - return retval; - } - - return FALSE; -} - -static void -gtk_entry_completion_changed (GtkWidget *widget, - gpointer user_data) -{ - GtkEntryCompletion *completion = GTK_ENTRY_COMPLETION (user_data); - GtkEntry *entry = GTK_ENTRY (widget); - GtkEntryPrivate *priv = entry->priv; - GdkDevice *device; - - /* (re)install completion timeout */ - if (completion->priv->completion_timeout) - g_source_remove (completion->priv->completion_timeout); - - if (!gtk_entry_get_text (entry)) - return; - - /* no need to normalize for this test */ - if (completion->priv->minimum_key_length > 0 && - strcmp ("", gtk_entry_get_text (entry)) == 0) - { - if (gtk_widget_get_visible (completion->priv->popup_window)) - _gtk_entry_completion_popdown (completion); - return; - } - - device = gtk_get_current_event_device (); - - if (device && gdk_device_get_source (device) == GDK_SOURCE_KEYBOARD) - device = gdk_device_get_associated_device (device); - - if (device) - priv->completion_device = device; - - completion->priv->completion_timeout = - gdk_threads_add_timeout (COMPLETION_TIMEOUT, - gtk_entry_completion_timeout, - completion); -} - -static gboolean -check_completion_callback (GtkEntryCompletion *completion) -{ - completion->priv->check_completion_idle = NULL; - - gtk_entry_completion_complete (completion); - gtk_entry_completion_insert_prefix (completion); - - return FALSE; -} - -static void -clear_completion_callback (GtkEntry *entry, - GParamSpec *pspec) -{ - if (pspec->name == I_("cursor-position") || - pspec->name == I_("selection-bound")) - { - GtkEntryCompletion *completion = gtk_entry_get_completion (entry); - - completion->priv->has_completion = FALSE; - } -} - -static gboolean -accept_completion_callback (GtkEntry *entry) -{ - GtkEntryCompletion *completion = gtk_entry_get_completion (entry); - - if (completion->priv->has_completion) - gtk_editable_set_position (GTK_EDITABLE (entry), - gtk_entry_buffer_get_length (get_buffer (entry))); - - return FALSE; -} - -static void -completion_insert_text_callback (GtkEntry *entry, - const gchar *text, - gint length, - gint position, - GtkEntryCompletion *completion) -{ - /* idle to update the selection based on the file list */ - if (completion->priv->check_completion_idle == NULL) - { - completion->priv->check_completion_idle = g_idle_source_new (); - g_source_set_priority (completion->priv->check_completion_idle, G_PRIORITY_HIGH); - g_source_set_closure (completion->priv->check_completion_idle, - g_cclosure_new_object (G_CALLBACK (check_completion_callback), - G_OBJECT (completion))); - g_source_attach (completion->priv->check_completion_idle, NULL); - } -} - -static void -completion_changed (GtkEntryCompletion *completion, - GParamSpec *pspec, - gpointer data) -{ - GtkEntry *entry = GTK_ENTRY (data); - - if (pspec->name == I_("popup-completion") || - pspec->name == I_("inline-completion")) - { - disconnect_completion_signals (entry, completion); - connect_completion_signals (entry, completion); - } -} - -static void -disconnect_completion_signals (GtkEntry *entry, - GtkEntryCompletion *completion) -{ - g_signal_handlers_disconnect_by_func (completion, - G_CALLBACK (completion_changed), entry); - if (completion->priv->changed_id > 0 && - g_signal_handler_is_connected (entry, completion->priv->changed_id)) - { - g_signal_handler_disconnect (entry, completion->priv->changed_id); - completion->priv->changed_id = 0; - } - g_signal_handlers_disconnect_by_func (entry, - G_CALLBACK (gtk_entry_completion_key_press), completion); - if (completion->priv->insert_text_id > 0 && - g_signal_handler_is_connected (entry, completion->priv->insert_text_id)) - { - g_signal_handler_disconnect (entry, completion->priv->insert_text_id); - completion->priv->insert_text_id = 0; - } - g_signal_handlers_disconnect_by_func (entry, - G_CALLBACK (completion_insert_text_callback), completion); - g_signal_handlers_disconnect_by_func (entry, - G_CALLBACK (clear_completion_callback), completion); - g_signal_handlers_disconnect_by_func (entry, - G_CALLBACK (accept_completion_callback), completion); -} - -static void -connect_completion_signals (GtkEntry *entry, - GtkEntryCompletion *completion) -{ - if (completion->priv->popup_completion) - { - completion->priv->changed_id = - g_signal_connect (entry, "changed", - G_CALLBACK (gtk_entry_completion_changed), completion); - g_signal_connect (entry, "key-press-event", - G_CALLBACK (gtk_entry_completion_key_press), completion); - } - - if (completion->priv->inline_completion) - { - completion->priv->insert_text_id = - g_signal_connect (entry, "insert-text", - G_CALLBACK (completion_insert_text_callback), completion); - g_signal_connect (entry, "notify", - G_CALLBACK (clear_completion_callback), completion); - g_signal_connect (entry, "activate", - G_CALLBACK (accept_completion_callback), completion); - g_signal_connect (entry, "focus-out-event", - G_CALLBACK (accept_completion_callback), completion); - } - - g_signal_connect (completion, "notify", - G_CALLBACK (completion_changed), entry); -} - /** * gtk_entry_set_completion: * @entry: A #GtkEntry @@ -9791,24 +9935,7 @@ gtk_entry_set_completion (GtkEntry *entry, if (old) { - if (old->priv->completion_timeout) - { - g_source_remove (old->priv->completion_timeout); - old->priv->completion_timeout = 0; - } - - if (old->priv->check_completion_idle) - { - g_source_destroy (old->priv->check_completion_idle); - old->priv->check_completion_idle = NULL; - } - - if (gtk_widget_get_mapped (old->priv->popup_window)) - _gtk_entry_completion_popdown (old); - - disconnect_completion_signals (entry, old); - old->priv->entry = NULL; - + _gtk_entry_completion_disconnect (old); g_object_unref (old); } @@ -9821,8 +9948,8 @@ gtk_entry_set_completion (GtkEntry *entry, /* hook into the entry */ g_object_ref (completion); - connect_completion_signals (entry, completion); - completion->priv->entry = GTK_WIDGET (entry); + _gtk_entry_completion_connect (completion, entry); + g_object_set_data (G_OBJECT (entry), I_(GTK_ENTRY_COMPLETION_KEY), completion); g_object_notify (G_OBJECT (entry), "completion"); @@ -10185,12 +10312,7 @@ keymap_state_changed (GdkKeymap *keymap, if (gtk_entry_get_display_mode (entry) != DISPLAY_NORMAL && priv->caps_lock_warning) { - if (gdk_keymap_get_num_lock_state (keymap) - && gdk_keymap_get_caps_lock_state (keymap)) - text = _("Caps Lock and Num Lock are on"); - else if (gdk_keymap_get_num_lock_state (keymap)) - text = _("Num Lock is on"); - else if (gdk_keymap_get_caps_lock_state (keymap)) + if (gdk_keymap_get_caps_lock_state (keymap)) text = _("Caps Lock is on"); } @@ -10217,3 +10339,153 @@ _gtk_entry_set_is_cell_renderer (GtkEntry *entry, { entry->priv->is_cell_renderer = is_cell_renderer; } + +/** + * gtk_entry_set_input_purpose: + * @entry: a #GtkEntry + * @purpose: the purpose + * + * Sets the #GtkEntry:input-purpose property which + * can be used by on-screen keyboards and other input + * methods to adjust their behaviour. + * + * Since: 3.6 + */ +void +gtk_entry_set_input_purpose (GtkEntry *entry, + GtkInputPurpose purpose) + +{ + g_return_if_fail (GTK_IS_ENTRY (entry)); + + if (gtk_entry_get_input_purpose (entry) != purpose) + { + g_object_set (G_OBJECT (entry->priv->im_context), + "input-purpose", purpose, + NULL); + + g_object_notify (G_OBJECT (entry), "input-purpose"); + } +} + +/** + * gtk_entry_get_input_purpose: + * @entry: a #GtkEntry + * + * Gets the value of the #GtkEntry:input-purpose property. + * + * Since: 3.6 + */ +GtkInputPurpose +gtk_entry_get_input_purpose (GtkEntry *entry) +{ + GtkInputPurpose purpose; + + g_return_val_if_fail (GTK_IS_ENTRY (entry), GTK_INPUT_PURPOSE_FREE_FORM); + + g_object_get (G_OBJECT (entry->priv->im_context), + "input-purpose", &purpose, + NULL); + + return purpose; +} + +/** + * gtk_entry_set_input_hints: + * @entry: a #GtkEntry + * @hints: the hints + * + * Sets the #GtkEntry:input-hints property, which + * allows input methods to fine-tune their behaviour. + * + * Since: 3.6 + */ +void +gtk_entry_set_input_hints (GtkEntry *entry, + GtkInputHints hints) + +{ + g_return_if_fail (GTK_IS_ENTRY (entry)); + + if (gtk_entry_get_input_hints (entry) != hints) + { + g_object_set (G_OBJECT (entry->priv->im_context), + "input-hints", hints, + NULL); + + g_object_notify (G_OBJECT (entry), "input-hints"); + } +} + +/** + * gtk_entry_get_input_hints: + * @entry: a #GtkEntry + * + * Gets the value of the #GtkEntry:input-hints property. + * + * Since: 3.6 + */ +GtkInputHints +gtk_entry_get_input_hints (GtkEntry *entry) +{ + GtkInputHints hints; + + g_return_val_if_fail (GTK_IS_ENTRY (entry), GTK_INPUT_HINT_NONE); + + g_object_get (G_OBJECT (entry->priv->im_context), + "input-hints", &hints, + NULL); + + return hints; +} + +/** + * gtk_entry_set_attributes: + * @entry: a #GtkEntry + * @attrs: a #PangoAttrList + * + * Sets a #PangoAttrList; the attributes in the list are applied to the + * entry text. + * + * Since: 3.6 + */ +void +gtk_entry_set_attributes (GtkEntry *entry, + PangoAttrList *attrs) +{ + GtkEntryPrivate *priv = entry->priv; + 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 (G_OBJECT (entry), "attributes"); + + gtk_entry_recompute (entry); + gtk_widget_queue_resize (GTK_WIDGET (entry)); +} + +/** + * gtk_entry_get_attributes: + * @entry: a #GtkEntry + * + * Gets the attribute list that was set on the entry using + * gtk_entry_set_attributes(), if any. + * + * Return value: (transfer none): the attribute list, or %NULL + * if none was set. + * + * Since: 3.6 + */ +PangoAttrList * +gtk_entry_get_attributes (GtkEntry *entry) +{ + g_return_val_if_fail (GTK_IS_ENTRY (entry), NULL); + + return entry->priv->attrs; +} +