試験運用中なLinux備忘録・旧記事

はてなダイアリーで公開していた2007年5月-2015年3月の記事を保存しています。

PyGTKでテキストビューを用いる際にテキストタグを多く作成・設定する場合の処理の整理について

unified形式のdiffを取得してGTK+のテキストビューに表示する(diffファイル保存可)」でテキストバッファの中で書式変更を行うためのテキストタグを多く作成してそれぞれに色やスタイルを指定するための値を入れているが、これに関係した処理を整理してコードを見やすくすることができたので、その手法をここで扱う。

テキストタグ関係の処理を分離する

  • テキストタグオブジェクトはテキストタグテーブルのオブジェクトに入れ、そのテキストタグテーブルをテキストバッファのオブジェクトに指定して使うという形になっている
  • テキストバッファには各テキストタグに設定した名前(コンストラクタ引数)の文字列によってテキストタグを指定して書式変更(タグ適用)をするためのapply_tag_by_name()insert_with_tags_by_name()(追加と同時に適用する場合)などのメンバ関数が用意されている(直接個別のテキストタグオブジェクトにアクセスできなくてもよい)

といったことから、テキストタグテーブルやテキストバッファのクラスを継承してその中で各テキストタグの作成や設定の記述をするように分離することができる。

テキストタグの共通属性を適用したクラスを作ってそれを継承する

テキストタグについてもクラスの継承を用いて、例えば太字にするものはGObjectプロパティweightをpango.WEIGHT_BOLDにするようにしたクラスを作っておき、太字にしたいテキストタグオブジェクトを作るときにこのクラスのインスタンスを作る(追加の共通属性があれば更に継承しても可)というやり方で個別にその値を設定する記述を不要にできる。

unified形式のdiffを取得してGTK+のテキストビューに表示する(diffファイル保存可)」と同じ処理内容のコードを

  • テキストタグテーブルのクラスを継承した場合
  • テキストバッファのクラスを継承した場合

のそれぞれについてを下に貼り付ける。テキストタグテーブルのクラスを継承したもののほうが、元々の用途の関係もあるが手順も少ないために扱いやすい印象がある。

テキストタグテーブルのクラスを継承したもの

[任意]ファイル名: getdifftest2.py

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

import difflib
import urllib
import time
import sys
import os
try:
  import pygtk
  pygtk.require('2.0')
except:
  pass
try:
  import pango
  import gtk
except:
  print >> sys.stderr, 'Error: PyGTK is not installed'
  sys.exit(1)


def run_errordialog(title, format1, format2=None):
  """
  エラーダイアログを表示
  """
  errdlg = gtk.MessageDialog(type=gtk.MESSAGE_ERROR, buttons=gtk.BUTTONS_CLOSE, message_format=format1)
  if format2:
    errdlg.format_secondary_text(format2)
  errdlg.props.title = title
  errdlg.run()
  errdlg.destroy()

def save_difffile(outfile, text):
  """
  差分ファイルを保存
  """
  try:
    f_out = open(outfile, 'w')
  except IOError, (errno, msg):
    run_errordialog('Error', 'cannot open output file "%s": %s', (outfile, msg))
    return False
  try:
    try:
      f_out.write(text)
    except IOError, (errno, msg):
      run_errordialog('Error', 'cannot write output file "%s": %s', (outfile, msg))
      return False
  finally:
    f_out.close()
  return True


class LabelWithMnemonic(gtk.Label):
  """
  ニーモニック・ウィジェットを初期化時に指定するラベル
  """
  def __init__(self, str, widget):
    gtk.Label.__init__(self, str)
    self.props.use_underline = True
    self.props.mnemonic_widget = widget

class BrowseButton(gtk.Button):
  """
  参照ボタン
  """
  def __init__(self, entry):
    gtk.Button.__init__(self)
    self.__entry = entry
    self.props.label = '...'
    self.__filename = None
    self.connect('clicked', self.__on_self_clicked)
  def set_filename(self, filename):
    self.__filename = filename
  def __on_self_clicked(self, widget):
    opendlg = gtk.FileChooserDialog(title='Select file', buttons=(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, gtk.STOCK_OPEN, gtk.RESPONSE_ACCEPT))
    opendlg.props.local_only = True
    if self.__filename:
      opendlg.set_filename(self.__filename)
    if opendlg.run() == gtk.RESPONSE_ACCEPT:
      self.__filename = opendlg.get_filename()
      self.__entry.props.text = self.__filename
    opendlg.destroy()

