OSDN Git Service

e52b0e1834ce77664bdb4b25c1f8bd332d7b2d78
[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_THRESHOLD1 = u"threshold1"
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 = [view.VAL_NO_SKILL] + [self._skill_id2name_dict[x] for x in skill_ids]
140             for combo in combo_dict[amu_key]:
141                combo.SetItems(skill_names)
142                combo.SetSelection(0)
143
144         # amulet search
145         amu_id = self._amulet_name2id_dict[view.NAME_AMULET1]
146         self._set_skill_list_from_amulet(amu_id)
147         self.frame_view.amulet2radio_button_dict[view.NAME_AMULET1].SetValue(True)
148
149     def _set_skill_list_from_amulet(self, amulet_id):
150         u""" お守り種類の選択が変わった時の動作 """
151
152         for i, (combo, skill_dict) in enumerate(zip(
153                 [self.frame_view.combo_box_amulet_search_skill1, 
154                     self.frame_view.combo_box_amulet_search_skill2], 
155                 [self._amulet_id2skill1_id_dict, self._amulet_id2skill2_id_dict]
156                 )):
157             skill_ids = skill_dict[amulet_id]
158             skill_names = [self._skill_id2name_dict[_id] for _id in skill_ids]
159             if i == 0:
160                 combo.SetItems(skill_names)
161                 skill1_selected = skill_ids[0]
162             elif i == 1:
163                 combo.SetItems([view.VAL_NO_SKILL] + skill_names)
164             else:
165                 raise NotImplementedError(u"this iteration must be conducted only twice!")
166             combo.SetSelection(0)   # combo1 = skill1, combo2 = No Skill
167         self._set_spin_range(amulet_id, skill1_selected, None)
168
169     def _set_spin_views(self):
170         u""" Spin Ctrlの最大最小をセット"""
171         min1, max1, min2, max2 = self.db_accessor.get_skill_minmax()
172         self.frame_view.spin_ctrl_amulet_search_slot_num.SetRange(view.SLOT_MIN, view.SLOT_MAX)
173         self.frame_view.spin_ctrl_highlight.SetRange(view.THRESHOLD1_MIN, view.THRESHOLD1_MAX)
174         self.frame_view.spin_ctrl_highlight.SetValue(self._highlight_threshold1)
175
176     def _set_check_list_views(self):
177         u""" CheckListBoxの値のセット """
178         checklist = self.frame_view.check_list_box_highlight_skill
179         checklist.SetItems(self._skill_name2id_dict.keys())
180         checklist.SetCheckedStrings([self._skill_id2name_dict[x] for x in self._highlight_skills])
181
182     def _set_spin_range(self, amulet_id, skill1_id, skill2_id):
183         u""" スキルに応じてSpinCtrlの最大最小をセットする """
184         if skill2_id is None:
185             minmax_dict = self.db_accessor.select_minmax_from_skill_ids(amulet_id, [skill1_id])
186             min1, max1 = minmax_dict[skill1_id][0], minmax_dict[skill1_id][1]
187             min2, max2 = 0, 0
188         else:
189             minmax_dict = self.db_accessor.select_minmax_from_skill_ids(amulet_id, [skill1_id, skill2_id])
190             min1, max1 = minmax_dict[skill1_id][0], minmax_dict[skill1_id][1]
191             min2, max2 = minmax_dict[skill2_id][2], minmax_dict[skill2_id][3]
192         self.frame_view.spin_ctrl_amulet_search_skill1_val.SetRange(min1, max1)
193         self.frame_view.spin_ctrl_amulet_search_skill2_val.SetRange(min2, max2)
194
195     def OnClickSeedSearch(self, evt):
196         u""" search seeds from selected skills """
197         amu_id2skill_id_list_dict = {}
198         for amu_key, amu_name in zip([view.KEY_AMULET1, view.KEY_AMULET2, view.KEY_AMULET3], 
199                 [view.NAME_AMULET1, view.NAME_AMULET2, view.NAME_AMULET3]):
200             amu_id = self._amulet_name2id_dict[amu_name]
201             ls = []
202             for combo in self.frame_view.combo_box_skill_dict[amu_key]:
203                 name = combo.GetStringSelection()
204                 if name not in self._skill_name2id_dict:
205                     ls.append(None)
206                 else:
207                     ls.append(self._skill_name2id_dict[name])
208             amu_id2skill_id_list_dict[amu_id] = ls
209         seed_sets = self.db_accessor.select_seeds(amu_id2skill_id_list_dict)
210         self.frame_view.text_ctrl_seed_result.SetValue(u"""Seedの候補は{0}個です。""".format(len(seed_sets)))
211
212         if len(seed_sets) > 0:
213             self.frame_view.list_box_seed.SetItems([u"{0}".format(seed) for seed in seed_sets])
214             self.frame_view.list_box_seed.SetSelection(0)
215             self.frame_view.button_skill_from_seed_search.Enable()
216         else:
217             self.frame_view.list_box_seed.Clear()
218
219
220     def OnClickSeedClear(self, evt):
221         u""" reset seed search settings of combo box"""
222         combo_dict = self.frame_view.combo_box_skill_dict
223         for amu_key in [view.KEY_AMULET1, view.KEY_AMULET2, view.KEY_AMULET3]:
224             for combo in combo_dict[amu_key]:
225                 combo.SetSelection(0)
226         self.frame_view.text_ctrl_seed_result.SetValue(u"")
227         self.frame_view.button_skill_from_seed_search.Disable()
228         self.frame_view.list_box_seed.Clear()
229
230     def OnClickSkillSearchFromSeed(self, evt):
231         u""" change page to skill search from seed"""
232         seed = self.frame_view.list_box_seed.GetStringSelection()
233         if seed.isdigit():
234             self.frame_view.text_ctrl_seed_select.SetValue(seed)
235             self.frame_view.note_book.SetSelection(view.SKILL_SEARCH_PAGE)
236             self.OnClickSkillSearch(evt)
237
238     def OnClickSkillSearch(self, evt):
239         u""" skill search from seed"""
240         seed = self.frame_view.text_ctrl_seed_select.GetValue()
241         if seed.isdigit():
242             seed = int(seed)
243             skill_dict, threshold1_dict = self.db_accessor.select_skills_from_seeds([seed])
244
245             try:
246                 text_ctrl_dict = self.frame_view.text_ctrl_seed_skill_dict
247                 for amu_key, amu_name in zip([view.KEY_AMULET1, view.KEY_AMULET2, view.KEY_AMULET3], 
248                     [view.NAME_AMULET1, view.NAME_AMULET2, view.NAME_AMULET3]):
249                     amu_id = self._amulet_name2id_dict[amu_name]
250                     for txt_ctr, skill_id in zip(text_ctrl_dict[amu_key], skill_dict[amu_id][seed]):
251                         txt_ctr.SetValue(self._skill_id2name_dict[skill_id])
252                 for txt_ctr, threshold1 in zip(text_ctrl_dict[view.KEY_THRESHOLD1], threshold1_dict[seed]):
253                     txt_ctr.SetValue(u"{0}".format(threshold1))
254                 self._update_highlight()
255
256             except KeyError, e:
257                 self._show_message_dialog(message=u"指定されたSeed値は存在しません")
258         else:
259             self._show_message_dialog(message=u"Seed値には数字を入力してください")
260
261     def OnClickSkillClear(self, evt):
262         u""" clear skills from seed """
263         text_ctrl_dict = self.frame_view.text_ctrl_seed_skill_dict
264         for amu_key, amu_name in zip([view.KEY_AMULET1, view.KEY_AMULET2, view.KEY_AMULET3], 
265             [view.NAME_AMULET1, view.NAME_AMULET2, view.NAME_AMULET3]):
266             amu_id = self._amulet_name2id_dict[amu_name]
267             for txt_ctr in text_ctrl_dict[amu_key]:
268                 txt_ctr.Clear()
269         for txt_ctr in text_ctrl_dict[view.KEY_THRESHOLD1]:
270             txt_ctr.Clear()
271         self.frame_view.list_box_seed_skill_amulet_prospect.Clear() 
272
273     def OnClickAmuletRadio(self, evt):
274         u""" switch skill lists by amulet id """
275         btn_id = evt.GetId()
276         if btn_id == self.frame_view.ID_RADIO_BUTTON_AMULET1:
277             amu_id = self._amulet_name2id_dict[view.NAME_AMULET1]
278         elif btn_id == self.frame_view.ID_RADIO_BUTTON_AMULET2:
279             amu_id = self._amulet_name2id_dict[view.NAME_AMULET2]
280         elif btn_id == self.frame_view.ID_RADIO_BUTTON_AMULET3:
281             amu_id = self._amulet_name2id_dict[view.NAME_AMULET3]
282         else:
283             return
284         self._set_skill_list_from_amulet(amu_id)
285
286     def OnClickAmuletCombo(self, evt):
287         u""" switch skill minmax by amulet id and skill_id"""
288         skill1_name = self.frame_view.combo_box_amulet_search_skill1.GetStringSelection()
289         skill2_name = self.frame_view.combo_box_amulet_search_skill2.GetStringSelection()
290         skill1_id = self._skill_name2id_dict[skill1_name]
291         if skill2_name in self._skill_name2id_dict:
292             skill2_id = self._skill_name2id_dict[skill2_name]
293         else:
294             skill2_id = None
295         if self.frame_view.amulet2radio_button_dict[view.NAME_AMULET1].GetValue():
296             amu_id = self._amulet_name2id_dict[view.NAME_AMULET1]
297         elif self.frame_view.amulet2radio_button_dict[view.NAME_AMULET2].GetValue():
298             amu_id = self._amulet_name2id_dict[view.NAME_AMULET2]
299         elif self.frame_view.amulet2radio_button_dict[view.NAME_AMULET3].GetValue():
300             amu_id = self._amulet_name2id_dict[view.NAME_AMULET3]
301         else:
302             raise IndexError(u"amulet id is unknown")
303         self._set_spin_range(amu_id, skill1_id, skill2_id)
304
305     def OnClickAmuletSearch(self, evt):
306         u""" search seeds from amulet condition """
307         skill1_name = self.frame_view.combo_box_amulet_search_skill1.GetStringSelection()
308         skill2_name = self.frame_view.combo_box_amulet_search_skill2.GetStringSelection()
309         skill1_val = self.frame_view.spin_ctrl_amulet_search_skill1_val.GetValue()
310         skill2_val = self.frame_view.spin_ctrl_amulet_search_skill2_val.GetValue()
311         slot_val = self.frame_view.spin_ctrl_amulet_search_slot_num.GetValue()
312         if self.frame_view.amulet2radio_button_dict[view.NAME_AMULET1].GetValue():
313             amu_id = self._amulet_name2id_dict[view.NAME_AMULET1]
314         elif self.frame_view.amulet2radio_button_dict[view.NAME_AMULET2].GetValue():
315             amu_id = self._amulet_name2id_dict[view.NAME_AMULET2]
316         elif self.frame_view.amulet2radio_button_dict[view.NAME_AMULET3].GetValue():
317             amu_id = self._amulet_name2id_dict[view.NAME_AMULET3]
318         else:
319             raise IndexError(u"amulet id is unknown")
320
321         list_box = self.frame_view.list_box_amulet_search_seeds
322         if skill1_name == skill2_name:
323             self._show_message_dialog(message=u"異なるスキルを選択してください")
324         elif skill1_val == 0:
325             self._show_message_dialog(message=u"第1スキルの値には0以外を指定してください")
326         else:
327             skill1_id = self._skill_name2id_dict[skill1_name]
328             if skill2_name in self._skill_name2id_dict:
329                 skill2_id = self._skill_name2id_dict[skill2_name]
330             else:
331                 skill2_id = None # for skill2 is 0
332             suff_dict = {}
333             tup = self.db_accessor.get_sufficient_value(
334                 amu_id, skill1_id, skill2_id, skill1_val, skill2_val)
335             if tup is not None:
336                 suff_val = tup[0]
337                 seeds_set = self.db_accessor.select_seeds_from_sufficient_val(amu_id, suff_val, slot_val, skill2_id)
338                 if len(seeds_set) > 0:
339                     self.frame_view.text_ctrl_amulet_search_result.SetValue(
340                         u"{0}個のSeedで出現するお守りです".format(len(seeds_set)))
341                     list_box.SetItems([u"{0}".format(seed) for seed in seeds_set])
342                     list_box.SetSelection(0)
343                     self.frame_view.button_amulet_search_skill.Enable()
344                 else:
345                     self.frame_view.text_ctrl_amulet_search_result.SetValue(
346                         u"指定されたお守りは見つかりませんでした")
347                     self.frame_view.button_amulet_search_skill.Disable()
348                     list_box.Clear()
349             else:
350                 self.frame_view.text_ctrl_amulet_search_result.SetValue(
351                     u"エラー。充足値が計算できません")
352                 self.frame_view.button_amulet_search_skill.Disable()
353                 list_box.Clear()
354
355     def OnClickAmuletClear(self, evt):
356         u""" clear amulet conditions """
357         amu_id = self._amulet_name2id_dict[view.NAME_AMULET1]
358         self._set_skill_list_from_amulet(amu_id)
359         self.frame_view.amulet2radio_button_dict[view.NAME_AMULET1].SetValue(True)
360         self.frame_view.button_amulet_search_skill.Disable()
361         self.frame_view.text_ctrl_amulet_search_result.SetValue(u"")
362         self.frame_view.list_box_amulet_search_seeds.Clear()
363
364     def OnClickSkillSearchFromAmulet(self, evt):
365         u""" change page to skill search from amulet"""
366         seed = self.frame_view.list_box_amulet_search_seeds.GetStringSelection()
367         if seed.isdigit():
368             self.frame_view.text_ctrl_seed_select.SetValue(seed)
369             self.frame_view.note_book.SetSelection(view.SKILL_SEARCH_PAGE)
370             self.OnClickSkillSearch(evt)
371
372     def OnClickSettingOK(self, evt):
373         u""" get settings of setting tab """
374         self._highlight_threshold1 = self.frame_view.spin_ctrl_highlight.GetValue()
375         self._highlight_skills = set([self._skill_name2id_dict[x] for x in 
376                 self.frame_view.check_list_box_highlight_skill.GetCheckedStrings()
377                 if x in self._skill_name2id_dict])
378         self._update_highlight()
379
380     def OnClickSettingClear(self, evt):
381         u""" reset settings of setting tab """
382         self._highlight_threshold1 = view.HIGHLIGHT_THRESHOLD1
383         self.frame_view.spin_ctrl_highlight.SetValue(view.HIGHLIGHT_THRESHOLD1)
384         self._highlight_skills = set()
385         for idx in self.frame_view.check_list_box_highlight_skill.GetChecked():
386             self.frame_view.check_list_box_highlight_skill.Check(idx, False)
387         self._update_highlight()
388
389     def _update_highlight(self):
390         u""" update highlight cells """
391         text_dict = self.frame_view.text_ctrl_seed_skill_dict
392         for key in [view.KEY_AMULET1, view.KEY_AMULET2, view.KEY_AMULET3]:
393             for text_ctrl in text_dict[key]:
394                 val = text_ctrl.GetValue()
395                 if (val in self._skill_name2id_dict and
396                         self._skill_name2id_dict[val] in self._highlight_skills):
397                     text_ctrl.SetBackgroundColour("Yellow")
398                 else:
399                     text_ctrl.SetBackgroundColour(wx.NullColor)
400         for text_ctrl in text_dict[view.KEY_THRESHOLD1]:
401             val = text_ctrl.GetValue()
402             if val.isdigit() and int(val) >= self._highlight_threshold1:
403                 text_ctrl.SetBackgroundColour("Yellow")
404             else:
405                 text_ctrl.SetBackgroundColour(wx.NullColor)
406
407
408     def _show_error_dialog(self, message=u"予期せぬエラーが発生しました", caption=u"エラー"):
409         u""" エラーダイアログを表示し、
410         OKボタンが押されたらアプリケーションを終了する
411         """
412         dlg = wx.MessageDialog(self.frame_view.frame, 
413             message,
414             caption, wx.OK | wx.ICON_ERROR)
415         dlg.ShowModal()
416         dlg.Destroy()
417         wx.Exit()
418     
419     def _show_message_dialog(self, message, caption=u"メッセージ"):
420         u""" メッセージダイアログを表示する
421         """
422         dlg = wx.MessageDialog(self.frame_view.frame, 
423             message,
424             caption, wx.OK | wx.ICON_INFORMATION)
425         dlg.ShowModal()
426         dlg.Destroy()
427
428     def CloseHandler(self, evt):
429         dlg = wx.MessageDialog(parent = self.frame_view.frame,
430                 message = u"終了します。よろしいですか?", 
431                 caption = u"終了確認", 
432                 style = wx.YES_NO)
433         result = dlg.ShowModal()
434         dlg.Destroy()
435         if result == wx.ID_YES:
436             self._write_settings()
437             wx.Exit()
438
439     def OnClose(self, evt):
440         self.frame_view.Close()
441
442     def OnAboutBox(self, evt):
443         info = self.frame_view.GetAboutInfo()
444         wx.AboutBox(info)
445
446     def _write_settings(self):
447         with open(SETTING_FILE, mode="w") as f:
448             data = {SETTING_THRESHOLD1:self._highlight_threshold1, 
449                     SETTING_SKILLS:self._highlight_skills}
450             pickle.dump(data, f)
451
452     def _read_settings(self):
453         if os.path.exists(SETTING_FILE):
454             with open(SETTING_FILE, mode="r") as f:
455                 try:
456                     data = pickle.load(f)
457                     self._highlight_threshold1 = data[SETTING_THRESHOLD1] 
458                     self._highlight_skills = data[SETTING_SKILLS]
459                 except EOFError, e:
460                     self._highlight_threshold1 = view.HIGHLIGHT_THRESHOLD1
461                     self._highlight_skills = set()
462         else:
463             self._highlight_threshold1 = view.HIGHLIGHT_THRESHOLD1
464             self._highlight_skills = set()
465
466 if __name__ == "__main__":
467     app = AmuletToolController(False)
468     app.MainLoop()
469