Confused about GMenuModel and GtkMenu

Hi,

In my application, I am trying to create a new GtkPopover and connect it to a GtkTextView widget, where the GtkPopover is shown when the user right-clicks over some selected text.

However, I am confused about the process of creating the content of the GtkPopover. From the documentation of the widget, it seem to require a GMenuModel object that defines the menu. In the process of researching how that works, I got myself confused about the following:

  • How is a GMenuModel defined in the context of GtkBuildable. I know that that I can define a GtkMenu by using the <menu> definition. Is the same keyword used for GMenuModel?
  • Since GtkTextView does not implement GtkActionable, I can’t define a new GActionGroup for the widget. On the other hand, it seems that GMenuItems only allow for actions and I can’t connect to their activate signal since they don’t have any signals. How can I use GMenuItems with widgets that don’t implement GtkActionable?
  • All of this would be a lot simpler if I could convert a GMenuItem to a GtkMenuItem. However, I can’t find a way to do so. Is there such a way?

Thank you for your help.

In gtk3, it is probably easier to stick with GtkMenu for the context menu of a GtkTextView. You can connect to the populate-popup signal to insert additional GtkMenuItems. (See the documentation for that signal for a note about supporting touch popups as well). In gtk4, that signal is gone, replaced by set_extra_menu() which takes a GMenuModel.

<menu> actually creates a GMenu (an implementation of GMenuModel), not a GtkMenu. Because GMenu is in GLib, it’s can’t implement GtkBuildable, so GtkBuilder supports it directly.

GtkActionable allows a widget (e.g. a GtkButton) to activate an action when the widget itself is activated. For a GMenuModel, GtkActionable is implemented by the widgets built from the model (GtkMenuItem or GtkModelButton)

You can’t do that, but you can insert the GMenuItem into a GMenu , then use gtk_menu_new_from_model() to make a GtkMenu. Note that when you insert a GMenuItem, you are actually only copying its attributes (e.g. label) and links (e.g. submenu) into the menu at the specified position. The GMenuItem serves no purpose afterward.

1 Like

Hi, thank you for the response.

I played with the populate-popup signal. However, it seem that it’s designed only for extending a popup, not for editing. Of course, I can completely “re-build” the popup content but the signal, itself, and the GtkMenu API provides very in the way of being able to examine the already existing elements of the menu.

For example, the GtkTextView that I am using is read-only. Therefore, I have no use for the default “Paste” or “Insert Emoji” menu items. However, there is no reliable way to remove those. I could do string compares on the menu item labels but that is not reliable as I have to figure out where the accelerator symbol (“_”) is. Also, for a different locale, the string compare is completely broken.

Approaching it from the other end, I can’t even figure out what the connected handler for the menu item is so I can filter by that.

I am a bit confused. Obviously, GtkTextView cannot be activated. So, I am assuming that I can’t add an action group to it with gtk_widget_insert_action_group() so as to be able to define an action like my-text-view.custom-action?

You can set editable to false, which should at least make those items insensitive.

Those are separate concepts. Adding an action group to a widget provides actions that can be used by Actionable widgets that are descendants of it. Here is an example of a TextView subclass with an action group and custom popover. The popup menu logic is based on this.

#!/usr/bin/python3

import gi
gi.require_version('Gtk', '3.0')

from gi.repository import Gio, Gtk, Gdk

class PopView(Gtk.TextView):
    def __init__(self):
        super().__init__()
        
        menu = Gio.Menu()
        menu.append("Copy", "text.copy")
        menu.append("Paste", "text.paste")
        
        self.popover = Gtk.Popover.new_from_model(self, menu)
        
        group = Gio.SimpleActionGroup()
        
        action = Gio.SimpleAction.new("copy", None)
        action.connect("activate", self.on_copy)
        group.add_action(action)
        
        action = Gio.SimpleAction.new("paste", None)
        action.connect("activate", self.on_paste)
        group.add_action(action)
        
        self.insert_action_group("text", group)

    def on_copy(self, *args):
        print("COPY")
        
    def on_paste(self, *args):
        print("PASTE")
        
    def do_popup_menu(self, event=None):
        if event is not None:
            rect = Gdk.Rectangle()
            rect.x, rect.y = event.x, event.y
            self.popover.set_pointing_to(rect)
            
        self.popover.popup()
        return True
            
    def do_button_press_event(self, event):
        if event.triggers_context_menu() and event.type == Gdk.EventType.BUTTON_PRESS:
            self.do_popup_menu(event)
            return Gdk.EVENT_STOP
        else:
            return Gtk.TextView.do_button_press_event(self, event)

This is exactly what I was looking for! Thank you. The GtkActionable concept was confusing to me and your example clarifies it.

Yes, it does make them insensitive but that’s just a waste of screen space as they will never be sensitive and, therefore, useful.

