wxPythonでブックマーク管理ツールを作ろう!

あいさつ

つかさです。
今回は番外編です。ブックマーク管理ツールを作ってみます。AA管理ツールで学んだことを応用します。


準備

今回作るプログラムは、あらかじめブックマークを手動でテキストファイルに書き込んでおき、それを読み込む、という形式をとります。
まずそのブックマークを書き込んだファイルを作りましょう。

  • bookmark.ini
つかさのほえほえ日記 |http://d.hatena.ne.jp/tukasa1919/
はてなブックマーク |http://b.hatena.ne.jp/tukasa1919/
2ちゃんねる サーバ負荷監視所 |http://ch2.ath.cx/
Google リーダー | http://www.google.com/reader/view/#overview-page
PPx help | http://homepage1.nifty.com/toro/ppxhlp.html
Twitter | http://twitter.com/
wxPythonリファレンス | http://wxwindowsjp.sourceforge.jp/docs/html/wx/wx26.htm#classref
gmail | https://mail.google.com/mail
簡易AAエディタ | http://iranegi.s5.xrea.com:8080/2ch/aaedit/aaedit.php

"|"で区切っています。サイト名とurlが対になってます。これをスクリプトと同じフォルダにおくことで、ブックマークを読み込みます。
また、Migemoも使うので、それに関係するファイル(migemo.dll migemo.pyd Dict)も、同じフォルダに用意しておいてください。
では次に、このファイルを読み込むプログラムを作成します。


GUIを作る

まずはGUIを作るところからはじめましょう。

  • Bookmark01.py
import wx

class MyTxtFrm(wx.Frame):
    def __init__(self, parent, id):
        wx.Frame.__init__(self, parent, id)
        self.TxtCtr = wx.TextCtrl(self, -1)
        self.LBox = wx.ListBox(self, -1)
        self.sizer = wx.BoxSizer(wx.VERTICAL)
        self.sizer.Add(self.TxtCtr, 0, wx.EXPAND)
        self.sizer.Add(self.LBox, 1, wx.EXPAND)
        self.SetSizer(self.sizer)
        self.SetAutoLayout(1)
        self.sizer.Fit(self)

class MyApp(wx.PySimpleApp): 
    def OnInit(self):
        self.TxtFrm = MyTxtFrm(None, -1)
        self.TxtFrm.SetSize( (180, 200) )
        self.TxtFrm.Show()
        self.TxtFrm.TxtCtr.SetFocus()
        return 1

app = MyApp()
app.MainLoop()

wxPythonでAA管理ツールをつくろう!第一回 GUIを作ると全く同じですので、詳しくはそこを参照してください。

リストを作る

次に、bookmark.iniを読み込み、サイト名とurlが対になった辞書を作り、サイト名のリストをセットします。

  • Bookmark02.py
import wx

class MyTxtFrm(wx.Frame):
    def __init__(self, parent, id):
        wx.Frame.__init__(self, parent, id)

        self.TxtCtr = wx.TextCtrl(self, -1)
        self.LBox = wx.ListBox(self, -1)

        bf = open("bookmark.ini")
        booklist = bf.readlines()

        booklist = map((lambda x: x.decode("shift-jis").strip()),booklist)
        booklist = map((lambda x: x.rsplit("|")),booklist)
        booklist = map((lambda x: tuple(x)),booklist)

        self.BookDict = {}
        for x in booklist:
            name = x[0].strip()
            url = x[1].strip()
            self.BookDict[name] = url
        
        self.site_list = self.BookDict.keys()
        self.LBox.Set(self.site_list)
        
        self.sizer = wx.BoxSizer(wx.VERTICAL)
        self.sizer.Add(self.TxtCtr, 0, wx.EXPAND)
        self.sizer.Add(self.LBox, 1, wx.EXPAND)
        self.SetSizer(self.sizer)
        self.SetAutoLayout(1)
        self.sizer.Fit(self)

class MyApp(wx.PySimpleApp): 
    def OnInit(self):
        self.TxtFrm = MyTxtFrm(None, -1)
        self.TxtFrm.SetSize( (180, 200) )
        self.TxtFrm.Show()
        self.TxtFrm.TxtCtr.SetFocus()
        return 1

app = MyApp()
app.MainLoop()


辞書の作成
        bf = open("bookmark.ini")
        booklist = bf.readlines()

        booklist = map((lambda x: x.decode("shift-jis").strip()),booklist)
        booklist = map((lambda x: x.rsplit("|")),booklist)
        booklist = map((lambda x: tuple(x)),booklist)

        self.BookDict = {}
        for x in booklist:
            name = x[0].strip()
            url = x[1].strip()
            self.BookDict[name] = url

まずは辞書を作るところから。"|"を区切り文字にして、サイト名とurlの対を取り出します。
次いで空の辞書self.BookDictを作り、サイト名をキーに。urlを値にして一つずつ登録します。
これで次のような辞書ができます。