class PathEntry(gtk.Entry):
  """
  パス指定用テキスト入力欄
  """
  __TARGET_TYPE_TEXT_URI_LIST = 12345
  __dnd_list = [('text/uri-list', 0, __TARGET_TYPE_TEXT_URI_LIST),]
  def __init__(self):
    gtk.Entry.__init__(self)
    self.connect('drag_data_received', self.__on_drag_data_received)
    self.drag_dest_set(gtk.DEST_DEFAULT_MOTION |
                       gtk.DEST_DEFAULT_HIGHLIGHT | gtk.DEST_DEFAULT_DROP,
                       self.__dnd_list,
                       gtk.gdk.ACTION_COPY)
  def __on_drag_data_received(self, widget, context, x, y, selection, info, time):
    """
    テキストを受け取ったパスに指定
    """
    for item in selection.data.rstrip('\x00').splitlines():
      if item.startswith('file:'):
        path = os.path.normpath(urllib.url2pathname(item[5:]))
        if os.access(path, os.R_OK):
          self.props.text = path

class BoldTextTag(gtk.TextTag):
  """
  太字にするテキストタグ
  """
  def __init__(self, *args, **kwargs):
    gtk.TextTag.__init__(self, *args, **kwargs)
    self.props.weight = pango.WEIGHT_BOLD

class MyTextTagTable(gtk.TextTagTable):
  """
  テキストタグテーブル
  """
  def __init__(self, *args, **kwargs):
    gtk.TextTagTable.__init__(self, *args, **kwargs)
    # テキストタグ
    self.__texttag_plus3 = BoldTextTag('plus3')
    self.__texttag_plus3.props.foreground = '#e71585'
    self.__texttag_plus3.props.background = '#ffe4e1'
    self.__texttag_plus = BoldTextTag('plus')
    self.__texttag_plus.props.family = 'Monospace'
    self.__texttag_plus.props.foreground = '#e71585'
    self.__texttag_minus3 = BoldTextTag('minus3')
    self.__texttag_minus3.props.foreground = '#4169e1'
    self.__texttag_minus3.props.background = '#e6e6fa'
    self.__texttag_minus = BoldTextTag('minus')
    self.__texttag_minus.props.foreground = '#4169e1'
    self.__texttag_hunk = gtk.TextTag('hunk')
    self.__texttag_hunk.props.foreground = '#2e8b57'
    self.__texttag_hunk.props.background = '#f0fff0'
    self.__texttag_normal = gtk.TextTag('normal')
    self.__texttag_normal.props.family = 'Monospace'
    self.__texttag_error = gtk.TextTag('error')
    self.__texttag_error.props.foreground = '#c00'
    self.add(self.__texttag_plus3)
    self.add(self.__texttag_plus)
    self.add(self.__texttag_minus3)
    self.add(self.__texttag_minus)
    self.add(self.__texttag_hunk)
    self.add(self.__texttag_normal)
    self.add(self.__texttag_error)

