Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Submenu item does not call function [with working solution]

    #submenu
    clearMenu = gtk.Menu()

    item = gtk.MenuItem("submenu item")
    item.connect("activate", lambda w: self.callBackFunction())
    clearMenu.append(item)
    item.show()


    '''TOP level'''
    menu = gtk.Menu()

    item = gtk.ImageMenuItem("Item1")
    img = gtk.Image()
    img.set_from_file('image1.png')
    item.set_image(img)
    menu.append(item)
    item.set_submenu(clearMenu) #attach submenu
    item.show()

    item = gtk.ImageMenuItem("Item2")
    img = gtk.Image()
    img.set_from_file('image2.png')
    item.set_image(img)   
    item.connect("activate", lambda w: self.callBackFunction())
    menu.append(item)
    item.show()

My top level item "Item2" calls defined function "callBackFunction". But why "submenu item" does not? What i'm doing wrong?


EDIT

here is how i've managed to force submenu items kick-in desired action:

item.connect("button-press-event", self.callBackFunction, argument1, argument2)

But i still dont get it why event "activate" does not works on submenu items, while works in top level menu items

like image 501
Lixas Avatar asked Jan 21 '23 07:01

Lixas


2 Answers

While "button-press-event" works, it has a few drawbacks:

  • It doesn't work for keyboard-only navigation
  • If the callback blocks, the whole X Server is blocked (see gPodder bug 1778)

For my own application (gPodder), I've worked around this in commit a09b204a.

What we want:

  1. React to the "activate" signal (for keyboard navigation and for situations where the parent menu item is also clicked)
  2. React to the "button-press-event" signal (to work around the bug)
  3. Run the callback in the next main loop iteration (to avoid blocking the X Server)
  4. Make sure that the callback is only called once ("activate" and "button-press-event" can both happen in some circumstances)

For 1. and 2. we can simply connect to both signals. For 3. we can use gobject.idle_add(). For 4. we can use a threading.Semaphore.

This results in the following code:

import threading
import gobject

def submenu_item_connect_hack(menu_item, callback, *args_for_callback):
    only_once = threading.Semaphore(1)

    def handle_event(item, event=None):
        if only_once.acquire(False):
            gobject.idle_add(callback, *args_for_callback)

    menu_item.connect('button-press-event', handle_event)
    menu_item.connect('activate', handle_event)

You can now use this in your code as follows: Instead of calling one of:

item.connect("activate", lambda w: self.callBackFunction())
item.connect("button-press-event", self.callBackFunction, argument1, argument2)

You call this instead:

submenu_item_connect_hack(item, self.callBackFunction, argument1, argument2)

Also, filed bug 695488 in GNOME Bugzilla.

like image 55
Thomas Perl Avatar answered Jan 22 '23 20:01

Thomas Perl


It's an inherent problem with submenu focus explained here:

the submenu doesn't get focus until the menu item it's attached to is clicked (even though the submenu appears when the mouse is over the menu item.)

The upshot is that items in the sub menu don't emit the activate signal unless the parent menu item is clicked first.

This explains why keyboard navigation seems to work.

I've been working around this problem for more than a year, and I don't know of any solution to it — just the "button-press-event" workaround you've discovered.

like image 34
detly Avatar answered Jan 22 '23 20:01

detly