wiki:PyGTK

PyGTK

適当メモ.主に日記からの転載.

TextView

textField  = gtk.TextView()
textBuffer = textField.get_buffer()

[startIter, endIter] = textBuffer.get_bounds()
# [最初の位置, 最後の位置]

print textBuffer.get_text(startIter, endIter)
  • 最初の位置と最後の位置を取得し,中の文字列を得る

ある処理を x 秒後に裏で実行

def __init__(self):
    self.id = gtk.timeout_add(1000, self.run)

def run(self):
    ... # process
    return True

def stop(self):
    if self.id:
        gtk.timeout_remove(self.id)
  • run が True を返し続ける限りは繰り返し実行される.False が返ると止まる.

×ボタンが押された時にウィジェットを破壊せずに hide する

window = gtk.Widnow(gtk.WINDOW_TOPLEVEL)
window.connect("delete-event", close)
open(window)

def close(w):
    w.hide()
    return True

def open(w):
    w.show()

注意点は以下の通り.

  • destroy イベントでなく delete-event をフックする
  • close メソッドで True を返す

TextView 内の文字に色づけなど

textview = gtk.TextView()
buffer = textview.get_buffer()

tag = buffer.create_tag('tag_name')
tag.set_property('foreground', 'blue')
# tag_name という名前の,字の色を青にするタグの生成

buffer.insert_with_tags_by_name(buffer.get_end_iter(),
                                'hogehoge', 'tag_name')

TextView 内にハイパーリンク的なものを作ってみる

DnD は未実装.

#!/usr/bin/env python

import gtk
import gobject
import pango

class HyperTextView(gtk.TextView):
    __gtype_name__ = 'HyperTextView'
    __gsignals__ = {'anchor-clicked': (gobject.SIGNAL_RUN_LAST, None, (str, str, int))}
    __gproperties__ = {
        'link':  (gobject.TYPE_PYOBJECT, 'link color', 'link color of TextView', gobject.PARAM_READWRITE),
        'active':(gobject.TYPE_PYOBJECT, 'active color', 'active color of TextView', gobject.PARAM_READWRITE),
        'hover': (gobject.TYPE_PYOBJECT, 'link:hover color', 'link:hover color of TextView', gobject.PARAM_READWRITE),
        }

    def do_get_property(self, prop):
        try:
            return getattr(self, prop.name)
        except AttributeError:
            raise AttributeError, 'unknown property %s' % prop.name

    def do_set_property(self, prop, val):
        if prop.name in self.__gproperties__.keys():
            setattr(self, prop.name, val)
        else:
            raise AttributeError, 'unknown property %s' % prop.name

    def __init__(self, buffer=None):
        gtk.TextView.__init__(self, buffer)
        self.link   = {'background': 'white', 'foreground': 'blue', 'underline': pango.UNDERLINE_SINGLE}
        self.active = {'background': 'light gray', 'foreground': 'red', 'underline': pango.UNDERLINE_SINGLE}
        self.hover  = {'background': 'light gray', 'foreground': 'blue', 'underline': pango.UNDERLINE_SINGLE}

        self.set_editable(False)
        self.set_cursor_visible(False)

        self.__tags = []

        self.connect('motion-notify-event', self._motion)
        self.connect('focus-out-event', lambda w, e: self.get_buffer().get_tag_table().foreach(self.__tag_reset, e.window))

    def insert(self, text, _iter=None):
        b = self.get_buffer()
        if _iter is None:
            _iter = b.get_end_iter()
        b.insert(_iter, text)

    def insert_with_anchor(self, text, anchor=None, _iter=None):
        b = self.get_buffer()
        if _iter is None:
            _iter = b.get_end_iter()
        if anchor is None:
            anchor = text

        tag = b.create_tag(None, **self.get_property('link'))
        tag.set_data('is_anchor', True)
        tag.connect('event', self._tag_event, text, anchor)
        self.__tags.append(tag)
        b.insert_with_tags(_iter, text, tag)

    def _motion(self, view, ev):
        window = ev.window
        x, y, _ = window.get_pointer()
        x, y = view.window_to_buffer_coords(gtk.TEXT_WINDOW_TEXT, x, y)
        tags = view.get_iter_at_location(x, y).get_tags()
        for tag in tags:
            if tag.get_data('is_anchor'):
                for t in set(self.__tags) - set([tag]):
                    self.__tag_reset(t, window)
                self.__set_anchor(window, tag, gtk.gdk.Cursor(gtk.gdk.HAND2), self.get_property('hover'))
                break
        else:
            tag_table = self.get_buffer().get_tag_table()
            tag_table.foreach(self.__tag_reset, window)

    def _tag_event(self, tag, view, ev, _iter, text, anchor):
        _type = ev.type
        if _type == gtk.gdk.MOTION_NOTIFY:
            return
        elif _type in [gtk.gdk.BUTTON_PRESS, gtk.gdk.BUTTON_RELEASE]:
            button = ev.button
            cursor = gtk.gdk.Cursor(gtk.gdk.HAND2)
            if _type == gtk.gdk.BUTTON_RELEASE:
                self.emit('anchor-clicked', text, anchor, button)
                self.__set_anchor(ev.window, tag, cursor, self.get_property('hover'))
            elif button in [1, 2]:
                self.__set_anchor(ev.window, tag, cursor, self.get_property('active'))

    def __tag_reset(self, tag, window):
        if tag.get_data('is_anchor'):
            self.__set_anchor(window, tag, None, self.get_property('link'))

    def __set_anchor(self, window, tag, cursor, prop):
        window.set_cursor(cursor)
        for key, val in prop.iteritems():
            tag.set_property(key, val)

