2014年2月18日火曜日

ほむランチャ ver0.01 公開

あいさつ


以前作ったほむランチャを、py2exeでexe化してHPに公開した(ほむランチャ - hoehoeSoft)。


ほむランチャは、PPxでステップイン・ステップアウトする方法ないかなと考えて試作したもの。基本的にはファイラの補助としての使用を想定している。
というわけで使い方の紹介。

準備


まずはhomu.iniを編集して、open_folder_cmdを

open_folder_command = D:\bin\ppw\PPCW.EXE  -r {folder}

とする。

次に、PPxの適当なキーにほむランチャを登録する。以下を編集して取込。

KC_main = { ; PPcメイン窓
G ,%Obi D:\bin\hlnch\hlnch.exe %1
}

もしもPPcウィンドウの真ん中に表示させたいなら、PPXMES.DLLとPPXWIN.DLLをPPxフォルダにおいて以下を編集して取込。

KC_main = { ; PPcメイン窓
G ,%Obi D:\bin\hlnch\hlnch.exe %1 %: *fitwindow %NC,%*findwindowtitle("HomuLauncher"),20
}

これで準備はおk。

使い方



  1. ほむランチャを起動
  2. 移動したいフォルダにカーソル移動
  3. Enter


これでフォルダジャンプができる。

他のキーの登録


homu.iniの[KEY]セクションを編集することで、他のコマンドを任意のキーに登録することができる。たとえば

c-o = D:\bin\ppw\PPCW.EXE -r -noactive -bootid:~ {folder}
c-enter = D:\bin\ppw\PPCW.EXE -r -noactive -bootid:~ {folder}
c-a = D:\bin\afxw\AFXW.EXE -s -p{folder}
c-c = D:\bin\ppw\PPCW.EXE -r -k *ppcfile copy,{folder}
c-m = D:\bin\ppw\PPCW.EXE -r -k *ppcfile move,{folder}

とすれば、

  • Ctrl+Enter→PPxの反対窓で開く
  • Ctrl+O→PPxの反対窓で開く
  • Ctrl+A→あふで開く
  • Ctrl+C→PPxのカーソルファイルをほむランチャで表示しているフォルダにコピー
  • Ctrl+M→PPxのカーソルファイルをほむランチャで表示しているフォルダに移動

ができる。

置換マクロには
  •  {path}  .....  パス名
  •  {folder}  .....  フォルダ名
が使用可能。

その他


あふとか他のファイラでも使えるが、ほむランチャ自体には表示位置を調整する機能が無いので微妙かもしれない。可能ならコマンドラインオプションで前のウィンドウの真ん中に移動とかできるようにしたいがとりあえず今のところやり方を知らない
あと、dataフォルダのsort.txtを編集することでフォルダごとにソート状態を変更できたり、homu.iniをいじってファイルを表示したりもできる。

ちなみに僕のhomu.iniは以下

[MAIN]
historymode = 1
x = 300
y = 300
showfile = 1
mainwindowwidthadd = 22
open_folder_command = D:\bin\ppw\PPCW.EXE  -r {folder}
open_file_command = D:\bin\ppw\PPCW.EXE  -r {folder}
start_folder = 
selecttype = 0

[KEY]
c-a = D:\bin\afxw\AFXW.EXE -s -p{folder}
c-enter = D:\bin\ppw\PPCW.EXE -r -noactive -bootid:~ {folder}
c-c = D:\bin\ppw\PPCW.EXE -r -k *ppcfile copy,{folder}
c-m = D:\bin\ppw\PPCW.EXE -r -k *ppcfile move,{folder}


[HISTORY]
x = 300
y = 300

2014年2月9日日曜日

wxPythonで常駐ランチャをつくろう!第三回 タスクトレイアイコンの表示

あいさつ


つかさです。今回はタスクトレイアイコンの表示。


タスクトレイアイコンを

  • 右クリックでメニューを表示
  • 左ダブルクリックで表示、非表示のトグル

できるようにします。

以下のコードを保存。同じフォルダにhomu.icoという名前の適当なアイコンをおいてから、実行してください。


hlnch03.py

# -*- encoding: utf-8 -*-

import wx,subprocess,os,sys,SocketServer,socket,threading

