Python」タグアーカイブ

Gio GFileEnumerator and load_contents

seed をやっている日本人がいた、あぁ日本語で読めるのは素晴らしい。
とはいえ海外でもあまり盛り上がっていないわけですが。
前ページを見て笑った、日本語で探すとそうなってしまうんだよ。

seedでGIOしてみる – ふとしの日記

なるほど、Gio でディレクトリ内容列挙したい場合はこうするのか。
引数は必ずしも JSON でなくてもいいのか?又解らないことが増えた。
while は for in 文でいいんじゃない?と思ったが…
そういえば JavaScript ではオブジェクトメソッドの列挙になるのよね。

ま、私は PyGI でヤルんですけど。
ということで $HOME のファイルを列挙するサンプルコード。
“standard::name” だと byte になるので “standard::display-name” にした。

#!/usr/bin/env python
#-*- coding:utf-8 -*-

import os
from gi.repository import Gio

# set Directory Name
d = Gio.file_new_for_path(os.path.expanduser("~"));
# Get GFileEnumerator
enum = d.enumerate_children(
        Gio.FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME,
        Gio.FileQueryInfoFlags.NONE,
        None )
for info in enum:
    # info: GFileInfo
    print info.get_display_name()

Python だと当然のように for in 文でイケるようだ。
seed は JavaScript の仕様そのままなので面倒かも。
逆に enumerate_children 等は引数を全部指定する必要がある。
seed は引数が曖昧でいいところもやっぱり JavaScript なのね。

ついでに Gio もう一つ、海外。
GIO tutorial: Stream IO ? Johannes Sasongko’s blog

#!/usr/bin/env python
#-*- coding:utf-8 -*-

import os
from gi.repository import Gio

# set File Name
fn = os.path.expanduser("~/.gtk-bookmarks")
# Create GLocalFile
f = Gio.File.new_for_path(fn)
# load_contents
result, contents, length = f.load_contents(None)
if result:
    print contents

で .gtk-bookmarks 内容が print される。
PyGtk を PyGI に書き換えしたのだが引数も戻り値も微妙に違った。
とにかくファイルの一騎読み込みはこうすればいいみたい。

でも stream のほうは PyGI では上手く行かない。
何か手段があるのだろうけど今はまだ Gio 勉強中。

# おまけ

fedora 16 アップデート通知に Gedit があった。

やっとドラッグアンドドロップ編集で落ちるのを修正してくれた!
ついやってしまっていったい何度書いたコードを無駄にしたことか。

Read in gjotsfile

昨日のコードを .gjotsfile を読み込むように改造してみた。
Gjots2 が保存するファイルは

“\NewEntry” でエントリーの追加
“\NewFolder” で入れ子の作成
“\EndFolder” で入れ子を一階層戻る
ツリータイトルはエントリーの一行め

だけという恐ろしく単純明解な構造である。
ルールもタイトルの一文字めには \ を使わないということだけ。
これなら初心者でも自力で読み書きするコードが書けそうです。

Python でファイルを一行毎に処理する方法は

path = os.path.expanduser("~/.gjotsfile")
f = open(path)
x = f.read()
f.close()
lines = x.split("\n")
for line in lines:
    hoge

という一番単純な方法と

path = os.path.expanduser("~/.gjotsfile")
f = open(path)
try:
    for line in f:
        hoge
finally:
    f.close()

の方法がある、下の方法のほうが圧倒的に早い。
一度全部読み込んで展開するのかストリームで読むかの差である。
但し行末に改行コードが含まれている、それと try 文にしないと何かあった場合に close できないというオチがあるので注意して使いましょう。

とりあえず上記フラグがあった場合に分岐して if 文を作成。
現在は空状態を示す方法として “\NonTitle” みたいなフラグを追加。
“\NewEntry” で GtkTreeStore にアペンドして読み込んだ部分を空にする。
イテレータを “\NewFolder” で入れ替え “\EndFolder” で書き戻し。

