Programming」カテゴリーアーカイブ

GtkSourceDrawSpacesFlags

あけましておめでとうございます。
ということで GtkSourceView でアプリを作っているのだが。

GtkSourceView

GtkSourceDrawSpacesFlags というのがある。
gtk_source_view_set_draw_spaces で指定するのね。
以前書いたコードに追記して試しに使ってみる。

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

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

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", "O", "open", self.on_open),
                ("save", Gtk.STOCK_SAVE, "_Save", "S", "save", self.on_save),
                ("save_as", Gtk.STOCK_SAVE_AS, "Save as...", "S", "save as", self.on_save_as),
                ("quit", Gtk.STOCK_QUIT, "Quit", "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()
        # GtkSourceView
        self.view = GtkSource.View()
        desc = Pango.font_description_from_string("Monospace 11")
        self.view.override_font(desc)
        self.view.set_show_line_numbers(True)
        self.view.set_wrap_mode(Gtk.WrapMode.CHAR)
        self.view.set_draw_spaces(GtkSource.DrawSpacesFlags.ALL)
        # 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)

これは、まるで秀丸ではないか。
なんだよ、GtkSourceView には改行やタブ文字表示機能があるんだ。
dconf-editor で Gedit の設定を漁ったけど無かった…

GTK_SOURCE_DRAW_SPACES_NBSP とかは試してもよく解らないのだけど。
日本語圏内では解らないことなのだろうか、うーん。

ついでに、上記コードのようにフォント指定を作っている。
いや、デフォルトに戻したくなる場合もあるわけで。

# Get Default Font
settings = Gtk.Settings.get_default()
defaultfont = settings.props.gtk_font_name
desc = Pango.font_description_from_string(defaultfont)

これでデフォルトの PangoFontDescription が得られるわけだ。

どうでもいいけど Fedora の Monospace では半角全角で幅が違う。
VL ゴシックにすればいいわけだがコレは人によるからな。

ぶっちゃけ設定は Gedit の劣化パクリである。
括弧の強調表示方法がイマイチ解らない、GtkSourceBuffer 側なのよね。

現在インデントが全部半角スペースになる現象に悩まされている…
明日には出したいな、休みは明日までだし。

PangoFontDescription

memopoli の読み書きとツリー移動がなんとかなったので本サイトで公開。
まだ実装していないメニュー項目があるし設定も何もないけど「まあいいや」で。

どうしても 2011 年中に間に合わせたかっただけだ!
こんな最小限の実装までにとうとう大晦日まで掛かってしまった…

memopoli ヘルプ – L’Isola di Niente

私的にたいしたことはやっていないと思うので blog に書くことが無い。
後は自分で利用(バグ出し)しながら細かい機能を実装していくつもり。

次にフォント設定をやろうと考えていきなり困った。
GTK+ ではフォント情報をどうやって保存するのだろう?

Fonts

Pango にpango_font_description_to_string という関数があるね。
そういえば以前フォント変更する方法だけはやっていた。

GtkFontChooserDialog | PaePoi

今頃気がついたが gtk_widget_modify_font は古いようだ。
gtk_widget_override_font にすればいいのね。
よし、この前回コードをフォント情報が保存できるように書き換えてみよう。

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

from gi.repository import Gtk, Gio, Pango

class Win(Gtk.Window):
    def __init__(self):
        Gtk.Window.__init__(self)
        self.set_title("font test")
        entry = Gtk.Entry()
        entry.set_text("homurachan")
        button = Gtk.Button("Select Font")
        button.connect("clicked", self.on_clicked, entry)
        vbox = Gtk.VBox()
        vbox.pack_start(button, False, False, 0)
        vbox.pack_start(entry, False, False, 0)
        self.add(vbox)
        self.connect("delete-event", self.on_delete)
        # Config File Read
        try:
            f = open("font.conf", "r")
            s = f.read()
            f.close()
        except:
            s = "Monospace 16"
        # PangoFontDescription
        self.desc = Pango.font_description_from_string(s)
        entry.override_font(self.desc)
        self.show_all()

    def on_delete(self, widget, event):
        c = self.desc.to_string()
        f = open("font.conf", "w")
        f.write(c)
        f.close()

    def on_clicked(self, widget, entry):
        dlg = Gtk.FontChooserDialog("Select Font")
        if dlg.run() == Gtk.ResponseType.OK:
            self.desc = dlg.get_font_desc()
            #entry.modify_font(self.desc) # old Func
            entry.override_font(self.desc)
        dlg.destroy()

class App(Gtk.Application):
    def __init__(self):
        Gtk.Application.__init__(
                self,
                application_id="apps.test.font",
                flags=Gio.ApplicationFlags.FLAGS_NONE)
        self.connect("activate", self.on_activate)

    def on_activate(self, data=None):
        w = Win()
        w.set_application(self)

if __name__ == "__main__":
    app = App()
    app.run(None)

なるほど、単純に「フォント名 サイズ」文字列だけでいいのか。
これなら簡単だ、早速 memopoli に実装することにしよう。
メニューに乗せるかダイアログに入れるか、うーん…

ということで。

2011 年イッパイになったのでめでたく Y901, Palepoli 等の公開を終了。
早く消したかったけど我慢して一年も間をおいたので多分大丈夫だろう。
それでは良いお年を。

Dark Theme

GNOME 3.2 から導入された Dark Theme って簡単なんですね。
gtk_settings_get_default ()
で得られる Settings のプロパティを弄くるだけ。

ついでだから他にどんなプロパティがあるかを書き出す処理も。

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

from gi.repository import Gtk

# Sharing settings Default Value Check
settings = Gtk.Settings.get_default()
for s in dir(settings.props):
    if not s.startswith("_"):
        print "{0}: {1}".format(s, settings.get_property(s))

# Set Dark Theme
settings.props.gtk_application_prefer_dark_theme = True
# or
#settings.set_property('gtk-application-prefer-dark-theme', True)

w = Gtk.Window()
w.connect("delete-event", Gtk.main_quit)
w.show()
Gtk.main()

こんなに簡単な処理で Totem 等と同じ見た目になる。
他色々プロパティがあるけど利用するかどうかは微妙かな。

ついでに memopolix の現状。
memopoli に名前変更する、メモポリエックスじゃ言いにくい。
クリポリエックスも次で x を取る、Windows 版と名前を分ける必要はなかった。
ワイキュウマルイチエックスだけは x を付けないと混乱する人が出る。

ガシガシ例外を吐いてくれる、もう少しまともになったら何か書く。

destroy and delete-event Signal

現在作っている PyGI アプリで Window size を保存したい。
GtkApplication から起動なら delete-event で Gtk.main_quit せずに閉じる。
ちなみに delete-event は標準の「閉じる」ボタンか吐くシグナルです。

なので destroy Signal で処理しようとしたのだけど。

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

from gi.repository import Gtk, Gio

class DestroyTest(Gtk.Window):
    def __init__(self):
        Gtk.Window.__init__(self)
        cx, cy = self.get_size()
        print "initialize size {0},{1}".format(cx, cy)
        self.connect("delete-event", self.on_delete_event)
        self.connect("destroy", self.on_destroy)
        # Resize
        self.resize(320, 240)
        self.show_all()

    def on_destroy(self, widget):
        cx, cy = self.get_size()
        print "destroy size {0},{1}".format(cx, cy)

    def on_delete_event(self, widget, event):
        cx, cy = self.get_size()
        print "delete-event size {0},{1}".format(cx, cy)

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

""" output
initialize size 200,200
delete-event size 320,240
destroy size 200,200
"""

こうなっってしまった。
破棄された後に値を得ようとしているのだからそりゃそうだ。
.NET とかなら例外、って GTK+ は C 言語だったね。

それなら delete-event で設定保持を行えばいい。
んでもって emit も destroy から変更して。
で、又困ってしまった。

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

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

class DestroyTest(Gtk.Window):
    def __init__(self):
        Gtk.Window.__init__(self)
        button = Gtk.Button("emit delete-event")
        button.connect("clicked", self.on_button_clicked)
        self.add(button)
        self.connect("delete-event", self.on_delete_event, button)
        self.show_all()

    def on_button_clicked(self, button, data=None):
        """
            emit is No Close
        """
        event = Gdk.Event(Gdk.EventType.DELETE)
        self.emit("delete-event", event)
        # Enable a Close
        #self.emit("destroy")

    def on_delete_event(self, widget, event, button):
        button.set_label("catch delete-event")
        return False

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

delete-event の emit だけじゃ終了してくれない。
メニューやボタンから終了するなら destroy も投げる必要あり?

GtkApplication はどうやって閉じているのだろう。
やっぱり普通に Gtk.main() を使ったほうが楽かも。

memopolix 0.0.0

gjots2 の代わりを現在作っている。
Read in gjotsfile | PaePoi

躓きまくっているので Blog の更新が止まりぎみですが…
勉強だけやっていても面白くないし進歩もない、とにかく何か作りたい。
実際にアプリケーションを作ってみないと解らないことが結構多いですから。

アプリ名と仕様を考えるのに一番時間を使ったのだが…
名前ごときに時間を割いてもしかたがない、memopolix でいいや。
***poli で x を末尾につける、このパターンはつまり(以下略

ファイル仕様は gjots2 完全互換、但しコピーして元ファイルは汚さないように。
本体ツールバーはメインツールバーに保存と移動矢印のみとする。
それと一行めだったタイトルを GtkEntry に分離、ファイルはそのまま。
拡張するのは v2 からでいいだろう、もし拡張するならだが。
とにかく徹底的にシンプル、よし仕様はこんなものだろう。

Gio での Streaming I/O も解ったことだし限界まで Python 標準モジュールを使わずに gi を利用する。
そうしておけば Seed や Vala からも参考にできるという理由だが。
gnome-games の Swell Foop って Seed と Clutter で作っているんだね。

せっかくなので gettext で国際化してみたい。
コレは python モジュールを利用するしか無いみたい。

$ xgettext ui.py #=> messages.po
$ # Rewrite messages.po
$ msgfmt messages.po #=> messages.mo
$ mv messages.mo ja/LC_MESSAGES
import gettext
import os

gettext.bindtextdomain('messages', os.path.dirname(__file__))
_ = gettext.gettext

色々調べてみたけどコレが一番簡単だった。
インストールせずに使いたいので /usr/share/locale には入れたくないし。
ところで GtkActionEntry に stock_id 指定を行い label を None にすれば勝手に国際化表記なメニューやツールバーになるんだね、今頃知ったよ。
Opera とかみたいに自力でやったほうが簡単なことは内緒だよ。
Seed からは _ = imports.gettext.gettext; で使える。

それと GtkActionEntry で <shift><control><alt>Down なんてキーを割り付け上下移動させようと思ったけど GNOME3 では仮想デスクトップの移動になってしまう、<control>D とかにするのも、うーん。
割り付け無しのほうが潔いか。

それより詰まったのはファイルの書き込みだった。
GtkListstore なら seemex でやったけど GtkTreeStore はどうすれば?
再起を使えばいいということは解るけどイザ手段となると全然掴めない。

自力を諦めて素直に gjots2 のコードを参考にする。
SourceArchive.com

def tree_all_text(self, store, it, first):
    if not first:
        self.result += "\\NewEntry\n"
    body = store.get_value(it, 1)
    self.result += body
    if body and not body[-1] == "\n":
        self.result += "\n"
    it_child = store.iter_nth_child(it, 0)
    if it_child:
        if not first:
            self.result += "\\NewFolder\n"
        while it_child:
            self.tree_all_text(store, it_child, 0)
            it_child = store.iter_next(it_child)
        if not first:
            self.result += "\\EndFolder\n"

def write_file(self, store):
    self.result = ""
    it = store.get_iter_first()
    if it:
        self.tree_all_text(store, it, 1)
    f = open("test.txt", "w")
    f.write(self.result)
    f.close()

手段が解ってしまえば「なるほど〜」なんだけど再起ってムズい。
コレを分離する予定な一行めタイトルを入れる処理に変更しなきゃ…
読み取りは簡単なのにまさか書き込みがこんなに難しいとは。
やっぱり実際にアプリを作ってみないと解らないコトって多い。

先はまだ長い、まだ移動処理すら手をつけていない状態。
まあ Y901x は安定させるまでに二年掛かったしこんなものだ。
しかしツリーメモとしか使っていなかったけど gjots2 は多機能なのね。
memopolix は単なるツリーメモでいくけど gjots GTK3 版が出たらどうしよう?

一応バックアップ。
memopolix-0.0.0.tar.gz