PanelTeams.py 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564
  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. Parameters
  18. ----------
  19. selectedTeamId : string
  20. Currently selected team ID (default None).
  21. selectedTeamName : string
  22. Currently selected team name (default None).
  23. selectedTeamPriority : int
  24. Currently selected team priority (default None).
  25. titleChangePending : boolean
  26. Indicates if there is any change on the selected team title to save in
  27. the database.
  28. priorityChangePending : boolean
  29. Indicates if there is any change on the selected team priority to save
  30. in the database.
  31. teamList : wx.ListCtrl
  32. Selectable list with all the teams.
  33. detailsSizer : wx.BoxSizer
  34. Holds all the details. Hidden until a team is selected.
  35. titleText : wx.TextCtrl
  36. Text editor to change the team name.
  37. priorityText : wx.TextCtrl
  38. Text editor to change the team priority.
  39. teamUnitList : wx.ListCtrl
  40. Selectable list with all the units in the selected team.
  41. allUnitList : wx.ListCtrl
  42. Selectable list with all the units not in the selected team.
  43. filterNameText : wx.TextCtrl
  44. Text input to filter units names.
  45. filterNameTexts : wx.CheckBox
  46. Checkbox to include or exclude units in storage.
  47. filterNoRunesCheck : wx.CheckBox
  48. Checkbox to include or exclude units without runes.
  49. filterNoTeamsCheck : wx.CheckBox
  50. Checkbox to include or exclude units in no teams.
  51. titleTimer : wx.Timer
  52. Timer to defer database updates on team name changes.
  53. priorityTimer : wx.Timer
  54. Timer to defer database updates on team priority changes.
  55. Methods
  56. -------
  57. """
  58. selectedTeamId = None
  59. selectedTeamName = None
  60. selectetdTeamPriority = None
  61. titleChangePending = False
  62. priorityChangePending = False
  63. teamList = None
  64. detailsSizer = None
  65. titleText = None
  66. priorityText = None
  67. teamUnitList = None
  68. allUnitList = None
  69. filterNameText = None
  70. filterNameTexts = None
  71. filterNoRunesCheck = None
  72. filterNoTeamsCheck = None
  73. titleTimer = None
  74. priorityTimer = None
  75. def __init__(self, parent, id=wx.ID_ANY):
  76. """Initializes the panel.
  77. Sets upt all the widgets.
  78. Parameters
  79. ----------
  80. parent : wx.*
  81. Panel parent.
  82. id : int, optional
  83. ID for the panel (default wx.ID_ANY).
  84. """
  85. # Parent constructor
  86. wx.Panel.__init__(self, parent=parent, id=id)
  87. wx.StaticText(
  88. parent=self, id=wx.ID_ANY,
  89. label="Name Prio.",
  90. pos=(10, 10), size=(240, 20)
  91. )
  92. self.teamList = wx.ListCtrl(
  93. parent=self, id=wx.ID_ANY, pos=(10, 30), size=(240, 460),
  94. style=wx.LC_REPORT|wx.LC_NO_HEADER
  95. )
  96. self.teamList.InsertColumn(0, "Name", width=200)
  97. self.teamList.InsertColumn(1, "Prio.", width=40)
  98. self.populateTeamList(event=None)
  99. self.Bind(wx.EVT_LIST_ITEM_SELECTED, self.teamSelected, self.teamList)
  100. createTeamButton = wx.Button(
  101. parent=self, id=wx.ID_ANY, pos=(50, 500), size=(170, 40),
  102. style=wx.LC_REPORT, label="New team"
  103. )
  104. self.Bind(wx.EVT_BUTTON, self.createTeam, createTeamButton)
  105. # Sizer for all team details. Will be hidden until a team is selected
  106. self.detailsSizer = wx.BoxSizer(wx.VERTICAL)
  107. # Team title and priority editors
  108. self.titleText = wx.TextCtrl(
  109. parent=self, id=wx.ID_ANY,
  110. pos=(270, 30), size=(200, 25), style=wx.TE_RICH|wx.TE_MULTILINE
  111. )
  112. self.detailsSizer.Add(self.titleText)
  113. self.Bind(wx.EVT_TEXT, self.changeTitle, self.titleText);
  114. priortiyLabel = wx.StaticText(
  115. parent=self, id=wx.ID_ANY, label="Priority:",
  116. pos=(270, 80), size=(80, 30)
  117. )
  118. self.detailsSizer.Add(priortiyLabel)
  119. self.priorityText = wx.TextCtrl(
  120. parent=self, id=wx.ID_ANY,
  121. pos=(330, 80), size=(30, 25), style=wx.TE_RICH|wx.TE_MULTILINE)
  122. self.detailsSizer.Add(self.priorityText)
  123. self.Bind(wx.EVT_TEXT, self.changePriority, self.priorityText);
  124. # Team unit list
  125. self.teamUnitList = wx.ListCtrl(
  126. parent=self, id=wx.ID_ANY, pos=(270, 130), size=(140, 360),
  127. style=wx.LC_REPORT|wx.LC_NO_HEADER
  128. )
  129. self.detailsSizer.Add(self.teamUnitList)
  130. self.teamUnitList.InsertColumn(0, "Name", width=140)
  131. self.teamUnitList.InsertColumn(1, "Level", width=140)
  132. # All unit selector
  133. allUnitLabel = wx.StaticText(
  134. parent=self, id=wx.ID_ANY,
  135. label="Name Prio. Sto.",
  136. pos=(500, 10), size=(190, 20)
  137. )
  138. self.detailsSizer.Add(allUnitLabel)
  139. self.allUnitList = wx.ListCtrl(
  140. parent=self, id=wx.ID_ANY, pos=(500, 30), size=(190, 350),
  141. style=wx.LC_REPORT|wx.LC_NO_HEADER
  142. )
  143. self.detailsSizer.Add(self.allUnitList)
  144. self.allUnitList.InsertColumn(0, "Name", width=120)
  145. self.allUnitList.InsertColumn(1, "Prio.", width=40)
  146. self.allUnitList.InsertColumn(2, "Sto.", width=30)
  147. # List filters
  148. filterBox = wx.StaticBox(
  149. parent=self, label="Filters:",id=wx.ID_ANY,
  150. pos=(500, 390), size=(190, 150)
  151. )
  152. self.detailsSizer.Add(filterBox)
  153. wx.StaticText(
  154. parent=filterBox, label="Monster name", pos=(5, 5), size=(180, 20)
  155. )
  156. self.filterNameText = wx.TextCtrl(
  157. parent=filterBox, id=wx.ID_ANY, value="", pos=(5, 25),
  158. size=(177, 20), style=wx.TE_PROCESS_ENTER|wx.TE_PROCESS_TAB
  159. )
  160. self.Bind(wx.EVT_TEXT, self.populateUnitList, self.filterNameText)
  161. self.filterStorageCheck = wx.CheckBox(
  162. parent=filterBox, id=wx.ID_ANY,
  163. label="Monsters in storage", pos=(5, 55), size=(180, 20)
  164. )
  165. self.filterStorageCheck.SetValue(True)
  166. self.Bind(
  167. wx.EVT_CHECKBOX, self.populateUnitList, self.filterStorageCheck
  168. )
  169. self.filterNoRunesCheck = wx.CheckBox(
  170. parent=filterBox, id=wx.ID_ANY,
  171. label="Monsters without runes", pos=(5, 75), size=(180, 20)
  172. )
  173. self.filterNoRunesCheck.SetValue(True)
  174. self.Bind(
  175. wx.EVT_CHECKBOX, self.populateUnitList, self.filterNoRunesCheck
  176. )
  177. self.filterNoTeamsCheck = wx.CheckBox(
  178. parent=filterBox, id=wx.ID_ANY,
  179. label="Monsters not in teams", pos=(5, 95), size=(180, 20)
  180. )
  181. self.filterNoTeamsCheck.SetValue(True)
  182. self.Bind(
  183. wx.EVT_CHECKBOX, self.populateUnitList, self.filterNoTeamsCheck
  184. )
  185. self.populateUnitList(None)
  186. addButton = wx.Button(
  187. parent=self, id=wx.ID_ANY, pos=(420, 300), size=(70, 40),
  188. style=wx.LC_REPORT, label="<<<"
  189. )
  190. self.detailsSizer.Add(addButton)
  191. self.Bind(wx.EVT_BUTTON, self.addToTeam, addButton)
  192. removeButton = wx.Button(
  193. parent=self, id=wx.ID_ANY, pos=(420, 350), size=(70, 40),
  194. style=wx.LC_REPORT, label=">>>"
  195. )
  196. self.detailsSizer.Add(removeButton)
  197. self.Bind(wx.EVT_BUTTON, self.removeFromTeam, removeButton)
  198. deleteButton = wx.Button(
  199. parent=self, id=wx.ID_ANY, pos=(270, 500), size=(140, 40),
  200. style=wx.LC_REPORT, label="Delete team"
  201. )
  202. self.detailsSizer.Add(deleteButton)
  203. self.Bind(wx.EVT_BUTTON, self.deleteTeam, deleteButton)
  204. # By default, hide all details
  205. self.detailsSizer.ShowItems(False)
  206. def populateTeamList(self, event=None):
  207. """Populates the team list.
  208. Parameters
  209. ----------
  210. event : wx.Event, optional
  211. The event that triggered the call (default is None).
  212. """
  213. self.teamList.DeleteAllItems()
  214. query = """
  215. SELECT
  216. id,
  217. name,
  218. priority
  219. FROM teams
  220. ORDER BY priority DESC
  221. """
  222. cursor = conn.execute(query)
  223. i = 0
  224. for row in cursor:
  225. self.teamList.InsertItem(i, row[1])
  226. self.teamList.SetItem(i, 1, str(row[2]))
  227. self.teamList.SetItemData(i, int(row[0]))
  228. i = i + 1
  229. def populateUnitList(self, event=None):
  230. """Populates the list of all units.
  231. Uses the filters.
  232. Parameters
  233. ----------
  234. event : wx.Event, optional
  235. The event that triggered the call (default is None).
  236. """
  237. # Get units from db
  238. name = self.filterNameText.GetValue()
  239. query = """
  240. SELECT
  241. id,
  242. name,
  243. (
  244. SELECT cast(total(teams.priority) as int)
  245. FROM teams, units_teams
  246. WHERE teams.id = units_teams.team AND units_teams.unit = units.id
  247. ) as priority,
  248. storage
  249. FROM units
  250. WHERE
  251. name LIKE '%""" + name + """%'
  252. """
  253. if self.filterStorageCheck.GetValue() == False:
  254. query += " AND storage = 0 "
  255. if self.filterNoRunesCheck.GetValue() == False:
  256. query += " AND id IN (SELECT DISTINCT unit FROM runes) "
  257. if self.filterNoTeamsCheck.GetValue() == False:
  258. query += " AND id IN (SELECT DISTINCT unit FROM units_teams) "
  259. query += " ORDER BY priority DESC; ";
  260. cursor = conn.execute(query)
  261. i = 0
  262. self.allUnitList.DeleteAllItems()
  263. for row in cursor:
  264. self.allUnitList.InsertItem(i, row[1])
  265. self.allUnitList.SetItem(i, 1, str(row[2]))
  266. self.allUnitList.SetItem(i, 2, "")
  267. self.allUnitList.SetItemData(i, int(row[0]))
  268. if (row[3] == 1):
  269. self.allUnitList.SetItem(i, 2, "X")
  270. else:
  271. self.allUnitList.SetItem(i, 2, " ")
  272. i = i + 1
  273. def populateTeamUnitList(self, event=None):
  274. """Populates the list of units in the selected team.
  275. Uses the filters.
  276. Parameters
  277. ----------
  278. event : wx.Event, optional
  279. The event that triggered the call (default is None).
  280. """
  281. # Get units from db
  282. name = self.filterNameText.GetValue()
  283. query = """
  284. SELECT
  285. id,
  286. name,
  287. level
  288. FROM units
  289. WHERE id IN (SELECT DISTINCT unit FROM units_teams WHERE
  290. team = '""" + self.selectedTeamId + """')
  291. """
  292. cursor = conn.execute(query)
  293. i = 0
  294. self.teamUnitList.DeleteAllItems()
  295. for row in cursor:
  296. self.teamUnitList.InsertItem(i, row[1])
  297. self.teamUnitList.SetItem(i, 0, str(row[1]))
  298. self.teamUnitList.SetItem(i, 1, "Lv." + str(row[2]))
  299. self.teamUnitList.SetItemData(i, int(row[0]))
  300. i = i + 1
  301. def createTeam(self, event=None):
  302. """Opens a dialog to create a new team.
  303. Parameters
  304. ----------
  305. event : wx.Event, optional
  306. The event that triggered the call (default is None).
  307. """
  308. with DialogNewTeam(parent=self) as dlg:
  309. if dlg.ShowModal() == wx.ID_OK:
  310. self.populateTeamList()
  311. def deleteTeam(self, event=None):
  312. """Deletes a team.
  313. Shows a confirmation dialog first.
  314. Parameters
  315. ----------
  316. event : wx.Event, optional
  317. The event that triggered the call (default is None).
  318. """
  319. message = "Are you sure you want to delete the team " + \
  320. self.selectedTeamName + "?\n\nThis can't be undone."
  321. with DialogConfirm(parent=self, message=message) as dlg:
  322. if dlg.ShowModal() == wx.ID_OK:
  323. cursor = conn.execute(
  324. "DELETE FROM units_teams WHERE team = ?",
  325. (self.selectedTeamId,))
  326. cursor = conn.execute(
  327. "DELETE FROM teams WHERE id = ?", (self.selectedTeamId,)
  328. )
  329. conn.commit()
  330. # Clear the selection and hide the details
  331. self.selectedIndex = None
  332. self.detailsSizer.ShowItems(False)
  333. self.populateTeamList(event=None)
  334. self.GetParent().frameUnits.populateUnitList(event=None)
  335. def addToTeam(self, event=None):
  336. """Adds unit to the currently selected team.
  337. Adds all the units selected in self.allUnitList. Refreshes both unit
  338. lists and also the unit list in the units panel.
  339. Parameters
  340. ----------
  341. event : wx.Event, optional
  342. The event that triggered the call (default is None).
  343. """
  344. selectedIndex = self.allUnitList.GetFirstSelected()
  345. while (selectedIndex != -1):
  346. unitId = self.allUnitList.GetItemData(selectedIndex)
  347. query = "INSERT INTO units_teams (team, unit) VALUES (?, ?)";
  348. cursor = conn.execute(query, (self.selectedTeamId, unitId))
  349. selectedIndex = self.allUnitList.GetNextSelected(selectedIndex)
  350. conn.commit()
  351. self.populateTeamUnitList(event=None)
  352. self.populateUnitList(event=None)
  353. self.GetParent().frameUnits.populateUnitList(event=None)
  354. def removeFromTeam(self, event=None):
  355. """Removes units the currently selected team.
  356. Removes all the units selected in self.teamUnitList. Refreshes both unit
  357. lists and also the unit list in the units panel.
  358. Parameters
  359. ----------
  360. event : wx.Event, optional
  361. The event that triggered the call (default is None).
  362. """
  363. selectedIndex = self.teamUnitList.GetFirstSelected()
  364. while (selectedIndex != -1):
  365. unitId = self.teamUnitList.GetItemData(selectedIndex)
  366. query = "DELETE FROM units_teams WHERE team = ? AND unit = ?";
  367. cursor = conn.execute(query, (self.selectedTeamId, unitId))
  368. selectedIndex = self.teamUnitList.GetNextSelected(selectedIndex)
  369. conn.commit()
  370. self.populateTeamUnitList(event=None)
  371. self.populateUnitList(event=None)
  372. self.GetParent().frameUnits.populateUnitList(event=None)
  373. def teamSelected(self, event=None):
  374. """Shows the team details.
  375. Called when a team is selected.
  376. Parameters
  377. ----------
  378. event : wx.Event, optional
  379. The event that triggered the call (default is None).
  380. """
  381. self.selectedTeamId = \
  382. str(self.teamList.GetItemData(self.teamList.GetFirstSelected()))
  383. self.selectedTeamName = \
  384. self.teamList.GetItem(self.teamList.GetFirstSelected(), 0).GetText()
  385. self.selectedTeamPriority = \
  386. self.teamList.GetItem(self.teamList.GetFirstSelected(), 1).GetText()
  387. self.populateTeamUnitList(event=None)
  388. # Unbind for the automatic change
  389. self.Unbind(wx.EVT_TEXT, self.titleText)
  390. self.titleText.SetValue(self.selectedTeamName)
  391. # Rebind
  392. self.Bind(wx.EVT_TEXT, self.changeTitle, self.titleText);
  393. # Unbind for the automatic change
  394. self.Unbind(wx.EVT_TEXT, self.priorityText);
  395. self.priorityText.SetValue(self.selectedTeamPriority)
  396. # Rebind
  397. self.Bind(wx.EVT_TEXT, self.changePriority, self.priorityText);
  398. # Show details
  399. self.detailsSizer.ShowItems(True)
  400. def changeTitle(self, event):
  401. """Prepares for a title change.
  402. Called everytime the selected team name is changed. It schedules a call
  403. to self.saveNewTitle in two seconds, to give the user time to enter the
  404. full title.
  405. Parameters
  406. ----------
  407. event : wx.Event, optional
  408. The event that triggered the call (default is None).
  409. """
  410. newTitle = self.titleText.GetValue()
  411. self.titleChangePending = True
  412. self.titleTimer = wx.Timer(self)
  413. self.Bind(wx.EVT_TIMER, self.saveNewTitle, self.titleTimer)
  414. self.titleTimer.StartOnce(2000)
  415. def saveNewTitle(self, event=None):
  416. """Saves the team name to the database.
  417. Called two seconds after the last change in self.titleText. If the name
  418. is not valid (i.e. less than two characters), it doesn't apply the
  419. change and sets the font color to red to indicate error.
  420. Parameters
  421. ----------
  422. event : wx.Event, optional
  423. The event that triggered the call (default is None).
  424. """
  425. if (self.titleChangePending):
  426. newTitle = self.titleText.GetValue().replace("\n", "").strip()
  427. self.titleChangePending = False
  428. if len(newTitle.replace(" ", "")) > 2:
  429. self.titleText.SetStyle(
  430. 0, len(self.titleText.GetValue()),
  431. wx.TextAttr(colText=wx.BLACK)
  432. )
  433. query = "UPDATE teams SET name = ? WHERE id = ?";
  434. cursor = conn.execute(query, (newTitle, self.selectedTeamId))
  435. conn.commit()
  436. self.populateTeamList(event=None)
  437. else:
  438. monospaceFont = wx.Font(
  439. pointSize=8, family=wx.FONTFAMILY_TELETYPE,
  440. style=wx.FONTSTYLE_NORMAL, weight=wx.FONTWEIGHT_NORMAL
  441. )
  442. self.titleText.SetStyle(
  443. 0, len(self.titleText.GetValue()), wx.TextAttr(colText=wx.RED)
  444. )
  445. def changePriority(self, event):
  446. """Prepares for a priority change.
  447. Called everytime the selected team priority is changed. It schedules a
  448. call to self.saveNewPriority in two seconds, to give the user time to
  449. enter the full text.
  450. Parameters
  451. ----------
  452. event : wx.Event, optional
  453. The event that triggered the call (default is None).
  454. """
  455. newPriority = self.priorityText.GetValue()
  456. self.priorityChangePending = True
  457. self.priorityTimer = wx.Timer(self)
  458. self.Bind(wx.EVT_TIMER, self.saveNewPriority, self.priorityTimer)
  459. self.priorityTimer.StartOnce(2000)
  460. def saveNewPriority(self, event=None):
  461. """Saves the team priority to the database.
  462. Called two seconds after the last change in self.priorityText. If the
  463. priority is not valid (i.e. not a number, lower than 0 or greater than
  464. 50), it doesn't apply the change and sets the font color to red to
  465. indicate error.
  466. Parameters
  467. ----------
  468. event : wx.Event, optional
  469. The event that triggered the call (default is None).
  470. """
  471. if (self.priorityChangePending):
  472. newPriority = self.priorityText.GetValue().replace("\n", "").strip()
  473. self.priorityChangePending = False
  474. if newPriority.isnumeric() and \
  475. int(newPriority) >= 0 and int(newPriority) <= 50:
  476. self.priorityText.SetStyle(
  477. 0, len(self.priorityText.GetValue()),
  478. wx.TextAttr(colText=wx.BLACK)
  479. )
  480. query = "UPDATE teams SET priority = ? WHERE id = ?";
  481. cursor = conn.execute(query, (newPriority, self.selectedTeamId))
  482. conn.commit()
  483. self.populateTeamList(event=None)
  484. self.GetParent().frameUnits.populateUnitList(event=None)
  485. else:
  486. self.priorityText.SetStyle(
  487. 0, len(self.priorityText.GetValue()),
  488. wx.TextAttr(colText=wx.RED)
  489. )