diff options
author | Matthias Clasen <mclasen@redhat.com> | 2019-04-08 04:27:59 +0000 |
---|---|---|
committer | Matthias Clasen <mclasen@redhat.com> | 2019-06-13 11:59:50 +0000 |
commit | a28d5d188882a5ca6998a8fcca0e23ea4515d3f5 (patch) | |
tree | 8ecff05a6251e6af6a6ee01ceb37bd5f60b61aa7 | |
parent | cbc0a8447d12aee306d2c2e56a53527384142240 (diff) | |
download | gtk+-a28d5d188882a5ca6998a8fcca0e23ea4515d3f5.tar.gz |
text, entry: Implement context menu api
Drop the ::populate-popup signal and implement
the new context menu api.
-rw-r--r-- | docs/reference/gtk/gtk4-sections.txt | 4 | ||||
-rw-r--r-- | gtk/gtkentry.c | 69 | ||||
-rw-r--r-- | gtk/gtkentry.h | 6 | ||||
-rw-r--r-- | gtk/gtktext.c | 557 | ||||
-rw-r--r-- | gtk/gtktext.h | 6 | ||||
-rw-r--r-- | gtk/gtktextprivate.h | 7 |
6 files changed, 423 insertions, 226 deletions
diff --git a/docs/reference/gtk/gtk4-sections.txt b/docs/reference/gtk/gtk4-sections.txt index 5efb6214c5..e42715a721 100644 --- a/docs/reference/gtk/gtk4-sections.txt +++ b/docs/reference/gtk/gtk4-sections.txt @@ -893,6 +893,8 @@ gtk_text_get_attributes gtk_text_set_tabs gtk_text_get_tabs gtk_text_grab_focus_without_selecting +gtk_text_set_extra_menu +gtk_text_get_extra_menu <SUBSECTION Private> gtk_text_get_type </SECTION> @@ -963,6 +965,8 @@ GtkInputHints gtk_entry_set_input_hints gtk_entry_get_input_hints gtk_entry_grab_focus_without_selecting +gtk_entry_set_extra_menu +gtk_entry_get_extra_menu <SUBSECTION Standard> GTK_ENTRY diff --git a/gtk/gtkentry.c b/gtk/gtkentry.c index e1cf3131f0..c9a6cfed24 100644 --- a/gtk/gtkentry.c +++ b/gtk/gtkentry.c @@ -224,6 +224,7 @@ enum { PROP_ATTRIBUTES, PROP_POPULATE_ALL, PROP_TABS, + PROP_EXTRA_MENU, PROP_SHOW_EMOJI_ICON, PROP_ENABLE_EMOJI_COMPLETION, PROP_EDITING_CANCELED, @@ -836,6 +837,19 @@ gtk_entry_class_init (GtkEntryClass *class) FALSE, GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY); + /** + * GtkEntry:extra-menu: + * + * A menu model whose contents will be appended to + * the context menu. + */ + entry_props[PROP_EXTRA_MENU] = + g_param_spec_object ("extra-menu", + P_("Extra menu"), + P_("Model menu to append to the context menu"), + G_TYPE_MENU_MODEL, + GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY); + entry_props[PROP_ENABLE_EMOJI_COMPLETION] = g_param_spec_boolean ("enable-emoji-completion", P_("Enable Emoji completion"), @@ -1062,6 +1076,10 @@ gtk_entry_set_property (GObject *object, set_show_emoji_icon (entry, g_value_get_boolean (value)); break; + case PROP_EXTRA_MENU: + gtk_entry_set_extra_menu (entry, g_value_get_object (value)); + break; + default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -1219,6 +1237,10 @@ gtk_entry_get_property (GObject *object, g_value_set_boolean (value, priv->show_emoji_icon); break; + case PROP_EXTRA_MENU: + g_value_set_object (value, gtk_entry_get_extra_menu (entry)); + break; + default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -3471,6 +3493,8 @@ set_show_emoji_icon (GtkEntry *entry, gboolean value) { GtkEntryPrivate *priv = gtk_entry_get_instance_private (entry); + GActionGroup *actions; + GAction *action; if (priv->show_emoji_icon == value) return; @@ -3512,6 +3536,12 @@ set_show_emoji_icon (GtkEntry *entry, g_object_notify_by_pspec (G_OBJECT (entry), entry_props[PROP_SHOW_EMOJI_ICON]); gtk_widget_queue_resize (GTK_WIDGET (entry)); + + actions = gtk_widget_get_action_group (priv->text, "context"); + action = g_action_map_lookup_action (G_ACTION_MAP (actions), "insert-emoji"); + g_simple_action_set_enabled (G_SIMPLE_ACTION (action), + priv->show_emoji_icon || + (gtk_entry_get_input_hints (entry) & GTK_INPUT_HINT_NO_EMOJI) == 0); } GtkEventController * @@ -3529,3 +3559,42 @@ gtk_entry_get_text_widget (GtkEntry *entry) return GTK_TEXT (priv->text); } + +/** + * gtk_entry_set_extra_menu: + * @entry: a #GtkEntry + * @model: (allow-none): a #GMenuModel + * + * Sets a menu model to add when constructing + * the context menu for @entry. + */ +void +gtk_entry_set_extra_menu (GtkEntry *entry, + GMenuModel *model) +{ + GtkEntryPrivate *priv = gtk_entry_get_instance_private (entry); + + g_return_if_fail (GTK_IS_ENTRY (entry)); + + gtk_text_set_extra_menu (GTK_TEXT (priv->text), model); + + g_object_notify_by_pspec (G_OBJECT (entry), entry_props[PROP_EXTRA_MENU]); +} + +/** + * gtk_entry_get_extra_menu: + * @self: a #GtkText + * + * Gets the menu model set with gtk_entry_set_extra_menu(). + * + * Returns: (transfer none): (nullable): the menu model + */ +GMenuModel * +gtk_entry_get_extra_menu (GtkEntry *entry) +{ + GtkEntryPrivate *priv = gtk_entry_get_instance_private (entry); + + g_return_val_if_fail (GTK_IS_ENTRY (entry), NULL); + + return gtk_text_get_extra_menu (GTK_TEXT (priv->text)); +} diff --git a/gtk/gtkentry.h b/gtk/gtkentry.h index 1048382271..66d0742c64 100644 --- a/gtk/gtkentry.h +++ b/gtk/gtkentry.h @@ -310,6 +310,12 @@ PangoTabArray *gtk_entry_get_tabs (GtkEntry GDK_AVAILABLE_IN_ALL void gtk_entry_grab_focus_without_selecting (GtkEntry *entry); +GDK_AVAILABLE_IN_ALL +void gtk_entry_set_extra_menu (GtkEntry *entry, + GMenuModel *model); +GDK_AVAILABLE_IN_ALL +GMenuModel * gtk_entry_get_extra_menu (GtkEntry *entry); + G_END_DECLS #endif /* __GTK_ENTRY_H__ */ diff --git a/gtk/gtktext.c b/gtk/gtktext.c index f89b8555f4..ae703231ff 100644 --- a/gtk/gtktext.c +++ b/gtk/gtktext.c @@ -23,6 +23,7 @@ #include "gtktextprivate.h" +#include "gtkactionable.h" #include "gtkadjustment.h" #include "gtkbindings.h" #include "gtkbox.h" @@ -51,7 +52,7 @@ #include "gtkmenu.h" #include "gtkmenuitem.h" #include "gtkpango.h" -#include "gtkpopover.h" +#include "gtkpopovermenu.h" #include "gtkprivate.h" #include "gtkseparatormenuitem.h" #include "gtkselection.h" @@ -145,7 +146,6 @@ struct _GtkTextPrivate { GtkEntryBuffer *buffer; GtkIMContext *im_context; - GtkWidget *popup_menu; int text_baseline; @@ -174,6 +174,10 @@ struct _GtkTextPrivate GtkCssNode *block_cursor_node; GtkCssNode *undershoot_node[2]; + GActionMap *context_actions; + GtkWidget *popup_menu; + GMenuModel *extra_menu; + float xalign; int ascent; /* font ascent in pango units */ @@ -233,7 +237,6 @@ struct _GtkTextPasswordHint enum { ACTIVATE, - POPULATE_POPUP, MOVE_CURSOR, INSERT_AT_CURSOR, DELETE_FROM_CURSOR, @@ -263,10 +266,10 @@ enum { PROP_INPUT_PURPOSE, PROP_INPUT_HINTS, PROP_ATTRIBUTES, - PROP_POPULATE_ALL, PROP_TABS, PROP_ENABLE_EMOJI_COMPLETION, PROP_PROPAGATE_TEXT_WIDTH, + PROP_EXTRA_MENU, NUM_PROPERTIES }; @@ -383,6 +386,7 @@ static void gtk_text_set_alignment (GtkText *self, /* Default signal handlers */ +static GMenuModel *gtk_text_get_menu_model (GtkText *self); static gboolean gtk_text_popup_menu (GtkWidget *widget); static void gtk_text_move_cursor (GtkText *self, GtkMovementStep step, @@ -512,8 +516,6 @@ static void gtk_text_paste (GtkText *self, GdkClipboard *clipboard); static void gtk_text_update_primary_selection (GtkText *self); static void gtk_text_schedule_im_reset (GtkText *self); -static void gtk_text_do_popup (GtkText *self, - const GdkEvent *event); static gboolean gtk_text_mnemonic_activate (GtkWidget *widget, gboolean group_cycling); static void gtk_text_check_cursor_blink (GtkText *self); @@ -540,6 +542,10 @@ static void begin_change (GtkText *self); static void end_change (GtkText *self); static void emit_changed (GtkText *self); +static void gtk_text_add_context_actions (GtkText *self); +static void gtk_text_update_clipboard_actions (GtkText *self); +static void gtk_text_update_emoji_action (GtkText *self); + /* GtkTextContent implementation */ @@ -859,20 +865,7 @@ gtk_text_class_init (GtkTextClass *class) GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY); /** - * GtkText:populate-all: - * - * If :populate-all is %TRUE, the #GtkText::populate-popup - * signal is also emitted for touch popups. - */ - text_props[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|G_PARAM_EXPLICIT_NOTIFY); - - /** - * GtkText::tabs: + * GtkText:tabs: * * A list of tabstops to apply to the text of the self. */ @@ -904,38 +897,23 @@ gtk_text_class_init (GtkTextClass *class) FALSE, GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY); + /** + * GtkText:extra-menu: + * + * A menu model whose contents will be appended to + * the context menu. + */ + text_props[PROP_EXTRA_MENU] = + g_param_spec_object ("extra-menu", + P_("Extra menu"), + P_("Menu model to append to the context menu"), + G_TYPE_MENU_MODEL, + GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY); + g_object_class_install_properties (gobject_class, NUM_PROPERTIES, text_props); gtk_editable_install_properties (gobject_class, NUM_PROPERTIES); - /** - * GtkText::populate-popup: - * @self: The self on which the signal is emitted - * @widget: the container that is being populated - * - * The ::populate-popup signal gets emitted before showing the - * context menu of the self. - * - * If you need to add items to the context menu, connect - * to this signal and append your items to the @widget, which - * will be a #GtkMenu in this case. - * - * If #GtkText: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"), - G_OBJECT_CLASS_TYPE (gobject_class), - G_SIGNAL_RUN_LAST, - G_STRUCT_OFFSET (GtkTextClass, populate_popup), - NULL, NULL, - NULL, - G_TYPE_NONE, 1, - GTK_TYPE_WIDGET); - /* Action signals */ /** @@ -1505,14 +1483,6 @@ gtk_text_set_property (GObject *object, gtk_text_set_attributes (self, g_value_get_boxed (value)); break; - case PROP_POPULATE_ALL: - if (priv->populate_all != g_value_get_boolean (value)) - { - priv->populate_all = g_value_get_boolean (value); - g_object_notify_by_pspec (object, pspec); - } - break; - case PROP_TABS: gtk_text_set_tabs (self, g_value_get_boxed (value)); break; @@ -1530,6 +1500,10 @@ gtk_text_set_property (GObject *object, } break; + case PROP_EXTRA_MENU: + gtk_text_set_extra_menu (self, g_value_get_object (value)); + break; + default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -1633,10 +1607,6 @@ gtk_text_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; - case PROP_TABS: g_value_set_boxed (value, priv->tabs); break; @@ -1649,6 +1619,10 @@ gtk_text_get_property (GObject *object, g_value_set_boolean (value, priv->propagate_text_width); break; + case PROP_EXTRA_MENU: + g_value_set_object (value, priv->extra_menu); + break; + default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -1746,6 +1720,7 @@ gtk_text_init (GtkText *self) } set_text_cursor (GTK_WIDGET (self)); + gtk_text_add_context_actions (self); } static void @@ -1791,6 +1766,10 @@ gtk_text_dispose (GObject *object) keymap = gdk_display_get_keymap (gtk_widget_get_display (GTK_WIDGET (object))); g_signal_handlers_disconnect_by_func (keymap, keymap_direction_changed, self); + g_clear_object (&priv->context_actions); + g_clear_pointer (&priv->popup_menu, gtk_widget_unparent); + g_clear_object (&priv->extra_menu); + G_OBJECT_CLASS (gtk_text_parent_class)->dispose (object); } @@ -2056,12 +2035,6 @@ gtk_text_unrealize (GtkWidget *widget) if (gdk_clipboard_get_content (clipboard) == priv->selection_content) gdk_clipboard_set_content (clipboard, NULL); - if (priv->popup_menu) - { - gtk_widget_destroy (priv->popup_menu); - priv->popup_menu = NULL; - } - GTK_WIDGET_CLASS (gtk_text_parent_class)->unrealize (widget); } @@ -2200,6 +2173,9 @@ gtk_text_size_allocate (GtkWidget *widget, if (priv->magnifier_popover) gtk_native_check_resize (GTK_NATIVE (priv->magnifier_popover)); + + if (priv->popup_menu) + gtk_native_check_resize (GTK_NATIVE (priv->popup_menu)); } static void @@ -2449,6 +2425,40 @@ gesture_get_current_point_in_layout (GtkGestureSingle *gesture, } static void +gtk_text_do_popup (GtkText *self, + double x, + double y) +{ + GtkTextPrivate *priv = gtk_text_get_instance_private (self); + + gtk_text_update_clipboard_actions (self); + + if (!priv->popup_menu) + { + GMenuModel *model; + + model = gtk_text_get_menu_model (self); + priv->popup_menu = gtk_popover_menu_new_from_model (GTK_WIDGET (self), model); + gtk_popover_set_position (GTK_POPOVER (priv->popup_menu), GTK_POS_BOTTOM); + + gtk_popover_set_has_arrow (GTK_POPOVER (priv->popup_menu), FALSE); + gtk_widget_set_halign (priv->popup_menu, GTK_ALIGN_START); + + g_object_unref (model); + } + + if (x != -1 && y != -1) + { + GdkRectangle rect = { x, y, 1, 1 }; + gtk_popover_set_pointing_to (GTK_POPOVER (priv->popup_menu), &rect); + } + else + gtk_popover_set_pointing_to (GTK_POPOVER (priv->popup_menu), NULL); + + gtk_popover_popup (GTK_POPOVER (priv->popup_menu)); +} + +static void gtk_text_click_gesture_pressed (GtkGestureClick *gesture, int n_press, double widget_x, @@ -2483,7 +2493,7 @@ gtk_text_click_gesture_pressed (GtkGestureClick *gesture, if (gdk_event_triggers_context_menu (event)) { - gtk_text_do_popup (self, event); + gtk_text_do_popup (self, x, y); } else if (n_press == 1 && button == GDK_BUTTON_MIDDLE && get_middle_click_paste (self)) @@ -5590,155 +5600,219 @@ gtk_text_set_alignment (GtkText *self, } } -/* Quick hack of a popup menu - */ static void -activate_cb (GtkWidget *menuitem, - GtkText *self) +hide_selection_bubble (GtkText *self) { - const char *signal; + GtkTextPrivate *priv = gtk_text_get_instance_private (self); - signal = g_object_get_qdata (G_OBJECT (menuitem), quark_gtk_signal); - g_signal_emit_by_name (self, signal); + if (priv->selection_bubble && gtk_widget_get_visible (priv->selection_bubble)) + gtk_widget_hide (priv->selection_bubble); } - -static gboolean -gtk_text_mnemonic_activate (GtkWidget *widget, - gboolean group_cycling) +static void +cut_clipboard_activated (GSimpleAction *action, + GVariant *parameter, + gpointer user_data) { - gtk_widget_grab_focus (widget); - return GDK_EVENT_STOP; + g_signal_emit_by_name (user_data, "cut-clipboard"); + hide_selection_bubble (GTK_TEXT (user_data)); } static void -append_action_signal (GtkText *self, - GtkWidget *menu, - const char *label, - const char *signal, - gboolean sensitive) +copy_clipboard_activated (GSimpleAction *action, + GVariant *parameter, + gpointer user_data) { - GtkWidget *menuitem = gtk_menu_item_new_with_mnemonic (label); + g_signal_emit_by_name (user_data, "copy-clipboard"); + hide_selection_bubble (GTK_TEXT (user_data)); +} - g_object_set_qdata (G_OBJECT (menuitem), quark_gtk_signal, (char *)signal); - g_signal_connect (menuitem, "activate", - G_CALLBACK (activate_cb), self); +static void +paste_clipboard_activated (GSimpleAction *action, + GVariant *parameter, + gpointer user_data) +{ + g_signal_emit_by_name (user_data, "paste-clipboard"); + hide_selection_bubble (GTK_TEXT (user_data)); +} - gtk_widget_set_sensitive (menuitem, sensitive); - - gtk_widget_show (menuitem); - gtk_menu_shell_append (GTK_MENU_SHELL (menu), menuitem); +static void +delete_selection_activated (GSimpleAction *action, + GVariant *parameter, + gpointer user_data) +{ + gtk_text_delete_cb (GTK_TEXT (user_data)); + hide_selection_bubble (GTK_TEXT (user_data)); } - + static void -popup_menu_detach (GtkWidget *attach_widget, - GtkMenu *menu) +select_all_activated (GSimpleAction *action, + GVariant *parameter, + gpointer user_data) { - GtkText *self_attach = GTK_TEXT (attach_widget); - GtkTextPrivate *priv_attach = gtk_text_get_instance_private (self_attach); + gtk_text_select_all (GTK_TEXT (user_data)); +} - priv_attach->popup_menu = NULL; +static void +insert_emoji_activated (GSimpleAction *action, + GVariant *parameter, + gpointer user_data) +{ + gtk_text_insert_emoji (GTK_TEXT (user_data)); + hide_selection_bubble (GTK_TEXT (user_data)); } static void -gtk_text_do_popup (GtkText *self, - const GdkEvent *event) +gtk_text_add_context_actions (GtkText *self) { GtkTextPrivate *priv = gtk_text_get_instance_private (self); - GdkEvent *trigger_event; - /* In order to know what entries we should make sensitive, we - * ask for the current targets of the clipboard, and when - * we get them, then we actually pop up the menu. - */ - trigger_event = event ? gdk_event_copy (event) : gtk_get_current_event (); + GActionEntry entries[] = { + { "cut-clipboard", cut_clipboard_activated, NULL, NULL, NULL }, + { "copy-clipboard", copy_clipboard_activated, NULL, NULL, NULL }, + { "paste-clipboard", paste_clipboard_activated, NULL, NULL, NULL }, + { "delete-selection", delete_selection_activated, NULL, NULL, NULL }, + { "select-all", select_all_activated, NULL, NULL, NULL }, + { "insert-emoji", insert_emoji_activated, NULL, NULL, NULL }, + }; - if (gtk_widget_get_realized (GTK_WIDGET (self))) - { - DisplayMode mode; - gboolean clipboard_contains_text; - GtkWidget *menu; - GtkWidget *menuitem; + GSimpleActionGroup *actions = g_simple_action_group_new (); + GAction *action; - clipboard_contains_text = gdk_content_formats_contain_gtype (gdk_clipboard_get_formats (gtk_widget_get_clipboard (GTK_WIDGET (self))), - G_TYPE_STRING); - if (priv->popup_menu) - gtk_widget_destroy (priv->popup_menu); + priv->context_actions = G_ACTION_MAP (actions); - priv->popup_menu = menu = gtk_menu_new (); - gtk_style_context_add_class (gtk_widget_get_style_context (menu), - GTK_STYLE_CLASS_CONTEXT_MENU); + g_action_map_add_action_entries (G_ACTION_MAP (actions), entries, G_N_ELEMENTS (entries), self); - gtk_menu_attach_to_widget (GTK_MENU (menu), GTK_WIDGET (self), popup_menu_detach); + action = g_action_map_lookup_action (G_ACTION_MAP (actions), "cut-clipboard"); + g_simple_action_set_enabled (G_SIMPLE_ACTION (action), FALSE); + action = g_action_map_lookup_action (G_ACTION_MAP (actions), "copy-clipboard"); + g_simple_action_set_enabled (G_SIMPLE_ACTION (action), FALSE); + action = g_action_map_lookup_action (G_ACTION_MAP (actions), "paste-clipboard"); + g_simple_action_set_enabled (G_SIMPLE_ACTION (action), FALSE); + action = g_action_map_lookup_action (G_ACTION_MAP (actions), "delete-selection"); + g_simple_action_set_enabled (G_SIMPLE_ACTION (action), FALSE); + action = g_action_map_lookup_action (G_ACTION_MAP (actions), "select-all"); + g_simple_action_set_enabled (G_SIMPLE_ACTION (action), FALSE); + action = g_action_map_lookup_action (G_ACTION_MAP (actions), "insert-emoji"); + g_simple_action_set_enabled (G_SIMPLE_ACTION (action), FALSE); - mode = gtk_text_get_display_mode (self); - append_action_signal (self, menu, _("Cu_t"), "cut-clipboard", - priv->editable && mode == DISPLAY_NORMAL && - priv->current_pos != priv->selection_bound); + gtk_widget_insert_action_group (GTK_WIDGET (self), "context", G_ACTION_GROUP (actions)); +} - append_action_signal (self, menu, _("_Copy"), "copy-clipboard", - mode == DISPLAY_NORMAL && - priv->current_pos != priv->selection_bound); +static void +gtk_text_update_clipboard_actions (GtkText *self) + { + GtkTextPrivate *priv = gtk_text_get_instance_private (self); + DisplayMode mode; + GdkClipboard *clipboard; + gboolean has_clipboard; + GAction *action; + + clipboard = gtk_widget_get_clipboard (GTK_WIDGET (self)); + has_clipboard = gdk_content_formats_contain_gtype (gdk_clipboard_get_formats (clipboard), G_TYPE_STRING); + mode = gtk_text_get_display_mode (self); - append_action_signal (self, menu, _("_Paste"), "paste-clipboard", - priv->editable && clipboard_contains_text); + action = g_action_map_lookup_action (priv->context_actions, "cut-clipboard"); + g_simple_action_set_enabled (G_SIMPLE_ACTION (action), + mode == DISPLAY_NORMAL && + priv->editable && + priv->current_pos != priv->selection_bound); - menuitem = gtk_menu_item_new_with_mnemonic (_("_Delete")); - gtk_widget_set_sensitive (menuitem, priv->editable && priv->current_pos != priv->selection_bound); - g_signal_connect_swapped (menuitem, "activate", - G_CALLBACK (gtk_text_delete_cb), self); - gtk_widget_show (menuitem); - gtk_menu_shell_append (GTK_MENU_SHELL (menu), menuitem); + action = g_action_map_lookup_action (priv->context_actions, "copy-clipboard"); + g_simple_action_set_enabled (G_SIMPLE_ACTION (action), + mode == DISPLAY_NORMAL && + priv->current_pos != priv->selection_bound); - menuitem = gtk_separator_menu_item_new (); - gtk_widget_show (menuitem); - gtk_menu_shell_append (GTK_MENU_SHELL (menu), menuitem); + action = g_action_map_lookup_action (priv->context_actions, "paste-clipboard"); + g_simple_action_set_enabled (G_SIMPLE_ACTION (action), + priv->editable && has_clipboard); - menuitem = gtk_menu_item_new_with_mnemonic (_("Select _All")); - gtk_widget_set_sensitive (menuitem, gtk_entry_buffer_get_length (priv->buffer) > 0); - g_signal_connect_swapped (menuitem, "activate", - G_CALLBACK (gtk_text_select_all), self); - gtk_widget_show (menuitem); - gtk_menu_shell_append (GTK_MENU_SHELL (menu), menuitem); + action = g_action_map_lookup_action (priv->context_actions, "delete-selection"); + g_simple_action_set_enabled (G_SIMPLE_ACTION (action), + priv->editable && + priv->current_pos != priv->selection_bound); - if ((gtk_text_get_input_hints (self) & GTK_INPUT_HINT_NO_EMOJI) == 0) - { - menuitem = gtk_menu_item_new_with_mnemonic (_("Insert _Emoji")); - gtk_widget_set_sensitive (menuitem, - mode == DISPLAY_NORMAL && - priv->editable); - g_signal_connect_swapped (menuitem, "activate", - G_CALLBACK (gtk_text_insert_emoji), self); - gtk_widget_show (menuitem); - gtk_menu_shell_append (GTK_MENU_SHELL (menu), menuitem); - } + action = g_action_map_lookup_action (priv->context_actions, "select-all"); + g_simple_action_set_enabled (G_SIMPLE_ACTION (action), + priv->buffer && (gtk_entry_buffer_get_length (priv->buffer) > 0)); +} - g_signal_emit (self, signals[POPULATE_POPUP], 0, menu); +static void +gtk_text_update_emoji_action (GtkText *self) +{ + GtkTextPrivate *priv = gtk_text_get_instance_private (self); + GAction *action; - if (trigger_event && gdk_event_triggers_context_menu (trigger_event)) - gtk_menu_popup_at_pointer (GTK_MENU (menu), trigger_event); - else - { - gtk_menu_popup_at_widget (GTK_MENU (menu), - GTK_WIDGET (self), - GDK_GRAVITY_SOUTH_EAST, - GDK_GRAVITY_NORTH_WEST, - trigger_event); + action = g_action_map_lookup_action (priv->context_actions, "insert-emoji"); + g_simple_action_set_enabled (G_SIMPLE_ACTION (action), + (gtk_text_get_input_hints (self) & GTK_INPUT_HINT_NO_EMOJI) == 0); +} - gtk_menu_shell_select_first (GTK_MENU_SHELL (menu), FALSE); - } - } +static GMenuModel * +gtk_text_get_menu_model (GtkText *self) +{ + GtkTextPrivate *priv = gtk_text_get_instance_private (self); + GMenu *menu, *section; + GMenuItem *item; + + menu = g_menu_new (); + + section = g_menu_new (); + item = g_menu_item_new (_("Cu_t"), "context.cut-clipboard"); + g_menu_item_set_attribute (item, "touch-icon", "s", "edit-cut-symbolic"); + g_menu_append_item (section, item); + g_object_unref (item); + item = g_menu_item_new (_("_Copy"), "context.copy-clipboard"); + g_menu_item_set_attribute (item, "touch-icon", "s", "edit-copy-symbolic"); + g_menu_append_item (section, item); + g_object_unref (item); + item = g_menu_item_new (_("_Paste"), "context.paste-clipboard"); + g_menu_item_set_attribute (item, "touch-icon", "s", "edit-paste-symbolic"); + g_menu_append_item (section, item); + g_object_unref (item); + item = g_menu_item_new (_("_Delete"), "context.delete-selection"); + g_menu_item_set_attribute (item, "touch-icon", "s", "edit-delete-symbolic"); + g_menu_append_item (section, item); + g_object_unref (item); + g_menu_append_section (menu, NULL, G_MENU_MODEL (section)); + g_object_unref (section); + + section = g_menu_new (); + + item = g_menu_item_new (_("Select _All"), "context.select-all"); + g_menu_item_set_attribute (item, "touch-icon", "s", "edit-select-all-symbolic"); + g_menu_append_item (section, item); + g_object_unref (item); + + item = g_menu_item_new ( _("Insert _Emoji"), "context.insert-emoji"); + g_menu_item_set_attribute (item, "hidden-when", "s", "action-disabled"); + g_menu_item_set_attribute (item, "touch-icon", "s", "face-smile-symbolic"); + g_menu_append_item (section, item); + g_object_unref (item); + g_menu_append_section (menu, NULL, G_MENU_MODEL (section)); + g_object_unref (section); - g_clear_object (&trigger_event); + if (priv->extra_menu) + g_menu_append_section (menu, NULL, priv->extra_menu); + + return G_MENU_MODEL (menu); } static gboolean -gtk_text_popup_menu (GtkWidget *widget) +gtk_text_mnemonic_activate (GtkWidget *widget, + gboolean group_cycling) { - gtk_text_do_popup (GTK_TEXT (widget), NULL); + gtk_widget_grab_focus (widget); return GDK_EVENT_STOP; } +static gboolean +gtk_text_popup_menu (GtkWidget *widget) +{ + gtk_text_do_popup (GTK_TEXT (widget), -1, -1); + return TRUE; +} + static void show_or_hide_handles (GtkWidget *popover, GParamSpec *pspec, @@ -5766,40 +5840,56 @@ show_or_hide_handles (GtkWidget *popover, } static void -activate_bubble_cb (GtkWidget *item, - GtkText *self) +append_bubble_item (GtkText *self, + GtkWidget *toolbar, + GMenuModel *model, + int index) { GtkTextPrivate *priv = gtk_text_get_instance_private (self); - const char *signal; + GtkWidget *item, *image; + GVariant *att; + const char *icon_name; + const char *action_name; + GAction *action; + GMenuModel *link; + + link = g_menu_model_get_item_link (model, index, "section"); + if (link) + { + int i; + for (i = 0; i < g_menu_model_get_n_items (link); i++) + append_bubble_item (self, toolbar, link, i); + g_object_unref (link); + return; + } - signal = g_object_get_qdata (G_OBJECT (item), quark_gtk_signal); - gtk_widget_hide (priv->selection_bubble); - if (strcmp (signal, "select-all") == 0) - gtk_text_select_all (self); - else - g_signal_emit_by_name (self, signal); -} + att = g_menu_model_get_item_attribute_value (model, index, "touch-icon", G_VARIANT_TYPE_STRING); + if (att == NULL) + return; -static void -append_bubble_action (GtkText *self, - GtkWidget *toolbar, - const char *label, - const char *icon_name, - const char *signal, - gboolean sensitive) -{ - GtkWidget *item, *image; + icon_name = g_variant_get_string (att, NULL); + g_variant_unref (att); + + att = g_menu_model_get_item_attribute_value (model, index, "action", G_VARIANT_TYPE_STRING); + if (att == NULL) + return; + action_name = g_variant_get_string (att, NULL); + g_variant_unref (att); + + if (g_str_has_prefix (action_name, "context.")) + { + action = g_action_map_lookup_action (priv->context_actions, action_name + strlen ("context.")); + if (action && !g_action_get_enabled (action)) + return; + } item = gtk_button_new (); gtk_widget_set_focus_on_click (item, FALSE); image = gtk_image_new_from_icon_name (icon_name); gtk_widget_show (image); gtk_container_add (GTK_CONTAINER (item), image); - gtk_widget_set_tooltip_text (item, label); gtk_style_context_add_class (gtk_widget_get_style_context (item), "image-button"); - g_object_set_qdata (G_OBJECT (item), quark_gtk_signal, (char *)signal); - g_signal_connect (item, "clicked", G_CALLBACK (activate_bubble_cb), self); - gtk_widget_set_sensitive (GTK_WIDGET (item), sensitive); + gtk_actionable_set_action_name (GTK_ACTIONABLE (item), action_name); gtk_widget_show (GTK_WIDGET (item)); gtk_container_add (GTK_CONTAINER (toolbar), item); } @@ -5811,21 +5901,19 @@ gtk_text_selection_bubble_popup_show (gpointer user_data) GtkTextPrivate *priv = gtk_text_get_instance_private (self); cairo_rectangle_int_t rect; GtkAllocation allocation; - int start_x, end_x; gboolean has_selection; - gboolean has_clipboard; - gboolean all_selected; - DisplayMode mode; + int start_x, end_x; GtkWidget *box; GtkWidget *toolbar; - int length; GtkAllocation text_allocation; + GMenuModel *model; + int i; + + gtk_text_update_clipboard_actions (self); gtk_text_get_text_allocation (self, &text_allocation); has_selection = priv->selection_bound != priv->current_pos; - length = gtk_entry_buffer_get_length (get_buffer (self)); - all_selected = (priv->selection_bound == 0) && (priv->current_pos == length); if (!has_selection && !priv->editable) { @@ -5851,24 +5939,12 @@ gtk_text_selection_bubble_popup_show (gpointer user_data) gtk_container_add (GTK_CONTAINER (priv->selection_bubble), box); gtk_container_add (GTK_CONTAINER (box), toolbar); - has_clipboard = gdk_content_formats_contain_gtype (gdk_clipboard_get_formats (gtk_widget_get_clipboard (GTK_WIDGET (self))), - G_TYPE_STRING); - mode = gtk_text_get_display_mode (self); - - if (priv->editable && has_selection && mode == DISPLAY_NORMAL) - append_bubble_action (self, toolbar, _("Select all"), "edit-select-all-symbolic", "select-all", !all_selected); - - if (priv->editable && has_selection && mode == DISPLAY_NORMAL) - append_bubble_action (self, toolbar, _("Cut"), "edit-cut-symbolic", "cut-clipboard", TRUE); + model = gtk_text_get_menu_model (self); - if (has_selection && mode == DISPLAY_NORMAL) - append_bubble_action (self, toolbar, _("Copy"), "edit-copy-symbolic", "copy-clipboard", TRUE); + for (i = 0; i < g_menu_model_get_n_items (model); i++) + append_bubble_item (self, toolbar, model, i); - if (priv->editable) - append_bubble_action (self, toolbar, _("Paste"), "edit-paste-symbolic", "paste-clipboard", has_clipboard); - - if (priv->populate_all) - g_signal_emit (self, signals[POPULATE_POPUP], 0, box); + g_object_unref (model); gtk_widget_get_allocation (GTK_WIDGET (self), &allocation); @@ -5944,7 +6020,7 @@ gtk_text_drag_begin (GtkWidget *widget, text = _gtk_text_get_selected_text (self); - if (text) + if (self) { int *ranges, n_ranges; GdkPaintable *paintable; @@ -6492,6 +6568,7 @@ gtk_text_set_input_hints (GtkText *self, NULL); g_object_notify_by_pspec (G_OBJECT (self), text_props[PROP_INPUT_HINTS]); + gtk_text_update_emoji_action (self); } } @@ -6686,3 +6763,45 @@ gtk_text_get_key_controller (GtkText *self) return priv->key_controller; } + +/** + * gtk_text_set_extra_menu: + * @self: a #GtkText + * @model: (allow-none): a #GMenuModel + * + * Sets a menu model to add when constructing + * the context menu for @self. + */ +void +gtk_text_set_extra_menu (GtkText *self, + GMenuModel *model) +{ + GtkTextPrivate *priv = gtk_text_get_instance_private (self); + + g_return_if_fail (GTK_IS_TEXT (self)); + + if (g_set_object (&priv->extra_menu, model)) + { + g_clear_pointer (&priv->popup_menu, gtk_widget_unparent); + + g_object_notify_by_pspec (G_OBJECT (self), text_props[PROP_EXTRA_MENU]); + } +} + +/** + * gtk_text_get_extra_menu: + * @self: a #GtkText + * + * Gets the menu model set with gtk_text_set_extra_menu(). + * + * Returns: (transfer none): (nullable): the menu model + */ +GMenuModel * +gtk_text_get_extra_menu (GtkText *self) +{ + GtkTextPrivate *priv = gtk_text_get_instance_private (self); + + g_return_val_if_fail (GTK_IS_TEXT (self), NULL); + + return priv->extra_menu; +} diff --git a/gtk/gtktext.h b/gtk/gtktext.h index 9907daee9d..025fdd3d89 100644 --- a/gtk/gtktext.h +++ b/gtk/gtktext.h @@ -134,6 +134,12 @@ PangoTabArray * gtk_text_get_tabs (GtkText *self); GDK_AVAILABLE_IN_ALL void gtk_text_grab_focus_without_selecting (GtkText *self); +GDK_AVAILABLE_IN_ALL +void gtk_text_set_extra_menu (GtkText *self, + GMenuModel *model); +GDK_AVAILABLE_IN_ALL +GMenuModel * gtk_text_get_extra_menu (GtkText *self); + G_END_DECLS #endif /* __GTK_TEXT_H__ */ diff --git a/gtk/gtktextprivate.h b/gtk/gtktextprivate.h index ec1d0af6d2..daeed71bce 100644 --- a/gtk/gtktextprivate.h +++ b/gtk/gtktextprivate.h @@ -34,9 +34,6 @@ typedef struct _GtkTextClass GtkTextClass; /*<private> * GtkTextClass: * @parent_class: The parent class. - * @populate_popup: Class handler for the #GtkText::populate-popup signal. If - * non-%NULL, this will be called to add additional entries to the context - * menu when it is displayed. * @activate: Class handler for the #GtkText::activate signal. The default * implementation activates the gtk.activate-default action. * @move_cursor: Class handler for the #GtkText::move-cursor signal. The @@ -70,10 +67,6 @@ struct _GtkTextClass { GtkWidgetClass parent_class; - /* Hook to customize right-click popup */ - void (* populate_popup) (GtkText *self, - GtkWidget *popup); - /* Action signals */ void (* activate) (GtkText *self); |