そんな感じにして上手くいったコード。

#!/usr/bin/env python
#-*- coding:utf-8 -*-

import os
from gi.repository import Gtk, Gio, GtkSource

class TreeWin(Gtk.Window):
    """
        GtkTreeView Sample
    """
    def __init__(self):
        Gtk.Window.__init__(self)
        # CellRenderer
        cell = Gtk.CellRendererText()
        column = Gtk.TreeViewColumn("Tree Title", cell)
        column.add_attribute(cell, "text", 0)
        # TreeView and TreeStore
        store = Gtk.TreeStore.new([str, str])
        tree = Gtk.TreeView.new_with_model(store)
        tree.append_column(column)
        # Hide Header
        tree.props.headers_visible = False
        # Signal
        selection = tree.get_selection()
        selection.connect("changed", self.selection_changeed)
        #
        # Data
        self.path = os.path.expanduser("~/.gjotsfile")
        t = ".gjotsfile"
        s = ""
        it = None
        itold = None
        f = open(self.path)
        try:
            for line in f:
                if line == "\\NewEntry\n":
                    if t == ".gjotsfile":
                        it = store.append(it, [t, s])
                    elif t != "\\NoneTitle":
                        store.append(it, [t, s])
                    s = ""
                    t = "\\NoneTitle"
                elif line == "\\NewFolder\n":
                    itold = it.copy()
                    it = store.append(it, [t, s])
                    s = ""
                    t = "\\NoneTitle"
                elif line == "\\EndFolder\n":
                    store.append(it, [t, s])
                    it = itold
                    s = ""
                    t = "\\NoneTitle"
                else:
                    if t == "\\NoneTitle":
                        t = line[:-1]
                    s += "{0}".format(line)
        finally:
            f.close()
        #
        # GtkSourceView
        self.view = GtkSource.View()
        self.view.set_show_line_numbers(True)
        # Paned and ScrolledWindow
        swa = Gtk.ScrolledWindow()
        swa.add(tree)
        swb = Gtk.ScrolledWindow()
        swb.add(self.view)
        hp = Gtk.HPaned()
        hp.add1(swa)
        hp.add2(swb)
        # self
        self.set_title("TreeView Test")
        self.add(hp)
        self.show_all()

    def selection_changeed(self, widget, data=None):
        """
            Params @ widget: GtkTreeSelection
        """
        model, it = widget.get_selected()
        if it:
            buf = self.view.get_buffer()
            buf.set_text(model.get_value(it, 1))

class App(Gtk.Application):
    def __init__(self):
        Gtk.Application.__init__(
                self,
                application_id="apps.test.treeview",
                flags=Gio.ApplicationFlags.FLAGS_NONE)
        self.connect("activate", self.on_activate)
        
    def on_activate(self, data=None):
        w = TreeWin()
        w.set_application(self)
    
if __name__ == "__main__":
    app = App()
    app.run(None)

もう少しスマートにできそうなんだけど…
そういえば Ubuntu でも動くかな、仮想マシンの 11.10 で実験。

よし動く、後は書き込みとツリーの追加削除移動なんかだ。
とりあえず今日はココまで、ノンビリすぎだと思うけど。

Python with GTK3 @ GtkTreeView

gjots2 が ORBit2 を必要とするので GNOME3 に入れたくない。
というわけで代わりになるものを自作することにした。
まてよ、私は GtkTreeView を ListView としてしか使ったことがない。
GtkListStore を GtkTreeStore にすればいい、だと思うけど実験だ。

ところで PyGI のウイジェットには new というメソッドがあるわけだが。

store = Gtk.TreeStore.new([str, str])
# or Compatibility PyGtk
store = Gtk.TreeStore(str, str)

GtkTreeStore を作るには new でシーケンスを渡すか PyGtk と同様に作成。
というか TreeStore(str, str) のほうは多分 PyGtk 互換用だろう。
C では数と内容をズラズラだから PyGI 的にはシーケンスで書くほうが正しいかと思う。

