[gtk/wip/matthiasc/context-menu: 1/13] text, entry: Implement context menu api



commit a28d5d188882a5ca6998a8fcca0e23ea4515d3f5
Author: Matthias Clasen <mclasen redhat com>
Date:   Mon Apr 8 04:27:59 2019 +0000

    text, entry: Implement context menu api
    
    Drop the ::populate-popup signal and implement
    the new context menu api.

 docs/reference/gtk/gtk4-sections.txt |   4 +
 gtk/gtkentry.c                       |  69 +++++
 gtk/gtkentry.h                       |   6 +
 gtk/gtktext.c                        | 557 +++++++++++++++++++++--------------
 gtk/gtktext.h                        |   6 +
 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
@@ -2448,6 +2424,40 @@ gesture_get_current_point_in_layout (GtkGestureSingle *gesture,
     *y = py - ty;
 }
 
+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,
@@ -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);


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]