翻訳して勉強するGtkチュートリアル第20章は Drag and Drop です。
ノート
PyGObjectのバージョンが3.0.3未満のものは、ドラッグ&ドロップが正しく機能できないというバグがあります。したがって以下の例が動作するにはPyGObjectバージョン3.0.3以上が必要です。
※バージョンの確認方法
>>> from gi.repository import GObject
>>> GObject.pygobject_version
(3, 2, 2)
ウィジェット間のドラッグ&ドロップの設定は、Gtk.Widget.drag_source_set() メソッドでドラッグ元 (ユーザがドラッグを開始するウィジェット) を選択し、Gtk.Widget.drag_dest_set() メソッドでドラッグ先 (ユーザがドロップするウィジェット) を選択し、両方のウィジェットで関連するシグナルを処理することで構成されています。
Gtk.Widget.drag_source_set() や Gtk.Widget.drag_dest_set() を使う代わりに、いくつかの特殊なウィジェットでは特定の関数を使う必要があります (Gtk.TreeView や Gtk.IconView など)。
基本的なドラッグ&ドロップは、ソースが “drag-data-get” シグナルに接続し、デスティネーションが “drag-data-received” シグナルに接続するだけです。特定のドロップエリアやカスタムのドラッグアイコンのようなより複雑なものは、追加のシグナルに接続し、それが提供する Gdk.DragContext オブジェクトと対話する必要があります。
転送元と転送先の間でデータを転送するためには、“drag-data-get” および “drag-data-received” シグナルで指定された Gtk.SelectionData 変数と Gtk.SelectionData の get メソッドと set メソッドを使ってやりとりをする必要があります。
20.1. ターゲット・エントリ
ドラッグ元とドラッグ先がどのようなデータを受信・送信しているかを知るためには、共通の Gtk.TargetEntry のリストが必要です。Gtk.TargetEntry は、ドラッグ元が送信し、ドラッグ先が受信するデータの一部を記述します。
Gtk.TargetEntry をソースとデスティネーションに追加するには、2つの方法があります。ドラッグ&ドロップが単純で、それぞれのターゲット・エントリが異なるタイプのものであれば、GithubのGtk3.0ドキュメントで述べられているメソッドのグループを使うことができます。
複数のタイプのデータが必要な場合や、より複雑な処理を行いたい場合は、Gtk.TargetEntry.new() メソッドを使用して Gtk.TargetEntry を作成する必要があります。
20.2. ドラッグソース信号
名前 | いつ発出されるか | 共通の目的 |
---|---|---|
drag-begin | ユーザがドラッグを開始した時 | ドラッグアイコンの設定 |
drag-data-get | 移動先からドラッグデータを要求された時 | ドラッグデータを転送元から転送先に転送する |
drag-data-delete | アクションGdk.DragAction.MOVEでのドラッグが完了した時 | 「移動」を完了するためにソースからデータを削除する |
drag-end | ドラッグが完了した時 | drag-beginで行ったことをすべて元に戻す |
20.3. ドラッグデスティネーション信号
名前 | いつ発出されるか | 共通の目的 |
---|---|---|
drag-motion | ドラッグアイコンがドロップエリアの上を移動する時 | 特定の領域のみにドロップすることを許可 |
drag-drop | アイコンをドラッグエリアにドロップした時 | 特定の領域のみにドロップすることを許可 |
drag-data-received | 移動先でドラッグデータを受信した場合 | ドラッグデータの転送元から転送先への転送 |
20.4. 例
# tut20.py
# ドラッグ&ドロップ
import gi
gi.require_version("Gtk", "3.0")
from gi.repository import Gtk, Gdk, GdkPixbuf
(TARGET_ENTRY_TEXT, TARGET_ENTRY_PIXBUF) = range(2)
(COLUMN_TEXT, COLUMN_PIXBUF) = range(2)
DRAG_ACTION = Gdk.DragAction.COPY
class DragDropWindow(Gtk.Window):
def __init__(self):
Gtk.Window.__init__(self, title="Drag and Drop Demo")
vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=6)
self.add(vbox)
hbox = Gtk.Box(spacing=12)
vbox.pack_start(hbox, True, True, 0)
self.iconview = DragSourceIconView()
self.drop_area = DropArea()
hbox.pack_start(self.iconview, True, True, 0)
hbox.pack_start(self.drop_area, True, True, 0)
button_box = Gtk.Box(spacing=6)
vbox.pack_start(button_box, True, False, 0)
image_button = Gtk.RadioButton.new_with_label_from_widget(None, "Images")
image_button.connect("toggled", self.add_image_targets)
button_box.pack_start(image_button, True, False, 0)
text_button = Gtk.RadioButton.new_with_label_from_widget(image_button, "Text")
text_button.connect("toggled", self.add_text_targets)
button_box.pack_start(text_button, True, False, 0)
self.add_image_targets()
def add_image_targets(self, button=None):
targets = Gtk.TargetList.new([])
targets.add_image_targets(TARGET_ENTRY_PIXBUF, True)
self.drop_area.drag_dest_set_target_list(targets)
self.iconview.drag_source_set_target_list(targets)
def add_text_targets(self, button=None):
self.drop_area.drag_dest_set_target_list(None)
self.iconview.drag_source_set_target_list(None)
self.drop_area.drag_dest_add_text_targets()
self.iconview.drag_source_add_text_targets()
class DragSourceIconView(Gtk.IconView):
def __init__(self):
Gtk.IconView.__init__(self)
self.set_text_column(COLUMN_TEXT)
self.set_pixbuf_column(COLUMN_PIXBUF)
model = Gtk.ListStore(str, GdkPixbuf.Pixbuf)
self.set_model(model)
self.add_item("Item 1", "image-missing")
self.add_item("Item 2", "help-about")
self.add_item("Item 3", "edit-copy")
self.enable_model_drag_source(Gdk.ModifierType.BUTTON1_MASK, [], DRAG_ACTION)
self.connect("drag-data-get", self.on_drag_data_get)
def on_drag_data_get(self, widget, drag_context, data, info, time):
selected_path = self.get_selected_items()[0]
selected_iter = self.get_model().get_iter(selected_path)
if info == TARGET_ENTRY_TEXT:
text = self.get_model().get_value(selected_iter, COLUMN_TEXT)
data.set_text(text, -1)
elif info == TARGET_ENTRY_PIXBUF:
pixbuf = self.get_model().get_value(selected_iter, COLUMN_PIXBUF)
data.set_pixbuf(pixbuf)
def add_item(self, text, icon_name):
pixbuf = Gtk.IconTheme.get_default().load_icon(icon_name, 16, 0)
self.get_model().append([text, pixbuf])
class DropArea(Gtk.Label):
def __init__(self):
Gtk.Label.__init__(self)
self.set_label("Drop something on me!")
self.drag_dest_set(Gtk.DestDefaults.ALL, [], DRAG_ACTION)
self.connect("drag-data-received", self.on_drag_data_received)
def on_drag_data_received(self, widget, drag_context, x, y, data, info, time):
if info == TARGET_ENTRY_TEXT:
text = data.get_text()
print("Received text: %s" % text)
elif info == TARGET_ENTRY_PIXBUF:
pixbuf = data.get_pixbuf()
width = pixbuf.get_width()
height = pixbuf.get_height()
print("Received pixbuf with width %spx and height %spx" % (width, height))
win = DragDropWindow()
win.connect("destroy", Gtk.main_quit)
win.show_all()
Gtk.main()