OSDN Git Service

Prepare for Release of 4.3.5
authorShyouzou Sugitani <shy@users.sourceforge.jp>
Sun, 4 Dec 2011 02:39:44 +0000 (11:39 +0900)
committerShyouzou Sugitani <shy@users.sourceforge.jp>
Sun, 4 Dec 2011 02:39:44 +0000 (11:39 +0900)
ChangeLog
lib/ninix/install.py
lib/ninix/menu.py
lib/ninix/pix.py
lib/ninix/prefs.py
lib/ninix/sakura.py
lib/ninix/version.py
lib/ninix_main.py
setup.py

index 3f5ce01..9ea8ff3 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,9 +1,34 @@
+Sun December 4 2011   Shyouzou Sugitani <shy@users.sourceforge.jp>
+       * バージョン4.3.5リリース.
+       * Windows環境でもGtk+に渡す場合にはファイル名の文字コードを
+         utf-8に変換するようにした.
+       * 画像ファイルの読み込みが全てpix.py経由になるように修正した.
+         (menu.pyに直接Gtk+を呼んでいる部分があった.)
+
+Sat December 3 2011   Shyouzou Sugitani <shy@users.sourceforge.jp>
+       * ゴースト等のインストール先ディレクトリ名の文字コードに
+         Windows環境の場合にはmbcsを, それ以外の環境ではutf-8を
+         使用するようにした.(これまでは環境によらずutf-8を使用していた.)
+         (Thanks to Donさん)
+       * ユーザーが選択したシェルをSETTINGSファイルに記録しておき,
+         次回の起動の際にはそのシェルで起動するようにした.
+         そのためメニューの召喚/交代からはシェルの指定を削除した.
+       * シェルとバルーンの情報を管理するためのMemeクラスを追加した.
+         (まだ構想段階で実際には使用していない.)
+         シェル/バルーンを構築するのに必要な情報(baseinfo)とユーザーが
+         シェル/バルーンにアクセスするための情報(menuitem)を保持するが,
+         Holonクラスと異なりシェル/バルーンのinstanceへの参照は保持しない.
+       * Holonクラスのデータ構造を一部変更した.
+
 Wed November 30 2011   Shyouzou Sugitani <shy@users.sourceforge.jp>
        * 既に起動しているゴーストを上書きインストールした場合には
          ゴーストを再起動するようにした.
        * Sakuraクラスにデフォルトのシェルを取得するためのメソッドを追加した.
        * SakuraクラスにIfGhostを処理するためのメソッドを追加した.
        * ゴーストを管理するデータ構造としてHolonクラスを追加した.
+         ゴーストを構築するのに必要な情報(baseinfo), ゴーストの本体
+         (Sakuraクラスのinstanceへの参照)とユーザーがゴーストに
+         アクセスするための情報(menuitem)を保持している.
 
 Mon November 28 2011   Shyouzou Sugitani <shy@users.sourceforge.jp>
        * シェル選択のメニュー項目も各ゴーストを管理しているデータ構造に
index d672799..ef7130c 100644 (file)
@@ -55,6 +55,7 @@ def fatal(error):
 class Installer(object):
 
     def __init__(self, mode=INTERACTIVE|DOWNLOAD, gui=1):
+        self.fs_encoding = 'mbcs' if os.name == 'nt' else 'utf-8'
         self.mode = mode
         if gui and gtk is not None:
             self.dialog = gtk.MessageDialog(type=gtk.MESSAGE_QUESTION,
@@ -301,9 +302,13 @@ class Installer(object):
             return answer.lower().startswith('y')
 
     def confirm_overwrite(self, path, type_string):
+        if os.name == 'nt':
+            path = unicode(path, 'mbcs', 'ignore').encode('utf-8')
         return self.confirm('Overwrite "{0}"({1})?'.format(path, type_string))
 
     def confirm_removal(self, path, type_string):
+        if os.name == 'nt':
+            path = unicode(path, 'mbcs', 'ignore').encode('utf-8')
         return self.confirm('Remove "{0}"({1})?'.format(path, type_string))
 
     def select(self, candidates):
@@ -351,7 +356,7 @@ class Installer(object):
         target_dir = inst.get('directory')
         if target_dir is None:
             fatal('"directory" not found in install.txt')
-        target_dir = target_dir.encode('utf-8')
+        target_dir = target_dir.encode(self.fs_encoding)
         prefix = os.path.join(homedir, 'ghost', target_dir)
         ghost_src = os.path.join(tmpdir, 'ghost', 'master')
         shell_src = os.path.join(tmpdir, 'shell')
@@ -480,7 +485,7 @@ class Installer(object):
         target_dir = inst.get('directory')
         if target_dir is None:
             fatal('"directory" not found in install.txt')
-        target_dir = target_dir.encode('utf-8')
+        target_dir = target_dir.encode(self.fs_encoding)
         dstdir = os.path.join(homedir, 'balloon', target_dir)
         filelist = []
         for path in self.list_all_files(srcdir, ''):
@@ -570,7 +575,7 @@ class Installer(object):
         if kinoko['extractpath'] is not None:
             dstdir = os.path.join(
                 homedir, 'kinoko',
-                kinoko['extractpath'].encode('utf-8', 'ignore'))
+                kinoko['extractpath'].encode(self.fs_encoding, 'ignore'))
         else:
             dstdir = os.path.join(
                 homedir, 'kinoko', os.path.basename(archive)[:-4])