けど new_with_model とかのパラメータ指定差を吸収する互換も便利だから捨てがたい。
パラメータが void しか無いウイジェットは互換のままでいいと思うけど。

そういえば、最近気がついたのだが Property も props メソッドから辿れる。
どちらでも結果は同じなのでお好みで、しかしなんというか Gtk# 臭くなったものだ。
ハイフンをアンダースコアに変換するのを忘れないでね。

# Convert '-' to '_'
tree.props.headers_visible = False
# or Compatibility PyGtk
tree.set_property("headers-visible", False)

それはともかく、GtkTreeView の利用方法。
CellRenderer, TreeViewColumn, TreeStore(ListStore) が必要。

ツリーに表示するレンダラを決める、今回は文字列なので GtkCellRendererText を。
そのレンダラを表示するカラムが必要、複数作れば ListView になると解るね。
シーケンスの何番目データをツリー表示するか、今回は最初のデータなので 0 を指定。

型を指定してシーケンスにして TreeStore を作成。
ソレを TreeModel として TreeView 作成のパラメータにする。
先ほど作成したカラムをアペンド、コレでツリー部分作成は終わり。

ツリー選択を変更したシグナルを捕まえるコードを忘れずに。
ハンドラの widget は GtkTreeSelection になるようだ。

後は GtkTreeIter を介してデータを入れていく。
GtkTreeIter に None を指定すればツリーの先頭に入る。

人により微妙に言い回しが違う専門用語だらけになったけどなんとなく解って。
GTK+ には簡単な ListBox が無いので嫌でも使うハメになる時があるから。

ということでサンプルコード。

#!/usr/bin/env python
#-*- coding:utf-8 -*-

from gi.repository import Gtk, Gio, GtkSource

class TreeWin(Gtk.Window):
    """
        GtkTreeView Sample
    """
    def __init__(self):
        Gtk.Window.__init__(self)
        # CellRenderer
        cell = Gtk.CellRendererText()
        column = Gtk.TreeViewColumn("Tree Title", cell)
        column.add_attribute(cell, "text", 0)
        # TreeView and TreeStore
        store = Gtk.TreeStore.new([str, str])
        tree = Gtk.TreeView.new_with_model(store)
        tree.append_column(column)
        # Hide Header
        tree.props.headers_visible = False
        # Signal
        selection = tree.get_selection()
        selection.connect("changed", self.selection_changeed)
        #
        # Data
        it0 = store.append(None, ["1.0", "Kaname Madoka"])
        store.append(it0, ["1.1", "Akemi Homura"])
        store.append(it0, ["1.2", "Tomoe Mami"])
        store.append(None, ["2.0", "Miki Sayaka"])
        it1 = store.append(None, ["3.0", "Sakura Kyoko"])
        store.append(it1, ["3.1", "QB"])
        #
        # GtkSourceView
        self.view = GtkSource.View()
        self.view.set_show_line_numbers(True)
        # Paned and ScrolledWindow
        swa = Gtk.ScrolledWindow()
        swa.add(tree)
        swb = Gtk.ScrolledWindow()
        swb.add(self.view)
        hp = Gtk.HPaned()
        hp.add1(swa)
        hp.add2(swb)
        # self
        self.set_title("TreeView Test")
        self.add(hp)
        self.show_all()

    def selection_changeed(self, widget, data=None):
        """
            Params @ widget: GtkTreeSelection
        """
        model, it = widget.get_selected()
        if it:
            buf = self.view.get_buffer()
            buf.set_text(model.get_value(it, 1))

class App(Gtk.Application):
    def __init__(self):
        Gtk.Application.__init__(
                self,
                application_id="apps.test.treeview",
                flags=Gio.ApplicationFlags.FLAGS_NONE)
        self.connect("activate", self.on_activate)
        
    def on_activate(self, data=None):
        w = TreeWin()
        w.set_application(self)
    
if __name__ == "__main__":
    app = App()
    app.run(None)