class MyTxtCtr(wx.PySimpleApp):
    
    def OnInit(self):
        HOST, PORT = socket.gethostname(), 61955
        argvs = sys.argv

        instance_name = u"%s-%s" % (self.GetAppName(), wx.GetUserId())
        self.instance = wx.SingleInstanceChecker(instance_name)
        if self.instance.IsAnotherRunning():
            if len(argvs) >= 2:
                self.client(HOST, PORT, argvs)
            wx.Exit()
        else:
            server = self.start_server(HOST, PORT)

        # タスクトレイ
        self.tb_ico=wx.TaskBarIcon()
        self.tb_ico.Bind(wx.EVT_TASKBAR_LEFT_DCLICK, self.OnTbiLeftDClick)
        self.tb_ico.Bind(wx.EVT_TASKBAR_RIGHT_UP, self.OnTbiRightUp)
        self.ico = wx.Icon("homu.ico", wx.BITMAP_TYPE_ICO)
        self.tb_ico.SetIcon(self.ico, u"homuhomu")

        # タスクトレイ用メニューの作成
        self.menu = wx.Menu()
        self.menu.Append(1,   u"Exit(&X)")
        wx.EVT_MENU(self.menu, 1, self.OnClose)

        self.Frm = wx.Frame(None, -1, "homuLauncher", size=(400,60),pos=(400,400))
        self.TxtCtr = wx.TextCtrl(self.Frm, -1)
        self.Frm.Show()
        return 1

    def start_server(self,host, port):

        server = ThreadedTCPServer((host, port), ThreadedTCPRequestHandler)
        ip, port = server.server_address
        server_thread = threading.Thread(target=server.serve_forever)
        server_thread.setDaemon(True)
        server_thread.start()

    def client(self,ip, port, argvs):
        message = os.path.abspath(' '.join(argvs[1:]))
        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        sock.connect((ip, port))
        sock.send(message)
        response = sock.recv(1024)
#        print "Received: %s" % response
        sock.close()

    # タスクトレイ左ダブルクリック
    def OnTbiLeftDClick(self, evt):
        if self.Frm.IsShown():
            self.Frm.Show(False)
        else:
            self.Frm.Show()
            self.Frm.Raise()
            
    # タスクトレイ右クリック
    def OnTbiRightUp(self, evt):
        self.tb_ico.PopupMenu(self.menu)

    def OnClose(self, evt):
        self.tb_ico.RemoveIcon()
        wx.Exit()

class ThreadedTCPRequestHandler(SocketServer.BaseRequestHandler):
    def handle(self):
        data = self.request.recv(1024)
        wx.GetApp().TxtCtr.SetValue(data)
        response = 'string length: %d' % len(data)
#        print 'responding to',data,'with',response
        self.request.send(response)
        
class ThreadedTCPServer(SocketServer.ThreadingMixIn, SocketServer.TCPServer):
    pass

app = MyTxtCtr()
app.MainLoop()

解説


        self.tb_ico=wx.TaskBarIcon()
        self.tb_ico.Bind(wx.EVT_TASKBAR_LEFT_DCLICK, self.OnTbiLeftDClick)
        self.tb_ico.Bind(wx.EVT_TASKBAR_RIGHT_UP, self.OnTbiRightUp)
        self.ico = wx.Icon("homu.ico", wx.BITMAP_TYPE_ICO)
        self.tb_ico.SetIcon(self.ico, u"homuhomu")

  1. タスクトレイアイコンのインスタンスを作成
  2. タスクトレイアイコンをクリックした時のイベントをセット
  3. アイコン画像をセット

というようにしてます。

左ダブルクリック


    # タスクトレイ左ダブルクリック
    def OnTbiLeftDClick(self, evt):
        if self.Frm.IsShown():
            self.Frm.Show(False)
        else:
            self.Frm.Show()
            self.Frm.Raise()

メインウィンドウが表示されてるかどうかを取得し、表示されていれば非表示に。非表示ならば表示にしてます。

右クリック


        # タスクトレイ用メニューの作成
        self.menu = wx.Menu()
        self.menu.Append(1,   u"Exit(&X)")
        wx.EVT_MENU(self.menu, 1, self.OnClose)

クリックした時のメニューは

  1. メニューインスタンスを作成
  2. そこに表示項目を追加し
  3. それに対応するイベントをセット

のように作成。このようにして作ったメニューを

    # タスクトレイ右クリック
    def OnTbiRightUp(self, evt):
        self.tb_ico.PopupMenu(self.menu)

で登録してます。

wxPythonで常駐ランチャをつくろう!第二回 実行中のファイルに引数を渡す

あいさつ


つかさです。今回は、実行中のプログラムへの引数の送り方です。
メインウィンドウの文字列を与えられた引数に置き換えます。

例えば、既にランチャが起動しているときに

D:\bin\homuLauncher\htest2.py H:\howm2\Python

とすれば実行中のウィンドウがこんな感じになるようにします。



hlnch02.py

# -*- encoding: utf-8 -*-

