How do I position a Gtk.PopoverMenu at the mouse pointer coordinates?

Using GTK4, Python 3.10.

I would like to have a PopoverMenu appear at the mouse pointer position when I right-click over a certain widget (i.e. a TreeView). In my GestureClick right-click callback, if I just do self.popover.popup(), it appears just below the center of the bottom edge of the widget. I have also tried the following, both of which make it appear in the upper-left corner of the widget:

def onRightClick1(self, controller, n_clicks, x, y):
   self.popover.set_pointing_to(Gdk.Rectangle(x, y, 0, 0))
   self.popover.popup()

def onRightClick2(self, controller, n_clicks, x, y):
   pointer = self.treeView.get_display().get_default_seat().get_pointer()
   surface = pointer.get_surface_at_position()
   self.popover.set_pointing_to(Gdk.Rectangle(surface.win_x, surface.win_y, 0, 0))
   self.popover.popup()

Any advice on how to make this work?

What are you setting as the parent of the PopoverMenu? If you have the TreeView in a ScrolledWindow, you need to set as the parent the ScrolledWindow.

I have a Box, containing a Label and a ScrolledWindow, with the TreeView inside the ScrolledWindow. Setting the ScrolledWindow as the PopoverMenu's parent leads to 19 warnings:

(gtk4_popup.py:15612): Gtk-WARNING **: 12:00:01.671: Allocating size to GtkPopoverMenu 00000207384AFD00 without calling gtk_widget_measure(). How does the code know the size to allocate?

Because of that, I’d been setting the Box as the parent - not sure if that’s problematic or not.

Regardless of the warnings, setting the ScrolledWindow as the parent still results in the popover appearing near the upper-left corner of the TreeView, rather than at the cursor position.

Here’s some sample code for a simple demo:

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

from random import randint
import sys


class PopoverDemo(Gtk.Application):
   def __init__(self):
      Gtk.Application.__init__(self, application_id=None, flags=Gio.ApplicationFlags.FLAGS_NONE)
      self.window = None
      self.connect('activate', self.on_activate)

   def on_activate(self, application):
      self.window = MainWindow(application=application)
      self.window.set_title('Right-Click Popover Demo')
      self.window.present()


class MainWindow(Gtk.ApplicationWindow):
   def __init__(self, *args, **kwargs):
      super(MainWindow, self).__init__(*args, **kwargs)
      self.set_size_request(440, 250)

      box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
      self.set_child(box)

      self.model = Gtk.ListStore(str, str, str, str)
      # Populate some dummy rows
      self.onAddRow(None, None)
      self.onAddRow(None, None)

      self.treeView = Gtk.TreeView(model=self.model)
      self.create_columns()

      self.scrolledWindow = Gtk.ScrolledWindow()
      self.scrolledWindow.set_vexpand(True)
      self.scrolledWindow.set_halign(Gtk.Align.FILL)
      self.scrolledWindow.set_child(self.treeView)
      box.append(self.scrolledWindow)

      clickEvent = Gtk.GestureClick()
      clickEvent.set_button(3)  # Right-click
      clickEvent.connect('pressed', self.onRightMouseClick)
      self.treeView.add_controller(clickEvent)

      action = Gio.SimpleAction.new('add_row', None)
      action.connect('activate', self.onAddRow)
      self.add_action(action)

      menu = Gio.Menu()
      menu.append('Add Row', 'win.add_row')

      self.popover = Gtk.PopoverMenu()
      self.popover.set_menu_model(menu)
      self.popover.set_has_arrow(False)
      self.popover.set_parent(self.scrolledWindow)


   def create_columns(self):
      columnNames = ['Column A', 'Column A', 'Column A', 'Column A']

      for colNum, colName in enumerate(columnNames):
         renderer = Gtk.CellRendererText()
         column = Gtk.TreeViewColumn(colName, renderer, text=colNum)

         column.set_fixed_width(108)
         column.set_sort_column_id(colNum)
         self.treeView.append_column(column)


   def onAddRow(self, _widget, _):
      row = [f'{randint(0, 999999)}'.zfill(6) for _ in range(4)]
      self.model.append(row)


   def onRightMouseClick(self, _controller, _click_count, x, y):
      self.popover.set_pointing_to(Gdk.Rectangle(x, y, 0, 0))

      # # I've also tried this, but the results are no different
      # # --------------------------------------------------------------------
      # pointer = self.treeView.get_display().get_default_seat().get_pointer()
      # surface = pointer.get_surface_at_position()
      # self.popover.set_pointing_to(Gdk.Rectangle(surface.win_x, surface.win_y, 0, 0))

      self.popover.popup()

      return True


app = PopoverDemo()
app.run(sys.argv)

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