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

のようなコマンドに。

0 件のコメント:

コメントを投稿