ツリー構築だけならそんなに難しくはないね。
後はファイルを読み込んでどうメモリ展開するかだ。

せっかくなので不満だった部分
・GtkSourceView を利用した色分け表示
・タイトルは一行目ではなく独自データに
なんかも実装しつつ Gjots2 完全互換も可能にできればいいな。

# おまけ

窓の杜 – 【NEWS】「AzPainter」「AzDrawing」の作者がWindows向けの全ソフトの開発終了を宣言

書いていることが Linux を初めたばかり丸解りなんだが…
AU○RA とかみたく寒いことにならなきゃいいけど。

Linux では Windows なんて比較にならないほど有名どころ以外は見向きもされないのだから外国人向けに英語でサンプルコードでも書いて自分が使う範囲だけ作ってたほうがマシだって。

Gio get_description

最近は溜まりまくっている Python 平書きコードの整理をしている。
といっても大半が PyGtk の覚書だったものを PyGI に作り直しであるが。
こんな状態だっったりする。

ソレより GNOME3 で gnomevfs が使えなくなったのをなんとかしないと。
Gio で「ファイルの種類」を得る方法が中々見つからず苦労した。
一旦 Content Type を得て変換するようだと解った。

とりあえず GLocalFile を作成。
query_info の引数に得たい情報文字列をコンマ区切りで突っ込む。
Gio.FILE_ATTRIBUTE_STANDARD_SIZE 等は只の #define された文字列。
GFileInfo
後はメソッドで取り出し、こんな感じかな。

ということで解ったところまで。
ファイルを何かドロップすると詳細が書き出されるウインドウ。

#!/usr/bin/env python
#-*- coding:utf-8 -*-

import sys
import gi
try:
    gi.require_version("Gtk", "3.0")
except:
    print "This Program is GTK+ 3.0 or later."
    sys.exit()

from gi.repository import Gtk, Gdk, Gio

res = """----------
Name: {0}
Size: {1} byte
Content Type: {2}
Mime Type: {3}
Description: {4}
----------
"""

attr = (
    Gio.FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME,
    Gio.FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE,
    Gio.FILE_ATTRIBUTE_STANDARD_SIZE )

class Win(Gtk.Window):
    """
        Information at Dropped File
    """
    def __init__(self):
        Gtk.Window.__init__(self)
        # DnD
        dnd_list = Gtk.TargetEntry.new("text/uri-list", 0, 0)
        self.drag_dest_set(
                Gtk.DestDefaults.MOTION |
                Gtk.DestDefaults.HIGHLIGHT |
                Gtk.DestDefaults.DROP,
                [dnd_list],
                Gdk.DragAction.MOVE )
        # GtkLabel
        self.label = Gtk.Label("Please drop your files")
        self.add(self.label)
        # Gtk.Window
        self.drag_dest_add_uri_targets()
        self.connect("drag-data-received", self.on_drag_data_received)
        self.set_title("dnd_type")
        self.show_all()

    def on_drag_data_received(self, widget, drag_context, x, y, data, info, time):
        uris = data.get_uris()
        s = ""
        for uri in uris:
            # Create GLocalFile
            f = Gio.file_new_for_uri(uri)
            # Create GFileInfo
            info = f.query_info(
                    ",".join(attr),
                    Gio.FileQueryInfoFlags.NONE,
                    None )
            # Anyway Get Content Type
            ct = info.get_content_type()
            # splintf
            s += res.format(
                    info.get_display_name(),
                    info.get_size(),
                    ct,
                    Gio.content_type_get_mime_type(ct),
                    Gio.content_type_get_description(ct) )
        self.label.set_text(s)

class App(Gtk.Application):
    def __init__(self):
        Gtk.Application.__init__(
                self,
                application_id="apps.test.akemi.homura",
                flags=Gio.ApplicationFlags.FLAGS_NONE)
        self.connect("activate", self.on_activate)
        
    def on_activate(self, application, user_data=None):
        w = Win()
        w.set_application(self)
    