@@ -602,7 +607,7 @@ class Installer(object):
         target_dir = inst.get('directory')
         if target_dir is None:
             fatal('"directory" not found in install.txt')
-        target_dir = target_dir.encode('utf-8')
+        target_dir = target_dir.encode(self.fs_encoding)
         dstdir = os.path.join(homedir, 'nekodorif', 'skin', target_dir)
         # find files
         filelist = []
@@ -633,7 +638,7 @@ class Installer(object):
         target_dir = inst.get('directory')
         if target_dir is None:
             fatal('"directory" not found in install.txt')
-        target_dir = target_dir.encode('utf-8')
+        target_dir = target_dir.encode(self.fs_encoding)
         dstdir = os.path.join(homedir, 'nekodorif', 'katochan', target_dir)
         # find files
         filelist = []
index 1613d1f..99d9f23 100644 (file)
@@ -550,13 +550,10 @@ class Menu(object):
         else:
             self.__set_visible('Recommend', 0)
 
-    def create_ghost_menuitem(self, name, icon, shell_list, handler):
+    def create_ghost_menuitem(self, name, icon, key, handler):
         if icon is not None:
-            try:
-                pixbuf = gtk.gdk.pixbuf_new_from_file(icon)
-                pixbuf = pixbuf.scale_simple(
-                    16, 16, gtk.gdk.INTERP_BILINEAR)
-            except: # compressed icons are not supported. :-(
+            pixbuf = ninix.pix.create_icon_pixbuf(icon)
+            if pixbuf is None:
                 item = gtk.MenuItem(name)
             else:
                 image = gtk.Image()
@@ -569,21 +566,7 @@ class Menu(object):
             item = gtk.MenuItem(name)
         item.set_name('popup menu item')
         item.show()
-        if len(shell_list) <= 1:
-            shell_name, value = shell_list[0]
-            item.connect(
-                'activate', lambda a, v: handler(v), (value))
-        else:
-            submenu = gtk.Menu()
-            submenu.set_name('popup menu')
-            item.set_submenu(submenu)
-            for shell_name, value in shell_list:
-                subitem = gtk.MenuItem(shell_name)
-                subitem.set_name('popup menu item')
-                subitem.connect(
-                    'activate', lambda a, v: handler(v), (value))
-                subitem.show()
-                submenu.append(subitem)
+        item.connect('activate', lambda a, v: handler(v), (key))
         return item
 
     def __set_ghost_menu(self):
@@ -598,21 +581,24 @@ class Menu(object):
             menuitem = self.ui_manager.get_widget(''.join(('/popup/', path)))
             menuitem.set_submenu(ghost_menu)
 
+    def create_shell_menu(self, shell_menuitem):
+        shell_menu = gtk.Menu()
+        for item in shell_menuitem.values():
+            shell_menu.append(item)
+        return shell_menu
+
     def create_shell_menuitem(self, shell_name, value, handler):
         item = gtk.MenuItem(shell_name)
         item.set_name('popup menu item')
-        item.connect('activate', lambda a, v: handler(v), (value))
         item.show()
+        item.connect('activate', lambda a, v: handler(v), (value))
         return item
 
     def __set_shell_menu(self):
-        shell_menu = gtk.Menu()
-        for item in self.request_parent('GET', 'get_shell_menus'):
-            if item.get_parent():
-                item.reparent(shell_menu)
-            else:
-                shell_menu.append(item)
+        shell_menu = self.request_parent('GET', 'get_shell_menu')
         menuitem = self.ui_manager.get_widget(''.join(('/popup/', 'Shell')))
+        if menuitem.get_submenu():
+            menuitem.remove_submenu()
         menuitem.set_submenu(shell_menu)
 
     def __set_balloon_menu(self):
index c1924a1..8ab2a7a 100644 (file)
@@ -208,6 +208,20 @@ def get_png_IHDR(path):
         buf = f.read(24)
     return buf
 
+def __pixbuf_new_from_file(path):
+    if os.name == 'nt': # XXX
+        path = unicode(path, 'mbcs').encode('utf-8')
+    return gtk.gdk.pixbuf_new_from_file(path)
+
+def create_icon_pixbuf(path):
+    try:
+        pixbuf = __pixbuf_new_from_file(path)
+    except: # compressed icons are not supported. :-(
+        pixbuf = None
+    else:
+        pixbuf = pixbuf.scale_simple(16, 16, gtk.gdk.INTERP_BILINEAR)
+    return pixbuf
+
 def create_blank_pixbuf(width, height):
     pixbuf = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB, True, 8, width, height)
     pixbuf.fill(0xffffffffL)
@@ -232,7 +246,7 @@ def create_pixbuf_from_DGP_file(path):
     key_length = len(key)
     if key_length == 0: # not encrypted
         logging.warning(''.join((filename, ' generates a null key.')))
