]> Pileus Git - ~andy/gtk/blobdiff - gtk/gtkentry.c
Add GtkBubbleWindow
[~andy/gtk] / gtk / gtkentry.c
index c3a50f1aa5f83fa84a1053ed79808e79275d37d2..de85158482632cb1e3b12c762a50bcbe808ac724 100644 (file)
@@ -65,6 +65,7 @@
 #include "gtkicontheme.h"
 #include "gtkwidgetprivate.h"
 #include "gtkstylecontextprivate.h"
+#include "gtktexthandleprivate.h"
 
 #include "a11y/gtkentryaccessible.h"
 
@@ -157,6 +158,8 @@ struct _GtkEntryPrivate
 
   gchar        *placeholder_text;
 
+  GtkTextHandle *text_handle;
+
   gfloat        xalign;
 
   gint          ascent;                     /* font ascent in pango units  */
@@ -213,6 +216,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 +318,7 @@ enum {
 };
 
 static guint signals[LAST_SIGNAL] = { 0 };
+static gboolean test_touchscreen = FALSE;
 
 typedef enum {
   CURSOR_STANDARD,
@@ -558,6 +564,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 +586,12 @@ 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         begin_change                       (GtkEntry *entry);
 static void         end_change                         (GtkEntry *entry);
@@ -693,6 +710,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 +1945,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 +2571,10 @@ 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);
 }
 
 static void
@@ -2788,6 +2811,7 @@ gtk_entry_finalize (GObject *object)
   if (priv->recompute_idle)
     g_source_remove (priv->recompute_idle);
 
+  g_object_unref (priv->text_handle);
   g_free (priv->placeholder_text);
   g_free (priv->im_module);
 
@@ -2949,7 +2973,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 +3033,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 +3100,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 +3109,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 +3137,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 +3145,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 +3164,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 +3217,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 +3265,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 +3394,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 +3413,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 +3422,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 +3437,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 +3744,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 +3904,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)
@@ -3919,6 +4073,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 +4157,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 +4173,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 +4186,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:
@@ -4087,9 +4257,16 @@ gtk_entry_button_release (GtkWidget      *widget,
   if (priv->in_drag)
     {
       gint tmp_pos = gtk_entry_find_position (entry, priv->drag_start_x);
+      GdkDevice *source;
 
       gtk_editable_set_position (GTK_EDITABLE (entry), tmp_pos);
 
+      source = gdk_event_get_source_device ((GdkEvent *) event);
+
+      if (test_touchscreen ||
+          gdk_device_get_source (source) == GDK_SOURCE_TOUCHSCREEN)
+        gtk_entry_update_handles (entry, GTK_TEXT_HANDLE_MODE_CURSOR);
+
       priv->in_drag = 0;
     }
 
@@ -4211,13 +4388,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 +4439,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 +4493,10 @@ gtk_entry_key_press (GtkWidget   *widget,
   gtk_entry_reset_blink_time (entry);
   gtk_entry_pend_cursor_blink (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 +4590,9 @@ gtk_entry_focus_out (GtkWidget     *widget,
   GtkEntryCompletion *completion;
   GdkKeymap *keymap;
 
+  _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 +4872,6 @@ gtk_entry_style_updated (GtkWidget *widget)
 
   gtk_entry_update_cached_style_values (entry);
 
-  gtk_entry_recompute (entry);
-
   icon_theme_changed (entry);
 }
 
@@ -5573,10 +5771,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 +5970,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 +6215,70 @@ 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;
+
+  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);
+    }
+}
+
 /**
  * gtk_entry_reset_im_context:
  * @entry: a #GtkEntry
@@ -6166,6 +6435,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 +6464,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 +6501,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 +6554,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 +6569,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 +6579,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 +6587,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 +8798,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");
 }
 
 /**