class MainWindow(gtk.Window):
  """
  メインウィンドウ
  """
  def __init__(self, *args, **kwargs):
    gtk.Window.__init__(self, *args, **kwargs)
    # ショートカットキー(アクセラレータ)
    self.__accelgroup = gtk.AccelGroup()
    self.add_accel_group(self.__accelgroup)
    # メニュー項目
    self.__item_quit = gtk.ImageMenuItem(gtk.STOCK_QUIT, self.__accelgroup)
    self.__item_save = gtk.ImageMenuItem(gtk.STOCK_SAVE, self.__accelgroup)
    self.__item_saveas = gtk.ImageMenuItem(gtk.STOCK_SAVE_AS, self.__accelgroup)
    self.__menu_file = gtk.Menu()
    self.__menu_file.prepend(self.__item_quit)
    self.__menu_file.prepend(gtk.SeparatorMenuItem())
    self.__menu_file.prepend(self.__item_saveas)
    self.__menu_file.prepend(self.__item_save)
    self.__item_file = gtk.MenuItem('_File')
    self.__item_file.props.submenu = self.__menu_file
    self.__menubar = gtk.MenuBar()
    self.__menubar.append(self.__item_file)
    # 入力ファイル
    self.__entry_file_a = PathEntry()
    self.__entry_file_b = PathEntry()
    self.__btn_browse_file_a = BrowseButton(self.__entry_file_a)
    self.__label_file_a = LabelWithMnemonic('_A:', self.__entry_file_a)
    self.__label_file_b = LabelWithMnemonic('_B:', self.__entry_file_b)
    self.__btn_browse_file_b = BrowseButton(self.__entry_file_b)
    # テキストビュー
    self.__texttagtable = MyTextTagTable()
    self.__textbuf = gtk.TextBuffer(self.__texttagtable)
    self.__textview = gtk.TextView(self.__textbuf)
    self.__textview.props.wrap_mode = gtk.WRAP_CHAR
    self.__textview.props.editable = False
    self.__textview.modify_font(pango.FontDescription('Monospace 8'))
    self.__sw = gtk.ScrolledWindow()
    self.__sw.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
    self.__sw.add(self.__textview)
    # ボタン
    self.__fontbtn = gtk.FontButton('Monospace 8')
    self.__button_comp = gtk.Button('Get _diff')
    # レイアウト用コンテナ
    self.__hbox_files = gtk.HBox()
    self.__hbox_files.pack_start(self.__label_file_a, expand=False, fill=False)
    self.__hbox_files.pack_start(self.__entry_file_a)
    self.__hbox_files.pack_start(self.__btn_browse_file_a, expand=False, fill=False)
    self.__hbox_files.pack_start(self.__label_file_b, expand=False, fill=False)
    self.__hbox_files.pack_start(self.__entry_file_b)
    self.__hbox_files.pack_start(self.__btn_browse_file_b, expand=False, fill=False)
    self.__hbox_btns = gtk.HBox()
    self.__hbox_btns.pack_start(self.__fontbtn)
    self.__hbox_btns.pack_start(self.__button_comp)
    self.__vbox = gtk.VBox()
    self.__vbox.pack_start(self.__menubar, expand=False, fill=False)
    self.__vbox.pack_start(self.__hbox_files, expand=False, fill=False)
    self.__vbox.pack_start(self.__sw)
    self.__vbox.pack_start(self.__hbox_btns, expand=False, fill=False)
    # シグナル
    self.connect('delete_event', gtk.main_quit)
    self.__item_quit.connect('activate', gtk.main_quit)
    self.__item_save.connect('activate', self.__on_item_save_activate)
    self.__item_saveas.connect('activate', self.__on_item_saveas_activate)
    self.__fontbtn.connect('font-set', lambda widget: self.__textview.modify_font(pango.FontDescription(widget.props.font_name)))
    self.__button_comp.connect('clicked', self.__on_button_comp_clicked)
    # ウィンドウ
    self.add(self.__vbox)
    self.set_size_request(400, 300)
    self.props.title = '(no input) - getdifftest'
    # 出力ファイル
    self.__outfile = None
  def __on_item_save_activate(self, widget):
    """
    差分を上書き保存
    """
    if self.__outfile:
      save_difffile(self.__outfile, self.__textbuf.props.text)
    else:
      self.__item_saveas.activate()
  def __on_item_saveas_activate(self, widget):
    """
    差分を別名で保存
    """
    savedlg = gtk.FileChooserDialog(title='Save diff file', action=gtk.FILE_CHOOSER_ACTION_SAVE, buttons=(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, gtk.STOCK_SAVE, gtk.RESPONSE_ACCEPT))
    savedlg.props.do_overwrite_confirmation = True
    savedlg.props.local_only = True
    if self.__outfile:
      savedlg.set_filename(self.__outfile)
    res = savedlg.run()
    if res == gtk.RESPONSE_ACCEPT:
      self.__outfile = savedlg.get_filename()
    savedlg.destroy()
    if res == gtk.RESPONSE_ACCEPT:
      save_difffile(self.__outfile, self.__textbuf.props.text)
      self.props.title = '%s - getdifftest' % self.__outfile
  def __append_output(self, text, tagname):
    """
    TextBufferにテキストを追加
    """
    iter = self.__textbuf.get_end_iter()
    self.__textbuf.place_cursor(iter)
    self.__textbuf.insert_with_tags_by_name(iter, text, tagname)
  def __on_button_comp_clicked(self, widget):
    """
    比較ボタンが押されたら差分をテキストビューに表示
    """
    self.__textbuf.props.text = ''
    # 各ファイルのデータをリストに読み込む
    infile_a = self.__entry_file_a.props.text
    try:
      f_in_a = open(infile_a, 'r')
    except IOError, (errno, msg):
      self.__append_output('Error: cannot open file "%s": %s' % (infile_a, msg), 'error')
      return
    infile_b = self.__entry_file_b.props.text
    try:
      f_in_b = open(infile_b, 'r')
    except IOError, (errno, msg):
      f_in_a.close()
      self.__append_output('Error: cannot open file "%s": %s' % (infile_b, msg), 'error')
      return
    try:
      try:
        text_a = f_in_a.readlines()
      except IOError, (errno, msg):
        self.__append_output('Error: cannot read file "%s": %s' % (infile_a, msg), 'error')
        f_in_b.close()
        return
    finally:
      f_in_a.close()
    try:
      try:
        text_b = f_in_b.readlines()
      except IOError, (errno, msg):
        self.__append_output('Error: cannot read file "%s": %s' % (infile_b, msg), 'error')
        return
    finally:
      f_in_b.close()
    # 比較
    g = difflib.unified_diff(text_a,
                             text_b,
                             infile_a,
                             infile_b,
                             time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(os.path.getmtime(infile_a))),
                             time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(os.path.getmtime(infile_b))),
                             3,
                             '\n')
    # 結果をテキストビューに表示
    for l in g:
      if l.startswith('+++'):
        self.__append_output(l, 'plus3')
      elif l.startswith('---'):
        self.__append_output(l, 'minus3')
      elif l.startswith('+'):
        self.__append_output(l, 'plus')
      elif l.startswith('-'):
        self.__append_output(l, 'minus')
      elif l.startswith('@@'):
        self.__append_output(l, 'hunk')
      else:
        self.__append_output(l, 'normal')
    self.props.title = '*new diff* - getdifftest'
    self.__outfile = None