-        pixbuf = gtk.gdk.pixbuf_new_from_file(filename)
+        pixbuf = __pixbuf_new_from_file(filename)
         return pixbuf
     key = ''.join((key[1:], key[0]))
     key_pos = 0
@@ -271,7 +285,7 @@ def create_pixbuf_from_file(path, is_pnr=True, use_pna=False):
     elif ext == '.ddp':
         pixbuf = create_pixbuf_from_DDP_file(path)
     else:
-        pixbuf = gtk.gdk.pixbuf_new_from_file(path)
+        pixbuf = __pixbuf_new_from_file(path)
     if is_pnr:
         array = pixbuf.get_pixels_array()
         if not pixbuf.get_has_alpha():
@@ -289,7 +303,7 @@ def create_pixbuf_from_file(path, is_pnr=True, use_pna=False):
         path = os.path.join(head, ''.join((basename, '.pna')))
         if os.path.exists(path):
             assert pixbuf.get_has_alpha()
-            pna_pixbuf = gtk.gdk.pixbuf_new_from_file(path)
+            pna_pixbuf = __pixbuf_new_from_file(path)
             pna_array = pna_pixbuf.get_pixels_array()
             assert pna_pixbuf.get_bits_per_sample() / 8 == 1
             pixbuf_array = pixbuf.get_pixels_array()
