OSDN Git Service

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