pythonpython-3.xgtkgtk3gobject

Is there an official way to create discrete-valued range widget in GTK?


From what I can see, the behavior any Range-based widget is defined by an Adjustements one.

Since there seems to be no official way to restrict the range to a few discrete values, I have tried a lots of ways to actually reset the values.

What works, but is not optimal, is simply to take the current value and determine which valid discrete value is the nearest from that continuous value and use that instead.

But what I would like is to visually freeze the slider to its current position until the user grabs it far enough and then change its value at once to the next valid value.

I want the user to understand and feel that these are discrete values. The problem with my working solution above is that the 3 users who tested the program told me that I should put more figures in the number I displayed, thinking the change was continuous.

NB: by "official", I mean either a "hidden" option or subclass of the Adjustements class if at all available. If not, any way that is reasonably efficient to achieve such a requirement (i.e. that don't use 50% of CPU for a simple slider!). In particular, I am not asking for the "ultimate best way" to do this.


Solution

  • It seems you can just override the change-value signal:

    class DiscreteScale(Gtk.Scale):
        def __init__(self, values, *args, **kwargs):
            super().__init__(*args, **kwargs)
            
            values.sort()
            self.values = values
            
            adjustment = self.get_adjustment()
            adjustment.set_lower(values[0])
            adjustment.set_upper(values[-1])
            
            self.__changed_value_id = self.connect('change-value', self.__change_value)
        
        def __change_value(self, scale, scroll_type, value):
            # find the closest valid value
            value = self.__closest_value(value)
            # emit a new signal with the new value
            self.handler_block(self.__changed_value_id)
            self.emit('change-value', scroll_type, value)
            self.handler_unblock(self.__changed_value_id)
            return True #prevent the signal from escalating
        
        def __closest_value(self, value):
            return min(self.values, key=lambda v:abs(value-v))
    

    A little demo:

    w = Gtk.Window()
    s = DiscreteScale([0, 1, 5, 20, 22])
    w.add(s)
    w.set_size_request(500, 50)
    
    w.connect('delete-event', Gtk.main_quit)
    w.show_all()
    Gtk.main()