OSDN Git Service

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