PanelTeams.py 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514
  1. """
  2. This file is part of RuneOptimizer.
  3. RuneOptimizer is free software: you can redistribute it and/or modify it
  4. under the terms of the GNU General Public License as published by the Free
  5. Software Foundation, either version 3 of the License, or (at your option)
  6. any later version.
  7. RuneOptimizer is distributed in the hope that it will be useful, but WITHOUT
  8. ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  9. FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
  10. more details.
  11. You should have received a copy of the GNU General Public License along with
  12. RuneOptimizer. If not, see <https://www.gnu.org/licenses/>.
  13. """
  14. class PanelTeams(wx.Panel):
  15. """
  16. The team management Panel.
  17. Lists teams, and allows for team creation and deletion, and for ading and
  18. removing units to and from teams.
  19. """
  20. _selected_team = None
  21. _title_change_pending = False
  22. _priority_change_pending = False
  23. _team_list = None
  24. _details_sizer = None
  25. _title_text = None
  26. _priority_text = None
  27. _team_unit_list = None
  28. _all_unit_list = None
  29. _filter_name_text = None
  30. _filter_name_texts = None
  31. _filter_no_runes_check = None
  32. _filter_no_teams_check = None
  33. _title_timer = None
  34. _priority_time = None
  35. def __init__(self, parent, id=wx.ID_ANY):
  36. """Initializes the panel.
  37. Sets upt all the widgets.
  38. Parameters
  39. ----------
  40. parent : wx.*
  41. Panel parent.
  42. id : int, optional
  43. ID for the panel (default wx.ID_ANY).
  44. """
  45. # Parent constructor
  46. wx.Panel.__init__(self, parent=parent, id=id)
  47. # Prepare some fonts
  48. bold_font = wx.Font(
  49. pointSize=10, family=wx.FONTFAMILY_DEFAULT,
  50. style=wx.FONTSTYLE_NORMAL, weight=wx.FONTWEIGHT_BOLD
  51. )
  52. team_list_box = wx.StaticBox(
  53. self, label="Select unit:", id=wx.ID_ANY, pos=(0, 0), size=(270, 590)
  54. )
  55. wx.StaticText(
  56. parent=team_list_box, id=wx.ID_ANY,
  57. label="Name Prio.",
  58. pos=(10, 10), size=(240, 20)
  59. ).SetFont(bold_font)
  60. self._team_list = wx.ListCtrl(
  61. parent=team_list_box, id=wx.ID_ANY, pos=(10, 30), size=(250, 460),
  62. style=wx.LC_REPORT|wx.LC_NO_HEADER
  63. )
  64. self._team_list.InsertColumn(0, "Name", width=200)
  65. self._team_list.InsertColumn(1, "Prio.", width=40)
  66. self._populate_team_list(event=None)
  67. self._team_list.Bind(wx.EVT_LIST_ITEM_SELECTED, self._team_selected)
  68. new_team_button = wx.Button(
  69. parent=team_list_box, id=wx.ID_ANY, pos=(50, 510), size=(170, 40),
  70. style=wx.LC_REPORT, label="New team"
  71. )
  72. new_team_button.Bind(wx.EVT_BUTTON, self._create_team)
  73. # Sizer for all team details. Will be hidden until a team is selected
  74. self._details_sizer = wx.BoxSizer(wx.VERTICAL)
  75. # Team title and priority editors
  76. self._title_text = wx.TextCtrl(
  77. parent=self, id=wx.ID_ANY,
  78. pos=(300, 30), size=(200, 25), style=wx.TE_RICH|wx.TE_MULTILINE
  79. )
  80. self._details_sizer.Add(self._title_text)
  81. self._title_text.Bind(wx.EVT_TEXT, self._change_title);
  82. self._details_sizer.Add(
  83. wx.StaticText(
  84. parent=self, id=wx.ID_ANY, label="Priority:",
  85. pos=(310, 80), size=(80, 30)
  86. )
  87. )
  88. self._priority_text = wx.TextCtrl(
  89. parent=self, id=wx.ID_ANY,
  90. pos=(360, 80), size=(30, 25), style=wx.TE_RICH|wx.TE_MULTILINE)
  91. self._details_sizer.Add(self._priority_text)
  92. self._priority_text.Bind(wx.EVT_TEXT, self._change_priority);
  93. # Team unit list
  94. team_units_box = wx.StaticBox(
  95. parent=self, label="Units in team:",
  96. id=wx.ID_ANY, pos=(300, 130), size=(150, 380)
  97. )
  98. self._team_unit_list = wx.ListCtrl(
  99. parent=team_units_box, id=wx.ID_ANY, pos=(5, 5), size=(140, 360),
  100. style=wx.LC_REPORT|wx.LC_NO_HEADER
  101. )
  102. self._details_sizer.Add(team_units_box)
  103. self._team_unit_list.InsertColumn(0, "Name", width=140)
  104. self._team_unit_list.InsertColumn(1, "Level", width=140)
  105. # All unit selector
  106. all_units_box = wx.StaticBox(
  107. parent=self, label="Other units:",
  108. id=wx.ID_ANY, pos=(600, 130), size=(210, 380)
  109. )
  110. self._details_sizer.Add(all_units_box)
  111. wx.StaticText(
  112. parent=all_units_box, id=wx.ID_ANY,
  113. label="Name Prio. Sto.",
  114. pos=(10, 10), size=(190, 20)
  115. ).SetFont(bold_font)
  116. self._all_unit_list = wx.ListCtrl(
  117. parent=all_units_box, id=wx.ID_ANY, pos=(10, 30), size=(190, 165),
  118. style=wx.LC_REPORT|wx.LC_NO_HEADER
  119. )
  120. self._details_sizer.Add(self._all_unit_list)
  121. self._all_unit_list.InsertColumn(0, "Name", width=120)
  122. self._all_unit_list.InsertColumn(1, "Prio.", width=40)
  123. self._all_unit_list.InsertColumn(2, "Sto.", width=30)
  124. # List filters
  125. filter_box = wx.StaticBox(
  126. parent=all_units_box, label="Filters:",id=wx.ID_ANY,
  127. pos=(10, 200), size=(190, 150)
  128. )
  129. self._details_sizer.Add(filter_box)
  130. wx.StaticText(
  131. parent=filter_box, label="Monster name", pos=(5, 5), size=(180, 20)
  132. )
  133. self._filter_name_text = wx.TextCtrl(
  134. parent=filter_box, id=wx.ID_ANY, value="", pos=(5, 25),
  135. size=(177, 20), style=wx.TE_PROCESS_ENTER|wx.TE_PROCESS_TAB
  136. )
  137. self._filter_name_text.Bind(wx.EVT_TEXT, self._populate_unit_list)
  138. self._filter_storage_check = wx.CheckBox(
  139. parent=filter_box, id=wx.ID_ANY,
  140. label="Monsters in storage", pos=(5, 55), size=(180, 20)
  141. )
  142. self._filter_storage_check.SetValue(True)
  143. self._filter_storage_check.Bind(wx.EVT_CHECKBOX, self._populate_unit_list)
  144. self._filter_no_runes_check = wx.CheckBox(
  145. parent=filter_box, id=wx.ID_ANY,
  146. label="Monsters without runes", pos=(5, 75), size=(180, 20)
  147. )
  148. self._filter_no_runes_check.SetValue(True)
  149. self._filter_no_runes_check.Bind(
  150. wx.EVT_CHECKBOX, self._populate_unit_list
  151. )
  152. self._filter_no_teams_check = wx.CheckBox(
  153. parent=filter_box, id=wx.ID_ANY,
  154. label="Monsters not in teams", pos=(5, 95), size=(180, 20)
  155. )
  156. self._filter_no_teams_check.SetValue(True)
  157. self._filter_no_teams_check.Bind(
  158. wx.EVT_CHECKBOX, self._populate_unit_list
  159. )
  160. self._populate_unit_list(None)
  161. add_button = wx.Button(
  162. parent=self, id=wx.ID_ANY, pos=(450, 200), size=(150, 40),
  163. style=wx.LC_REPORT, label="<<< Add <<<"
  164. )
  165. self._details_sizer.Add(add_button)
  166. add_button.Bind(wx.EVT_BUTTON, self._add_to_team)
  167. remove_button = wx.Button(
  168. parent=self, id=wx.ID_ANY, pos=(450, 250), size=(150, 40),
  169. style=wx.LC_REPORT, label=">>> Remove >>>"
  170. )
  171. self._details_sizer.Add(remove_button)
  172. remove_button.Bind(wx.EVT_BUTTON, self._remove_from_team)
  173. delete_button = wx.Button(
  174. parent=self, id=wx.ID_ANY, pos=(300, 530), size=(150, 40),
  175. style=wx.LC_REPORT, label="Delete team"
  176. )
  177. self._details_sizer.Add(delete_button)
  178. delete_button.Bind(wx.EVT_BUTTON, self._delete_team)
  179. # By default, hide all details
  180. self._details_sizer.ShowItems(False)
  181. def _populate_team_list(self, event=None):
  182. """Populates the team list.
  183. Parameters
  184. ----------
  185. event : wx.Event, optional
  186. The event that triggered the call (default is None).
  187. """
  188. self._team_list.DeleteAllItems()
  189. i = 0
  190. for team in teams.values():
  191. self._team_list.InsertItem(i, team.name)
  192. self._team_list.SetItem(i, 1, str(team.priority))
  193. self._team_list.SetItemData(i, int(team.id))
  194. i = i + 1
  195. def _populate_unit_list(self, event=None):
  196. """Populates the list of all units.
  197. Uses the filters.
  198. Parameters
  199. ----------
  200. event : wx.Event, optional
  201. The event that triggered the call (default is None).
  202. """
  203. # Loop all units
  204. i = 0
  205. filter_name = self._filter_name_text.GetValue()
  206. opt_storage = self._filter_storage_check.GetValue()
  207. opt_no_runes = self._filter_storage_check.GetValue()
  208. opt_no_teams = self._filter_no_teams_check.GetValue()
  209. self._all_unit_list.DeleteAllItems()
  210. for unit in units.values():
  211. if opt_storage == False and unit.in_storage == True:
  212. continue
  213. if opt_no_runes == False and unit.has_runes == False:
  214. continue
  215. if opt_no_teams == False and unit.in_teams == False:
  216. continue
  217. if len(filter_name) > 0:
  218. if filter_name.upper() not in unit.name.upper():
  219. continue
  220. self._all_unit_list.InsertItem(i, unit.name)
  221. self._all_unit_list.SetItem(i, 1, str(unit.priority))
  222. if (unit.in_storage):
  223. self._all_unit_list.SetItem(i, 2, "X")
  224. else:
  225. self._all_unit_list.SetItem(i, 2, " ")
  226. # TODO Items can't hold too much data in Windows, store the first
  227. # nine digits and rerieve it from database with LIKE
  228. self._all_unit_list.SetItemData(i, int(unit.id))
  229. i = i + 1
  230. def _populate_team_unit_list(self, event=None):
  231. """Populates the list of units in the selected team.
  232. Uses the filters.
  233. Parameters
  234. ----------
  235. event : wx.Event, optional
  236. The event that triggered the call (default is None).
  237. """
  238. i = 0
  239. self._team_unit_list.DeleteAllItems()
  240. for unit in self._team_selected.units:
  241. self._team_unit_list.InsertItem(i, unit.name)
  242. self._team_unit_list.SetItem(i, 1, "Lv." + str(unit.level))
  243. self._team_unit_list.SetItemData(i, int(unit.id))
  244. i = i + 1
  245. def _create_team(self, event=None):
  246. """Opens a dialog to create a new team.
  247. Parameters
  248. ----------
  249. event : wx.Event, optional
  250. The event that triggered the call (default is None).
  251. """
  252. with DialogNewTeam(parent=self) as dlg:
  253. if dlg.ShowModal() == wx.ID_OK:
  254. reload_teams()
  255. self._populate_team_list()
  256. def _delete_team(self, event=None):
  257. """Deletes a team.
  258. Shows a confirmation dialog first.
  259. Parameters
  260. ----------
  261. event : wx.Event, optional
  262. The event that triggered the call (default is None).
  263. """
  264. message = "Are you sure you want to delete the team " + \
  265. self._team_selected.name + "?\n\nThis can't be undone."
  266. with DialogConfirm(parent=self, message=message) as dlg:
  267. if dlg.ShowModal() == wx.ID_OK:
  268. # TODO: Do this with a command
  269. cursor = conn.execute(
  270. "DELETE FROM units_teams WHERE team = ?",
  271. (self._team_selected.id,))
  272. cursor = conn.execute(
  273. "DELETE FROM teams WHERE id = ?", (self._team_selected.id,)
  274. )
  275. conn.commit()
  276. reload_teams()
  277. # Clear the selection and hide the details
  278. self._details_sizer.ShowItems(False)
  279. self._populate_team_list(event=None)
  280. self.GetParent().panel_units.populate_unit_list(event=None)
  281. def _add_to_team(self, event=None):
  282. """Adds unit to the currently selected team.
  283. Adds all the units selected in self._all_unit_list. Refreshes both unit
  284. lists and also the unit list in the units panel.
  285. Parameters
  286. ----------
  287. event : wx.Event, optional
  288. The event that triggered the call (default is None).
  289. """
  290. selected_index = self._all_unit_list.GetFirstSelected()
  291. while (selected_index != -1):
  292. unitId = self._all_unit_list.GetItemData(selected_index)
  293. # TODO: Do this with a command
  294. query = "INSERT INTO units_teams (team, unit) VALUES (?, ?)";
  295. cursor = conn.execute(query, (self._team_selected.id, unitId))
  296. selected_index = self._all_unit_list.GetNextSelected(selected_index)
  297. conn.commit()
  298. reload_teams();
  299. self._populate_team_unit_list(event=None)
  300. self._populate_unit_list(event=None)
  301. self.GetParent().panel_units.populate_unit_list(event=None)
  302. def _remove_from_team(self, event=None):
  303. """Removes units the currently selected team.
  304. Removes all the units selected in self._team_unit_list. Refreshes both unit
  305. lists and also the unit list in the units panel.
  306. Parameters
  307. ----------
  308. event : wx.Event, optional
  309. The event that triggered the call (default is None).
  310. """
  311. selected_index = self._team_unit_list.GetFirstSelected()
  312. while (selected_index != -1):
  313. unitId = self._team_unit_list.GetItemData(selected_index)
  314. # TODO: Do this with a command
  315. query = "DELETE FROM units_teams WHERE team = ? AND unit = ?";
  316. cursor = conn.execute(query, (self._team_selected.id, unitId))
  317. selected_index = self._team_unit_list.GetNextSelected(selected_index)
  318. conn.commit()
  319. reload_teams()
  320. self._populate_team_unit_list(event=None)
  321. self._populate_unit_list(event=None)
  322. self.GetParent().panel_units.populate_unit_list(event=None)
  323. def _team_selected(self, event=None):
  324. """Shows the team details.
  325. Called when a team is selected.
  326. Parameters
  327. ----------
  328. event : wx.Event, optional
  329. The event that triggered the call (default is None).
  330. """
  331. selected_team_id = \
  332. str(self._team_list.GetItemData(self._team_list.GetFirstSelected()))
  333. self._team_selected = teams[selected_team_id]
  334. self._populate_team_unit_list(event=None)
  335. # Unbind for the automatic change
  336. self._title_text.Unbind(wx.EVT_TEXT)
  337. self._title_text.SetValue(self._team_selected.name)
  338. # Rebind
  339. self._title_text.Bind(wx.EVT_TEXT, self._change_title);
  340. # Unbind for the automatic change
  341. self._priority_text.Unbind(wx.EVT_TEXT);
  342. self._priority_text.SetValue(str(self._team_selected.priority))
  343. # Rebind
  344. self._priority_text.Bind(wx.EVT_TEXT, self._change_priority);
  345. # Show details
  346. self._details_sizer.ShowItems(True)
  347. def _change_title(self, event):
  348. """Prepares for a title change.
  349. Called everytime the selected team name is changed. It schedules a call
  350. to self._save_new_title in two seconds, to give the user time to enter the
  351. full title.
  352. Parameters
  353. ----------
  354. event : wx.Event, optional
  355. The event that triggered the call (default is None).
  356. """
  357. self._title_change_pending = True
  358. self._title_timer = wx.Timer(self)
  359. self._title_timer.Bind(wx.EVT_TIMER, self._save_new_title)
  360. self._title_timer.StartOnce(2000)
  361. def _save_new_title(self, event=None):
  362. """Saves the team name to the database.
  363. Called two seconds after the last change in self._title_text. If the name
  364. is not valid (i.e. less than two characters), it doesn't apply the
  365. change and sets the font color to red to indicate error.
  366. Parameters
  367. ----------
  368. event : wx.Event, optional
  369. The event that triggered the call (default is None).
  370. """
  371. if (self._title_change_pending):
  372. new_title = self._title_text.GetValue().replace("\n", "").strip()
  373. self._title_change_pending = False
  374. if len(new_title.replace(" ", "")) > 2:
  375. self._title_text.SetStyle(
  376. 0, len(self._title_text.GetValue()),
  377. wx.TextAttr(colText=wx.BLACK)
  378. )
  379. # TODO: Do this with a command
  380. query = "UPDATE teams SET name = ? WHERE id = ?";
  381. cursor = conn.execute(query, (new_title, self._team_selected.id))
  382. conn.commit()
  383. reload_teams()
  384. self._populate_team_list(event=None)
  385. else:
  386. monospaceFont = wx.Font(
  387. pointSize=8, family=wx.FONTFAMILY_TELETYPE,
  388. style=wx.FONTSTYLE_NORMAL, weight=wx.FONTWEIGHT_NORMAL
  389. )
  390. self._title_text.SetStyle(
  391. 0, len(self._title_text.GetValue()), wx.TextAttr(colText=wx.RED)
  392. )
  393. def _change_priority(self, event):
  394. """Prepares for a priority change.
  395. Called everytime the selected team priority is changed. It schedules a
  396. call to self._save_new_priority in two seconds, to give the user time to
  397. enter the full text.
  398. Parameters
  399. ----------
  400. event : wx.Event, optional
  401. The event that triggered the call (default is None).
  402. """
  403. self._priority_change_pending = True
  404. self._priority_time = wx.Timer(self)
  405. self._priority_time.Bind(wx.EVT_TIMER, self._save_new_priority)
  406. self._priority_time.StartOnce(2000)
  407. def _save_new_priority(self, event=None):
  408. """Saves the team priority to the database.
  409. Called two seconds after the last change in self._priority_text. If the
  410. priority is not valid (i.e. not a number, lower than 0 or greater than
  411. 50), it doesn't apply the change and sets the font color to red to
  412. indicate error.
  413. Parameters
  414. ----------
  415. event : wx.Event, optional
  416. The event that triggered the call (default is None).
  417. """
  418. if (self._priority_change_pending):
  419. new_priority = self._priority_text.GetValue().replace("\n", "").strip()
  420. self._priority_change_pending = False
  421. if new_priority.isnumeric() and \
  422. int(new_priority) >= 0 and int(new_priority) <= 50:
  423. self._priority_text.SetStyle(
  424. 0, len(self._priority_text.GetValue()),
  425. wx.TextAttr(colText=wx.BLACK)
  426. )
  427. # TODO Do this with a command, when there is a command to do it.
  428. query = "UPDATE teams SET priority = ? WHERE id = ?";
  429. cursor = conn.execute(query, (new_priority, self._team_selected.id))
  430. conn.commit()
  431. reload_teams()
  432. self._populate_team_list(event=None)
  433. self.GetParent().panel_units.populate_unit_list(event=None)
  434. else:
  435. self._priority_text.SetStyle(
  436. 0, len(self._priority_text.GetValue()),
  437. wx.TextAttr(colText=wx.RED)
  438. )