OSDN Git Service

Prepare for Release of 4.0.5
authorShyouzou Sugitani <shy@users.sourceforge.jp>
Wed, 28 Apr 2010 12:39:41 +0000 (21:39 +0900)
committerShyouzou Sugitani <shy@users.sourceforge.jp>
Wed, 28 Apr 2010 12:39:41 +0000 (21:39 +0900)
29 files changed:
ChangeLog
doc/saori.txt
lib/ninix-install.py
lib/ninix/balloon.py
lib/ninix/config.py
lib/ninix/dll.py
lib/ninix/dll/bln.py
lib/ninix/dll/hanayu.py
lib/ninix/dll/httpc.py
lib/ninix/dll/mciaudio.py
lib/ninix/dll/mciaudior.py
lib/ninix/dll/osuwari.py
lib/ninix/dll/saori_cpuid.py
lib/ninix/dll/ssu.py
lib/ninix/dll/textcopy.py
lib/ninix/dll/wmove.py
lib/ninix/kinoko.py
lib/ninix/main.py
lib/ninix/menu.py
lib/ninix/nekodorif.py
lib/ninix/ngm.py
lib/ninix/pix.py
lib/ninix/plugin.py [new file with mode: 0755]
lib/ninix/prefs.py
lib/ninix/sakura.py
lib/ninix/sstp.py
lib/ninix/surface.py
lib/ninix/update.py
lib/ninix/version.py

index fdd0d52..a30ac9a 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,73 @@
+Thu April 29 2010   Shyouzou Sugitani <shy@users.sourceforge.jp>
+       * バージョン4.0.5リリース.
+
+Wed April 28 2010   Shyouzou Sugitani <shy@users.sourceforge.jp>
+       * lib/ninix/sakura.py:
+         GStreamerの奇妙な振舞いへの対策を追加.
+         (https://bugzilla.gnome.org/show_bug.cgi?id=549879 を参照.)
+         具体的な動作としては--help(-h)コマンドラインオプションを指定すると
+         GStreamerのヘルプが表示されていたのをninix-ayaのものが出るように修正.
+
+Mon April 26 2010   Shyouzou Sugitani <shy@users.sourceforge.jp>
+       * lib/ninix/main.py:
+         廃止したコマンドラインオプションの処理が残っていたのを削除.
+       * lib/ninix/menu.py:
+         メニューからゴーストの再読み込みを削除.
+
+Fri April 23 2010   Shyouzou Sugitani <shy@users.sourceforge.jp>
+       * lib/ninix/dll/httpc.py:
+         HTTP Status Codeが200(OK)以外の場合にはデータを返さないように修正.
+       * クラスのインスタンス生成時に生成元のクラス等他のクラスへの参照を
+         渡していたのを止めて, 代わりにコールバック関数の入った辞書型変数を
+         渡すようにした.(lib/ninix/dll/以下のファイルについては除外.)
+       * lib/ninix/balloon.py:
+         バルーンのウインドウにdeleteイベントが送られてもゴーストが終了しない
+         ように変更.
+       * lib/ninix/dll.py:
+         SAORI互換モジュールからPreferencesクラスへの参照を削除し, Sakuraへの
+         参照を使うようにした.
+       * Pluginに関するコードをmain.pyから分離してplugin.pyを作成した.
+       * lib/ninix/main.py:
+         SSTP Serverの管理をApplicationクラスから分離してSSTPControlerクラスを
+         作成した.
+       * lib/ninix/main.py:
+         ApplicationクラスをNew-style classにした.(今後全クラスを変更予定.)
+         current_sakuraをApplicationクラスのpropertyにした.
+         current_sakuraのsetter(a function for setting)内で変更をPreferences
+         に伝えるようにした.
+
+Fri April 9 2010   Shyouzou Sugitani <shy@users.sourceforge.jp>
+       * doc/saori.txt: osuwari.pyとhttpc.pyに関する記述を追加.
+       * lib/ninix/config.py:
+         Configクラスのgetint, getfloatメソッドをget_with_typesに統合.
+       * lib/ninix/dll.py:
+         SAORIクラスに辞書型変数RESPONSEを作成し, 戻り値を持たない
+         SAORI/1.0のレスポンス(204, 400, 500)を入れた.
+         SAORI互換モジュールはこの変数を使用するように修正.
+       * lib/ninix/main.py:
+         Application.get_plugin_list()で変数の代入前に値を参照している部分が
+         あったのを修正.
+       * lib/ninix/main.py:
+         'OnShellChanging'イベントの引数に渡す変数名を間違えていたのを修正.
+         (3.9.9でのregression.)
+       * lib/ninix/pix.py:
+         create_pixbuf_from_file()の引数is_pnrとuse_pnaには1/0ではなく
+         True/Falseを渡すように変更.
+       * lib/ninix/prefs.py:
+         Preferences.get_with_type()の引数の順序を変更した.
+       * lib/ninix/sakura.py:
+         専用バルーンを持つゴーストでバルーンを専用バルーンとは別のバルーンに
+         変えてから専用バルーンに戻すと落ちる問題を修正.
+       * lib/ninix/balloon.py:
+         BalloonWindowのconfig_getintメソッドを__get_with_scalingに変更.
+         (__get_with_scalingでは戻り値の型を第2引数に渡す必要がある.)
+       * lib/ninix/surface.py:
+         SurfaceWindowのget_config_intメソッドを__get_with_scalingに変更.
+         (__get_with_scalingでは戻り値の型を第2引数に渡す必要がある.)
+       * lib/ninix/prefs.py:
+         デフォルトバルーンに設定されているバルーンの存在確認を省略.
+         (存在していない場合には最初に見付かったバルーンが選ばれる.)
+
 Tue April 6 2010   Shyouzou Sugitani <shy@users.sourceforge.jp>
        * バージョン4.0.4リリース.
 
@@ -23,7 +93,7 @@ Sun April 4 2010   Shyouzou Sugitani <shy@users.sourceforge.jp>
        * lib/ninix/surface.py:
          サーフェス倍率が100%以外の場合に当り判定領域の座標の計算結果が全て
          0になるバグを修正.(バージョン4.0.3でのregression.)
-       
+
 Thu April 1 2010   Shyouzou Sugitani <shy@users.sourceforge.jp>
        * Makefile: 使用していなかったPythonのdistutilsに関する部分を削除.
          それに合わせてPKG-INFOファイルを削除した.
@@ -40,7 +110,7 @@ Thu April 1 2010   Shyouzou Sugitani <shy@users.sourceforge.jp>
 
 Wed March 31 2010   Shyouzou Sugitani <shy@users.sourceforge.jp>
        * バージョン4.0.3リリース.
-       
+
 Mon March 29 2010   Shyouzou Sugitani <shy@users.sourceforge.jp>
        * 画像を拡大/縮小する際に8x8ピクセル以下にならないように制限をかけた.
          (これまでも一部で制限をかけてあったが1x1ピクセルであったり,
@@ -50,7 +120,7 @@ Mon March 29 2010   Shyouzou Sugitani <shy@users.sourceforge.jp>
           スクリプト再生は未対応のまま.)
        * lib/ninix/dll/aya5.py:
          未実装のシステム関数を呼び出そうとして落ちる問題を修正.
-       
+
 Fri March 26 2010   Shyouzou Sugitani <shy@users.sourceforge.jp>
        * サーフェス/バルーンの拡大縮小で100%の場合を特別扱いするのをやめた.
        * pixbuf, pixmapを明示的に削除するのをやめた.
@@ -88,11 +158,11 @@ Fri March 26 2010   Shyouzou Sugitani <shy@users.sourceforge.jp>
           エラーになっていた.)
        * lib/ninix/kawari.py: read_local_script()内のtypoを修正.
          (readline() -> readlines())
-       
+
 Thu February 25 2010   Shyouzou Sugitani <shy@users.sourceforge.jp>
        * バージョン4.0.2リリース.
        * lib/ninix/main.py: ゴースト消滅後の交代で落ちる問題を修正.
-       
+
 Tue February 23 2010   Shyouzou Sugitani <shy@users.sourceforge.jp>
        * lib/ninix/surface.py:
          ドロップされたファイルのパスがURLエンコードされたままになっていたため
@@ -127,7 +197,7 @@ Mon February 22 2010   Shyouzou Sugitani <shy@users.sourceforge.jp>
        * lib/ninix/ngm.py: 使用していないモジュールのimportを削除.
        * lib/ninix/home.py, lib/ninix/main.py:
          gtkrcの読み込み処理を削除.
-       * lib/ninix/home.py: 
+       * lib/ninix/home.py:
          「きのこ」と「猫どりふ」の設定ファイルの読み込みでエラー処理が
          抜けていたのを修正.
        * lib/ninix/dll/wmove.py, lib/ninix/dll/bln.py:
@@ -154,7 +224,7 @@ Thu December 24 2009   Shyouzou Sugitani <shy@users.sourceforge.jp>
          \_v[]タグとMCIAudio, MCIAudioR互換モジュールを変更した.
          MCIAudioRはループ演奏に対応した.
          READMEの「必要なもの」にGStreamer Python bindingsを追加した.
-       
+
 Fri December 4 2009   Shyouzou Sugitani <shy@users.sourceforge.jp>
        * Webブラウザの呼び出しにはPythonのwebbrowerモジュールを
          使用するようにした.(ユーザー設定からWebブラウザを削除した.)
@@ -163,7 +233,7 @@ Sun November 29 2009   Shyouzou Sugitani <shy@users.sourceforge.jp>
        * バージョン3.9.9aリリース.
        * CommunicateWindowクラス(とそれを継承しているクラス)を修正.
          (Thanks to addone@users.sourceforge.jp.)
-       
+
 Wed July 22 2009   Shyouzou Sugitani <shy@users.sourceforge.jp>
        * バージョン3.9.9リリース.
        * 「きのこ」の'ontop'の処理にgtk.Window.set_transient_for()を
@@ -181,7 +251,7 @@ Sun July 5 2009   Shyouzou Sugitani <shy@users.sourceforge.jp>
        * NGMクローンからのゴーストの更新方法を変更した.
          (Sakuraクラスのupdate()メソッドを直接呼ぶのではなく,
           "\![updatebymyself]\e"をスクリプトキューに入れるようにした.)
-       
+
 Fri June 12 2009   Shyouzou Sugitani <shy@users.sourceforge.jp>
        * バージョン3.9.8eリリース.
        * 3.9.8dでeasyballoon互換モジュールが落ちるのを修正した.
@@ -195,7 +265,7 @@ Fri June 12 2009   Shyouzou Sugitani <shy@users.sourceforge.jp>
          集約した. ユーザー設定の項目も減らした.
          SHIORIイベントの発生を抑制する項目(PREFS_EVENT_KILL_LIST)と
          マウスボタンの機能を設定する項目(PREFS_MOUSE_BUTTON1,
-         PREFS_MOUSE_BUTTON3)を削除した. 
+         PREFS_MOUSE_BUTTON3)を削除した.
          また, メニューから個々のゴーストについてサーフェス倍率とスクリプトの
          再生スピードを設定する項目を削除した.(全ゴーストが同じ設定になる.)
 
@@ -209,7 +279,7 @@ Mon May 11 2009   Shyouzou Sugitani <shy@users.sourceforge.jp>
          ("Extended Window Manager Hints"の_NET_WORKAREAを使用している.
           詳細は http://standards.freedesktop.org/wm-spec/wm-spec-latest.html
           を参照.)
-       
+
 Wed May 6 2009   Shyouzou Sugitani <shy@users.sourceforge.jp>
        * バージョン3.9.8bリリース.
        * sourceforge.jpにGitリポジトリを作成し, ソースコードの管理をGitに
@@ -317,7 +387,7 @@ Sun July 29 2007   Shyouzou Sugitani <shy@users.sourceforge.jp>
 Sun July 22 2007   Shyouzou Sugitani <shy@users.sourceforge.jp>
        * バージョン3.9.6リリース.
        * OnBallonCloseイベントのサポートを追加.
-       
+
 Sat July 21 2007   Shyouzou Sugitani <shy@users.sourceforge.jp>
        * OnMouseEnterAll, OnMouseLeaveAll, OnMouseEnter, OnMouseLeave
        イベントのサポートを追加.
@@ -2278,7 +2348,7 @@ Sat Apr 12 2003  Shyouzou Sugitani <shy@users.sourceforge.jp>
 - 全てのスクリプトがトランスレータを通るように修正.(sakura.py)
 - 選択肢の範囲チェックを修正.
   選択肢の先頭が表示領域内でも途中から外に出ている場合がある.(sakura.py)
-       
+
 2002/10/27 period 20
 - 花柚(hanayu.dll)互換 SAORI モジュール hanayu.py を追加.
 - れたす(lettuce.dll)互換 SAORI モジュール lettuce.py を追加.
@@ -2373,7 +2443,7 @@ Sat Apr 12 2003  Shyouzou Sugitani <shy@users.sourceforge.jp>
  - 再読み込み後に発生させるイベントを OnBoot に変更.(sakura.py)
  - OnGhostChanged に反応が無い場合には OnBoot を呼ぶようにした.(sakura.py)
  - '\x' による一時停止時にバルーンに下向き矢印を出させるようにした.(sakura.py)
-       
+
 2002/09/02 period 17
  - DLL 互換モジュールのデフォルトサーチパスの指定を必須にした.(dll.py)
  - DLL 互換モジュールを要求する際にサーチパスを追加出来るように変更.(dll.py)
@@ -2423,10 +2493,10 @@ Sat Apr 12 2003  Shyouzou Sugitani <shy@users.sourceforge.jp>
  - SAORI互換機能 (saory.py) 追加.
  - ユーザーからゴーストへのコミュニケート対応(aya のみ).
  - 「和音」のメニューからの MIDI 演奏に対応.
-デバッグ:        
+デバッグ:
  - 簡易配列を拡張する処理の条件判定が逆になっていたのを修正.
  - 変数への代入の際に既に変数が存在するかどうかの判定を忘れていたのを追加.
- - 変数を操作する場合, 事前に AyaVAriable.reset メソッドが実行されるようにした. 
+ - 変数を操作する場合, 事前に AyaVAriable.reset メソッドが実行されるようにした.
    これにより文字列の演算による簡易配列としての構造の変化に対応.
 
 2002/07/30 period 13
@@ -2593,7 +2663,7 @@ Sat Apr 12 2003  Shyouzou Sugitani <shy@users.sourceforge.jp>
    CALLBYNAME, LOGGING, TOUPPER, TOLOWER, TONUMBER2, TOSTRING2,
    FLOOR, CEIL, ROUND, SIN, COS, TAN, LOG, LOG10, POW, SQRT,
    SETSEPARATOR, FOPEN, FCLOSE, FREAD, FWRITE, ISINTEGER, ISREAL
+
 2002/05/28 period 5 (canceled)
  - 「文」Ver.3 文法に対応
  - SHIORI/1.0 APIに対応
@@ -2616,7 +2686,7 @@ Sat Apr 12 2003  Shyouzou Sugitani <shy@users.sourceforge.jp>
  - システム関数 ARRAYSIZE を実装.
  - 「和音」がエラーで固まるのを防ぐために ghostexcount は常に0を返
    すようにした. (COMMUNICATE実装までの暫定措置)
+
 2002/05/20 period 2
  - 文 ver.3 一部対応
  - 全角スペースを空白と見なしていなかった問題の修正
index 7f25067..4bfd26d 100644 (file)
@@ -59,6 +59,16 @@ ssu.py
 ------
 櫛ケ浜やぎ氏が開発されている「ssu.dll」と互換のモジュールです。
 
+osuwari.py
+--------
+ukiya氏が開発されている「osuwari.dll」と互換のモジュールです。
+ゴーストをウィンドウに「お座り」させる機能を提供します。
+
+httpc.py
+--------
+櫛ヶ浜やぎ氏が開発されている「httpc.dll」と互換のモジュールです。
+Web上の各種データをHTTPで取得する機能を提供します。
+
 連絡先
 ------
 この資料の著作権と文責は、
index c9fcd9d..1fc9a27 100755 (executable)
@@ -500,7 +500,7 @@ def install_redo_ghost(archive, tmpdir, homedir):
         inst_dst = ninix.home.read_install_txt(prefix)
         if not inst_dst or inst_dst.get('name') != inst.get('name'):
             mode |= INTERACTIVE
-        if inst.getint('refresh', 0):
+        if inst.get_with_type('refresh', int, 0):
             # uninstall older versions of the ghost
             if confirm_removal(prefix):
                 mask = inst.get('refreshundeletemask', '').lower().split(':')
@@ -552,7 +552,7 @@ def install_redo_shell(archive, tmpdir, homedir):
         inst_dst = ninix.home.read_install_txt(prefix)
         if not inst_dst or inst_dst.get('name') != inst.get('name'):
             mode |= INTERACTIVE
-        if inst.getint('refresh', 0):
+        if inst.get_with_type('refresh', int, 0):
             # uninstall older versions of the ghost
             if confirm_removal(prefix):
                 mask = inst.get('refreshundeletemask', '').lower().split(':')
@@ -737,7 +737,7 @@ def install_inverse_ghost(archive, tmpdir, homedir):
         inst_dst = ninix.home.read_install_txt(prefix)
         if not inst_dst or inst_dst.get('name') != inst.get('name'):
             mode |= INTERACTIVE
-        if inst.getint('refresh', 0):
+        if inst.get_with_type('refresh', int, 0):
             # uninstall older versions of the ghost
             if confirm_removal(prefix):
                 mask = inst.get('refreshundeletemask', '').lower().split(':')
@@ -787,7 +787,7 @@ def install_inverse_shell(archive, tmpdir, homedir):
         inst_dst = ninix.home.read_install_txt(prefix)
         if not inst_dst or inst_dst.get('name') != inst.get('name'):
             mode |= INTERACTIVE
-        if inst.getint('refresh', 0):
+        if inst.get_with_type('refresh', int, 0):
             # uninstall older versions of the shell
             if confirm_removal(prefix):
                 mask = inst.get('refreshundeletemask', '').lower().split(':')
@@ -866,7 +866,7 @@ def install_balloon(archive, srcdir, homedir):
         inst_dst = ninix.home.read_install_txt(dstdir)
         if not inst_dst or inst_dst.get('name') != inst.get('name'):
             mode |= INTERACTIVE
-        if inst.getint('refresh', 0):
+        if inst.get_with_type('refresh', int, 0):
             # uninstall older versions of the balloon
             if confirm_removal(dstdir):
                 mask = inst.get('refreshundeletemask', '').lower().split(':')
index 43d8815..fdc93be 100644 (file)
@@ -27,23 +27,34 @@ import ninix.pix
 
 class Balloon:
 
-    def __init__(self, sakura, prefs, debug=0):
-        self.__sakura = sakura
-        self.prefs = prefs
+    def __init__(self, callback, debug=0):
+        self.callback = callback
         self.debug = debug
         self.synchronized = []
         self.__scalling = 0
         self.__scale = 100 # %
-        self.__use_pna = 0
+        self.__use_pna = False
         self.__alpha_channel = None
         self.user_interaction = False
         self.window = []
         # create communicatebox
-        self.communicatebox = CommunicateBox(self, sakura, debug)
+        communicatebox_callback = {
+            'reset_user_interaction': self.reset_user_interaction,
+            'notify_event':  self.callback['notify_event'],
+            }
+        self.communicatebox = CommunicateBox(communicatebox_callback, debug)
         # create teachbox
-        self.teachbox = TeachBox(self, sakura, debug)
+        teachbox_callback = {
+            'reset_user_interaction': self.reset_user_interaction,
+            'notify_user_teach':  self.callback['notify_user_teach'],
+            }
+        self.teachbox = TeachBox(teachbox_callback, debug)
         # create inputbox
-        self.inputbox = InputBox(self, sakura, debug)
+        inputbox_callback = {
+            'reset_user_interaction': self.reset_user_interaction,
+            'notify_event': self.callback['notify_event'],
+            }
+        self.inputbox = InputBox(inputbox_callback, debug)
 
     def reset_user_interaction(self):
         self.user_interaction = False
@@ -72,10 +83,7 @@ class Balloon:
             self.window[side].reset_text_count()
 
     def set_use_pna(self, flag):
-        if flag:
-            self.__use_pna = 1
-        else:
-            self.__use_pna = 0
+        self.__use_pna = bool(flag)
         for balloon_window in self.window:
             balloon_window.set_use_pna(flag)
             balloon_window.reset_balloon()
@@ -108,7 +116,7 @@ class Balloon:
         window.set_resizable(False)
         window.set_skip_pager_hint(False)
         window.set_skip_taskbar_hint(True)
-        window.font_name = self.prefs.get('balloon_fonts')
+        window.font_name = self.callback['get_preference']('balloon_fonts')
         window.connect('delete_event', self.delete)
         window.realize()
         return window
@@ -120,8 +128,7 @@ class Balloon:
         return False
 
     def delete(self, window, event):
-        self.__sakura.finalize()
-        return False
+        return True
 
     def finalize(self):
         for balloon_window in self.window:
@@ -157,21 +164,14 @@ class Balloon:
                 communicate2 = value # teach box
             elif key == 'c3':
                 communicate3 = value # input box
-        self.balloon1 = balloon1 ## FIXME
+        self.balloon0 = balloon0
+        self.balloon1 = balloon1
         # create balloon windows
         for balloon_window in self.window:
             balloon_window.destroy()
         self.window = []
-        for name, side, id_format, balloon in [('sakura', 0, 's%d', balloon0),
-                                               ('kero', 1, 'k%d', balloon1)]:
-            gtk_window = self.create_gtk_window(''.join(('balloon.', name)))
-            balloon_window = BalloonWindow(
-                gtk_window, side, self.__sakura, desc, balloon,
-                id_format, self.__use_pna, self.__alpha_channel, self.debug)
-            self.window.append(balloon_window)
-        for balloon_window in self.window:
-            balloon_window.set_scalling(self.__scalling)
-            balloon_window.set_scale(self.__scale)
+        self.add_window(0)
+        self.add_window(1)
         # configure communicatebox
         self.communicatebox.new(desc, communicate1)
         # configure teachbox
@@ -180,19 +180,32 @@ class Balloon:
         self.inputbox.new(desc, communicate3)
 
     def add_window(self, side):
