挨拶
ステップイン・ステップアウトをやる方法を考えてて、そういや俺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 件のコメント:
コメントを投稿