import wx,subprocess,os,sys,SocketServer,socket,threading

class MyTxtCtr(wx.PySimpleApp):
    
    def OnInit(self):
        HOST, PORT = socket.gethostname(), 61955
        argvs = sys.argv
        instance_name = u"%s-%s" % (self.GetAppName(), wx.GetUserId())
        self.instance = wx.SingleInstanceChecker(instance_name)
        if self.instance.IsAnotherRunning():
            if len(argvs) >= 2:
                self.client(HOST, PORT, argvs)
            wx.Exit()
        else:
            server = self.start_server(HOST, PORT)
        self.Frm = wx.Frame(None, -1, "homuLauncher", size=(400,60),pos=(400,400))
        self.TxtCtr = wx.TextCtrl(self.Frm, -1)
        self.Frm.Show()
        return 1

    def start_server(self,host, port):
        server = ThreadedTCPServer((host, port), ThreadedTCPRequestHandler)
        ip, port = server.server_address
        server_thread = threading.Thread(target=server.serve_forever)
        server_thread.setDaemon(True)
        server_thread.start()

    def client(self,ip, port, arg):
        message = os.path.abspath(' '.join(arg[1:]))
        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        sock.connect((ip, port))
        sock.send(message)
        response = sock.recv(1024)
#        print "Received: %s" % response
        sock.close()

class ThreadedTCPRequestHandler(SocketServer.BaseRequestHandler):
    def handle(self):
        data = self.request.recv(1024)
        wx.GetApp().TxtCtr.SetValue(data)
        response = 'string length: %d' % len(data)
#        print 'responding to',data,'with',response
        self.request.send(response)
        
class ThreadedTCPServer(SocketServer.ThreadingMixIn, SocketServer.TCPServer):
    pass

app = MyTxtCtr()
app.MainLoop()

説明


  1. 初回起動時に、本体と共にバックグラウンドでサーバーを起動する。そのサーバーが何かメッセージを受け取れば、それに応じて本体に反映させるようにしておく
  2. 二度目の起動時には、そのサーバーに接続するクライアントを作成。引数を渡し、自身は終了する

という構造です。
ポート番号を予め指定し、サーバーとクライアントを作ることで、初回起動時のプログラムと二度目以降のとをつなげるという仕組みらしい。もっと簡単にできると思ってたけどわりと大掛かりなんですね。それとも他の方法があるのかしらん。

解説


二重起動のチェック


        if self.instance.IsAnotherRunning():
            if len(argvs) >= 2:
                self.client(HOST, PORT, argvs)
            wx.Exit()
        else:
            server = self.start_server(HOST, PORT)


wx.SingleInstanceCheckerで、既に起動中のプログラムがあるかどうかで分岐。

  • 初回起動なら サーバーをバックグラウンドで起動する
  • 既に起動しているなら クライアントを作成して、既に起動中のプログラムのサーバーに接続する

という具合です。それぞれstart_serverとclientで行ってます。

サーバー側


    def start_server(self,host, port):

        server = ThreadedTCPServer((host, port), ThreadedTCPRequestHandler)
        ip, port = server.server_address
        server_thread = threading.Thread(target=server.serve_forever)
        server_thread.setDaemon(True)
        server_thread.start()


ここでサーバーを動かしてるわけですが、二つのクラスを利用してます。

1 ThreadedTCPRequestHandlerクラス

クライアントからメッセージを受け取った時の処理をThreadedTCPRequestHandlerクラスで定めます。

class ThreadedTCPRequestHandler(SocketServer.BaseRequestHandler):
    def handle(self):
        data = self.request.recv(1024)
        wx.GetApp().TxtCtr.SetValue(data)
        #Note to the self.server.app
        response = 'string length: %d' % len(data)
#        print 'responding to',data,'with',response
        self.request.send(response)


受け取ったデータを、SetValueでテキストボックスに貼り付けてますね。

2 ThreadedTCPServerクラス

class ThreadedTCPServer(SocketServer.ThreadingMixIn, SocketServer.TCPServer):
    pass


非同期的な動作をサポートするのにこれが必要らしい。参考にしたのがこうしてたのでとりあえずこうしてます。


クライアント側


    def client(self,ip, port, arg):
        message = os.path.abspath(' '.join(arg[1:]))
        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        sock.connect((ip, port))
        sock.send(message)
        response = sock.recv(1024)
#        print "Received: %s" % response
        sock.close()


  1. ソケットを作成
  2. サーバーに接続
  3. コマンドライン引数をサーバーに送る
  4. レスポンスを受け取り
  5. 終了

という順序ですね。

