2011年5月26日木曜日

おっさんにも解るwxPython

wxPythonでGUIアプリを作ろう第三弾です。今回のネタは既にお馴染みとなったおっさんにも解るPythonの題材であるみくんちゅ♪セットアップヘルパを取り上げてみましょう。


まあ、別に@kaorin_linux氏とやり合ったワケじゃないんですが(笑)、恐らくGUI初心者的にはwxPythonの方が扱いがラクじゃないのか、って事がありまして。Gladeの場合、GUI関係のコードはXMLに纏められてるので、実は何か書いた場合のソースコード内にはGUIの影もカタチもありません。ある意味非常にスッキリとしてるんですね。


ただ、明示的にGTK+で生成されたオブジェクトをたぐり寄せてこなければならないと言うのは初心者的にはちょっとキツいのでは、と。総体的には人間が書くコード量が増えてしまう

加えると最終的にどう言うGUIで表示されるのか、と言うのが、コードを走らせてみないと分からないんですよ。その辺敷居が高いのでは、とちょっと気になりました。一方、wxGladeですと、今まで見てきた通り、スケルトンのプレビューを見る事は簡単です。


僕的にはそのうちGTK+に移るのは全然やぶさかではないんですけど、取り敢えずWeb上には、pyGTK系関係の情報よりwxPython系の情報の方がまだ多いんですね。すなわち、自習するならwxPythonの方がラクだと言う背景もあります。


まあ、そんなわけで、wxGlade + wxPythonの方が、記述量が減る、って事をお見せしようと思っています。ただ、基本的にはやっぱり極私的備忘録でありんす(笑)。


なお、良く分からんのですが、Audaciousと言うアプリケーションが別途必要な模様です(何か知らんが、コイツのSkinを変える能力があるらしい)。



スケルトンを作る


wxGladeの大まかな使い方は既に第二弾で見たんで大幅に端折ります(笑)。そこで主なな注意点と進め方を箇条書き的に記述していきましょう。



  1. 原版ではベースはダイアログだったがwxGladeではあくまでベースはwxFrame


    これは大事ですね。Glade + pyGTKの場合は組み込みダイアログをいきなり呼び出して構わないようですが、残念ながらwxPythonではそれは許されません(だけでなく、実際推奨されていない模様です)。従って、Application直下の子はwx.Frameでキマリです。


  2. frameのNameを"TopLevel"、Classを"MikunchApp"、Titleを"みくんちゅ♪セットアップヘルパ"にする


    これは良いですね。原版のコード名に合わせました。


  3. BoxSizerを使って次のようなウィジェット配置の雛形に持っていく



    これも良いですね。すぐ出来るでしょう。



  4. BoxSizerの一行目にStaticTextを貼り、Labelを"みくんちゅ♪セットアップヘルパ"にする



    これも前回同様ですね。プロパティウィンドウの[Layout]タブ => [Alignment]と辿っていって、wxALIGN_CENTER_HORIZONTALにチェックを入れておけば、タイトル用ラベルが真ん中に配置されます。



  5. BoxSizerの二行目、三行目、四行目の左側にCheckBoxを配置する


    ここで初めて出てくるウィジェットの一つ目がCheckBoxです。これはパレットのここにあります。



    CheckBoxの配置が終われば次のようになりますね。



    それぞれのCheckBoxのLabelを上から順にみくつべ♪をインストールみくかべ♪をインストールAudaciousのスキンをインストールに変更します。また、それぞれのNameを上から順にchkMikutubechkMikukabechkAudaciousにします。全部終わればデザイナは次のようになっているでしょう。



    一つだけTips。chkAudaciousのプロパティウィンドウから[Layout]タブ => [Alignment]と辿って行って、wxALIGN_CENTER_VERTICALにチェックを入れておきましょう。これはこの後すぐ右側に入れるComboBoxとの見た目のバランスを取ってくれる筈です。


  6. BoxSizerの四行目右側にComboBoxを配置する


    ここで初めて出てくるウィジェットの二つ目が、ComboBoxです。これはパレットのここにあります。



    これを配置するとデザイナは次のようになります。



    ComboBoxのプロパティウィンドウでNameをCmbAudaciousConfにしてください。次に[Widget]タブに切り替えます。そこでまず、[Style]でwxCB_READONLYにチェックを入れます。



    そして、下にChoicesってのが見えると思うんですけど、Addボタンを三回押して、ComboBox用の選択肢を三つ作ります。



    そして、それらのLabel初音ミク001初音ミク002初音ミク003と記入していきます。一つだけ注意点が。これらはUTF-8なんで日本語を認識します。が、入力に難がある。ちょっとリターンキーが「記入」と被るので変換が上手く行かないのです。そこで今与えるラベルは別にテキストエディタでも使って、コピペで貼り付けちゃいましょう。そこまで終われば次のようになってる筈です。



    デザイナは次のようになってますね。



    一旦プレビューもしておきますか。



    いい感じですね。


  7. 最下段左端にSpacerを埋める


    これもいいですね。SpacerをBoxSizerに置いた時にポップアップが出てきますが、サイズをwidth=240、height=30にしちゃいましょう。




  8. 残りのBoxSizerにButtonを置く


    これも良いでしょう。一つはCancelボタン(中央)、もう一つはOKボタン(右端)です。それぞれのプロパティウィンドウでNameをbtCancelbtOKにします。また、[Widget]タブでstockitemにチェックを入れて、それぞれCancelとOKを選びます。最後に[Events]タブでそれぞれのHandleron_btCancel_clickedon_btOK_clickedと記入します。




    プレビューしてみましょうか。



    充分ですね。それではxmlファイルをパレットから[File] => [Save As...]と辿ってプロジェクトフォルダにmikunchu.wxgとして保存します。また、Applicationのプロパティウィンドウから同じくプロジェクトフォルダをOutput pathとして、mikunchu.pyを生成します。そして[Generate code]ボタンをクリックします。



