Search code examples
python-3.xgtk3

How to detect mouse position on custom CellRenderer


It makes a while that I've been struggling to detect the mouse coordinates over a custom Gtk.CellRenderer.

I've been reading some good docs like:

But I still not sure of how to do it. I've tried many things like trying to connect a signal with __gsignals__ or by trying to use the virtual methods.

I need to do this because I'm building a rating widget. The raiting widget contain starts like: ★★★★★ and I'd like that when the user pases the mouse over, the relative x position modifies the number of starts.

I also would like to connect a clicked signal in order to save the number of starts that the user selected.

Here below is my current working code. Any help on this is highly appreciated!

import gi
gi.require_version('Gtk', '3.0')
gi.require_version('PangoCairo', '1.0')
from gi.repository import Gtk, Gdk, cairo, Pango, PangoCairo, GObject

class CellRendererRating(Gtk.CellRenderer):
    """ Cellrenderer to display ratings from 0 to 5: ★★★★★, ★★★☆☆, etc """

    __gproperties__ = {
        'rating': ( int, # type
                    "integer prop", # nick
                    "A property that contains an integer", # blurb
                    0, # min
                    5, # max
                    0, # default
                    GObject.PARAM_READWRITE # flags
                    ),
    }

    def __init__(self):
        super().__init__()
        self.font_size=15
        self.font="Sans Bold {}".format(self.font_size)
        self.rating = 0

    def activate(event, widget, path, background_area, cell_area, flags):
        print(path)

    def do_set_property(self, pspec, value):
        setattr(self, pspec.name, value)

    def do_get_property(self, pspec):
        return getattr(self, pspec.name)

    def do_get_size(self, widget, cell_area):
        return (0, 0, self.font_size*5, self.font_size+5)

    def do_start_editing(event, widget, path, background_area, cell_area, flags):
        print('called')

    def do_render(self, cr, treeview, background_area, cell_area, flags):
        cr.translate (0, 0)
        layout = PangoCairo.create_layout(cr)
        #layout.set_font_description(FONT_CELLRATING_DESCRIPTION)

        if 'GTK_CELL_RENDERER_FOCUSED' in str(flags) and self.rating < 5:
            for i in range(5):
                if i < self.rating:
                    layout.set_text("★", -1)
                else:
                    layout.set_text("☆", -1)

                cr.save()
                PangoCairo.update_layout (cr, layout)
                cr.move_to (cell_area.x+i*(self.font_size+1), cell_area.y)
                PangoCairo.show_layout (cr, layout)
                cr.restore()

        else:
            for i in range(self.rating):
                layout.set_text("★", -1)
                cr.save()
                PangoCairo.update_layout (cr, layout)
                cr.move_to (cell_area.x+i*(self.font_size+1), cell_area.y)
                PangoCairo.show_layout (cr, layout)
                cr.restore()

GObject.type_register(CellRendererRating)


if __name__ == '__main__':


    class Window(Gtk.Window):
        def __init__(self):
            Gtk.Window.__init__(self)
            self.connect('destroy', self.on_quit)

            liststore = Gtk.ListStore(int)

            for i in range(6):
                liststore.append([i])


            treeview = Gtk.TreeView(liststore)

            treeviewcolumn = Gtk.TreeViewColumn("Rating")
            treeviewcolumn.set_resizable(True)
            cellrenderer = CellRendererRating()
            treeviewcolumn.pack_start(cellrenderer, True)
            treeviewcolumn.add_attribute(cellrenderer, 'rating', 0)
            treeview.append_column(treeviewcolumn)

            self.add(treeview)
            self.show_all()

        def on_quit(self, widget, data=None):
            Gtk.main_quit()

    w = Window()
    Gtk.main()

Solution

  • It is possible to get the position of the cursor on a CellRenderer by doing:

    def do_render(self, cr, treeview, background_area, cell_area, flags):
        mouse_x, mouse_y = treeview.get_pointer()
        cell_render_x = mouse_x - cell_area.x
        cell_render_y = mouse_y - cell_area.y
    

    Yet this might not be the optimal solution for your issue as there is already a Rating-CellRenderer that does exactly what you're trying to do namely RB.CellRendererRating. I would recommend using their implementation, either as is or as the basis for your custom version.

    RB.CellRendererRating.new()

    Create a cell renderer that will display some pixbufs for representing the rating of a song. It is also able to update the rating.

    Source of RB.CellRendererRating