Drag and Drop¶
See also
Drag-and-Drop in the GTK documentation.
GTK drag-and-drop works with drag sources and drop targets.
These are event controllers that can be set to any widget using
Gtk.Widget.add_controller()
. The data begin moved in the operation is
provided through a Gdk.ContentProvider
.
Drag sources¶
Gtk.DragSource
is the event controller that allows a widget to be used
as a drag source.
You can set up everything needed for the drag-and-drop operation ahead of time
or do it on the fly using the signals provided by Gtk.DragSource
.
You can use Gtk.DragSource.set_content()
to set the
Gdk.ContentProvider
that will be sent to drop targets.
A content provider is usually created using
Gdk.ContentProvider.new_for_value()
where you only pass the value to send.
To pass different values for multiple possible targets you can use
Gdk.ContentProvider.new_union()
were you can pass a list of
Gdk.ContentProviders
.
Gtk.DragSource
provides signals for the different stages of the drag
event.
The prepare
signal is emitted when a
drag is about to be initiated, here you should return the
Gdk.ContentProvider
that will be sent, otherwise the one set with
Gtk.DragSource.set_content()
will be used.
The drag-begin
signal is emitted
when the drag is started, here you can do things like changing the icon attached
to the cursor when dragging using Gtk.DragSource.set_icon()
.
Also the drag-end
signal is provided,
you can use it to undo things done in the previous signals.
Finally drag-cancel
allows you
to do things when the operation has been cancelled.
Drop targets¶
Gtk.DropTarget
is the event controller to receive drag-and-drop
operations in a widget.
When creating a new drop target with Gtk.DropTarget.new()
you should
provide the data type and Gdk.DragAction
that your target accepts.
If you want to support multiple data types you can pass GObject.TYPE_NONE
and then use Gtk.DropTarget.set_gtypes()
where you can pass a list of
types, be aware that the order of the list establish the priorities.
Gtk.DropTarget
provides multiple signals for the process. These are
accept
,
drop
,
enter
,
leave
, and
motion
.
Generally connecting to drop
is only
needed, this signal will receive the value sended by the Gtk.DragSource
.
For more complex use cases checkout Gtk.DropTargetAsync
.
Example¶
1import gi
2
3gi.require_version('Gdk', '4.0')
4gi.require_version('Gtk', '4.0')
5from gi.repository import Gdk, GObject, Gtk
6
7
8class DragDropWindow(Gtk.ApplicationWindow):
9 def __init__(self, *args, **kargs):
10 super().__init__(*args, **kargs, title='Drag and Drop Example')
11
12 self.set_default_size(500, 400)
13
14 views_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
15 views_box.props.vexpand = True
16 self.set_child(views_box)
17
18 flow_box = Gtk.FlowBox()
19 views_box.append(flow_box)
20 flow_box.props.selection_mode = Gtk.SelectionMode.NONE
21 flow_box.append(SourceFlowBoxChild('Item 1', 'image-missing'))
22 flow_box.append(SourceFlowBoxChild('Item 2', 'help-about'))
23 flow_box.append(SourceFlowBoxChild('Item 3', 'edit-copy'))
24
25 views_box.append(Gtk.Separator())
26
27 self.target_view = TargetView(vexpand=True)
28 views_box.append(self.target_view)
29
30
31class SourceFlowBoxChild(Gtk.FlowBoxChild):
32 def __init__(self, name, icon_name):
33 super().__init__()
34
35 self.name = name
36 self.icon_name = icon_name
37
38 box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=6)
39 self.set_child(box)
40
41 icon = Gtk.Image(icon_name=self.icon_name)
42 label = Gtk.Label(label=self.name)
43
44 box.append(icon)
45 box.append(label)
46
47 drag_controller = Gtk.DragSource()
48 drag_controller.connect('prepare', self.on_drag_prepare)
49 drag_controller.connect('drag-begin', self.on_drag_begin)
50 self.add_controller(drag_controller)
51
52 def on_drag_prepare(self, _ctrl, _x, _y):
53 item = Gdk.ContentProvider.new_for_value(self)
54 string = Gdk.ContentProvider.new_for_value(self.name)
55 return Gdk.ContentProvider.new_union([item, string])
56
57 def on_drag_begin(self, ctrl, _drag):
58 icon = Gtk.WidgetPaintable.new(self)
59 ctrl.set_icon(icon, 0, 0)
60
61
62class TargetView(Gtk.Box):
63 def __init__(self, **kargs):
64 super().__init__(**kargs)
65
66 self.stack = Gtk.Stack(hexpand=True)
67 self.append(self.stack)
68
69 empty_label = Gtk.Label(label='Drag some item, text, or files here.')
70 self.stack.add_named(empty_label, 'empty')
71 self.stack.set_visible_child_name('empty')
72
73 box = Gtk.Box(
74 orientation=Gtk.Orientation.VERTICAL,
75 vexpand=True,
76 valign=Gtk.Align.CENTER,
77 )
78 self.stack.add_named(box, 'item')
79
80 self.icon = Gtk.Image()
81 box.append(self.icon)
82 self.label = Gtk.Label()
83 box.append(self.label)
84
85 self.text = Gtk.Label()
86 self.stack.add_named(self.text, 'other')
87
88 drop_controller = Gtk.DropTarget.new(
89 type=GObject.TYPE_NONE, actions=Gdk.DragAction.COPY
90 )
91 drop_controller.set_gtypes([SourceFlowBoxChild, Gdk.FileList, str])
92 drop_controller.connect('drop', self.on_drop)
93 self.add_controller(drop_controller)
94
95 def on_drop(self, _ctrl, value, _x, _y):
96 if isinstance(value, SourceFlowBoxChild):
97 self.label.props.label = value.name
98 self.icon.props.icon_name = value.icon_name
99 self.stack.set_visible_child_name('item')
100
101 elif isinstance(value, Gdk.FileList):
102 files = value.get_files()
103 names = ''
104 for file in files:
105 names += f'Loaded file {file.get_basename()}\n'
106 self.text.props.label = names
107 self.stack.set_visible_child_name('other')
108
109 elif isinstance(value, str):
110 self.text.props.label = value
111 self.stack.set_visible_child_name('other')
112
113
114def on_activate(app):
115 win = DragDropWindow(application=app)
116 win.present()
117
118
119app = Gtk.Application(application_id='com.example.App')
120app.connect('activate', on_activate)
121
122app.run(None)