]> Pileus Git - ~andy/gtk/blobdiff - gtk/gtktextview.c
spinbutton: don't override initial text in non-numeric-only spin buttons
[~andy/gtk] / gtk / gtktextview.c
index 494f1004f18883229105d00b8166ba80a07e4477..2de8a1e623be9f15d7b3bf23ead0c54b9a9580cb 100644 (file)
@@ -53,6 +53,8 @@
 #include "gtktexthandleprivate.h"
 #include "gtkstylecontextprivate.h"
 #include "gtkcssstylepropertyprivate.h"
+#include "gtkbubblewindowprivate.h"
+#include "gtktoolbar.h"
 
 #include "a11y/gtktextviewaccessibleprivate.h"
 
@@ -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;
@@ -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)
@@ -4759,6 +4819,8 @@ gtk_text_view_key_press_event (GtkWidget *widget, GdkEventKey *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;
 }
 
@@ -4809,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 */
@@ -4926,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;
@@ -4942,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
@@ -4961,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;
 
@@ -5046,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);
 
@@ -6327,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
@@ -8700,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 */
 
 
@@ -8868,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);
     }
 }