pygtkで遊ぼう(12) gtk.TreeView編
最近飲み会とかが多くて、なかなかサンプルコードをまとめる時間がなかったのですが、今日はたっぷりと時間があったので何とか形になりました。
こんかい作ったサンプルは、
「ファイル選択ダイアログで選択したファイル(複数可)をディレクトリツリーにして表示する」
というもです。
実行させるとこんな感じになります。
ディレクトリツリーといっても簡易的なものなので、ディレクトリが入れ子にはなりません。
まだ存在しないディレクトリは常にトップ階層のRowとして作られます。
ソースコードは以下のようになっています。
# -*- coding: utf-8 -*- import sys import os.path import pygtk if sys.platform == 'win32': pygtk.require('2.0') import gtk import gobject # GTKスレッドの初期化 gtk.gdk.threads_init() # # class FileTreeModel: ''' ツリーデータモデルクラス ''' def __init__(self, targets=): ''' 初期化 ''' self.store = gtk.TreeStore(gobject.TYPE_STRING) self.set_src_targets(targets) def set_src_targets(self, targets): ''' ターゲットを指定して、モデルを更新する。 モデルにターゲットの情報を追加する前に、一旦全てをクリアする ''' if targets == None: return self.clear_targets() self.append_src_targets(targets) def append_src_targets(self, srcList): ''' ファイルパスをツリーモデルに追加する。 ''' if srcList == None: return for srcPath in srcList: # ディレクトリ名、ファイル名に分割 srcPath = os.path.normpath(os.path.expanduser(srcPath)) if os.path.isdir(srcPath): # パスがディレクトリパスだったら # ディレクトリ以下のファイルリストを取得して # 再帰呼び出しを行う。 fileList = os.listdir(srcPath) self.append_src_targets(fileList) elif os.path.isfile(srcPath): # パスがファイルパスだったら、 # まずモデル中で追加すべきディレクトリ用Rowのイテレータ # を探す。 dname, fname = os.path.split(srcPath) aIter = self.find_iter_dir(self.store.get_iter_first(), dname) if aIter == None: # ディレクトリ用Rowのイテレータが存在しない場合には # トップレベルの階層に新規のRow追加して # その子としてファイル名のRowを追加する。 parent = self.store.append(None, (dname,)) self.store.append(parent, (fname,)) else: # ディレクトリ用Rowのイテレータが存在した場合には、 # 既にその子供として同名のファイル名が登録されて # いないか調べ、存在しない場合にのみファイル名 # の行を追加する。 childIter = self.store.iter_children(aIter) while childIter != None: if self.store.get_value(childIter, 0) == fname: break childIter = self.store.iter_next(childIter) else: # while文をブレークせずに通過、つまり # 同名のものが存在しなかった場合にのみ実行される。 self.store.append(aIter, (fname,)) def find_iter_dir(self, aIter, dname): ''' aIterで指定されたのと同じ階層のRowに関して dnameで指定された文字列をもつ行を探す。 存在しない場合にはNoneを返す。 ''' if aIter == None: return None if self.store.get_value(aIter, 0) == dname: return aIter return self.find_iter_dir(self.store.iter_next(aIter), dname) def clear_targets(self): ''' 保持しているターゲットモデルの内容を全て削除する ''' self.store.clear() def get_model(self): ''' ツリーモデルを取得する ''' return self.store # # class FileTreeDispModel: ''' ツリーデータモデル表示用クラス ''' def construct_view(self, model): ''' 初期化 ''' # ツリービューの生成 tree = gtk.TreeView(model) tree.set_rules_hint(True) tree.set_enable_tree_lines(True) tree.set_grid_lines(gtk.TREE_VIEW_GRID_LINES_VERTICAL) # セルレンダラの生成 render = gtk.CellRendererText() col = gtk.TreeViewColumn('File List', render, text=0) col.set_resizable(True) tree.append_column(col) return tree # # class TestWindow: ''' ウインドウクラス。 ''' mainMenu = '''''' def __init__(self): ''' 初期化 ''' # データモデルの生成 self.treeModel = FileTreeModel() # データ表示用モデルの作成 self.dispModel = FileTreeDispModel() # ツリービューの作成 model = self.treeModel.get_model() self.treeView = self.dispModel.construct_view(model) sel = self.treeView.get_selection() if sel: sel.set_mode(gtk.SELECTION_MULTIPLE) # 編集ウインドウの作成 self.wind = gtk.Window(gtk.WINDOW_TOPLEVEL) self.wind.set_size_request(400, 300) self.wind.set_position(gtk.WIN_POS_CENTER) self.wind.connect('destroy', self.on_exit) # メニューバーの作成 self.menubar = self.get_main_menubar() # ツリービューをウインドウ上に配置 vbox = gtk.VBox() vbox.pack_start(self.menubar, False, False, 4) scrl = gtk.ScrolledWindow() scrl.add(self.treeView) vbox.pack_start(scrl) self.wind.add(vbox) # ウインドウの表示 self.wind.show_all() def get_main_menubar(self): ''' ''' # UIManagerの生成 self.menuMgr = gtk.UIManager() # アクセラレータグループをウインドウに追加 acclGrp = self.menuMgr.get_accel_group() self.wind.add_accel_group(acclGrp) # アクショングループの作成 actGrpBase = gtk.ActionGroup('MenuActionGrpBase') actGrpBase.add_actions([('File', None, '_File')]) self.menuMgr.insert_action_group(actGrpBase, 0) # Fileメニューアクションの登録 actGrpBase.add_actions([('Open...', None, '_Open...', ' o', 'Open setting file(.xml)', self.on_menu_file_open), ('Exit', None, 'E_xit', None, 'Exit this tool', self.on_exit)]) self.menuMgr.add_ui_from_string(self.mainMenu) return self.menuMgr.get_widget('/MenuBar') def on_menu_file_open(self, widget, event=None, data=None): ''' メニューのOpenが選択された場合の処理。 複数のファイルが選択可能なFileChooserDialogを 表示し、ツリーに追加すべきファイルを選択させる。 ''' buttons = (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, gtk.STOCK_OK, gtk.RESPONSE_OK) chooser = gtk.FileChooserDialog('Choose Files', None, gtk.FILE_CHOOSER_ACTION_OPEN, buttons) chooser.set_position(gtk.WIN_POS_CENTER) chooser.set_select_multiple(True) response = chooser.run() if response == gtk.RESPONSE_OK: files = if chooser.get_select_multiple(): files = chooser.get_filenames() else: f = chooser.get_filename() files = [f] # ツリーのモデルに選択されたファイルを追加 self.treeModel.append_src_targets(files) chooser.destroy() return True def on_exit(self, widget, event=None, data=None): ''' 終了時の処理。 GTKのイベントループを終了させる。 ''' print '--- quit ---' gtk.main_quit() # # def main(): # パッケージファイル編集ウインドウ表示 wind = TestWindow() # GTKのイベントループ開始 gtk.gdk.threads_enter() gtk.main() gtk.gdk.threads_leave() # # if __name__ == '__main__': main()
今までも何度かgtk.TreeView()を使ったツールは作ったことがあるんですが、今回初めて(笑)PyGTKのチュートリアルを参考にしてデータモデル、表示用モデルを別々のクラスに分けました。
今回のコードは、前回までのコードよりも長くなってしまいましたが、
上記のソースコード中の赤で書かれている部分が肝で、あとはおまけのようなものだと思います。
赤で書かれている部分は、TreeViewで表示されるデータの内容を作る部分です。
ディレクトリを入れ子で表現するように変更するには、メソッドappend_src_targets(), find_iter_dir()あたり
を変更すればいいと思います。
「ここ、変なんじゃない?」とかありましたら、どしどし突っ込んでくださいまし。
つづく。