-        assert side >= 2 and len(self.window) == side ## FIXME
-        gtk_window = self.create_gtk_window('balloon.char%d' % side)
-        id_format = 'k%d'
-        balloon = self.balloon1 ## FIXME
+        assert len(self.window) == side
+        if side == 0:
+            name = 'balloon.sakura'
+            id_format = 's%d'
+            balloon = self.balloon0
+        elif side == 1:
+            name = 'balloon.kero'
+            id_format = 'k%d'
+            balloon = self.balloon1
+        else:
+            name = 'balloon.char%d' % side
+            id_format = 'k%d'
+            balloon = self.balloon1
+        gtk_window = self.create_gtk_window(name)
+        balloon_window_callback = self.callback.copy() ## FIXME
         balloon_window = BalloonWindow(
-            gtk_window, side, self.__sakura, self.desc, balloon,
+            gtk_window, side, balloon_window_callback, self.desc, balloon,
             id_format, self.__use_pna, self.__alpha_channel, self.debug)
         self.window.append(balloon_window)
+        balloon_window.set_scalling(self.__scalling)
         balloon_window.set_scale(self.__scale)
-        balloon_window.set_balloon(0) ## FIXME
+        if side > 1:
+            balloon_window.set_balloon(0) ## FIXME
 
     def reset_fonts(self):
-        font_name = self.prefs.get('balloon_fonts')
+        font_name = self.callback['get_preference']('balloon_fonts')
         for window in self.window:
             window.window.font_name = font_name
             window.update_gc()
@@ -243,10 +256,7 @@ class Balloon:
 
     def is_shown(self, side):
         if len(self.window) > side:
-            if self.window[side].is_shown():
-                return 1
-            else:
-                return 0
+            return 1 if self.window[side].is_shown() else 0
         else:
             return 0
 
@@ -369,7 +379,7 @@ class Balloon:
     def open_teachbox(self):
         if not self.user_interaction:
             self.user_interaction = True
-            self.__sakura.notify_event('OnTeachStart')
+            self.callback['notify_event']('OnTeachStart')
             self.teachbox.show()
 
     def open_inputbox(self, symbol, limittime=-1, default=None):
@@ -381,11 +391,11 @@ class Balloon:
 
 class BalloonWindow:
 
-    def __init__(self, window, side, sakura, desc, balloon,
+    def __init__(self, window, side, callback, desc, balloon,
                  id_format, use_pna, alpha, debug):
         self.window = window
         self.side = side
-        self.__sakura = sakura
+        self.callback = callback
         self.desc = desc
         self.balloon = balloon
         self.balloon_id = None
@@ -422,24 +432,23 @@ class BalloonWindow:
                               gtk.gdk.POINTER_MOTION_MASK|
                               gtk.gdk.POINTER_MOTION_HINT_MASK|
                               gtk.gdk.SCROLL_MASK)
-        self.callbacks = []
         for signal, func in [('expose_event',        self.redraw),
                              ('button_press_event',  self.button_press),
                              ('motion_notify_event', self.motion_notify),
                              ('scroll_event',        self.scroll)]:
-            self.callbacks.append(self.darea.connect(signal, func))
+            self.darea.connect(signal, func)
         self.window.add(self.darea)
         self.darea.realize()
         self.darea.window.set_back_pixmap(None, False)
-        mask_r = desc.getint('maskcolor.r', 128)
-        mask_g = desc.getint('maskcolor.g', 128)
-        mask_b = desc.getint('maskcolor.b', 128)
+        mask_r = desc.get_with_type('maskcolor.r', int, 128)
+        mask_g = desc.get_with_type('maskcolor.g', int, 128)
+        mask_b = desc.get_with_type('maskcolor.b', int, 128)
         self.cursor_color = '#%02x%02x%02x' % (mask_r, mask_g, mask_b)
-        text_r = desc.getint(['font.color.r', 'fontcolor.r'], 0)
-        text_g = desc.getint(['font.color.g', 'fontcolor.g'], 0)
-        text_b = desc.getint(['font.color.b', 'fontcolor.b'], 0)
+        text_r = desc.get_with_type(['font.color.r', 'fontcolor.r'], int, 0)
+        text_g = desc.get_with_type(['font.color.g', 'fontcolor.g'], int, 0)
+        text_b = desc.get_with_type(['font.color.b', 'fontcolor.b'], int, 0)
         self.text_normal_color = '#%02x%02x%02x' % (text_r, text_g, text_b)
-        if desc.getint('maskmethod') == 1:
+        if desc.get_with_type('maskmethod', int) == 1:
             text_r = 255 - text_r
             text_g = 255 - text_g
             text_b = 255 - text_b
@@ -462,10 +471,7 @@ class BalloonWindow:
                                 (pixbuf.get_width(), pixbuf.get_height()))
 
     def set_use_pna(self, flag):
-        if flag:
-            self.__use_pna = 1
-        else:
-            self.__use_pna = 0
+        self.__use_pna = bool(flag)
         self.reset_pixbuf_cache()
 
     def set_alpha_channel(self, value):
@@ -475,10 +481,7 @@ class BalloonWindow:
         self.redraw()
 
     def get_scale(self):
-        if self.__scalling:
-            return self.__scale
-        else:
-            return 100 # %
+        return self.__scale if self.__scalling else 100 # %
 
     def set_scale(self, scale):
         self.__scale = scale # %
@@ -549,7 +552,7 @@ class BalloonWindow:
             self.sstp_gc = self.new_mask_gc(mask, x, y)
         # font
         default_size = 12 # for Windows environment
-        size = self.desc.getint(['font.height', 'font.size'], default_size)
+        size = self.desc.get_with_type(['font.height', 'font.size'], int, default_size)
         self.layout = pango.Layout(self.darea.get_pango_context())
         self.font_desc = pango.FontDescription(self.window.font_name)
         pango_size = self.font_desc.get_size()
@@ -566,7 +569,7 @@ class BalloonWindow:
         # font for sstp message
         if self.side == 0:
             default_size = 10 # for Windows environment
-            size = self.desc.getint('sstpmessage.font.height', default_size)
+            size = self.desc.get_with_type('sstpmessage.font.height', int, default_size)
             self.sstp_layout = pango.Layout(self.darea.get_pango_context())
             self.sstp_font_desc = pango.FontDescription(self.window.font_name)
             pango_size = self.sstp_font_desc.get_size()
@@ -581,24 +584,26 @@ class BalloonWindow:
         else:
             sstp_font_height = 0
         # font metrics
-        origin_x = self.config_getint(
-            'origin.x',
-            self.config_getint('zeropoint.x',
-                               self.config_getint('validrect.left', 14)))
-        origin_y = self.config_getint(
-            'origin.y',
-            self.config_getint('zeropoint.y',
-                               self.config_getint('validrect.top', 14)))
-        wpx = self.config_getint(
-            'wordwrappoint.x',
-            self.config_getint('validrect.right', -14))
+        origin_x = self.__get_with_scaling(
+            'origin.x', int,
+            self.__get_with_scaling(
+                'zeropoint.x', int,
+                self.__get_with_scaling('validrect.left', int, 14)))
+        origin_y = self.__get_with_scaling(
+            'origin.y', int,
+            self.__get_with_scaling(
+                'zeropoint.y', int,
+                self.__get_with_scaling('validrect.top', int, 14)))
+        wpx = self.__get_with_scaling(
+            'wordwrappoint.x', int, 
+            self.__get_with_scaling('validrect.right', int, -14))
         if wpx > 0:
             line_width = wpx - origin_x
         elif wpx < 0:
             line_width = self.width - origin_x + wpx
         else:
             line_width = self.width - origin_x * 2
-        wpy = self.config_getint('validrect.bottom', -14)
+        wpy = self.__get_with_scaling('validrect.bottom', int, -14)
         if wpy > 0:
             text_height = min(wpy, self.height) - origin_y
         elif wpy < 0:
@@ -621,11 +626,12 @@ class BalloonWindow:
             self.sstp_region = (x, y, w, h)
 
     def update_line_regions(self, offset, new_y):
-        origin_y = self.config_getint(
-            'origin.y',
-            self.config_getint('zeropoint.y',
-                               self.config_getint('validrect.top', 14)))
-        wpy = self.config_getint('validrect.bottom', -14)
+        origin_y = self.__get_with_scaling(
+            'origin.y', int, 
+            self.__get_with_scaling(
+                'zeropoint.y', int,
+                self.__get_with_scaling('validrect.top', int, 14)))
+        wpy = self.__get_with_scaling('validrect.bottom', int, -14)
         if wpy > 0:
             text_height = min(wpy, self.height) - origin_y
         elif wpy < 0:
@@ -680,7 +686,7 @@ class BalloonWindow:
         if self.__shown:
             self.update_gc()
         if reset_position:
-            self.__sakura.position_balloons()
+            self.callback['position_balloons']()
 
     def set_direction(self, dir):
         if self.direction != dir:
@@ -689,23 +695,23 @@ class BalloonWindow:
 
     def config_adjust(self, name, base, default_value):
         path, config = self.balloon[self.balloon_id]
-        value = config.getint(name)
+        value = config.get_with_type(name, int)
         if value is None:
-            value = self.desc.getint(name)
+            value = self.desc.get_with_type(name, int)
         if value is None:
             value = default_value
         if value < 0:
             value = base + value
         return int(value * self.get_scale() / 100)
 
-    def config_getint(self, name, default_value):
+    def __get_with_scaling(self, name, conv, default_value):
         path, config = self.balloon[self.balloon_id]
-        value = config.getint(name)
+        value = config.get_with_type(name, conv)
         if value is None:
-            value = self.desc.getint(name)
+            value = self.desc.get_with_type(name, conv)
             if value is None:
                 value = default_value
-        return int(value * self.get_scale() / 100)
+        return conv(value * self.get_scale() / 100)
 
     def __move(self):
         x, y = self.get_position()
@@ -720,32 +726,29 @@ class BalloonWindow:
         return self.position
 
     def destroy(self, finalize=0):
-        for tag in self.callbacks:
-            self.darea.disconnect(tag)
         self.window.remove(self.darea)
         self.darea.destroy()
         self.window.destroy()
 
     def is_shown(self):
-        if self.__shown:
-            return 1
-        else:
-            return 0
+        return 1 if self.__shown else 0
 
     def show(self):
-        if not self.__shown:
-            self.window.show()
-            self.__shown = 1
-            # make sure window is in its position
-            self.__move()
-            self.update_gc()
-            self.raise_()
+        if self.__shown:
+            return
+        self.window.show()
+        self.__shown = 1
+        # make sure window is in its position
+        self.__move()
+        self.update_gc()
+        self.raise_()
 
     def hide(self):
-        if self.__shown:
-            self.window.hide()
-            self.__shown = 0
-            self.images = []
+        if not self.__shown:
+            return
+        self.window.hide()
+        self.__shown = 0
+        self.images = []
 
     def raise_(self):
         if self.__shown:
@@ -814,7 +817,7 @@ class BalloonWindow:
         x, y = self.arrow[1]
         w, h = self.arrow1_pixbuf[2]
         if self.lineno + self.lines < len(self.text_buffer) or \
-           self.__sakura.is_paused():
+           self.callback['is_paused']():
             self.darea.window.draw_pixbuf(
                 self.arrow1_gc, self.arrow1_pixbuf[0], 0, 0, x, y, w, h)
         elif clear:
@@ -1039,11 +1042,11 @@ class BalloonWindow:
             if self.selection != new_selection:
                 sl, sn, el, en, link_id, raw_text, text = \
                     self.link_buffer[new_selection]
