pygtkで遊ぼう(21) gtksourceview(gtk.TextView)で文字をハイライトさせる。
今日はgtksourceviewで文字列をハイライトさせる方法についてです。
この方法はgtk.TextViewにも共通して使えます。
サンプルとして、現在制作中のエディタの検索機能を例にしようと思います。
とりあえず、検索機能のテストも兼ねて、次のような検索バーを付けてみました。
(本当にただ付けました。。といった感じで。。)
検索文字列を入力フィールドに入力すると、次のように、該当する文字列
の状態が変わります。
まず、検索文字入力フィールドに対して'edited'というシグナルハンドラとして
connectします。
この関数は、入力フィールドに文字列が入力されるたびに呼び出されます。
この関数が呼び出されると、以下の処理を行います。
(1) 正規検索モジュールの re を使って文字列を検索します。
(2) reの検索結果をSourceViewのサブクラスに「これハイライトして」と渡す。
(3) カーソル位置に一番近い文字列にカーソルを移動。
def __on_search_keyword_changed(self, widget, keyword, caseSence): ''' 現在表示されているテキスト中をkeywordで検索する。 keyword: 検索文字列 caseSence: True -> 大文字、小文字を区別する。 False -> 大文字、小文字を区別しない。 ''' page, editView = self.__get_current_edit_view() if editView == None: return if len(keyword) == 0: # マークをクリア editView.set_highlight(None) return flag = re.VERBOSE if caseSence == False: flag |= re.IGNORECASE # 検索 result = re.finditer(re.compile(keyword.decode('utf-8'), flag), editView.get_text().decode('utf-8')) print 're complete' # 検索結果をハイライトし、現在のカーソル位置の直後のハイライト # にカーソルを移動 editView.set_highlight([r.span() for r in result]) editView.cursor_to_highlight()
上記の処理、動き的にはインクリメンタルサーチっぽくなります。
インクリメンタルサーチに 正規検索をつかうと大変なことに(ヒットした文字の数が膨大に)なる可能性
があるので、本来こんなことはしないと思いますけど。。reも使ってみたかったので。。(笑
実際、入力フィールドに「\D」とか入力すると。。。検索結果がハイライトされるまでに
長〜〜い時間がかかります。
正規検索自体は一瞬で終わるんですが、文字列をハイライトさせる箇所が膨大なので、
ハイライトデータの設定処理に時間がかかってしまいます。
少し脱線しましたが、次にsourceviewのサブクラス側でハイライトの設定を行っているメソッドです。
sourceviewの文字列をハイライトさせるためには、sourceviewのバッファ(gtksourceview.SourceBuffer)
に対してタグという情報を設定すればOKです。
タグというのは、「ここからここまでの文字を、この属性(色とか)で表示してね」という指定です。
以下のメソッドでは、
(1) それまでにSourceBufferに設定されているタグを削除
(2) 渡された位置情報をもとに、タグ情報を作成してSourceBufferに設定。
を行います。
def set_highlight(self, posList=None): ''' ハイライトさせるテキスト位置を設定する。 それまで保持していた内部情報をクリアした後で、 posListで渡された位置情報をもとに、内部情報を構築する。 (内部情報) タプル(タグ情報、テキスト開始イテレータ, テキスト終端イテレータ) をリストで管理する。 posList: ハイライトさせるテキストの位置情報のタプル [(start, end), (start, end), ....]の形式となっている。 start-> ハイライトさせる文字列の開始位置(ゼロオリジン) end -> ハイライトさせる文字列の終端位置(ゼロオリジン) ''' bf = self.get_buffer() if bf == None: return startIter = bf.get_start_iter() endIter = bf.get_end_iter() # 現在設定されているタグを削除 for tag, sIter, eIter in self.highlights: bf.remove_tag(tag, startIter, endIter) del self.highlights[:] if not posList: return # 新しくタグを設定 for start, end in posList: tag = bf.create_tag(foreground='#FFFF00', underline=pango.UNDERLINE_DOUBLE) sIter = bf.get_iter_at_offset(start) eIter = bf.get_iter_at_offset(end) bf.apply_tag(tag, sIter, eIter) self.highlights.append((tag, sIter, eIter)) self.curHighlight = None
さてと。。
上記の方法では、
・検索した結果を全てハイライトさせるため、検索結果が多くなると、検索結果が表示されるまでに時間がかかる。
・インクリメンタルサーチにreを使うのはちょっと。。
という問題があるので、これから上記の方法以外の方法(reを使用せずに)に変更してみようと思います。
うまくいったらまたご紹介します。