Reorder rows in a list (GTK4)

Hi!
I’d like to port my application to gtk4 but I need something to reorder rows (with dnd) in a list. So far I did not find anything which fits my needs. With gtk3 I used a TreeView with reorderable=True. According to my tests with gtk4 and https://gitlab.gnome.org/GNOME/gtk/-/issues/3649 this is still broken in gtk4. Has anyone an idea how to achieve this? A solution using one of the new list widgets (e.g. ColumnView) would be great.

Hello,
same here I cannot find a way to reorder a TreeView in GK4 … and would appreciate to get this feature back :wink:

I also need this feature in Gtk4… I’d like to know how to reorder the rows of a GtkColumnView, if possible.

Could anyone provide a little sample code ?

Hi,
I have managed to implement this with GtkColumnView in python. It’s obviously not very pretty but I think it is enough to get an idea of how this could be implemented. I used this blog post for the initial concept: .

Here is the script:

#!/usr/bin/python3
import gi
gi.require_version("Gtk", "4.0")
from gi.repository import Gtk, Gio, Gdk, Pango, GObject, GLib
from random import randint as ran
import random, string

class Test(GObject.Object):
	def __init__(self, track):
		super().__init__()
		self._track=track
		self._string=''.join(random.choice(string.ascii_letters) for i in range(20)).strip().lower()
		sec=str(ran(0,60))
		minu=str(ran(2,10))		
		self._time=f"{minu.zfill(2)}:{sec.zfill(2)}"

	def get_string(self):
		return self._string

	def get_time(self):
		return self._time

	def get_track(self):
		return str(self._track)

class Scroll(Gtk.ScrolledWindow):
	def __init__(self):
		super().__init__()
		self.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC)

		def drop(control, origin_pos, x, y, dest_pos):
			height=control.get_widget().get_allocated_height()
			obj=glist.get_item(origin_pos)
			glist.remove(origin_pos)
			if origin_pos < dest_pos:
				pos=dest_pos-1
			else:
				pos=dest_pos
			if y < height//2:  # insert above
				glist.insert(pos, obj)
			else:  # insert below
				glist.insert(pos+1, obj)

		def setup(factory, item, xalign, chars):
			label=Gtk.Label(xalign=xalign, width_chars=chars, ellipsize=Pango.EllipsizeMode.END, valign=Gtk.Align.FILL, vexpand=True)
			item.set_child(label)

		def bind0(factory, item):
			label=item.get_child()
			label.set_label(item.get_item().get_track())

		def bind1(factory, item):
			label=item.get_child()
			p=label.get_parent()
			dt=Gtk.DropTarget()
			ds=Gtk.DragSource()
			p.add_controller(ds)
			p.add_controller(dt)
			dt.set_actions(Gdk.DragAction.COPY)
			dt.set_gtypes((int,))
			c=Gdk.ContentProvider.new_for_value(item.get_position())
			ds.set_content(c)
			dt.connect("drop", drop, item.get_position())
			label.set_label(item.get_item().get_string())

		def bind2(factory, item):
			label=item.get_child()
			label.set_label(item.get_item().get_time())

		l=Gtk.ColumnView(hexpand=True, single_click_activate=True)
		l.add_css_class("rich-list")

		c0=Gtk.ColumnViewColumn(title="Nr.")
		f0=Gtk.SignalListItemFactory()
		f0.connect("setup", setup, 1, 3)
		f0.connect("bind", bind0)
		c0.set_factory(f0)
		l.append_column(c0)

		c1=Gtk.ColumnViewColumn(title="Titel", expand=True)
		f1=Gtk.SignalListItemFactory()
		f1.connect("setup", setup, 0, -1)
		f1.connect("bind", bind1)
		c1.set_factory(f1)
		l.append_column(c1)

		c2=Gtk.ColumnViewColumn(title="Länge")
		f2=Gtk.SignalListItemFactory()
		f2.connect("setup", setup, 1, -1)
		f2.connect("bind", bind2)
		c2.set_factory(f2)
		l.append_column(c2)

		def drop2(control, origin_pos, x, y):
			obj=glist.get_item(origin_pos)
			glist.remove(origin_pos)
			glist.append(obj)

		dt=Gtk.DropTarget()
		l.add_controller(dt)
		dt.set_actions(Gdk.DragAction.COPY)
		dt.set_gtypes((int,))
		dt.connect("drop", drop2)

		glist=Gio.ListStore.new(Test)
		for i in range(11):
			glist.append(Test(i))
		model=Gtk.SingleSelection(model=glist)
		l.set_model(model)

		self.set_child(l)

def on_activate(app):
	s=Scroll()
	win=Gtk.ApplicationWindow(application=app)
	win.set_child(s)
	win.present()

app=Gtk.Application(application_id="org.test.popover")
app.connect("activate", on_activate)
app.run(None)

I hope this helps.

Thanks a lot Martin,

I just see your post. I don’t know why the platorm did not emailed me.

Had the same problem a few days ago…