2014年2月7日金曜日

wxPythonで常駐ランチャをつくろう!第一回 多重起動の防止

あいさつ


つかさです。常駐ランチャを作ってみます。

1 多重起動の防止
2 実行中のファイルに引数を投げる
3 アイコン表示

の三回の予定です。今回は多重起動の防止。



説明の簡略化のため、テキストコントロールだけを表示します。
ランチャっぽいものをどう作るか自体は、Pythonでコマンドラインランチャを作ろう!シリーズの記事を参考にしてください。そのあたりの記事と組み合わせたら常駐ランチャにおそらくなります。 

hlnch01.py
# -*- encoding: utf-8 -*-

import wx

class MyTxtCtr(wx.PySimpleApp):
    
    def OnInit(self):
        instance_name = u"%s-%s" % (self.GetAppName(), wx.GetUserId())
        self.instance = wx.SingleInstanceChecker(instance_name)
        if self.instance.IsAnotherRunning():
            wx.MessageBox("Another instance is running", "ERROR")
            return False
        self.Frm = wx.Frame(None, -1, "homuLauncher", size=(400,60),pos=(400,400))
        self.TxtCtr = wx.TextCtrl(self.Frm, -1)
        self.Frm.Show()
        return 1

app = MyTxtCtr()
app.MainLoop()

説明


要は

wx.SingleInstanceChecker

を使えばいい、ということですね。

instance_name = u"%s-%s" % (self.GetAppName(), wx.GetUserId())

でファイル名とユーザーIDからなる文字列を取得。これと同じものが起動中かどうかをIsAnotherRunning()で判別し、既に起動していればエラーメッセージを出して終了するようにしてます。

参考


OneInstanceRunning - wxPyWiki

2014年2月2日日曜日

PPxでステップイン・ステップアウトをしたい③ wxPythonで自作


挨拶


ステップイン・ステップアウトをやる方法を考えてて、そういや俺Pythonでランチャとか作ってたよなと思って試しに作ってみる。案外すぐにそれっぽいのができた。
というわけで、今回はPython + wxPythonでステップイン・ステップアウトのみが出来る旧倉っぽいランチャを作ってみる。
今までは、他のソフトをカスタマイズするとかスクリプトを作るとか組み合わせるとかしか選択肢がなかったけど、「作る」コマンドが状況によっては浮かぶようになったのかもしれにゃい。

準備


とりあえずは環境を整える。

Python
wxPython
Python Win32 Extensions

をダウンロードし、インストール。

適当なフォルダを作り、そこに以下の二つのファイルを保存。homu_ini.pyはCraftLaunchのソースを参考にした

homuLauncher.py
# -*- encoding: utf-8 -*-

import wx,subprocess,os,sys,re,win32file,ConfigParser
import homu_ini

os.chdir(os.path.dirname(sys.argv[0]) or '.')

