]> Pileus Git - ~andy/gtk/blobdiff - gtk/gtktextview.c
stylecontext: Do invalidation on first resize container
[~andy/gtk] / gtk / gtktextview.c
index dc9addc478ac081ad019a83caaada947ae309b8c..2de8a1e623be9f15d7b3bf23ead0c54b9a9580cb 100644 (file)
@@ -13,9 +13,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 <http://www.gnu.org/licenses/>.
  */
 
 /*
 #include "gtkwindow.h"
 #include "gtkscrollable.h"
 #include "gtktypebuiltins.h"
+#include "gtktexthandleprivate.h"
+#include "gtkstylecontextprivate.h"
+#include "gtkcssstylepropertyprivate.h"
+#include "gtkbubblewindowprivate.h"
+#include "gtktoolbar.h"
 
+#include "a11y/gtktextviewaccessibleprivate.h"
 
 /**
  * SECTION:gtktextview
@@ -133,6 +137,9 @@ struct _GtkTextViewPrivate
   GdkDevice *dnd_device;
 
   gulong selection_drag_handler;
+  GtkTextHandle *text_handle;
+  GtkWidget *selection_bubble;
+  guint selection_bubble_timeout_id;
 
   GtkTextWindow *text_window;
   GtkTextWindow *left_window;
@@ -230,6 +237,9 @@ struct _GtkTextViewPrivate
    * driving the scrollable adjustment values */
   guint hscroll_policy : 1;
   guint vscroll_policy : 1;
+  guint cursor_handle_dragged : 1;
+  guint selection_handle_dragged : 1;
+  guint populate_all   : 1;
 };
 
 struct _GtkTextPendingScroll
@@ -282,7 +292,10 @@ enum
   PROP_HADJUSTMENT,
   PROP_VADJUSTMENT,
   PROP_HSCROLL_POLICY,
-  PROP_VSCROLL_POLICY
+  PROP_VSCROLL_POLICY,
+  PROP_INPUT_PURPOSE,
+  PROP_INPUT_HINTS,
+  PROP_POPULATE_ALL
 };
 
 static void gtk_text_view_finalize             (GObject          *object);
@@ -339,7 +352,7 @@ static gboolean gtk_text_view_focus            (GtkWidget        *widget,
                                                 GtkDirectionType  direction);
 static void gtk_text_view_select_all           (GtkWidget        *widget,
                                                 gboolean          select);