If you want to pop up your own menu you’ll have to override the button-press-event handler in GtkTextView, by connecting to the signal, building and popping up your own menu, and then returning GDK_EVENT_STOP or True.

1 Like

Hi, thank you for the response. I am not sure if it wasn’t clear in my original post but I am already doing that to replace the existing GtkPopup with a GtkPopover.

However, the secondary point that we were discussing was why I had chosen to do this to begin with. There doesn’t seem to be good way to edit a GtkMenu. The populate-popup signal is a great way to extend a GtkMenu but, as I said, if I wanted to remove some elements, I am totally out of luck.

A menu created by GTK is not for you to change. You can add to it, but you cannot modify things that GTK did internally.

Having said that, a GtkMenu is a GtkContainer, so you could go around and rummage in the list of children. There’s no guarantee of stability if you do that, though, so you get to keep both the pieces when things break.

Admittedly, I am just a novice at writing GTK application but I am not sure that I agree with this philosophy. I can agree with applying it to internal GTK/GLib state. However, when it comes to widgets, ultimately, it’s my application with my functionality so GTK should not impose widget content that my application may not need (like a “Paste” option in a non-editable GtkTextView).

Now, I know the counter argument - the “Paste” option is insensitive when the GtkTextView is not editable, or “build your own menu”. However, my main motivation for even attempting to “edit” the GtkMenu was that I don’t want to replicate work. While I don’t want the “Paste” option because the GtkTextView will never be editable, I do want the “Copy” and “Select All” option. So, I didn’t want to throw away all the work that GTK was done to setup the clipboard and all the other internal plumbing to get copy/paste to work just so I can redo it again.

Could you please explain how GTK will break if I did remove random elements of the GtkMenu inside a GtkPopup? In other words, why can’t I change the GtkMenu? If there are technical reasons why and not just “because I said so”, they should be stated in the documentation. (If they already are somewhere, that’s great. I didn’t find anything.)

Yes, I am aware and I’ve already mentioned that I have experimented with looking at the GtkMenuItems inside it. However, there is absolutely no good way to look through them in order to decide what to keep and what to remove.

You will have a hard time with any toolkit you did not write yourself. Then, once you publish that toolkit for somebody else to use, you’ll realise that maintaining an API that allows people to pick apart whatever you made is impossible, and will just lead to a broken mess.

Well, the people that made the toolkit decided, and since you’re using the toolkit you are, by definition, abiding to their decisions. Decisions, by the way, that they are in no way, shape, or form required to justify to you or anybody else. The documentation is the existing API documentation, not a place to renegotiate every single decision made in 20+ years just because somebody wants something that is currently not possible. What purpose would be served by explaining “why” you can’t rummage inside the widgets GTK created internally? You can’t.

Anyway, the “technical reason” is that every menu has state; it has callbacks, actions, and other ancillary data attached to it. Changing things underneath the toolkit breaks assumptions made by the toolkit itself. It presents a broken API to the application developers; it presents a broken UI to the users, who now have to deal with a widget that doesn’t behave like every other widget.

Another “technical reason” for not hiding the Paste menu when a GtkTextView is not editable is that the editable property can be toggled while the menu is open, and that would lead to menu items appearing and disappearing. Or that you could still have the ability to paste content in a non-editable text view.

The only maintainable escape hatch at your disposal is to take full control of the menu, and create your own; in essence, telling GTK that “this is my app”.

As I said above, if you want to be in control of the menu, you get to create your own menu, and prevent GTK from presenting its own menu to the user.

Alternatively, you can open an issue and try to convince the GTK developers and designers to hide the Paste menu if the GtkTextView is not editable.

1 Like

In the context of a toolkit/library/service, there are so many things wrong with this statement. I am not sure if this is the overall attitude of the GNOME project developers but it’s disappointing if it were.

No one thinks/said the documentation is a place to negotiate. However, it absolutely is a place where the workings of a widget are/should be documented, including limitations that developers should know about. Please, show me where it says that I can’t modify a GtkMenu? The closest I’ve been able to find is this:

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 items to the popup , which will be a GtkMenu in this case.

If “populate-all” 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.

(emphasis mine)

It’s always “my app”. Whether I am using the pre-built GtkMenu or I am creating my own, it’s still “my app”. Just like it is “my app” when I don’t provide a way for the application or the user to change the “editable” property of the GtkTextView thus making it non-editable for the life of the application. So, the fact that the API provides a way to change the property is irrelevant in the context of my usage of the widget.

Thank you for the offer but I don’t think that I’ll be doing that. It just seems like (at least, in this particular case) the stance is “our way or the highway.” I just don’t want to deal with that. I will modify my app within the confines that you’ve outlined and will leave the rest of this thread alone.

Again, thank you for your input.

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.