BaRud
BaRud

Reputation: 3218

add gtk3 menubar to main window

I am very new in gtk3, and possibly messed up my python as well.

In my menu.py, I have defined my menubar in xml:

import gi
gi.require_version("Gtk", "3.0")
from gi.repository import Gtk, Gdk, Pango

UI_INFO = """
<ui>
  <menubar name='MenuBar'>
      <menu action='FileNew'>
        <menuitem action='FileNewStandard' />
      <menuitem action='FileOpenStandard' />
      <menuitem action='FileQuit' />
    </menu>
    <menu action='EditMenu'>
      <menuitem action='EditCopy' />
      <menuitem action='EditPaste' />
    </menu>
    <menu action='ChoicesMenu'>
      <menuitem action='Book'/>
      <menuitem action='Wine'/>
    </menu>
  </menubar>
  <popup name='PopupMenu'>
    <menuitem action='EditCopy' />
    <menuitem action='EditPaste' />
  </popup>
</ui>
"""

class MenuWindow(Gtk.Window):

    def __init__(self):
        Gtk.Window.__init__(self, title="Menu Example")

        action_group = Gtk.ActionGroup("my_actions")

        self.add_file_menu_actions(action_group)
        self.add_edit_menu_actions(action_group)
        self.add_choices_menu_actions(action_group)

        uimanager = self.create_ui_manager()
        uimanager.insert_action_group(action_group)

        menubar = uimanager.get_widget("/MenuBar")
        # button = Gtk.Button("Open")    # Submit button to write to
        # button.connect("clicked", self.on_button_clicked)


    def add_file_menu_actions(self, action_group):
        action_filenewmenu = Gtk.Action("FileNew", None, None, Gtk.STOCK_NEW)
        action_group.add_action(action_filenewmenu)
        action_new = Gtk.Action("FileNewStandard", "_New",
                                "Create a new file", Gtk.STOCK_NEW)
        # action_new.connect("activate", self.on_menu_file_new_generic)
        action_group.add_action_with_accel(action_new, None)

        action_fileopen = Gtk.Action("FileOpen", None, None, Gtk.STOCK_OPEN)
        action_group.add_action(action_fileopen)
        action_open = Gtk.Action("FileOpenStandard", "_Open",
                                 "Open an existing file", Gtk.STOCK_OPEN)
        # action_open.connect("activate", self.file_open_clicked)
        action_group.add_action_with_accel(action_open, None)

        action_filequit = Gtk.Action("FileQuit", None, None, Gtk.STOCK_QUIT)
        # action_filequit.connect("activate", self.on_menu_file_quit)
        action_group.add_action(action_filequit)

    def add_edit_menu_actions(self, action_group):
        action_group.add_actions([
            ("EditMenu", None, "Edit"),
            ("EditCopy", Gtk.STOCK_COPY, None, None, None,
             self.on_menu_others),
            ("EditPaste", Gtk.STOCK_PASTE, None, None, None,
             self.on_menu_others),
        ])

    def add_choices_menu_actions(self, action_group):
        action_group.add_action(Gtk.Action("ChoicesMenu", "Choices", None,
                                           None))

        action_group.add_radio_actions([
            ("Book", None, "Book", None, None, 1),
            ("Wine", None, "Wine", None, None, 2)
        ], 1, self.on_menu_choices_changed)

    def create_ui_manager(self):
        uimanager = Gtk.UIManager()

        # Throws exception if something went wrong
        uimanager.add_ui_from_string(UI_INFO)

        # Add the accelerator group to the toplevel window
        accelgroup = uimanager.get_accel_group()
        self.add_accel_group(accelgroup)
        return uimanager

And I have called this in my main function as:

#!/usr/bin/python3
import gi
gi.require_version("Gtk", "3.0")
from gi.repository import Gtk, Gdk
import menu

class MainWindow(Gtk.Window):
    def __init__(self):
        Gtk.Window.__init__(self, title="Collection Manager")

        self.set_default_size(1000, 20)
        self.set_border_width(10)

        box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
        MenuElem = menu.MenuWindow()
        box.pack_start(MenuElem, False, False, 0)
        self.add(box)
window = MainWindow()
window.connect("delete-event", Gtk.main_quit)
window.show_all()
Gtk.main()

I must be dowing something very wrong, as I am getting error:

python3 main2.py 

