title: Drag and drop with GTK4: connecting DragSource and DropTarget via ContentProvider for derived classes
body: <p>I am working on (py)gtk4's drag-and-drop functionality and I hit a wall. I have a flowbox-derived class <code>MediaGallery</code> that contains frames with images and their filenames (class <code>MediaFile</code>), and a listbox-derived class <code>Albums</code>. I want to drag one or more selected images from <code>MediaGallery</code> to <code>Albums</code>, which will eventually add them to the underlying database.</p>
  <p>Relevant piece of code:</p>
  <pre><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'&lt;MediaFile {self.filename}&gt;'
  
  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'&lt;MediaGallery {self.name}&gt;'
  
      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}')
  </code></pre>
  <p>The entire code is available from <a href="https://github.com/aprsa/medieval" rel="nofollow noreferrer">here</a>. The output I get:</p>
  <pre><code>in on_dnd_prepare(); drag_source=&lt;Gtk.DragSource object at 0x7f755e760940 (GtkDragSource at 0x2d100e0)&gt;, x=176.15234375, y=172.96092224121094, data=[&lt;MediaFile 20210712_190722A.jpg&gt;]
  in on_dnd_begin(); drag_source=&lt;Gtk.DragSource object at 0x7f755e760940 (GtkDragSource at 0x2d100e0)&gt;, data=&lt;__gi__.GdkWaylandDrag object at 0x7f75424f2e00 (GdkWaylandDrag at 0x41b6ac0)&gt;, content=&lt;__gi__.GdkContentProviderValue object at 0x7f75424eb040 (GdkContentProviderValue at 0x43a8c10)&gt;
  in on_dnd_accept(); drop=&lt;Gtk.DropTarget object at 0x7f755e760940 (GtkDropTarget at 0x3f56aa0)&gt;, user_data=&lt;__gi__.GdkWaylandDrop object at 0x7f7542374440 (GdkWaylandDrop at 0x7f7560157390)&gt;
  in on_dnd_enter(); drop_target=&lt;Gtk.DropTarget object at 0x7f755e760940 (GtkDropTarget at 0x3f56aa0)&gt;, x=139.6796875, y=0.0898437574505806
  in on_dnd_motion(); drop_target=&lt;Gtk.DropTarget object at 0x7f755e760940 (GtkDropTarget at 0x3f56aa0)&gt;, x=139.6796875, y=0.0898437574505806
  in on_dnd_motion(); drop_target=&lt;Gtk.DropTarget object at 0x7f755e760940 (GtkDropTarget at 0x3f56aa0)&gt;, 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 '&lt;invalid&gt;' 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=&lt;Gtk.DropTarget object at 0x7f7542374480 (GtkDropTarget at 0x3f56aa0)&gt;
  </code></pre>
  <p>I can't find any documentation on how to set <code>gdk</code> type for <code>DropTarget</code> appropriately and how to make <code>DragSource</code> and <code>DropTarget</code> exchange the data via <code>ContentProvider</code>. Can anyone provide any insight, please? Thanks in advance!</p>