X-Git-Url: http://pileus.org/git/?a=blobdiff_plain;f=gtk%2Fgtkentry.c;h=159120473c8a323d598478645894c51c5ee1e856;hb=3c8e1c92a85b2e41161698f141747ced2c574f32;hp=184277c01bc39c7eb818320fd268902e9db94bef;hpb=e6de45964dc1937781f8428c8f9d7a1457f7fba0;p=~andy%2Fgtk diff --git a/gtk/gtkentry.c b/gtk/gtkentry.c index 184277c01..159120473 100644 --- a/gtk/gtkentry.c +++ b/gtk/gtkentry.c @@ -66,6 +66,8 @@ #include "gtkwidgetprivate.h" #include "gtkstylecontextprivate.h" #include "gtktexthandleprivate.h" +#include "gtkbubblewindowprivate.h" +#include "gtktoolbar.h" #include "a11y/gtkentryaccessible.h" @@ -158,7 +160,10 @@ struct _GtkEntryPrivate gchar *placeholder_text; + GtkBubbleWindow *bubble_window; GtkTextHandle *text_handle; + GtkWidget *selection_bubble; + guint selection_bubble_timeout_id; gfloat xalign; @@ -218,6 +223,7 @@ struct _GtkEntryPrivate guint truncate_multiline : 1; guint cursor_handle_dragged : 1; guint selection_handle_dragged : 1; + guint populate_all : 1; }; struct _EntryIconInfo @@ -314,7 +320,8 @@ enum { PROP_COMPLETION, PROP_INPUT_PURPOSE, PROP_INPUT_HINTS, - PROP_ATTRIBUTES + PROP_ATTRIBUTES, + PROP_POPULATE_ALL }; static guint signals[LAST_SIGNAL] = { 0 }; @@ -592,6 +599,12 @@ static void gtk_entry_handle_dragged (GtkTextHandle *h gint x, gint y, GtkEntry *entry); +static void gtk_entry_handle_drag_finished (GtkTextHandle *handle, + GtkTextHandlePosition pos, + GtkEntry *entry); + +static void gtk_entry_selection_bubble_popup_set (GtkEntry *entry); +static void gtk_entry_selection_bubble_popup_unset (GtkEntry *entry); static void begin_change (GtkEntry *entry); static void end_change (GtkEntry *entry); @@ -1412,6 +1425,21 @@ gtk_entry_class_init (GtkEntryClass *class) PANGO_TYPE_ATTR_LIST, GTK_PARAM_READWRITE)); + /** GtkEntry:populate-all: + * + * If ::populate-all is %TRUE, the #GtkEntry::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)); + /** * GtkEntry:icon-prelight: * @@ -1470,13 +1498,20 @@ gtk_entry_class_init (GtkEntryClass *class) /** * GtkEntry::populate-popup: * @entry: The entry 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 - * context menu of the entry. + * The ::populate-popup signal gets emitted before showing the + * context menu of the entry. * * 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 @widget, which + * will be a #GtkMenu in this case. + * + * If #GtkEntry::populate-all is %TRUE, this signal will + * also be emitted to populate touch popups. In this case, + * @widget will be a different container, e.g. a #GtkToolbar. + * The signal handler should not make assumptions about the + * type of @widget. */ signals[POPULATE_POPUP] = g_signal_new (I_("populate-popup"), @@ -1486,7 +1521,7 @@ gtk_entry_class_init (GtkEntryClass *class) NULL, NULL, _gtk_marshal_VOID__OBJECT, G_TYPE_NONE, 1, - GTK_TYPE_MENU); + GTK_TYPE_WIDGET); /* Action signals */ @@ -2228,6 +2263,10 @@ gtk_entry_set_property (GObject *object, gtk_entry_set_attributes (entry, g_value_get_boxed (value)); break; + case PROP_POPULATE_ALL: + entry->priv->populate_all = g_value_get_boolean (value); + break; + case PROP_SCROLL_OFFSET: case PROP_CURSOR_POSITION: default: @@ -2464,6 +2503,10 @@ gtk_entry_get_property (GObject *object, g_value_set_boxed (value, priv->attrs); 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; @@ -2575,6 +2618,8 @@ gtk_entry_init (GtkEntry *entry) priv->text_handle = _gtk_text_handle_new (GTK_WIDGET (entry)); g_signal_connect (priv->text_handle, "handle-dragged", G_CALLBACK (gtk_entry_handle_dragged), entry); + g_signal_connect (priv->text_handle, "drag-finished", + G_CALLBACK (gtk_entry_handle_drag_finished), entry); } static void @@ -2811,6 +2856,9 @@ gtk_entry_finalize (GObject *object) if (priv->recompute_idle) g_source_remove (priv->recompute_idle); + if (priv->selection_bubble) + gtk_widget_destroy (priv->selection_bubble); + g_object_unref (priv->text_handle); g_free (priv->placeholder_text); g_free (priv->im_module); @@ -2973,7 +3021,7 @@ realize_icon_info (GtkWidget *widget, icon_info->window = gdk_window_new (gtk_widget_get_window (widget), &attributes, attributes_mask); - gdk_window_set_user_data (icon_info->window, widget); + gtk_widget_register_window (widget, icon_info->window); gtk_widget_queue_resize (widget); } @@ -3100,7 +3148,7 @@ gtk_entry_realize (GtkWidget *widget) &attributes, attributes_mask); - gdk_window_set_user_data (priv->text_area, entry); + gtk_widget_register_window (widget, priv->text_area); if (attributes_mask & GDK_WA_CURSOR) g_object_unref (attributes.cursor); @@ -3145,7 +3193,7 @@ gtk_entry_unrealize (GtkWidget *widget) if (priv->text_area) { - gdk_window_set_user_data (priv->text_area, NULL); + gtk_widget_unregister_window (widget, priv->text_area); gdk_window_destroy (priv->text_area); priv->text_area = NULL; } @@ -3164,6 +3212,7 @@ gtk_entry_unrealize (GtkWidget *widget) { if (icon_info->window != NULL) { + gtk_widget_unregister_window (widget, icon_info->window); gdk_window_destroy (icon_info->window); icon_info->window = NULL; } @@ -3743,36 +3792,40 @@ gtk_entry_draw (GtkWidget *widget, GtkEntryPrivate *priv = entry->priv; int i; - context = gtk_widget_get_style_context (widget); + if (gtk_cairo_should_draw_window (cr, + gtk_widget_get_window (widget))) + { + context = gtk_widget_get_style_context (widget); - /* Draw entry_bg, shadow, progress and focus */ - gtk_entry_draw_frame (widget, context, cr); + /* Draw entry_bg, shadow, progress and focus */ + gtk_entry_draw_frame (widget, context, cr); - /* Draw text and cursor */ - cairo_save (cr); + /* Draw text and cursor */ + cairo_save (cr); - gtk_cairo_transform_to_window (cr, widget, priv->text_area); + gtk_cairo_transform_to_window (cr, widget, priv->text_area); - if (priv->dnd_position != -1) - gtk_entry_draw_cursor (GTK_ENTRY (widget), cr, CURSOR_DND); - - gtk_entry_draw_text (GTK_ENTRY (widget), cr); + if (priv->dnd_position != -1) + gtk_entry_draw_cursor (GTK_ENTRY (widget), cr, CURSOR_DND); - /* When no text is being displayed at all, don't show the cursor */ - if (gtk_entry_get_display_mode (entry) != DISPLAY_BLANK && - gtk_widget_has_focus (widget) && - priv->selection_bound == priv->current_pos && priv->cursor_visible) - gtk_entry_draw_cursor (GTK_ENTRY (widget), cr, CURSOR_STANDARD); + gtk_entry_draw_text (GTK_ENTRY (widget), cr); - cairo_restore (cr); + /* When no text is being displayed at all, don't show the cursor */ + if (gtk_entry_get_display_mode (entry) != DISPLAY_BLANK && + gtk_widget_has_focus (widget) && + priv->selection_bound == priv->current_pos && priv->cursor_visible) + gtk_entry_draw_cursor (GTK_ENTRY (widget), cr, CURSOR_STANDARD); - /* Draw icons */ - for (i = 0; i < MAX_ICONS; i++) - { - EntryIconInfo *icon_info = priv->icons[i]; + cairo_restore (cr); - if (icon_info != NULL) - draw_icon (widget, cr, i); + /* Draw icons */ + for (i = 0; i < MAX_ICONS; i++) + { + EntryIconInfo *icon_info = priv->icons[i]; + + if (icon_info != NULL) + draw_icon (widget, cr, i); + } } return FALSE; @@ -4013,6 +4066,8 @@ gtk_entry_button_press (GtkWidget *widget, gint sel_start, sel_end; gint i; + gtk_entry_selection_bubble_popup_unset (entry); + for (i = 0; i < MAX_ICONS; i++) { icon_info = priv->icons[i]; @@ -4217,6 +4272,8 @@ gtk_entry_button_release (GtkWidget *widget, GtkEntry *entry = GTK_ENTRY (widget); GtkEntryPrivate *priv = entry->priv; EntryIconInfo *icon_info = NULL; + gboolean is_touchscreen; + GdkDevice *source; gint i; for (i = 0; i < MAX_ICONS; i++) @@ -4249,21 +4306,23 @@ gtk_entry_button_release (GtkWidget *widget, if (event->window != priv->text_area || priv->button != event->button) return FALSE; + source = gdk_event_get_source_device ((GdkEvent *) event); + is_touchscreen = (test_touchscreen || + gdk_device_get_source (source) == GDK_SOURCE_TOUCHSCREEN); + if (priv->in_drag) { gint tmp_pos = gtk_entry_find_position (entry, priv->drag_start_x); - GdkDevice *source; gtk_editable_set_position (GTK_EDITABLE (entry), tmp_pos); - source = gdk_event_get_source_device ((GdkEvent *) event); - - if (test_touchscreen || - gdk_device_get_source (source) == GDK_SOURCE_TOUCHSCREEN) + if (is_touchscreen) gtk_entry_update_handles (entry, GTK_TEXT_HANDLE_MODE_CURSOR); priv->in_drag = 0; } + else if (is_touchscreen) + gtk_entry_selection_bubble_popup_set (entry); priv->button = 0; priv->device = NULL; @@ -4487,8 +4546,12 @@ gtk_entry_key_press (GtkWidget *widget, gtk_entry_reset_blink_time (entry); gtk_entry_pend_cursor_blink (entry); - _gtk_text_handle_set_mode (priv->text_handle, - GTK_TEXT_HANDLE_MODE_NONE); + + gtk_entry_selection_bubble_popup_unset (entry); + + if (!event->send_event) + _gtk_text_handle_set_mode (priv->text_handle, + GTK_TEXT_HANDLE_MODE_NONE); if (priv->editable) { @@ -4583,6 +4646,7 @@ gtk_entry_focus_out (GtkWidget *widget, GtkEntryCompletion *completion; GdkKeymap *keymap; + gtk_entry_selection_bubble_popup_unset (entry); _gtk_text_handle_set_mode (priv->text_handle, GTK_TEXT_HANDLE_MODE_NONE); @@ -5492,6 +5556,8 @@ gtk_entry_cut_clipboard (GtkEntry *entry) { gtk_widget_error_bell (GTK_WIDGET (entry)); } + + gtk_entry_selection_bubble_popup_unset (entry); } static void @@ -6220,6 +6286,8 @@ gtk_entry_handle_dragged (GtkTextHandle *handle, GtkTextHandleMode mode; gint *min, *max; + gtk_entry_selection_bubble_popup_unset (entry); + cursor_pos = priv->current_pos; selection_bound_pos = priv->selection_bound; mode = _gtk_text_handle_get_mode (handle); @@ -6272,6 +6340,15 @@ gtk_entry_handle_dragged (GtkTextHandle *handle, } } +static void +gtk_entry_handle_drag_finished (GtkTextHandle *handle, + GtkTextHandlePosition pos, + GtkEntry *entry) +{ + gtk_entry_selection_bubble_popup_set (entry); +} + + /** * gtk_entry_reset_im_context: * @entry: a #GtkEntry @@ -9212,6 +9289,151 @@ gtk_entry_popup_menu (GtkWidget *widget) return TRUE; } +static void +activate_bubble_cb (GtkWidget *item, + GtkEntry *entry) +{ + const gchar *signal = g_object_get_data (G_OBJECT (item), "gtk-signal"); + g_signal_emit_by_name (entry, signal); + _gtk_bubble_window_popdown (GTK_BUBBLE_WINDOW (entry->priv->selection_bubble)); +} + +static void +append_bubble_action (GtkEntry *entry, + 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), entry); + 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) +{ + GtkEntry *entry = user_data; + GtkEntryPrivate *priv = entry->priv; + cairo_rectangle_int_t rect; + GtkAllocation allocation; + gint start_x, end_x; + gboolean has_selection; + gboolean has_clipboard; + DisplayMode mode; + GtkWidget *toolbar; + + has_selection = gtk_editable_get_selection_bounds (GTK_EDITABLE (entry), + NULL, NULL); + if (!has_selection && !priv->editable) + { + priv->selection_bubble_timeout_id = 0; + return; + } + + if (priv->selection_bubble) + gtk_widget_destroy (priv->selection_bubble); + + 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); + + has_clipboard = gtk_selection_data_targets_include_text (data); + mode = gtk_entry_get_display_mode (entry); + + append_bubble_action (entry, toolbar, GTK_STOCK_CUT, "cut-clipboard", + priv->editable && has_selection && mode == DISPLAY_NORMAL); + + append_bubble_action (entry, toolbar, GTK_STOCK_COPY, "copy-clipboard", + has_selection && mode == DISPLAY_NORMAL); + + append_bubble_action (entry, toolbar, GTK_STOCK_PASTE, "paste-clipboard", + priv->editable && has_clipboard); + + if (priv->populate_all) + g_signal_emit (entry, signals[POPULATE_POPUP], 0, toolbar); + + gtk_widget_get_allocation (GTK_WIDGET (entry), &allocation); + + gtk_entry_get_cursor_locations (entry, CURSOR_STANDARD, &start_x, NULL); + + start_x -= priv->scroll_offset; + start_x = CLAMP (start_x, 0, gdk_window_get_width (priv->text_area)); + + rect.y = 0; + rect.height = gdk_window_get_height (priv->text_area); + + if (has_selection) + { + end_x = gtk_entry_get_selection_bound_location (entry) - priv->scroll_offset; + end_x = CLAMP (end_x, 0, gdk_window_get_width (priv->text_area)); + + rect.x = MIN (start_x, end_x); + rect.width = MAX (start_x, end_x) - rect.x; + } + else + { + rect.x = start_x; + rect.width = 0; + } + + _gtk_bubble_window_popup (GTK_BUBBLE_WINDOW (priv->selection_bubble), + priv->text_area, &rect, GTK_POS_TOP); + + priv->selection_bubble_timeout_id = 0; +} + +static gboolean +gtk_entry_selection_bubble_popup_cb (gpointer user_data) +{ + GtkEntry *entry = user_data; + + gtk_clipboard_request_contents (gtk_widget_get_clipboard (GTK_WIDGET (entry), GDK_SELECTION_CLIPBOARD), + gdk_atom_intern_static_string ("TARGETS"), + bubble_targets_received, + entry); + return G_SOURCE_REMOVE; +} + +static void +gtk_entry_selection_bubble_popup_unset (GtkEntry *entry) +{ + GtkEntryPrivate *priv; + + priv = entry->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_entry_selection_bubble_popup_set (GtkEntry *entry) +{ + GtkEntryPrivate *priv; + + priv = entry->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_entry_selection_bubble_popup_cb, entry); +} + static void gtk_entry_drag_begin (GtkWidget *widget, GdkDragContext *context)