if __name__ == "__main__":
    app = App()
    app.run(None)

うんコレで拡張子無しでもキチンと description が得られる。
Mime Type って Content Type と何が違うのかイマイチ解らない。

後は Last Write Time 等なんだがまだ解っていない。
os.stat から得られるのは知っているが Gio でやりたいので。

てゆーか昔のコードはほとんど役に立たなくなってしまったような。
もう少し纏めたら又 Tips ページを作ります。

ll command and GtkSourceView3

Fedora 16 の update 通知があったので適用。
100 個もあるのか、まぁ chrome や flash も含まれていることだし。

PyGI が上書きされちゃったみたいだけど GBoxed 関連は修正されていた。
修正してくれたようで、放置されると疑ってごめんなさい。

ところで Fedora は ~/.bash_profile, ~/.bashrc どちらにもエイリアス指定が無い。
けど ll が通る、’ls -alF’ ではなく ‘ls -l’ のエイリアスのようだが。
Ubuntu から乗り換えて半年以上たつのに今頃違いに気がついた私って…

Ubuntu は ~/.bashrc で指定だけど Fedora はチト違うんだね。
てか Fedora は which コマンドのみで alias も探せるようにしているのか。
alias コマンドで充分だと思うけど。

【88】シェル(bash)を自分用にカスタマイズ【alias の覚書】 – 分室の分室

ついでに見つけたページ、lls は早速真似させていただくことにする。
Content Type でソートできれば拡張子もいらないけどさすがに無理か。

**********

ところで上画像のように Update に GtkSourceView があった。
何って Gedit や Anjuta の GTK+ 製エディタ部品そのものです。
テキストの DnD 編集で Gedit が落ちるのは、直っていなかった…

思いついた、コレを使って Gjot2 の代わりでも試しに作ってみようかなと。
Gedit のハイライト設定をパクれば表示は Gedit と同じになって便利かも。

API ドキュメントは以下にある。
GtkSourceView 3 Reference Manual

PyGI からは GtkSource の import で使える。
とりあえず基本読み書きと行番号表示のみの最小限コード。

#!/usr/bin/env python
#-*- coding:utf-8 -*-

from gi.repository import Gtk, Gio, Gdk, GtkSource

