OSDN Git Service

marshal database generator & accessor
[amulettoolsmh4/main.git] / amulettool.py
1 #!/usr/bin/python
2 # -*- coding: utf-8 -*-
3
4 # お守りスキルのSeed特定ツールのコントロールクラス
5 # 2013/12/04 written by kei9
6
7 import threading
8 import time
9 import os.path
10 import sys
11 import pickle
12 import sqlite3
13
14 import wx
15
16 import view
17 import model
18 import convertcoding
19
20 SETTING_FILE = u"settings"
21 SETTING_THRESHOLD1 = u"threshold1"
22 SETTING_THRESHOLD2 = u"threshold2"
23 SETTING_SKILLS = u"skills"
24
25 def _get_base_dir():
26     u""" for pyinstaller 2.1 """
27     if getattr(sys, 'frozen', False):
28         # we are running in a |PyInstaller| bundle
29         basedir = sys._MEIPASS
30     else:
31         # we are running in a normal Python environment
32         #1basedir = os.path.dirname(__file__)
33         basedir = os.path.dirname(os.path.abspath(__file__))
34     return convertcoding.convert_unicode(basedir)
35
36
37 class AmuletToolController(wx.App):
38     u""" アプリケーションの制御クラス """
39     def OnInit(self):
40         self._read_settings()
41         self.frame_view = view.MainFrameView(os.path.join(_get_base_dir(), u"view", view.XRC_MAIN_FRAME))
42
43         self._init_events()
44
45         self.frame_view.Show()
46
47         self.frame_view.DisableNoteBook()
48
49         self._init_database()
50         self._init_notebook_seed1()
51         self._init_notebook_seed2()
52         self._init_notebook_skill2()
53         self._init_notebook_setting()
54         self._init_notebook_amulet()
55
56         self.frame_view.EnableNoteBook()
57         return True
58
59     def _init_events(self):
60         u"""イベント登録"""
61         frame = self.frame_view.frame
62
63         # menu event
64         frame.Bind(wx.EVT_MENU, self.OnClose, id=self.frame_view.ID_MENU_ITEM_EXIT)
65         frame.Bind(wx.EVT_MENU, self.OnAboutBox, id=self.frame_view.ID_MENU_ITEM_ABOUT)
66         frame.Bind(wx.EVT_CLOSE, self.CloseHandler)
67
68         # button event
69
70     def _init_database(self):
71         u""" DBの初期設定 """
72
73         if not os.path.exists(model.DB_FILE_NAME):
74             u""" DBが存在しない時は再生成する """
75             frame = self.frame_view.frame
76             try:
77                 generator = model.DataBaseGenerator(model.DB_FILE_NAME)
78                 #generator = model.DataBaseGenerator()
79
80                 dlg_view = view.GaugeDialogView(os.path.join(_get_base_dir(), u"view", view.XRC_GAUGE_DIALOG))
81                 def _loop():
82                     while t1.is_alive():
83                         dlg_view.gauge.Pulse()
84                         time.sleep(0.2)
85                     dlg_view.finish_generation()
86
87                 t1 = threading.Thread(target=generator.generate_db)
88                 t2 = threading.Thread(target=_loop)
89                 t1.start()
90                 t2.start()
91
92                 dlg_view.ShowModal()
93                 t1.join()
94                 t2.join()
95                 dlg_view.Destroy()
96             except sqlite3.Error as e:
97                 self._show_error_dialog(u"データベース生成中にエラーが発生しました")
98
99         # access to db
100         try:
101             self.db_accessor = model.DataBaseAccessor(model.DB_FILE_NAME)
102         except sqlite3.Error as e:
103             self._show_error_dialog(u"データベースが壊れています")
104
105         self._minmax_dict = self.db_accessor.get_minmax_dict()
106         self._amulet_names = self.db_accessor.get_id_sorted_amulet_names()
107         self._skill_names = self.db_accessor.get_id_sorted_skill_names()
108
109     def _init_notebook_seed1(self):
110         u""" Seed1特定タブのviewの初期化 """
111         self.notebook_seed1_view = self.frame_view.notebook_seed1_view
112         self.notebook_seed1_view.bind_button_search(self.OnClickSeed1Search)
113         self.notebook_seed1_view.bind_button_clear(self.OnClickSeed1Clear)
114
115         u" 各種お守りの第1スキル選択のセット "
116         name2skill1s = {}
117         for name in self._amulet_names:
118             name2skill1s[name] = tuple([x for x in self._skill_names if x in self._minmax_dict[name][0]])
119         self.notebook_seed1_view.set_amuletname_skillnames_dict(self._amulet_names, name2skill1s)
120
121     def _init_notebook_seed2(self):
122         u""" Seed2特定タブのviewの初期化 """
123         self.notebook_seed2_view = self.frame_view.notebook_seed2_view
124         self.notebook_seed2_view.bind_button_search(self.OnClickSeed2Search)
125         self.notebook_seed2_view.bind_button_clear(self.OnClickSeed2Clear)
126         self.notebook_seed2_view.bind_button_skill2(self.OnClickSkill2SearchFromSeed2)
127
128         u" 各種お守りの第2スキル選択 "
129         for amu_key, amu_name in zip([view.KEY_AMULET1, view.KEY_AMULET2, view.KEY_AMULET3],
130                 [view.NAME_AMULET1, view.NAME_AMULET2, view.NAME_AMULET3]):
131             skill_names = [view.VAL_NO_SKILL] + [x for x in self._skill_names 
132                     if x in self._minmax_dict[amu_name][1]]
133             self.notebook_seed2_view.set_skill_names(amu_key, skill_names)
134         self.notebook_seed2_view.set_skill_selected_idx(0)
135
136     def _init_notebook_skill2(self):
137         u""" Seed2によるSkill2一覧タブのviewの初期化 """
138         self.notebook_skill2_view = self.frame_view.notebook_skill2_view
139         self.notebook_skill2_view.bind_button_search(self.OnClickSkillSearch)
140         self.notebook_skill2_view.bind_button_clear(self.OnClickSkillClear)
141
142     def _update_notebook_amulet(self, amulet_id=None):
143         u""" お守り検索タブのviewの更新
144         amulet_id が Noneでない場合、お守りの種類に応じてComboboxを再設定する
145         また、選択中のスキルに応じて最大最小を設定する"""
146         pass
147
148
149     def _update_notebook_amulet_seed2s(self):
150         u"""お守り検索タブのSeed2リストを更新する"""
151         pass
152
153     def _init_notebook_amulet(self):
154         u""" お守り検索タブの初期設定 """
155         self.notebook_amulet_view = self.frame_view.notebook_amulet_view
156         self.notebook_amulet_view.bind_radiobox_amulet(self.OnClickAmuletRadioAmulet)
157         self.notebook_amulet_view.bind_radiobox_threshold_type(self.OnClickAmuletRadioThresholdType)
158         self.notebook_amulet_view.bind_combobox(self.OnClickAmuletCombo)
159         self.notebook_amulet_view.bind_button_search(self.OnClickAmuletSearch)
160         self.notebook_amulet_view.bind_button_clear(self.OnClickAmuletClear)
161         self.notebook_amulet_view.bind_button_skill(self.OnClickSkillSearchFromAmulet)
162
163         self.notebook_amulet_view.set_radio_value(True, view.NAME_AMULET1)
164         self._update_notebook_amulet()
165
166     def _init_notebook_setting(self):
167         u""" 設定タブの初期設定 """
168         self.notebook_setting_view = self.frame_view.notebook_setting_view
169         self.notebook_setting_view.bind_button_ok(self.OnClickSettingOK)
170         self.notebook_setting_view.bind_button_clear(self.OnClickSettingClear)
171         self.notebook_setting_view.set_skill_strings(self._skill_names)
172         self._update_notebook_setting()
173
174     def _update_notebook_setting(self):
175         u"""設定タブの値更新"""
176         self.notebook_setting_view.set_threshold(self._highlight_threshold1,
177                 self._highlight_threshold2)
178         self.notebook_setting_view.set_checked_strings(self._highlight_skills)
179
180     def _update_highlight(self):
181         u""" update highlight cells """
182         self.notebook_skill2_view.set_skill2_highlight(
183                 self._highlight_skills, self._highlight_threshold1, self._highlight_threshold2)
184         self.notebook_seed1_view.update_highlight(self._highlight_skills)
185
186     u""" Seed1 view's event"""
187     def OnClickSeed1Search(self, evt):
188         u""" search seed1s from selected skill1s """
189         alchemy_type = self.notebook_seed1_view.get_tenun_radio_key()
190         amu_skill_name_list = self.notebook_seed1_view.get_selected_amulets_and_names()
191         if alchemy_type == view.KEY_TENUN555:
192             alchemy_type = model.KEY_TENUN555
193         elif alchemy_type == view.KEY_TENUN888:
194             alchemy_type = model.KEY_TENUN888
195         else:
196             raise KeyError(u"key '{0}' not found in alchemy type".format(alchemy_type))
197         seed1s = self.db_accessor.select_seed1s(amu_skill_name_list, alchemy_type)
198         if len(seed1s) == 0:
199             self.notebook_seed1_view.set_text_result(u"条件に一致するSeed1は見つかりません")
200             self.notebook_seed1_view.clear_seed1_grid()
201         elif len(seed1s) == 1:
202             seed1 = [x for x in seed1s][0]
203             no, table_no, result_num = self.db_accessor.select_table_nos_from_seed1(seed1, alchemy_type)
204             self.notebook_seed1_view.set_text_result(
205                     u"Seed1: {0}, テーブルNo: {1}, 通し番号: {2}".format(seed1, table_no, no))
206             # list of (no, seed1)
207             near_num = view.NEAR_SEED1_NUMBERS
208             no_seed1_dict = self.db_accessor.select_near_seed1s_from_table_no(
209                     no, table_no, near_num, near_num, alchemy_type)
210
211             # no -> (seed1, result_num, amu_names, skill_names)
212             self.notebook_seed1_view.set_no2seed1s_dict(no_seed1_dict, no)
213             self.notebook_seed1_view.update_highlight(self._highlight_skills)
214         else:
215             self.notebook_seed1_view.set_text_result(
216                     u"Seed1は{0}件あります。条件を絞ってください".format(len(seed1s)))
217             self.notebook_seed1_view.clear_seed1_grid()
218
219     def OnClickSeed1Clear(self, evt):
220         u""" clear seed1s from selected skill1s """
221         self.notebook_seed1_view.clear_combobox()
222         self.notebook_seed1_view.clear_seed1_grid()
223         self.notebook_seed1_view.clear_text_result()
224
225     u""" Seed2 view's event """
226     def OnClickSeed2Search(self, evt):
227         u""" search seed2s from selected skill2s """
228         amu2skills_dict = {}
229         for amu_key, amu_name in zip([view.KEY_AMULET1, view.KEY_AMULET2, view.KEY_AMULET3], 
230                 [view.NAME_AMULET1, view.NAME_AMULET2, view.NAME_AMULET3]):
231             names = self.notebook_seed2_view.get_selected_skill_names(amu_key)
232             amu2skills_dict[amu_name] = [name if name in self._skill_names else None for name in names]
233         seed_sets = self.db_accessor.select_seed2s(amu2skills_dict)
234         self.notebook_seed2_view.set_result_text(u"""Seedの候補は{0}個です。""".format(len(seed_sets)))
235
236         if len(seed_sets) > 0:
237             self.notebook_seed2_view.set_seed_lists([u"{0}".format(seed) for seed in sorted(seed_sets)])
238             self.notebook_seed2_view.set_skill2_button_enable(True)
239         else:
240             self.notebook_seed2_view.clear_seed_list()
241             self.notebook_seed2_view.set_skill2_button_enable(False)
242
243     def OnClickSeed2Clear(self, evt):
244         u""" reset seed2 search settings of combo box"""
245         self.notebook_seed2_view.set_skill_selected_idx(0)
246         self.notebook_seed2_view.clear_seed_list()
247         self.notebook_seed2_view.set_result_text(u"")
248         self.notebook_seed2_view.set_skill2_button_enable(False)
249
250     def OnClickSkill2SearchFromSeed2(self, evt):
251         u""" change page to skill2 search from seed2"""
252         seed2 = self.notebook_seed2_view.get_selected_seed2()
253         if seed2 is not None:
254             self.notebook_skill2_view.set_seed2_value(seed2)
255             self.frame_view.note_book.SetSelection(view.SKILL_SEARCH_PAGE)
256             self.OnClickSkillSearch(evt)
257
258     u""" Skill2 search from Seed2's event """
259     def OnClickSkillSearch(self, evt):
260         u""" skill search from seed2"""
261         seed2 = self.notebook_skill2_view.get_seed2_value()
262         if seed2 is not None:
263             table_no, no, skill_dict, th1s, th2s = self.db_accessor.select_names_from_seed2(seed2)
264             try:
265                 for amu_key, amu_name in zip([view.KEY_AMULET1, view.KEY_AMULET2, view.KEY_AMULET3], 
266                         [view.NAME_AMULET1, view.NAME_AMULET2, view.NAME_AMULET3]):
267                     skill_names = skill_dict[amu_name]
268                     self.notebook_skill2_view.set_skill2_by_col_key(amu_key, skill_names)
269                 th_vals = [u"{0}".format(x) for x in th1s]
270                 self.notebook_skill2_view.set_skill2_by_col_key(view.KEY_THRESHOLD1, th_vals)
271                 th_vals = [u"{0}".format(x) for x in th2s]
272                 self.notebook_skill2_view.set_skill2_by_col_key(view.KEY_THRESHOLD2, th_vals)
273                 #inishie
274                 skill_name, th1, th2 = self.db_accessor.select_inishie_skill2_from_seed2(seed2)
275                 self.notebook_skill2_view.set_inishie(skill_name, th1, th2)
276                 # explanation
277                 self.notebook_skill2_view.set_result_text(
278                         u"SEED2: {2}, 通し番号: {1}, テーブルNo: {0}".format(table_no, no, seed2))
279             except KeyError, e:
280                 self._show_message_dialog(message=u"指定されたSeed値は存在しません")
281             finally:
282                 self._update_highlight()
283
284         else:
285             self._show_message_dialog(message=u"Seed値には数字を入力してください")
286
287     def OnClickSkillClear(self, evt):
288         u""" clear skills from seed """
289         self.notebook_skill2_view.clear_skill2_grid()
290         self.notebook_skill2_view.clear_skill2_highlight()
291         self.notebook_skill2_view.clear_items_amulet_prospects()
292         self.notebook_skill2_view.clear_result_text()
293         self.notebook_skill2_view.clear_inishie()
294
295     u""" amulet search event """
296     def OnClickAmuletRadioAmulet(self, evt):
297         u""" switch skill lists by amulet id """
298         #btn_id = evt.GetId()
299         amulet_name = self.notebook_amulet_view.get_selected_amulet()
300         amu_id = self._amulet_name2id_dict[amulet_name]
301         self._update_notebook_amulet(amu_id)
302         self.notebook_amulet_view.set_result_text_ctrl_value(u"")
303         self.notebook_amulet_view.set_skill_button_enable(False)
304         self.notebook_amulet_view.clear_grid()
305
306     def OnClickAmuletRadioThresholdType(self, evt):
307         u""" switch seed lists by threshold_type """
308         self._update_notebook_amulet_seed2s()
309
310     def OnClickAmuletCombo(self, evt):
311         u""" switch skill minmax by amulet id and skill_id"""
312         self._update_notebook_amulet()
313
314     def OnClickAmuletSearch(self, evt):
315         u""" search seeds from amulet condition """
316         pass
317
318     def OnClickAmuletClear(self, evt):
319         u""" clear amulet conditions """
320         pass
321
322     def OnClickSkillSearchFromAmulet(self, evt):
323         u""" change page to skill search from amulet"""
324         seed = self.notebook_amulet_view.get_grid_selected_seed2()
325         if seed is not None:
326             self.notebook_skill2_view.set_seed2_value(seed)
327             self.frame_view.note_book.SetSelection(view.SKILL_SEARCH_PAGE)
328             self.OnClickSkillSearch(evt)
329
330     u""" settings' event """
331     def OnClickSettingOK(self, evt):
332         u""" get settings of setting tab """
333         (self._highlight_threshold1, 
334                 self._highlight_threshold2) = self.frame_view.notebook_setting_view.get_threshold()
335         self._highlight_skills = set([x for x in self.frame_view.notebook_setting_view.get_checked_strings()
336                 if x in self._skill_names])
337         self._update_highlight()
338
339     def OnClickSettingClear(self, evt):
340         u""" reset settings of setting tab """
341         self._highlight_threshold1 = view.HIGHLIGHT_THRESHOLD1
342         self._highlight_threshold2 = view.HIGHLIGHT_THRESHOLD2
343         self._highlight_skills = set()
344         self._update_notebook_setting()
345         self._update_highlight()
346
347     def _show_error_dialog(self, message=u"予期せぬエラーが発生しました", caption=u"エラー"):
348         u""" エラーダイアログを表示し、
349         OKボタンが押されたらアプリケーションを終了する
350         """
351         dlg = wx.MessageDialog(self.frame_view.frame, 
352             message,
353             caption, wx.OK | wx.ICON_ERROR)
354         dlg.ShowModal()
355         dlg.Destroy()
356         wx.Exit()
357
358     def _show_message_dialog(self, message, caption=u"メッセージ"):
359         u""" メッセージダイアログを表示する
360         """
361         dlg = wx.MessageDialog(self.frame_view.frame, 
362             message,
363             caption, wx.OK | wx.ICON_INFORMATION)
364         dlg.ShowModal()
365         dlg.Destroy()
366
367     def CloseHandler(self, evt):
368         dlg = wx.MessageDialog(parent = self.frame_view.frame,
369                 message = u"終了します。よろしいですか?", 
370                 caption = u"終了確認", 
371                 style = wx.YES_NO)
372         result = dlg.ShowModal()
373         dlg.Destroy()
374         if result == wx.ID_YES:
375             self._write_settings()
376             wx.Exit()
377
378     def OnClose(self, evt):
379         self.frame_view.Close()
380
381     def OnAboutBox(self, evt):
382         info = self.frame_view.GetAboutInfo()
383         wx.AboutBox(info)
384
385     def _write_settings(self):
386         with open(SETTING_FILE, mode="w") as f:
387             data = {SETTING_THRESHOLD1:self._highlight_threshold1, 
388                     SETTING_THRESHOLD2:self._highlight_threshold2, 
389                     SETTING_SKILLS:self._highlight_skills}
390             pickle.dump(data, f)
391
392     def _read_settings(self):
393         if os.path.exists(SETTING_FILE):
394             with open(SETTING_FILE, mode="r") as f:
395                 try:
396                     data = pickle.load(f)
397                     self._highlight_threshold1 = data[SETTING_THRESHOLD1] 
398                     self._highlight_threshold2 = data[SETTING_THRESHOLD2] 
399                     self._highlight_skills = data[SETTING_SKILLS]
400                 except EOFError, e:
401                     self._highlight_threshold1 = view.HIGHLIGHT_THRESHOLD1
402                     self._highlight_threshold2 = view.HIGHLIGHT_THRESHOLD2
403                     self._highlight_skills = set()
404         else:
405             self._highlight_threshold1 = view.HIGHLIGHT_THRESHOLD1
406             self._highlight_threshold2 = view.HIGHLIGHT_THRESHOLD2
407             self._highlight_skills = set()
408
409 if __name__ == "__main__":
410     app = AmuletToolController(False)
411     app.MainLoop()
412