{'つかさのほえほえ日記' : 'http://d.hatena.ne.jp/tukasa1919/',
'はてなブックマーク' : 'http://b.hatena.ne.jp/tukasa1919/',
'2ちゃんねる サーバ負荷監視所' : 'http://ch2.ath.cx/',
'Google リーダー' : 'http://www.google.com/reader/view/#overview-page',
以下略}
リストボックスに登録
        self.site_list = self.BookDict.keys()
        self.LBox.Set(self.site_list)

次に、この辞書からサイト名のリストを作ります。キーのリストをkeys()で作成して、それをListBoxにセットします。

これで見た目は完成です。
次に、カーソル移動とMigemoによるインクリメンタルサーチができるようにします。

カーソル移動とインクリメンタルサーチの追加(コピペ)

  • Bookmark03.py
import wx,migemo,re

migemo_object = None

class IncrementalSearch:
    def __init__(self,pattern,list):
        self.pattern = pattern
        self.new_list = []
        self.old_list = list

    def GetBackList(self):
        global migemo_object
        
        if migemo_object==None:
            migemo_object = migemo.Migemo('Dict\migemo-dict')
        re_pattern = migemo_object.query(self.pattern)
        try:
            migemo_re_object = re.compile(re_pattern, re.IGNORECASE)
        except re.error:
            return False
                    
        for x in self.old_list:
            if migemo_re_object.search(x):
                self.new_list.append(x)
        return self.new_list


class MyTxtFrm(wx.Frame):
    def __init__(self, parent, id):
        wx.Frame.__init__(self, parent, id)

        self.TxtCtr = wx.TextCtrl(self, -1)
        self.LBox = wx.ListBox(self, -1)

        bf = open("bookmark.ini")
        booklist = bf.readlines()

        booklist = map((lambda x: x.decode("shift-jis").strip()),booklist)
        booklist = map((lambda x: x.rsplit("|")),booklist)
        booklist = map((lambda x: tuple(x)),booklist)

        self.BookDict = {}
        for x in booklist:
            name = x[0].strip()
            url = x[1].strip()
            self.BookDict[name] = url
        
        self.site_list = self.BookDict.keys()
        self.LBox.Set(self.site_list)

        self.TxtCtr.Bind(wx.EVT_KEY_DOWN, self.OnKeyChar)
        self.TxtCtr.Bind(wx.EVT_TEXT,self.OnText)

        self.sizer = wx.BoxSizer(wx.VERTICAL)
        self.sizer.Add(self.TxtCtr, 0, wx.EXPAND)
        self.sizer.Add(self.LBox, 1, wx.EXPAND)
        self.SetSizer(self.sizer)
        self.SetAutoLayout(1)
        self.sizer.Fit(self)


    def OnKeyChar(self,event):
        key = event.GetKeyCode()
        if key ==  wx.WXK_ESCAPE:
            wx.Exit()
        elif key == wx.WXK_UP:
            count = self.LBox.GetCount()
            next = self.LBox.GetSelection() - 1
            if next >=  0:
                self.LBox.SetSelection(next)
            else: self.LBox.SetSelection(count - 1)
        
        elif key == wx.WXK_DOWN:
            count = self.LBox.GetCount()
            next = self.LBox.GetSelection() + 1
            if next < count:
                self.LBox.SetSelection(next)
            else: self.LBox.SetSelection(0)

        elif key in (wx.WXK_RETURN, wx.WXK_NUMPAD_ENTER):
            fr = self.LBox.GetStringSelection()
            webbrowser.open(self.BookDict[fr])
            wx.Exit()
        else: event.Skip()

    def OnText(self,event):
        word = self.TxtCtr.GetValue()
        isearch = IncrementalSearch(word,self.BookDict.keys())
        try:
            new_list = isearch.GetBackList()
        except:
            return False
        if new_list:
            self.LBox.Set(new_list)
            self.LBox.SetSelection(0)
        else: event.Skip()
        event.Skip()

class MyApp(wx.PySimpleApp): 
    def OnInit(self):
        self.TxtFrm = MyTxtFrm(None, -1)
        self.TxtFrm.SetSize( (180, 200) )
        self.TxtFrm.Show()
        self.TxtFrm.TxtCtr.SetFocus()
        return 1

app = MyApp()
app.MainLoop()

変更したのは以下の点です。

  • reとmigemoをインポート
  • migemoをglobal変数として宣言
  • IncrementalSearchクラスをコピペ
  • OnKeyCharとOnTextの二つの関数をコピペ

そして、

        self.TxtCtr.Bind(wx.EVT_KEY_DOWN, self.OnKeyChar)
        self.TxtCtr.Bind(wx.EVT_TEXT,self.OnText)