以上でスケルトン作成は終了です。



まずは自動生成されたPythonコードを見る


最初にwxGladeが生成したコードを見てみます。


#!/usr/bin/env python
# -*- coding: utf-8 -*-
# generated by wxGlade 0.6.3 on Thu May 26 19:39:49 2011
import wx
# begin wxGlade: extracode
# end wxGlade
class MikunchApp(wx.Frame):
def __init__(self, *args, **kwds):
# begin wxGlade: MikunchApp.__init__
kwds["style"] = wx.DEFAULT_FRAME_STYLE
wx.Frame.__init__(self, *args, **kwds)
self.label_1 = wx.StaticText(self, -1, u"みくんちゅ♪セットアップヘルパ")
self.chkMikutube = wx.CheckBox(self, -1, u"みくつべ♪のインストール")
self.chkMikukabe = wx.CheckBox(self, -1, u"みくかべ♪のインストール")
self.chkAudacious = wx.CheckBox(self, -1, u"audaciousのスキンをインストール")
self.cmbAudaciousConf = wx.ComboBox(self, -1, choices=[u"初音ミク001", u"初音ミク002", u"初音ミク003"], style=wx.CB_DROPDOWN|wx.CB_READONLY)
self.btCancel = wx.Button(self, wx.ID_CANCEL, "")
self.btOK = wx.Button(self, wx.ID_OK, "")
self.__set_properties()
self.__do_layout()
self.Bind(wx.EVT_BUTTON, self.on_btCancel_clicked, self.btCancel)
self.Bind(wx.EVT_BUTTON, self.on_btOK_clicked, self.btOK)
# end wxGlade
def __set_properties(self):
# begin wxGlade: MikunchApp.__set_properties
self.SetTitle(u"みくんちゅ♪セットアップヘルパ")
self.cmbAudaciousConf.SetSelection(-1)
# end wxGlade
def __do_layout(self):
# begin wxGlade: MikunchApp.__do_layout
sizer_1 = wx.BoxSizer(wx.VERTICAL)
sizer_2 = wx.BoxSizer(wx.VERTICAL)
sizer_4 = wx.BoxSizer(wx.HORIZONTAL)
sizer_3 = wx.BoxSizer(wx.HORIZONTAL)
sizer_2.Add(self.label_1, 0, wx.ALIGN_CENTER_HORIZONTAL, 0)
sizer_2.Add(self.chkMikutube, 0, 0, 0)
sizer_2.Add(self.chkMikukabe, 0, 0, 0)
sizer_3.Add(self.chkAudacious, 0, wx.ALIGN_CENTER_VERTICAL, 0)
sizer_3.Add(self.cmbAudaciousConf, 0, 0, 0)
sizer_2.Add(sizer_3, 1, wx.EXPAND, 0)
sizer_4.Add((240, 30), 0, 0, 0)
sizer_4.Add(self.btCancel, 0, 0, 0)
sizer_4.Add(self.btOK, 0, 0, 0)
sizer_2.Add(sizer_4, 1, wx.EXPAND, 0)
sizer_1.Add(sizer_2, 1, wx.EXPAND, 0)
self.SetSizer(sizer_1)
sizer_1.Fit(self)
self.Layout()
# end wxGlade
def on_btCancel_clicked(self, event): # wxGlade: MikunchApp.<event_handler>
print "Event handler `on_btCancel_clicked' not implemented!"
event.Skip()
def on_btOK_clicked(self, event): # wxGlade: MikunchApp.<event_handler>
print "Event handler `on_btOK_clicked' not implemented!"
event.Skip()
# end of class MikunchApp
if __name__ == "__main__":
app = wx.PySimpleApp(0)
wx.InitAllImageHandlers()
TopLevel = MikunchApp(None, -1, "")
app.SetTopWindow(TopLevel)
TopLevel.Show()
app.MainLoop()

