X-Git-Url: http://pileus.org/git/?a=blobdiff_plain;f=gtk%2Fgtkentry.c;h=a24b344cc6fd35599142f8eccd5014c4934030c8;hb=9f41970832b60f3cf6644dfbd154df7ec24f26ce;hp=c3a50f1aa5f83fa84a1053ed79808e79275d37d2;hpb=1ac2982265c87a6164a986b6df161b1af011c944;p=~andy%2Fgtk diff --git a/gtk/gtkentry.c b/gtk/gtkentry.c index c3a50f1aa..a24b344cc 100644 --- a/gtk/gtkentry.c +++ b/gtk/gtkentry.c @@ -65,6 +65,8 @@ #include "gtkicontheme.h" #include "gtkwidgetprivate.h" #include "gtkstylecontextprivate.h" +#include "gtktexthandleprivate.h" +#include "gtkselectionwindow.h" #include "a11y/gtkentryaccessible.h" @@ -157,6 +159,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 */ @@ -213,6 +220,8 @@ struct _GtkEntryPrivate guint select_words : 1; guint select_lines : 1; guint truncate_multiline : 1; + guint cursor_handle_dragged : 1; + guint selection_handle_dragged : 1; }; struct _EntryIconInfo @@ -313,6 +322,7 @@ enum { }; static guint signals[LAST_SIGNAL] = { 0 }; +static gboolean test_touchscreen = FALSE; typedef enum { CURSOR_STANDARD, @@ -558,6 +568,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, @@ -575,6 +590,18 @@ static GdkPixbuf * gtk_entry_ensure_pixbuf (GtkEntry *en static void gtk_entry_update_cached_style_values(GtkEntry *entry); static gboolean get_middle_click_paste (GtkEntry *entry); +/* 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); @@ -693,6 +720,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"); @@ -1927,6 +1955,7 @@ gtk_entry_class_init (GtkEntryClass *class) 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); } @@ -2552,6 +2581,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 @@ -2788,6 +2823,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); @@ -2949,7 +2988,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); } @@ -3009,6 +3048,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) @@ -3073,7 +3115,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); @@ -3082,7 +3124,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() @@ -3110,6 +3152,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)) @@ -3117,7 +3160,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; } @@ -3136,6 +3179,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; } @@ -3188,19 +3232,14 @@ gtk_entry_get_preferred_width (GtkWidget *widget, PangoFontMetrics *metrics; 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, &borders); @@ -3241,28 +3280,25 @@ gtk_entry_get_preferred_height (GtkWidget *widget, GtkEntryPrivate *priv = entry->priv; PangoFontMetrics *metrics; GtkBorder borders; - GtkStyleContext *style_context; - GtkStateFlags state; 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, &borders); + pango_layout_get_pixel_size (layout, NULL, &height); - height = PANGO_PIXELS (priv->ascent + priv->descent) + borders.top + borders.bottom; - - pango_font_metrics_unref (metrics); + height += borders.top + borders.bottom; *minimum = height; *natural = height; @@ -3373,12 +3409,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; @@ -3393,7 +3428,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) { @@ -3402,8 +3437,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) @@ -3418,6 +3452,36 @@ get_frame_size (GtkEntry *entry, } } +static void +get_frame_size (GtkEntry *entry, + gboolean relative_to_window, + gint *x, + gint *y, + gint *width, + gint *height) +{ + GtkEntryClass *class; + GtkWidget *widget = GTK_WIDGET (entry); + + g_return_if_fail (GTK_IS_ENTRY (entry)); + + class = GTK_ENTRY_GET_CLASS (entry); + + if (class->get_frame_size) + class->get_frame_size (entry, x, y, width, height); + + if (!relative_to_window) + { + GtkAllocation allocation; + gtk_widget_get_allocation (widget, &allocation); + + if (x) + *x -= allocation.x; + if (y) + *y -= allocation.y; + } +} + static void gtk_entry_size_allocate (GtkWidget *widget, GtkAllocation *allocation) @@ -3695,36 +3759,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; @@ -3851,7 +3919,108 @@ in_selection (GtkEntry *entry, g_free (ranges); return retval; } - + +static void +gtk_entry_move_handle (GtkEntry *entry, + GtkTextHandlePosition pos, + gint x, + gint y, + gint height) +{ + GtkEntryPrivate *priv = entry->priv; + + if (!_gtk_text_handle_get_is_dragged (priv->text_handle, pos) && + (x < 0 || x > gdk_window_get_width (priv->text_area))) + { + /* Hide the handle if it's not being manipulated + * and fell outside of the visible text area. + */ + _gtk_text_handle_set_visible (priv->text_handle, pos, FALSE); + } + else + { + GdkRectangle rect; + + rect.x = CLAMP (x, 0, gdk_window_get_width (priv->text_area)); + rect.y = y; + rect.width = 1; + rect.height = height; + + _gtk_text_handle_set_visible (priv->text_handle, pos, TRUE); + _gtk_text_handle_set_position (priv->text_handle, pos, &rect); + } +} + +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; + + layout = gtk_entry_ensure_layout (entry, FALSE); + text = pango_layout_get_text (layout); + index = g_utf8_offset_to_pointer (text, priv->selection_bound) - text; + pango_layout_index_to_pos (layout, index, &pos); + + if (gtk_widget_get_direction (GTK_WIDGET (entry)) == GTK_TEXT_DIR_RTL) + x = (pos.x + pos.width) / PANGO_SCALE; + else + x = pos.x / PANGO_SCALE; + + return x; +} + +static void +gtk_entry_update_handles (GtkEntry *entry, + GtkTextHandleMode mode) +{ + GtkEntryPrivate *priv = 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) @@ -3864,6 +4033,8 @@ gtk_entry_button_press (GtkWidget *widget, 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]; @@ -3919,6 +4090,12 @@ gtk_entry_button_press (GtkWidget *widget, 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; @@ -3997,7 +4174,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: @@ -4008,6 +4190,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: @@ -4018,6 +4203,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: @@ -4052,6 +4239,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++) @@ -4084,14 +4273,23 @@ 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; @@ -4211,13 +4409,22 @@ gtk_entry_motion_notify (GtkWidget *widget, } 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; @@ -4253,13 +4460,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; } @@ -4300,6 +4514,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)) @@ -4393,6 +4613,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)); @@ -4672,8 +4896,6 @@ gtk_entry_style_updated (GtkWidget *widget) gtk_entry_update_cached_style_values (entry); - gtk_entry_recompute (entry); - icon_theme_changed (entry); } @@ -5301,6 +5523,8 @@ gtk_entry_cut_clipboard (GtkEntry *entry) { gtk_widget_error_bell (GTK_WIDGET (entry)); } + + gtk_entry_selection_bubble_popup_unset (entry); } static void @@ -5573,10 +5797,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; @@ -5765,7 +5996,7 @@ get_layout_position (GtkEntry *entry, layout = gtk_entry_ensure_layout (entry, TRUE); - gtk_entry_get_text_area_size (entry, NULL, NULL, &area_width, &area_height); + 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; @@ -6010,6 +6241,81 @@ gtk_entry_draw_cursor (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; + + gtk_entry_selection_bubble_popup_unset (entry); + + cursor_pos = priv->current_pos; + selection_bound_pos = priv->selection_bound; + mode = _gtk_text_handle_get_mode (handle); + tmp_pos = gtk_entry_find_position (entry, x + priv->scroll_offset); + + if (mode == GTK_TEXT_HANDLE_MODE_CURSOR || + cursor_pos >= selection_bound_pos) + { + max = &cursor_pos; + min = &selection_bound_pos; + } + else + { + max = &selection_bound_pos; + min = &cursor_pos; + } + + if (pos == GTK_TEXT_HANDLE_POSITION_SELECTION_END) + { + if (mode == GTK_TEXT_HANDLE_MODE_SELECTION) + { + gint min_pos; + + min_pos = MAX (*min + 1, 0); + tmp_pos = MAX (tmp_pos, min_pos); + } + + *max = tmp_pos; + } + else + { + if (mode == GTK_TEXT_HANDLE_MODE_SELECTION) + { + gint max_pos; + + max_pos = *max - 1; + *min = MIN (tmp_pos, max_pos); + } + } + + if (cursor_pos != priv->current_pos || + selection_bound_pos != priv->selection_bound) + { + if (mode == GTK_TEXT_HANDLE_MODE_CURSOR) + 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 @@ -6166,6 +6472,23 @@ 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) { @@ -6178,6 +6501,7 @@ gtk_entry_adjust_scroll (GtkEntry *entry) PangoLayout *layout; PangoLayoutLine *line; PangoRectangle logical_rect; + GtkTextHandleMode handle_mode; if (!gtk_widget_get_realized (GTK_WIDGET (entry))) return; @@ -6214,22 +6538,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) @@ -6256,6 +6591,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 @@ -6266,8 +6606,6 @@ gtk_entry_move_adjustments (GtkEntry *entry) GtkAdjustment *adjustment; PangoContext *context; PangoFontMetrics *metrics; - GtkStyleContext *style_context; - GtkStateFlags state; GtkBorder borders; gint x, layout_x; gint char_width; @@ -6278,7 +6616,7 @@ 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, &borders); @@ -6286,11 +6624,9 @@ gtk_entry_move_adjustments (GtkEntry *entry) /* 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; @@ -8499,6 +8835,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"); } /** @@ -8917,6 +9256,106 @@ gtk_entry_popup_menu (GtkWidget *widget) return TRUE; } +static gboolean +gtk_entry_selection_bubble_popup_cb (gpointer user_data) +{ + GtkEntry *entry = user_data; + GtkEntryPrivate *priv = entry->priv; + cairo_rectangle_int_t rect; + GtkAllocation allocation; + gint start_x, end_x; + gboolean has_selection; + + has_selection = gtk_editable_get_selection_bounds (GTK_EDITABLE (entry), + NULL, NULL); + if (!has_selection && !priv->editable) + { + priv->selection_bubble_timeout_id = 0; + return FALSE; + } + + if (priv->selection_bubble) + gtk_widget_destroy (priv->selection_bubble); + + priv->selection_bubble = gtk_selection_window_new (); + g_signal_connect_swapped (priv->selection_bubble, "cut", + G_CALLBACK (gtk_entry_cut_clipboard), + entry); + g_signal_connect_swapped (priv->selection_bubble, "copy", + G_CALLBACK (gtk_entry_copy_clipboard), + entry); + g_signal_connect_swapped (priv->selection_bubble, "paste", + G_CALLBACK (gtk_entry_paste_clipboard), + entry); + + gtk_selection_window_set_editable (GTK_SELECTION_WINDOW (priv->selection_bubble), + priv->editable); + gtk_selection_window_set_has_selection (GTK_SELECTION_WINDOW (priv->selection_bubble), + has_selection); + + 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; + return FALSE; +} + +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_seconds (1, gtk_entry_selection_bubble_popup_cb, + entry); +} + static void gtk_entry_drag_begin (GtkWidget *widget, GdkDragContext *context)