-
+static gboolean get_middle_click_paste         (GtkTextView      *text_view);
 
 /* Source side drag signals */
 static void gtk_text_view_drag_begin       (GtkWidget        *widget,
@@ -453,6 +466,8 @@ static void gtk_text_view_target_list_notify     (GtkTextBuffer     *buffer,
 static void gtk_text_view_paste_done_handler     (GtkTextBuffer     *buffer,
                                                   GtkClipboard      *clipboard,
                                                   gpointer           data);
+static void gtk_text_view_buffer_changed_handler (GtkTextBuffer     *buffer,
+                                                  gpointer           data);
 static void gtk_text_view_get_virtual_cursor_pos (GtkTextView       *text_view,
                                                   GtkTextIter       *cursor,
                                                   gint              *x,
@@ -496,6 +511,22 @@ static void gtk_text_view_forall (GtkContainer *container,
                                   GtkCallback   callback,
                                   gpointer      callback_data);
 
+/* GtkTextHandle handlers */
+static void gtk_text_view_handle_dragged       (GtkTextHandle         *handle,
+                                                GtkTextHandlePosition  pos,
+                                                gint                   x,
+                                                gint                   y,
+                                                GtkTextView           *text_view);
+static void gtk_text_view_handle_drag_finished (GtkTextHandle         *handle,
+                                                GtkTextHandlePosition  pos,
+                                                GtkTextView           *text_view);
+static void gtk_text_view_update_handles       (GtkTextView           *text_view,
+                                                GtkTextHandleMode      mode);
+
+static void gtk_text_view_selection_bubble_popup_unset (GtkTextView *text_view);
+static void gtk_text_view_selection_bubble_popup_set   (GtkTextView *text_view);
+
+
 /* FIXME probably need the focus methods. */
 
 typedef struct _GtkTextViewChild GtkTextViewChild;
@@ -558,6 +589,7 @@ static gint           text_window_get_height      (GtkTextWindow     *win);
 
 
 static guint signals[LAST_SIGNAL] = { 0 };
+static gboolean test_touchscreen = FALSE;
 
 G_DEFINE_TYPE_WITH_CODE (GtkTextView, gtk_text_view, GTK_TYPE_CONTAINER,
                         G_IMPLEMENT_INTERFACE (GTK_TYPE_SCROLLABLE, NULL))
@@ -778,7 +810,7 @@ gtk_text_view_class_init (GtkTextViewClass *klass)
    /**
     * GtkTextView:im-module:
     *
-    * Which IM (input method) module should be used for this entry
+    * Which IM (input method) module should be used for this text_view
     * See #GtkIMContext.
     *
     * Setting this to a non-%NULL value overrides the
@@ -795,6 +827,58 @@ gtk_text_view_class_init (GtkTextViewClass *klass)
                                                          NULL,
                                                          GTK_PARAM_READWRITE));
 
+  /**
+   * GtkTextView: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.
+   *
+   * 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));
+
+  /**
+   * GtkTextView:input-hints:
+   *
+   * Additional hints (beyond #GtkTextView: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));
+
+  /** GtkTextView:populate-all:
+   *
+   * If ::populate-all is %TRUE, the #GtkTextView::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));
+
+
    /* GtkScrollable interface */
    g_object_class_override_property (gobject_class, PROP_HADJUSTMENT,    "hadjustment");
    g_object_class_override_property (gobject_class, PROP_VADJUSTMENT,    "vadjustment");
@@ -1056,14 +1140,23 @@ gtk_text_view_class_init (GtkTextViewClass *klass)
 
   /**
    * GtkTextView::populate-popup:
-   * @entry: The text view on which the signal is emitted
-   * @menu: the menu that is being populated
+   * @text_view: The text view on which the signal is emitted
+   * @popup: the container that is being populated
    *
-   * The ::populate-popup signal gets emitted before showing the 
+   * The ::populate-popup signal gets emitted before showing the
    * context menu of the text view.
    *
    * 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 @popup, which
+   * will be a #GtkMenu in this case.
+   *
+   * If #GtkEntry::populate-toolbar is %TRUE, this signal will
+   * also be emitted to populate touch popups. In this case,
+   * @popup will be a different container, e.g. a #GtkToolbar.
+   *
+   * The signal handler should not make assumptions about the
+   * type of @widget, but check whether @popup is a #GtkMenu
+   * or #GtkToolbar or another kind of container.
    */
   signals[POPULATE_POPUP] =
     g_signal_new (I_("populate-popup"),
@@ -1073,7 +1166,7 @@ gtk_text_view_class_init (GtkTextViewClass *klass)
                  NULL, NULL,
                  _gtk_marshal_VOID__OBJECT,
                  G_TYPE_NONE, 1,
-                 GTK_TYPE_MENU);
+                 GTK_TYPE_WIDGET);
   
   /**
    * GtkTextView::select-all:
@@ -1351,6 +1444,9 @@ gtk_text_view_class_init (GtkTextViewClass *klass)
                                GTK_TYPE_DIRECTION_TYPE, GTK_DIR_TAB_BACKWARD);
 
   g_type_class_add_private (gobject_class, sizeof (GtkTextViewPrivate));
+
+  gtk_widget_class_set_accessible_type (widget_class, GTK_TYPE_TEXT_VIEW_ACCESSIBLE);
+  test_touchscreen = g_getenv ("GTK_TEST_TOUCHSCREEN") != NULL;
 }
 
 static void
@@ -1417,6 +1513,12 @@ gtk_text_view_init (GtkTextView *text_view)
 
   /* We handle all our own redrawing */
   gtk_widget_set_redraw_on_allocate (widget, FALSE);
+
+  priv->text_handle = _gtk_text_handle_new (widget);
+  g_signal_connect (priv->text_handle, "handle-dragged",
+                    G_CALLBACK (gtk_text_view_handle_dragged), text_view);
+  g_signal_connect (priv->text_handle, "drag-finished",
+                    G_CALLBACK (gtk_text_view_handle_drag_finished), text_view);
 }
 
 /**
@@ -1476,6 +1578,7 @@ gtk_text_view_set_buffer (GtkTextView   *text_view,
                           GtkTextBuffer *buffer)
 {
   GtkTextViewPrivate *priv;
+  GtkTextBuffer *old_buffer;
 
   g_return_if_fail (GTK_IS_TEXT_VIEW (text_view));
   g_return_if_fail (buffer == NULL || GTK_IS_TEXT_BUFFER (buffer));
@@ -1485,6 +1588,7 @@ gtk_text_view_set_buffer (GtkTextView   *text_view,
   if (priv->buffer == buffer)
     return;
 
+  old_buffer = priv->buffer;
   if (priv->buffer != NULL)
     {
       /* Destroy all anchored children */
@@ -1517,6 +1621,9 @@ gtk_text_view_set_buffer (GtkTextView   *text_view,
       g_signal_handlers_disconnect_by_func (priv->buffer,
                                             gtk_text_view_paste_done_handler,
                                             text_view);
+      g_signal_handlers_disconnect_by_func (priv->buffer,
+                                            gtk_text_view_buffer_changed_handler,
+                                            text_view);
 
       if (gtk_widget_get_realized (GTK_WIDGET (text_view)))
        {
@@ -1528,7 +1635,6 @@ gtk_text_view_set_buffer (GtkTextView   *text_view,
       if (priv->layout)
         gtk_text_layout_set_buffer (priv->layout, NULL);
 
-      g_object_unref (priv->buffer);
       priv->dnd_mark = NULL;
       priv->first_para_mark = NULL;
       cancel_pending_scroll (text_view);
@@ -1567,6 +1673,9 @@ gtk_text_view_set_buffer (GtkTextView   *text_view,
       g_signal_connect (priv->buffer, "paste-done",
                        G_CALLBACK (gtk_text_view_paste_done_handler),
                         text_view);
+      g_signal_connect (priv->buffer, "changed",
+                       G_CALLBACK (gtk_text_view_buffer_changed_handler),
+                        text_view);
 
       gtk_text_view_target_list_notify (priv->buffer, NULL, text_view);
 
@@ -1576,8 +1685,14 @@ gtk_text_view_set_buffer (GtkTextView   *text_view,
                                                              GDK_SELECTION_PRIMARY);
          gtk_text_buffer_add_selection_clipboard (priv->buffer, clipboard);
        }
+
+      gtk_text_view_update_handles (text_view, GTK_TEXT_HANDLE_MODE_NONE);
     }
 
+  _gtk_text_view_accessible_set_buffer (text_view, old_buffer);
+  if (old_buffer)
+    g_object_unref (old_buffer);
+
   g_object_notify (G_OBJECT (text_view), "buffer");
   
   if (gtk_widget_get_visible (GTK_WIDGET (text_view)))
@@ -3062,6 +3177,10 @@ gtk_text_view_finalize (GObject *object)
   if (priv->bottom_window)
     text_window_free (priv->bottom_window);
 
+  if (priv->selection_bubble)
+    gtk_widget_destroy (priv->selection_bubble);
+
+  g_object_unref (priv->text_handle);
   g_object_unref (priv->im_context);
 
   g_free (priv->im_module);
@@ -3164,6 +3283,18 @@ gtk_text_view_set_property (GObject         *object,
       gtk_widget_queue_resize (GTK_WIDGET (text_view));
       break;
 
+    case PROP_INPUT_PURPOSE:
+      gtk_text_view_set_input_purpose (text_view, g_value_get_enum (value));
+      break;
+
+    case PROP_INPUT_HINTS:
+      gtk_text_view_set_input_hints (text_view, g_value_get_flags (value));
+      break;
+
+    case PROP_POPULATE_ALL:
+      text_view->priv->populate_all = g_value_get_boolean (value);
+      break;
+
     default:
       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
       break;
@@ -3260,6 +3391,18 @@ gtk_text_view_get_property (GObject         *object,
       g_value_set_enum (value, priv->vscroll_policy);
       break;
 
+    case PROP_INPUT_PURPOSE:
+      g_value_set_enum (value, gtk_text_view_get_input_purpose (text_view));
+      break;
+
+    case PROP_INPUT_HINTS:
+      g_value_set_flags (value, gtk_text_view_get_input_hints (text_view));
+      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;
@@ -3976,12 +4119,10 @@ gtk_text_view_realize (GtkWidget *widget)
   GtkTextView *text_view;
   GtkTextViewPrivate *priv;
   GtkStyleContext *context;
-  GtkStateFlags state;
   GdkWindow *window;
   GdkWindowAttr attributes;
   gint attributes_mask;
   GSList *tmp_list;
-  GdkRGBA color;
 
   text_view = GTK_TEXT_VIEW (widget);
   priv = text_view->priv;
@@ -4004,13 +4145,14 @@ gtk_text_view_realize (GtkWidget *widget)
   window = gdk_window_new (gtk_widget_get_parent_window (widget),
                            &attributes, attributes_mask);
   gtk_widget_set_window (widget, window);
-  gdk_window_set_user_data (window, widget);
+  gtk_widget_register_window (widget, window);
 
   context = gtk_widget_get_style_context (widget);
-  state = gtk_widget_get_state_flags (widget);
 
-  gtk_style_context_get_background_color (context, state, &color);
-  gdk_window_set_background_rgba (window, &color);
+  gtk_style_context_save (context);
+  gtk_style_context_add_class (context, GTK_STYLE_CLASS_VIEW);
+  gtk_style_context_set_background (context, window);
+  gtk_style_context_restore (context);
 
   text_window_realize (priv->text_window, widget);
 
@@ -4048,6 +4190,8 @@ gtk_text_view_realize (GtkWidget *widget)
 
   /* Ensure updating the spot location. */
   gtk_text_view_update_im_spot_location (text_view);
+
+  _gtk_text_handle_set_relative_to (priv->text_handle, priv->text_window->window);
 }
 
 static void
@@ -4088,6 +4232,8 @@ gtk_text_view_unrealize (GtkWidget *widget)
   if (priv->bottom_window)
     text_window_unrealize (priv->bottom_window);
 
+  _gtk_text_handle_set_relative_to (priv->text_handle, NULL);
+
   GTK_WIDGET_CLASS (gtk_text_view_parent_class)->unrealize (widget);
 }
 
@@ -4110,16 +4256,14 @@ gtk_text_view_set_background (GtkTextView *text_view)
   gtk_style_context_save (context);
   gtk_style_context_add_class (context, GTK_STYLE_CLASS_VIEW);
 
-  gtk_style_context_get_background_color (context, state, &color);
-  gdk_window_set_background_rgba (priv->text_window->bin_window, &color);
+  gtk_style_context_set_background (context, priv->text_window->bin_window);
+  gtk_style_context_set_background (context, gtk_widget_get_window (widget));
 
   gtk_style_context_restore (context);
 
   /* Set lateral panes background */
   gtk_style_context_get_background_color (context, state, &color);
 
-  gdk_window_set_background_rgba (gtk_widget_get_window (widget), &color);
-
   if (priv->left_window)
     gdk_window_set_background_rgba (priv->left_window->bin_window, &color);
 
@@ -4139,6 +4283,8 @@ gtk_text_view_style_updated (GtkWidget *widget)
   GtkTextView *text_view;
   GtkTextViewPrivate *priv;
   PangoContext *ltr_context, *rtl_context;
+  GtkStyleContext *style_context;
+  const GtkBitmask *changes;
 
   text_view = GTK_TEXT_VIEW (widget);
   priv = text_view->priv;
@@ -4150,7 +4296,11 @@ gtk_text_view_style_updated (GtkWidget *widget)
       gtk_text_view_set_background (text_view);
     }
 
-  if (priv->layout && priv->layout->default_style)
+
+  style_context = gtk_widget_get_style_context (widget);
+  changes = _gtk_style_context_get_changes (style_context);
+  if ((changes == NULL || _gtk_css_style_property_changes_affect_font (changes)) &&
+      priv->layout && priv->layout->default_style)
     {
       gtk_text_view_set_attributes_from_style (text_view,
                                                priv->layout->default_style);
@@ -4265,8 +4415,15 @@ gtk_text_view_grab_notify (GtkWidget *widget,
   if (priv->grab_device &&
       gtk_widget_device_is_shadowed (widget, priv->grab_device))
     {
+      if (priv->drag_start_x >= 0)
+        {
+          priv->drag_start_x = -1;
+          priv->drag_start_y = -1;
+        }
+
       gtk_text_view_end_selection_drag (GTK_TEXT_VIEW (widget));
       gtk_text_view_unobscure_mouse_cursor (GTK_TEXT_VIEW (widget));
+      priv->grab_device = NULL;
     }
 }
 
@@ -4350,6 +4507,183 @@ emit_event_on_tags (GtkWidget   *widget,
   return retval;
 }
 
+static void
+gtk_text_view_set_handle_position (GtkTextView           *text_view,
+                                   GtkTextIter           *iter,
+                                   GtkTextHandlePosition  pos)
+{
+  GtkTextViewPrivate *priv;
+  GdkRectangle rect;
+  gint x, y;
+
+  priv = text_view->priv;
+  gtk_text_view_get_cursor_locations (text_view, iter, &rect, NULL);
+
+  x = rect.x - priv->xoffset;
+  y = rect.y - priv->yoffset;
+
+  if (!_gtk_text_handle_get_is_dragged (priv->text_handle, pos) &&
+      (x < 0 || x > SCREEN_WIDTH (text_view) ||
+       y < 0 || y > SCREEN_HEIGHT (text_view)))
+    {
+      /* 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
+    {
+      _gtk_text_handle_set_visible (priv->text_handle, pos, TRUE);
+
+      rect.x = CLAMP (x, 0, SCREEN_WIDTH (text_view));
+      rect.y = CLAMP (y, 0, SCREEN_HEIGHT (text_view));
+      _gtk_text_handle_set_position (priv->text_handle, pos, &rect);
+    }
+}
+
+static void
+gtk_text_view_handle_dragged (GtkTextHandle         *handle,
+                              GtkTextHandlePosition  pos,
+                              gint                   x,
+                              gint                   y,
+                              GtkTextView           *text_view)
+{
+  GtkTextViewPrivate *priv;
+  GtkTextIter old_cursor, old_bound;
+  GtkTextIter cursor, bound, iter;
+  GtkTextIter *min, *max;
+  GtkTextHandleMode mode;
+  GtkTextBuffer *buffer;
+  GtkTextHandlePosition cursor_pos;
+
+  priv = text_view->priv;
+  buffer = get_buffer (text_view);
+  mode = _gtk_text_handle_get_mode (handle);
+
+  gtk_text_view_selection_bubble_popup_unset (text_view);
+  gtk_text_layout_get_iter_at_pixel (priv->layout, &iter,
+                                     x + priv->xoffset,
+                                     y + priv->yoffset);
+  gtk_text_buffer_get_iter_at_mark (buffer, &old_cursor,
+                                    gtk_text_buffer_get_insert (buffer));
+  gtk_text_buffer_get_iter_at_mark (buffer, &old_bound,
+                                    gtk_text_buffer_get_selection_bound (buffer));
+  cursor = old_cursor;
+  bound = old_bound;
+
+  if (mode == GTK_TEXT_HANDLE_MODE_CURSOR ||
+      gtk_text_iter_compare (&cursor, &bound) >= 0)
+    {
+      cursor_pos = GTK_TEXT_HANDLE_POSITION_CURSOR;
+      max = &cursor;
+      min = &bound;
+    }
+  else
+    {
+      cursor_pos = GTK_TEXT_HANDLE_POSITION_SELECTION_START;
+      max = &bound;
+      min = &cursor;
+    }
+
+  if (pos == GTK_TEXT_HANDLE_POSITION_SELECTION_END)
+    {
+      if (mode == GTK_TEXT_HANDLE_MODE_SELECTION &&
+         gtk_text_iter_compare (&iter, min) <= 0)
+        {
+          iter = *min;
+          gtk_text_iter_forward_char (&iter);
+        }
+
+      *max = iter;
+      gtk_text_view_set_handle_position (text_view, &iter, pos);
+    }
+  else
+    {
+      if (mode == GTK_TEXT_HANDLE_MODE_SELECTION &&
+         gtk_text_iter_compare (&iter, max) >= 0)
+        {
+          iter = *max;
+          gtk_text_iter_backward_char (&iter);
+        }
+
+      *min = iter;
+      gtk_text_view_set_handle_position (text_view, &iter, pos);
+    }
+
+  if (gtk_text_iter_compare (&old_cursor, &cursor) != 0 ||
+      gtk_text_iter_compare (&old_bound, &bound) != 0)
+    {
+      if (mode == GTK_TEXT_HANDLE_MODE_CURSOR)
+        gtk_text_buffer_place_cursor (buffer, &cursor);
+      else
+        gtk_text_buffer_select_range (buffer, &cursor, &bound);
+
+      if (_gtk_text_handle_get_is_dragged (priv->text_handle, cursor_pos))
+        gtk_text_view_scroll_mark_onscreen (text_view,
+                                            gtk_text_buffer_get_insert (buffer));
+      else
+        gtk_text_view_scroll_mark_onscreen (text_view,
+                                            gtk_text_buffer_get_selection_bound (buffer));
+    }
+}
+
+static void
+gtk_text_view_handle_drag_finished (GtkTextHandle         *handle,
+                                    GtkTextHandlePosition  pos,
+                                    GtkTextView           *text_view)
+{
+  gtk_text_view_selection_bubble_popup_set (text_view);
+}
+
+static void
+gtk_text_view_update_handles (GtkTextView       *text_view,
+                              GtkTextHandleMode  mode)
+{
+  GtkTextViewPrivate *priv = text_view->priv;
+  GtkTextIter cursor, bound, min, max;
+  GtkTextBuffer *buffer;
+
+  buffer = get_buffer (text_view);
+
+  gtk_text_buffer_get_iter_at_mark (buffer, &cursor,
+                                    gtk_text_buffer_get_insert (buffer));
+  gtk_text_buffer_get_iter_at_mark (buffer, &bound,
+                                    gtk_text_buffer_get_selection_bound (buffer));
+
+  if (mode == GTK_TEXT_HANDLE_MODE_SELECTION &&
+      gtk_text_iter_compare (&cursor, &bound) == 0)
+    {
+      mode = GTK_TEXT_HANDLE_MODE_CURSOR;
+    }
+
+  if (mode == GTK_TEXT_HANDLE_MODE_CURSOR &&
+      (!gtk_widget_is_sensitive (GTK_WIDGET (text_view)) || !priv->cursor_visible))
+    {
+      mode = GTK_TEXT_HANDLE_MODE_NONE;
+    }
+
+  _gtk_text_handle_set_mode (priv->text_handle, mode);
+
+  if (gtk_text_iter_compare (&cursor, &bound) >= 0)
+    {
+      min = bound;
+      max = cursor;
+    }
+  else
+    {
+      min = cursor;
+      max = bound;
+    }
+
+  if (mode != GTK_TEXT_HANDLE_MODE_NONE)
+    gtk_text_view_set_handle_position (text_view, &max,
+                                       GTK_TEXT_HANDLE_POSITION_SELECTION_END);
+
+  if (mode == GTK_TEXT_HANDLE_MODE_SELECTION)
+    gtk_text_view_set_handle_position (text_view, &min,
+                                       GTK_TEXT_HANDLE_POSITION_SELECTION_START);
+}
+
 static gint
 gtk_text_view_event (GtkWidget *widget, GdkEvent *event)
 {
@@ -4481,6 +4815,12 @@ gtk_text_view_key_press_event (GtkWidget *widget, GdkEventKey *event)
   gtk_text_view_reset_blink_time (text_view);
   gtk_text_view_pend_cursor_blink (text_view);
 
+  if (!event->send_event)
+    _gtk_text_handle_set_mode (priv->text_handle,
+                               GTK_TEXT_HANDLE_MODE_NONE);
+
+  gtk_text_view_selection_bubble_popup_unset (text_view);
+
   return retval;
 }
 
@@ -4515,6 +4855,8 @@ gtk_text_view_button_press_event (GtkWidget *widget, GdkEventButton *event)
 {
   GtkTextView *text_view;
   GtkTextViewPrivate *priv;
+  GdkDevice *device;
+  gboolean is_touchscreen;
 
   text_view = GTK_TEXT_VIEW (widget);
   priv = text_view->priv;
@@ -4529,20 +4871,30 @@ gtk_text_view_button_press_event (GtkWidget *widget, GdkEventButton *event)
     }
 
   gtk_text_view_reset_blink_time (text_view);
+  gtk_text_view_selection_bubble_popup_unset (text_view);
 
 #if 0
   /* debug hack */
-  if (event->button == 3 && (event->state & GDK_CONTROL_MASK) != 0)
+  if (event->button == GDK_BUTTON_SECONDARY && (event->state & GDK_CONTROL_MASK) != 0)
     _gtk_text_buffer_spew (GTK_TEXT_VIEW (widget)->buffer);
-  else if (event->button == 3)
+  else if (event->button == GDK_BUTTON_SECONDARY)
     gtk_text_layout_spew (GTK_TEXT_VIEW (widget)->layout);
 #endif
 
+  device = gdk_event_get_source_device ((GdkEvent *) event);
+  is_touchscreen = test_touchscreen ||
+                   gdk_device_get_source (device) == GDK_SOURCE_TOUCHSCREEN;
+
   if (event->type == GDK_BUTTON_PRESS)
     {
       gtk_text_view_reset_im_context (text_view);
 
-      if (event->button == 1)
+      if (gdk_event_triggers_context_menu ((GdkEvent *) event))
+        {
+         gtk_text_view_do_popup (text_view, event);
+         return TRUE;
+        }
+      else if (event->button == GDK_BUTTON_PRIMARY)
         {
           /* If we're in the selection, start a drag copy/move of the
            * selection; otherwise, start creating a new selection.
@@ -4558,20 +4910,33 @@ gtk_text_view_button_press_event (GtkWidget *widget, GdkEventButton *event)
           if (gtk_text_buffer_get_selection_bounds (get_buffer (text_view),
                                                     &start, &end) &&
               gtk_text_iter_in_range (&iter, &start, &end) &&
-              !(event->state & GDK_SHIFT_MASK))
+              !(event->state &
+                gtk_widget_get_modifier_mask (widget,
+                                              GDK_MODIFIER_INTENT_EXTEND_SELECTION)))
             {
+              priv->grab_device = event->device;
               priv->drag_start_x = event->x;
               priv->drag_start_y = event->y;
               priv->pending_place_cursor_button = event->button;
             }
           else
             {
+              GtkTextHandleMode mode;
+
               gtk_text_view_start_selection_drag (text_view, &iter, event);
+
+              if (gtk_widget_is_sensitive (widget) && is_touchscreen)
+                mode = GTK_TEXT_HANDLE_MODE_CURSOR;
+              else
+                mode = GTK_TEXT_HANDLE_MODE_NONE;
+
+              gtk_text_view_update_handles (text_view, mode);
             }
 
           return TRUE;
         }
-      else if (event->button == 2)
+      else if (event->button == GDK_BUTTON_MIDDLE &&
+               get_middle_click_paste (text_view))
         {
           GtkTextIter iter;
 
@@ -4590,17 +4955,13 @@ gtk_text_view_button_press_event (GtkWidget *widget, GdkEventButton *event)
                                           priv->editable);
           return TRUE;
         }
-      else if (event->button == 3)
-        {
-         gtk_text_view_do_popup (text_view, event);
-         return TRUE;
-        }
     }
   else if ((event->type == GDK_2BUTTON_PRESS ||
            event->type == GDK_3BUTTON_PRESS) &&
-          event->button == 1) 
+          event->button == GDK_BUTTON_PRIMARY)
     {
       GtkTextIter iter;
+      GtkTextHandleMode mode;
 
       gtk_text_view_end_selection_drag (text_view);
 
@@ -4608,11 +4969,18 @@ gtk_text_view_button_press_event (GtkWidget *widget, GdkEventButton *event)
                                         &iter,
                                         event->x + priv->xoffset,
                                         event->y + priv->yoffset);
-      
+
       gtk_text_view_start_selection_drag (text_view, &iter, event);
+
+      if (gtk_widget_is_sensitive (widget) && is_touchscreen)
+        mode = GTK_TEXT_HANDLE_MODE_SELECTION;
+      else
+        mode = GTK_TEXT_HANDLE_MODE_NONE;
+
+      gtk_text_view_update_handles (text_view, mode);
       return TRUE;
     }
-  
+
   return FALSE;
 }
 
@@ -4621,14 +4989,16 @@ gtk_text_view_button_release_event (GtkWidget *widget, GdkEventButton *event)
 {
   GtkTextView *text_view;
   GtkTextViewPrivate *priv;
+  GdkDevice *device;
 
   text_view = GTK_TEXT_VIEW (widget);
   priv = text_view->priv;
+  device = gdk_event_get_source_device ((GdkEvent *) event);
 
   if (event->window != priv->text_window->bin_window)
     return FALSE;
 
-  if (event->button == 1)
+  if (event->button == GDK_BUTTON_PRIMARY)
     {
       if (priv->drag_start_x >= 0)
         {
@@ -4637,9 +5007,15 @@ gtk_text_view_button_release_event (GtkWidget *widget, GdkEventButton *event)
         }
 
       if (gtk_text_view_end_selection_drag (GTK_TEXT_VIEW (widget)))
-        return TRUE;
+       {
+          if (test_touchscreen ||
+              gdk_device_get_source (device) == GDK_SOURCE_TOUCHSCREEN)
+           gtk_text_view_selection_bubble_popup_set (text_view);
+         return TRUE;
+       }
       else if (priv->pending_place_cursor_button == event->button)
         {
+          GtkTextHandleMode mode;
          GtkTextIter iter;
 
           /* Unselect everything; we clicked inside selection, but
@@ -4653,9 +5029,17 @@ gtk_text_view_button_release_event (GtkWidget *widget, GdkEventButton *event)
 
          gtk_text_buffer_place_cursor (get_buffer (text_view), &iter);
          gtk_text_view_check_cursor_blink (text_view);
-         
+
+          if (gtk_widget_is_sensitive (widget) &&
+              (test_touchscreen ||
+               gdk_device_get_source (device) == GDK_SOURCE_TOUCHSCREEN))
+           mode = GTK_TEXT_HANDLE_MODE_CURSOR;
+          else
+            mode = GTK_TEXT_HANDLE_MODE_NONE;
+
+          gtk_text_view_update_handles (text_view, mode);
           priv->pending_place_cursor_button = 0;
-          
+
           return FALSE;
         }
     }
@@ -4729,6 +5113,9 @@ gtk_text_view_focus_out_event (GtkWidget *widget, GdkEventFocus *event)
   g_signal_handlers_disconnect_by_func (gdk_keymap_get_for_display (gtk_widget_get_display (widget)),
                                        keymap_direction_changed,
                                        text_view);
+  gtk_text_view_selection_bubble_popup_unset (text_view);
+  _gtk_text_handle_set_mode (priv->text_handle,
+                             GTK_TEXT_HANDLE_MODE_NONE);
 
   if (priv->editable)
     {
@@ -4791,8 +5178,6 @@ gtk_text_view_paint (GtkWidget      *widget,
 {
   GtkTextView *text_view;
   GtkTextViewPrivate *priv;
-  GList *child_exposes;
-  GList *tmp_list;
   
   text_view = GTK_TEXT_VIEW (widget);
   priv = text_view->priv;
@@ -4820,33 +5205,15 @@ gtk_text_view_paint (GtkWidget      *widget,
           area->width, area->height);
 #endif
 
-  child_exposes = NULL;
-
   cairo_save (cr);
   cairo_translate (cr, -priv->xoffset, -priv->yoffset);
 
   gtk_text_layout_draw (priv->layout,
                         widget,
                         cr,
-                        &child_exposes);
+                        NULL);
 
   cairo_restore (cr);
-
-  tmp_list = child_exposes;
-  while (tmp_list != NULL)
-    {
-      GtkWidget *child = tmp_list->data;
-  
-      gtk_container_propagate_draw (GTK_CONTAINER (text_view),
-                                    child,
-                                    cr);
-
-      g_object_unref (child);
-      
-      tmp_list = tmp_list->next;
-    }
-
-  g_list_free (child_exposes);
 }
 
 static gboolean
@@ -4855,9 +5222,22 @@ gtk_text_view_draw (GtkWidget *widget,
 {
   GSList *tmp_list;
   GdkWindow *window;
-  
+  GtkStyleContext *context;
+
+  context = gtk_widget_get_style_context (widget);
+
   if (gtk_cairo_should_draw_window (cr, gtk_widget_get_window (widget)))
-    gtk_text_view_draw_focus (widget, cr);
+    {
+      gtk_style_context_save (context);
+      gtk_style_context_add_class (context, GTK_STYLE_CLASS_VIEW);
+      gtk_render_background (context, cr,
+                            0, 0,
+                            gtk_widget_get_allocated_width (widget),
+                            gtk_widget_get_allocated_height (widget));
+      gtk_style_context_restore (context);
+
+      gtk_text_view_draw_focus (widget, cr);
+    }
 
   window = gtk_text_view_get_window (GTK_TEXT_VIEW (widget),
                                      GTK_TEXT_WINDOW_TEXT);
@@ -4881,10 +5261,9 @@ gtk_text_view_draw (GtkWidget *widget,
       /* propagate_draw checks that event->window matches
        * child->window
        */
-      if (!vc->anchor)
-        gtk_container_propagate_draw (GTK_CONTAINER (widget),
-                                      vc->widget,
-                                      cr);
+      gtk_container_propagate_draw (GTK_CONTAINER (widget),
+                                    vc->widget,
+                                    cr);
       
       tmp_list = tmp_list->next;
     }
@@ -4903,7 +5282,7 @@ gtk_text_view_draw_focus (GtkWidget *widget,
                        "interior-focus", &interior_focus,
                        NULL);
   
-  if (gtk_widget_has_focus (widget) && !interior_focus)
+  if (gtk_widget_has_visible_focus (widget) && !interior_focus)
     {
       GtkStyleContext *context;
 
@@ -4921,24 +5300,31 @@ gtk_text_view_focus (GtkWidget        *widget,
 {
   GtkContainer *container;
   gboolean result;
-  
-  container = GTK_CONTAINER (widget);  
+
+  container = GTK_CONTAINER (widget);
 
   if (!gtk_widget_is_focus (widget) &&
       gtk_container_get_focus_child (container) == NULL)
     {
-      gtk_widget_grab_focus (widget);
-      return TRUE;
+      if (gtk_widget_get_can_focus (widget))
+        {
+          gtk_widget_grab_focus (widget);
+          return TRUE;
+        }
+
+      return FALSE;
     }
   else
     {
+      gboolean can_focus;
       /*
        * Unset CAN_FOCUS flag so that gtk_container_focus() allows
        * children to get the focus
        */
+      can_focus = gtk_widget_get_can_focus (widget);
       gtk_widget_set_can_focus (widget, FALSE);
       result = GTK_WIDGET_CLASS (gtk_text_view_parent_class)->focus (widget, direction);
-      gtk_widget_set_can_focus (widget, TRUE);
+      gtk_widget_set_can_focus (widget, can_focus);
 
       return result;
     }
@@ -5060,6 +5446,18 @@ cursor_blinks (GtkTextView *text_view)
   return FALSE;
 }
 
+static gboolean
+get_middle_click_paste (GtkTextView *text_view)
+{
+  GtkSettings *settings;
+  gboolean paste;
+
+  settings = gtk_widget_get_settings (GTK_WIDGET (text_view));
+  g_object_get (settings, "gtk-enable-primary-paste", &paste, NULL);
+
+  return paste;
+}
+
 static gint
 get_cursor_time (GtkTextView *text_view)
 {
@@ -5321,7 +5719,7 @@ gtk_text_view_move_cursor_internal (GtkTextView     *text_view,
       old_xpos = priv->xoffset;
       old_ypos = priv->yoffset;
       gtk_text_view_move_viewport (text_view, scroll_step, count);
-      if ((old_xpos != priv->xoffset || old_ypos != priv->yoffset) &&
+      if ((old_xpos == priv->xoffset && old_ypos == priv->yoffset) &&
           leave_direction != -1 &&
           !gtk_widget_keynav_failed (GTK_WIDGET (text_view),
                                      leave_direction))
@@ -5997,6 +6395,7 @@ gtk_text_view_cut_clipboard (GtkTextView *text_view)
   DV(g_print (G_STRLOC": scrolling onscreen\n"));
   gtk_text_view_scroll_mark_onscreen (text_view,
                                       gtk_text_buffer_get_insert (get_buffer (text_view)));
+  gtk_text_view_selection_bubble_popup_unset (text_view);
 }
 
 static void
@@ -6042,6 +6441,17 @@ gtk_text_view_paste_done_handler (GtkTextBuffer *buffer,
   priv->scroll_after_paste = TRUE;
 }
 
+static void
+gtk_text_view_buffer_changed_handler (GtkTextBuffer *buffer,
+                                      gpointer       data)
+{
+  GtkTextView *text_view = data;
+  GtkTextViewPrivate *priv = text_view->priv;
+
+  gtk_text_view_update_handles (text_view,
+                                _gtk_text_handle_get_mode (priv->text_handle));
+}
+
 static void
 gtk_text_view_toggle_overwrite (GtkTextView *text_view)
 {
@@ -6197,24 +6607,25 @@ get_iter_at_pointer (GtkTextView *text_view,
 }
 
 static void
-move_mark_to_pointer_and_scroll (GtkTextView *text_view,
-                                 const gchar *mark_name,
-                                 GdkDevice   *device)
+move_mark_to_pointer_and_scroll (GtkTextView    *text_view,
+                                 const gchar    *mark_name,
+                                 GdkDevice      *device,
+                                 GdkInputSource  source)
 {
   GtkTextIter newplace;
+  GtkTextBuffer *buffer;
   GtkTextMark *mark;
 
+  buffer = get_buffer (text_view);
   get_iter_at_pointer (text_view, device, &newplace, NULL, NULL);
-  
-  mark = gtk_text_buffer_get_mark (get_buffer (text_view), mark_name);
-  
+
+  mark = gtk_text_buffer_get_mark (buffer, mark_name);
+
   /* This may invalidate the layout */
   DV(g_print (G_STRLOC": move mark\n"));
-  
-  gtk_text_buffer_move_mark (get_buffer (text_view),
-                            mark,
-                            &newplace);
-  
+
+  gtk_text_buffer_move_mark (buffer, mark, &newplace);
+
   DV(g_print (G_STRLOC": scrolling onscreen\n"));
   gtk_text_view_scroll_mark_onscreen (text_view, mark);
 
@@ -6399,16 +6810,22 @@ selection_motion_event_handler (GtkTextView    *text_view,
                                SelectionData  *data)
 {
   GtkTextViewPrivate *priv;
+  GdkInputSource input_source;
+  GdkDevice *device;
 
   priv = text_view->priv;
   gdk_event_request_motions (event);
 
+  device = gdk_event_get_source_device ((GdkEvent *) event);
+  input_source = gdk_device_get_source (device);
+
   if (priv->grab_device != event->device)
     return FALSE;
 
   if (data->granularity == SELECT_CHARACTERS) 
     {
-      move_mark_to_pointer_and_scroll (text_view, "insert", event->device);
+      move_mark_to_pointer_and_scroll (text_view, "insert",
+                                       event->device, input_source);
     }
   else 
     {
@@ -6432,7 +6849,7 @@ selection_motion_event_handler (GtkTextView    *text_view,
       else
         gtk_text_buffer_select_range (buffer, &end, &orig_start);
 
-      gtk_text_view_scroll_mark_onscreen (text_view, 
+      gtk_text_view_scroll_mark_onscreen (text_view,
                                          gtk_text_buffer_get_insert (buffer));
     }
 
@@ -6447,6 +6864,9 @@ selection_motion_event_handler (GtkTextView    *text_view,
   text_view->priv->scroll_timeout =
     gdk_threads_add_timeout (50, selection_scan_timeout, text_view);
 
+  if (test_touchscreen || input_source == GDK_SOURCE_TOUCHSCREEN)
+    gtk_text_view_update_handles (text_view, GTK_TEXT_HANDLE_MODE_SELECTION);
+
   return TRUE;
 }
 
@@ -6488,7 +6908,9 @@ gtk_text_view_start_selection_drag (GtkTextView       *text_view,
   orig_start = ins;
   orig_end = bound;
 
-  if (button->state & GDK_SHIFT_MASK)
+  if (button->state &
+      gtk_widget_get_modifier_mask (GTK_WIDGET (text_view),
+                                    GDK_MODIFIER_INTENT_EXTEND_SELECTION))
     {
       /* Extend selection */
       GtkTextIter old_ins, old_bound;
@@ -6598,7 +7020,7 @@ gtk_text_view_set_attributes_from_style (GtkTextView        *text_view,
   if (values->font)
     pango_font_description_free (values->font);
 
-  values->font = pango_font_description_copy (gtk_style_context_get_font (context, state));
+  gtk_style_context_get (context, state, "font", &values->font, NULL);
 
   gtk_style_context_restore (context);
 }
@@ -7685,26 +8107,6 @@ gtk_text_view_value_changed (GtkAdjustment *adjustment,
    */
   gtk_text_view_validate_onscreen (text_view);
   
-  /* process exposes */
-  if (gtk_widget_get_realized (GTK_WIDGET (text_view)))
-    {
-      DV (g_print ("Processing updates (%s)\n", G_STRLOC));
-      
-      if (priv->left_window)
-        gdk_window_process_updates (priv->left_window->bin_window, TRUE);
-
-      if (priv->right_window)
-        gdk_window_process_updates (priv->right_window->bin_window, TRUE);
-
-      if (priv->top_window)
-        gdk_window_process_updates (priv->top_window->bin_window, TRUE);
-      
-      if (priv->bottom_window)
-        gdk_window_process_updates (priv->bottom_window->bin_window, TRUE);
-  
-      gdk_window_process_updates (priv->text_window->bin_window, TRUE);
-    }
-
   /* If this got installed, get rid of it, it's just a waste of time. */
   if (priv->first_validate_idle != 0)
     {
@@ -7716,7 +8118,10 @@ gtk_text_view_value_changed (GtkAdjustment *adjustment,
    * changes made by the validation are pushed through.
    */
   gtk_text_view_update_im_spot_location (text_view);
-  
+
+  gtk_text_view_update_handles (text_view,
+                                _gtk_text_handle_get_mode (priv->text_handle));
+
   DV(g_print(">End scroll offset changed handler ("G_STRLOC")\n"));
 }
 
@@ -7892,7 +8297,11 @@ gtk_text_view_mark_set_handler (GtkTextBuffer     *buffer,
     }
 
   if (need_reset)
-    gtk_text_view_reset_im_context (text_view);
+    {
+      gtk_text_view_reset_im_context (text_view);
+      gtk_text_view_update_handles (text_view,
+                                    _gtk_text_handle_get_mode (text_view->priv->text_handle));
+    }
 }
 
 static void
@@ -8137,7 +8546,7 @@ popup_position_func (GtkMenu   *menu,
 
   monitor_num = gdk_screen_get_monitor_at_point (screen, *x, *y);
   gtk_menu_set_monitor (menu, monitor_num);
-  gdk_screen_get_monitor_geometry (screen, monitor_num, &monitor);
+  gdk_screen_get_monitor_workarea (screen, monitor_num, &monitor);
 
   *x = CLAMP (*x, monitor.x, monitor.x + MAX (0, monitor.width - req.width));
   *y = CLAMP (*y, monitor.y, monitor.y + MAX (0, monitor.height - req.height));
@@ -8150,6 +8559,7 @@ typedef struct
   GtkTextView *text_view;
   gint button;
   guint time;
+  GdkDevice *device;
 } PopupInfo;
 
 static gboolean
@@ -8303,9 +8713,9 @@ popup_targets_received (GtkClipboard     *clipboard,
                     0,
                     priv->popup_menu);
       
-      if (info->button)
-       gtk_menu_popup (GTK_MENU (priv->popup_menu), NULL, NULL,
-                       NULL, NULL,
+      if (info->device)
+       gtk_menu_popup_for_device (GTK_MENU (priv->popup_menu), 
+      info->device, NULL, NULL, NULL, NULL, NULL,
                        info->button, info->time);
       else
        {
@@ -8336,11 +8746,13 @@ gtk_text_view_do_popup (GtkTextView    *text_view,
     {
       info->button = event->button;
       info->time = event->time;
+      info->device = event->device;
     }
   else
     {
       info->button = 0;
       info->time = gtk_get_current_event_time ();
+      info->device = NULL;
     }
 
   gtk_clipboard_request_contents (gtk_widget_get_clipboard (GTK_WIDGET (text_view),
@@ -8357,6 +8769,165 @@ gtk_text_view_popup_menu (GtkWidget *widget)
   return TRUE;
 }
 
+static void
+gtk_text_view_get_selection_rect (GtkTextView           *text_view,
+                                 cairo_rectangle_int_t *rect)
+{
+  cairo_rectangle_int_t rect_cursor, rect_bound;
+  GtkTextIter cursor, bound;
+  GtkTextBuffer *buffer;
+  gint x1, y1, x2, y2;
+
+  buffer = get_buffer (text_view);
+  gtk_text_buffer_get_iter_at_mark (buffer, &cursor,
+                                    gtk_text_buffer_get_insert (buffer));
+  gtk_text_buffer_get_iter_at_mark (buffer, &bound,
+                                    gtk_text_buffer_get_selection_bound (buffer));
+
+  gtk_text_view_get_cursor_locations (text_view, &cursor, &rect_cursor, NULL);
+  gtk_text_view_get_cursor_locations (text_view, &bound, &rect_bound, NULL);
+
+  x1 = MIN (rect_cursor.x, rect_bound.x);
+  x2 = MAX (rect_cursor.x, rect_bound.x);
+  y1 = MIN (rect_cursor.y, rect_bound.y);
+  y2 = MAX (rect_cursor.y + rect_cursor.height, rect_bound.y + rect_bound.height);
+
+  rect->x = x1;
+  rect->y = y1;
+  rect->width = x2 - x1;
+  rect->height = y2 - y1;
+}
+
+static void
+activate_bubble_cb (GtkWidget   *item,
+                    GtkTextView *text_view)
+{
+  const gchar *signal = g_object_get_data (G_OBJECT (item), "gtk-signal");
+  g_signal_emit_by_name (text_view, signal);
+  _gtk_bubble_window_popdown (GTK_BUBBLE_WINDOW (text_view->priv->selection_bubble));
+}
+
+static void
+append_bubble_action (GtkTextView  *text_view,
+                      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), text_view);
+  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)
+{
+  GtkTextView *text_view = user_data;
+  GtkTextViewPrivate *priv = text_view->priv;
+  cairo_rectangle_int_t rect;
+  gboolean has_selection;
+  gboolean has_clipboard;
+  gboolean can_insert;
+  GtkTextIter iter;
+  GtkTextIter sel_start, sel_end;
+  GdkWindow *window;
+  GtkWidget *toolbar;
+
+  has_selection = gtk_text_buffer_get_selection_bounds (get_buffer (text_view),
+                                                        &sel_start, &sel_end);
+  if (!priv->editable && !has_selection)
+    {
+      priv->selection_bubble_timeout_id = 0;
+      return;
+    }
+
+  if (priv->selection_bubble)
+    gtk_widget_destroy (priv->selection_bubble);
+
+  window = gtk_widget_get_window (GTK_WIDGET (text_view));
+  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);
+
+  gtk_text_buffer_get_iter_at_mark (get_buffer (text_view), &iter,
+                                    gtk_text_buffer_get_insert (get_buffer (text_view)));
+  can_insert = gtk_text_iter_can_insert (&iter, priv->editable);
+  has_clipboard = gtk_selection_data_targets_include_text (data);
+
+  append_bubble_action (text_view, toolbar, GTK_STOCK_CUT, "cut-clipboard",
+                        has_selection &&
+                        range_contains_editable_text (&sel_start, &sel_end,
+                                                      priv->editable));
+  append_bubble_action (text_view, toolbar, GTK_STOCK_COPY, "copy-clipboard",
+                        has_selection);
+  append_bubble_action (text_view, toolbar, GTK_STOCK_PASTE, "paste-clipboard",
+                        can_insert && has_clipboard);
+
+  if (priv->populate_all)
+    g_signal_emit (text_view, signals[POPULATE_POPUP], 0, toolbar);
+
+  gtk_text_view_get_selection_rect (text_view, &rect);
+  rect.x -= priv->xoffset;
+  rect.y -= priv->yoffset;
+  _gtk_bubble_window_popup (GTK_BUBBLE_WINDOW (priv->selection_bubble),
+                           window, &rect, GTK_POS_TOP);
+
+  priv->selection_bubble_timeout_id = 0;
+}
+
+static gboolean
+gtk_text_view_selection_bubble_popup_cb (gpointer user_data)
+{
+  GtkTextView *text_view = user_data;
+  gtk_clipboard_request_contents (gtk_widget_get_clipboard (GTK_WIDGET (text_view),
+                                                           GDK_SELECTION_CLIPBOARD),
+                                 gdk_atom_intern_static_string ("TARGETS"),
+                                 bubble_targets_received,
+                                 text_view);
+
+  return G_SOURCE_REMOVE;
+}
+
+static void
+gtk_text_view_selection_bubble_popup_unset (GtkTextView *text_view)
+{
+  GtkTextViewPrivate *priv;
+
+  priv = text_view->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_text_view_selection_bubble_popup_set (GtkTextView *text_view)
+{
+  GtkTextViewPrivate *priv;
+
+  priv = text_view->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_text_view_selection_bubble_popup_cb,
+                             text_view);
+}
+
 /* Child GdkWindows */
 
 
@@ -8422,7 +8993,7 @@ text_window_realize (GtkTextWindow *win,
                                 &attributes, attributes_mask);
 
   gdk_window_show (win->window);
-  gdk_window_set_user_data (win->window, win->widget);
+  gtk_widget_register_window (win->widget, win->window);
   gdk_window_lower (win->window);
 
   attributes.x = 0;
@@ -8431,6 +9002,7 @@ text_window_realize (GtkTextWindow *win,
   attributes.height = win->allocation.height;
   attributes.event_mask = (GDK_EXPOSURE_MASK            |
                            GDK_SCROLL_MASK              |
+                           GDK_SMOOTH_SCROLL_MASK       |
                            GDK_KEY_PRESS_MASK           |
                            GDK_BUTTON_PRESS_MASK        |
                            GDK_BUTTON_RELEASE_MASK      |
@@ -8443,7 +9015,7 @@ text_window_realize (GtkTextWindow *win,
                                     attributes_mask);
 
   gdk_window_show (win->bin_window);
-  gdk_window_set_user_data (win->bin_window, win->widget);
+  gtk_widget_register_window (win->widget, win->bin_window);
 
   context = gtk_widget_get_style_context (widget);
   state = gtk_widget_get_state_flags (widget);
@@ -8494,8 +9066,8 @@ text_window_unrealize (GtkTextWindow *win)
                                         NULL);
     }
 
-  gdk_window_set_user_data (win->window, NULL);
-  gdk_window_set_user_data (win->bin_window, NULL);
+  gtk_widget_unregister_window (win->widget, win->window);
+  gtk_widget_unregister_window (win->widget, win->bin_window);
   gdk_window_destroy (win->bin_window);
   gdk_window_destroy (win->window);
   win->window = NULL;
@@ -8524,8 +9096,13 @@ text_window_scroll        (GtkTextWindow *win,
                            gint           dx,
                            gint           dy)
 {
+  GtkTextView *view = GTK_TEXT_VIEW (win->widget);
+  GtkTextViewPrivate *priv = view->priv;
+
   if (dx != 0 || dy != 0)
     {
+      if (priv->selection_bubble)
+        _gtk_bubble_window_popdown (GTK_BUBBLE_WINDOW (priv->selection_bubble));
       gdk_window_scroll (win->bin_window, dx, dy);
     }
 }
@@ -8612,7 +9189,7 @@ text_window_invalidate_cursors (GtkTextWindow *win)
   gtk_text_layout_get_cursor_locations (priv->layout, &iter,
                                         &strong, &weak);
 
-  /* cursor width calculation as in gtkstyle.c:draw_insertion_cursor(),
+  /* cursor width calculation as in gtkstylecontext.c:draw_insertion_cursor(),
    * ignoring the text direction be exposing both sides of the cursor
    */
 
@@ -9640,3 +10217,106 @@ gtk_text_view_move_visually (GtkTextView *text_view,
 
   return gtk_text_layout_move_iter_visually (text_view->priv->layout, iter, count);
 }
+
+/**
+ * gtk_text_view_set_input_purpose:
+ * @text_view: a #GtkTextView
+ * @purpose: the purpose
+ *
+ * Sets the #GtkTextView:input-purpose property which
+ * can be used by on-screen keyboards and other input
+ * methods to adjust their behaviour.
+ *
+ * Since: 3.6
+ */
+
+void
+gtk_text_view_set_input_purpose (GtkTextView     *text_view,
+                                 GtkInputPurpose  purpose)
+
+{
+  g_return_if_fail (GTK_IS_TEXT_VIEW (text_view));
+
+  if (gtk_text_view_get_input_purpose (text_view) != purpose)
+    {
+      g_object_set (G_OBJECT (text_view->priv->im_context),
+                    "input-purpose", purpose,
+                    NULL);
+
+      g_object_notify (G_OBJECT (text_view), "input-purpose");
+    }
+}
+
+/**
+ * gtk_text_view_get_input_purpose:
+ * @text_view: a #GtkTextView
+ *
+ * Gets the value of the #GtkTextView:input-purpose property.
+ *
+ * Since: 3.6
+ */
+
+GtkInputPurpose
+gtk_text_view_get_input_purpose (GtkTextView *text_view)
+{
+  GtkInputPurpose purpose;
+
+  g_return_val_if_fail (GTK_IS_TEXT_VIEW (text_view), GTK_INPUT_PURPOSE_FREE_FORM);
+
+  g_object_get (G_OBJECT (text_view->priv->im_context),
+                "input-purpose", &purpose,
+                NULL);
+
+  return purpose;
+}
+
+/**
+ * gtk_text_view_set_input_hints:
+ * @text_view: a #GtkTextView
+ * @hints: the hints
+ *
+ * Sets the #GtkTextView:input-hints property, which
+ * allows input methods to fine-tune their behaviour.
+ *
+ * Since: 3.6
+ */
+
+void
+gtk_text_view_set_input_hints (GtkTextView   *text_view,
+                               GtkInputHints  hints)
+
+{
+  g_return_if_fail (GTK_IS_TEXT_VIEW (text_view));
+
+  if (gtk_text_view_get_input_hints (text_view) != hints)
+    {
+      g_object_set (G_OBJECT (text_view->priv->im_context),
+                    "input-hints", hints,
+                    NULL);
+
+      g_object_notify (G_OBJECT (text_view), "input-hints");
+    }
+}
+
+/**
+ * gtk_text_view_get_input_hints:
+ * @text_view: a #GtkTextView
+ *
+ * Gets the value of the #GtkTextView:input-hints property.
+ *
+ * Since: 3.6
+ */
+
+GtkInputHints
+gtk_text_view_get_input_hints (GtkTextView *text_view)
+{
+  GtkInputHints hints;
+
+  g_return_val_if_fail (GTK_IS_TEXT_VIEW (text_view), GTK_INPUT_HINT_NONE);
+
+  g_object_get (G_OBJECT (text_view->priv->im_context),
+                "input-hints", &hints,
+                NULL);
+
+  return hints;
+}