PyGTKでファイルマネージャからファイルアイコンをドラッグ・アンド・ドロップを受け取る(URIリストの加工と環境による違いの吸収)
「PyGTKでファイルマネージャからファイルアイコンをドラッグ・アンド・ドロップを受け取る(ファイルマネージャや環境による挙動の違いについて)」ではD&Dを受け取ったときに「drag_data_received」シグナルのハンドラに渡される引数から得られる情報を可能な限りそのままの状態で表示したが、ここでは実際にその情報を利用するにあたっての加工処理と、環境によって若干出力が異なるのを吸収する部分についてを扱う。
selection.dataの文字列を分割
ハンドラに渡される引数の1つであるselectionのメンバ変数dataには、ファイル一覧(場合によってはURLエンコードされている)が改行区切りで入っている。そこで、Pythonの基本的な機能である文字列メソッドの1つsplitlines()によって行ごとに分割する。
WineでWindows版PyGTKで実験すると、改行による分割だけでは一番最後に空の項目ができてしまう(下のテストでの出力結果を参照)。これは「\x00」の文字をselection.dataから削ることで回避でき、両方をあわせると下のようなfor文で処理することになる。
for item in selection.data.rstrip("\x00").splitlines(): [ここで変数itemとして各ファイルの絶対パスを含む文字列が扱える]
URLデコードと「file:」「file://」の処理
URLエンコードされているファイル名はurllibモジュールのurllib.url2pathname()関数によって簡単にデコードできる。しかし、「file:」や「file://」は取り除いてくれないため、手動で取り除く。
飛ばしたい長さだけ切ってしまうのが手っ取り早いのだが、「file://」の文字数に合わせてしまうと「file:」だけを先頭に付けるxffm4で問題が起こるため、文字数は「file:」の5文字を切ってしまうことにする。
その場合、xffm4以外からD&Dを行うと、例えば「file:///tmp/work/...」という場所だと「///tmp/work/...」というような形になってしまい、重複する「/」が気になるのだが、これはos.path.normpath()に入れるとうまく1つにまとめてくれることが分かった。
if item.startswith("file:"): path = os.path.normpath(urllib.url2pathname(item[5:]))
上の書き方をすることで、「file:」を削る処理とURLデコードが1行で行える。
テスト
上の処理に加えて、加工して得た各ファイル/ディレクトリのパスにos.access()でアクセスを試みている。
[任意]ファイル名: dnd-urllistreceivetest2.py
#! /usr/bin/python # -*- encoding: utf-8 -*- import urllib 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 # selection.dataの個別の項目 # Windows版対策として「\x00」を削ったものを改行で分割 #for item in selection.data.splitlines(): # 空の項目が最後に残る for item in selection.data.rstrip("\x00").splitlines(): if item.startswith("file:"): # 個別のファイル/ディレクトリの場所を得る(「file:」の後ろをURLデコード) # 下の「5」はlen("file:") #path = urllib.url2pathname(item[5:]) # 動作はするが「/」が重複する path = os.path.normpath(urllib.url2pathname(item[5:])) # 実際にアクセスできるかどうかを試す if os.access(path, os.R_OK): access = "access OK" else: access = "access NG" print "* %s => %s (%s)" % (item, path, access) # 「file:」以外の項目(特別なデスクトップアイコンなど) else: print "* %s" % item class PyGTKDndUrilistReceiveTest2: """ D&DによるURIリスト形式の受け取りテスト2 """ def main(self): win = MainWindow() # 今回はウィンドウの中には何も入れないで表示 win.show_all() gtk.main() if __name__ == "__main__": app = PyGTKDndUrilistReceiveTest2() app.main()
下はGUIファイルマネージャごとの実行結果。
(Nautilus) context: <gtk.gdk.DragContext object at 0x23b7b90 (GdkDragContext at 0x24be070)> x: 35 y: 19 selection: <GtkSelectionData at 0x7fffd119e280> info: 12345 time: 419875142 action: <flags GDK_ACTION_COPY of type GdkDragAction> actions: <flags GDK_ACTION_COPY | GDK_ACTION_MOVE | GDK_ACTION_LINK | GDK_ACTION_ASK of type GdkDragAction> suggested_action: <flags GDK_ACTION_COPY of type GdkDragAction> targets: ['x-special/gnome-icon-list', 'text/uri-list', 'UTF8_STRING', 'COMPOUND_TEXT', 'TEXT', 'STRING', 'text/plain;charset=utf-8', 'text/plain'] selection.data: [file:///tmp/work/dir file:///tmp/work/%E6%97%A5%E6%9C%AC%E8%AA%9E.txt file:///tmp/work/s%20p%20a%20c%20e.txt file:///tmp/work/test.txt ] * file:///tmp/work/dir => /tmp/work/dir (access OK) * file:///tmp/work/%E6%97%A5%E6%9C%AC%E8%AA%9E.txt => /tmp/work/日本語.txt (access OK) * file:///tmp/work/s%20p%20a%20c%20e.txt => /tmp/work/s p a c e.txt (access OK) * file:///tmp/work/test.txt => /tmp/work/test.txt (access OK) (xffm4) context: <gtk.gdk.DragContext object at 0x23b7b90 (GdkDragContext at 0x24be230)> x: 13 y: 24 selection: <GtkSelectionData at 0x7fffd119e280> info: 12345 time: 419924703 action: <flags GDK_ACTION_COPY of type GdkDragAction> actions: <flags GDK_ACTION_COPY | GDK_ACTION_MOVE | GDK_ACTION_LINK of type GdkDragAction> suggested_action: <flags GDK_ACTION_COPY of type GdkDragAction> targets: ['text/uri-list', 'text/x-moz-url', 'text/plain', 'UTF8_STRING', 'STRING'] selection.data: [file:/tmp/work/dir file:/tmp/work/s p a c e.txt file:/tmp/work/日本語.txt file:/tmp/work/test.txt ] * file:/tmp/work/dir => /tmp/work/dir (access OK) * file:/tmp/work/s p a c e.txt => /tmp/work/s p a c e.txt (access OK) * file:/tmp/work/日本語.txt => /tmp/work/日本語.txt (access OK) * file:/tmp/work/test.txt => /tmp/work/test.txt (access OK)
Wine上のPyGTKでは、追加するファイルによっては一覧が壊れてうまく処理できない*1が、ファイル名によっては
context: <gtk.gdk.DragContext object at 0xb50580 (GdkDragContext at 0x1acc18)> x: 356 y: 61 selection: <GtkSelectionData at 0x41f64c> info: 12345 time: 436606 action: <flags 0 of type GdkDragAction> actions: <flags GDK_ACTION_COPY of type GdkDragAction> suggested_action: <flags GDK_ACTION_COPY of type GdkDragAction> targets: ['text/uri-list'] selection.data: [file:///Z:/tmp/work/s%20p%20a%20c%20e.txt file:///Z:/tmp/work/test.txt file:///Z:/tmp/work/test2.jpg file:///Z:/tmp/work/test3.c ] * file:///Z:/tmp/work/s%20p%20a%20c%20e.txt => Z:\tmp\work\s p a c e.txt (access OK) * file:///Z:/tmp/work/test.txt => Z:\tmp\work\test.txt (access OK) * file:///Z:/tmp/work/test2.jpg => Z:\tmp\work\test2.jpg (access OK) * file:///Z:/tmp/work/test3.c => Z:\tmp\work\test3.c (access OK)
のように処理される。
(2010/9/4)Wineのバージョン1.3.1の時点では日本語ファイル名を含んだときに一覧がおかしくなるということはないが、上のスクリプトは
if os.access(path, os.R_OK):
の行を
if os.access(path.decode('utf-8').encode('cp932'), os.R_OK):
としないとアクセスには失敗するようだ(上はGNU/LinuxがUTF-8ロケールの場合)。以下、以前の内容となる。
なお、先述の「\x00」を削る処理を行わないと
* file:///Z:/tmp/work/s%20p%20a%20c%20e.txt => Z:\tmp\work\s p a c e.txt (access OK) * file:///Z:/tmp/work/test.txt => Z:\tmp\work\test.txt (access OK) * file:///Z:/tmp/work/test2.jpg => Z:\tmp\work\test2.jpg (access OK) * file:///Z:/tmp/work/test3.c => Z:\tmp\work\test3.c (access OK) *
のように、最後に空の項目が出る。
関連記事:
- PyGTKでファイルマネージャからファイルアイコンをドラッグ・アンド・ドロップを受け取る(概要と簡単な例)
- PyGTKでファイルマネージャからファイルアイコンをドラッグ・アンド・ドロップを受け取る(ファイルマネージャや環境による挙動の違いについて)
参考URL:
使用したバージョン:
*1:Wine側の問題の可能性がある・日本語(複数バイト文字)があると不具合が出る?