class TextEditor(Gtk.Window):
    def __init__(self):
        Gtk.Window.__init__(self)
        # GtkUIManager and GtkAccelGroup
        self.uimanager = Gtk.UIManager()
        accelgroup = self.uimanager.get_accel_group()
        self.add_accel_group(accelgroup)
        # GtkActionGroup
        self.actiongroup = Gtk.ActionGroup("editor_menu")
        # GtkActionEntry
        ae = [  ("open", Gtk.STOCK_OPEN, "_Open", "<Control>O", "open", self.on_open),
                ("save", Gtk.STOCK_SAVE, "_Save", "<Control>S", "save", self.on_save),
                ("save_as", Gtk.STOCK_SAVE_AS, "Save as...", "<Shift><Control>S", "save as", self.on_save_as),
                ("quit", Gtk.STOCK_QUIT, "Quit", "<Control>Q", "quit", self.on_quit),
                ("file", None, "_File") ]
        self.actiongroup.add_actions(ae)
        self.uimanager.insert_action_group(self.actiongroup, 0)
        self.uimanager.add_ui_from_string(menu_xml)
        # MenuBar
        menubar = self.uimanager.get_widget('/MenuBar')
        # Toolbar and Style
        toolbar = self.uimanager.get_widget('/ToolBar')
        toolbar.set_style(Gtk.ToolbarStyle.ICONS)
        style = toolbar.get_style_context()
        Gtk.StyleContext.add_class(style, Gtk.STYLE_CLASS_PRIMARY_TOOLBAR)
        # StatusBar
        statusbar = Gtk.Statusbar()
        #
        #
        # Create GtkSourceView
        self.view = GtkSource.View()
        # Show Line Number
        self.view.set_show_line_numbers(True)
        #
        #
        # Add
        sw = Gtk.ScrolledWindow()
        sw.add(self.view)
        vbox = Gtk.VBox()
        vbox.pack_start(menubar, False, True, 0)
        vbox.pack_start(toolbar, False, True, 0)
        vbox.pack_start(sw, True, True, 0)
        vbox.pack_start(statusbar, False, True, 0)
        self.add(vbox)
        # self
        self.open_filename = ""
        self.resize(300, 300)
        self.set_title("Text Editor")
        self.show_all()
        self.set_focus(self.view)

    def on_open(self, widget, data=None):
        dlg = Gtk.FileChooserDialog(
                "Open File",
                self,
                Gtk.FileChooserAction.OPEN,
                (Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL, 
                Gtk.STOCK_OPEN, Gtk.ResponseType.ACCEPT))
        r = dlg.run()
        if r == Gtk.ResponseType.ACCEPT:
            self.open_filename = dlg.get_filename()
            f = open(self.open_filename, "r")
            t = f.read()
            f.close()
            buf = self.view.get_buffer()
            buf.set_text(t)
        dlg.destroy()

    def on_save(self, widget, data=None):
        if self.open_filename == "":
            self.on_save_as(widget)
        else:
            buf = self.view.get_buffer()
            t = buf.get_text(buf.get_start_iter(), buf.get_end_iter(), False)
            f = open(self.open_filename, "w")
            f.write(t)
            f.close()

    def on_save_as(self, widget, data=None):
        dlg = Gtk.FileChooserDialog(
                "Save File",
                self,
                Gtk.FileChooserAction.SAVE,
                (Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL, 
                Gtk.STOCK_SAVE, Gtk.ResponseType.ACCEPT))
        r = dlg.run()
        if r == Gtk.ResponseType.ACCEPT:
            buf = self.view.get_buffer()
            t = buf.get_text(buf.get_start_iter(), buf.get_end_iter(), False)
            self.open_filename = dlg.get_filename()
            f = open(self.open_filename, "w")
            f.write(t)
            f.close()
        dlg.destroy()

    def on_quit(self, widget, data=None):
        self.emit("destroy")

menu_xml = """<ui>
    <menubar name="MenuBar">
        <menu action="file">
            <menuitem action="open"/>
            <menuitem action="save"/>
            <menuitem action="save_as"/>
            <separator/>
            <menuitem action="quit"/>
        </menu>
    </menubar>
    <toolbar name="ToolBar">
        <toolitem action="open"/>
        <toolitem action="save"/>
    </toolbar>
</ui>"""

class App(Gtk.Application):
    def __init__(self):
        Gtk.Application.__init__(
                self,
                application_id="apps.test.editor",
                flags=Gio.ApplicationFlags.FLAGS_NONE)
        self.connect("activate", self.on_activate)
        
    def on_activate(self, data=None):
        w = TextEditor()
        w.set_application(self)
    
if __name__ == "__main__":
    app = App()
    app.run(None)

FileChooserDialog の save で WARNNING が出て困っていたけど…
Gedit でも出るヤン、FileChooserDialog 自体のバグだろう。

読み書きに関しては Gio を使うべきなんだろうけど今はワカンネ!
これで問題なく読み書きできるから充分かと。

ついでに今頃解ったけど destroy を emit すれば閉じることができる。
GtkApplication が管理するので delete-event は使えない。

しかし GtkSourceView のみなら DnD 編集しても落っこちないのね。
Gedit 側のバグだったみたい、とにかく気長に修正待ちするけど。

ということで Gedit と同じ行番号表示ができるエディタでっきあっがりぃ。

コレを上手く使って Gjot2 の代わりっぽいを作りたいな。
平書きな Python コードも既に 1000 個を越えているしもっとまとめたい。
っっってその大半が PyGtk のコード、PyGI に書き直しメンドクセ!
まあその財産があるからこんなコードが数時間で書けるのであるが。