class MyApp(wx.PySimpleApp):
    def OnInit(self):
        homu_ini.read()
        # 引数の取得
        argvs = sys.argv
        argc = len(argvs)
        # 引数を連結する
        argdir = os.path.abspath(' '.join(argvs[1:]))
        start_folder = homu_ini.get( "MAIN", "start_folder", "" )
        print start_folder
        if argc >= 2 and os.path.isdir(argdir):
            # 引数があればそのフォルダを起動ディレクトリにする
            self.CurrentDir = argdir
        elif os.path.isdir(start_folder):
            self.CurrentDir = start_folder
        else:
            self.CurrentDir = os.path.abspath(os.path.dirname(sys.argv[0]))
        # 前回終了時の位置を復元
        if homu_ini.getint( "MAIN", "HistoryMode", 1 ) == 1:
            self.x = homu_ini.getint( "HISTORY", "X", 300 )
            self.y = homu_ini.getint( "HISTORY", "Y", 300 )
        # デフォルトの位置
        else:
            self.x = homu_ini.getint( "MAIN", "X", 300 )
            self.y = homu_ini.getint( "MAIN", "Y", 300 )
        # インクリメンタルサーチ用の変数
        self.typedtext = ""
        self.lbox_list = []
        # メインウィンドウを作成
        self.Frm = wx.Frame(None, -1, "homuLauncher",style=wx.DOUBLE_BORDER|wx.FRAME_NO_TASKBAR)
        # 位置を指定
        pos = (self.x, self.y)
        self.Frm.SetPosition(pos)
        self.TxtCtr = wx.TextCtrl(self.Frm, -1)
        self.Frm.Bind(wx.EVT_MOVE,self.onMove)
        self.TxtCtr.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown)
        self.TxtCtr.Bind(wx.EVT_CHAR,self.OnKeyChar)
        self.tb_sizer = wx.BoxSizer(wx.VERTICAL)
        self.tb_sizer.Add(self.TxtCtr, 1, wx.EXPAND)
        self.Frm.SetSizer(self.tb_sizer)

        # リストウィンドウを作成
        self.lbFrame = wx.Frame(None, 0, "wxPython",style=wx.DOUBLE_BORDER|wx.FRAME_NO_TASKBAR)
        self.LBox = wx.ListBox(self.lbFrame, -1)
        self.lb_sizer = wx.BoxSizer(wx.VERTICAL)
        self.lb_sizer.Add(self.LBox, 1, wx.EXPAND)
        self.lbFrame.SetSizer(self.lb_sizer)
        # サブディレクトリのリストを取得し、リストボックスにセット
        self.LBox.SetItems(self.GetSubDirList(self.CurrentDir))
        # メインウィンドウのサイザーをセット
        self.Frm.SetAutoLayout(True)
        self.tb_sizer.Fit(self.Frm)
        # メインウィンドウの大きさを調整
        self.MainWindowSetSize(self.AddSlash(self.CurrentDir))
        # テキストボックスにパスを入力
        self.TxtCtr.SetValue(self.AddSlash(self.CurrentDir))
        # リストウィンドウの大きさを調整
        self.ListWindowSetSize()
        self.Frm.Show()
        if self.LBox.GetCount() != 0:
            self.lbFrame.Show()
        self.TxtCtr.SetInsertionPoint(-1)
        self.TxtCtr.SetFocus()
        return 1

    def OnKeyDown(self,event):
        key = event.GetKeyCode()
        if key ==  wx.WXK_ESCAPE:
            self.OnExit()
        elif key == wx.WXK_UP:
            count = self.LBox.GetCount()
            if count == 0:
                return
            next = self.LBox.GetSelection() - 1
            if next >=  0:
                self.LBox.SetSelection(next)
            else: self.LBox.SetSelection(count - 1)

            hoge = os.path.join(self.CurrentDir,self.LBox.GetStringSelection())
            # メインウィンドウの大きさを調整
            self.MainWindowSetSize(hoge)
            # テキストボックスにパスを入力
            self.TxtCtr.SetValue(hoge)
            # テキストボックスの選択範囲を調整
            self.TxtCtr.SetSelection(len(self.AddSlash(self.CurrentDir)),-1)
        elif key == wx.WXK_DOWN:
            count = self.LBox.GetCount()
            if count == 0:
                return
            next = self.LBox.GetSelection() + 1
            if next < count:
                self.LBox.SetSelection(next)
            else: self.LBox.SetSelection(0)

            hoge = os.path.join(self.CurrentDir,self.LBox.GetStringSelection())
            # メインウィンドウの大きさを調整
            self.MainWindowSetSize(hoge)
            # テキストボックスにパスを入力
            self.TxtCtr.SetValue(hoge)
            # テキストボックスの選択範囲を調整
            self.TxtCtr.SetSelection(len(self.AddSlash(self.CurrentDir)),-1)
        elif key == wx.WXK_RIGHT:
            if self.typedtext == "":
                # リストボックスが空なら終了
                if self.LBox.GetSelection() == -1:
                    return
                hoge = os.path.join(self.CurrentDir,self.LBox.GetStringSelection())
                fuga = self.GetSubDirList(hoge)
                # サブディレクトリがなければ終了
                if fuga == []:
                    return
                # リストボックスにサブディレクトリのリストをセット
                self.LBox.SetItems(fuga)
                # メインウィンドウの大きさを調整
                self.MainWindowSetSize(self.AddSlash(hoge))
                # テキストボックスにパスを入力
                self.TxtCtr.SetValue(self.AddSlash(hoge))
                # テキストボックスの選択範囲を調整
                self.TxtCtr.SetInsertionPoint(-1)
                self.ListWindowSetSize()
                self.CurrentDir = hoge
            else:
                hoge = os.path.join(self.CurrentDir,self.LBox.GetStringSelection())
                fuga = self.GetSubDirList(hoge)
                # サブディレクトリがなければ終了
                if fuga == []:
                    return
                self.typedtext = ""
                # リストボックスにサブディレクトリのリストをセット
                self.LBox.SetItems(fuga)
                # メインウィンドウの大きさを調整
                self.MainWindowSetSize(self.AddSlash(hoge))
                # テキストボックスにパスを入力
                self.TxtCtr.SetValue(self.AddSlash(hoge))
                # テキストボックスの選択範囲を調整
                self.TxtCtr.SetInsertionPoint(-1)
                self.ListWindowSetSize()
                self.CurrentDir = hoge
                
                
        elif key == wx.WXK_LEFT:
            if self.typedtext == "":
                # ルートフォルダなら何もしない
                if self.CurrentDir == os.path.dirname(os.path.abspath(self.CurrentDir)):
                    return
                self.CurrentDir = os.path.dirname(os.path.abspath(self.CurrentDir))
                # メインウィンドウの大きさを調整
                self.MainWindowSetSize(self.AddSlash(self.CurrentDir))
                # テキストボックスにパスを入力
                self.TxtCtr.SetValue(self.AddSlash(self.CurrentDir))
                # テキストボックスの選択範囲を調整
                self.TxtCtr.SetInsertionPoint(-1)
                self.LBox.SetItems(self.GetSubDirList(self.CurrentDir))
                self.ListWindowSetSize()
                if not self.lbFrame.IsShown():
                    self.lbFrame.Show()
                self.TxtCtr.SetFocus()
            else:
                self.typedtext = ""
                hoge = self.AddSlash(self.CurrentDir)
                # メインウィンドウのサイズを調整
                self.MainWindowSetSize(hoge)
                # エディットボックスに入力
                self.TxtCtr.SetValue(hoge)
                self.TxtCtr.SetInsertionPoint(-1)
                self.LBox.SetItems(self.GetSubDirList(hoge))
                self.ListWindowSetSize()
                self.lbox_list = []
        elif key in (wx.WXK_RETURN, wx.WXK_NUMPAD_ENTER):
            d = {"path": "\"" + self.TxtCtr.GetValue().encode("shift_jis") + "\""}
            hoge = self.TxtCtr.GetValue().encode("shift_jis")
            if os.path.isdir(hoge):
                folder_cmd = homu_ini.get( "MAIN", "open_folder_command", "" ).format(**d).strip()
                if folder_cmd != "":
                    subprocess.Popen(folder_cmd)
                else:
                    subprocess.Popen('explorer \"' + hoge + '\"' )
            elif os.path.isfile(hoge):
                file_cmd = homu_ini.get( "MAIN", "open_file_command", "" ).format(**d).strip()
                if file_cmd != "":
                    subprocess.Popen(file_cmd)
                else:
                    subprocess.Popen('explorer \"' + hoge + '\"' )
            self.OnExit()
            return
        else: event.Skip()


    def OnKeyChar(self,event):
        key = event.GetKeyCode()
        # BACKSPACEで一文字消去
        if key ==  wx.WXK_BACK:
            word = self.typedtext[:-1]
            if self.typedtext == "":
                # ルートフォルダなら何もしない
                if self.CurrentDir == os.path.dirname(os.path.abspath(self.CurrentDir)):
                    return
                self.CurrentDir = os.path.dirname(os.path.abspath(self.CurrentDir))
                # メインウィンドウの大きさを調整
                self.MainWindowSetSize(self.AddSlash(self.CurrentDir))
                # テキストボックスにパスを入力
                self.TxtCtr.SetValue(self.AddSlash(self.CurrentDir))
                # テキストボックスの選択範囲を調整
                self.TxtCtr.SetInsertionPoint(-1)
                self.LBox.SetItems(self.GetSubDirList(self.CurrentDir))
                self.ListWindowSetSize()
                if not self.lbFrame.IsShown():
                    self.lbFrame.Show()
                    self.TxtCtr.SetFocus()
            elif word == "":
                self.typedtext = word
                hoge = self.AddSlash(self.CurrentDir)
                # メインウィンドウのサイズを調整
                self.MainWindowSetSize(hoge)
                # エディットボックスに入力
                self.TxtCtr.SetValue(hoge)
                self.TxtCtr.SetInsertionPoint(-1)
                self.LBox.SetItems(self.GetSubDirList(hoge))
                self.ListWindowSetSize()
                self.lbox_list = []
            else:
                isearch = IncrementalSearch(word,self.GetSubDirList(self.CurrentDir))
                try:
                    new_list = isearch.GetBackList()
                except:
                    return False
                if new_list != self.lbox_list:
                    # リストをセットして大きさを調整
                    self.LBox.SetItems(new_list)
                    self.ListWindowSetSize()
                    self.LBox.SetSelection(0)
                    # エディットボックスとリストボックスからパスを生成
                    hoge = os.path.join(self.CurrentDir,new_list[0])
                    # メインウィンドウのサイズを調整
                    self.MainWindowSetSize(hoge)
                    self.TxtCtr.SetValue(hoge)
                    self.TxtCtr.SetSelection(len(self.CurrentDir + word) , -1)
                    self.typedtext = word
                    self.lbox_list = new_list
                else:
                    self.TxtCtr.SetSelection(len(self.CurrentDir + word) , -1)
                    self.typedtext = word

        # 英数字で前方一致検索
        elif key >= 32 and key <= 127:
            word = self.typedtext + chr(key)
            isearch = IncrementalSearch(word,self.GetSubDirList(self.CurrentDir))
            try:
                new_list = isearch.GetBackList()
            except:
                return False
            if new_list == []:
                return
            if new_list != self.lbox_list:
                # リストをセット
                self.LBox.SetItems(new_list)
                self.LBox.SetSelection(0)
                # パスを生成
                hoge = os.path.join(self.CurrentDir,new_list[0])
                # メインウィンドウのサイズを調整
                self.MainWindowSetSize(hoge)
                self.TxtCtr.SetValue(hoge)
                self.TxtCtr.SetSelection(len(self.AddSlash(self.CurrentDir) + word) , -1)
                self.typedtext = word
                self.lbox_list = new_list
                self.ListWindowSetSize()
            else:
                self.TxtCtr.SetSelection(len(self.AddSlash(self.CurrentDir) + word) , -1)
                self.typedtext = word
        else: pass
        return


    def GetSubDirList(self,fp):
        lookup = ('utf_8', 'euc_jp', 'euc_jis_2004', 'euc_jisx0213',
                  'shift_jis', 'shift_jis_2004','shift_jisx0213',
                  'iso2022jp', 'iso2022_jp_1', 'iso2022_jp_2', 'iso2022_jp_3',
                  'iso2022_jp_ext','latin_1', 'ascii')
        for encoding in lookup:
            try:
                fp = fp.decode(encoding)
                break
            except:
                pass
        if os.path.isfile(fp):
            return []
        SubDirList = []
        SubFileList = []
        file_list = os.listdir(fp)
        for file_name in file_list:
            x = os.path.join(fp,file_name)
            # 隠しフォルダは除外
            attrib = win32file.GetFileAttributesEx(x)
            if (attrib[0] & win32file.FILE_ATTRIBUTE_HIDDEN) == 0:
                # フォルダをリストに追加
                if os.path.isdir(x):
                    SubDirList.append(x)
                # ファイルをリストに追加
                if os.path.isfile(x) and homu_ini.getint( "MAIN", "ShowFile", 0 ) == 1:
                    SubFileList.append(x)
        return map((lambda x: os.path.basename(x) + "\\"),SubDirList) + map((lambda x: os.path.basename(x)),SubFileList)

    def ListWindowSetSize(self):
        # 位置
        self.lb_sizer.Fit(self.lbFrame)
        pos = (self.Frm.GetPosition().x + self.Frm.GetTextExtent(self.TxtCtr.GetValue())[0], self.Frm.GetPosition().y + self.Frm.GetSize().y)
        # 大きさ。リスト数が10を超えたら調整
        if self.LBox.GetCount() >= 10:
            lbox_y = self.Frm.GetTextExtent(self.TxtCtr.GetValue())[1] * 10
        else:
            lbox_y = self.Frm.GetTextExtent(self.TxtCtr.GetValue())[1] * (self.LBox.GetCount() +1)
        size = (self.lbFrame.GetSize().x, lbox_y )
        # 位置と大きさをセット
        self.lbFrame.SetDimensions(pos[0],pos[1],size[0],size[1])

    def MainWindowSetSize(self,text):
        #self.tb_sizer.Fit(self.Frm)
        size = (self.Frm.GetTextExtent(text)[0] + homu_ini.getint( "MAIN", "MainWindowWidthAdd", 22 ), self.Frm.GetSize().y)
        self.Frm.SetSize(size)

    # 末尾に\をつける
    def AddSlash(self,word):
        hoge = os.path.abspath(word)
        if hoge.endswith("\\"):
            return hoge
        else:
            return hoge + '\\'

    def SearchWord(self,word):
        new_list = []
        for line in self.List:
            if word in line:
                new_list.append(line)
        return new_list

    def onMove(self,event):
        self.ListWindowSetSize()

    # iniファイルへの書き込み
    def WriteINI(self):
        homu_ini.set('MAIN','HistoryMode',str(homu_ini.getint( "MAIN", "HistoryMode", 1 )))
        homu_ini.set('MAIN','x',str(homu_ini.getint( "MAIN", "x", 300 )))
        homu_ini.set('MAIN','y',str(homu_ini.getint( "MAIN", "y", 300 )))
        homu_ini.set('MAIN','showfile',str(homu_ini.getint( "MAIN", "showfile", 0 )))
        homu_ini.set('MAIN','MainWindowWidthAdd',str(homu_ini.getint( "MAIN", "MainWindowWidthAdd", 22 )))
        homu_ini.set('MAIN','open_folder_command',homu_ini.get( "MAIN", "open_folder_command", "" ))
        homu_ini.set('MAIN','open_file_command',homu_ini.get( "MAIN", "open_file_command", "" ))
        homu_ini.set('MAIN','start_folder',homu_ini.get( "MAIN", "start_folder", "" ))
        if homu_ini.getint( "MAIN", "HistoryMode", 1 ) == 1:
            homu_ini.set('HISTORY','X',str(self.Frm.GetPosition()[0]))
            homu_ini.set('HISTORY','Y',str(self.Frm.GetPosition()[1]))
        homu_ini.write()

    def OnExit(self):
        # iniファイルへの書き込み
        self.WriteINI()
        wx.Exit()

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

    def GetBackList(self):
        for x in self.old_list:
            if x.lower().find(self.pattern.lower()) == 0:
                self.new_list.append(x)
        return self.new_list


