OSDN Git Service

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