OSDN Git Service

revise unicode variables and this app can run under unicode path.
[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_SKILLS = u"skills"
23
24 def _get_base_dir():
25     u""" for pyinstaller 2.1 """
26     if getattr(sys, 'frozen', False):
27         # we are running in a |PyInstaller| bundle
28         basedir = sys._MEIPASS
29     else:
30         # we are running in a normal Python environment
31         #1basedir = os.path.dirname(__file__)
32         basedir = os.path.dirname(os.path.abspath(__file__))
33     return convertcoding.convert_unicode(basedir)
34
35
36 class AmuletToolController(wx.App):
37     u""" アプリケーションの制御クラス """
38     def OnInit(self):
39         self._read_settings()
40         self.frame_view = view.MainFrameView(os.path.join(_get_base_dir(), u"view", view.XRC_MAIN_FRAME))
41
42         self._init_events()
43
44         self.frame_view.Show()
45
46         self.frame_view.DisableNoteBook()
47
48         self._init_database()
49         self._set_views()
50
51         self.frame_view.EnableNoteBook()
52         return True
53
54     def _init_events(self):
55         u"""イベント登録"""
56         frame = self.frame_view.frame
57
58         # menu event
59         frame.Bind(wx.EVT_MENU, self.OnClose, id=self.frame_view.ID_MENU_ITEM_EXIT)
60         frame.Bind(wx.EVT_MENU, self.OnAboutBox, id=self.frame_view.ID_MENU_ITEM_ABOUT)
61         frame.Bind(wx.EVT_CLOSE, self.CloseHandler)
62
63         # button event
64         frame.Bind(wx.EVT_BUTTON, self.OnClickSeedSearch, id=self.frame_view.ID_BUTTON_SEED_SEARCH)
65         frame.Bind(wx.EVT_BUTTON, self.OnClickSeedClear, id=self.frame_view.ID_BUTTON_SEED_CLEAR)
66         frame.Bind(wx.EVT_BUTTON, self.OnClickSkillSearchFromSeed, id=self.frame_view.ID_BUTTON_SKILL_FROM_SEED_SEARCH)
67         frame.Bind(wx.EVT_BUTTON, self.OnClickSkillSearch, id=self.frame_view.ID_BUTTON_SKILL_SEARCH)
68         frame.Bind(wx.EVT_BUTTON, self.OnClickSkillClear, id=self.frame_view.ID_BUTTON_SKILL_CLEAR)
69         frame.Bind(wx.EVT_BUTTON, self.OnClickAmuletSearch, id=self.frame_view.ID_BUTTON_AMULET_SEARCH_SEARCH)
70         frame.Bind(wx.EVT_BUTTON, self.OnClickAmuletClear, id=self.frame_view.ID_BUTTON_AMULET_SEARCH_CLEAR)
71         frame.Bind(wx.EVT_BUTTON, self.OnClickSkillSearchFromAmulet, id=self.frame_view.ID_BUTTON_AMULET_SEARCH_SKILL)
72         frame.Bind(wx.EVT_BUTTON, self.OnClickSettingOK, id=self.frame_view.ID_BUTTON_SETTING_OK)
73         frame.Bind(wx.EVT_BUTTON, self.OnClickSettingClear, id=self.frame_view.ID_BUTTON_SETTING_CLEAR)
74
75         # radio button event
76         frame.Bind(wx.EVT_RADIOBUTTON, self.OnClickAmuletRadio, id=self.frame_view.ID_RADIO_BUTTON_AMULET1)
77         frame.Bind(wx.EVT_RADIOBUTTON, self.OnClickAmuletRadio, id=self.frame_view.ID_RADIO_BUTTON_AMULET2)
78         frame.Bind(wx.EVT_RADIOBUTTON, self.OnClickAmuletRadio, id=self.frame_view.ID_RADIO_BUTTON_AMULET3)
79
80         # combo box event
81         frame.Bind(wx.EVT_COMBOBOX, self.OnClickAmuletCombo, id=self.frame_view.ID_COMBO_BOX_AMULET_SEARCH_SKILL1)
82         frame.Bind(wx.EVT_COMBOBOX, self.OnClickAmuletCombo, id=self.frame_view.ID_COMBO_BOX_AMULET_SEARCH_SKILL2)
83
84     def _init_database(self):
85         u""" DBの初期設定 """
86
87         if not os.path.exists(model.DB_FILE_NAME):
88             u""" DBが存在しない時は再生成する """
89             frame = self.frame_view.frame
90             try:
91                 generator = model.DataBaseGenerator(model.DB_FILE_NAME)
92                 #generator = model.DataBaseGenerator()
93
94                 dlg_view = view.GaugeDialogView(os.path.join(_get_base_dir(), u"view", view.XRC_GAUGE_DIALOG))
95                 def _loop():
96                     while t1.is_alive():
97                         dlg_view.gauge.Pulse()
98                         time.sleep(0.2)
99                     dlg_view.finish_generation()
100
101                 t1 = threading.Thread(target=generator.generate_db)
102                 t2 = threading.Thread(target=_loop)
103                 t1.start()
104                 t2.start()
105
106                 dlg_view.ShowModal()
107                 t1.join()
108                 t2.join()
109                 dlg_view.Destroy()
110             except sqlite3.Error as e:
111                 self._show_error_dialog(u"データベース生成中にエラーが発生しました")
112
113         # access to db
114         try:
115             self.db_accessor = model.DataBaseAccessor(model.DB_FILE_NAME)
116         except sqlite3.Error as e:
117             self._show_error_dialog(u"データベースが壊れています")
118
119         # get dictionaries
120         (self._skill_id2name_dict, 
121             self._skill_name2id_dict,
122             self._amulet_id2name_dict, 
123             self._amulet_name2id_dict,
124             self._amulet_id2skill1_id_dict,
125             self._amulet_id2skill2_id_dict) = self.db_accessor.get_dicts()
126
127
128     def _set_views(self):
129         u""" GUIにDBの各種値をセットする """
130         self._set_combo_views()
131         self._set_spin_views()
132         self._set_check_list_views()
133
134     def _set_combo_views(self):
135         u""" ComboBoxの値をセットする """
136         combo_dict = self.frame_view.combo_box_skill_dict
137
138         u" 各種お守りの第2スキル選択 "
139         for amu_key, amu_name in zip([view.KEY_AMULET1, view.KEY_AMULET2, view.KEY_AMULET3], 
140                 [view.NAME_AMULET1, view.NAME_AMULET2, view.NAME_AMULET3]):
141             amu_id = self._amulet_name2id_dict[amu_name]
142             skill_ids = self._amulet_id2skill2_id_dict[amu_id]
143             skill_names = [view.VAL_NO_SKILL] + [self._skill_id2name_dict[x] for x in skill_ids]
144             for combo in combo_dict[amu_key]:
145                combo.SetItems(skill_names)
146                combo.SetSelection(0)
147
148         # amulet search
149         amu_id = self._amulet_name2id_dict[view.NAME_AMULET1]
150         self._set_skill_list_from_amulet(amu_id)
151         self.frame_view.amulet2radio_button_dict[view.NAME_AMULET1].SetValue(True)
152
153     def _set_skill_list_from_amulet(self, amulet_id):
154         u""" お守り種類の選択が変わった時の動作 """
155
156         for i, (combo, skill_dict) in enumerate(zip(
157                 [self.frame_view.combo_box_amulet_search_skill1, 
158                     self.frame_view.combo_box_amulet_search_skill2], 
159                 [self._amulet_id2skill1_id_dict, self._amulet_id2skill2_id_dict]
160                 )):
161             skill_ids = skill_dict[amulet_id]
162             skill_names = [self._skill_id2name_dict[_id] for _id in skill_ids]
163             if i == 0:
164                 combo.SetItems(skill_names)
165                 skill1_selected = skill_ids[0]
166             elif i == 1:
167                 combo.SetItems([view.VAL_NO_SKILL] + skill_names)
168             else:
169                 raise NotImplementedError(u"this iteration must be conducted only twice!")
170             combo.SetSelection(0)   # combo1 = skill1, combo2 = No Skill
171         self._set_spin_range(amulet_id, skill1_selected, None)
172
173     def _set_spin_views(self):
174         u""" Spin Ctrlの最大最小をセット"""
175         min1, max1, min2, max2 = self.db_accessor.get_skill_minmax()
176         self.frame_view.spin_ctrl_amulet_search_slot_num.SetRange(view.SLOT_MIN, view.SLOT_MAX)
177         self.frame_view.spin_ctrl_highlight.SetRange(view.THRESHOLD1_MIN, view.THRESHOLD1_MAX)
178         self.frame_view.spin_ctrl_highlight.SetValue(self._highlight_threshold1)
179
180     def _set_check_list_views(self):
181         u""" CheckListBoxの値のセット """
182         checklist = self.frame_view.check_list_box_highlight_skill
183         checklist.SetItems(self._skill_name2id_dict.keys())
184         checklist.SetCheckedStrings([self._skill_id2name_dict[x] for x in self._highlight_skills])
185
186     def _set_spin_range(self, amulet_id, skill1_id, skill2_id):
187         u""" スキルに応じてSpinCtrlの最大最小をセットする """
188         if skill2_id is None:
189             minmax_dict = self.db_accessor.select_minmax_from_skill_ids(amulet_id, [skill1_id])
190             min1, max1 = minmax_dict[skill1_id][0], minmax_dict[skill1_id][1]
191             min2, max2 = 0, 0
192         else:
193             minmax_dict = self.db_accessor.select_minmax_from_skill_ids(amulet_id, [skill1_id, skill2_id])
194             min1, max1 = minmax_dict[skill1_id][0], minmax_dict[skill1_id][1]
195             min2, max2 = minmax_dict[skill2_id][2], minmax_dict[skill2_id][3]
196         self.frame_view.spin_ctrl_amulet_search_skill1_val.SetRange(min1, max1)
197         self.frame_view.spin_ctrl_amulet_search_skill2_val.SetRange(min2, max2)
198
199     def OnClickSeedSearch(self, evt):
200         u""" search seeds from selected skills """
201         amu_id2skill_id_list_dict = {}
202         for amu_key, amu_name in zip([view.KEY_AMULET1, view.KEY_AMULET2, view.KEY_AMULET3], 
203                 [view.NAME_AMULET1, view.NAME_AMULET2, view.NAME_AMULET3]):
204             amu_id = self._amulet_name2id_dict[amu_name]
205             ls = []
206             for combo in self.frame_view.combo_box_skill_dict[amu_key]:
207                 name = combo.GetStringSelection()
208                 if name not in self._skill_name2id_dict:
209                     ls.append(None)
210                 else:
211                     ls.append(self._skill_name2id_dict[name])
212             amu_id2skill_id_list_dict[amu_id] = ls
213         seed_sets = self.db_accessor.select_seeds(amu_id2skill_id_list_dict)
214         self.frame_view.text_ctrl_seed_result.SetValue(u"""Seedの候補は{0}個です。""".format(len(seed_sets)))
215
216         if len(seed_sets) > 0:
217             self.frame_view.list_box_seed.SetItems([u"{0}".format(seed) for seed in seed_sets])
218             self.frame_view.list_box_seed.SetSelection(0)
219             self.frame_view.button_skill_from_seed_search.Enable()
220         else:
221             self.frame_view.list_box_seed.Clear()
222
223
224     def OnClickSeedClear(self, evt):
225         u""" reset seed search settings of combo box"""
226         combo_dict = self.frame_view.combo_box_skill_dict
227         for amu_key in [view.KEY_AMULET1, view.KEY_AMULET2, view.KEY_AMULET3]:
228             for combo in combo_dict[amu_key]:
229                 combo.SetSelection(0)
230         self.frame_view.text_ctrl_seed_result.SetValue(u"")
231         self.frame_view.button_skill_from_seed_search.Disable()
232         self.frame_view.list_box_seed.Clear()
233
234     def OnClickSkillSearchFromSeed(self, evt):
235         u""" change page to skill search from seed"""
236         seed = self.frame_view.list_box_seed.GetStringSelection()
237         if seed.isdigit():
238             self.frame_view.text_ctrl_seed_select.SetValue(seed)
239             self.frame_view.note_book.SetSelection(view.SKILL_SEARCH_PAGE)
240             self.OnClickSkillSearch(evt)
241
242     def OnClickSkillSearch(self, evt):
243         u""" skill search from seed"""
244         seed = self.frame_view.text_ctrl_seed_select.GetValue()
245         if seed.isdigit():
246             seed = int(seed)
247             skill_dict, threshold1_dict = self.db_accessor.select_skills_from_seeds([seed])
248
249             try:
250                 text_ctrl_dict = self.frame_view.text_ctrl_seed_skill_dict
251                 for amu_key, amu_name in zip([view.KEY_AMULET1, view.KEY_AMULET2, view.KEY_AMULET3], 
252                     [view.NAME_AMULET1, view.NAME_AMULET2, view.NAME_AMULET3]):
253                     amu_id = self._amulet_name2id_dict[amu_name]
254                     for txt_ctr, skill_id in zip(text_ctrl_dict[amu_key], skill_dict[amu_id][seed]):
255                         txt_ctr.SetValue(self._skill_id2name_dict[skill_id])
256                 for txt_ctr, threshold1 in zip(text_ctrl_dict[view.KEY_THRESHOLD1], threshold1_dict[seed]):
257                     txt_ctr.SetValue(u"{0}".format(threshold1))
258                 self._update_highlight()
259
260             except KeyError, e:
261                 self._show_message_dialog(message=u"指定されたSeed値は存在しません")
262         else:
263             self._show_message_dialog(message=u"Seed値には数字を入力してください")
264
265     def OnClickSkillClear(self, evt):
266         u""" clear skills from seed """
267         text_ctrl_dict = self.frame_view.text_ctrl_seed_skill_dict
268         for amu_key, amu_name in zip([view.KEY_AMULET1, view.KEY_AMULET2, view.KEY_AMULET3], 
269             [view.NAME_AMULET1, view.NAME_AMULET2, view.NAME_AMULET3]):
270             amu_id = self._amulet_name2id_dict[amu_name]
271             for txt_ctr in text_ctrl_dict[amu_key]:
272                 txt_ctr.Clear()
273         for txt_ctr in text_ctrl_dict[view.KEY_THRESHOLD1]:
274             txt_ctr.Clear()
275         self.frame_view.list_box_seed_skill_amulet_prospect.Clear() 
276
277     def OnClickAmuletRadio(self, evt):
278         u""" switch skill lists by amulet id """
279         btn_id = evt.GetId()
280         if btn_id == self.frame_view.ID_RADIO_BUTTON_AMULET1:
281             amu_id = self._amulet_name2id_dict[view.NAME_AMULET1]
282         elif btn_id == self.frame_view.ID_RADIO_BUTTON_AMULET2:
283             amu_id = self._amulet_name2id_dict[view.NAME_AMULET2]
284         elif btn_id == self.frame_view.ID_RADIO_BUTTON_AMULET3:
285             amu_id = self._amulet_name2id_dict[view.NAME_AMULET3]
286         else:
287             return
288         self._set_skill_list_from_amulet(amu_id)
289
290     def OnClickAmuletCombo(self, evt):
291         u""" switch skill minmax by amulet id and skill_id"""
292         skill1_name = self.frame_view.combo_box_amulet_search_skill1.GetStringSelection()
293         skill2_name = self.frame_view.combo_box_amulet_search_skill2.GetStringSelection()
294         skill1_id = self._skill_name2id_dict[skill1_name]
295         if skill2_name in self._skill_name2id_dict:
296             skill2_id = self._skill_name2id_dict[skill2_name]
297         else:
298             skill2_id = None
299         if self.frame_view.amulet2radio_button_dict[view.NAME_AMULET1].GetValue():
300             amu_id = self._amulet_name2id_dict[view.NAME_AMULET1]
301         elif self.frame_view.amulet2radio_button_dict[view.NAME_AMULET2].GetValue():
302             amu_id = self._amulet_name2id_dict[view.NAME_AMULET2]
303         elif self.frame_view.amulet2radio_button_dict[view.NAME_AMULET3].GetValue():
304             amu_id = self._amulet_name2id_dict[view.NAME_AMULET3]
305         else:
306             raise IndexError(u"amulet id is unknown")
307         self._set_spin_range(amu_id, skill1_id, skill2_id)
308
309     def OnClickAmuletSearch(self, evt):
310         u""" search seeds from amulet condition """
311         skill1_name = self.frame_view.combo_box_amulet_search_skill1.GetStringSelection()
312         skill2_name = self.frame_view.combo_box_amulet_search_skill2.GetStringSelection()
313         skill1_val = self.frame_view.spin_ctrl_amulet_search_skill1_val.GetValue()
314         skill2_val = self.frame_view.spin_ctrl_amulet_search_skill2_val.GetValue()
315         slot_val = self.frame_view.spin_ctrl_amulet_search_slot_num.GetValue()
316         if self.frame_view.amulet2radio_button_dict[view.NAME_AMULET1].GetValue():
317             amu_id = self._amulet_name2id_dict[view.NAME_AMULET1]
318         elif self.frame_view.amulet2radio_button_dict[view.NAME_AMULET2].GetValue():
319             amu_id = self._amulet_name2id_dict[view.NAME_AMULET2]
320         elif self.frame_view.amulet2radio_button_dict[view.NAME_AMULET3].GetValue():
321             amu_id = self._amulet_name2id_dict[view.NAME_AMULET3]
322         else:
323             raise IndexError(u"amulet id is unknown")
324
325         list_box = self.frame_view.list_box_amulet_search_seeds
326         if skill1_name == skill2_name:
327             self._show_message_dialog(message=u"異なるスキルを選択してください")
328         elif skill1_val == 0:
329             self._show_message_dialog(message=u"第1スキルの値には0以外を指定してください")
330         else:
331             skill1_id = self._skill_name2id_dict[skill1_name]
332             if skill2_name in self._skill_name2id_dict:
333                 skill2_id = self._skill_name2id_dict[skill2_name]
334             else:
335                 skill2_id = None # for skill2 is 0
336             suff_dict = {}
337             tup = self.db_accessor.get_sufficient_value(
338                 amu_id, skill1_id, skill2_id, skill1_val, skill2_val)
339             if tup is not None:
340                 suff_val = tup[0]
341                 seeds_set = self.db_accessor.select_seeds_from_sufficient_val(amu_id, suff_val, slot_val, skill2_id)
342                 if len(seeds_set) > 0:
343                     self.frame_view.text_ctrl_amulet_search_result.SetValue(
344                         u"{0}個のSeedで出現するお守りです".format(len(seeds_set)))
345                     list_box.SetItems([u"{0}".format(seed) for seed in seeds_set])
346                     list_box.SetSelection(0)
347                     self.frame_view.button_amulet_search_skill.Enable()
348                 else:
349                     self.frame_view.text_ctrl_amulet_search_result.SetValue(
350                         u"指定されたお守りは見つかりませんでした")
351                     self.frame_view.button_amulet_search_skill.Disable()
352                     list_box.Clear()
353             else:
354                 self.frame_view.text_ctrl_amulet_search_result.SetValue(
355                     u"エラー。充足値が計算できません")
356                 self.frame_view.button_amulet_search_skill.Disable()
357                 list_box.Clear()
358
359     def OnClickAmuletClear(self, evt):
360         u""" clear amulet conditions """
361         amu_id = self._amulet_name2id_dict[view.NAME_AMULET1]
362         self._set_skill_list_from_amulet(amu_id)
363         self.frame_view.amulet2radio_button_dict[view.NAME_AMULET1].SetValue(True)
364         self.frame_view.button_amulet_search_skill.Disable()
365         self.frame_view.text_ctrl_amulet_search_result.SetValue(u"")
366         self.frame_view.list_box_amulet_search_seeds.Clear()
367
368     def OnClickSkillSearchFromAmulet(self, evt):
369         u""" change page to skill search from amulet"""
370         seed = self.frame_view.list_box_amulet_search_seeds.GetStringSelection()
371         if seed.isdigit():
372             self.frame_view.text_ctrl_seed_select.SetValue(seed)
373             self.frame_view.note_book.SetSelection(view.SKILL_SEARCH_PAGE)
374             self.OnClickSkillSearch(evt)
375
376     def OnClickSettingOK(self, evt):
377         u""" get settings of setting tab """
378         self._highlight_threshold1 = self.frame_view.spin_ctrl_highlight.GetValue()
379         self._highlight_skills = set([self._skill_name2id_dict[x] for x in 
380                 self.frame_view.check_list_box_highlight_skill.GetCheckedStrings()
381                 if x in self._skill_name2id_dict])
382         self._update_highlight()
383
384     def OnClickSettingClear(self, evt):
385         u""" reset settings of setting tab """
386         self._highlight_threshold1 = view.HIGHLIGHT_THRESHOLD1
387         self.frame_view.spin_ctrl_highlight.SetValue(view.HIGHLIGHT_THRESHOLD1)
388         self._highlight_skills = set()
389         for idx in self.frame_view.check_list_box_highlight_skill.GetChecked():
390             self.frame_view.check_list_box_highlight_skill.Check(idx, False)
391         self._update_highlight()
392
393     def _update_highlight(self):
394         u""" update highlight cells """
395         text_dict = self.frame_view.text_ctrl_seed_skill_dict
396         for key in [view.KEY_AMULET1, view.KEY_AMULET2, view.KEY_AMULET3]:
397             for text_ctrl in text_dict[key]:
398                 val = text_ctrl.GetValue()
399                 if (val in self._skill_name2id_dict and
400                         self._skill_name2id_dict[val] in self._highlight_skills):
401                     text_ctrl.SetBackgroundColour("Yellow")
402                 else:
403                     text_ctrl.SetBackgroundColour(wx.NullColor)
404         for text_ctrl in text_dict[view.KEY_THRESHOLD1]:
405             val = text_ctrl.GetValue()
406             if val.isdigit() and int(val) >= self._highlight_threshold1:
407                 text_ctrl.SetBackgroundColour("Yellow")
408             else:
409                 text_ctrl.SetBackgroundColour(wx.NullColor)
410
411
412     def _show_error_dialog(self, message=u"予期せぬエラーが発生しました", caption=u"エラー"):
413         u""" エラーダイアログを表示し、
414         OKボタンが押されたらアプリケーションを終了する
415         """
416         dlg = wx.MessageDialog(self.frame_view.frame, 
417             message,
418             caption, wx.OK | wx.ICON_ERROR)
419         dlg.ShowModal()
420         dlg.Destroy()
421         wx.Exit()
422     
423     def _show_message_dialog(self, message, caption=u"メッセージ"):
424         u""" メッセージダイアログを表示する
425         """
426         dlg = wx.MessageDialog(self.frame_view.frame, 
427             message,
428             caption, wx.OK | wx.ICON_INFORMATION)
429         dlg.ShowModal()
430         dlg.Destroy()
431
432     def CloseHandler(self, evt):
433         dlg = wx.MessageDialog(parent = self.frame_view.frame,
434                 message = u"終了します。よろしいですか?", 
435                 caption = u"終了確認", 
436                 style = wx.YES_NO)
437         result = dlg.ShowModal()
438         dlg.Destroy()
439         if result == wx.ID_YES:
440             self._write_settings()
441             wx.Exit()
442
443     def OnClose(self, evt):
444         self.frame_view.Close()
445
446     def OnAboutBox(self, evt):
447         info = self.frame_view.GetAboutInfo()
448         wx.AboutBox(info)
449
450     def _write_settings(self):
451         with open(SETTING_FILE, mode="w") as f:
452             data = {SETTING_THRESHOLD1:self._highlight_threshold1, 
453                     SETTING_SKILLS:self._highlight_skills}
454             pickle.dump(data, f)
455
456     def _read_settings(self):
457         if os.path.exists(SETTING_FILE):
458             with open(SETTING_FILE, mode="r") as f:
459                 try:
460                     data = pickle.load(f)
461                     self._highlight_threshold1 = data[SETTING_THRESHOLD1] 
462                     self._highlight_skills = data[SETTING_SKILLS]
463                 except EOFError, e:
464                     self._highlight_threshold1 = view.HIGHLIGHT_THRESHOLD1
465                     self._highlight_skills = set()
466         else:
467             self._highlight_threshold1 = view.HIGHLIGHT_THRESHOLD1
468             self._highlight_skills = set()
469
470 if __name__ == "__main__":
471     app = AmuletToolController(False)
472     app.MainLoop()
473