Search code examples
pythontogglegtk3gtktreeview

Custom Gtk.CellRenderer with a Toggle cycling between True, None (displayed as inconsistent) and False, python GTK 3


I need to write a custom Gtk.CellRenderer (let's call it CellRendererToogleWithNone) that behaves similar to Gtk.CellRendererToggle, but instead of only supporting True and False I also want it to support None which should be displayed as inconsistent state like this: inconsistent state pygtk

On toggle I want to rotate in some defined order, for example like so: True -> False -> None (But that's probably the easy part, I still thought I mention that)

I also don't know how to make it work with TreeStore because if I do

self.treestore = Gtk.TreeStore.new(types = (bool,))
iter = self.treestore.append(parent = None, row = (None,))

it will convert any None value to False because bool seems not to allow for None values

I failed to find any helpful custom Gtk.CellRenderer examples online.

I want to do it by inheriting from Gtk.CellRenderer and NOT from Gtk.CellRendererToggle because this should also serve me as a helpful example to implement more cell renderers like this.

I can only guess that I have to define some custom data type for the TreeStore, something like bool_or_none instead of bool (no idea how to do that either) and hold my own Gtk.ToggleButton inside of my custom CellRendererToogleWithNone.

Edit 0:

This post about how to write custom Gtk.CellRenderer gives a couple of hints which maybe can be reused but does not solve my problem. It doesn't show how to make Gtk.TreeStore accept None values for bool variables and I don't understand everything there. It doesn't even use a Gtk.Button, instead it seems to paint a box inside of a widget that I'm guessing may be the parent. I don't want to paint my own Toggle, I want to reuse Gtk.ToggleButton and its inconsistent property

Edit 1:

Since it seems custom cell renderers are not easy, I think it would be especially useful to see a minimal working example in python. I should also mention that I want to display the data as compactly as possible which excludes suggested workarounds such as having two toggles for one value.


Solution

  • Question: Custom Gtk.CellRendererToggle with a Toggle cycling between True, None (displayed as inconsistent) and False.

    You are misguided, it's not the part of a Gtk.CellRendererToggle object to set the inconsistant flag. It's implemented allready there. You have to implement a Gtk.TreeCellDataFunc instead.

    I also want it to support None

    I don't get it to work with a None value,
    therefore i use type int with values: [False, True, 2]

    Note:

    • In def on_toggle(...) the model values get changed to 0 == False and 1 == True. If you want it to be stay of type boolean, implement accordingly.
    • You should be aware, that the value 2, evaluates to True also.

    App


    Reference:

    • Gtk.TreeViewColumn.set_cell_data_func

      This function is used instead of the standard attributes mapping for setting the column value, and should set the value of self’s cell renderer as appropriate.

    • Gtk.TreeCellDataFunc

      A function to set the properties of a cell instead of just using the straight mapping between the cell and the model. This is useful for customizing the cell renderer


    Implementation: Core point: .set_cell_data_func(cell_renderer, .set_status)

    class TreeViewColumnTriState(Gtk.TreeViewColumn):
    
        def __init__(self, title='', model=None, **attributes):
            cell_renderer = Gtk.CellRendererToggle()
            cell_renderer.connect("toggled", self.on_toggle, model)
    
            self.inconsistent = attributes.pop('inconsistent', None)
            super().__init__(title, cell_renderer, active=0, **attributes)
            self.set_cell_data_func(cell_renderer, self.set_status)
    
        def set_status(self, column, cell, model, _iter, *ignore):    
            if model.get_value(_iter, 0) == self.inconsistent:
                cell.set_property('inconsistent', True)
            else:
                cell.set_property('inconsistent', False)
    
        def on_toggle(self, cell, path, model, *ignore):
            if path is not None:
                model = model[model.get_iter(path)]    
                model[0] += 1
                if model[0] == 3:
                    model[0] = 0
    

    Usage:

    import gi
    gi.require_version('Gtk', '3.0')
    from gi.repository import Gtk, GLib
    
    
    class App(Gtk.ApplicationWindow):
        def __init__(self):
            super().__init__()
            self.connect("destroy", Gtk.main_quit)
    
            model = Gtk.ListStore(int)
    
            #  Some initial data
            for n in [False, True, 2]:
                model.append([n])
    
            col = TreeViewColumnTriState("Foo", model, inconsistent=2)
    
            tv = Gtk.TreeView(model)
            tv.append_column(col)
            self.add(tv)
    
            self.show_all()
    
    
    if __name__ == "__main__":
        main = App()
        Gtk.main()
    

    Tested with Python: 3.5 - gi.__version__: 3.22.0