class GetDiffTest2:
  """
  unified形式のdiffを取得してテキストビューに表示
  テキストタグテーブルのクラスを継承したものの中にテキストタグ関係の処理を入れたもの
  """
  def main(self):
    """
    アプリケーションのメイン処理
    """
    win = MainWindow()
    win.show_all()
    gtk.main()


if __name__ == '__main__':
  app = GetDiffTest2()
  app.main()
テキストバッファのクラスを継承したもの

[任意]ファイル名: getdifftest3.py

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

import difflib
import urllib
import time
import sys
import os
try:
  import pygtk
  pygtk.require('2.0')
except:
  pass
try:
  import pango
  import gtk
except:
  print >> sys.stderr, 'Error: PyGTK is not installed'
  sys.exit(1)
try:
  import gobject
except:
  print >> sys.stderr, 'Error: PyGObject is not installed'
  sys.exit(1)


def run_errordialog(title, format1, format2=None):
  """
  エラーダイアログを表示
  """
  errdlg = gtk.MessageDialog(type=gtk.MESSAGE_ERROR, buttons=gtk.BUTTONS_CLOSE, message_format=format1)
  if format2:
    errdlg.format_secondary_text(format2)
  errdlg.props.title = title
  errdlg.run()
  errdlg.destroy()

def save_difffile(outfile, text):
  """
  差分ファイルを保存
  """
  try:
    f_out = open(outfile, 'w')
  except IOError, (errno, msg):
    run_errordialog('Error', 'cannot open output file "%s": %s', (outfile, msg))
    return False
  try:
    try:
      f_out.write(text)
    except IOError, (errno, msg):
      run_errordialog('Error', 'cannot write output file "%s": %s', (outfile, msg))
      return False
  finally:
    f_out.close()
  return True


