DobbyTheElf
DobbyTheElf

Reputation: 789

Linking GMenuModel to actions for context menus in Python Gtk3

I've built an application using Gtk3 and Gtk.Builder, using GMenuModel for the menus. So far so good. Now I'd like to add context (i.e. right-click) menus.

The menus themselves appear, but as I can't seem to find the right incantation to link the actions, they are always ghosted. Here is my toy code example:

import sys
import os
import gi

gi.require_version("Gtk", "3.0")
from gi.repository import GLib, Gio, Gtk

_curr_dir = os.path.split(__file__)[0]

# This would typically be its own file
MENU_XML = """
<?xml version="1.0" encoding="UTF-8"?>
<interface>
  <menu id="context_menu">
    <section>
      <item>
        <attribute name="label" translatable="yes">Option 1</attribute>
        <attribute name="action">app.option1</attribute>
      </item>
      <item>
        <attribute name="label" translatable="yes">Option 2</attribute>
        <attribute name="action">app.option2</attribute>
      </item>
    </section>
  </menu>
</interface>
"""


class AppWindow(Gtk.ApplicationWindow):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        self.set_default_size(400, 300)
        self.connect("destroy", Gtk.main_quit)

        # Load the UI from the Glade file
        builder = Gtk.Builder()
        builder = Gtk.Builder.new_from_string(MENU_XML, -1)

        # Get the context menu defined in the XML
        self.context_menu = Gtk.Menu.new_from_model(builder.get_object("context_menu"))

        # Add a simple label
        self.label = Gtk.Label(label="Right-click to open context menu")
        self.add(self.label)
        self.label.show()
        # Connect the button press event (right-click)
        self.connect("button-press-event", self.on_right_click)

    def on_right_click(self, widget, event):
        # Check if it's the right-click (button 3)
        if event.button == 3:
            # Show the context menu
            # self.context_menu.popup(None, None, None, None, event.button, event.time)
            self.context_menu.show_all()
            self.context_menu.popup_at_pointer(event)


class Application(Gtk.Application):

    def __init__(self, *args, **kwargs):
        super().__init__(
            *args,
            application_id="org.example.myapp",
            **kwargs
        )
        self.window = None

    def do_startup(self):
        Gtk.Application.do_startup(self)
        action = Gio.SimpleAction.new("option1", None)
        action.connect("activate", self.on_option1_activated)
        self.add_action(action)
        action = Gio.SimpleAction.new("option2", None)
        action.connect("activate", self.on_option2_activated)
        self.add_action(action)
        builder = Gtk.Builder.new_from_string(MENU_XML, -1)
        self.set_app_menu(builder.get_object("app-menu"))

    def do_activate(self):

        # We only allow a single window and raise any existing ones
        if not self.window:

            # Windows are associated with the application
            # when the last one is closed the application shuts down
            self.window = AppWindow(application=self, title="Main Window")

        self.window.present()

    def on_quit(self, action, param):
        self.quit()

    def on_option1_activated(self, widget):
        print("Option 1 selected")

    def on_option2_activated(self, widget):
        print("Option 2 selected")


if __name__ == "__main__":
    app = Application()
    app.run()

How can I link up the actions so that I can use the menu?

Upvotes: 0

Views: 33

Answers (1)

DobbyTheElf
DobbyTheElf

Reputation: 789

This is my current workaround - not an answer I like, because it doesn't use the actions directly:

import sys
import os
import gi

gi.require_version("Gtk", "3.0")
from gi.repository import GLib, Gio, Gtk

_curr_dir = os.path.split(__file__)[0]

# This would typically be its own file
MENU_XML = """
<?xml version="1.0" encoding="UTF-8"?>
<interface>
  <object class="GtkMenu" id="context_menu">
    <child>
      <object class="GtkMenuItem" id="option1">
        <property name="label">Option 1</property>
        <signal name="activate" handler="on_option1_activated"/>
      </object>
    </child>
    <child>
      <object class="GtkMenuItem" id="option2">
        <property name="label">Option 2</property>
        <signal name="activate" handler="on_option2_activated"/>
      </object>
    </child>
  </object>
</interface>
"""


class RightClickMenu(Gtk.Window):
    def __init__(self):
        super().__init__(title="Right-Click Context Menu")

        self.set_default_size(400, 300)
        self.connect("destroy", Gtk.main_quit)

        # Load the UI from the Glade file
        builder = Gtk.Builder()
        builder = Gtk.Builder.new_from_string(MENU_XML, -1)

        # Get the context menu defined in the XML
        self.context_menu = builder.get_object("context_menu")

        # Connect the signals for menu item activation
        builder.connect_signals(self)

        # Add a simple label
        self.label = Gtk.Label(label="Right-click to open context menu")
        self.add(self.label)

        # Connect the button press event (right-click)
        self.connect("button-press-event", self.on_right_click)

    def on_right_click(self, widget, event):
        # Check if it's the right-click (button 3)
        if event.button == 3:
            # Show the context menu
            # self.context_menu.popup(None, None, None, None, event.button, event.time)
            self.context_menu.show_all()
            self.context_menu.popup_at_pointer(event)

    def on_option1_activated(self, widget):
        print("Option 1 selected")

    def on_option2_activated(self, widget):
        print("Option 2 selected")

if __name__ == "__main__":
    win = RightClickMenu()
    win.show_all()
    Gtk.main()

Upvotes: 0

Related Questions