Search code examples
pythongtkflatpak

GtkDropDown search not working with plain strings


I'm trying to create a GtkDropDown with a searchbar to sort through a list of timezones.
The Gtk4 documentation states

GtkDropDown knows how to obtain strings from the items in a GtkStringList

which is the model I originally tried to use.
It also says in the set_enable_search() method description

Note that GtkDropDown:expression must be set for search to work.

I've looked on GitLab and GitHub for other projects and how they are doing it, but all the methods I found don't seem to work for me, throwing errors in python or straight up ignoring the text input.

After multiple attempts, here is the snippet of the last one which throws a ValueError: row sequence has the incorrect number of elements on model.append(Timezone(_(tz)))

# unrelated code above
model = Gtk.ListStore(GObject.GType(Timezone))
for tz in pytz.common_timezones:
    model.append(Timezone(_(tz)))
dropdown = Gtk.DropDown()
dropdown.set_model(model)
dropdown.set_enable_search(True)
dropdown.set_expression(Gtk.PropertyExpression(GObject.GType(Timezone), None, "name"))
self.action_row.add_suffix(dropdown) # parent defined above
# unrelated code below

The class Timezone:

from gi.repository import GObject

class Timezone(GObject.GObject):
    def __init__(self, name):
        __gtype_name__ = "Timezone"
        self.name = name

    def __len__(self):
        return len(self.name)

    def __str__(self):
        return self.name

I'm stuck, what am I missing to make it work?


Solution

  • Here is an example of what I believe you are trying to achieve:

    #!/usr/bin/env python3
    
    import gi
    import pytz
    
    gi.require_version("Gtk", "4.0")
    from gi.repository import Gtk  # noqa: E402
    
    
    class ApplicationWindow(Gtk.ApplicationWindow):
        def __init__(self, *args, **kwargs):
            super().__init__(*args, **kwargs)
    
            drop_down = self.create_drop_down()
            self.set_child(drop_down)
    
        def create_drop_down(self):
            string_list_items = "\n".ljust(11).join(
                [f"<item>{time_zone}</item>" for time_zone in pytz.common_timezones]
            )
    
            drop_down_ui_string = f"""<interface>
      <object class="GtkDropDown" id="drop-down">
        <property name="model">
          <object class="GtkStringList" id="string-list">
            <items>
              {string_list_items}
            </items>
          </object>
        </property>
        <property name="enable-search">true</property>
        <property name="expression">
          <lookup type="GtkStringObject" name="string"></lookup>
        </property>
      </object>
    </interface>"""
    
            builder = Gtk.Builder.new_from_string(drop_down_ui_string, -1)
            drop_down = builder.get_object("drop-down")
    
            return drop_down
    
    
    class Application(Gtk.Application):
        def __init__(self, *args, **kwargs):
            super().__init__(*args, **kwargs)
            self.connect("activate", self.on_activate)
    
        def on_activate(self, app):
            self.win = ApplicationWindow(application=app)
            self.win.show()
    
    
    app = Application()
    app.run()
    

    A few comments:

    • PyGObject does not support GtkExpression. The only way to currently make the search feature work in a GtkDropDown is to use a GtkBuilder.
    • As the documentation mentions, options are given to GtkDropDown in the form of GListModel. This means that you cannot set the model using a GtkListStore. Indeed, the documentation explains that a GtkListStore object is a list model for use with a GtkTreeView widget, which is different from a GtkDropDown.
    • If you really want to use a GtkListStore, then you can do so in the following way:
    import gi
    import pytz
    
    gi.require_version("Gtk", "4.0")
    from gi.repository import GObject, Gtk  # noqa: E402
    
    
    class Timezone(GObject.GObject):
        __gtype_name__ = "Timezone"
    
        def __init__(self, name, *args, **kwargs):
            super().__init__(*args, **kwargs)
    
            self.name = name
    
        def __len__(self):
            return len(self.name)
    
        def __str__(self):
            return self.name
    
    
    model = Gtk.ListStore.new([GObject.GType(Timezone)])
    for time_zone in pytz.common_timezones:
        model.append([Timezone(time_zone)])