gobject.type_register(HyperTextView)

if __name__ == '__main__':
    def clicked(widget, text, anchor, button):
        print widget, text, anchor, button

    t = HyperTextView()
    t.connect('anchor-clicked', clicked)
    t.link['foreground'] = 'dark blue'
    t.insert_with_anchor('Google', 'http://www.google.com/')
    t.insert('\n')
    t.insert_with_anchor('Yahoo!', 'http://www.yahoo.com/')

    w = gtk.Window()
    w.set_default_size(200, 100)
    w.connect('destroy', lambda w: gtk.main_quit())
    w.add(t)

    w.show_all()
    gtk.main()

key-press-event の罠

  • SCIM とかならキー入力が key-press-event に渡らないが,XIM だと渡ってしまうくさい.
  • 例えば Return 入力時の動作をいじるとき,key-press-event をフックする方法を採ると SCIM とかは大丈夫でも XIM でおかしなことになる.
  • キーバインドをシグナルに結びつける方法を採ればこの問題は解消する.
    import gobject
    import gtk
    
    class EnterKeyPressTextView(gtk.TextView):
        __gsignals__ = {
            'enter-pressed': (gobject.SIGNAL_RUN_LAST|gobject.SIGNAL_ACTION,
                              gobject.TYPE_NONE, (gobject.TYPE_INT,))
        }
    
    gobject.type_register(EnterKeyPressTextView)
    
    for mod in [0, gtk.gdk.SHIFT_MASK, gtk.gdk.CONTROL_MASK, gtk.gdk.MOD1_MASK]:
        gtk.binding_entry_add_signal(EnterKeyPressTextView, gtk.keysyms.Return, mod,
                                     'enter-pressed', gobject.TYPE_INT, mod)
        gtk.binding_entry_add_signal(EnterKeyPressTextView, gtk.keysyms.KP_Enter, mod,
                                     'enter-pressed', gobject.TYPE_INT, mod)
    
    if __name__ == '__main__':
        import sys
    
        e = EnterKeyPressTextView()
        e.connect('enter-pressed', lambda _e, m: sys.stdout.write('mod:%d\n' % m))
    
        w = gtk.Window()
        w.add(e)
        w.connect('delete-event', lambda _w, _e: gtk.main_quit())
        w.set_default_size(640, 480)
        w.show_all()
    
        gtk.main()