(main2.py:15800): Gtk-WARNING **: Can't set a parent on a toplevel widget


(main2.py:15800): Gtk-CRITICAL **: gtk_widget_destroy: assertion 'GTK_IS_WIDGET (widget)' failed

And instead of menubar, I am getting two window as:shown here

Kindly help

EDIT: From Cilyan's reply I am getting error:

 python3 main_mini.py 
Traceback (most recent call last):
  File "main_mini.py", line 24, in <module>
    window = MainWindow()
  File "main_mini.py", line 15, in __init__
    MenuElem = menu.MenuManager()
  File "/home/rudra/Devel/Cmanage/menu.py", line 32, in __init__
    super.__init__()
TypeError: descriptor '__init__' of 'super' object needs an argument

Upvotes: 1

Views: 1984

Answers (1)

Cilyan
Cilyan

Reputation: 8471

Explain Blabla

One important point, is that when write something like class SubClass(BaseClass), you extend BaseClass, which means that SubClass is a BaseClass with additions. In your code, this means that MenuWindow is a Gtk.Window (a detached rectangular area of the UI with a title bar and possibly some widgets in it). And actually, you don't want to pack a window into another window. You want to pack a menubar into your main window.

My understanding is that you wanted to encapsulate the code related to the menu in its own class, but you do not need this class to be a widget by itself. To do this, you usually do not need to inherit anything(*). However, as your class will probably manipulate GObject signals, you should at least derive from GObject.GObject.

Practical solution

I followed another approach, however. What your MainWindow needs is just a UIManager. But a UIManager that would be specially sized for the application's purpose so that the MainWindow code is not cluttered with menu related tasks. I choose to directly inherit from Gtk.UIManager and extend it to include everything the application needs and create some kind of specialized UIManager for the application. Here is what I changed from MenuWindow:

# New base class, new name to better define what it really is (optional)
# Now, our object will be UIManager by itself, and we can extend it
class MenuManager(Gtk.UIManager):

    def __init__(self):
        # Use super, it's much easier and flexible
        super().__init__()

        action_group = Gtk.ActionGroup("my_actions")

        self.add_file_menu_actions(action_group)
        self.add_edit_menu_actions(action_group)
        self.add_choices_menu_actions(action_group)

        # This code comes from the create_ui_manager function.
        # No need to instanciate a Gtk.UIManager, self is the UIManager already
        self.add_ui_from_string(UI_INFO)
        self.insert_action_group(action_group)

Then, I changed nothing apart from removing create_ui_manager. Now, the integration into the MainWindow is slightly different:

class MainWindow(Gtk.Window):
    def __init__(self):
        Gtk.Window.__init__(self, title="Collection Manager")

        self.set_default_size(1000, 20)
        self.set_border_width(10)

        box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
        # Don't forget to change the class's name
        MenuElem = menu.MenuManager()
        # Code moved from MenuWindow's __init__ function
        menubar = MenuElem.get_widget("/MenuBar")
        # Now you really pack the menubar widget, not a window containing a menu
        box.pack_start(menubar, False, False, 0)
        self.add(box)
        # Come from create_ui_manager. The AccelGroup must be added to the main window, not to the UIManager itself
        self.add_accel_group(MenuElem.get_accel_group())

One more thing

There is one last thing you may need to do, which I can't review because you did not provide code for it: the on_menu_choices_changed and on_menu_others functions. These functions will probably have an impact on the program as its whole, so you need them to access some kind of back-reference to your application, and possibly to other UI elements, i.e. other widgets that will be packed into the MainWindow.

One possible solution would be to pass a reference to the MainWindow object when instanciating the menu and save it so that the callback can use it when it's signaled:

class MenuManager(Gtk.UIManager):
    def __init__(self, mainwindow):
        super().__init__()
        self.mainwindow = mainwindow
        # ...

    def on_menu_choices_changed(self, widget, current):
        # For example
        if current.get_name() == "Wine":
            self.mainwindow.set_icon_to_glass_of_wine()
        else:
            self.mainwindow.set_icon_to_book()

# ...

class MainWindow(Gtk.Window):
    def __init__(self):
        # ...
        MenuElem = menu.MenuManager(self)
        # ...

That said, when you start dealing with bigger applications, it's easier and cleaner to switch to a stronger software design, like a Model-View-Controller architecture.

Upvotes: 3

Related Questions