を__ini__に付け足してます。
やってることはAA管理ツールのコードの流用です。wxPythonでAA管理ツールを作ろう!第六回 PyMigemoでインクリメンタルサーチあたりのコードから該当箇所をコピペしてます。

URLをデフォルトのブラウザで開く

いよいよラストです。
実際にブックマークをブラウザで開けるようにしましょう。変更点は、

  • urlをデフォルトのブラウザで開くためにwebbrowserをインポート
  • OnKeyCharのEnterの箇所

です。

  • Bookmark04.py
import wx,migemo,re,webbrowser

migemo_object = None

class IncrementalSearch:
    def __init__(self,pattern,list):
        self.pattern = pattern
        self.new_list = []
        self.old_list = list

    def GetBackList(self):
        global migemo_object
        
        if migemo_object==None:
            migemo_object = migemo.Migemo('Dict\migemo-dict')
        re_pattern = migemo_object.query(self.pattern)
        try:
            migemo_re_object = re.compile(re_pattern, re.IGNORECASE)
        except re.error:
            return False
                    
        for x in self.old_list:
            if migemo_re_object.search(x):
                self.new_list.append(x)
        return self.new_list


class MyTxtFrm(wx.Frame):
    def __init__(self, parent, id):
        wx.Frame.__init__(self, parent, id)

        self.TxtCtr = wx.TextCtrl(self, -1)
        self.LBox = wx.ListBox(self, -1)

        bf = open("bookmark.ini")
        booklist = bf.readlines()

        booklist = map((lambda x: x.decode("shift-jis").strip()),booklist)
        booklist = map((lambda x: x.rsplit("|")),booklist)
        booklist = map((lambda x: tuple(x)),booklist)

        self.BookDict = {}
        for x in booklist:
            name = x[0].strip()
            url = x[1].strip()
            self.BookDict[name] = url
        
        self.site_list = self.BookDict.keys()
        self.LBox.Set(self.site_list)

        self.TxtCtr.Bind(wx.EVT_KEY_DOWN, self.OnKeyChar)
        self.TxtCtr.Bind(wx.EVT_TEXT,self.OnText)

        self.sizer = wx.BoxSizer(wx.VERTICAL)
        self.sizer.Add(self.TxtCtr, 0, wx.EXPAND)
        self.sizer.Add(self.LBox, 1, wx.EXPAND)
        self.SetSizer(self.sizer)
        self.SetAutoLayout(1)
        self.sizer.Fit(self)


    def OnKeyChar(self,event):
        key = event.GetKeyCode()
        if key ==  wx.WXK_ESCAPE:
            wx.Exit()
        elif key == wx.WXK_UP:
            count = self.LBox.GetCount()
            next = self.LBox.GetSelection() - 1
            if next >=  0:
                self.LBox.SetSelection(next)
            else: self.LBox.SetSelection(count - 1)
        
        elif key == wx.WXK_DOWN:
            count = self.LBox.GetCount()
            next = self.LBox.GetSelection() + 1
            if next < count:
                self.LBox.SetSelection(next)
            else: self.LBox.SetSelection(0)

        elif key in (wx.WXK_RETURN, wx.WXK_NUMPAD_ENTER):
            fr = self.LBox.GetStringSelection()
            webbrowser.open(self.BookDict[fr])
            wx.Exit()
        else: event.Skip()

    def OnText(self,event):
        word = self.TxtCtr.GetValue()
        isearch = IncrementalSearch(word,self.BookDict.keys())
        try:
            new_list = isearch.GetBackList()
        except:
            return False
        if new_list:
            self.LBox.Set(new_list)
            self.LBox.SetSelection(0)
        else: event.Skip()
        event.Skip()


class MyApp(wx.PySimpleApp): 
    def OnInit(self):
        self.TxtFrm = MyTxtFrm(None, -1)
        self.TxtFrm.SetSize( (180, 200) )
        self.TxtFrm.Show()
        self.TxtFrm.TxtCtr.SetFocus()
        return 1

app = MyApp()
app.MainLoop()


        elif key in (wx.WXK_RETURN, wx.WXK_NUMPAD_ENTER):
            fr = self.LBox.GetStringSelection()
            webbrowser.open(self.BookDict[fr])
            wx.Exit()

Enterを押したときに、サイト名に対応したurlを開くようにします。

  1. 選択しているサイト名をキーにして、urlを取り出し
  2. それをデフォルトのブラウザで開く

ということをしてるわけですね。

使い方

登録はbookmark.iniを直接編集することで行います。

上下カーソルでの選択も、migemoでの絞り込みもできます。Enterでブックマークを開きます。Escで終了します。


まとめ

要は、AA管理ツールの枠組みを使えば、

  • 登録する辞書
  • Enterを押した時の挙動

の二つをいじるだけで、いろんなことができるというわけです。
他に僕が思いついたのは、定型文の管理。あと、辞書をうまく作ることができれば、クリップボード履歴を監視するeClipみたいなのも作れますね。