OSDN Git Service

In widgets of wxpython, use SetItems() instead of Append() iteration
[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.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.SetItems([u"{0}".format(seed) for seed in seeds_set])
341                     list_box.SetSelection(0)
342                     self.frame_view.button_amulet_search_skill.Enable()
343                 else:
344                     self.frame_view.text_ctrl_amulet_search_result.SetValue(
345                         u"指定されたお守りは見つかりませんでした")
346                     self.frame_view.button_amulet_search_skill.Disable()
347                     list_box.Clear()
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
354     def OnClickAmuletClear(self, evt):
355         u""" clear amulet conditions """
356         amu_id = self._amulet_name2id_dict[view.NAME_AMULET1]
357         self._set_skill_list_from_amulet(amu_id)
358         self.frame_view.amulet2radio_button_dict[view.NAME_AMULET1].SetValue(True)
359         self.frame_view.button_amulet_search_skill.Disable()
360         self.frame_view.text_ctrl_amulet_search_result.SetValue(u"")
361         self.frame_view.list_box_amulet_search_seeds.Clear()
362
363     def OnClickSkillSearchFromAmulet(self, evt):
364         u""" change page to skill search from amulet"""
365         seed = self.frame_view.list_box_amulet_search_seeds.GetStringSelection()
366         if seed.isdigit():
367             self.frame_view.text_ctrl_seed_select.SetValue(seed)
368             self.frame_view.note_book.SetSelection(view.SKILL_SEARCH_PAGE)
369             self.OnClickSkillSearch(evt)
370
371     def OnClickSettingOK(self, evt):
372         u""" get settings of setting tab """
373         self._highlight_threshold1 = self.frame_view.spin_ctrl_highlight.GetValue()
374         self._highlight_skills = set([self._skill_name2id_dict[x] for x in 
375                 self.frame_view.check_list_box_highlight_skill.GetCheckedStrings()
376                 if x in self._skill_name2id_dict])
377         self._update_highlight()
378
379     def OnClickSettingClear(self, evt):
380         u""" reset settings of setting tab """
381         self._highlight_threshold1 = view.HIGHLIGHT_THRESHOLD1
382         self.frame_view.spin_ctrl_highlight.SetValue(view.HIGHLIGHT_THRESHOLD1)
383         self._highlight_skills = set()
384         for idx in self.frame_view.check_list_box_highlight_skill.GetChecked():
385             self.frame_view.check_list_box_highlight_skill.Check(idx, False)
386         self._update_highlight()
387
388     def _update_highlight(self):
389         u""" update highlight cells """
390         text_dict = self.frame_view.text_ctrl_seed_skill_dict
391         for key in [view.KEY_AMULET1, view.KEY_AMULET2, view.KEY_AMULET3]:
392             for text_ctrl in text_dict[key]:
393                 val = text_ctrl.GetValue()
394                 if (val in self._skill_name2id_dict and
395                         self._skill_name2id_dict[val] in self._highlight_skills):
396                     text_ctrl.SetBackgroundColour("Yellow")
397                 else:
398                     text_ctrl.SetBackgroundColour(wx.NullColor)
399         for text_ctrl in text_dict[view.KEY_THRESHOLD1]:
400             val = text_ctrl.GetValue()
401             if val.isdigit() and int(val) >= self._highlight_threshold1:
402                 text_ctrl.SetBackgroundColour("Yellow")
403             else:
404                 text_ctrl.SetBackgroundColour(wx.NullColor)
405
406
407     def _show_error_dialog(self, message=u"予期せぬエラーが発生しました", caption=u"エラー"):
408         u""" エラーダイアログを表示し、
409         OKボタンが押されたらアプリケーションを終了する
410         """
411         dlg = wx.MessageDialog(self.frame_view.frame, 
412             message,
413             caption, wx.OK | wx.ICON_ERROR)
414         dlg.ShowModal()
415         dlg.Destroy()
416         wx.Exit()
417     
418     def _show_message_dialog(self, message, caption=u"メッセージ"):
419         u""" メッセージダイアログを表示する
420         """
421         dlg = wx.MessageDialog(self.frame_view.frame, 
422             message,
423             caption, wx.OK | wx.ICON_INFORMATION)
424         dlg.ShowModal()
425         dlg.Destroy()
426
427     def CloseHandler(self, evt):
428         dlg = wx.MessageDialog(parent = self.frame_view.frame,
429                 message = u"終了します。よろしいですか?", 
430                 caption = u"終了確認", 
431                 style = wx.YES_NO)
432         result = dlg.ShowModal()
433         dlg.Destroy()
434         if result == wx.ID_YES:
435             self._write_settings()
436             wx.Exit()
437
438     def OnClose(self, evt):
439         self.frame_view.Close()
440
441     def OnAboutBox(self, evt):
442         info = self.frame_view.GetAboutInfo()
443         wx.AboutBox(info)
444
445     def _write_settings(self):
446         with open(SETTING_FILE, mode="w") as f:
447             data = {SETTING_THRESHOLD1:self._highlight_threshold1, 
448                     SETTING_SKILLS:self._highlight_skills}
449             pickle.dump(data, f)
450
451     def _read_settings(self):
452         if os.path.exists(SETTING_FILE):
453             with open(SETTING_FILE, mode="r") as f:
454                 try:
455                     data = pickle.load(f)
456                     self._highlight_threshold1 = data[SETTING_THRESHOLD1] 
457                     self._highlight_skills = data[SETTING_SKILLS]
458                 except EOFError, e:
459                     self._highlight_threshold1 = view.HIGHLIGHT_THRESHOLD1
460                     self._highlight_skills = set()
461         else:
462             self._highlight_threshold1 = view.HIGHLIGHT_THRESHOLD1
463             self._highlight_skills = set()
464
465 if __name__ == "__main__":
466     app = AmuletToolController(False)
467     app.MainLoop()
468