class LabelWithMnemonic(gtk.Label):
  """
  ニーモニック・ウィジェットを初期化時に指定するラベル
  """
  def __init__(self, str, widget):
    gtk.Label.__init__(self, str)
    self.props.use_underline = True
    self.props.mnemonic_widget = widget

class BrowseButton(gtk.Button):
  """
  参照ボタン
  """
  def __init__(self, entry):
    gtk.Button.__init__(self)
    self.__entry = entry
    self.props.label = '...'
    self.__filename = None
    self.connect('clicked', self.__on_self_clicked)
  def set_filename(self, filename):
    self.__filename = filename
  def __on_self_clicked(self, widget):
    opendlg = gtk.FileChooserDialog(title='Select file', buttons=(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, gtk.STOCK_OPEN, gtk.RESPONSE_ACCEPT))
    opendlg.props.local_only = True
    if self.__filename:
      opendlg.set_filename(self.__filename)
    if opendlg.run() == gtk.RESPONSE_ACCEPT:
      self.__filename = opendlg.get_filename()
      self.__entry.props.text = self.__filename
    opendlg.destroy()

class PathEntry(gtk.Entry):
  """
  パス指定用テキスト入力欄
  """
  __TARGET_TYPE_TEXT_URI_LIST = 12345
  __dnd_list = [('text/uri-list', 0, __TARGET_TYPE_TEXT_URI_LIST),]
  def __init__(self):
    gtk.Entry.__init__(self)
    self.connect('drag_data_received', self.__on_drag_data_received)
    self.drag_dest_set(gtk.DEST_DEFAULT_MOTION |
                       gtk.DEST_DEFAULT_HIGHLIGHT | gtk.DEST_DEFAULT_DROP,
                       self.__dnd_list,
                       gtk.gdk.ACTION_COPY)
  def __on_drag_data_received(self, widget, context, x, y, selection, info, time):
    """
    テキストを受け取ったパスに指定
    """
    for item in selection.data.rstrip('\x00').splitlines():
      if item.startswith('file:'):
        path = os.path.normpath(urllib.url2pathname(item[5:]))
        if os.access(path, os.R_OK):
          self.props.text = path

class BoldTextTag(gtk.TextTag):
  """
  太字にするテキストタグ
  """
  def __init__(self, *args, **kwargs):
    gtk.TextTag.__init__(self, *args, **kwargs)
    self.props.weight = pango.WEIGHT_BOLD

class MyTextBuffer(gtk.TextBuffer):
  """
  テキストバッファ
  """
  __texttagtable = gtk.TextTagTable()
  def __init__(self, *args, **kwargs):
    # GObjectプロパティtag-tableはConstruct Onlyの属性がある(後から変更できない)ため
    # gobject.GObject.__init__()で設定する必要がある
    gobject.GObject.__init__(self, tag_table=self.__texttagtable)
    gtk.TextBuffer.__init__(self, *args, **kwargs)
    # テキストタグ
    self.__texttag_plus3 = BoldTextTag('plus3')
    self.__texttag_plus3.props.foreground = '#e71585'
    self.__texttag_plus3.props.background = '#ffe4e1'
    self.__texttag_plus = BoldTextTag('plus')
    self.__texttag_plus.props.family = 'Monospace'
    self.__texttag_plus.props.foreground = '#e71585'
    self.__texttag_minus3 = BoldTextTag('minus3')
    self.__texttag_minus3.props.foreground = '#4169e1'
    self.__texttag_minus3.props.background = '#e6e6fa'
    self.__texttag_minus = BoldTextTag('minus')
    self.__texttag_minus.props.foreground = '#4169e1'
    self.__texttag_hunk = gtk.TextTag('hunk')
    self.__texttag_hunk.props.foreground = '#2e8b57'
    self.__texttag_hunk.props.background = '#f0fff0'
    self.__texttag_normal = gtk.TextTag('normal')
    self.__texttag_normal.props.family = 'Monospace'
    self.__texttag_error = gtk.TextTag('error')
    self.__texttag_error.props.foreground = '#c00'
    self.__texttagtable.add(self.__texttag_plus3)
    self.__texttagtable.add(self.__texttag_plus)
    self.__texttagtable.add(self.__texttag_minus3)
    self.__texttagtable.add(self.__texttag_minus)
    self.__texttagtable.add(self.__texttag_hunk)
    self.__texttagtable.add(self.__texttag_normal)
    self.__texttagtable.add(self.__texttag_error)