app = MyApp()
app.MainLoop()

homu_ini.py
# -*- encoding: utf-8 -*-

import os
import sys
import ConfigParser

ini = None
ini_filename = "homu.ini"

#--------------------------------------------------------------------

def read():

    global ini

    ini = ConfigParser.RawConfigParser()

    try:
        ini.read(ini_filename)
    except:
        pass

def write():

    try:
        f = open(ini_filename,"w")
        ini.write(f)
        f.close()
    except:
        pass

def get( section, option, default=None ):
    #print "ini.get", section, option
    try:
        return ini.get( section, option )
    except:
        if default!=None:
            return default
        raise

def getint( section, option, default=None ):
    #print "ini.getint", section, option
    try:
        return ini.getint( section, option )
    except:
        if default!=None:
            return default
        raise

def set( section, option, value ):
    #print "ini.set", section, option, value
    assert( type(value)==str )
    
    try:
        if ini.get(section,option)==value:
            return
    except:
        pass

    try:
        ini.add_section(section)
    except ConfigParser.DuplicateSectionError:
        pass

    ini.set( section, option, value )

def setint( section, option, value ):

    #print "ini.setint", section, option, value
    assert( type(value)==int )

    try:
        if ini.getint(section,option)==value:
            return
    except:
        pass

    try:
        ini.add_section(section)
    except ConfigParser.DuplicateSectionError:
        pass

    ini.set( section, option, str(value) )