前回見た通り、wxGladeでボタン作成をする際に、Event Handlerの名前を定義しておくとそれらHandlerのテンプレートが自動で挿入されます。ここですね。


def on_btCancel_clicked(self, event): # wxGlade: MikunchApp.<event_handler>
print "Event handler `on_btCancel_clicked' not implemented!"
event.Skip()
def on_btOK_clicked(self, event): # wxGlade: MikunchApp.<event_handler>
print "Event handler `on_btOK_clicked' not implemented!"
event.Skip()

GUI上のボタンとこれらは既にBindメソッドによって結ばれているので、あとはこれらの定義(メソッド本体)を書き換えればボタン動作の定義は終了します。例えばCancelボタンは、みくんちゅ♪セットアップヘルパを閉じるのが目的のボタンなんで、on_btCancel_clicked()と言うメソッドは次のように書き換えれば良いわけです。


def on_btCancel_clicked(self, event): # wxGlade: MikunchApp.<event_handler>
self.Close()

ハナクソですね(笑)。



大まかにテンプレートに付け加えるもの


まず、原版のコードを精査してみると、大まかに付け加えなければならないものが二箇所あります。まずは冒頭五行目。


import wx
view raw import_wx.py hosted with ❤ by GitHub

ここはwxだけじゃなくって、いくつか他にライブラリをインポートしないといけません。調べてみると、原版はトータルで六つライブラリをimportしてますが、そこまで必要でなく、あと二つ追加すれば何とかなる事が分かりました。


import wx, commands, os.path

これで充分ですね。commandsと言うライブラリはバックグラウンドで端末によるCLIの命令を走らせるライブラリです。つまり、ボタンをクリックした時、実際は裏で端末での処理を行わせるのが目的なのです(要するに、このアプリケーションは複雑なCLIの命令入力を隠蔽するフロントエンドです)、


os.pathと言うのは、OSのディレクトリの「場所」に関わる事を一元的に引き受けてくれるユーティリティです。


もう一つ付け加えましょう。原版ではMikunchAppと言うクラス定義の前にインストールコマンド群を大域変数として定義しています。メンド臭いんで(笑)、それを全部コピーして、同じようにMikunchAppのクラス定義の前にペーストしちゃいましょう。


#インストールコマンド群
installCommands=(
( #みくつべ♪インストールコマンド群
"gksudo add-apt-repository ppa:khf03353/ppa-kaorin","gksudo apt-get update",'gksudo "apt-get -y install mikutube"'
),
( #みくかべ♪インストールコマンド群
"gksudo add-apt-repository ppa:khf03353/ppa-kaorin","gksudo apt-get update",'gksudo "apt-get -y install mikukabe"'
),
( #audaciousスキンインストールコマンド群
"gksudo apt-get update",'gksudo "apt-get -y install audacious"',
#gksudo に渡す際に"でコマンドをくくる必要があるので、文字列定義としては'を使っている
'gksudo "wget -P /usr/share/audacious/Skins/ http://mangareview.up.seesaa.net/image/mikumiku001.tar.gz"',
'gksudo "wget -P /usr/share/audacious/Skins/ http://mangareview.up.seesaa.net/image/mikumiku_00220.tar.gz"',
'gksudo "wget -P /usr/share/audacious/Skins/ http://mangareview.up.seesaa.net/image/mikumiku_003.tar.gz"',
'gksudo "tar xvzf /usr/share/audacious/Skins/mikumiku001.tar.gz -C /usr/share/audacious/Skins/"',
'gksudo "tar xvzf /usr/share/audacious/Skins/mikumiku_00220.tar.gz -C /usr/share/audacious/Skins/"',
'gksudo "tar xvzf /usr/share/audacious/Skins/mikumiku_003.tar.gz -C /usr/share/audacious/Skins/"',
'gksudo "rm -f /usr/share/audacious/Skins/mikumiku001.tar.gz"',
'gksudo "rm -f /usr/share/audacious/Skins/mikumiku_00220.tar.gz"',
'gksudo "rm -f /usr/share/audacious/Skins/mikumiku_003.tar.gz"',
)
)

on_btOK_clicked()の実装


実際問題、この「みくんちゅ♪セットアップヘルパ」では、「アプリが何か行う」のはボタンがクリックされた時なんです。んで、良く考えてみるとCancelボタンに関してはとっくに実装が終わっています。っつー事はですね。極端な話、残るはon_btOK_clicked()の中身さえ定義してしまえば、このアプリの開発は終了するわけです。


def on_btOK_clicked(self, event): # wxGlade: MikunchApp.<event_handler>
print "Event handler `on_btOK_clicked' not implemented!"
event.Skip()

ここをどう書き換えるのか、ってのが実はこのアプリの肝なんです。取り敢えず原版のその部分を見てみましょうか。


def on_btOK_clicked(self,widget):
#適用を行ってアプリケーションを終了する
if self.chkMikutube.get_active() == True:
#みくつべ♪インストールコマンドを実行
for cmd in installCommands[0]:
self.execCommand(cmd)
if self.chkMikukabe.get_active() == True:
#みくかべ♪インストールコマンドを実行
for cmd in installCommands[1]:
self.execCommand(cmd)
if self.chkAudacious.get_active() == True:
#Audacious用のスキンインストールと適用
for cmd in installCommands[2]:
self.execCommand(cmd)
#適用スキンのコンフィグ名を定義
confName = ("mikumiku001","mikumiku_002 ","mikumiku_003")
#選択されているスキンの番号を取得
sel = self.cmbAudaciousConf.get_active()
#コンフィグファイルのパスを生成
configpath = os.path.expanduser("~") + "/.config/audacious/config"
#選択スキンをコンフィグファイルに設定
self.editConfig("skin=", "skin=/usr/share/audacious/Skins/"+confName[sel], configpath)
#壊れているスキンも有効にする設定をTRUEに
self.editConfig('allow_broken_skins=',"allow_broken_skins=TRUE", configpath)
#ウィンドウを閉じてアプリケーションを終了する
gtk.main_quit()

んでまず「流れ」を把握しておきます。大きく言うと「何かして」 => 「閉じる」ってのが流れですね。「何かして」の部分は取り敢えず置いておいて、最後のgtk.main_quit()と言うメソッドでアプリケーションを「最後に閉じる」と言う事だけは確定しているわけです。これはCancelボタンの動作と基本的には同じなんです。従って、ここでもon_btOK_clickedの最後はwxPythonのアプリケーション終了命令、self.Close()が来る、と言う事が分かります。


def on_btOK_clicked(self, event): # wxGlade: MikunchApp.<event_handler>
print "Event handler `on_btOK_clicked' not implemented!"
event.Skip()
#ウィンドウを閉じてアプリケーションを終了する
self.Close()

次に原版のコードを精査してみると特徴的な命令がある事が分かります。つまり、



self.何とか.get_active() = True:

と言うパターンですね。これは一体何をやってるのか?要するにチェックボックスにチェックが入ってるのか否か、ってのを調べているんです。


例えばみくかべ♪だけインストールしたい、とする。ユーザーは「みくかべ♪をインストール」だけにチェックを入れるわけです。そこで「みくんちゅ♪セットアップヘルパ」と言うアプリケーションが何をするのか?次のような「流れ」が存在するんです。



  1. 「みくつべ♪をインストール」がチェックされているか調べる => チェックされてた場合はインストールする、されてない場合は無視。今回はチェックされてないので無視する。

  2. 「みくかべ♪をインストール」がチェックされているか調べる => チェックされてた場合はインストールする。されてない場合は無視。今回はチェックされてるのでインストールする。

  3. 「Audaciousのスキンをインストール」がチェックされているか調べる => チェックされてた場合はインストールする。されてない場合は無視。今回はチェックされてないので無視する。


これを上から順序良く全部行ってるんです。結構バカみたいでしょ(笑)?これは当然、@kaorin_linux氏がどーの、って話じゃなくって、コンピュータのプログラムってこーゆーモノなんです。上から順序良く調べて行ってる(専門的には逐次処理と呼びます)。ただ、人間と違って高速に処理するので、一瞬で判断してるように見えるわけですね。


また、おっさんにも解るPythonにも記述されていますが、☓☓だったら△△する、と言う記述を条件分岐と呼びます。今必要なのは、「☓☓だったら」の部分で、ここは今は要するに「特定のチェックボックスにチェックが入ってたら」と言う事ですね。そして、そうなるとチェックボックスにチェックが入ってるかどうかの情報を得られないといけない。そこがpyGTKの場合は



self.何とか.get_active() = True:

で表現されているわけです。同様の動作のwx.Python版は、



self.チェックボックスのインスタンス名.GetValue()

です。例えば「みくつべ♪をインストール」のチェックボックスのインスタンス名はchkMikutubeなんで、



self.chkMikubute.GetValue()

でそこにチェックが入ってるかどうか調べる事が可能です。従って、少なくとも「みくつべ♪をインストール」「みくかべ♪をインストール」の二つに関して言うと、


def on_btOK_clicked(self, event): # wxGlade: MikunchApp.<event_handler>
# 適用を行ってアプリケーションを終了する
# みくつべ♪インストールコマンドを実行
[self.execCommand(cmd) for cmd in installCommands[0] if self.chkMikutube.GetValue()]
# みくかべ♪インストールコマンドを実行
[self.execCommand(cmd) for cmd in installCommands[1] if self.chkMikukabe.GetValue()]
#ウィンドウを閉じてアプリケーションを終了する
self.Close()

で済みます。なお、@kaorin_linux氏の書き方と違って、ここではリスト内包表記と言う書き方を用いています。何故ならその方が短く書けるから、です。それぞれ記述が一行で済む(笑)。ここもリストを返すのが目的じゃなくって、execCommand()ってのを走らせるのが目的ですね。Lisp的な言い方すると「破壊的操作」が目的で、返り値はどーでも良いわけです(笑)。ちなみにexecCommand()ってのは未定義です。今から定義します


ここまで分かれば、残りの部分も原版の



を参考にすればすぐ移植が可能です。次のようになりますね。



殆ど同じです。注意点としては、



  1. get_active() == Trueの代わりはGetValue()だけで良い。== Trueは要らない。

  2. wx.Pythonでは,ComboBox(ここではインタンス名がcmbAudaciousConf)の値を取得するには、get_active()の代わりにGetCurrentSelection()メソッドを用いる。これは選択肢の順番に従って、0から始まる値を返す。

  3. オリジナルのコードではconfigpathは文字列結合にしているが、こっちではPythonコードの推奨の書き方でos.path.joinを用いた書き方にしてある。

  4. editConfig()は未定義。今から定義します


つまり、on_btOK_clickedは全体的にはこうなります。


def on_btOK_clicked(self, event): # wxGlade: MikunchApp.<event_handler>
# 適用を行ってアプリケーションを終了する
# みくつべ♪インストールコマンドを実行
[self.execCommand(cmd) for cmd in installCommands[0] if self.chkMikutube.GetValue()]
# みくかべ♪インストールコマンドを実行
[self.execCommand(cmd) for cmd in installCommands[1] if self.chkMikukabe.GetValue()]
if self.chkAudacious.GetValue():
# Audacious用のスキンインストールと適用
for cmd in installCommands[2]:
self.execCommand(cmd)
# 適用スキンのコンフィグ名を定義
confName = ("mikumiku_001", "mikumiku_002", "mikumiku_003")
# 選択されているスキンの番号を取得
sel = self.cmbAudaciousConf.GetCurrentSelection()
# コンフィグファイルのパスを生成
configpath = os.path.join(os.path.expanduser("~"), ".config/audacious/config")
# 選択スキンをコンフィグファイルに設定
self.editConfig("skin=", "skin=/usr/share/audacious/Skins/" + \
confName[sel], configpath)
# 壊れているスキンも有効にする設定を True に
self.editConfig("allow_broken_skins=", "allow_broken_skins=TRUE", \
configpath)
# ウィンドウを閉じてアプリケーションを終了する
self.Close()

基本的にはこれで全ての実装が終了したようなモンです。ただし、未だ未定義のメソッドを二つ使ってますね。execCommand()editConfig()の二つです。ではこの二つを実装しましょう。


execCommand()の実装


まずはexecCommand()から。っつってもこれは原版のまま流用してもらって結構です。


#コマンド実行のメソッド
def execCommand(self,command):
print command #受け渡されたコマンドのデバッグ用プリント
ret = commands.getoutput(command)
print ret #実行結果のデバッグ用プリント
return ret
view raw execCommand.py hosted with ❤ by GitHub

多分これは本質的に作らなくっていいから、でしょう(笑)。恐らく、基本動作的には、commands.getoutput()だけでイイんですけど、@kaorin_linux氏がデバッグ目的で敢えてメソッドとしてラップしたんだと思います。よってこれに付いてはここまで、です(笑)。


editConfig()の実装


これも基本的には全くそのまま原版のコードを流用して構いません。と言うのもこれも別にpyGTK依存のコードじゃないから、ですね。ただ、ここではまたもやリスト内包表記を使ってコードを若干短くしてみますか。


# 設定ファイルの書き換え
def editConfig(self, keyword, replaceString, configPath):
print keyword
print replaceString
# ファイルをオープン
f = open(configPath, 'r')
# 新しい書き込み用リストを生成
cnf = [txt.strip().find(keyword) >= 0 and replaceString + '\n' or txt \
for txt in [line for line in f.readlines()]]
f.close()
# 書き込み用に再度ファイルを開く
fw = open(configPath, "w")
# 生成したリストを書きこむ
fw.writelines(cnf)
# 書き込んだのでファイルを閉じる
fw.close()
view raw editConfig.py hosted with ❤ by GitHub

基本的なロジックはそのままです。冒頭二行はまたもやデバッグ用のprint文ですね。ここはロジックには関係ありません。


@kaorin_linux氏がプログラミング初心者を念頭にコメントを記入してくれているので、基本的な「流れ」はこのままです。ただ、書き込み用リスト生成の部分でリスト内包表記を使いました。ここですね。


cnf = [txt.strip().find(keyword) >= 0 and replaceString + '\n' or txt \
for txt in [line for line in f.readlines()]]

ここではリスト内包表記を二重に使っています。内側の[line for line in f.readlines()]ではreadlines()メソッドを用いて、行別に文字列に纏めた要素のリストを生成して、それに対してkeywordに引っかかった文字列をreplaceString + '\n'で置き換え、そうじゃない場合はそのままtxtを返して、結果リストにまとめているんですね。ちょっと外側なんて特にLisp的に見えますが(笑)、複雑な条件分岐を持つリスト内包表記では上のように論理演算子を用いるのがティピカルな手段の模様です。


wxPython版みくんちゅ♪セットアップヘルパ完成


以上で終了ですね。割にあっと言う間に終わるでしょ(笑)?では、wxPython版みくんちゅ♪セットアップヘルパの全コードを。


#!/usr/bin/env python
# -*- coding: utf-8 -*-
# generated by wxGlade 0.6.3 on Thu May 26 19:39:49 2011
import wx, commands, os.path
# begin wxGlade: extracode
# end wxGlade
#インストールコマンド群
installCommands=(
( #みくつべ♪インストールコマンド群
"gksudo add-apt-repository ppa:khf03353/ppa-kaorin","gksudo apt-get update",'gksudo "apt-get -y install mikutube"'
),
( #みくかべ♪インストールコマンド群
"gksudo add-apt-repository ppa:khf03353/ppa-kaorin","gksudo apt-get update",'gksudo "apt-get -y install mikukabe"'
),
( #audaciousスキンインストールコマンド群
"gksudo apt-get update",'gksudo "apt-get -y install audacious"',
#gksudo に渡す際に"でコマンドをくくる必要があるので、文字列定義としては'を使っている
'gksudo "wget -P /usr/share/audacious/Skins/ http://mangareview.up.seesaa.net/image/mikumiku001.tar.gz"',
'gksudo "wget -P /usr/share/audacious/Skins/ http://mangareview.up.seesaa.net/image/mikumiku_00220.tar.gz"',
'gksudo "wget -P /usr/share/audacious/Skins/ http://mangareview.up.seesaa.net/image/mikumiku_003.tar.gz"',
'gksudo "tar xvzf /usr/share/audacious/Skins/mikumiku001.tar.gz -C /usr/share/audacious/Skins/"',
'gksudo "tar xvzf /usr/share/audacious/Skins/mikumiku_00220.tar.gz -C /usr/share/audacious/Skins/"',
'gksudo "tar xvzf /usr/share/audacious/Skins/mikumiku_003.tar.gz -C /usr/share/audacious/Skins/"',
'gksudo "rm -f /usr/share/audacious/Skins/mikumiku001.tar.gz"',
'gksudo "rm -f /usr/share/audacious/Skins/mikumiku_00220.tar.gz"',
'gksudo "rm -f /usr/share/audacious/Skins/mikumiku_003.tar.gz"',
)
)
class MikunchApp(wx.Frame):
def __init__(self, *args, **kwds):
# begin wxGlade: MikunchApp.__init__
kwds["style"] = wx.DEFAULT_FRAME_STYLE
wx.Frame.__init__(self, *args, **kwds)
self.label_1 = wx.StaticText(self, -1, u"みくんちゅ♪セットアップヘルパ")
self.chkMikutube = wx.CheckBox(self, -1, u"みくつべ♪のインストール")
self.chkMikukabe = wx.CheckBox(self, -1, u"みくかべ♪のインストール")
self.chkAudacious = wx.CheckBox(self, -1, u"audaciousのスキンをインストール")
self.cmbAudaciousConf = wx.ComboBox(self, -1, choices=[u"初音ミク001", u"初音ミク002", u"初音ミク003"], style=wx.CB_DROPDOWN|wx.CB_READONLY)
self.btCancel = wx.Button(self, wx.ID_CANCEL, "")
self.btOK = wx.Button(self, wx.ID_OK, "")
self.__set_properties()
self.__do_layout()
self.Bind(wx.EVT_BUTTON, self.on_btCancel_clicked, self.btCancel)
self.Bind(wx.EVT_BUTTON, self.on_btOK_clicked, self.btOK)
# end wxGlade
def __set_properties(self):
# begin wxGlade: MikunchApp.__set_properties
self.SetTitle(u"みくんちゅ♪セットアップヘルパ")
self.cmbAudaciousConf.SetSelection(-1)
# end wxGlade
def __do_layout(self):
# begin wxGlade: MikunchApp.__do_layout
sizer_1 = wx.BoxSizer(wx.VERTICAL)
sizer_2 = wx.BoxSizer(wx.VERTICAL)
sizer_4 = wx.BoxSizer(wx.HORIZONTAL)
sizer_3 = wx.BoxSizer(wx.HORIZONTAL)
sizer_2.Add(self.label_1, 0, wx.ALIGN_CENTER_HORIZONTAL, 0)
sizer_2.Add(self.chkMikutube, 0, 0, 0)
sizer_2.Add(self.chkMikukabe, 0, 0, 0)
sizer_3.Add(self.chkAudacious, 0, wx.ALIGN_CENTER_VERTICAL, 0)
sizer_3.Add(self.cmbAudaciousConf, 0, 0, 0)
sizer_2.Add(sizer_3, 1, wx.EXPAND, 0)
sizer_4.Add((240, 30), 0, 0, 0)
sizer_4.Add(self.btCancel, 0, 0, 0)
sizer_4.Add(self.btOK, 0, 0, 0)
sizer_2.Add(sizer_4, 1, wx.EXPAND, 0)
sizer_1.Add(sizer_2, 1, wx.EXPAND, 0)
self.SetSizer(sizer_1)
sizer_1.Fit(self)
self.Layout()
# end wxGlade
def on_btCancel_clicked(self, event): # wxGlade: MikunchApp.<event_handler>
self.Close()
def on_btOK_clicked(self, event): # wxGlade: MikunchApp.<event_handler>
# 適用を行ってアプリケーションを終了する
# みくつべ♪インストールコマンドを実行
[self.execCommand(cmd) for cmd in installCommands[0] if self.chkMikutube.GetValue()]
# みくかべ♪インストールコマンドを実行
[self.execCommand(cmd) for cmd in installCommands[1] if self.chkMikukabe.GetValue()]
if self.chkAudacious.GetValue():
# Audacious用のスキンインストールと適用
for cmd in installCommands[2]:
self.execCommand(cmd)
# 適用スキンのコンフィグ名を定義
confName = ("mikumiku_001", "mikumiku_002", "mikumiku_003")
# 選択されているスキンの番号を取得
sel = self.cmbAudaciousConf.GetCurrentSelection()
# コンフィグファイルのパスを生成
configpath = os.path.join(os.path.expanduser("~"), ".config/audacious/config")
# 選択スキンをコンフィグファイルに設定
self.editConfig("skin=", "skin=/usr/share/audacious/Skins/" + \
confName[sel], configpath)
# 壊れているスキンも有効にする設定を True に
self.editConfig("allow_broken_skins=", "allow_broken_skins=TRUE", \
configpath)
#ウィンドウを閉じてアプリケーションを終了する
self.Close()
# コマンド実行のメソッド
def execCommand(self, command):
print command # 受け渡されたコマンドのデバッグ用プリント
ret = commands.getoutput(command)
print ret # 実行結果のデバッグ用プリント
return ret
# 設定ファイルの書き換え
def editConfig(self, keyword, replaceString, configPath):
print keyword
print replaceString
# ファイルをオープン
f = open(configPath, 'r')
# 新しい書き込み用リストを生成
cnf = [txt.strip().find(keyword) >= 0 and replaceString + '\n' or txt \
for txt in [line for line in f.readlines()]]
f.close()
# 書き込み用に再度ファイルを開く
fw = open(configPath, "w")
# 生成したリストを書きこむ
fw.writelines(cnf)
# 書き込んだのでファイルを閉じる
fw.close()
# end of class MikunchApp
if __name__ == "__main__":
app = wx.PySimpleApp(0)
wx.InitAllImageHandlers()
TopLevel = MikunchApp(None, -1, "")
app.SetTopWindow(TopLevel)
TopLevel.Show()
app.MainLoop()
view raw mikunchu.py hosted with ❤ by GitHub

これは当然教材なんで、実用的には三つばかり問題点が見えました。


一点目。動作的には実際は、Audacious用のスキンをインストールする場合、実は一気にインストールされるのでComboBox上の選択自体はこの点では全く関係ないわけです。つまり、設定ファイルだけを自動で書き換えたくても、「必ず」インストールが始まるんですね。ですから、本当はインストールとComboBoxの選択肢は分けた方が良いとは思います。


二点目。editConfig()の大半は正規表現使えばもっとシンプルに書けるかな、とか思ったんですが失敗しました(笑)。正規表現難しいですね。全く慣れない。


三点目。これもeditConfig()に纏わる話なんですけど。そもそも僕は全くAudaciousってアプリケーションを使った事が無くって(笑)。良く分からなかったんですが(笑)。この設定ファイルにそもそも"skin="とか"allow_broken_skins="って項目は無いんですよ。従って、ロジックを良く精査してみれば分かると思うんですけど、「そもそもそれらが存在しない場合」設定ファイルにこれらの条件を追加する事が出来ないんです。つまり、出来れば「存在しなかった場合」の追加条件を記述した方が良いですね。


こんなカンジですかね。以上です。なお、ソースコードとwxgファイルはまたgithubにあげてあります。

0 件のコメント:

コメントを投稿