-                self.__sakura.notify_event(
+                self.callback['notify_event'](
                     'OnChoiceEnter', raw_text, link_id, self.selection)
         else:
             if self.selection is not None:
-                self.__sakura.notify_event('OnChoiceEnter')
+                self.callback['notify_event']('OnChoiceEnter')
         if new_selection == self.selection:
             return 0
         else:
@@ -1083,13 +1086,10 @@ class BalloonWindow:
         return True
 
     def button_press(self, darea, event):
-        self.__sakura.reset_idle_time()
-        if event.type == gtk.gdk.BUTTON_PRESS:
-            click = 1
-        else:
-            click = 2
-        if self.__sakura.is_paused():
-            self.__sakura.notify_balloon_click(event.button, click, self.side)
+        self.callback['reset_idle_time']()
+        click = 1 if event.type == gtk.gdk.BUTTON_PRESS else 2
+        if self.callback['is_paused']():
+            self.callback['notify_balloon_click'](event.button, click, self.side)
             return True
         # arrows
         px = int(event.x)
@@ -1115,11 +1115,11 @@ class BalloonWindow:
         if self.selection is not None:
             sl, sn, el, en, link_id, raw_text, text = \
                 self.link_buffer[self.selection]
-            self.__sakura.notify_link_selection(
+            self.callback['notify_link_selection'](
                 link_id, raw_text, self.selection)
             return True
         # balloon's background
-        self.__sakura.notify_balloon_click(event.button, click, self.side)
+        self.callback['notify_balloon_click'](event.button, click, self.side)
         return True
 
     def clear_text(self):
@@ -1298,9 +1298,8 @@ class CommunicateWindow:
     NAME = ''
     ENTRY = ''
 
-    def __init__(self, balloon, sakura, debug):
-        self.balloon = balloon
-        self.sakura = sakura
+    def __init__(self, callback, debug):
+        self.callback = callback
         self.debug = debug
         self.window = None
 
@@ -1318,8 +1317,8 @@ class CommunicateWindow:
         self.window.set_modal(True)
         self.window.set_position(gtk.WIN_POS_CENTER)
         self.window.realize()
-        w = desc.getint('communicatebox.width', 250)
-        h = desc.getint('communicatebox.height', -1)
+        w = desc.get_with_type('communicatebox.width', int, 250)
+        h = desc.get_with_type('communicatebox.height', int, -1)
         self.entry = gtk.Entry()
         self.entry.connect('activate', self.activate)
         self.entry.set_size_request(w, h)
@@ -1338,8 +1337,8 @@ class CommunicateWindow:
             gtk_image.set_padding(0, 0)
             gtk_image.set_from_pixmap(image, mask)
             gtk_image.show()
-            x = desc.getint('communicatebox.x', 10)
-            y = desc.getint('communicatebox.y', 20)
+            x = desc.get_with_type('communicatebox.x', int, 10)
+            y = desc.get_with_type('communicatebox.y', int, 20)
             fixed = gtk.Fixed()
             fixed.put(gtk_image, 0, 0)
             fixed.put(self.entry, x, y)
@@ -1408,14 +1407,14 @@ class CommunicateBox(CommunicateWindow):
     def delete(self, widget, event):
         self.window.hide()
         self.cancel()
-        self.balloon.reset_user_interaction()
+        self.callback['reset_user_interaction']()
         return True
 
     def key_press(self, widget, event):
         if event.keyval == gtk.keysyms.Escape:
             self.window.hide()
             self.cancel()
-            self.balloon.reset_user_interaction()
+            self.callback['reset_user_interaction']()
             return True
         return False
 
@@ -1434,7 +1433,7 @@ class CommunicateBox(CommunicateWindow):
         if data:
             data = unicode(data, 'utf-8')
         if data is not None:
-            self.sakura.notify_event('OnCommunicate', 'user', data)
+            self.callback['notify_event']('OnCommunicate', 'user', data)
 
 class TeachBox(CommunicateWindow):
 
@@ -1450,8 +1449,8 @@ class TeachBox(CommunicateWindow):
     def send(self, data):
         if data:
             data = unicode(data, 'utf-8')
-        self.sakura.notify_user_teach(data)
-        self.balloon.reset_user_interaction()
+        self.callback['notify_user_teach'](data)
+        self.callback['reset_user_interaction']()
 
 class InputBox(CommunicateWindow):
 
@@ -1506,13 +1505,13 @@ class InputBox(CommunicateWindow):
             data = ''
         ## CHECK: symbol
         if self.symbol == 'OnUserInput' and \
-           self.sakura.notify_event('OnUserInput', data):
+           self.callback['notify_event']('OnUserInput', data):
             pass
-        elif self.sakura.notify_event('OnUserInput', self.symbol, data):
+        elif self.callback['notify_event']('OnUserInput', self.symbol, data):
             pass
-        elif self.sakura.notify_event(self.symbol, data):
+        elif self.callback['notify_event'](self.symbol, data):
             pass
-        self.balloon.reset_user_interaction()
+        self.callback['reset_user_interaction']()
 
 
 def test():
index 7c73cd4..c2e3df6 100644 (file)
@@ -15,6 +15,16 @@ import codecs
 
 class Config(dict):
 
+    def get_with_type(self, name, conv, default=None):
+        value = self.get(name)
+        if value is None:
+            return default
+        ##assert conv is not None
+        try:
+            return conv(value)
+        except ValueError:
+            return default # XXX
+
     def get(self, name, default=None):
         keylist = name if isinstance(name, list) else [name]
         for key in keylist:
@@ -22,24 +32,6 @@ class Config(dict):
                 return self[key]
         return default
 
-    def getint(self, name, default=None):
-        value = self.get(name)
-        if value:
-            try:
-                return int(value)
-            except ValueError:
-                pass
-        return default
-
-    def getfloat(self, name, default=None):
-        value = self.get(name)
-        if value:
-            try:
-                return float(value)
-            except ValueError:
-                pass
-        return default
-
     def __str__(self):
         buf = []
         for item in self.iteritems():
index 6ad3014..b7b163e 100644 (file)
@@ -21,6 +21,11 @@ import codecs
 
 class SAORI:
 
+    RESPONSE = {204: 'SAORI/1.0 204 No Content\r\n\r\n',
+                400: 'SAORI/1.0 400 Bad Request\r\n\r\n',
+                500: 'SAORI/1.0 500 Internal Server Error\r\n\r\n',
+                }
+
     def __init__(self):
         self.loaded = 0
 
@@ -56,17 +61,14 @@ class SAORI:
     def request(self, req):
         req_type, argument = self.evaluate_request(req)
         if not req_type:
-            return 'SAORI/1.0 400 Bad Request\r\n\r\n'
+            return self.RESPONSE[400]
         elif req_type == 'GET Version':
-            return 'SAORI/1.0 204 No Content\r\n\r\n'
+            return self.RESPONSE[204]
         elif req_type == 'EXECUTE':
             result = self.execute(argument)
-            if result is None:
-                return 'SAORI/1.0 204 No Content\r\n\r\n'
-            else:
-                return result
+            return self.RESPONSE[204] if result is None else result
         else:
-            return 'SAORI/1.0 400 Bad Request\r\n\r\n'
+            return self.RESPONSE[400]
 
     def execute(self, args):
         return None
@@ -134,7 +136,6 @@ class Library:
             if self.__type == 'saori':
                 if getattr(module, 'Saori', None):
                     saori = module.Saori()
-                    saori.prefs = self.__sakura.prefs
                     if getattr(saori, 'need_ghost_backdoor', None):
                         saori.need_ghost_backdoor(self.__sakura)
                 else:
index 28b81fa..9e64395 100644 (file)
@@ -98,7 +98,7 @@ class Saori(SAORI):
 
     def execute(self, argument):
         if not argument:
-            return 'SAORI/1.0 400 Bad Request\r\n\r\n'
+            return self.RESPONSE[400]
         name = argument[0]
         text = argument[1] if len(argument) >= 2 else ''
         if len(argument) >= 3:
@@ -127,7 +127,7 @@ class Saori(SAORI):
                 del bln[bln_id]
             if text:
                 if update == 0 or bln_id not in bln:
-                    bln[bln_id] = Balloon(self.__sakura, self.prefs, self.dir,
+                    bln[bln_id] = Balloon(self.__sakura, self.dir,
                                           data, text,
                                           offset_x, offset_y, name, bln_id)
                 else:
@@ -145,11 +145,10 @@ class Saori(SAORI):
 
 class Balloon:
 
-    def __init__(self, sakura, prefs, dir, data,
+    def __init__(self, sakura, dir, data,
                  text, offset_x, offset_y, name, bln_id):
         self.dir = dir
         self.__sakura = sakura
-        self.prefs = prefs
         self.name = name
         self.id = bln_id
         self.data = data # XXX
@@ -405,8 +404,8 @@ class Balloon:
         return talking
 
     def get_sakura_status(self, key):
-        if key == 'SurfaceScale': ## FIXME
-            result = self.prefs.get('surface_scale')
+        if key == 'SurfaceScale':
+            result = self.__sakura.get_surface_scale()
         elif key == 'SurfaceSakura_Shown':
             result = int(self.__sakura.surface_is_shown(0))
         elif key == 'SurfaceSakura':
@@ -427,11 +426,6 @@ class Balloon:
                 s1_x, s1_y = 0, 0
                 s1_w, s1_h = 0, 0
             result = s1_x, s1_y, s1_w, s1_h
-        elif key == 'BalloonScale': ## FIXME
-            if self.prefs.get('balloon_scalling'):
-                result = self.prefs.get('surface_scale')
-            else:
-                result = 100
         elif key == 'BalloonSakura_Shown':
             result = int(self.__sakura.balloon_is_shown(0))
         elif key == 'BalloonSakura':
@@ -477,20 +471,11 @@ class Balloon:
                     self.life_time = 300000
         else:
             if self.visible:
-                if self.position == 'sakura' and not s0_shown:
-                    self.destroy()
-                    return None
-                if self.position == 'kero' and not s1_shown:
-                    self.destroy()
-                    return None
-                if self.position == 'sakurab' and not b0_shown:
-                    self.destroy()
-                    return None
-                if self.position == 'kerob' and not b1_shown:
-                    self.destroy()
-                    return None
-                if self.nooverlap and \
-                   not self.talking and sakura_talking:
+                if (self.position == 'sakura' and not s0_shown) or \
+                        (self.position == 'kero' and not s1_shown) or \
+                        (self.position == 'sakurab' and not b0_shown) or \
+                        (self.position == 'kerob' and not b1_shown) or \
+                        (self.nooverlap and not self.talking and sakura_talking):
                     self.destroy()
                     return None
             else:
@@ -536,10 +521,7 @@ class Balloon:
                              int(self.y + self.action_y + self.vy))
             if self.processed_script or self.processed_text:
                 self.interpret_script()
-        if self.talking and not sakura_talking:
-            self.talking = 0
-        else:
-            self.talking = sakura_talking
+        self.talking = 0 if self.talking and not sakura_talking else sakura_talking
         return True
 
     def redraw(self, widget, event):
index 838f2a9..7b906ec 100644 (file)
@@ -185,7 +185,7 @@ class Saori(SAORI):
 
     def execute(self, argument):
         if not argument:
-            return 'SAORI/1.0 400 Bad Request\r\n\r\n'
+            return self.RESPONSE[400]
         command = argument[0]
         if command == 'show':
             if len(argument) >= 2:
@@ -195,7 +195,7 @@ class Saori(SAORI):
             else:
                 name = ''
             if name not in self.data:
-                return 'SAORI/1.0 400 Bad Request\r\n\r\n'
+                return self.RESPONSE[400]
             if 'graph' in self.data[name] and \
                    self.data[name]['graph'] in ['line', 'bar',
                                                 'radar', 'radar2']:
@@ -234,10 +234,10 @@ class Saori(SAORI):
             if name in self.graphs:
                 self.graphs[name].destroy()
             else:
-                return 'SAORI/1.0 400 Bad Request\r\n\r\n'
+                return self.RESPONSE[400]
         else:
-            return 'SAORI/1.0 400 Bad Request\r\n\r\n'
-        return 'SAORI/1.0 204 No Content\r\n\r\n'
+            return self.RESPONSE[400]
+        return self.RESPONSE[204]
 
 class Graph:
 
index f32a200..497af06 100644 (file)
@@ -43,10 +43,12 @@ class Saori(SAORI):
     def get(self, url, start=None, end=None):
         url = urlparse.urlparse(url)
         if not (url[0] == 'http' and url[3] == url[4] == url[5] == ''):
-            return 'SAORI/1.0 400 Bad Request\r\n\r\n'
+            return self.RESPONSE[400]
         conn = httplib.HTTPConnection(url[1])
         conn.request("GET", url[2])
         res = conn.getresponse()
+        if res.status != 200: # XXX
+            return []
         data = res.read()
         c_type = res.getheader('content-type')
         if c_type:
@@ -97,7 +99,7 @@ class Saori(SAORI):
             if argument[0] == 'bg':
                 if len(argument) < 2:
                     # 'bgするならIDを指定していただけませんと。'
-                    return 'SAORI/1.0 400 Bad Request\r\n\r\n'
+                    return self.RESPONSE[400]
                 bg = argument[1]
                 argument = argument[2:]
         if len(argument) >= 1:
@@ -125,7 +127,7 @@ class Saori(SAORI):
             return 'SAORI/1.0 200 OK\r\n' \
                 'Result: %s\r\n\r\n' % self.loaded # XXX
         elif len(argument) > 3:
-            return 'SAORI/1.0 400 Bad Request\r\n\r\n'
+            return self.RESPONSE[400]
         elif len(argument) == 2: # FIXME: not supported yet
             return 'SAORI/1.0 200 OK\r\n' \
                 'Result: 0\r\n\r\n'
@@ -138,6 +140,8 @@ class Saori(SAORI):
                 return None # 'SAORI/1.0 204 No Content\r\n\r\n'
             else:
                 data = self.get(*argument)
+                if not data:
+                    return None # 'SAORI/1.0 204 No Content\r\n\r\n'
                 result = 'SAORI/1.0 200 OK\r\n' \
                     'Result: %s\r\n' % \
                     data[0].encode('Shift_JIS', 'replace')
index 82530d1..1211d18 100644 (file)
@@ -60,11 +60,11 @@ class Saori(SAORI):
                 if self.state == 2:
                     self.state = 1
                     self.player.set_state(gst.STATE_PLAYING)
-                    return 'SAORI/1.0 204 No Content\r\n\r\n'
+                    return self.RESPONSE[204]
                 elif self.state == 1:
                     self.state = 2
                     self.player.set_state(gst.STATE_PAUSED)
-                    return 'SAORI/1.0 204 No Content\r\n\r\n'
+                    return self.RESPONSE[204]
                 if os.path.isfile(self.filepath):
                     self.player.set_property("uri", "file://" + self.filepath)
                     self.player.set_state(gst.STATE_PLAYING)
@@ -73,11 +73,11 @@ class Saori(SAORI):
                 self.player.set_state(gst.STATE_NULL)
                 filename = argv[1].replace('\\', '/').lower()
                 if os.path.isabs(filename):
-                    return 'SAORI/1.0 400 Bad Request\r\n\r\n'
+                    return self.RESPONSE[400]
                 self.filepath = os.path.join(self.__sakura.get_prefix(),
                                              'ghost/master', self.dir,
                                              filename)
-        return 'SAORI/1.0 204 No Content\r\n\r\n'
+        return self.RESPONSE[204]
 
     def on_message(self, bus, message):
         t = message.type
index cb7987b..c6b9ffa 100644 (file)
@@ -53,11 +53,11 @@ class Saori(SAORI):
                 if self.state == 2:
                     self.state = 1
                     self.player.set_state(gst.STATE_PLAYING)
-                    return 'SAORI/1.0 204 No Content\r\n\r\n'
+                    return self.RESPONSE[204]
                 elif self.state == 1:
                     self.state = 2
                     self.player.set_state(gst.STATE_PAUSED)
-                    return 'SAORI/1.0 204 No Content\r\n\r\n'
+                    return self.RESPONSE[204]
                 if os.path.isfile(self.filepath):
                     self.player.set_property("uri", "file://" + self.filepath)
                     self.player.set_state(gst.STATE_PLAYING)
@@ -68,7 +68,7 @@ class Saori(SAORI):
                 if not os.path.isabs(filename): # XXX
                     filename = filename.lower()
                 self.filepath = os.path.join(self.dir, filename)
-        return 'SAORI/1.0 204 No Content\r\n\r\n'
+        return self.RESPONSE[204]
 
     def on_message(self, bus, message):
         t = message.type
index caece00..0862038 100755 (executable)
@@ -40,10 +40,10 @@ class Saori(SAORI):
 
     def execute(self, argument):
         if not argument:
-            return 'SAORI/1.0 400 Bad Request\r\n\r\n'
+            return self.RESPONSE[400]
         if argument[0] == 'START':
             if len(argument) < 7:
-                return 'SAORI/1.0 400 Bad Request\r\n\r\n'
+                return self.RESPONSE[400]
             try:
                 assert argument[2] in ['ACTIVE', 'FIX'] or \
                        argument[2].stratswith('@') or \
@@ -73,18 +73,18 @@ class Saori(SAORI):
                     assert position in ['TOP', 'LEFT', 'RIGHT', 'BOTTOM']
                     self.settings['except'] = (target, position)
             except:
-                return 'SAORI/1.0 400 Bad Request\r\n\r\n'
+                return self.RESPONSE[400]
             #self.timeout_id = gobject.timeout_add(self.settings['timeout'], self.do_idle_tasks)
             self.timeout_id = gobject.timeout_add(100, self.do_idle_tasks)
-            return 'SAORI/1.0 204 No Content\r\n\r\n'
+            return self.RESPONSE[204]
         elif argument[0] == 'STOP':
             if self.timeout_id is not None:
                 gobject.source_remove(self.timeout_id)
                 self.timeout_id = None
                 self.settings = {}
-            return 'SAORI/1.0 204 No Content\r\n\r\n'
+            return self.RESPONSE[204]
         else:
-            return 'SAORI/1.0 400 Bad Request\r\n\r\n'
+            return self.RESPONSE[400]
 
     def do_idle_tasks(self):
         if self.timeout_id is None:
@@ -128,7 +128,7 @@ class Saori(SAORI):
         if not target_flag[0]:
             return target_flag[1]
         pos = self.settings['position']
-        scale = self.prefs.get('surface_scale')
+        scale = self.__sakura.get_surface_scale()
         offset_x = int(self.settings['offset_x'] * scale / 100)
         offset_y = int(self.settings['offset_y'] * scale / 100)
         if self.settings['hwnd'].startswith('s'):
index 91936f9..c12b0bb 100644 (file)
@@ -45,9 +45,9 @@ class Saori(SAORI):
 
     def execute(self, argument):
         if not argument:
-            return 'SAORI/1.0 400 Bad Request\r\n\r\n'
+            return self.RESPONSE[400]
         if len(argument) > 1 and argument[1] == 0:
-            return 'SAORI/1.0 204 No Content\r\n\r\n'
+            return self.RESPONSE[204]
         value = ''
         if argument[0] == 'platform':
             value = sys.platform ## os.name
@@ -62,4 +62,4 @@ class Saori(SAORI):
         if value:
             return 'SAORI/1.0 200 OK\r\nResult: %s\r\n\r\n' % value
         else:
-            return 'SAORI/1.0 204 No Content\r\n\r\n'
+            return self.RESPONSE[204]
index 571a2bb..2b14bc6 100644 (file)
@@ -56,18 +56,18 @@ class Saori(SAORI):
     def request(self, req):
         req_type, argument, charset = self.evaluate_request(req)
         if not req_type:
-            return 'SAORI/1.0 400 Bad Request\r\n\r\n'
+            return self.RESPONSE[400]
         elif req_type == 'GET Version':
-            return 'SAORI/1.0 204 No Content\r\n\r\n'
+            return self.RESPONSE[204]
         elif req_type == 'EXECUTE':
             if argument[0] not in self.function:
-                return 'SAORI/1.0 400 Bad Request\r\n\r\n'
+                return self.RESPONSE[400]
             name = argument[0]
             argument = argument[1:]
             if self.function[name][1] == [None]:
                 pass
             elif len(argument) not in self.function[name][1]:
-                return 'SAORI/1.0 400 Bad Request\r\n\r\n'
+                return self.RESPONSE[400]
             self.value = []
             result = self.function[name][0](argument, charset)
             if result is not None and result != '':
@@ -80,9 +80,9 @@ class Saori(SAORI):
                 s = ''.join((s, '\r\n'))
                 return s
             else:
-                return 'SAORI/1.0 204 No Content\r\n\r\n'
+                return self.RESPONSE[204]
         else:
-            return 'SAORI/1.0 400 Bad Request\r\n\r\n'
+            return self.RESPONSE[400]
 
     def ssu_is_empty(self, args, charset):
         if not args:
index a76e153..4971bd5 100644 (file)
@@ -41,7 +41,7 @@ class Saori(SAORI):
 
     def execute(self, argument):
         if not argument or self.clipboard is None:
-            return 'SAORI/1.0 400 Bad Request\r\n\r\n'
+            return self.RESPONSE[400]
         text = argument[0].encode('utf-8')
         self.clipboard.set_text(text)
         if len(argument) >= 2 and argument[1] != 0:
@@ -49,4 +49,4 @@ class Saori(SAORI):
                    'Result: %s\r\n\r\n' % \
                    argument[0].encode(self.charset, 'replace')
         else:
-            return 'SAORI/1.0 204 No Content\r\n\r\n'
+            return self.RESPONSE[204]
index 895e00c..5100643 100755 (executable)
@@ -97,14 +97,14 @@ class Saori(SAORI):
         req_type, argument = self.evaluate_request(req)
 
         if not req_type:
-            result = 'SAORI/1.0 400 Bad Request\r\n\r\n'
+            result = self.RESPONSE[400]
         elif req_type == 'GET Version':
-            result = 'SAORI/1.0 204 No Content\r\n\r\n'
+            result = self.RESPONSE[204]
         elif req_type == 'EXECUTE':
             if not argument:
-                result = 'SAORI/1.0 400 Bad Request\r\n\r\n'
+                result = self.RESPONSE[400]
             elif not self.__check_argument(argument):
-                result = 'SAORI/1.0 400 Bad Request\r\n\r\n'
+                result = self.RESPONSE[400]
             else:
                 name = argument[0]
                 if name == 'GET_POSITION':
@@ -113,7 +113,7 @@ class Saori(SAORI):
                     elif argument[1] == self.kero_name: ## FIXME: HWND
                         side = 1
                     else:
-                        return 'SAORI/1.0 400 Bad Request\r\n\r\n'
+                        return self.RESPONSE[400]
                     try:
                         x, y = self.__sakura.get_surface_position(side)
                         w, h = self.__sakura.get_surface_size(side)
@@ -123,7 +123,7 @@ class Saori(SAORI):
                             'Value1: %d\r\n' \
                             'Value2: %d\r\n\r\n' % (x, x, x + w / 2, x + w)
                     except:
-                        result = 'SAORI/1.0 500 Internal Server Error\r\n\r\n'
+                        result = self.RESPONSE[500]
                 elif name == 'GET_DESKTOP_SIZE':
                    try:
                         left, top, scrn_w, scrn_h = ninix.pix.get_workarea()
@@ -132,14 +132,14 @@ class Saori(SAORI):
                             'Value0: %d\r\n' \
                             'Value1: %d\r\n\r\n' % (scrn_w, scrn_w, scrn_h)
                    except:
-                        result = 'SAORI/1.0 500 Internal Server Error\r\n\r\n'
+                        result = self.RESPONSE[500]
                 else:
                     self.enqueue_commands(name, argument[1:])
                     if self.timeout_id is None:
                         self.do_idle_tasks()
-                    result = 'SAORI/1.0 204 No Content\r\n\r\n'
+                    result = self.RESPONSE[204]
         else:
-            result = 'SAORI/1.0 400 Bad Request\r\n\r\n'
+            result = self.RESPONSE[400]
         return result
 
     def enqueue_commands(self, command, args):
index 5cf7098..6ffbc20 100644 (file)
@@ -28,8 +28,8 @@ import ninix.seriko
 
 class Menu:
 
-    def __init__(self, kinoko, accelgroup):
-        self.__kinoko = kinoko
+    def __init__(self, callback, accelgroup):
+        self.callback = callback
         ui_info = '''
         <ui>
           <popup name='popup'>
@@ -43,12 +43,12 @@ class Menu:
         '''
         self.__menu_list = {
             'settings': [('Settings', None, ('Settings...(_O)'), None,
-                          '', self.__kinoko.edit_preferences),
+                          '', self.callback['edit_preferences']),
                          '/ui/popup/Settings'],
             'skin':     [('Skin', None, _('Skin(_K)'), None),
                          None, '/ui/popup/Skin'],
             'exit':     [('Exit', None, _('Exit(_Q)'), None,
-                          '', self.__kinoko.close),
+                          '', self.callback['close']),
                          '/ui/popup/Exit'],
             }
         self.__skin_list = None
@@ -66,7 +66,7 @@ class Menu:
             self.__menu_list[key][1] = ui_manager.get_widget(path)
 
     def popup(self, button):
-        skin_list = self.__kinoko.get_skin_list()
+        skin_list = self.callback['get_skin_list']()
         self.__set_skin_menu(skin_list)
         self.__popup_menu.popup(
             None, None, None, button, gtk.get_current_event_time())
@@ -77,7 +77,7 @@ class Menu:
             menu = gtk.Menu()
             for skin in list:
                 item = gtk.MenuItem(skin['title'])
-                item.connect('activate', self.__kinoko.select_skin, (skin))
+                item.connect('activate', self.callback['select_skin'], (skin))
                 menu.add(item)
                 item.show()
             self.__menu_list[key][1].set_submenu(menu)
@@ -86,10 +86,12 @@ class Menu:
         else:
             self.__menu_list[key][1].hide()
 
+
 class Nayuki:
 
-    def __init__(self, kinoko):
-        self.kinoko = kinoko
+    def __init__(self):
+        pass
+
 
 class Kinoko:
 
@@ -112,8 +114,8 @@ class Kinoko:
         if event in ['set position', 'set surface']:
             self.skin.set_position()
             self.skin.show()
-        elif event == 'set scale': ## FIXME
-            scale = self.target.prefs.get('surface_scale')
+        elif event == 'set scale':
+            scale = self.target.get_surface_scale()
             self.skin.set_scale(scale)
         elif event == 'hide':
             if not self.target.surface_is_shown(0):
@@ -136,13 +138,24 @@ class Kinoko:
             ##print 'OBSERVER(kinoko): ignore -', event
             pass
 
+    def load_skin(self):
+        scale = self.target.get_surface_scale()
+        skin_callback ={
+            'edit_preferences': self.edit_preferences,
+            'close': self.close,
+            'get_skin_list': self.get_skin_list,
+            'select_skin': self.select_skin,
+            'get_target_window': lambda *a: self.target.surface.window[0].window, # XXX
+            'get_kinoko_position': self.target.get_kinoko_position,
+            }
+        self.skin = Skin(self.data, self.accelgroup, skin_callback, scale)
+
     def load(self, data, target):
         self.data = data
         self.target = target
         self.target.set_observer(self)
         self.accelgroup = gtk.AccelGroup()
-        scale = self.target.prefs.get('surface_scale')
-        self.skin = Skin(self.data, self.accelgroup, self, scale)
+        self.load_skin()
         if self.skin is None:
             return 0
         else:
@@ -179,22 +192,26 @@ class Kinoko:
         self.send_event('OnKinokoObjectChanging')
         self.skin.destroy()
         self.data = args
-        scale = self.target.prefs.get('surface_scale')
-        self.skin = Skin(self.data, self.accelgroup, self, scale)
+        self.load_skin()
         if self.skin is None:
             return 0
         else:
             self.send_event('OnKinokoObjectChanged')
         return 1
 
+
 class Skin:
 
-    def __init__(self, data, accelgroup, kinoko, scale):
+    def __init__(self, data, accelgroup, callback, scale):
         self.frame_buffer = []
         self.data = data
         self.accelgroup = accelgroup
-        self.kinoko = kinoko
-        self.__menu = Menu(self.kinoko, self.accelgroup)
+        self.callback = callback
+        menu_callback ={}
+        for key in ['edit_preferences', 'close',
+                    'get_skin_list', 'select_skin']:
+            menu_callback[key] = self.callback[key]
+        self.__menu = Menu(menu_callback, self.accelgroup)
         self.__scale = scale
         self.__shown = 0
         self.surface_id = 0 # dummy
@@ -235,7 +252,7 @@ class Skin:
                 w, h, gtk.gdk.INTERP_BILINEAR)
             image, mask = surface_pixbuf.render_pixmap_and_mask(255)
         except: ## FIXME
-            self.kinoko.close(None)
+            self.callback['close'](None)
             return
         ## FIXME
         self.w, self.h = image.get_size()
@@ -249,7 +266,7 @@ class Skin:
         self.window.add(self.darea)
         self.darea.realize()
         self.darea.window.set_back_pixmap(image, False)
-        target_window = self.kinoko.target.surface.window[0].window # XXX
+        target_window = self.callback['get_target_window']()
         if self.data['ontop']:
             self.window.set_transient_for(target_window)
         else:
@@ -276,7 +293,7 @@ class Skin:
         self.seriko.append_actor(frame, actor)
 
     def set_position(self, xoffset=0, yoffset=0):
