試験運用中なLinux備忘録・旧記事

はてなダイアリーで公開していた2007年5月-2015年3月の記事を保存しています。

PyGTKでファイルマネージャからファイルアイコンをドラッグ・アンド・ドロップを受け取る(概要と簡単な例)

GUIアプリケーションにおいて、ドラッグ・アンド・ドロップ(以下D&D)をすることでデータをアプリケーション間でやりとりしやすくなることがある。
PyGTKでもD&Dを扱うことは可能で、ここで扱うのは受け取り側のアプリケーションでファイルマネージャのアイコンなど、URIリスト形式のデータをどう処理するかについて。

ドロップを受け取るときのシグナル

(ウィンドウを含む)GUI部品はデータのドロップを受け取ると「drag_data_received」というシグナルが起こることになっていて、それに関連付けたハンドラ(関数)が呼ばれる。

シグナル「drag_data_received」を処理するハンドラの引数とその中身

D&Dを受け取るとき、ハンドラにはそのGUI部品に関するオブジェクト自身以外に多くの引数が渡される。
http://www.pygtk.org/pygtk2tutorial/sec-DNDMethods.html#id2873419
の「drag_data_received_cb」に書かれているのが全ての引数(「data」は追加で渡すユーザデータで任意となる)。一部は単なる値だが、context(gtk.gdk.DragContext)とselection(gtk.SelectionData)はオブジェクトとなっている。

gtk.gdk.DragContext

gtk.gdk.DragContextオブジェクトのデータはD&Dのそれ自体に関する情報をメンバ変数に持っている。「action」「actions」「suggested_action」「targets」などがあるが、完全な一覧はgtk.gdk.DragContextオブジェクトのページを参照。

gtk.SelectionData

gtk.SelectionDataオブジェクトの中にメンバ変数dataがあり、ここにファイルの一覧が文字列として入ってくる。実際に処理する際にはこの文字列を項目ごとに分割する必要がある。

部品側がD&Dを受け取るための設定

シグナルに対するハンドラにD&Dが起こったときの処理を記述し、これと関連付けただけではD&Dは受け取れず、受け取るGUI部品側でD&Dを受け取れるための指定が別に必要となる。
gtk.Widgetオブジェクトのメンバ関数drag_dest_set()drag_dest_unset()でその設定を行い、前者では条件を指定して受け取り可能な状態に設定し、後者は受け取り不可能な状態に戻す(入力ファイルに対する処理を開始してから終了するまでの間、新しくアイコンがドロップされるのをブロックするのに使える)。
drag_dest_set()の引数については下の例やリファレンスを参照。

下のコードを実行し、ウィンドウにGUIファイルマネージャからアイコンをD&Dすると、このD&Dに関する各種情報を端末に表示する。
[任意]ファイル名: dnd-urllistreceivetest.py

#! /usr/bin/python
# -*- encoding: utf-8 -*-

import sys
import os
try:
  import pygtk
  pygtk.require("2.0")
except:
  pass
try:
  import gtk
except:
  print >> sys.stderr, "Error: PyGTK is not installed"
  sys.exit(1)


class MainWindow(gtk.Window):
  """
  メインウィンドウ
  """
  # 種類の識別番号
  TARGET_TYPE_TEXT_URI_LIST = 12345
  # 受け付ける種類を識別番号と結びつけたタプルのリスト
  # 2番目には受け付けるドラッグ元の範囲を示す値を指定
  # 0: 制限なし
  # gtk.TARGET_SAME_APP: 同一アプリ内
  # gtk.TARGET_SAME_WIDGET: 同一部品内
  dnd_list = [('text/uri-list', 0, TARGET_TYPE_TEXT_URI_LIST),]
  def __init__(self):
    gtk.Window.__init__(self)  # 必須
    self.connect("delete_event", gtk.main_quit)  # 閉じるボタンで終了
    # D&D受け取り時のシグナル
    self.connect("drag_data_received", self.on_drag_data_received)
    # gtk.DEST_DEFAULT_MOTIONはドロップする項目の種類をチェック
    # gtk.DEST_DEFAULT_HIGHLIGHTはドロップできる項目を乗せたときに表示を変更
    # gtk.DEST_DEFAULT_DROPはドロップが正常にできたかをチェック
    # gtk.gdk.ACTION_COPYはコピー操作のアイコン
    self.drag_dest_set(gtk.DEST_DEFAULT_MOTION |
                       gtk.DEST_DEFAULT_HIGHLIGHT | gtk.DEST_DEFAULT_DROP,
                       self.dnd_list,
                       gtk.gdk.ACTION_COPY)
  def on_drag_data_received(self, widget, context, x, y, selection, info, time):
    """
    項目がウィンドウにドロップされたときの処理
    """
    # 引数をそのまま表示
    print "context: %s\nx: %d\ny: %d\nselection: %s\ninfo: %d\ntime: %d" \
            % (context, x, y, selection, info, time)
    # ドラッグ・コンテキストの一部メンバ変数
    print "action: %s\nactions: %s\nsuggested_action: %s\ntargets: %s" \
            % (context.action, context.actions, context.suggested_action, context.targets)
    # 生のselection.data
    print "selection.data: [%s]" % selection.data

class PyGTKDndUrilistReceiveTest:
  """
  D&DによるURIリスト形式の受け取りテスト
  """
  def main(self):
    win = MainWindow()
    # 今回はウィンドウの中には何も入れないで表示
    win.show_all()
    gtk.main()


if __name__ == "__main__":
  app = PyGTKDndUrilistReceiveTest()
  app.main()

ファイルマネージャによっては若干挙動が異なり、また、Wine上のWindows版PyGTKでも別の部分で挙動が変わる。詳しくは別記事で扱うが、このあたりをアプリケーションや環境によって動作が変わらないようにコード側で工夫をする必要がある。

関連記事:

参考URL: