X-Git-Url: http://pileus.org/git/?a=blobdiff_plain;f=gtk%2Fgtktextview.c;h=2de8a1e623be9f15d7b3bf23ead0c54b9a9580cb;hb=5e2c23214564f7dcc687fa8467020eeb6b9407a9;hp=494f1004f18883229105d00b8166ba80a07e4477;hpb=17d3775555888151780fa404242e734a8e7f6b21;p=~andy%2Fgtk diff --git a/gtk/gtktextview.c b/gtk/gtktextview.c index 494f1004f..2de8a1e62 100644 --- a/gtk/gtktextview.c +++ b/gtk/gtktextview.c @@ -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); } }