-        base_x, base_y = self.kinoko.target.get_kinoko_position(
+        base_x, base_y = self.callback['get_kinoko_position'](
             self.data['baseposition'])
         a, b = [(0.5, 1), (0.5, 0), (0, 0.5), (1, 0.5), (0, 1),
                 (1, 1), (0, 0), (1, 0), (0.5, 0.5)][self.data['baseadjust']]
@@ -392,7 +409,7 @@ class Skin:
         self.seriko.invoke(self, actor_id, update)
 
     def delete(self, widget, event):
-        self.kinoko.close(None)
+        self.callback['close'](None)
 
     def destroy(self): ## FIXME
         self.seriko.terminate(self)
index 6537554..07e39ba 100755 (executable)
@@ -46,6 +46,7 @@ import ninix.sakura
 import ninix.sstp
 import ninix.communicate
 import ninix.ngm
+import ninix.plugin
 
 import ninix.nekodorif
 import ninix.kinoko
@@ -115,7 +116,7 @@ def main():
         raise SystemExit, 'Error: cannot open display (abort)\n'
     # parse command line arguments
     try:
-        options, rest = getopt.getopt(sys.argv[1:], 'H:U:R:ph',
+        options, rest = getopt.getopt(sys.argv[1:], 'H:U:h',
             ['homedir=', 'userdir=', 'sstp-port=', 'debug=', 'help'])
     except getopt.error, e:
         sys.stderr.write('Error: %s\n' % str(e))
@@ -222,26 +223,15 @@ def main():
     lockfile.close()
 
 
+class SSTPControler:
 
-class Application:
-
-    def __init__(self, config, sstp_port=[9801, 11000], debug=0):
-        self.ghosts, self.shells, self.balloons, self.plugins, \
-                     self.nekoninni, self.katochan, self.kinoko = config
+    def __init__(self, callback, sstp_port):
+        self.callback = callback
         self.sstp_port = sstp_port
-        self.debug = debug
-        self.plugin_pids = []
         self.sstp_servers = []
         self.__sstp_queue = []
         self.__sstp_flag = 0
         self.__current_sender = None
-        # create preference dialog
-        self.prefs = ninix.prefs.PreferenceDialog(self)
-        # create usage dialog
-        self.usage_dialog = UsageDialog()
-        self.__communicate = ninix.communicate.Communicate()
-        # create ghost manager
-        self.__ngm = ninix.ngm.NGM(self)
 
     def enqueue_script_if_ghost(self, if_ghost, script, sender, handle,
                                 address, show_sstp_marker,
@@ -250,6 +240,16 @@ class Application:
             (if_ghost, script, sender, handle, address, show_sstp_marker,
              use_translator, entry_db))
 
+    def check_request_queue(self, sender):
+        count = 0
+        for request in self.__sstp_queue:
+            if request[2].split(' / ')[0] == sender.split(' / ')[0]:
+                count += 1
+        if self.__sstp_flag and \
+           self.__current_sender.split(' / ')[0] == sender.split(' / ')[0]:
+            count += 1
+        return str(count), str(len(self.__sstp_queue))
+
     def set_sstp_flag(self, sender):
         self.__sstp_flag = 1
         self.__current_sender = sender
@@ -261,20 +261,16 @@ class Application:
     def handle_sstp_queue(self):
         if self.__sstp_flag or not self.__sstp_queue:
             return
-        if_ghost = self.__sstp_queue[0][0]
-        sakura = self.get_ghost(if_ghost)
-        if sakura is not None:
-            self.select_current_sakura(if_ghost)
-        elif not self.prefs.get('allowembryo'): # Refuse
-            self.__sstp_queue.pop(0)
-            return
         if_ghost, script, sender, handle, address, show_sstp_marker, \
                   use_translator, entry_db = self.__sstp_queue.pop(0)
+        if not self.callback['if_ghost'](if_ghost) and \
+                not self.callback['get_preference']('allowembryo'): # Refuse
+            return
+        self.callback['select_current_sakura'](if_ghost)
         self.set_sstp_flag(sender)
-        sakura = self.ghost_list[self.current_sakura[1]]['instance']
-        sakura.enter_temp_mode()
-        sakura.enqueue_script(script, sender, handle, address,
-                              show_sstp_marker, use_translator, entry_db)
+        self.callback['enqueue_script'](
+            script, sender, handle, address,
+            show_sstp_marker, use_translator, entry_db, temp_mode=True)
 
     def receive_sstp_request(self):
         try:
@@ -285,10 +281,152 @@ class Application:
         except ValueError: # may happen when ninix is terminated
             return
 
+    def get_sstp_port(self):
+        if not self.sstp_servers:
+            return None
+        return self.sstp_servers[0].server_address[1]
+
+    def quit(self):
+        for server in self.sstp_servers:
+            server.close()
+
+    def start_servers(self):
+        for port in self.sstp_port:
+            sstp_callback = {
+                'enqueue_script_if_ghost': self.enqueue_script_if_ghost,
+                'check_request_queue': self.check_request_queue,
+                }
+            for key in ['get_sakura_cantalk', 'get_event_response',
+                        'if_ghost', 'keep_silence',
+                        'get_ghost_names', 'get_ghost_name',
+                        'enqueue_event', 'enqueue_script']:
+                sstp_callback[key] = self.callback[key]
+            try:
+                server = ninix.sstp.SSTPServer(('', port), sstp_callback)
+            except socket.error, (code, message):
+                sys.stderr.write('Port %d: %s (ignored)\n' % (port, message))
+                continue
+            self.sstp_servers.append(server)
+            print 'Serving SSTP on port', port
+
+
+class Application(object):
+
+    def __init__(self, config, sstp_port=[9801, 11000], debug=0):
+        self.ghosts, self.shells, self.balloons, self.plugins, \
+                     self.nekoninni, self.katochan, self.kinoko = config ## FIXME
+        self.debug = debug
+        # create preference dialog
+        prefs_callback = {
+            'notify_preference_changed': self.notify_preference_changed,
+            'get_balloons': self.get_balloons,
+            }
+        self.prefs = ninix.prefs.PreferenceDialog(prefs_callback)
+        sstp_controler_callback = {
+            'get_sakura_cantalk': self.get_sakura_cantalk,
+            'get_event_response': self.get_event_response,
+            'if_ghost': self.if_ghost,
+            'keep_silence': self.keep_silence,
+            'get_ghost_names': self.get_ghost_names,
+            'get_ghost_name': self.get_ghost_name,
+            'enqueue_event': self.enqueue_event,
+            'enqueue_script': self.enqueue_script,
+            'select_current_sakura': self.select_current_sakura,
+            'get_preference': self.prefs.get,
+            }
+        self.sstp_controler = SSTPControler(sstp_controler_callback, sstp_port)
+        plugin_callback = {
+            'get_sstp_port': self.sstp_controler.get_sstp_port,
+            }
+        self.plugin_controler = ninix.plugin.PluginControler(
+            self.plugins, plugin_callback)
+        # create usage dialog
+        self.usage_dialog = UsageDialog()
+        self.communicate = ninix.communicate.Communicate()
+        # create ghost manager
+        ngm_callback = {
+            'update_sakura': self.update_sakura,
+            }
+        self.__ngm = ninix.ngm.NGM(ngm_callback)
+        self.current_sakura = property(self._get_current_sakura,
+                                       self._set_current_sakura)
+
+    def _set_current_sakura(self, item):
+        print 'DEBUG(_set_current_sakura):', item
+        self._current_sakura = item
+        self.prefs.set_curretn_sakura(self.get_current_sakura_name())
+
+    def _get_current_sakura(self):
+        print 'DEBUG(_get_current_sakura):', self._current_sakura
+        return self._current_sakura
+
     def create_ghost(self, data, debug):
         default_path = os.path.join(os.environ['PYTHONPATH'], 'ninix/dll')
-        return ninix.sakura.Sakura(self, data, default_path,
-                                   self.__communicate, debug)
+        sakura_callback = {
+            'change_sakura': self.change_sakura,
+            'close_all': self.close_all_ghosts,
+            'close_ghost': self.close_ghost,
+            'edit_preferences': self.prefs.edit_preferences,
+            'find_balloon_by_name': self.find_balloon_by_name,
+            'get_preference': self.prefs.get,
+            'get_balloon_list': self.get_balloon_list,
+            'get_balloon_description': self.get_balloon_description,
+            'get_ghost_list': self.get_ghost_list,
+            'get_kinoko_list': self.get_kinoko_list,
+            'get_nekodorif_list': self.get_nekodorif_list,
+            'get_plugin_list': self.get_plugin_list,
+            'get_shell_list': self.get_shell_list,
+            'get_otherghostname': self.communicate.get_otherghostname,
+            'open_ghost_manager': self.open_ghost_manager,
+            'rebuild_ghostdb': self.communicate.rebuild_ghostdb,
+            'reset_sstp_flag': self.sstp_controler.reset_sstp_flag,
+            'request_shell': self.request_shell,
+            'request_balloon': self.request_balloon,
+            'show_usage': self.show_usage,
+            'select_plugin': self.select_plugin,
+            'select_kinoko': self.select_kinoko,
+            'select_nekodorif': self.select_nekodorif,
+            'select_ghost': self.select_ghost,
+            'select_ghost_by_name': self.select_ghost_by_name,
+            'select_shell_by_name': self.select_shell_by_name,
+            'start_sakura': self.start_sakura_cb,
+            'stop_sakura': self.stop_sakura,
+            'select_current_sakura': self.select_current_sakura,
+            'send_message': self.communicate.send_message,
+            'update_sakura': self.update_sakura,
+            'vanish_sakura': self.vanish_sakura,
+            }
+        return ninix.sakura.Sakura(data, default_path, sakura_callback, debug)
+
+    def get_sakura_cantalk(self):
+        sakura = self.get_current_sakura_instance()
+        return sakura.cantalk
+
+    def get_event_response(self, event, *arglist, **argdict):
+        sakura = self.get_current_sakura_instance()
+        return sakura.get_event_response(*event)
+
+    def keep_silence(self, quiet):
+        sakura = self.get_current_sakura_instance()
+        sakura.keep_silence(quiet)
+
+    def get_ghost_name(self):
+        sakura = self.get_current_sakura_instance()
+        return '%s,%s' % (sakura.get_selfname(), sakura.get_keroname())
+
+    def enqueue_event(self, event, *arglist, **argdict):
+        sakura = self.get_current_sakura_instance()
+        sakura.enqueue_event(event, *arglist, **argdict)
+
+    def enqueue_script(self, script, sender, handle,
+                       host, show_sstp_marker, use_translator,
+                       db=None, request_handler=None, temp_mode=False):
+        sakura = self.get_current_sakura_instance()
+        if temp_mode:
+            sakura.enter_temp_mode()
+        sakura.enqueue_script(script, sender, handle,
+                              host, show_sstp_marker, use_translator,
+                              db, request_handler)
 
     def get_working_ghost(self, cantalk=0):
         working_list = [item['instance'] for item in self.ghost_list \
@@ -322,7 +460,8 @@ class Application:
             icon_path = None
         if icon_path is not None:
             try:
-                pixbuf = ninix.pix.create_pixbuf_from_file(icon_path, is_pnr=0)
+                pixbuf = ninix.pix.create_pixbuf_from_file(
+                    icon_path, is_pnr=False)
             except:
                 pixbuf = None
             if pixbuf is not None:
@@ -381,6 +520,9 @@ class Application:
             set_list.append(item)
         return set_list
 
+    def get_balloons(self):
+        return self.balloons
+
     def get_balloon_list(self): ## FIXME
         balloon_list = []
         for i in range(len(self.balloons)):
@@ -394,24 +536,8 @@ class Application:
             balloon_list.append(item)
         return balloon_list
 
-    def get_plugin_list(self):
-        plugin_list = []
-        for i, plugin in enumerate(self.plugins):
-            plugin_name = plugin[0]
-            if not menu_items:
-                continue
-            item = {}
-            item['name'] = plugin_name
-            item['icon'] = None
-            item_list = []
-            menu_items = plugin[3]
-            for j, menu_item in enumerate(menu_items):
-                label = menu_item[0]
-                value = (i, j)
-                item_list.append((label, value))
-            item['items'] = item_list
-            plugin_list.append(item)
-        return plugin_list
+    def get_plugin_list(self): ## FIXME
+        return self.plugin_controler.get_plugin_list()
 
     def get_nekodorif_list(self):
         nekodorif_list = []
@@ -495,40 +621,17 @@ class Application:
     def run(self): ## FIXME
         self.load()
         # set SIGCHLD handler
-        signal.signal(signal.SIGCHLD, self.terminate_plugin)
+        signal.signal(signal.SIGCHLD, self.plugin_controler.terminate_plugin)
         # start SSTP server
-        for port in self.sstp_port:
-            try:
-                server = ninix.sstp.SSTPServer(('', port), self)
-            except socket.error, (code, message):
-                sys.stderr.write('Port %d: %s (ignored)\n' % (port, message))
-                continue
-            self.sstp_servers.append(server)
-            print 'Serving SSTP on port', port
+        self.sstp_controler.start_servers()
         # start plugins
-        try:
-            os.setpgid(0, 0)
-        except OSError:
-            pass
-        for plugin_name, plugin_dir, startup, menu_items in self.plugins: ## FIXME
-            if startup is not None:
-                self.exec_plugin(plugin_dir, startup)
+        self.plugin_controler.start_plugins()
         self.set_timeout()
         gtk.main()
 
-    def get_current_sakura(self):
+    def get_current_sakura_instance(self):
         return self.ghost_list[self.current_sakura[1]]['instance']
 
-    def check_request_queue(self, sender):
-        count = 0
-        for request in self.__sstp_queue:
-            if request[2].split(' / ')[0] == sender.split(' / ')[0]:
-                count += 1
-        if self.__sstp_flag and \
-           self.__current_sender.split(' / ')[0] == sender.split(' / ')[0]:
-            count += 1
-        return str(count), str(len(self.__sstp_queue))
-
     def get_ghost_names(self): ## FIXME
         name_list = []
         for item in self.ghost_list: ## FIXME
@@ -561,6 +664,16 @@ class Application:
         else:
             return 0
 
+    def update_sakura(self, name, sender):
+        item = self.find_ghost_by_name(name, 0)
+        if item is None:
+            return
+        sakura = self.select_ghost_from_list(item)
+        if not sakura.is_running():
+            self.start_sakura(item, init=1)
+        sakura.enqueue_script('\![updatebymyself]\e', sender,
+                              None, None, 0, 0, None)
+
     def select_current_sakura(self, ifghost=None):
         if ifghost is not None:
             for item in self.ghost_list:
@@ -580,10 +693,14 @@ class Application:
                     break
                 else:
                     pass
+            else:
+                return
         else:
             working_list = self.get_working_ghost(cantalk=1)
             if working_list:
                 self.current_sakura = random.choice(working_list).current
+            else:
+                return ## FIXME
 
     def close_ghost(self, sakura):
         if not self.get_working_ghost():
@@ -598,8 +715,7 @@ class Application:
     def quit(self):
         gobject.source_remove(self.timeout_id)
         self.usage_dialog.close()
-        for server in self.sstp_servers:
-            server.close()
+        self.sstp_controler.quit() ## FIXME
         self.save_preferences()
         gtk.main_quit()
         # stop plugins
@@ -622,9 +738,10 @@ class Application:
         except IOError:
             sys.stderr.write('Cannot write preferences to file (ignored).\n')
 
-    def select_ghost(self, sakura, sequential, event=1):
+    def select_ghost(self, sakura, sequential, event=1, vanished=0):
         if len(self.range_ghosts()) < 2:
             return
+        # select another ghost
         set_type, i, j = sakura.current
         if set_type == 's':
             i = 0
@@ -636,7 +753,9 @@ class Application:
             index_list = self.range_ghosts()
             index_list.remove(i)
             i = random.choice(index_list)
-        self.change_sakura(sakura, ('g', i, 0), 'automatic', event)
+        if self.current_sakura == sakura.current: # XXX
+            self.current_sakura = ('g', i, 0)
+        self.change_sakura(sakura, ('g', i, 0), 'automatic', event, vanished)
 
     def select_shell_by_name(self, sakura, name, event=1):
         set_type, i, j = sakura.current
@@ -661,13 +780,6 @@ class Application:
             return
         self.change_sakura(sakura, item, 'automatic', event)
 
-    def select_sakura(self, event, args):
-        sakura, item = args
-        if sakura.busy(): ## FIXME
-            gtk.gdk.beep()
-            return
-        self.change_sakura(sakura, item, 'manual')
-
     def change_sakura(self, sakura, item, method, event=1, vanished=0):
         set_type, i, j = item
         assert self.ghosts[i] is not None
@@ -699,7 +811,7 @@ class Application:
             proc()
         elif sakura.current[0] == set_type and sakura.current[1] == i:
             assert sakura.current[2] != j
-            sakura.enqueue_event('OnShellChanging', name, path, proc=proc)
+            sakura.enqueue_event('OnShellChanging', name, surface_dir, proc=proc)
         else:
             name = surface_desc.get('sakura.name', desc.get('sakura.name'))
             sakura.enqueue_event('OnGhostChanging', name, method, proc=proc)
@@ -740,15 +852,13 @@ class Application:
             name = self.ghost_list[prev[1]]['instance'].get_selfname()
         else:
             name = None
-        sakura.set_use_pna(self.prefs.get('use_pna')) ## FIXME
-        sakura.set_surface_scale(self.prefs.get('surface_scale')) ## FIXME
-        sakura.set_balloon_scalling(self.prefs.get('balloon_scalling')) ## FIXME
-        sakura.set_surface_alpha(self.prefs.get('surface_alpha')) ## FIXME
-        sakura.set_balloon_alpha(self.prefs.get('balloon_alpha')) ## FIXME
-        sakura.set_animation_quality(self.prefs.get('animation_quality')) ## FIXME
-        sakura.set_seriko_inactive(self.prefs.get('seriko_inactive')) ## FIXME
+        sakura.notify_preference_changed()
         sakura.start(item, init, temp, vanished, ghost_changed, name)
 
+    def notify_preference_changed(self):
+        for sakura in self.get_working_ghost():
+            sakura.notify_preference_changed()
+
     def start_sakura_cb(self, event, item):
         self.start_sakura(item, init=1)
 
@@ -794,76 +904,20 @@ class Application:
                     shutil.rmtree(os.path.join(prefix, filename))
                 except:
                     print '*** REMOVE FAILED *** :', filename
-        # select another ghost
         set_type, i, j = sakura.current
-        if set_type == 's':
-            i = 0
-        index_list = self.range_ghosts()
-        index_list.remove(i)
-        next_i = random.choice(index_list)
-        assert i != next_i
-        if self.current_sakura == sakura.current:
-            self.current_sakura = ('g', next_i, 0)
-        # reload and start the new ghost
-        self.change_sakura(sakura, ('g', next_i, 0), 'automatic', vanished=1)
+        self.select_ghost(sakura, 0, vanished=1)
         self.ghosts[i] = None
         self.update_ghost_list(i, sakura)
 
-    def select_plugin(self, event, item):
-        i, j = item
-        plugin_name, plugin_dir, startup, menu_items = self.plugins[i]
-        label, argv = menu_items[j]
-        self.exec_plugin(plugin_dir, argv)
+    def select_plugin(self, event, item): ## FIXME
+        self.plugin_controler.select_plugin(event, item)
 
-    def select_nekodorif(self, event, item): ## FIXME
-        target, nekodorif_dir = item
+    def select_nekodorif(self, nekodorif_dir, target):
         ninix.nekodorif.Nekoninni().load(nekodorif_dir, self.katochan, target)
 
-    def select_kinoko(self, event, item): ## FIXME
-        target, data = item
+    def select_kinoko(self, data, target):
         ninix.kinoko.Kinoko(self.kinoko).load(data, target)
 
-    def terminate_plugin(self, signum, frame):
-        for pid in self.plugin_pids[:]:
-            try:
-                (pid, status) = os.waitpid(pid, os.WNOHANG)
-            except OSError:
-                ##print 'Process %d not found.' % pid
-                self.plugin_pids.remove(pid)
-            if pid > 0:
-                ##print 'Process %d terminated.' % pid
-                self.plugin_pids.remove(pid)
-        ##print 'Running subprocesses:', self.plugin_pids
-
-    def get_sstp_port(self):
-        if not self.sstp_servers:
-            return None
-        return self.sstp_servers[0].server_address[1]
-
-    def exec_plugin(self, plugin_dir, argv):
-        ##print 'exec_plugin:', ' '.join(argv)
-        if not os.path.exists(argv[0]):
-            return
-        port = self.get_sstp_port()
-        if port is None:
-            port = 'none'
-        environ = os.environ.copy()
-        environ['NINIX_PID'] = str(os.getpid())
-        environ['NINIX_SSTP_PORT'] = str(port)
-        environ['NINIX_PLUGIN_DIR'] = plugin_dir
-        try:
-            pid = os.fork()
-        except OSError:
-            sys.stderr.write('Error: %s failed (ignored)\n' % argv[0])
-            return
-        if pid == 0:
-            os.chdir(plugin_dir)
-            try:
-                os.execve(argv[0], argv, environ)
-            except OSError:
-                raise SystemExit, 'Error: %s failed (abort)\n' % argv[0]
-        self.plugin_pids.append(pid)
-
     def open_ghost_manager(self):
         self.__ngm.show_dialog()
 
@@ -910,8 +964,8 @@ class Application:
         self.timeout_id = gobject.timeout_add(100, self.do_idle_tasks) # 100ms
 
     def do_idle_tasks(self):
-        self.handle_sstp_queue()
-        self.receive_sstp_request()
+        self.sstp_controler.handle_sstp_queue()
+        self.sstp_controler.receive_sstp_request()
         self.set_timeout()
 
 
@@ -954,7 +1008,8 @@ class UsageDialog:
         if ai_list:
             path = random.choice(ai_list)
             assert os.path.exists(path)
-            self.pixbuf = ninix.pix.create_pixbuf_from_file(path, is_pnr=0)
+            self.pixbuf = ninix.pix.create_pixbuf_from_file(
+                path, is_pnr=False)
             self.pixbuf.saturate_and_pixelate(self.pixbuf, 1.0, True)
         else:
             self.pixbuf = None
index c79be44..606d850 100644 (file)
@@ -21,9 +21,8 @@ if 'DISPLAY' in os.environ:
 
 class Menu:
 
-    def __init__(self, sakura, surface):
-        self.__sakura = sakura
-        self.__surface = surface
+    def __init__(self, callback):
+        self.callback = callback
         ui_info = '''
         <ui>
           <popup name='popup'>
@@ -39,7 +38,6 @@ class Menu:
             <menu action='Options'>
             <menuitem action='Update'/>
             <menuitem action='Vanish'/>
-            <menuitem action='Reload'/>
             <menuitem action='Preferences'/>
             <menuitem action='Manager'/>
             </menu>
@@ -71,61 +69,77 @@ class Menu:
         </ui>
         '''
         self.__menu_list = { ## FIXME
-            'Portal': [('Portal', None, _('Portal sites(_P)'), None),
-                       None, 1, 1],
-            'Recommend': [('Recommend', None, _('Recommend sites(_R)'), None),
-                          None, 1, 1],
-            'Options': [('Options', None, _('Options(_F)'), None),
-                     None, 1, 1],
-            'Options/Update': [('Update', None, _('Network Update(_U)'), None,
-                             '', lambda *a: self.__sakura.network_update()),
-                            None, 1, 1],
-            'Options/Vanish': [('Vanish', None, _('Vanish(_F)'), None,
-                             '', lambda *a: self.__sakura.vanish()),
-                            None, 1, 1],
-            'Options/Reload': [('Reload', None, _('Reload(_L)'), None,
-                             '', lambda *a: self.__sakura.reload()),
-                            None, 1, 1],
-            'Options/Preferences': [('Preferences', None, _('Preferences...(_O)'), None,
-                               '', lambda *a: self.__sakura.prefs.edit_preferences()),
-                              None, 1, 1],
-            'Options/Manager': [('Manager', None, _('Ghost Manager(_M)'), None,
-                              '',
-                              lambda *a: self.__sakura.app.open_ghost_manager()),
-                             None, 1, 1],
-            'Information': [('Information', None, _('Information(_I)'), None),
-                      None, 1, 1],
-            'Information/Usage': [('Usage', None, _('Usage graph(_A)'), None,
-                            '', lambda *a: self.__sakura.app.show_usage()),
-                           None, 1, 1],
-            'Information/Version': [('Version', None, _('Version(_V)'), None,
-                              '', lambda *a: self.__sakura.about()),
-                             None, 1, 1],
-            'Close': [('Close', None, _('Close(_W)'), None,
-                      '', lambda *a: self.__sakura.close()),
-                     None, 1, 1],
+            'Portal': [
+                ('Portal', None, _('Portal sites(_P)'), None),
+                None, 1, 1],
+            'Recommend': [
+                ('Recommend', None, _('Recommend sites(_R)'), None),
+                None, 1, 1],
+            'Options': [
+                ('Options', None, _('Options(_F)'), None),
+                None, 1, 1],
+            'Options/Update': [
+                ('Update', None, _('Network Update(_U)'), None,
+                 '', lambda *a: self.callback['network_update']()),
+                None, 1, 1],
+            'Options/Vanish': [
+                ('Vanish', None, _('Vanish(_F)'), None,
+                 '', lambda *a: self.callback['vanish_sakura']()),
+                None, 1, 1],
+            'Options/Preferences': [
+                ('Preferences', None, _('Preferences...(_O)'), None,
+                 '', lambda *a: self.callback['edit_preferences']()),
+                None, 1, 1],
+            'Options/Manager': [
+                ('Manager', None, _('Ghost Manager(_M)'), None,
+                 '', lambda *a: self.callback['open_ghost_manager']()),
+                None, 1, 1],
+            'Information': [
+                ('Information', None, _('Information(_I)'), None),
+                None, 1, 1],
+            'Information/Usage': [
+                ('Usage', None, _('Usage graph(_A)'), None,
+                 '', lambda *a: self.callback['show_usage']()),
+                None, 1, 1],
+            'Information/Version': [
+                ('Version', None, _('Version(_V)'), None,
+                 '', lambda *a: self.callback['about']()),
+                None, 1, 1],
+            'Close': [
+                ('Close', None, _('Close(_W)'), None,
+                 '', lambda *a: self.callback['close_sakura']()),
+                None, 1, 1],
             'Quit': [('Quit', None, _('Quit(_Q)'), None,
-                      '', lambda *a: self.__sakura.get_callback('close all')()),
+                      '', lambda *a: self.callback['close_all']()),
                      None, 1, 1],
-            'Change': [('Change', None, _('Change(_G)'), None),
-                       None, 1, 1],
-            'Summon': [('Summon', None, _('Summon(_X)'), None),
-                       None, 1, 1],
-            'Shell': [('Shell', None, _('Shell(_S)'), None),
-                      None, 1, 1],
-            'Balloon': [('Balloon', None, _('Balloon(_B)'), None),
-                        None, 1, 1],
-            'Costume': [('Costume', None, _('Costume(_C)'), None),
-                        None, 1, 1],
-            'Stick': [('Stick', None, _('Stick(_Y)'), None,
-                       '', self.__surface.window_stick, False),
-                      None, 1, 1],
-            'Nekodorif': [('Nekodorif', None, _('Nekodorif(_N)'), None),
-                          None, 1, 1],
-            'Kinoko': [('Kinoko', None, _('Kinoko(_K)'), None),
-                       None, 1, 1],
-            'Plugin': [('Plugin', None, _('Plugin(_P)'), None),
-                       None, 1, 1],
+            'Change': [
+                ('Change', None, _('Change(_G)'), None),
+                None, 1, 1],
+            'Summon': [
+                ('Summon', None, _('Summon(_X)'), None),
+                None, 1, 1],
+            'Shell': [
+                ('Shell', None, _('Shell(_S)'), None),
+                None, 1, 1],
+            'Balloon': [
+                ('Balloon', None, _('Balloon(_B)'), None),
+                None, 1, 1],
+            'Costume': [
+                ('Costume', None, _('Costume(_C)'), None),
+                None, 1, 1],
+            'Stick': [
+                ('Stick', None, _('Stick(_Y)'), None,
+                 '', self.callback['stick_window'], False),
+                None, 1, 1],
+            'Nekodorif': [
+                ('Nekodorif', None, _('Nekodorif(_N)'), None),
+                None, 1, 1],
+            'Kinoko': [
+                ('Kinoko', None, _('Kinoko(_K)'), None),
+                None, 1, 1],
+            'Plugin': [
+                ('Plugin', None, _('Plugin(_P)'), None),
+                None, 1, 1],
             }
         self.__pixmap = None
         self.__pixmap_with_sidebar = None
@@ -153,8 +167,8 @@ class Menu:
         pixbuf = None
         if path_background and os.path.exists(path_background):
             try:
-                pixbuf = ninix.pix.create_pixbuf_from_file(path_background,
-                                                           is_pnr=0)
+                pixbuf = ninix.pix.create_pixbuf_from_file(
+                    path_background, is_pnr=False)
             except:
                 pixbuf = None
         if pixbuf is None:
@@ -169,7 +183,7 @@ class Menu:
         if path_sidebar and os.path.exists(path_sidebar):
             try:
                 pixbuf_sidebar = ninix.pix.create_pixbuf_from_file(
-                    path_sidebar, is_pnr=0)
+                    path_sidebar, is_pnr=False)
             except:
                 pixbuf_sidebar = None
         if pixbuf_sidebar:
@@ -204,8 +218,8 @@ class Menu:
         pixbuf = None
         if path_foreground and os.path.exists(path_foreground):
             try:
-                pixbuf = ninix.pix.create_pixbuf_from_file(path_foreground,
-                                                           is_pnr=0)
+                pixbuf = ninix.pix.create_pixbuf_from_file(
+                    path_foreground, is_pnr=False)
             except:
                 pixbuf = None
         if pixbuf is None:
@@ -263,7 +277,7 @@ class Menu:
                         item = gtk.CheckMenuItem(name)
                         item.set_name('popup menu item')
                         item.set_active(bool(state))
-                        item.connect('activate', self.__surface.toggle_bind,
+                        item.connect('activate', self.callback['toggle_bind'],
                                      (index, key))
                     else:
                         item = gtk.SeparatorMenuItem()
@@ -312,7 +326,7 @@ class Menu:
                 name_list = self.__ui[key][0][side]
             if name_list: # caption
                 for name in name_list:
-                    caption = self.__sakura.getstring(name)
+                    caption = self.callback['getstring'](name)
                     if caption:
                         break
                 if caption:
@@ -326,7 +340,7 @@ class Menu:
                 name_list = self.__ui[key][2][side]
             if name_list: # visible
                 for name in name_list:
-                    visible = self.__sakura.getstring(name)
+                    visible = self.callback['getstring'](name)
                     if visible is not None:
                         break
                 if visible == '0':
@@ -375,11 +389,11 @@ class Menu:
             assert side in [0, 1] ## FIXME
             string = ['sakura', 'kero'][side]
         string = ''.join((string, '.popupmenu.visible'))
-        if self.__sakura.getstring(string) == '0':
+        if self.callback['getstring'](string) == '0':
             return
         self.__update_ui(side)
         if side == 0:
-            portal = self.__sakura.getstring('sakura.portalsites')
+            portal = self.callback['getstring']('sakura.portalsites')
         else:
             portal = None
         self.__set_portal_menu(side, portal)
@@ -389,7 +403,7 @@ class Menu:
             assert side in [0, 1] ## FIXME
             string = ['sakura', 'kero'][side]
         string = ''.join((string, '.recommendsites'))
-        recommend = self.__sakura.getstring(string)
+        recommend = self.callback['getstring'](string)
         self.__set_recommend_menu(recommend)
         self.__set_ghost_menu()
         self.__set_shell_menu()
@@ -484,7 +498,8 @@ class Menu:
                         #else:
                         #    bannar = None
                         item = gtk.MenuItem(title)
-                        item.connect('activate', self.__sakura.notify_site_selection,
+                        item.connect('activate',
+                                     self.callback['notify_site_selection'],
                                      (title, url))
                     menu.add(item)
                     item.show()
@@ -518,7 +533,8 @@ class Menu:
                     #else:
                     #    bannar = None
                     item = gtk.MenuItem(title)
-                    item.connect('activate', self.__sakura.notify_site_selection,
+                    item.connect('activate',
+                                 self.callback['notify_site_selection'],
                                  (title, url))
                 menu.add(item)
                 item.show()
@@ -552,10 +568,7 @@ class Menu:
             ghost_menu.append(item)
             if len(shell_list) <= 1:
                 shell_name, value = shell_list[0]
-                if summon:
-                    item.connect('activate', callback, value)
-                else:
-                    item.connect('activate', callback, (self.__sakura, value))
+                item.connect('activate', callback, value)
                 if set_list[i]['instance'].is_running():
                     item.set_sensitive(False)
             else:
@@ -565,18 +578,16 @@ class Menu:
                 for shell_name, value in shell_list:
                     item = gtk.MenuItem(shell_name)
                     item.set_name('popup menu item')
-                    if summon:
-                        item.connect('activate', callback, value)
-                    else:
-                        item.connect('activate', callback, (self.__sakura, value))
+                    item.connect('activate', callback, value)
                     item.show()
                     if set_list[i]['instance'].is_running():
                         if summon:
                             item.set_sensitive(False)
                         else:
-                            if self.__sakura.current[1] != value[1]:
+                            set_type, i, j = self.callback['get_current_item']()
+                            if i != value[1]:
                                 item.set_sensitive(False)
-                            elif self.__sakura.current[2] == value[2]:
+                            elif j == value[2]:
                                 item.set_sensitive(False)
                     submenu.append(item)
         path = 'Summon' if summon else 'Change'
@@ -585,12 +596,12 @@ class Menu:
         self.__menu_list[path][3] = 1
 
     def __set_ghost_menu(self):
-        ghosts = self.__sakura.app.get_ghost_list()
-        self.__update_ghost_menu(ghosts, self.__sakura.app.select_sakura, False)
-        self.__update_ghost_menu(ghosts, self.__sakura.app.start_sakura_cb, True)
+        ghosts = self.callback['get_ghost_list']()
+        self.__update_ghost_menu(ghosts, self.callback['select_sakura'], False)
+        self.__update_ghost_menu(ghosts, self.callback['start_sakura'], True)
 
     def __set_shell_menu(self):
-        set_list = self.__sakura.app.get_shell_list() ## FIXME
+        set_list = self.callback['get_shell_list']()
         shell_menu = gtk.Menu()
         for i in range(len(set_list)):
             name = set_list[i]['name']
@@ -601,8 +612,7 @@ class Menu:
             shell_list = set_list[i]['shell']
             if len(shell_list) <= 1:
                 shell_name, value = shell_list[0]
-                item.connect('activate', self.__sakura.app.select_sakura,
-                             (self.__sakura, value))
+                item.connect('activate', self.callback['select_sakura'], value)
                 ##if working:
                 ##    item.set_sensitive(False)
             else:
@@ -612,8 +622,8 @@ class Menu:
                 for shell_name, value in shell_list:
                     item = gtk.MenuItem(shell_name)
                     item.set_name('popup menu item')
-                    item.connect('activate', self.__sakura.app.select_sakura,
-                                 (self.__sakura, value))
+                    item.connect('activate', self.callback['select_sakura'],
+                                 value)
                     item.show()
                     ##if working: ## FIXME
                     ##    item.set_sensitive(False)
@@ -623,14 +633,14 @@ class Menu:
         self.__menu_list['Shell'][3] = 1
 
     def __set_balloon_menu(self):
-        balloon_list = self.__sakura.app.get_balloon_list()
+        balloon_list = self.callback['get_balloon_list']()
         balloon_menu = gtk.Menu()
         group = None
-        name_own = self.__sakura.get_own_balloon_name()
+        name_own = self.callback['get_own_balloon_name']()
         if name_own is not None:
             item = group = gtk.RadioMenuItem(group, name_own)
             item.set_name('popup menu item')
-            hid = item.connect('activate', self.__sakura.select_balloon,
+            hid = item.connect('activate', self.callback['select_balloon'],
                                name_own)
             item.show()
             balloon_menu.append(item)
@@ -641,10 +651,11 @@ class Menu:
             name = balloon_list[i]['name']
             item = group = gtk.RadioMenuItem(group, name)
             item.set_name('popup menu item')
-            hid = item.connect('activate', self.__sakura.select_balloon, name)
+            hid = item.connect('activate',
+                               self.callback['select_balloon'], name)
             item.show()
             balloon_menu.append(item)
-            if name == self.__sakura.get_current_balloon_name():
+            if name == self.callback['get_current_balloon_name']():
                 item.handler_block(hid)
                 item.activate()
                 item.handler_unblock(hid)
@@ -653,7 +664,7 @@ class Menu:
         self.__menu_list['Balloon'][3] = 1
 
     def __set_plugin_menu(self):
-        plugin_list = self.__sakura.app.get_plugin_list()
+        plugin_list = self.callback['get_plugin_list']()
         plugin_menu = gtk.Menu()
         for i in range(len(plugin_list)):
             name = plugin_list[i]['name']
@@ -664,7 +675,7 @@ class Menu:
             item_list = plugin_list[i]['items']
             if len(item_list) <= 1:
                 label, value = item_list[0]
-                item.connect('activate', self.__sakura.app.select_plugin, value)
+                item.connect('activate', self.callback['select_plugin'], value)
                 ##if working:
                 ##    item.set_sensitive(False)
             else:
@@ -674,7 +685,7 @@ class Menu:
                 for label, value in item_list:
                     item = gtk.MenuItem(label)
                     item.set_name('popup menu item')
-                    item.connect('activate', self.__sakura.app.select_plugin,
+                    item.connect('activate', self.callback['select_plugin'],
                                  value)
                     item.show()
                     ##if working:
@@ -685,7 +696,7 @@ class Menu:
         self.__menu_list['Plugin'][3] = 1
 
     def __set_nekodorif_menu(self):
-        nekodorif_list = self.__sakura.app.get_nekodorif_list()
+        nekodorif_list = self.callback['get_nekodorif_list']()
         nekodorif_menu = gtk.Menu()
         for i in range(len(nekodorif_list)):
             name = nekodorif_list[i]['name']
@@ -693,8 +704,8 @@ class Menu:
             item.set_name('popup menu item')
             item.show()
             nekodorif_menu.append(item)
-            item.connect('activate', self.__sakura.app.select_nekodorif,
-                         (self.__sakura, nekodorif_list[i]['dir']))
+            item.connect('activate', self.callback['select_nekodorif'],
+                         nekodorif_list[i]['dir'])
             ##if working:
             ##    item.set_sensitive(False)
         menuitem = self.ui_manager.get_widget(
@@ -703,7 +714,7 @@ class Menu:
         self.__menu_list['Nekodorif'][3] = 1
 
     def __set_kinoko_menu(self):
-        kinoko_list = self.__sakura.app.get_kinoko_list()
+        kinoko_list = self.callback['get_kinoko_list']()
         kinoko_menu = gtk.Menu()
         for i in range(len(kinoko_list)):
             name = kinoko_list[i]['title']
@@ -711,8 +722,8 @@ class Menu:
             item.set_name('popup menu item')
             item.show()
             kinoko_menu.append(item)
-            item.connect('activate', self.__sakura.app.select_kinoko,
-                         (self.__sakura, kinoko_list[i]))
+            item.connect('activate', self.callback['select_kinoko'],
+                         kinoko_list[i])
             ##if working:
             ##    item.set_sensitive(False)
         menuitem = self.ui_manager.get_widget(''.join(('/popup/', 'Kinoko')))
index 7ce5e89..53ef9b6 100644 (file)
@@ -58,8 +58,8 @@ import ninix.home
 
 class Menu:
 
-    def __init__(self, nekoninni, accelgroup):
-        self.__nekoninni = nekoninni
+    def __init__(self, callback, accelgroup):
+        self.callback = callback
         ui_info = '''
         <ui>
           <popup name='popup'>
@@ -73,12 +73,12 @@ class Menu:
         '''
         self.__menu_list = {
             'settings': [('Settings', None, _('Settings...(_O)'), None,
-                          '', self.__nekoninni.edit_preferences),
+                          '', self.callback['edit_preferences']),
                          '/ui/popup/Settings'],
             'katochan': [('Katochan', None,_('Katochan(_K)'), None),
                          '/ui/popup/Katochan'],
             'exit':     [('Exit', None,_('Exit(_Q)'), None,
-                          '', self.__nekoninni.close),
+                          '', self.callback['close']),
                          '/ui/popup/Exit'],
             }
         self.__katochan_list = None
@@ -96,7 +96,7 @@ class Menu:
             self.__menu_list[key][1] = ui_manager.get_widget(path)
 
     def popup(self, button):
-        katochan_list = self.__nekoninni.get_katochan_list()
+        katochan_list = self.callback['get_katochan_list']()
         self.__set_katochan_menu(katochan_list)
         self.__popup_menu.popup(
             None, None, None, button, gtk.get_current_event_time())
@@ -108,7 +108,7 @@ class Menu:
             for katochan in list:
                 item = gtk.MenuItem(katochan['name'])
                 item.connect(
-                    'activate', self.__nekoninni.select_katochan, (katochan))
+                    'activate', self.callback['select_katochan'], (katochan))
                 menu.add(item)
                 item.show()
             self.__menu_list[key][1].set_submenu(menu)
@@ -131,8 +131,8 @@ class Nekoninni:
                 self.skin.set_position()
             if self.katochan is not None and self.katochan.loaded:
                 self.katochan.set_position()
-        elif event == 'set scale': ## FIXME
-            scale = self.target.prefs.get('surface_scale')
+        elif event == 'set scale':
+            scale = self.target.get_surface_scale()
             if self.skin is not None:
                 self.skin.set_scale(scale)
             if self.katochan is not None:
@@ -150,8 +150,17 @@ class Nekoninni:
         self.target = target
         self.target.set_observer(self)
         self.accelgroup = gtk.AccelGroup()
-        scale = self.target.prefs.get('surface_scale')
-        self.skin = Skin(self.dir, self.accelgroup, self, scale)
+        scale = self.target.get_surface_scale()
+        skin_callback ={
+            'finalize': self.finalize,
+            'has_katochan': self.has_katochan,
+            'drop_katochan': self.drop_katochan,
+            'edit_preferences': self.edit_preferences,
+            'close': self.close,
+            'get_katochan_list': lambda *a: self.katochan_list,
+            'select_katochan': self.select_katochan,
+            }
+        self.skin = Skin(self.dir, self.accelgroup, skin_callback, scale)
         if self.skin is None:
             return 0
         self.katochan_list = katochan
@@ -204,7 +213,13 @@ class Nekoninni:
     def launch_katochan(self, katochan):
         if self.katochan:
             self.katochan.destroy()
-        self.katochan = Katochan(self, self.target)
+        katochan_callback = {
+            'finalize': self.finalize,
+            'send_event': self.send_event,
+            'get_mode': lambda *a: self.mode,
+            'delete_katochan': self.delete_katochan,
+            }
+        self.katochan = Katochan(katochan_callback, self.target)
         self.katochan.load(katochan)
 
     def edit_preferences(self, action):
@@ -223,19 +238,21 @@ class Nekoninni:
     def close(self, action):
         self.finalize()
 
-    def get_katochan_list(self): ## FIXME
-        return self.katochan_list
 
 class Skin:
 
-    def __init__(self, dir, accelgroup, nekoninni, scale):
+    def __init__(self, dir, accelgroup, callback, scale):
         self.dir = dir
         self.accelgroup = accelgroup
-        self.nekoninni = nekoninni
+        self.callback = callback
         self.x_root = None
         self.y_root = None
         self.__scale = scale
-        self.__menu = Menu(self.nekoninni, self.accelgroup)
+        menu_callback = {}
+        for key in ['edit_preferences', 'close',
+                    'get_katochan_list', 'select_katochan']:
+            menu_callback[key] = self.callback[key]
+        self.__menu = Menu(menu_callback, self.accelgroup)
         path = os.path.join(self.dir, 'omni.txt')
         self.omni = int(os.path.isfile(path) and os.path.getsize(path) == 0)
         self.window = gtk.Window()
@@ -275,7 +292,7 @@ class Skin:
         pass
 
     def delete(self, widget, event):
-        self.nekoninni.finalize()
+        self.callback['finalize']()
 
     def destroy(self): ## FIXME
         self.window.destroy()
@@ -290,9 +307,9 @@ class Skin:
                 ##    gtk.get_current_event_time())
                 pass
             elif event.type == gtk.gdk._2BUTTON_PRESS: # double click
-                if self.nekoninni.has_katochan():
+                if self.callback['has_katochan']():
                     self.start() ## FIXME
-                    self.nekoninni.drop_katochan()
+                    self.callback['drop_katochan']()
         elif event.button == 3:                
             if event.type == gtk.gdk.BUTTON_PRESS:
                 self.__menu.popup(event.button)
@@ -315,7 +332,7 @@ class Skin:
             pixbuf = pixbuf.scale_simple(w, h, gtk.gdk.INTERP_BILINEAR)
             image, mask = pixbuf.render_pixmap_and_mask(255)
         except:
-            self.nekoninni.finalize()
+            self.callback['finalize']()
             return
         self.w, self.h = image.get_size()
         self.darea.set_size_request(self.w, self.h)
@@ -402,10 +419,10 @@ class Katochan:
                      'other'      # 上記カテゴリに当てはまらないもの
                      ]
 
-    def __init__(self, nekoninni, target):
+    def __init__(self, callback, target):
         self.side = 0
         self.target = target
-        self.nekoninni = nekoninni
+        self.callback = callback
         self.settings = {}
         self.settings['state'] = 'before'
         self.settings['fall.type'] = 'gravity'
@@ -500,7 +517,7 @@ class Katochan:
             pixbuf = pixbuf.scale_simple(w, h, gtk.gdk.INTERP_BILINEAR)
             image, mask = pixbuf.render_pixmap_and_mask(255)
         except:
-            self.nekoninni.finalize()
+            self.callback['finalize']()
             return
         self.w, self.h = image.get_size()
         self.darea.set_size_request(self.w, self.h)
@@ -510,7 +527,7 @@ class Katochan:
 
     def load(self, data):
         self.data = data
-        self.__scale = self.target.prefs.get('surface_scale')
+        self.__scale = self.target.get_surface_scale()
         self.set_state('before')
         ##print 'DATA:', self.data
         if 'category' in self.data:
@@ -532,8 +549,8 @@ class Katochan:
                 self.side = 0 # XXX
         else:
             self.side = 0 # XXX
-        if self.nekoninni.mode == 1:
-            self.nekoninni.send_event('Emerge')
+        if self.callback['get_mode']() == 1:
+            self.callback['send_event']('Emerge')
         else:
             if 'before.script' in self.data:
                 pass ## FIXME
@@ -635,10 +652,10 @@ class Katochan:
             if self.check_collision():
                 self.set_state('hit')
                 self.hit = 1
-                if self.nekoninni.mode == 1:
+                if self.callback['get_mode']() == 1:
                     self.id = 1
                     self.set_surface()
-                    self.nekoninni.send_event('Hit')
+                    self.callback['send_event']('Hit')
                 else:
                     pass ## FIXME
             if self.check_mikire():
@@ -647,10 +664,10 @@ class Katochan:
             if self.hit_stop >= self.data.get('hit.waittime', 0):
                 self.set_state('after')
                 self.set_movement('after')
-                if self.nekoninni.mode == 1:
+                if self.callback['get_mode'] == 1:
                     self.id = 2
                     self.set_surface()
-                    self.nekoninni.send_event('Drop')
+                    self.callback['send_event']('Drop')
                 else:
                     pass ## FIXME
             else:
@@ -662,18 +679,18 @@ class Katochan:
             if self.check_mikire():
                 self.set_state('end')
         elif self.settings['state'] == 'end':
-            if self.nekoninni.mode == 1:
-                self.nekoninni.send_event('Vanish')
+            if self.callback['get_mode']() == 1:
+                self.callback['send_event']('Vanish')
             else:
                 pass ## FIXME
-            self.nekoninni.delete_katochan()
+            self.callback['delete_katochan']()
             return False
         elif self.settings['state'] == 'dodge':
-            if self.nekoninni.mode == 1:
-                self.nekoninni.send_event('Dodge')
+            if self.callback['get_mode'] == 1:
+                self.callback['send_event']('Dodge')
             else:
                 pass ## FIXME
-            self.nekoninni.delete_katochan()
+            self.callback['delete_katochan']()
             return False
         else:
             pass ## check collision and mikire
index b8c8787..3e5b7f8 100644 (file)
@@ -246,22 +246,22 @@ class Catalog(Catalog_xml):
             except:
                 return ## FIXME
 
-class InstallationManager(list):
-
-    def __init__(self, ngm):
-        self.master = ngm
-
-    def set_current_ghost(self, set_id):
-        self.current_ghost = set_id
-
-    def install_ghost(self, dialog):
-        a = self.master.ui.catalog.get(self.current_ghost, 'ArchiveUrl')
-        os.system("%s '%s'" % (self.master.ninix_install_cmd, a))
+#class InstallationManager(list):
+#
+#    def __init__(self, ngm):
+#        self.master = ngm
+#
+#    def set_current_ghost(self, set_id):
+#        self.current_ghost = set_id
+#
+#    def install_ghost(self, dialog):
+#        a = self.master.ui.catalog.get(self.current_ghost, 'ArchiveUrl')
+#        os.system("%s '%s'" % (self.master.ninix_install_cmd, a))
 
 class SearchDialog:
 
-    def __init__(self, ui):
-        self.ui = ui
+    def __init__(self, callback):
+        self.callback = callback
         self.dialog = gtk.Dialog()
         self.dialog.connect('delete_event', self.cancel)
         self.dialog.set_modal(True)
@@ -296,7 +296,7 @@ class SearchDialog:
 
     def ok(self, widget, event=None):
         word = unicode(self.get_pattern(), 'utf-8')
-        self.ui.search(word)
+        self.callback['search'](word)
         self.hide()
         return True
 
@@ -305,8 +305,8 @@ class SearchDialog:
         return True
 
 class UI:
-    def __init__(self, ngm):
-        self.ngm = ngm
+    def __init__(self, callback):
+        self.callback = callback
         self.ui_info = '''
         <ui>
           <menubar name='MenuBar'>
@@ -347,7 +347,7 @@ class UI:
             ( 'Settings', None,
               _('Settings(_O)'), None,
               None,
-              lambda *a: self.ngm.open_preference_dialog() ),
+              lambda *a: self.callback['open_preference_dialog']() ),
             ( 'DB Network Update', None,
               _('DB Network Update(_N)'), None,
               None,
@@ -359,15 +359,15 @@ class UI:
             ( 'Mask', None,
               _('Mask(_M)'), None,
               None,
-              lambda *a: self.ngm.open_mask_dialog() ),
+              lambda *a: self.callback['open_mask_dialog']() ),
             ( 'Reset to Default', None,
               _('Reset to Default(_Y)'), None,
               None,
-              lambda *a: self.ngm.reset_to_default() ),
+              lambda *a: self.callback['reset_to_default']() ),
             ( 'Show All', None,
               _('Show All(_Z)'), None,
               None,
-              lambda *a: self.ngm.show_all() ),
+              lambda *a: self.callback['show_all']() ),
             )
         self.opened = 0
         self.textview = [None, None]
@@ -376,7 +376,10 @@ class UI:
         self.button = {}
         self.url = {}
         self.search_word = ''
-        self.search_dialog = SearchDialog(self)
+        search_dialog_callback = {
+            'search': self.search,
+            }
+        self.search_dialog = SearchDialog(search_dialog_callback)
         self.create_dialog()
 
     def create_dialog(self):
@@ -430,7 +433,7 @@ class UI:
         self.statusbar.show()
 
     def network_update(self):
-        self.ngm.network_update()
+        self.callback['network_update']()
         self.update()
 
     def open_search_dialog(self):
@@ -439,24 +442,24 @@ class UI:
     def search(self, word):
         if word:
             self.search_word = word
-            if self.ngm.search(word):
+            if self.callback['search'](word):
                 self.update()
             else:
                 pass ## FIXME
 
     def search_forward(self):
         if self.search_word:
-            if self.ngm.search_forward(self.search_word):
+            if self.callback['search_forward'](self.search_word):
                 self.update()
             else:
                 pass ## FIXME
 
     def show_next(self):
-        self.ngm.next()
+        self.callback['next']()
         self.update()
 
     def show_previous(self):
-        self.ngm.previous()
+        self.callback['previous']()
         self.update()
 
     def create_surface_area(self, side):
@@ -477,11 +480,11 @@ class UI:
 
     def update_surface_area(self):
         for side in [0, 1]:
-            name = self.ngm.get('SakuraName') if side == 0 else \
-                self.ngm.get('KeroName')
+            name = self.callback['get']('SakuraName') if side == 0 else \
+                self.callback['get']('KeroName')
             textbuffer = self.textview[side].get_buffer()
             textbuffer.set_text(name)
-            filename = self.ngm.get_image_filename(side)
+            filename = self.callback['get_image_filename'](side)
             darea = self.darea[side]
             darea.realize()
             if filename is not None:
@@ -507,12 +510,12 @@ class UI:
         box.set_layout(gtk.BUTTONBOX_SPREAD)
         box.show()
         button = gtk.Button(_('Install'))
-        button.connect('clicked', lambda b, w=self: w.ngm.install_current())
+        button.connect('clicked', lambda b, w=self: w.callback['install_current']())
         box.add(button)
         button.show()
         self.button['install'] = button
         button = gtk.Button(_('Update'))
-        button.connect('clicked', lambda b, w=self: w.ngm.update_current())
+        button.connect('clicked', lambda b, w=self: w.callback['update_current']())
         box.add(button)
         button.show()
         self.button['update'] = button
@@ -553,44 +556,44 @@ class UI:
                      (_('Version:'), 'Version'),
                      (_('AIName:'), 'AIName')]
         text = ''
-        text = ''.join((text, self.ngm.get('Name'), '\n'))
+        text = ''.join((text, self.callback['get']('Name'), '\n'))
         for item in info_list:
-            text = ''.join((text, item[0], self.ngm.get(item[1]), '\n'))
+            text = ''.join((text, item[0], self.callback['get'](item[1]), '\n'))
         text = ''.join((text,
-                        self.ngm.get('SakuraName'),
-                        _('SurfaceList:'), self.ngm.get('SakuraSurfaceList'),
+                        self.callback['get']('SakuraName'),
+                        _('SurfaceList:'), self.callback['get']('SakuraSurfaceList'),
                         '\n'))
         text = ''.join((text,
-                        self.ngm.get('KeroName'),
-                        _('SurfaceList:'), self.ngm.get('KeroSurfaceList'),
+                        self.callback['get']('KeroName'),
+                        _('SurfaceList:'), self.callback['get']('KeroSurfaceList'),
                         '\n'))
         textbuffer = self.info.get_buffer()
         textbuffer.set_text(text)
-        url = self.ngm.get('HPUrl')
-        text = self.ngm.get('HPTitle')
+        url = self.callback['get']('HPUrl')
+        text = self.callback['get']('HPTitle')
         self.url['HP'][0] = url
         label = self.url['HP'][1]
         label.set_markup('<span foreground="blue">%s</span>' % text)
-        url = self.ngm.get('PublicUrl')
-        text = ''.join((self.ngm.get('Name'), _(' Web Page')))
+        url = self.callback['get']('PublicUrl')
+        text = ''.join((self.callback['get']('Name'), _(' Web Page')))
         self.url['Public'][0] = url
         label = self.url['Public'][1]
         label.set_markup('<span foreground="blue">%s</span>' % text)        
-        target_dir = os.path.join(self.ngm.home_dir, 'ghost',
-                                  self.ngm.get('InstallDir'))
+        target_dir = os.path.join(self.callback['get_home_dir'](), 'ghost',
+                                  self.callback['get']('InstallDir'))
         self.button['install'].set_sensitive(
             bool(not os.path.isdir(target_dir) and
-                 self.ngm.get('ArchiveUrl') != 'No data'))
+                 self.callback['get']('ArchiveUrl') != 'No data'))
         self.button['update'].set_sensitive(
             bool(os.path.isdir(target_dir) and
-                 self.ngm.get('GhostType') == 'ゴースト' and
-                 self.ngm.get('UpdateUrl') != 'No data'))
+                 self.callback['get']('GhostType') == 'ゴースト' and
+                 self.callback['get']('UpdateUrl') != 'No data'))
 
     def update(self):
         self.update_surface_area()
         self.update_info_area()
-        self.button['next'].set_sensitive(bool(self.ngm.exist_next()))
-        self.button['previous'].set_sensitive(bool(self.ngm.exist_previous()))
+        self.button['next'].set_sensitive(bool(self.callback['exist_next']()))
+        self.button['previous'].set_sensitive(bool(self.callback['exist_previous']()))
 
     def show(self):
         if self.opened:
@@ -605,13 +608,31 @@ class UI:
 
 class NGM:
 
-    def __init__(self, app):
+    def __init__(self, callback):
+        self.callback = callback
         self.current = 0
-        self.app = app
         self.opened = 0
         self.home_dir = ninix.home.get_ninix_home()
         self.catalog = Catalog(os.path.join(self.home_dir, 'ngm/data'))
-        self.ui = UI(self)
+        ui_callback = {
+            'open_preference_dialog': self.open_preference_dialog,
+            'open_mask_dialog': self.open_mask_dialog,
+            'reset_to_default': self.reset_to_default,
+            'show_all': self.show_all,
+            'network_update': self.network_update,
+            'search': self.search,
+            'search_forward': self.search_forward,
+            'next': self.next,
+            'previous': self.previous,
+            'get': self.get,
+            'get_image_filename': self.get_image_filename,
+            'get_home_dir': lambda *a: self.home_dir,
+            'exist_next': self.exist_next,
+            'exist_previous': self.exist_previous,
+            'install_current': self.install_current,
+            'update_current': self.update_current,
+            }
+        self.ui = UI(ui_callback)
 
     def get(self, element):
         if self.current in self.catalog.data:
@@ -685,12 +706,7 @@ class NGM:
         pass
 
     def update_current(self): ## FIXME
-        item = self.app.find_ghost_by_name(self.get('Name'), 0)
-        sakura = self.app.select_ghost_from_list(item)
-        if not sakura.is_running():
-            self.app.start_sakura(item, init=1)
-        sakura.enqueue_script('\![updatebymyself]\e', 'NGM',
-                              None, None, 0, 0, None)
+        self.callback['update_sakura'](self.get('Name'), 'NGM')
 
 
 if __name__ == '__main__':
index 8c26572..c85ad95 100644 (file)
@@ -98,7 +98,7 @@ def create_pixbuf_from_DDP_file(path):
     loader.close()
     return pixbuf
 
-def create_pixbuf_from_file(path, is_pnr=1, use_pna=0):
+def create_pixbuf_from_file(path, is_pnr=True, use_pna=False):
     head, tail = os.path.split(path)
     basename, suffix = os.path.splitext(tail)
     if suffix == '.dgp':
diff --git a/lib/ninix/plugin.py b/lib/ninix/plugin.py
new file mode 100755 (executable)
index 0000000..2b228eb
--- /dev/null
@@ -0,0 +1,96 @@
+# -*- coding: utf-8 -*-
+#
+#  Copyright (C) 2001, 2002 by Tamito KAJIYAMA
+#  Copyright (C) 2002, 2003 by MATSUMURA Namihiko <nie@counterghost.net>
+#  Copyright (C) 2002-2010 by Shyouzou Sugitani <shy@users.sourceforge.jp>
+#  Copyright (C) 2003-2005 by Shun-ichi TAHARA <jado@flowernet.gr.jp>
+#
+#  This program is free software; you can redistribute it and/or modify it
+#  under the terms of the GNU General Public License (version 2) as
+#  published by the Free Software Foundation.  It is distributed in the
+#  hope that it will be useful, but WITHOUT ANY WARRANTY; without even the
+#  implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+#  PURPOSE.  See the GNU General Public License for more details.
+#
+
+import os
+import signal
+import sys
+
+
+class PluginControler:
+
+    def __init__(self, plugins, callback):
+        self.plugins = plugins
+        self.plugin_pids = []
+        self.get_sstp_port = callback['get_sstp_port']
+
+    def get_plugin_list(self):
+        plugin_list = []
+        for i, plugin in enumerate(self.plugins):
+            plugin_name = plugin[0]
+            menu_items = plugin[3]
+            if not menu_items:
+                continue
+            item = {}
+            item['name'] = plugin_name
+            item['icon'] = None
+            item_list = []
+            for j, menu_item in enumerate(menu_items):
+                label = menu_item[0]
+                value = (i, j)
+                item_list.append((label, value))
+            item['items'] = item_list
+            plugin_list.append(item)
+        return plugin_list
+
+    def select_plugin(self, event, item):
+        i, j = item
+        plugin_name, plugin_dir, startup, menu_items = self.plugins[i]
+        label, argv = menu_items[j]
+        self.exec_plugin(plugin_dir, argv)
+
+    def terminate_plugin(self, signum, frame):
+        for pid in self.plugin_pids[:]:
+            try:
+                (pid, status) = os.waitpid(pid, os.WNOHANG)
+            except OSError:
+                ##print 'Process %d not found.' % pid
+                self.plugin_pids.remove(pid)
+            if pid > 0:
+                ##print 'Process %d terminated.' % pid
+                self.plugin_pids.remove(pid)
+        ##print 'Running subprocesses:', self.plugin_pids
+
+    def exec_plugin(self, plugin_dir, argv):
+        ##print 'exec_plugin:', ' '.join(argv)
+        if not os.path.exists(argv[0]):
+            return
+        port = self.get_sstp_port()
+        if port is None:
+            port = 'none'
+        environ = os.environ.copy()
+        environ['NINIX_PID'] = str(os.getpid())
+        environ['NINIX_SSTP_PORT'] = str(port)
+        environ['NINIX_PLUGIN_DIR'] = plugin_dir
+        try:
+            pid = os.fork()
+        except OSError:
+            sys.stderr.write('Error: %s failed (ignored)\n' % argv[0])
+            return
+        if pid == 0:
+            os.chdir(plugin_dir)
+            try:
+                os.execve(argv[0], argv, environ)
+            except OSError:
+                raise SystemExit, 'Error: %s failed (abort)\n' % argv[0]
+        self.plugin_pids.append(pid)
+
+    def start_plugins(self):
+        try:
+            os.setpgid(0, 0)
+        except OSError:
+            pass
+        for plugin_name, plugin_dir, startup, menu_items in self.plugins:
+            if startup is not None:
+                self.exec_plugin(plugin_dir, startup)
index 990584a..6347845 100644 (file)
@@ -63,7 +63,7 @@ class Preferences(dict):
             f.write('%s: %s\n' % (key, self[key]))
         f.close()
 
-    def get_with_type(self, name, default, conv=None):
+    def get_with_type(self, name, conv, default):
         value = self.get(name)
         if value:
             if conv is None:
@@ -96,8 +96,8 @@ class PreferenceDialog:
                   'seriko_inactive': int,
                   }
 
-    def __init__(self, app):
-        self.app = app
+    def __init__(self, callback):
+        self.callback = callback
         self.window = gtk.Dialog()
         self.window.set_title('Preferences')
         self.window.connect('delete_event', self.cancel)
@@ -133,17 +133,16 @@ class PreferenceDialog:
     def load(self):
         filename = ninix.home.get_preferences()
         self.__prefs = Preferences(filename)
+        self.save = self.__prefs.save
         self.__prefs.load()
         self.reset()
         self.__saved_prefs = self.__prefs.copy()
-        self.notify() ## FIXME
+        self.callback['notify_preference_changed']()
         
     def reset(self): ### FIXME ###
         self.fontsel.set_font_name(
             self.get('balloon_fonts', DEFAULT_BALLOON_FONTS))
-        name = self.get('default_balloon')
-        if self.app.find_balloon_by_name(name) is not None:
-            self.set_default_balloon(name)
+        self.set_default_balloon(self.get('default_balloon'))
         self.ignore_button.set_active(
             bool(self.get('ignore_default', 0)))
         scale = self.get('surface_scale', get_default_surface_scale())
@@ -173,13 +172,13 @@ class PreferenceDialog:
 
     def get(self, name, default=None):
         assert name in self.PREFS_TYPE
-        return self.__prefs.get_with_type(name, default, self.PREFS_TYPE[name])
+        return self.__prefs.get_with_type(name, self.PREFS_TYPE[name], default)
 
-    def save(self): ## FIXME: check key is valid
-        name, surface = self.app.get_current_sakura_name() ## FIXME
-        self.__prefs['sakura_name'] = self.__saved_prefs['sakura_name'] = name
-        self.__prefs['sakura_surface'] = self.__saved_prefs['sakura_surface'] = surface
-        self.__prefs.save()
+    def set_current_sakura(self, name, surface):
+        key = 'sakura_name'
+        self.__prefs[key] = self.__saved_prefs[key] = name
+        key = 'sakura_surface'
+        self.__prefs[key] = self.__saved_prefs[key] = surface
 
     def edit_preferences(self):
         self.show()
@@ -187,12 +186,10 @@ class PreferenceDialog:
     def update(self): ## FIXME
         self.__prefs['allowembryo'] = str(int(self.allowembryo_button.get_active()))
         self.__prefs['balloon_fonts'] = self.fontsel.get_font_name()
-        name = None
         selected = self.balloon_treeview.get_selection().get_selected()
         if selected:
             model, listiter = selected
             name = model.get_value(listiter, 0)
-        if self.app.find_balloon_by_name(name) is not None:
             self.__prefs['default_balloon'] = name
         self.__prefs['ignore_default'] = str(int(self.ignore_button.get_active()))
         self.__prefs['surface_scale'] = str(int(range_scale[self.surface_scale_combo.get_active()]))
@@ -211,17 +208,17 @@ class PreferenceDialog:
         self.hide()
         self.update()
         self.__saved_prefs.update(self.__prefs)
-        self.notify() ## FIXME
+        self.callback['notify_preference_changed']()
 
     def apply(self, widget):
         self.update()
-        self.notify() ## FIXME
+        self.callback['notify_preference_changed']()
 
     def cancel(self, widget, event=None):
         self.hide()
         self.__prefs.update(self.__saved_prefs)
         self.reset()
-        self.notify() ## FIXME
+        self.callback['notify_preference_changed']()
         return True
 
     def show(self):
@@ -285,7 +282,7 @@ class PreferenceDialog:
         box.pack_start(scrolled, True)
         scrolled.show()
         model = gtk.ListStore(gobject.TYPE_STRING)
-        for desc, balloon in self.app.balloons:
+        for desc, balloon in self.callback['get_balloons']():
             name = desc.get('name', '')
             listiter = model.append()
             model.set_value(listiter, 0, name)
@@ -425,24 +422,6 @@ class PreferenceDialog:
         button.show()
         return page
 
-    def notify(self): ## FIXME
-        for sakura in self.app.get_working_ghost():
-            sakura.reset_balloon_fonts() ## FIXME
-            flag = self.get('balloon_scalling')
-            sakura.set_balloon_scalling(flag) ## FIXME
-            scale = self.get('surface_scale')
-            sakura.set_surface_scale(scale) ## FIXME
-            flag = self.get('use_pna')
-            sakura.set_use_pna(flag) ## FIXME
-            alpha = self.get('surface_alpha')
-            sakura.set_surface_alpha(alpha) ## FIXME
-            alpha = self.get('balloon_alpha')
-            sakura.set_balloon_alpha(alpha) ## FIXME
-            quality = self.get('animation_quality')
-            sakura.set_animation_quality(quality) ## FIXME
-            flag = self.get('seriko_inactive')
-            sakura.set_seriko_inactive(flag) ## FIXME
-
     def set_default_balloon(self, name):
         model = self.balloon_treeview.get_model()
         listiter = model.get_iter_first()
index 95a0f01..1b7d6e8 100644 (file)
@@ -28,9 +28,14 @@ if 'DISPLAY' in os.environ:
     import gtk ## FIXME
     import gobject
 
+# For some reasons gst parses sys.argv on import. This is a workaround.
+# (See https://bugzilla.gnome.org/show_bug.cgi?id=549879 .)
+argv = sys.argv
+sys.argv = []
 import pygst
 pygst.require("0.10")
 import gst
+sys.argv = argv
 
 import ninix.surface
 import ninix.balloon
@@ -61,11 +66,9 @@ class Sakura:
     except:
         name2codepoint = None
 
-    def __init__(self, app, data, default_path, communicate, debug=0):
-        self.app = app
-        self.prefs = app.prefs
+    def __init__(self, data, default_path, callback, debug=0):
+        self.callback = callback
         self.debug = debug
-        self.communicate = communicate
         self.sstp_handle = None
         self.sstp_entry_db = None
         self.sstp_request_handler = None
@@ -93,7 +96,11 @@ class Sakura:
         ##
         self.old_otherghostname = None ## FIXME
         # create vanish dialog
-        self.__vanish_dialog = VanishDialog(self)
+        vanish_dialog_callback = {
+            'notify_vanish_confirmation': self.notify_vanish_confirmation,
+            'notify_event': self.notify_event,
+            }
+        self.__vanish_dialog = VanishDialog(vanish_dialog_callback)
         self.cantalk = 1
         self.__sender = 'ninix-aya'
         self.__charset = 'Shift_JIS'
@@ -103,24 +110,76 @@ class Sakura:
             'shiori', default_path, saori_lib=saori_lib)
         self.__temp_mode = 0
         self.__observer = []
-        self.balloon = ninix.balloon.Balloon(self, self.prefs, debug)
-        self.surface = ninix.surface.Surface(self, self.prefs, debug)
+        balloon_callback = {
+            'is_paused': self.is_paused,
+            'notify_balloon_click': self.notify_balloon_click,
+            'notify_event': self.notify_event,
+            'notify_link_selection': self.notify_link_selection,
+            'notify_user_teach': self.notify_user_teach,
+            'position_balloons': self.position_balloons,
+            'reset_idle_time': self.reset_idle_time,
+            }
+        for key in ['get_preference']:
+            balloon_callback[key] = self.callback[key]
+        self.balloon = ninix.balloon.Balloon(balloon_callback, debug)
+        surface_callback = {
+            'about': self.about,
+            'busy': self.busy,
+            'close_sakura': self.close,
+            'enqueue_event': self.enqueue_event,
+            'getstring': self.getstring,
+            'get_balloon_size': self.get_balloon_size,
+            'get_current_balloon_name': self.get_current_balloon_name,
+            'get_keroname': self.get_keroname,
+            'get_own_balloon_name': self.get_own_balloon_name,
+            'get_prefix': self.get_prefix,
+            'get_selfname': self.get_selfname,
+            'is_running': self.is_running,
+            'network_update': self.network_update,
+            'notify_deiconified': self.notify_deiconified,
+            'notify_event': self.notify_event,
+            'notify_iconified': self.notify_iconified,
+            'notify_observer': self.notify_observer,
+            'notify_site_selection': self.notify_site_selection,
+            'notify_surface_click': self.notify_surface_click,
+            'notify_surface_mouse_motion': self.notify_surface_mouse_motion,
+            'reset_idle_time': self.reset_idle_time,
+            'select_balloon': self.select_balloon,
+            'select_kinoko': self.select_kinoko,
+            'select_nekodorif': self.select_nekodorif,
+            'select_sakura': self.select_sakura,
+            'set_balloon_direction': self.set_balloon_direction,
+            'set_balloon_position': self.set_balloon_position,
+            'vanish_sakura': self.vanish,
+            'get_current_item': self.get_current_item,
+            }
+        for key in ['close_all', 'get_preference', 'edit_preferences',
+                    'open_ghost_manager', 'show_usage', 'start_sakura',
+                    'get_ghost_list', 'get_shell_list', 'get_balloon_list',
+                    'get_nekodorif_list', 'get_kinoko_list',
+                    'get_plugin_list', 'select_plugin']:
+            surface_callback[key] = self.callback[key]
+        self.surface = ninix.surface.Surface(surface_callback, debug)
         self.keep_silence(False)
         desc, shiori_dir, use_makoto, surface_set, balloon, prefix, \
             shiori_dll, shiori_name = data
         self.new(desc, shiori_dir, use_makoto, surface_set, balloon, prefix,
                  shiori_dll, shiori_name)
 
-    def get_callback(self, name): ## FIXME
-        callback = {'close all':        self.app.close_all_ghosts,
-                    'select sakura':    self.app.select_sakura,
-                    'start':            self.app.start_sakura_cb,
-                    'select plugin':    self.app.select_plugin,
-                    'select nekodorif': self.app.select_nekodorif,
-                    'select kinoko':    self.app.select_kinoko,
-                    }
-        if name in callback:
-            return callback[name]
+    def get_current_item(self):
+        return self.current
+
+    def select_sakura(self, event, item):
+        if self.busy():
+            gtk.gdk.beep()
+            return
+        self.callback['change_sakura'](self, item, 'manual')
+
+    def select_nekodorif(self, event, item):
+        self.callback['select_nekodorif'](item, self)
+
+    def select_kinoko(self, event, item):
+        self.callback['select_kinoko'](item, self)
 
     def set_observer(self, observer):
         if observer not in self.__observer:
@@ -149,7 +208,11 @@ class Sakura:
         self.shiori_name = shiori_name
         name = (shiori_dll, shiori_name)
         self.shiori = self.__dll.request(name)
-        self.updateman = ninix.update.NetworkUpdate(self)
+        update_callback = {
+            'enqueue_event': self.enqueue_event,
+            'check_event_queue': self.check_event_queue,
+            }
+        self.updateman = ninix.update.NetworkUpdate(update_callback)
         self.audio_player = gst.element_factory_make("playbin", "player")
         fakesink = gst.element_factory_make("fakesink", "fakesink")
         self.audio_player.set_property("video-sink", fakesink)
@@ -283,6 +346,9 @@ class Sakura:
 
     reset_event = ['OnGhostChanging', 'OnShellChanging', 'OnVanishSelected']
 
+    def check_event_queue(self):
+        return bool(self.event_queue)
+
     def enqueue_event(self, event, *arglist, **argdict): ## FIXME
         for key in argdict:
             assert key in ['proc'] # trap typo, etc.
@@ -353,6 +419,23 @@ class Sakura:
     def reset_idle_time(self):
         self.idle_start = time.time()
 
+    def notify_preference_changed(self): ## FIXME
+        self.balloon.reset_fonts()
+        flag = self.callback['get_preference']('use_pna')
+        self.set_use_pna(flag)
+        scale = self.callback['get_preference']('surface_scale')
+        self.set_surface_scale(scale)
+        flag = self.callback['get_preference']('balloon_scalling')
+        self.set_balloon_scalling(flag)
+        alpha = self.callback['get_preference']('surface_alpha')
+        self.set_surface_alpha(alpha)
+        alpha = self.callback['get_preference']('balloon_alpha')
+        self.set_balloon_alpha(alpha)
+        quality = self.callback['get_preference']('animation_quality')
+        self.set_animation_quality(quality)
+        flag = self.callback['get_preference']('seriko_inactive')
+        self.set_seriko_inactive(flag)
+
     def set_use_pna(self, flag): ## FIXME
         self.surface.set_use_pna(flag)
         self.balloon.set_use_pna(flag)
@@ -380,8 +463,8 @@ class Sakura:
         result = self.surface.get_position(side)
         return result if result is not None else (0, 0)
 
-    def reset_balloon_fonts(self): ## FIXME: remove
-        self.balloon.reset_fonts()
+#    def reset_balloon_fonts(self): ## FIXME: remove
+#        self.balloon.reset_fonts()
 
     def set_balloon_position(self, side, x, y):
         self.balloon.set_position(side, x, y)
@@ -421,7 +504,7 @@ class Sakura:
     def vanish_by_myself(self):
         self.vanished_count += 1
         self.ghost_time = 0
-        self.app.stop_sakura(self.app.vanish_sakura)
+        self.callback['stop_sakura'](self.callback['vanish_sakura'])
 
     def get_name(self):
         return self.desc.get('name', unicode(_('Sakura&Unyuu'), 'utf-8'))
@@ -525,7 +608,7 @@ class Sakura:
             result = ''
         if to and result:
             def proc():
-                self.communicate.send_message(
+                self.callback['send_message'](
                     to, self.get_selfname(), result)
             communication = proc
         else:
@@ -579,13 +662,13 @@ class Sakura:
         def proc(self=self):
             self.vanished_count += 1
             self.ghost_time = 0
-            gobject.idle_add(self.app.vanish_sakura, self) ## FIXME
+            gobject.idle_add(self.callback['vanish_sakura'], self)
         self.enqueue_event('OnVanishSelected', proc=proc)
         self.vanished = 1 ## FIXME
 
     def notify_iconified(self):
         self.cantalk = 0
-        self.app.select_current_sakura()
+        self.callback['select_current_sakura']()
         if not self.passivemode:
             self.reset_script(1)
             self.stand_by(1)
@@ -594,7 +677,7 @@ class Sakura:
     def notify_deiconified(self):
         if not self.cantalk:
             self.cantalk = 1
-            self.app.select_current_sakura()
+            self.callback['select_current_sakura']()
             if not self.passivemode:
                 self.notify_event('OnWindowStateRestore')
 
@@ -768,7 +851,9 @@ class Sakura:
             return
         name = self.get_own_balloon_name()
         if item != name:
-            desc, balloon = self.app.get_balloon_description(item)
+            desc, balloon = self.callback['get_balloon_description'](item)
+        else:
+            desc, balloon = self.own_balloon
         self.balloon.hide_all()
         self.set_balloon(desc, balloon)
         self.balloon.set_balloon_default()
@@ -813,10 +898,12 @@ class Sakura:
     def set_surface_default(self, side=None):
         self.surface.set_surface_default(side)
 
+    def get_surface_scale(self):
+        return self.surface.get_scale()
+
     def set_surface_scale(self, scale):
         self.surface.set_scale(scale)
         self.balloon.set_scale(scale)
-        self.notify_observer('set scale')
 
     def get_surface_size(self, side):
         result = self.surface.get_surface_size(side)
@@ -923,7 +1010,7 @@ class Sakura:
         if set_type == 'g':
             print 'ghost', item
             if not self.surface_set:
-                shell_name, surface_set, balloon = self.app.request_shell(0)
+                shell_name, surface_set, balloon = self.callback['request_shell'](0)
                 name, surface_dir, surface_desc, surface_alias, surface, surface_tooltips = \
                       surface_set[0]
                 surface_set = None
@@ -934,7 +1021,7 @@ class Sakura:
                 balloon = self.own_balloon
         elif set_type == 's':
             print 'shell', item
-            shell_name, surface_set, balloon = self.app.request_shell(i)
+            shell_name, surface_set, balloon = self.callback['request_shell'](i)
             name, surface_dir, surface_desc, surface_alias, surface, surface_tooltips = \
                   surface_set[j]
         if not surface_set:
@@ -947,15 +1034,15 @@ class Sakura:
             name = prev_name
         self.set_surface(surface_desc, surface_alias, surface, surface_name,
                          surface_dir, surface_tooltips)
-        if self.prefs.get('ignore_default') or not balloon:
+        if self.callback['get_preference']('ignore_default') or not balloon:
             balloon_name = self.desc.get('balloon', '')
             number = None
             if balloon_name:
-                number = self.app.find_balloon_by_name(balloon_name)
+                number = self.callback['find_balloon_by_name'](balloon_name)
             if number is None:
-                default_balloon = self.prefs.get('default_balloon')
-                number = self.app.find_balloon_by_name(default_balloon) or 0
-            balloon = self.app.request_balloon(number)
+                default_balloon = self.callback['get_preference']('default_balloon')
+                number = self.callback['find_balloon_by_name'](default_balloon) or 0
+            balloon = self.callback['request_balloon'](number)
         desc, balloon = balloon
         self.set_balloon(desc, balloon)
         if not temp:
@@ -983,7 +1070,7 @@ class Sakura:
         self.notify_observer('finalize')
         self.__running = 0
         self.save_history()
-        self.communicate.rebuild_ghostdb(self, None)
+        self.callback['rebuild_ghostdb'](self, None)
         self.hide_all()
         self.surface.finalize()
         self.balloon.finalize()
@@ -996,13 +1083,13 @@ class Sakura:
         if self.clock[0] != second: ## FIXME
             if not self.__temp_mode:
                 self.ghost_time += 1
-            self.communicate.rebuild_ghostdb(
+            self.callback['rebuild_ghostdb'](
                 self,
                 self.get_selfname(),
                 self.get_surface_id(0),
-                self.get_surface_id(1)) ## FIXME
-            otherghostname = self.communicate.get_otherghostname(
-                self.get_selfname()) ## FIXME
+                self.get_surface_id(1))
+            otherghostname = self.callback['get_otherghostname'](
+                self.get_selfname())
             if otherghostname != self.old_otherghostname:
                 args = []
                 args.extend(otherghostname)
@@ -1042,7 +1129,7 @@ class Sakura:
             self.__balloon_life = 0
             self.stand_by(0)
             self.notify_event('OnBalloonClose', self.__current_script)
-            if self.prefs.get('sink_after_talk'):
+            if self.callback['get_preference']('sink_after_talk'):
                 self.surface.lower_all()
         elif self.event_queue and self.handle_event():
             pass
@@ -1098,11 +1185,11 @@ class Sakura:
                 if self.__temp_mode == 1:
                     time.sleep(1.4)
                     self.finalize()
-                    self.app.close_ghost(self)
-                    self.app.reset_sstp_flag()
+                    self.callback['close_ghost'](self)
+                    self.callback['reset_sstp_flag']()
                     return False
                 else:
-                    self.app.reset_sstp_flag()
+                    self.callback['reset_sstp_flag']()
                     self.leave_temp_mode()
                     return True
             else:
@@ -1112,7 +1199,7 @@ class Sakura:
             self.hide_all()
             sys.stdout.write('reloading....\n')
             self.shiori.unload()
-            self.app.stop_sakura(self, self.app.reload_current_sakura, self)
+            self.callback['stop_sakura'](self, self.callback['reload_current_sakura'], self)
             self.restart() ## FIXME
             sys.stdout.write('done.\n')
             self.enqueue_event(*self.reload_event)
@@ -1132,7 +1219,7 @@ class Sakura:
         return True
 
     def quit(self):
-        self.app.stop_sakura(self)
+        self.callback['stop_sakura'](self)
 
     ###   SCRIPT PLAYER   ###
     def start_script(self, script, origin=None):
@@ -1165,7 +1252,7 @@ class Sakura:
         self.balloon.set_balloon_default()
         self.current_time = time.localtime(time.time())
         self.reset_idle_time()
-        if self.prefs.get('raise_before_talk'):
+        if self.callback['get_preference']('raise_before_talk'):
             self.raise_all()
 
     def __yen_e(self, args):
@@ -1305,12 +1392,12 @@ class Sakura:
             self.script_wait = time.time() + amount
 
     def __yen_w(self, args):
-        script_speed = self.prefs.get('script_speed')
+        script_speed = self.callback['get_preference']('script_speed')
         if not self.quick_session and script_speed >= 0:
             self.__set_weight(args[0], 0.05) # 50ms
 
     def __yen__w(self, args):
-        script_speed = self.prefs.get('script_speed')
+        script_speed = self.callback['get_preference']('script_speed')
         if not self.quick_session and script_speed >= 0:
             self.__set_weight(args[0], 0.001) # 1ms
 
@@ -1391,10 +1478,10 @@ class Sakura:
         self.quit()
 
     def __yen_plus(self, args):
-        self.app.select_ghost(self, 1)
+        self.callback['select_ghost'](self, 1)
 
     def __yen__plus(self, args):
-        self.app.select_ghost(self, 0)
+        self.callback['select_ghost'](self, 0)
 
     def __yen_m(self, args):
         self.write_sstp_handle(self.expand_meta(args[0]))
@@ -1459,14 +1546,14 @@ class Sakura:
                 else:
                     self.balloon.open_inputbox(args[2])
         elif args[0:2] == ['open', 'configurationdialog']:
-            self.prefs.edit_preferences()
+            self.callback['edit_preferences']()
         elif args[0:2] == ['change', 'shell'] and argc > 2:
-            self.app.select_shell_by_name(self, args[2], 0)
+            self.callback['select_shell_by_name'](self, args[2], 0)
         elif args[0:2] == ['change', 'ghost'] and argc > 2:
             if args[2] == 'random':
-                self.app.select_ghost(self, 0, 0)
+                self.callback['select_ghost'](self, 0, 0)
             else:
-                self.app.select_ghost_by_name(self, args[2], 0)
+                self.callback['select_ghost_by_name'](self, args[2], 0)
         elif args[0:1] == ['updatebymyself']:
             if not self.busy(check_updateman=False):
                 self.__update()
@@ -1598,7 +1685,7 @@ class Sakura:
             count = self.balloon.get_text_count(self.script_side)
             if self.surface.invoke_talk(self.script_side, surface_id, count):
                 self.balloon.reset_text_count(self.script_side)
-            script_speed = self.prefs.get('script_speed')
+            script_speed = self.callback['get_preference']('script_speed')
             if script_speed > 0:
                 self.script_wait = time.time() + script_speed * 0.02
             return
@@ -1613,7 +1700,7 @@ class Sakura:
             text = self.expand_meta(node[1])
             if self.anchor:
                 self.anchor[1] = ''.join((self.anchor[1], text))
-            script_speed = self.prefs.get('script_speed')
+            script_speed = self.callback['get_preference']('script_speed')
             if not self.quick_session and script_speed >= 0:
                 self.processed_text = text
             else:
@@ -1754,8 +1841,8 @@ class Sakura:
 
 class VanishDialog:
 
-    def __init__(self, sakura):
-        self.__sakura = sakura
+    def __init__(self, callback):
+        self.callback = callback
         self.window = gtk.Dialog()
         self.window.connect('delete_event', self.cancel)
         self.window.set_title('Vanish')
@@ -1785,12 +1872,12 @@ class VanishDialog:
 
     def ok(self, widget, event=None):
         self.window.hide()
-        self.__sakura.notify_vanish_confirmation()
+        self.callback['notify_vanish_confirmation']()
         return True
 
     def cancel(self, widget, event=None):
         self.window.hide()
-        self.__sakura.notify_event('OnVanishCancel')
+        self.callback['notify_event']('OnVanishCancel')
         return True
 
 
index cde6b4b..0d3cb27 100644 (file)
@@ -25,8 +25,8 @@ from sstplib import AsynchronousSSTPServer, BaseSSTPRequestHandler
 
 class SSTPServer(AsynchronousSSTPServer):
 
-    def __init__(self, address, app):
-        self.__app = app
+    def __init__(self, address, callback):
+        self.callback = callback
         AsynchronousSSTPServer.__init__(self, address, SSTPRequestHandler)
         self.request_handler = None
 
@@ -57,30 +57,11 @@ class SSTPServer(AsynchronousSSTPServer):
     def close(self):
         self.socket.close()
 
-    def get_current_sakura(self):
-        return self.__app.get_current_sakura()
-
-    def if_ghost(self, ifghost):
-        return self.__app.if_ghost(ifghost)
-
-    def get_ghost_names(self):
-        return self.__app.get_ghost_names()
-
-    def check_request_queue(self, sender):
-        return self.__app.check_request_queue(sender)
-
-    def enqueue_script_if_ghost(
-        self, if_ghost, script, sender, handle,
-        address, show_sstp_marker, use_translator, entry_db):
-        self.__app.enqueue_script_if_ghost(
-            if_ghost, script, sender, handle,
-            address, show_sstp_marker, use_translator, entry_db)
 
 class SSTPRequestHandler(BaseSSTPRequestHandler):
 
     def handle(self):
-        sakura = self.server.get_current_sakura()
-        if not sakura.cantalk:
+        if not self.server.callback['get_sakura_cantalk']():
             self.error = self.version = None
             if not self.parse_request(self.rfile.readline()):
                 return
@@ -157,8 +138,7 @@ class SSTPRequestHandler(BaseSSTPRequestHandler):
         event = self.get_event()
         if event is None:
             return
-        sakura = self.server.get_current_sakura()
-        script = sakura.get_event_response(*event)
+        script = self.server.callback['get_event_response'](*event)
         if script and self.check_script(script):
             return
         if version == 1.0:
@@ -184,7 +164,7 @@ class SSTPRequestHandler(BaseSSTPRequestHandler):
             address = self.client_address
         self.send_response(200) # OK
         show_sstp_marker, use_translator = self.get_options()
-        self.server.enqueue_script_if_ghost(
+        self.server.callback['enqueue_script_if_ghost'](
             if_ghost, script, sender, handle,
             address, show_sstp_marker, use_translator, entry_db)
 
@@ -196,17 +176,16 @@ class SSTPRequestHandler(BaseSSTPRequestHandler):
         if entry_db is None or entry_db.is_empty():
             self.send_response(200) # OK
             show_sstp_marker, use_translator = self.get_options()
-            sakura = self.server.get_current_sakura()
-            sakura.enqueue_script(script, sender, handle, address,
-                                  show_sstp_marker, use_translator)
+            self.server.callback['enqueue_script'](
+                script, sender, handle, address,
+                show_sstp_marker, use_translator)
         elif self.server.request_handler:
             self.send_response(409) # Conflict
         else:
             show_sstp_marker, use_translator = self.get_options()
-            sakura = self.server.get_current_sakura()
-            sakura.enqueue_script(script, sender, handle, address,
-                                  show_sstp_marker, use_translator,
-                                  entry_db, self.server)
+            self.server.callback['enqueue_script'](
+                script, sender, handle, address,
+                show_sstp_marker, use_translator, entry_db, self.server)
             self.server.request_handler = self # keep alive
 
     PROHIBITED_TAGS = [r'\j', r'\-', r'\+', r'\_+', r'\!']
@@ -255,11 +234,11 @@ class SSTPRequestHandler(BaseSSTPRequestHandler):
                     continue
                 script = line[7:].strip()
                 if current: # NOTIFY
-                    ghost = self.ghost_names()
+                    ghost = self.server.callback['get_ghost_name']()
                     if ghost == if_ghost:
                         return script, if_ghost
                 else: # SEND
-                    if self.server.if_ghost(if_ghost):
+                    if self.server.callback['if_ghost'](if_ghost):
                         return script, if_ghost
                 if default is None:
                     default = script, if_ghost
@@ -352,10 +331,6 @@ class SSTPRequestHandler(BaseSSTPRequestHandler):
             result = host ==  '127.0.0.1'
         return result
 
-    def ghost_names(self):
-        sakura = self.server.get_current_sakura()
-        return '%s,%s' % (sakura.get_selfname(), sakura.get_keroname())
-
     # EXECUTE
     def do_EXECUTE_1_0(self):
         self.handle_command()
@@ -390,7 +365,7 @@ class SSTPRequestHandler(BaseSSTPRequestHandler):
             return
         elif command == 'getname':
             self.send_response(200)
-            name = self.ghost_names()
+            name = self.server.callback['get_ghost_name']()
             self.wfile.write(''.join((name.encode('utf-8', 'ignore'), '\r\n')))
             self.wfile.write('Charset: UTF-8\r\n')
         elif command == 'getversion':
@@ -398,21 +373,19 @@ class SSTPRequestHandler(BaseSSTPRequestHandler):
             self.wfile.write('ninix-aya %s\r\n' % ninix.version.VERSION)
         elif command == 'quiet':
             self.send_response(200)
-            sakura = self.server.get_current_sakura()
-            sakura.keep_silence(True)
+            self.server.callback['keep_silence'](True)
         elif command == 'restore':
             self.send_response(200)
-            sakura = self.server.get_current_sakura()
-            sakura.keep_silence(False)
+            self.server.callback['keep_silence'](False)
         elif command == 'getnames':
             self.send_response(200)
-            for name in self.server.get_ghost_names():
+            for name in self.server.callback['get_ghost_names']():
                 self.wfile.write(
                     ''.join((name.encode('utf-8', 'ignore'), '\r\n')))
             self.wfile.write('Charset: UTF-8\r\n')
         elif command == 'checkqueue':
             self.send_response(200)
-            count, total = self.server.check_request_queue(sender)
+            count, total = self.server.callback['check_request_queue'](sender)
             self.wfile.write(''.join((count, '\r\n')))
             self.wfile.write(''.join((total, '\r\n')))
         else:
@@ -428,7 +401,7 @@ class SSTPRequestHandler(BaseSSTPRequestHandler):
             return None
         return unicode(command, charset, 'replace').lower()
 
-    def do_COMMUNICATE_1_1(self): ## FIXME
+    def do_COMMUNICATE_1_1(self):
         if not self.check_decoder():
             return
         sender = self.get_sender()
@@ -438,8 +411,8 @@ class SSTPRequestHandler(BaseSSTPRequestHandler):
         if sentence is None:
             return
         self.send_response(200) # OK
-        sakura = self.server.get_current_sakura()
-        sakura.enqueue_event('OnCommunicate', sender, sentence)
+        self.server.callback['enqueue_event'](
+            'OnCommunicate', sender, sentence)
         return
 
     def get_sentence(self):
index 1f838e8..ac6f245 100644 (file)
@@ -37,9 +37,7 @@ class Surface:
         keymap_old = {}
         keymap_new = {}
 
-    def __init__(self, sakura, prefs, debug=0):
-        self.__sakura = sakura
-        self.prefs = prefs
+    def __init__(self, callback, debug=0):
         self.debug = debug
         self.window = []
         self.__menu = None
@@ -47,6 +45,7 @@ class Surface:
         self.desc = None
         self.__use_pna = False
         self.__alpha_channel = None
+        self.callback = callback
 
     def set_debug(self, debug):
         self.debug = debug
@@ -77,6 +76,7 @@ class Surface:
         for surface_window in self.window:
             surface_window.set_scale(scale)
             surface_window.reset_surface()
+        self.callback['notify_observer']('set scale')
 
     def set_animation_quality(self, quality):
         for surface_window in self.window:
@@ -129,12 +129,12 @@ class Surface:
                 gtk_window.deiconify()
 
     def window_state(self, window, event):
-        if not self.__sakura.is_running():
+        if not self.callback['is_running']():
             return
         if event.new_window_state & gtk.gdk.WINDOW_STATE_ICONIFIED:
             if window == self.window[0].window:
-                self.__sakura.notify_iconified()
-                self.__sakura.notify_observer('iconified')
+                self.callback['notify_iconified']()
+                self.callback['notify_observer']('iconified')
             for surface_window in self.window:
                 gtk_window = surface_window.window
                 if gtk_window != window and \
@@ -149,18 +149,17 @@ class Surface:
                    gtk.gdk.WINDOW_STATE_ICONIFIED:
                     gtk_window.deiconify()
             if window == self.window[0].window:
-                self.__sakura.notify_deiconified()
+                self.callback['notify_deiconified']()
         return
 
     def delete(self, window, event):
-        self.__sakura.quit()
-        return False
+        return True
 
     def key_press(self, window, event):
         name = self.keymap_old.get(event.keyval, event.string)
         keycode = self.keymap_new.get(event.keyval, event.string)
         if name or keycode:
-            self.__sakura.notify_event('OnKeyPress', name, keycode)
+            self.callback['notify_event']('OnKeyPress', name, keycode)
         return True
 
     def window_stick(self, action):
@@ -312,34 +311,32 @@ class Surface:
         for surface_window in self.window:
             surface_window.destroy()
         self.window = []
-        for name, side, default, alias in [('sakura', 0, '0', alias0),
-                                           ('kero', 1, '10', alias1)]:
-            if name == 'sakura':
-                title = self.__sakura.get_selfname() or \
-                        ''.join(('surface.', name)) ## FIXME
-            else:
-                title = self.__sakura.get_keroname() or \
-                        ''.join(('surface.', name)) ## FIXME
-            skip_taskbar = bool(side >= 1)
-            gtk_window = self.create_gtk_window(title, skip_taskbar)
-            seriko = self.get_seriko(surface)
-            tooltips = {}
-            if name in self.__tooltips:
-                tooltips = self.__tooltips[name]
-            surface_window = SurfaceWindow(
-                gtk_window, side, self.__sakura, self.prefs, desc, alias,
-                surface, tooltips, pixbufs, seriko, region, mayuna, bind[name],
-                default, self.__use_pna, self.__alpha_channel, self.debug)
-            self.window.append(surface_window)
         self.__surface = surface
-        self.__menu = ninix.menu.Menu(self.__sakura, self)
+        self.add_window(0, '0', alias0, mayuna, bind['sakura'])
+        self.add_window(1, '10', alias1, mayuna, bind['kero'])
+        menu_callback = {
+            'stick_window': self.window_stick,
+            'toggle_bind': self.toggle_bind,
+            }
+        for key in ['network_update', 'vanish_sakura', 'edit_preferences',
+                    'open_ghost_manager', 'show_usage', 'about',
+                    'close_sakura', 'close_all', 'getstring',
+                    'notify_site_selection', 'get_ghost_list', 'select_sakura',
+                    'start_sakura', 'get_shell_list', 'get_balloon_list',
+                    'get_own_balloon_name', 'select_balloon',
+                    'get_current_balloon_name', 'get_current_item',
+                    'get_plugin_list', 'select_plugin',
+                    'get_nekodorif_list', 'select_nekodorif',
+                    'get_kinoko_list', 'select_kinoko']:
+            menu_callback[key] = self.callback[key]
+        self.__menu = ninix.menu.Menu(menu_callback)
         top_dir = self.prefix
         name = self.desc.get('menu.background.bitmap.filename',
                              'menu_background.png')
         name = name.replace('\\', '/')
         path = os.path.join(top_dir, name)
         if not os.path.exists(path):
-            top_dir = os.path.join(self.__sakura.get_prefix(),
+            top_dir = os.path.join(self.callback['get_prefix'](),
                                    'ghost', 'master')
             path = os.path.join(top_dir, 'menu_background.png')
             if os.path.exists(path):
@@ -377,30 +374,47 @@ class Surface:
         if path_background:
             self.__menu.set_pixmap(
                 path_background, path_sidebar, path_foreground)
-        fontcolor_r = self.desc.getint('menu.background.font.color.r', 0)
-        fontcolor_g = self.desc.getint('menu.background.font.color.g', 0)
-        fontcolor_b = self.desc.getint('menu.background.font.color.b', 0)
+        fontcolor_r = self.desc.get_with_type('menu.background.font.color.r', int, 0)
+        fontcolor_g = self.desc.get_with_type('menu.background.font.color.g', int, 0)
+        fontcolor_b = self.desc.get_with_type('menu.background.font.color.b', int, 0)
         background = (fontcolor_r, fontcolor_g, fontcolor_b)
-        fontcolor_r = self.desc.getint('menu.foreground.font.color.r', 0)
-        fontcolor_g = self.desc.getint('menu.foreground.font.color.g', 0)
-        fontcolor_b = self.desc.getint('menu.foreground.font.color.b', 0)
+        fontcolor_r = self.desc.get_with_type('menu.foreground.font.color.r', int, 0)
+        fontcolor_g = self.desc.get_with_type('menu.foreground.font.color.g', int, 0)
+        fontcolor_b = self.desc.get_with_type('menu.foreground.font.color.b', int, 0)
         foreground = (fontcolor_r, fontcolor_g, fontcolor_b)
         self.__menu.set_fontcolor(background, foreground)
         self.__menu.create_mayuna_menu(self.get_mayuna_menu())
 
-    def add_window(self, side, default):
-        assert side >= 2 and len(self.window) == side ## FIXME
-        name = 'char%d' % side
-        title = ''.join(('surface.', name))
-        gtk_window = self.create_gtk_window(title, 1)
+    def add_window(self, side, default, alias=None, mayuna={}, bind={}):
+        assert len(self.window) == side
+        if side == 0:
+            name = 'sakura'
+            title = self.callback['get_selfname']() or \
+                ''.join(('surface.', name))
+        elif side == 1:
+            name = 'kero'
+            title = self.callback['get_keroname']() or \
+                ''.join(('surface.', name))
+        else:
+            name = 'char%d' % side
+            title = ''.join(('surface.', name))
+        skip_taskbar = bool(side >= 1)
+        gtk_window = self.create_gtk_window(title, skip_taskbar)
         seriko = self.get_seriko(self.__surface)
         tooltips = {}
         if name in self.__tooltips:
             tooltips = self.__tooltips[name]
+        surface_window_callback = {}
+        for key in ['enqueue_event', 'notify_observer', 'get_preference',
+                    'set_balloon_direction', 'reset_idle_time',
+                    'notify_surface_click', 'notify_event', 'busy',
+                    'notify_surface_mouse_motion']:
+            surface_window_callback[key] = self.callback[key]
         surface_window = SurfaceWindow(
-            gtk_window, side, self.__sakura, self.prefs, self.desc, None,
+            gtk_window, side, surface_window_callback, self.desc, alias,
             self.__surface, tooltips, self.__pixbufs, seriko, self.__region,
-            {}, {}, default, self.__use_pna, self.__alpha_channel, self.debug)
+            mayuna, bind, default, self.__use_pna, self.__alpha_channel,
+            self.debug)
         self.window.append(surface_window)
         surface_window.set_scale(self.__scale)
         ##surface_window.set_surface(default)
@@ -524,7 +538,7 @@ class Surface:
             x, y = self.get_position(side)
             sox, soy = self.window[side].get_surface_offset()
             w, h = self.get_surface_size(side)
-            bw, bh = self.__sakura.get_balloon_size(side)
+            bw, bh = self.callback['get_balloon_size'](side)
             ox, oy = self.get_balloon_offset(side)
             direction = self.get_direction(side)
             align = self.get_alignment(side)
@@ -543,7 +557,7 @@ class Surface:
                     by = min(y + soy + oy, top + scrn_h - bh)
             self.set_position(side, x, y)
             self.set_direction(side, direction)
-            self.__sakura.set_balloon_position(side, bx, by)
+            self.callback['set_balloon_position'](side, bx, by)
 
     def reset_position(self):
         left, top, scrn_w, scrn_h = ninix.pix.get_workarea()
@@ -556,13 +570,13 @@ class Surface:
                     y = top
                 else:
                     y = top + scrn_h - h
-                bw, bh = self.__sakura.get_balloon_size(side)
+                bw, bh = self.callback['get_balloon_size'](side)
                 ox, oy = self.get_balloon_offset(side)
                 direction = 0 # left
                 bx = max(x - bw + ox, left)
             else:
-                b0w, b0h = self.__sakura.get_balloon_size(side - 1)
-                b1w, b1h = self.__sakura.get_balloon_size(side)
+                b0w, b0h = self.callback['get_balloon_size'](side - 1)
+                b1w, b1h = self.callback['get_balloon_size'](side)
                 o0x, o0y = self.get_balloon_offset(side - 1)
                 o1x, o1y = self.get_balloon_offset(side)
                 w, h = self.get_surface_size(side)
@@ -588,14 +602,14 @@ class Surface:
                     by = min(y + oy, top + scrn_h - bh)
             self.set_position(side, x, y)
             self.set_direction(side, direction)
-            self.__sakura.set_balloon_position(side, bx, by)
+            self.callback['set_balloon_position'](side, bx, by)
             s0x, s0y, s0w, s0h = x, y, w, h ## FIXME
-        ##self.__sakura.notify_observer('set position')
+        ##self.callback['notify_observer']('set position')
 
     def set_position(self, side, x, y):
         if len(self.window) > side:
             self.window[side].set_position(x, y)
-        self.__sakura.notify_observer('set position')
+        self.callback['notify_observer']('set position')
 
     def get_position(self, side):
         if len(self.window) > side:
@@ -634,38 +648,38 @@ class Surface:
     def show(self, side):
         if len(self.window) > side:
             self.window[side].show()
-            self.__sakura.notify_observer('show')
-            self.__sakura.notify_observer('raise', (side)) ## FIXME
+            self.callback['notify_observer']('show')
+            self.callback['notify_observer']('raise', (side)) # XXX
 
     def hide_all(self):
         for side in range(len(self.window)):
             self.window[side].hide()
-        self.__sakura.notify_observer('hide')
+        self.callback['notify_observer']('hide')
 
     def hide(self, side):
         if len(self.window) > side:
             self.window[side].hide()
-        self.__sakura.notify_observer('hide')
+        self.callback['notify_observer']('hide')
 
     def raise_all(self):
         for side in range(len(self.window)):
             self.window[side].raise_()
-            self.__sakura.notify_observer('raise', (side))
+            self.callback['notify_observer']('raise', (side))
 
     def raise_(self, side):
         if len(self.window) > side:
             self.window[side].raise_()
-            self.__sakura.notify_observer('raise', (side))
+            self.callback['notify_observer']('raise', (side))
 
     def lower_all(self):
         for side in range(len(self.window)):
             self.window[side].lower()
-        self.__sakura.notify_observer('lower')
+        self.callback['notify_observer']('lower')
 
     def lower(self, side):
         if len(self.window) > side:
             self.window[side].lower()
-        self.__sakura.notify_observer('lower')
+        self.callback['notify_observer']('lower')
 
     def invoke(self, side, actor_id):
         if len(self.window) > side:
@@ -685,7 +699,7 @@ class Surface:
         pixbuf = None
         if path is not None:
             try:
-                pixbuf = ninix.pix.create_pixbuf_from_file(path, is_pnr=0)
+                pixbuf = ninix.pix.create_pixbuf_from_file(path, is_pnr=False)
             except:
                 pixbuf = None
         for window in self.window:
@@ -769,13 +783,12 @@ class SurfaceWindow:
         ('text/plain', 0, 0),
         ]
 
-    def __init__(self, window, side, sakura, prefs, desc, alias, surface, tooltips,
+    def __init__(self, window, side, callback, desc, alias, surface, tooltips,
                  pixbuf, seriko, region, mayuna, bind,
                  default_id, use_pna, alpha, debug):
         self.window = window
         self.side = side
-        self.__sakura = sakura
-        self.prefs = prefs
+        self.callback = callback
         self.desc = desc
         self.alias = alias
         self.tooltips = tooltips
@@ -813,7 +826,6 @@ class SurfaceWindow:
                               gtk.gdk.POINTER_MOTION_MASK|
                               gtk.gdk.POINTER_MOTION_HINT_MASK|
                               gtk.gdk.SCROLL_MASK)
-        self.callbacks = []
         for signal, func in [('expose_event',         self.redraw),
                              ('button_press_event',   self.button_press),
                              ('button_release_event', self.button_release),
@@ -821,7 +833,7 @@ class SurfaceWindow:
                              ('drag_data_received',   self.drag_data_received),
                              ('scroll_event',         self.scroll),
                              ]:
-            self.callbacks.append(self.darea.connect(signal, func))
+            self.darea.connect(signal, func)
         self.darea.drag_dest_set(gtk.DEST_DEFAULT_ALL, self.dnd_targets,
                                  gtk.gdk.ACTION_COPY)
         self.window.add(self.darea)
@@ -868,7 +880,7 @@ class SurfaceWindow:
                 if scheme == 'file' and os.path.exists(pathname):
                     filelist.append(pathname)
             if filelist:
-                self.__sakura.enqueue_event(
+                self.callback['enqueue_event'](
                     'OnFileDrop2', chr(1).join(filelist), self.side)
         return True
 
@@ -928,7 +940,7 @@ class SurfaceWindow:
         # relocate window
         self.set_position(x, y)
         if self.side < 2:
-            self.__sakura.notify_observer('set surface')
+            self.callback['notify_observer']('set surface')
 
     def iter_mayuna(self, surface_width, surface_height, mayuna, done):
         for surface, interval, method, args in mayuna.patterns:
@@ -1172,7 +1184,7 @@ class SurfaceWindow:
         cr.paint()
         cr.set_source_pixbuf(self.current_surface_pixbuf, 0, 0)
         cr.paint_with_alpha(self.__alpha_channel)
-        if self.prefs.get('check_collision'):
+        if self.callback['get_preference']('check_collision'):
             self.draw_region()
 
     def remove_overlay(self, actor):
@@ -1190,26 +1202,26 @@ class SurfaceWindow:
         self.__move(xoffset, yoffset)
         if self.side < 2:
             args = (self.side, xoffset, yoffset)
-            self.__sakura.notify_observer('move surface', args) # animation
+            self.callback['notify_observer']('move surface', args) # animation
 
     def get_balloon_offset(self):
         path, config = self.surface[''.join(('surface', self.surface_id))]
         side = self.side
         if side == 0:
             name = 'sakura'
-            x = config.getint('%s.balloon.offsetx' % name)
-            y = config.getint('%s.balloon.offsety' % name)
+            x = config.get_with_type('%s.balloon.offsetx' % name, int)
+            y = config.get_with_type('%s.balloon.offsety' % name, int)
         elif side == 1:
             name = 'kero'
-            x = config.getint('%s.balloon.offsetx' % name)
-            y = config.getint('%s.balloon.offsety' % name)
+            x = config.get_with_type('%s.balloon.offsetx' % name, int)
+            y = config.get_with_type('%s.balloon.offsety' % name, int)
         else:
             name = 'char%d' % side
             x, y = None, None # XXX
         if x is None:
-            x = self.desc.getint('%s.balloon.offsetx' % name, 0)
+            x = self.desc.get_with_type('%s.balloon.offsetx' % name, int, 0)
         if y is None:
-            y = self.desc.getint('%s.balloon.offsety' % name, 0)
+            y = self.desc.get_with_type('%s.balloon.offsety' % name, int, 0)
         x *= self.__scale / 100
         y *= self.__scale / 100
         return x, y
@@ -1247,23 +1259,22 @@ class SurfaceWindow:
                 return part
         return ''
 
-    def get_config_int(self, name, scaling=False):
+    def __get_with_scaling(self, name, conv):
         basename = ''.join(('surface', self.surface_id))
         path, config = self.surface[basename]
-        value = config.getint(name)
+        value = config.get_with_type(name, conv)
         if value is not None:
-            if scaling:
-                value = int(value * self.__scale / 100)
+            value = conv(value * self.__scale / 100)
         return value
 
     def get_center(self):
-        centerx = self.get_config_int('point.centerx', scaling=True)
-        centery = self.get_config_int('point.centery', scaling=True)
+        centerx = self.__get_with_scaling('point.centerx', int)
+        centery = self.__get_with_scaling('point.centery', int)
         return centerx, centery
 
     def get_kinoko_center(self):
-        centerx = self.get_config_int('point.kinoko.centerx', scaling=True)
-        centery = self.get_config_int('point.kinoko.centery', scaling=True)
+        centerx = self.__get_with_scaling('point.kinoko.centerx', int)
+        centery = self.__get_with_scaling('point.kinoko.centery', int)
         return centerx, centery
 
     def get_direction(self):
@@ -1271,7 +1282,7 @@ class SurfaceWindow:
 
     def set_direction(self, direction):
         self.direction = direction # 0: left, 1: right
-        self.__sakura.set_balloon_direction(self.side, direction)
+        self.callback['set_balloon_direction'](self.side, direction)
 
     def set_position(self, x, y):
         self.position = (x, y)
@@ -1300,7 +1311,7 @@ class SurfaceWindow:
         old_x, old_y = self.get_position()
         if new_x != old_x or new_y != old_y or new_y != y - q:
             self.set_position(new_x, new_y)
-            self.__sakura.notify_observer('set position')
+            self.callback['notify_observer']('set position')
 
     def get_position(self):
         return self.position
@@ -1330,8 +1341,6 @@ class SurfaceWindow:
 
     def destroy(self):
         self.reset_pixbuf_cache()
-        for tag in self.callbacks:
-            self.darea.disconnect(tag)
         self.seriko.terminate(self)
         self.window.remove(self.darea)
         self.darea.destroy()
@@ -1360,14 +1369,15 @@ class SurfaceWindow:
         self.window.window.lower()
 
     def button_press(self, window, event):
-        self.__sakura.reset_idle_time()
+        self.callback['reset_idle_time']()
         x = int(event.x)
         y = int(event.y)
         x = int(x * 100 / self.__scale)
         y = int(y * 100 / self.__scale)
         click = 1 if event.type == gtk.gdk.BUTTON_PRESS else 2
-        self.__sakura.notify_observer('raise', (self.side)) # automagical raise
-        self.__sakura.notify_surface_click(
+        # automagical raise
+        self.callback['notify_observer']('raise', (self.side))
+        self.callback['notify_surface_click'](
             event.button, click, self.side, x, y)
         if click == 1 and event.button == 1:
             self.window.begin_move_drag(
@@ -1389,7 +1399,7 @@ class SurfaceWindow:
             if part == '':
                 self.window.set_tooltip_text(None)
                 self.darea.window.set_cursor(None)
-                self.__sakura.notify_event(
+                self.callback['notify_event'](
                     'OnMouseLeave', x, y, '', self.side, self.__current_part)
             else:
                 if part in self.tooltips:
@@ -1399,16 +1409,16 @@ class SurfaceWindow:
                     self.window.set_tooltip_text(None)
                 cursor = gtk.gdk.Cursor(gtk.gdk.HAND1)
                 self.darea.window.set_cursor(cursor)
-                self.__sakura.notify_event(
+                self.callback['notify_event'](
                     'OnMouseEnter', x, y, '', self.side, part)
         self.__current_part = part
-        if not self.__sakura.busy():
+        if not self.callback['busy']():
             if state & gtk.gdk.BUTTON1_MASK or \
                state & gtk.gdk.BUTTON2_MASK or \
                state & gtk.gdk.BUTTON3_MASK:
                 pass
             else:
-                self.__sakura.notify_surface_mouse_motion(
+                self.callback['notify_surface_mouse_motion'](
                     self.side, x, y, part)
         return True
 
@@ -1425,8 +1435,8 @@ class SurfaceWindow:
             count = 0
         if count != 0:
             part = self.get_touched_region(x, y)
-            self.__sakura.notify_event('OnMouseWheel',
-                                       x, y, count, self.side, part)
+            self.callback['notify_event'](
+                'OnMouseWheel', x, y, count, self.side, part)
         return True
 
     def toggle_bind(self, bind_id):
@@ -1440,17 +1450,19 @@ class SurfaceWindow:
         x, y, state = event.x, event.y, event.state
         x = int(x * 100 / self.__scale)
         y = int(y * 100 / self.__scale)
-        self.__sakura.notify_event('OnMouseEnterAll', x, y, '', self.side, '')
+        self.callback['notify_event'](
+            'OnMouseEnterAll', x, y, '', self.side, '')
 
     def window_leave_notify(self, window, event):
         x, y, state = event.x, event.y, event.state
         x = int(x * 100 / self.__scale)
         y = int(y * 100 / self.__scale)
         if self.__current_part != '': # XXX
-            self.__sakura.notify_event(
+            self.callback['notify_event'](
                 'OnMouseLeave', x, y, '', self.side, self.__current_part)
             self.__current_part = ''
-        self.__sakura.notify_event('OnMouseLeaveAll', x, y, '', self.side, '')
+        self.callback['notify_event'](
+            'OnMouseLeaveAll', x, y, '', self.side, '')
         return True
 
     def configure(self, window, event):
index de26a88..dd670e7 100644 (file)
@@ -24,8 +24,8 @@ class NetworkUpdate:
 
     __BACKUP_SUFFIX = '.BACKUP'
 
-    def __init__(self, sakura):
-        self.__sakura = sakura
+    def __init__(self, callback):
+        self.callback = callback
         self.event_queue = []
         self.state = None
         self.backups = []
@@ -71,8 +71,7 @@ class NetworkUpdate:
 
     def interrupt(self):
         self.event_queue = []
-        if self.__sakura:
-            self.__sakura.enqueue_event('OnUpdateFailure', 'artificial')
+        self.callback['enqueue_event']('OnUpdateFailure', 'artificial')
         self.state = None
         self.stop(revert=1)
 
@@ -103,7 +102,7 @@ class NetworkUpdate:
         return time.time() - self.timestamp > self.timeout
 
     def run(self):
-        if self.state is None or (self.__sakura and self.__sakura.event_queue):
+        if self.state is None or self.callback['check_event_queue']():
             return 0
         elif self.state == 0:
             self.start_updates()
@@ -399,7 +398,8 @@ def test():
     import sys
     if len(sys.argv) != 3:
         raise SystemExit, 'Usage: update.py homeurl ghostdir\n'
-    update = NetworkUpdate(None)
+    update = NetworkUpdate({'enqueu_event': lambda *a: None,
+                            'check_event_queue': lambda *a: None,})
     update.start(sys.argv[1], sys.argv[2], timeout=60)
     while 1:
         state = update.state
index 54cf7c3..2f586f6 100644 (file)
@@ -10,7 +10,7 @@
 #  PURPOSE.  See the GNU General Public License for more details.
 #
 
-NUMBER = '4.0.4'
+NUMBER = '4.0.5'
 CODENAME = 'jump off into never-never land'
 
 VERSION = '%s (%s)' % (NUMBER, CODENAME)