class MainWindow(gtk.Window):
  """
  メインウィンドウ
  """
  def __init__(self, *args, **kwargs):
    gtk.Window.__init__(self, *args, **kwargs)
    # ショートカットキー(アクセラレータ)
    self.__accelgroup = gtk.AccelGroup()
    self.add_accel_group(self.__accelgroup)
    # メニュー項目
    self.__item_quit = gtk.ImageMenuItem(gtk.STOCK_QUIT, self.__accelgroup)
    self.__item_save = gtk.ImageMenuItem(gtk.STOCK_SAVE, self.__accelgroup)
    self.__item_saveas = gtk.ImageMenuItem(gtk.STOCK_SAVE_AS, self.__accelgroup)
    self.__menu_file = gtk.Menu()
    self.__menu_file.prepend(self.__item_quit)
    self.__menu_file.prepend(gtk.SeparatorMenuItem())
    self.__menu_file.prepend(self.__item_saveas)
    self.__menu_file.prepend(self.__item_save)
    self.__item_file = gtk.MenuItem('_File')
    self.__item_file.props.submenu = self.__menu_file
    self.__menubar = gtk.MenuBar()
    self.__menubar.append(self.__item_file)
    # 入力ファイル
    self.__entry_file_a = PathEntry()
    self.__entry_file_b = PathEntry()
    self.__btn_browse_file_a = BrowseButton(self.__entry_file_a)
    self.__label_file_a = LabelWithMnemonic('_A:', self.__entry_file_a)
    self.__label_file_b = LabelWithMnemonic('_B:', self.__entry_file_b)
    self.__btn_browse_file_b = BrowseButton(self.__entry_file_b)
    # テキストビュー
    self.__textbuf = MyTextBuffer()
    self.__textview = gtk.TextView(self.__textbuf)
    self.__textview.props.wrap_mode = gtk.WRAP_CHAR
    self.__textview.props.editable = False
    self.__textview.modify_font(pango.FontDescription('Monospace 8'))
    self.__sw = gtk.ScrolledWindow()
    self.__sw.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
    self.__sw.add(self.__textview)
    # ボタン
    self.__fontbtn = gtk.FontButton('Monospace 8')
    self.__button_comp = gtk.Button('Get _diff')
    # レイアウト用コンテナ
    self.__hbox_files = gtk.HBox()
    self.__hbox_files.pack_start(self.__label_file_a, expand=False, fill=False)
    self.__hbox_files.pack_start(self.__entry_file_a)
    self.__hbox_files.pack_start(self.__btn_browse_file_a, expand=False, fill=False)
    self.__hbox_files.pack_start(self.__label_file_b, expand=False, fill=False)
    self.__hbox_files.pack_start(self.__entry_file_b)
    self.__hbox_files.pack_start(self.__btn_browse_file_b, expand=False, fill=False)
    self.__hbox_btns = gtk.HBox()
    self.__hbox_btns.pack_start(self.__fontbtn)
    self.__hbox_btns.pack_start(self.__button_comp)
    self.__vbox = gtk.VBox()
    self.__vbox.pack_start(self.__menubar, expand=False, fill=False)
    self.__vbox.pack_start(self.__hbox_files, expand=False, fill=False)
    self.__vbox.pack_start(self.__sw)
    self.__vbox.pack_start(self.__hbox_btns, expand=False, fill=False)
    # シグナル
    self.connect('delete_event', gtk.main_quit)
    self.__item_quit.connect('activate', gtk.main_quit)
    self.__item_save.connect('activate', self.__on_item_save_activate)
    self.__item_saveas.connect('activate', self.__on_item_saveas_activate)
    self.__fontbtn.connect('font-set', lambda widget: self.__textview.modify_font(pango.FontDescription(widget.props.font_name)))
    self.__button_comp.connect('clicked', self.__on_button_comp_clicked)
    # ウィンドウ
    self.add(self.__vbox)
    self.set_size_request(400, 300)
    self.props.title = '(no input) - getdifftest'
    # 出力ファイル
    self.__outfile = None
  def __on_item_save_activate(self, widget):
    """
    差分を上書き保存
    """
    if self.__outfile:
      save_difffile(self.__outfile, self.__textbuf.props.text)
    else:
      self.__item_saveas.activate()
  def __on_item_saveas_activate(self, widget):
    """
    差分を別名で保存
    """
    savedlg = gtk.FileChooserDialog(title='Save diff file', action=gtk.FILE_CHOOSER_ACTION_SAVE, buttons=(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, gtk.STOCK_SAVE, gtk.RESPONSE_ACCEPT))
    savedlg.props.do_overwrite_confirmation = True
    savedlg.props.local_only = True
    if self.__outfile:
      savedlg.set_filename(self.__outfile)
    res = savedlg.run()
    if res == gtk.RESPONSE_ACCEPT:
      self.__outfile = savedlg.get_filename()
    savedlg.destroy()
    if res == gtk.RESPONSE_ACCEPT:
      save_difffile(self.__outfile, self.__textbuf.props.text)
      self.props.title = '%s - getdifftest' % self.__outfile
  def __append_output(self, text, tagname):
    """
    TextBufferにテキストを追加
    """
    iter = self.__textbuf.get_end_iter()
    self.__textbuf.place_cursor(iter)
    self.__textbuf.insert_with_tags_by_name(iter, text, tagname)
  def __on_button_comp_clicked(self, widget):
    """
    比較ボタンが押されたら差分をテキストビューに表示
    """
    self.__textbuf.props.text = ''
    # 各ファイルのデータをリストに読み込む
    infile_a = self.__entry_file_a.props.text
    try:
      f_in_a = open(infile_a, 'r')
    except IOError, (errno, msg):
      self.__append_output('Error: cannot open file "%s": %s' % (infile_a, msg), 'error')
      return
    infile_b = self.__entry_file_b.props.text
    try:
      f_in_b = open(infile_b, 'r')
    except IOError, (errno, msg):
      f_in_a.close()
      self.__append_output('Error: cannot open file "%s": %s' % (infile_b, msg), 'error')
      return
    try:
      try:
        text_a = f_in_a.readlines()
      except IOError, (errno, msg):
        self.__append_output('Error: cannot read file "%s": %s' % (infile_a, msg), 'error')
        f_in_b.close()
        return
    finally:
      f_in_a.close()
    try:
      try:
        text_b = f_in_b.readlines()
      except IOError, (errno, msg):
        self.__append_output('Error: cannot read file "%s": %s' % (infile_b, msg), 'error')
        return
    finally:
      f_in_b.close()
    # 比較
    g = difflib.unified_diff(text_a,
                             text_b,
                             infile_a,
                             infile_b,
                             time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(os.path.getmtime(infile_a))),
                             time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(os.path.getmtime(infile_b))),
                             3,
                             '\n')
    # 結果をテキストビューに表示
    for l in g:
      if l.startswith('+++'):
        self.__append_output(l, 'plus3')
      elif l.startswith('---'):
        self.__append_output(l, 'minus3')
      elif l.startswith('+'):
        self.__append_output(l, 'plus')
      elif l.startswith('-'):
        self.__append_output(l, 'minus')
      elif l.startswith('@@'):
        self.__append_output(l, 'hunk')
      else:
        self.__append_output(l, 'normal')
    self.props.title = '*new diff* - getdifftest'
    self.__outfile = None

class GetDiffTest3:
  """
  unified形式のdiffを取得してテキストビューに表示
  テキストバッファのクラスを継承したものの中にテキストタグ関係の処理を入れたもの
  """
  def main(self):
    """
    アプリケーションのメイン処理
    """
    win = MainWindow()
    win.show_all()
    gtk.main()


if __name__ == '__main__':
  app = GetDiffTest3()
  app.main()

関連記事:

参考URL:

使用したバージョン:

  • Python 2.6.5
  • PyGObject 2.21.1
  • PyGTK 2.17.0