index e147558..de12f6e 100644 (file)
@@ -94,7 +94,6 @@ class PreferenceDialog(object):
 
     PREFS_TYPE = {'sakura_name': None, # XXX: backward compat
                   'sakura_dir': None,
-                  'sakura_surface': None,
                   'default_balloon': None,
                   'ignore_default': int,
                   'script_speed': int,
@@ -193,7 +192,7 @@ class PreferenceDialog(object):
         assert name in self.PREFS_TYPE
         return self.__prefs.get_with_type(name, self.PREFS_TYPE[name], default)
 
-    def set_current_sakura(self, directory, surface):
+    def set_current_sakura(self, directory):
         key = 'sakura_name' # obsolete
         if key in self.__prefs:
             del self.__prefs[key]
@@ -201,10 +200,6 @@ class PreferenceDialog(object):
         if key in self.__prefs:
             del self.__prefs[key]
         self.__prefs[key] = directory
-        key = 'sakura_surface'
-        if key in self.__prefs:
-            del self.__prefs[key]
-        self.__prefs[key] = surface
 
     def edit_preferences(self):
         self.show()
index fca4c54..cf843c8 100644 (file)
@@ -162,7 +162,7 @@ class Sakura(object):
         self.shiori_dir = shiori_dir
         self.use_makoto = use_makoto
         self.surface_set = surface_set
-        self.prefix = prefix ## FIXME: ninix_home should not be included.(append in get_prefix())
+        self.prefix = prefix
         self.shiori_dll = shiori_dll
         self.shiori_name = shiori_name
         name = (shiori_dll, shiori_name)
@@ -198,6 +198,9 @@ class Sakura(object):
                 if self.balloon_directory is not None:
                     f.write('balloon_directory, {0}\n'.format(
                             self.balloon_directory))
+                if self.shell_directory is not None:
+                    f.write('shell_directory, {0}\n'.format(
+                            self.shell_directory))
         except IOError as e:
             code, message = e.args
             logging.error('cannot write {0}'.format(path))
@@ -237,6 +240,7 @@ class Sakura(object):
         path = os.path.join(self.get_prefix(), 'SETTINGS')
         if os.path.exists(path):
             balloon_directory = None
+            shell_directory = None
             try:
                 with open(path, 'r') as f:
                     for line in f:
@@ -245,12 +249,16 @@ class Sakura(object):
                         key, value = line.split(',', 1)
                         if key.strip() == 'balloon_directory':
                             balloon_directory = value.strip()
+                        if key.strip() == 'shell_directory':
+                            shell_directory = value.strip()
             except IOError as e:
                 code, message = e.args
                 logging.error('cannot read {0}'.format(path))
             self.balloon_directory = balloon_directory
+            self.shell_directory = shell_directory
         else:
             self.balloon_directory = None
+            self.shell_directory = None
 
     def load_shiori(self):
         if self.shiori and self.shiori.load(self.shiori_dir):
@@ -278,8 +286,8 @@ class Sakura(object):
     def leave_temp_mode(self):
         self.__temp_mode = 0
 
-    def set_surface(self, desc, alias, surface, name, dir, tooltips):
-        self.surface.new(desc, alias, surface, name, dir, tooltips)
+    def set_surface(self, desc, alias, surface, name, surface_dir, tooltips):
+        self.surface.new(desc, alias, surface, name, surface_dir, tooltips)
         for side in range(2, self.char):
             default = self.desc.get('char{0:d}.seriko.defaultsurface'.format(side))
             self.surface.add_window(side, default)
@@ -454,17 +462,17 @@ class Sakura(object):
         name = '{0}'.format(self.get_selfname())
         return bool(if_ghost in [name, names])
 
-    def get_name(self):
-        return self.desc.get('name', unicode(_('Sakura&Unyuu'), 'utf-8'))
+    def get_name(self, default=unicode(_('Sakura&Unyuu'), 'utf-8')):
+        return self.desc.get('name', default)
 
     def get_username(self):
         return self.getstring('username') or \
                self.surface.get_username() or \
                self.desc.get('user.defaultname', unicode(_('User'), 'utf-8'))
 
-    def get_selfname(self):
+    def get_selfname(self, default=unicode(_('Sakura'), 'utf-8')):
         return self.surface.get_selfname() or \
-               self.desc.get('sakura.name', unicode(_('Sakura'), 'utf-8'))
+               self.desc.get('sakura.name', default)
 
     def get_selfname2(self):
         return self.surface.get_selfname2() or \
@@ -845,28 +853,26 @@ class Sakura(object):
     def get_current_balloon_directory(self):
         return self.balloon.get_balloon_directory()
 
+    def get_current_shell(self):
+        return self.shell_directory
+
     def get_default_shell(self):
-        default = 'master'
+        default = self.shell_directory or 'master'            
         if default not in self.surface_set:
             default = self.surface_set.keys()[0] # XXX
         return default
 
-    def select_shell(self, item):
-        set_type, i, j = item
-        assert self.current[0] == set_type
-        assert self.current[1] == i
-        assert self.current[2] != j
-        assert set_type == 'g'
-        assert self.surface_set and j in self.surface_set
+    def select_shell(self, shell_key):
+        assert self.surface_set and shell_key in self.surface_set
+        self.shell_directory = shell_key # save user's choice
         name, surface_dir, surface_desc, surface_alias, surface, surface_tooltips = \
-            self.surface_set[j]
+            self.surface_set[shell_key]
         if not name: ## FIXME
-            surface_name = j
+            surface_name = shell_key
         else:
             surface_name = name
-        def proc(self=self, item=item):
-            logging.info('ghost {0} {1} {2}'.format(*item))
-            self.current = item
+        def proc(self=self, key=shell_key):
+            logging.info('ghost {0} {1}'.format(self.key, key))
             self.set_surface(surface_desc, surface_alias, surface, surface_name,
                              surface_dir, surface_tooltips)
             self.surface.reset_alignment()
@@ -1013,7 +1019,7 @@ class Sakura(object):
             self.__surface_life = random.randint(20, 30)
             ##logging.debug('surface_life = {0:d}'.format(self.__surface_life))
 
-    def start(self, item, init, temp, vanished, ghost_changed, prev_name):
+    def start(self, key, init, temp, vanished, ghost_changed, prev_name):
         if self.is_running():
             if temp:
                 self.enter_temp_mode()
@@ -1028,23 +1034,23 @@ class Sakura(object):
         self.vanished_count = 0
         self.__running = 1
         self.__temp_mode = temp
-        self.current = item
-        set_type, i, j = item
-        assert set_type == 'g'
-        logging.info('ghost {0} {1} {2}'.format(*item))
-        assert self.surface_set and j in self.surface_set
+        self.key = key
+        logging.info('ghost {0}'.format(key))
+        self.load_settings()
+        shell_key = self.get_default_shell()
+        self.shell_directory = shell_key # XXX
+        assert self.surface_set and shell_key in self.surface_set
         name, surface_dir, surface_desc, surface_alias, surface, surface_tooltips = \
-            self.surface_set[j]
+            self.surface_set[shell_key]
         surface_set = self.surface_set
         if not name: ## FIXME
-            surface_name = j
+            surface_name = shell_key
         else:
             surface_name = name
         if ghost_changed:
             name = prev_name
         self.set_surface(surface_desc, surface_alias, surface, surface_name,
                          surface_dir, surface_tooltips)
-        self.load_settings()
         if self.request_parent('GET', 'get_preference', 'ignore_default'): ## FIXME: change prefs key
             default_balloon = self.request_parent(
                 'GET', 'get_preference', 'default_balloon')
@@ -1579,13 +1585,10 @@ class Sakura(object):
         elif args[0:2] == ['open', 'configurationdialog']:
             self.request_parent('NOTIFY', 'edit_preferences')
         elif args[0:2] == ['change', 'shell'] and argc > 2:
-            set_type, i, j = self.current
-            assert set_type == 'g'
             for key in self.surface_set:
                 shell_name = self.surface_set[key][0]
                 if shell_name == args[2]:
-                    item = (set_type, i, key)
-                    self.select_shell(item)
+                    self.select_shell(key)
                     break
         elif args[0:2] == ['change', 'ghost'] and argc > 2:
             if args[2] == 'random':
index 34a382f..396c0c2 100644 (file)
@@ -10,7 +10,7 @@
 #  PURPOSE.  See the GNU General Public License for more details.
 #
 
-NUMBER = '4.3.4'
+NUMBER = '4.3.5'
 CODENAME = 'juggling eggs'
 
 VERSION = '{0} ({1})'.format(NUMBER, CODENAME)
index 13ea8b4..931dcf6 100644 (file)
@@ -415,49 +415,80 @@ class PluginControler:
         return None
 
 
+class Meme(object):
+
+    def __init__(self, key):
+        self.__key = key
+        self.request_parent = lambda *a: None # dummy ## FIXME
+        self.__baseinfo = None
+        self.__menuitem = None
+
+    def set_responsible(self, request_method): ## FIXME
+        self.request_parent = request_method
+
+    @property
+    def key(self): # read only
+        return self.__key
+
+    @property
+    def baseinfo(self):
+        return self.__baseinfo
+
+    @baseinfo.setter
+    def baseinfo(self, data): ## FIXME
+        self.__baseinfo = data
+        menuitem = self.request_parent(
+            'GET', 'create_shell_menuitem', self.key, data)
+        if menuitem is None:
+            self.request_parent('NOTIFY', 'delete_shell', self.key)
+            return
+        self.__menuitem = menuitem
+
+    @property
+    def menuitem(self): # read only
+        return self.__menuitem
+
+
 class Holon(object):
 
-    def __init__(self, directory):
-        self.__directory = directory
-        self.request_parent = lambda *a: None # dummy
+    def __init__(self, key):
+        self.__key = key
+        self.request_parent = lambda *a: None # dummy ## FIXME
         self.__baseinfo = None
         self.__menuitem = None
-        self.__shell_menuitem = None
+        self.__shell_menuitem = None ## FIXME
+        self.__shell_menu = None ## FIXME
         self.__instance = None
         
     def set_responsible(self, request_method):
         self.request_parent = request_method
 
     @property
-    def directory(self): # read only
-        return self.__directory
+    def key(self): # read only
+        return self.__key
 
     @property
-    def baseinfo(self):
-        return self.__baseinfo
+    def baseinfo(self): # forbidden
+        return None
 
     @baseinfo.setter
     def baseinfo(self, data):
-        home_dir = ninix.home.get_ninix_home()
-        prefix = os.path.join(home_dir, 'ghost', self.__directory)
-        assert data[4] == prefix
-        ghost_dir = os.path.join(prefix, 'ghost', 'master')
-        assert data[1] == ghost_dir
         self.__baseinfo = data
         if self.__instance is None:
             self.__instance = self.request_parent('GET', 'create_ghost', data)
             if self.__instance is None:
-                self.request_parent('NOTIFY', 'delete_ghost', self.directory)
+                self.request_parent('NOTIFY', 'delete_ghost', self.key)
                 return
         else:
             self.__instance.new(*data) # reset
         menuitem = self.request_parent(
-            'GET', 'create_menuitem', self.directory, data)
+            'GET', 'create_menuitem', self.key, data)
         if menuitem is None:
-            self.request_parent('NOTIFY', 'delete_ghost', self.directory)
+            self.request_parent('NOTIFY', 'delete_ghost', self.key)
             return
-        self.__menuitem = menuitem[0]
-        self.__shell_menuitem = menuitem[1]
+        self.__menuitem = menuitem[0] ## FIXME
+        self.__shell_menuitem = menuitem[1] ## FIXME
+        self.__shell_menu = menuitem[2] ## FIXME
 
     @property
     def instance(self): # read only
@@ -471,6 +502,10 @@ class Holon(object):
     def shell_menuitem(self): # read only
         return self.__shell_menuitem
 
+    @property
+    def shell_menu(self): # read only
+        return self.__shell_menu
+
 
 class Application(object):
 
@@ -555,8 +590,7 @@ class Application(object):
 
     @property
     def current_sakura_instance(self):
-        set_type, i, j = self.current_sakura
-        return self.ghosts[i].instance
+        return self.ghosts[self.current_sakura].instance
 
     def create_ghost(self, data):
         ghost = ninix.sakura.Sakura(data)
@@ -590,13 +624,15 @@ class Application(object):
                               db, request_handler)
 
     def get_working_ghost(self, cantalk=0):
-        working_list = [value.instance for value in self.ghosts.values() \
-                            if value.instance is not None and \
-                            value.instance.is_running()]
-        if cantalk:
-            working_list = [sakura for sakura in working_list \
-                                if sakura.cantalk]
-        return working_list
+        for value in self.ghosts.values():
+            sakura = value.instance
+            if sakura is None:
+                continue
+            if not sakura.is_running():
+                continue
+            if cantalk and not sakura.cantalk:
+                continue
+            yield sakura
 
     def getstring(self, name):
         return self.__menu_owner.getstring(name)
@@ -608,8 +644,8 @@ class Application(object):
     def toggle_bind(self, args):
         self.__menu_owner.toggle_bind(args)
 
-    def select_shell(self, item):
-        self.__menu_owner.select_shell(item)
+    def select_shell(self, key):
+        self.__menu_owner.select_shell(key)
 
     def select_balloon(self, widget, item):
         if not widget.active:
@@ -620,11 +656,14 @@ class Application(object):
     def get_current_balloon_directory(self): ## FIXME
         return self.__menu_owner.get_current_balloon_directory()
 
-    def select_sakura(self, item):
+    def start_sakura_cb(self, key): ## FIXME
+        self.start_sakura(key, init=1) # XXX
+
+    def select_sakura(self, key):
         if self.__menu_owner.busy():
             gtk.gdk.beep()
             return
-        self.change_sakura(self.__menu_owner, item, 'manual')
+        self.change_sakura(self.__menu_owner, key, 'manual')
 
     def notify_site_selection(self, args):
         self.__menu_owner.notify_site_selection(args)
@@ -656,51 +695,50 @@ class Application(object):
         for value in self.ghosts.values():
             yield value.menuitem
 
-    def get_shell_menus(self):
-        set_type, i, j = self.__menu_owner.current
-        shell_menuitems = self.ghosts[i].shell_menuitem
+    def get_shell_menu(self): ## FIXME
+        shell_key = self.__menu_owner.get_current_shell()
+        shell_menuitems = self.ghosts[self.__menu_owner.key].shell_menuitem
         for key, item in shell_menuitems.items():
-            item.set_sensitive(not bool(key == j)) # not working
-            yield item
+            item.set_sensitive(key != shell_key) # not working
+        return self.ghosts[self.__menu_owner.key].shell_menu
 
-    def create_menuitem(self, i, baseinfo):
-        set_type = 'g'
+    def create_menuitem(self, key, baseinfo): ## FIXME
         desc = baseinfo[0]
         shiori_dir = baseinfo[1]
         icon = desc.get('icon', None)
         if icon is not None:
+            if os.name == 'nt':
+                # XXX: path should be encoded in mbcs on Windows
+                icon = icon.encode('mbcs')
             icon_path = os.path.join(shiori_dir, icon)
             if not os.path.exists(icon_path):
                 icon_path = None
         else:
             icon_path = None
-        name = desc.get('name', i)
-        shell_list = []
+        name = desc.get('name')
         surface_set = baseinfo[3]
+        if not surface_set: # XXX
+            return None
         shell_menuitems = OrderedDict()
-        for j in surface_set.keys():
-            value = (set_type, i, j)
-            shell_name = surface_set[j][0]
-            shell_list.append((shell_name, value))
+        for shell_key in surface_set.keys():
+            shell_name = surface_set[shell_key][0]
             menuitem = self.__menu.create_shell_menuitem(
-                shell_name, value, self.select_shell)
-            shell_menuitems[j] = menuitem
-        if not shell_list: # XXX
-            return None
+                shell_name, shell_key, self.select_shell)
+            shell_menuitems[shell_key] = menuitem
+        shell_menu = self.__menu.create_shell_menu(shell_menuitems)
         start_menuitem = self.__menu.create_ghost_menuitem(
-            name, icon_path, shell_list,
-            lambda item: self.start_sakura(item, init=1)) # XXX
+            name, icon_path, key, self.start_sakura_cb) # XXX
         select_menuitem = self.__menu.create_ghost_menuitem(
-            name, icon_path, shell_list, self.select_sakura)
+            name, icon_path, key, self.select_sakura)
         menuitem = {
             'Summon': start_menuitem,
             'Change': select_menuitem,
             }
-        return menuitem, shell_menuitems
+        return menuitem, shell_menuitems, shell_menu
 
-    def delete_ghost(self, i):
-        assert i in self.ghosts
-        del self.ghosts[i]
+    def delete_ghost(self, key):
+        assert key in self.ghosts
+        del self.ghosts[key]
 
     def get_balloon_list(self):
         balloon_list = []
@@ -753,9 +791,8 @@ class Application(object):
         # choose default ghost/shell
         directory = self.prefs.get('sakura_dir')
         name = self.prefs.get('sakura_name') # XXX: backward compat
-        surface = self.prefs.get('sakura_surface')
-        default_sakura = self.find_ghost_by_dir(directory, surface) or \
-                         self.find_ghost_by_name(name, surface) or \
+        default_sakura = self.find_ghost_by_dir(directory) or \
+                         self.find_ghost_by_name(name) or \
                          self.choose_default_sakura()
         # load ghost
         self.current_sakura = default_sakura
@@ -765,34 +802,21 @@ class Application(object):
         ##            i, name.encode('utf-8', 'ignore')))
         self.start_sakura(self.current_sakura, init=1)
 
-    def find_ghost_by_dir(self, directory, surface):
-        if directory in self.ghosts:
-            baseinfo = self.ghosts[directory].baseinfo
-            surface_set = baseinfo[3]
-            if surface not in surface_set:
-                surface = self.ghosts[directory].instance.get_default_shell()
-            return ('g', directory, surface)
-        return None
+    def find_ghost_by_dir(self, directory):
+        return directory if directory in self.ghosts else None
 
-    def find_ghost_by_name(self, name, surface):
-        for i in self.ghosts:
-            baseinfo = self.ghosts[i].baseinfo
-            desc = baseinfo[0]
+    def find_ghost_by_name(self, name):
+        for key in self.ghosts:
+            sakura = self.ghosts[key].instance
             try:
-                if desc.get('name') == name:
-                    surface_set = baseinfo[3]
-                    assert surface_set
-                    if surface not in surface_set:
-                        surface = self.ghosts[i].instance.get_default_shell()
-                    return ('g', i, surface)
+                if sakura.get_name(default=None) == name:
+                    return key
             except: # old preferences(EUC-JP)
                 pass
         return None
 
     def choose_default_sakura(self):
-        i = list(self.ghosts.keys())[0]
-        j = self.ghosts[i].instance.get_default_shell()
-        return ('g', i, j)
+        return list(self.ghosts.keys())[0]
 
     def find_balloon_by_name(self, name):
         balloons = self.balloons
@@ -819,7 +843,7 @@ class Application(object):
         return None
 
     def run(self):
-        self.timeout_id = glib.timeout_add(100, self.do_idle_tasks) # 100ms
+        self.timeout_id = glib.timeout_add(100, self.do_idle_tasks) # 100[ms]
         gtk.main()
 
     def get_ghost_names(self):
@@ -839,12 +863,12 @@ class Application(object):
             return 0
 
     def update_sakura(self, name, sender):
-        item = self.find_ghost_by_name(name, '')
-        if item is None:
+        key = self.find_ghost_by_name(name, '')
+        if key is None:
             return
-        sakura = self.ghosts[i].instance
+        sakura = self.ghosts[key].instance
         if not sakura.is_running():
-            self.start_sakura(item, init=1)
+            self.start_sakura(key, init=1)
         sakura.enqueue_script('\![updatebymyself]\e', sender,
                               None, None, 0, 0, None)
 
@@ -856,34 +880,32 @@ class Application(object):
                     continue
                 if sakura.ifghost(ifghost):
                     if not sakura.is_running():
-                        shell_dir = sakura.get_default_shell()
-                        self.current_sakura = ('g', value.directory, shell_dir)
+                        self.current_sakura = value.key
                         self.start_sakura(self.current_sakura, init=1, temp=1) ## FIXME
                     else:
-                        self.current_sakura = sakura.current
+                        self.current_sakura = sakura.key
                     break
                 else:
                     pass
             else:
                 return
         else:
-            working_list = self.get_working_ghost(cantalk=1)
+            working_list = list(self.get_working_ghost(cantalk=1))
             if working_list:
-                self.current_sakura = random.choice(working_list).current
+                self.current_sakura = random.choice(working_list).key
             else:
                 return ## FIXME
 
-    def set_menu_sensitive(self, i, flag):
-        menuitems = self.ghosts[i].menuitem
+    def set_menu_sensitive(self, key, flag):
+        menuitems = self.ghosts[key].menuitem
         for item in menuitems.values():
             item.set_sensitive(flag)
 
     def close_ghost(self, sakura):
-        set_type, i, j = sakura.current
-        if not self.get_working_ghost():
-            self.prefs.set_current_sakura(i, j)
+        if not any(self.get_working_ghost()):
+            self.prefs.set_current_sakura(sakura.key)
             self.quit()
-        elif self.current_sakura == sakura.current:
+        elif self.current_sakura == sakura.key:
             self.select_current_sakura()
 
     def close_all_ghosts(self):
@@ -911,80 +933,62 @@ class Application(object):
         if len(keys) < 2:
             return
         # select another ghost
-        set_type, i, j = sakura.current
-        assert set_type == 'g'
         if sequential:
-            i = (keys.index(i) + 1) % len(keys)
+            key = (keys.index(sakura.key) + 1) % len(keys)
         else:
-            keys.remove(i)
-            i = random.choice(keys)
-        j = self.ghosts[i].instance.get_default_shell()
-        if self.current_sakura == sakura.current: # XXX
-            self.current_sakura = ('g', i, j)
-        self.change_sakura(sakura, ('g', i, j), 'automatic',
-                           event, vanished)
+            keys.remove(sakura.key)
+            key = random.choice(keys)
+        self.change_sakura(sakura, key, 'automatic', event, vanished)
 
     def select_ghost_by_name(self, sakura, name, event=1):
-        item = self.find_ghost_by_name(name, 0)
-        if item is None:
+        key = self.find_ghost_by_name(name, 0)
+        if key is None:
             return
-        self.change_sakura(sakura, item, 'automatic', event)
+        self.change_sakura(sakura, key, 'automatic', event)
 
-    def change_sakura(self, sakura, item, method, event=1, vanished=0):
-        set_type, i, j = item
-        if sakura.current == item: # XXX: needs reloading?
+    def change_sakura(self, sakura, key, method, event=1, vanished=0):
+        if sakura.key == key: # XXX: needs reloading?
             return
-        if sakura.current[1] == i:
-            return # XXX: not shell change
-        assert set_type == 'g'
-        desc, shiori_dir, use_makoto, surface_set, prefix, \
-            shiori_dll, shiori_name = self.ghosts[i].baseinfo
-        assert surface_set and j in surface_set
-        name, surface_dir, surface_desc, surface_alias, surface, surface_tooltips = \
-            surface_set[j]
-        def proc(self=self, item=item):
-            self.stop_sakura(sakura, self.start_sakura, item, sakura.current)
+        def proc(self=self, key=key):
+            self.stop_sakura(sakura, self.start_sakura, key, sakura.key)
         if vanished:
             sakura.finalize()
-            self.start_sakura(item, sakura.current, vanished)
+            self.start_sakura(key, sakura.key, vanished)
             self.close_ghost(sakura)
         elif not event:
             proc()
         else:
-            name = surface_desc.get('sakura.name', desc.get('sakura.name'))
+            name = self.ghosts[key].instance.get_selfname(default='')
             sakura.enqueue_event('OnGhostChanging', name, method, proc=proc)
 
     def stop_sakura(self, sakura, starter=None, *args):
-        set_type, i, j = sakura.current
         sakura.finalize()
         if starter is not None:
             starter(*args)
-        self.set_menu_sensitive(i, True)
+        self.set_menu_sensitive(sakura.key, True)
         self.close_ghost(sakura)
 
-    def start_sakura(self, item, prev=None, vanished=0, init=0, temp=0):
-        set_type, i, j = item
-        assert set_type == 'g'       
-        sakura = self.ghosts[i].instance
+    def start_sakura(self, key, prev=None, vanished=0, init=0, temp=0):
+        sakura = self.ghosts[key].instance
         assert sakura is not None
         if prev is not None:
-            assert prev[1] in self.ghosts ## FIXME: vanish case?
-            assert self.ghosts[prev[1]].instance is not None
+            assert prev in self.ghosts ## FIXME: vanish case?
+            assert self.ghosts[prev].instance is not None
         if init:
             ghost_changed = 0
         else:
             assert prev is not None ## FIXME
-            if prev[0] == set_type and prev[1] == i:
+            if prev == key:
                 ghost_changed = 0
             else:
                 ghost_changed = 1
         if ghost_changed:
-            name = self.ghosts[prev[1]].instance.get_selfname()
+            name = self.ghosts[prev].instance.get_selfname()
         else:
             name = None
         sakura.notify_preference_changed()
-        sakura.start(item, init, temp, vanished, ghost_changed, name)
-        self.set_menu_sensitive(i, False)
+        sakura.start(key, init, temp, vanished, ghost_changed, name)
+        self.set_menu_sensitive(key, False)
 
     def notify_preference_changed(self):
         for sakura in self.get_working_ghost():
@@ -1005,18 +1009,16 @@ class Application(object):
 
     def reload_current_sakura(self, sakura):
         self.save_preferences()
-        set_type, i, j = sakura.current
-        assert set_type == 'g'
+        key = sakura.key
         ghost_dir = os.path.split(sakura.get_prefix())[1] # XXX
         ghost_conf = ninix.home.search_ghosts([ghost_dir])
         if ghost_conf:
-            self.ghosts[i].baseinfo = ghost_conf[i]
+            self.ghosts[key].baseinfo = ghost_conf[key]
         else:
             self.close_ghost(sakura) ## FIXME
-            del self.ghosts[i]
+            del self.ghosts[key]
             return ## FIXME
-        item = (set_type, i, j)
-        self.start_sakura(item, item, init=1) 
+        self.start_sakura(key, key, init=1) 
 
     def add_sakura(self, ghost_dir):
         if ghost_dir in self.ghosts:
@@ -1029,12 +1031,12 @@ class Application(object):
         if ghost_conf:
             if exists:
                 sakura = self.ghosts[ghost_dir].instance
-                if sakura.is_running(): # reload if working
-                    item = sakura.current
+                if sakura.is_running(): # restart if working
+                    key = sakura.key
                     def proc(self=self):
                         self.ghosts[ghost_dir].baseinfo = ghost_conf[ghost_dir]
-                        logging.info('reloading....')
-                        self.start_sakura(item, item, init=1)
+                        logging.info('restarting....')
+                        self.start_sakura(key, key, init=1)
                         logging.info('done.')
                     self.stop_sakura(sakura, proc)
             else:
@@ -1066,10 +1068,8 @@ class Application(object):
                 except:
                     logging.error(
                         '*** REMOVE FAILED *** : {0}'.format(filename))
-        set_type, i, j = sakura.current
-        assert set_type == 'g'
         self.select_ghost(sakura, 0, vanished=1)
-        del self.ghosts[i]
+        del self.ghosts[sakura.key]
 
     def select_plugin(self, item):
         target = self.__menu_owner
@@ -1101,12 +1101,11 @@ class Application(object):
         for sakura in self.get_working_ghost():
             sakura.save_history()
         history = {}
-        for i in self.ghosts:
-            baseinfo = self.ghosts[i].baseinfo
-            desc = baseinfo[0]
-            name = desc.get('name', i)
+        for key in self.ghosts:
+            sakura = self.ghosts[key].instance
+            name = sakura.get_name(default=i)
             ghost_time = 0
-            prefix = baseinfo[4]
+            prefix = sakura.get_prefix()
             path = os.path.join(prefix, 'HISTORY')
             if os.path.exists(path):
                 try:
index 0698e81..46d6a2f 100644 (file)
--- a/setup.py
+++ b/setup.py
@@ -21,7 +21,7 @@ for lang in langs:
 
 setup(
     name = 'ninix-aya',
-    version = '4.3.4',
+    version = '4.3.5',
     package_dir = {'': 'lib'},
     packages=['ninix', 'ninix.dll'],
     scripts = ['lib/ninix_main.py', 'ninix_win32_postinst.py'],