I am working on (py)gtk4's drag-and-drop functionality and I hit a wall. I have a flowbox-derived class MediaGallery that contains frames with images and their filenames (class MediaFile), and a listbox-derived class Albums. I want to drag one or more selected images from MediaGallery to Albums, which will eventually add them to the underlying database.
Relevant piece of code:
class Album(gtk.Box):
def __init__(self, *args, **kwargs):
super().__init__(*args, orientation=gtk.Orientation.HORIZONTAL, **kwargs)
self.label = gtk.Label(hexpand=False)
self.append(self.label)
self.label.set_visible(False)
self.entry = gtk.Entry()
self.append(self.entry)
self.entry.set_visible(True)
self.entry.connect('activate', self.on_entry_changed)
dnd = gtk.DropTarget.new(gdk.FileList, gdk.DragAction.COPY)
dnd.connect('drop', self.on_dnd_drop)
dnd.connect('accept', self.on_dnd_accept)
dnd.connect('enter', self.on_dnd_enter)
dnd.connect('motion', self.on_dnd_motion)
dnd.connect('leave', self.on_dnd_leave)
self.add_controller(dnd)
def on_entry_changed(self, entry):
name = entry.get_text()
self.label.set_text(name)
self.entry.set_visible(False)
self.label.set_visible(True)
def on_dnd_drop(self, value, x, y, user_data):
print(f'in on_dnd_drop(); value={value}, x={x}, y={y}, user_data={user_data}')
def on_dnd_accept(self, drop, user_data):
print(f'in on_dnd_accept(); drop={drop}, user_data={user_data}')
return True
def on_dnd_enter(self, drop_target, x, y):
print(f'in on_dnd_enter(); drop_target={drop_target}, x={x}, y={y}')
return gdk.DragAction.COPY
def on_dnd_motion(self, drop_target, x, y):
print(f'in on_dnd_motion(); drop_target={drop_target}, x={x}, y={y}')
return gdk.DragAction.COPY
def on_dnd_leave(self, user_data):
print(f'in on_dnd_leave(); user_data={user_data}')
class MediaFile(gtk.FlowBoxChild):
def __init__(self, *args, file, **kwargs):
super().__init__(*args, **kwargs)
self.filename = file
frame = gtk.Frame()
self.set_child(frame)
vbox = gtk.Box(orientation=gtk.Orientation.VERTICAL)
frame.set_child(vbox)
self.image = gtk.Image.new_from_file(file)
self.image.set_pixel_size(256)
vbox.append(self.image)
label = gtk.Label.new(file[file.rfind('/')+1:])
vbox.append(label)
def __repr__(self):
return f'<MediaFile {self.filename}>'
class MediaGallery(gtk.FlowBox):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.name = kwargs.get('name', 'default')
self.connect('child-activated', self.on_media_selected)
dnd = gtk.DragSource.new()
dnd.set_actions(gdk.DragAction.COPY)
dnd.connect('prepare', self.on_dnd_prepare)
dnd.connect('drag-begin', self.on_dnd_begin)
dnd.connect('drag-end', self.on_dnd_end)
self.add_controller(dnd)
def __repr__(self):
return f'<MediaGallery {self.name}>'
def on_media_selected(self, gallery, media_file):
print(f'on_media_selected(); gallery={gallery}, media_file={media_file}')
def on_dnd_prepare(self, drag_source, x, y):
data = self.get_selected_children()
print(f'in on_dnd_prepare(); drag_source={drag_source}, x={x}, y={y}, data={data}')
if len(data) == 0:
return None
paintable = data[0].image.get_paintable()
drag_image = gtk.Image.new_from_paintable(paintable)
drag_image.set_opacity(0.5) # FIXME: not sure why transparency doesn't work
drag_source.set_icon(drag_image.get_paintable(), 128, 128) # FIXME: not sure why hot_x and hot_y don't work
content = gdk.ContentProvider.new_for_value(data)
return content
def on_dnd_begin(self, drag_source, data):
content = data.get_content()
print(f'in on_dnd_begin(); drag_source={drag_source}, data={data}, content={content}')
def on_dnd_end(self, drag, drag_data, flag):
print(f'in on_dnd_end(); drag={drag}, drag_data={drag_data}, flag={flag}')
The entire code is available from here. The output I get:
in on_dnd_prepare(); drag_source=<Gtk.DragSource object at 0x7f755e760940 (GtkDragSource at 0x2d100e0)>, x=176.15234375, y=172.96092224121094, data=[<MediaFile 20210712_190722A.jpg>]
in on_dnd_begin(); drag_source=<Gtk.DragSource object at 0x7f755e760940 (GtkDragSource at 0x2d100e0)>, data=<__gi__.GdkWaylandDrag object at 0x7f75424f2e00 (GdkWaylandDrag at 0x41b6ac0)>, content=<__gi__.GdkContentProviderValue object at 0x7f75424eb040 (GdkContentProviderValue at 0x43a8c10)>
in on_dnd_accept(); drop=<Gtk.DropTarget object at 0x7f755e760940 (GtkDropTarget at 0x3f56aa0)>, user_data=<__gi__.GdkWaylandDrop object at 0x7f7542374440 (GdkWaylandDrop at 0x7f7560157390)>
in on_dnd_enter(); drop_target=<Gtk.DropTarget object at 0x7f755e760940 (GtkDropTarget at 0x3f56aa0)>, x=139.6796875, y=0.0898437574505806
in on_dnd_motion(); drop_target=<Gtk.DropTarget object at 0x7f755e760940 (GtkDropTarget at 0x3f56aa0)>, x=139.6796875, y=0.0898437574505806
in on_dnd_motion(); drop_target=<Gtk.DropTarget object at 0x7f755e760940 (GtkDropTarget at 0x3f56aa0)>, x=139.6796875, y=0.0898437574505806
/usr/lib/python3/dist-packages/gi/overrides/Gio.py:42: Warning: ../../../gobject/gtype.c:4322: type id '0' is invalid
return Gio.Application.run(self, *args, **kwargs)
/usr/lib/python3/dist-packages/gi/overrides/Gio.py:42: Warning: cant peek value table for type '<invalid>' which is not currently referenced
return Gio.Application.run(self, *args, **kwargs)
/usr/lib/python3/dist-packages/gi/overrides/Gio.py:42: Warning: ../../../gobject/gvalue.c:185: cannot initialize GValue with type '(null)', this type has no GTypeValueTable implementation
return Gio.Application.run(self, *args, **kwargs)
(python:196456): Gdk-CRITICAL **: 22:21:40.071: gdk_content_provider_get_value: assertion 'G_IS_VALUE (value)' failed
(python:196456): Gdk-CRITICAL **: 22:21:40.071: gdk_content_provider_get_value: assertion 'G_IS_VALUE (value)' failed
(python:196456): GLib-GIO-CRITICAL **: 22:21:40.071: g_task_return_error: assertion 'error != NULL' failed
in on_dnd_leave(); user_data=<Gtk.DropTarget object at 0x7f7542374480 (GtkDropTarget at 0x3f56aa0)>
I can't find any documentation on how to set gdk type for DropTarget appropriately and how to make DragSource and DropTarget exchange the data via ContentProvider. Can anyone provide any insight, please? Thanks in advance!