]> Pileus Git - ~andy/gtk/blobdiff - gtk/gtktextview.c
GtkBubbleWindow: use OSD style class
[~andy/gtk] / gtk / gtktextview.c
index 585eaa4ad3b31024650c375c994965a817d552d2..1a0b56069f19de93521699efb54808549c57aa47 100644 (file)
 #include "gtktexthandleprivate.h"
 #include "gtkstylecontextprivate.h"
 #include "gtkcssstylepropertyprivate.h"
+#include "gtkbubblewindowprivate.h"
+#include "gtktoolbar.h"
 
-#include "a11y/gtktextviewaccessible.h"
+#include "a11y/gtktextviewaccessibleprivate.h"
 
 /**
  * SECTION:gtktextview
@@ -136,6 +138,8 @@ struct _GtkTextViewPrivate
 
   gulong selection_drag_handler;
   GtkTextHandle *text_handle;
+  GtkWidget *selection_bubble;
+  guint selection_bubble_timeout_id;
 
   GtkTextWindow *text_window;
   GtkTextWindow *left_window;
@@ -235,6 +239,7 @@ struct _GtkTextViewPrivate
   guint vscroll_policy : 1;
   guint cursor_handle_dragged : 1;
   guint selection_handle_dragged : 1;
+  guint populate_all   : 1;
 };
 
 struct _GtkTextPendingScroll
@@ -289,7 +294,8 @@ enum
   PROP_HSCROLL_POLICY,
   PROP_VSCROLL_POLICY,
   PROP_INPUT_PURPOSE,
-  PROP_INPUT_HINTS
+  PROP_INPUT_HINTS,
+  PROP_POPULATE_ALL
 };
 
 static void gtk_text_view_finalize             (GObject          *object);
@@ -511,9 +517,16 @@ static void gtk_text_view_handle_dragged       (GtkTextHandle         *handle,
                                                 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;
@@ -850,6 +863,22 @@ gtk_text_view_class_init (GtkTextViewClass *klass)
                                                        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");
@@ -1112,13 +1141,22 @@ gtk_text_view_class_init (GtkTextViewClass *klass)
   /**
    * GtkTextView::populate-popup:
    * @text_view: The text view on which the signal is emitted
-   * @menu: the menu that is being populated
+   * @popup: the container that is being populated
    *
-   * The ::populate-popup signal gets emitted before showing the 
+   * 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"),
@@ -1128,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:
@@ -1479,6 +1517,8 @@ gtk_text_view_init (GtkTextView *text_view)
   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);
 }
 
 /**
@@ -3137,6 +3177,9 @@ 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);
 
@@ -3248,6 +3291,10 @@ gtk_text_view_set_property (GObject         *object,
       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;
@@ -3352,6 +3399,10 @@ gtk_text_view_get_property (GObject         *object,
       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;
@@ -4094,7 +4145,7 @@ 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);
 
@@ -4509,6 +4560,7 @@ gtk_text_view_handle_dragged (GtkTextHandle         *handle,
   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);
@@ -4575,6 +4627,14 @@ gtk_text_view_handle_dragged (GtkTextHandle         *handle,
     }
 }
 
+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)
@@ -4755,8 +4815,11 @@ 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);
 
-  _gtk_text_handle_set_mode (priv->text_handle,
-                             GTK_TEXT_HANDLE_MODE_NONE);
+  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;
 }
@@ -4808,6 +4871,7 @@ 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 */
@@ -4925,9 +4989,11 @@ 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;
@@ -4941,11 +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;
-          GdkDevice *device;
          GtkTextIter iter;
 
           /* Unselect everything; we clicked inside selection, but
@@ -4960,12 +5030,10 @@ 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);
 
-          device = gdk_event_get_source_device ((GdkEvent *) event);
-
           if (gtk_widget_is_sensitive (widget) &&
               (test_touchscreen ||
                gdk_device_get_source (device) == GDK_SOURCE_TOUCHSCREEN))
-            mode = GTK_TEXT_HANDLE_MODE_CURSOR;
+           mode = GTK_TEXT_HANDLE_MODE_CURSOR;
           else
             mode = GTK_TEXT_HANDLE_MODE_NONE;
 
@@ -5045,6 +5113,7 @@ 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);
 
@@ -5153,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);
@@ -5218,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;
     }
@@ -6306,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
@@ -6356,7 +6446,10 @@ gtk_text_view_buffer_changed_handler (GtkTextBuffer *buffer,
                                       gpointer       data)
 {
   GtkTextView *text_view = data;
-  gtk_text_view_update_handles (text_view, GTK_TEXT_HANDLE_MODE_NONE);
+  GtkTextViewPrivate *priv = text_view->priv;
+
+  gtk_text_view_update_handles (text_view,
+                                _gtk_text_handle_get_mode (priv->text_handle));
 }
 
 static void
@@ -8676,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 */
 
 
@@ -8741,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;
@@ -8763,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);
@@ -8814,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;
@@ -8844,8 +9096,12 @@ 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)
     {
+      _gtk_bubble_window_popdown (GTK_BUBBLE_WINDOW (priv->selection_bubble));
       gdk_window_scroll (win->bin_window, dx, dy);
     }
 }