def items( section, default=None):
    #print "ini.get", section, option
    try:
        return ini.items( section )
    except:
        if default!=None:
            return default
        raise

homuLauncher.pyを実行してみよう。それで



こんなウィンドウがでたら成功です。カーソルキーで移動、Escで終了、Enterで実行です。あと前方一致で自動補完が効きます。
起動時のパスは、引数を与えることで指定可能。
コマンドプロンプトを出したくない場合は、homuLauncher.pyをhomuLauncher.pywにリネームしましょう。

設定ファイル


一度起動し、Escを押すなどして終了するとhomu.iniというファイルができるはず。そこでファイラなどを指定する。僕の場合だと次のようになる。
[MAIN]
historymode = 1
x = 300
y = 300
showfile = 1
mainwindowwidthadd = 22
open_folder_command = D:\bin\ppx\PPCW.EXE -r {path}
open_file_command = D:\bin\esExt\esExt5.exe {path}
start_folder = D:\bin

[HISTORY]
x = 300
y = 300

open_folder_commandでフォルダを開くファイラを指定。{path}はhomuLauncherで選択したパスを意味する。
open_file_commandはファイルを開くプログラムを指定。{path}はhomuLauncherで選択したパスを意味する。
start_folderは起動時のフォルダ。引数を指定しなければこれになる。
showfileはファイルを表示するかどうか。1なら表示、0なら非表示。
historymodeは場所や大きさを記憶するかどうか。1なら記憶、0なら記憶しない。
mainwindowwidthaddは、ここで指定した数値だけメインウィンドウの幅を伸ばす

PPxへの登録


以下のようなコマンドを登録することで、カレントフォルダを起動フォルダにしてほむランチャを開くことができる。

%Obi D:\Work\Python\homuLauncher.py %1

位置を調整したいときは、拡張子をpywに変え、PPXWIN.DLLとPPXMES.DLLをインストールして

%Obi D:\Work\Python\homuLauncher.pyw %1 %: *fitwindow %NC,%*findwindowtitle("homuLauncher"),20

のようなコマンドに。