TextView in ListView not acquiring focus properly

I cannot make TextView inside a ListView to capture mouse click properly. For some reason, I need to click the TextView exactly five time (or three times if I set single click focus) to focus it. It happens only for TextView, and other widgets like Button and Entry can be focused on a single click.

This happens first in a Rust gtk-rs project, then I replicated in a minimal Pygobject example. I don’t know if it’s a bug or intended, so I will post the script here:

import sys
import gi

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


class IntegerObject(GObject.Object):
    value = GObject.Property(type=int, default=0)

    def __init__(self, value=0):
        super().__init__()
        self.value = value


class MyApplication(Gtk.ApplicationWindow):
    def __init__(self, **kwargs):
        super().__init__(**kwargs, title="TextView in ListView")
        GLib.set_application_name("Test TextView in ListView")
        # The store
        store = Gio.ListStore()
        store.append(IntegerObject(0))
        store.append(IntegerObject(42))
        # Factory
        factory = Gtk.SignalListItemFactory()
        factory.connect("setup", self.on_setup)
        factory.connect("bind", self.on_bind)
        # The view
        list_view = Gtk.ListView.new(Gtk.NoSelection.new(store), factory)
        sw = Gtk.ScrolledWindow(hexpand=True, vexpand=True)
        sw.set_child(list_view)
        self.set_child(sw)

    def on_setup(self, w, list_item):
        tv = Gtk.TextView(hexpand=True, vexpand=True, height_request=64)
        frame = Gtk.Frame(hexpand=True, vexpand=True)
        frame.set_child(tv)
        list_item.set_child(frame)

    def on_bind(self, w, list_item):
        int_obj = list_item.props.item
        tv = list_item.props.child.props.child
        int_obj.bind_property(
            "value", tv.props.buffer, "text", GObject.BindingFlags.BIDIRECTIONAL
        )


def on_activate(self):
    window = MyApplication(application=self, default_width=640, default_height=480)
    window.present()


app = Gtk.Application(application_id="com.example.TextViewListView")
app.connect("activate", on_activate)
status = app.run(sys.argv)
sys.exit(status)

Did I miss something? Is there some properties I have to set?

(BTW, the Pygobject documentation is bad. If I don’t already have a little experience in gtk-rs, I would never figure out how to do anything substantial in it.)

It’s the Gtk.Frame. I don’t know if this is a bug but if you use any container instead of adding Gtk.TextView directly as a child to Gtk.ListItem this happens. If you use a Gtk.Box instead of Gtk.Frame it will also happen.

Ok, my rust program used Box, and I just checked that without the Frame or Box it works. Should I report this as a bug?

I think it’s worth it, it’s probably a Gtk.TextView bug.

Ok… I hope it will get fixed. It’s hard blocking my project and I might have to look for an alternative GUI option

1 Like

Well, I don’t know what you’re doing, but you’re probably going to run into another nasty bug when you combine Gtk.TextView and Gtk.ListView.

In your code example, I saw that you used height_request=64 to make sure that the Gtk.TextView shows up when it’s empty. This works, but when you load a Gtk.textView that already has text in it (for example, if you decided to filter or hide one of the Gtk.textViews in the list and then show it later), the Gtk.TextView doesn’t expand to show the entire text until you click on one of the Gtk.TextViews in the list.

This bug ended up derailing a project I had for a session-based text editor, something focused on creative writing.

Ok, interesting. However, I don’t see an alternative to using TextView in ListView. Well, technically maybe I can put the editor in a separate widget. Also, why do I need TextView to expand? It’s in a scrolled window.

Did you keep using Gtk for your project or did you change to something else? I picked Gtk only because it’s the only toolkit that has a workable C API.

Hi,

You have to make the Gtk.ListItem unactivatable and unselectable:

list_item.set_activatable(False)
list_item.set_selectable(False)

Some more remarks:

  • GtkTextView only properly allocates itself when put as child of a ScrolledWindow.
  • binding an IntegerObject with a buffer text string may be problematic… better use a string too, and add some mechanism for validation if you want to allow numbers only.

Here some updated code:

import sys
import gi

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


class IntegerObject(GObject.Object):
    value = GObject.Property(type=str, default="0")

    def __init__(self, value="0"):
        super().__init__()
        self.value = value


class MyApplication(Gtk.ApplicationWindow):
    def __init__(self, **kwargs):
        super().__init__(**kwargs, title="TextView in ListView")
        GLib.set_application_name("Test TextView in ListView")
        # The store
        store = Gio.ListStore()
        store.append(IntegerObject("0"))
        store.append(IntegerObject("42"))
        # Factory
        factory = Gtk.SignalListItemFactory()
        factory.connect("setup", self.on_setup)
        factory.connect("bind", self.on_bind)
        # The view
        list_view = Gtk.ListView.new(Gtk.NoSelection.new(store), factory)
        sw = Gtk.ScrolledWindow()
        sw.set_child(list_view)
        self.set_child(sw)

    def on_setup(self, w, list_item):
        tv = Gtk.TextView(height_request=64)
        frame = Gtk.ScrolledWindow(has_frame=True)
        frame.set_child(tv)
        list_item.set_child(frame)
        list_item.set_activatable(False)
        list_item.set_selectable(False)

    def on_bind(self, w, list_item):
        int_obj = list_item.props.item
        tv = list_item.props.child.props.child
        int_obj.bind_property(
            "value", tv.props.buffer, "text", GObject.BindingFlags.BIDIRECTIONAL | GObject.BindingFlags.SYNC_CREATE
        )


def on_activate(self):
    window = MyApplication(application=self, default_width=640, default_height=480)
    window.present()


app = Gtk.Application(application_id="com.example.TextViewListView")
app.connect("activate", on_activate)
status = app.run(sys.argv)
sys.exit(status)

I’ll give that a try.

Why don’t I need to set these properties when using Entry and Button?

I haven’t looked in detail, but probably because the TextView has more complex events statemachines that collide with the ListItem events handling.

In doubt, better remove potential sources on conflict on the ListItem side by disabling unnecessary features.

It does work. However, how does that relate to the GtkNoSelection I needed to use? Is that “select” different from the selection of the ListItem?

Think about the Model–View–Controller pattern. Those things are managed separately, and you can control their interconnections.

On the View side, the ListItem can control its selectability.
If it’s selectable, then it will listen to click events and try to reflect its selection state on the model.
But if the model is GtkNoSelection then nothing will happen.

That gives a lot of flexibility on who controls what.