Coverage for fpdb.pyw: 0%

923 statements  

« prev     ^ index     » next       coverage.py v7.6.1, created at 2024-09-28 16:41 +0000

1#!/usr/bin/env python 

2# -*- coding: utf-8 -*- 

3 

4# Copyright 2008-2013 Steffen Schaumburg 

5# This program is free software: you can redistribute it and/or modify 

6# it under the terms of the GNU Affero General Public License as published by 

7# the Free Software Foundation, version 3 of the License. 

8# 

9# This program is distributed in the hope that it will be useful, 

10# but WITHOUT ANY WARRANTY; without even the implied warranty of 

11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 

12# GNU General Public License for more details. 

13# 

14# You should have received a copy of the GNU Affero General Public License 

15# along with this program. If not, see <http://www.gnu.org/licenses/>. 

16# In the "official" distribution you can find the license in agpl-3.0.txt. 

17 

18 

19import os 

20import sys 

21import queue 

22# import qdarkstyle 

23if os.name == 'nt': 

24 pass 

25 

26import codecs 

27import Options 

28from functools import partial 

29 

30cl_options = '.'.join(sys.argv[1:]) 

31(options, argv) = Options.fpdb_options() 

32from L10n import set_locale_translation 

33import logging 

34 

35from PyQt5.QtCore import (QCoreApplication, QDate, Qt, QPoint) 

36from PyQt5.QtGui import (QIcon, QPalette) 

37from PyQt5.QtWidgets import QWidget, QLabel, QPushButton, QHBoxLayout, QSizePolicy 

38from PyQt5.QtWidgets import (QAction, QApplication, QCalendarWidget, 

39 QCheckBox, QDateEdit, QDialog, 

40 QDialogButtonBox, QFileDialog, 

41 QGridLayout, QLineEdit, QMainWindow, 

42 QMessageBox, QScrollArea, 

43 QTabWidget, QVBoxLayout, QComboBox) 

44 

45import interlocks 

46from Exceptions import * 

47 

48# these imports not required in this module, imported here to report version in About dialog 

49import numpy 

50 

51numpy_version = numpy.__version__ 

52import sqlite3 

53 

54sqlite3_version = sqlite3.version 

55sqlite_version = sqlite3.sqlite_version 

56 

57import DetectInstalledSites 

58import GuiPrefs 

59import GuiLogView 

60# import GuiDatabase 

61import GuiBulkImport 

62#import GuiTourneyImport 

63 

64import GuiRingPlayerStats 

65import GuiTourneyPlayerStats 

66import GuiPositionalStats 

67import GuiAutoImport 

68import GuiGraphViewer 

69import GuiTourneyGraphViewer 

70import GuiSessionViewer 

71import GuiHandViewer 

72import GuiTourHandViewer 

73#import GuiOddsCalc 

74#import GuiStove 

75 

76import SQL 

77import Database 

78import Configuration 

79import Card 

80import Exceptions 

81#import api, app 

82import cProfile 

83import pstats 

84import io 

85 

86 

87PROFILE_OUTPUT_DIR = os.path.join(os.path.expanduser("~"), "fpdb_profiles") 

88os.makedirs(PROFILE_OUTPUT_DIR, exist_ok=True) 

89 

90profiler = cProfile.Profile() 

91profiler.enable() 

92 

93 

94 

95Configuration.set_logfile("fpdb-log.txt") 

96 

97log = logging.getLogger("fpdb") 

98 

99try: 

100 assert not hasattr(sys, 'frozen') # We're surely not in a git repo if this fails 

101 import subprocess 

102 

103 VERSION = subprocess.Popen(["git", "describe", "--tags", "--dirty"], stdout=subprocess.PIPE).communicate()[0] 

104 VERSION = VERSION[:-1] 

105except Exception: 

106 VERSION = "3.0.0alpha" 

107 

108 

109class fpdb(QMainWindow): 

110 def launch_ppt(self): 

111 path = os.getcwd() 

112 if os.name == 'nt': 

113 pathcomp = f"{path}\pyfpdb\ppt\p2.jar" 

114 else: 

115 pathcomp = f"{path}/ppt/p2.jar" 

116 subprocess.call(['java', '-jar', pathcomp]) 

117 

118 def add_and_display_tab(self, new_page, new_tab_name): 

119 """adds a tab, namely creates the button and displays it and appends all the relevant arrays""" 

120 for name in self.nb_tab_names: # todo: check this is valid 

121 if name == new_tab_name: 

122 self.display_tab(new_tab_name) 

123 return # if tab already exists, just go to it 

124 

125 used_before = False 

126 for i, name in enumerate(self.nb_tab_names): 

127 if name == new_tab_name: 

128 used_before = True 

129 page = self.pages[i] 

130 break 

131 

132 if not used_before: 

133 page = new_page 

134 self.pages.append(new_page) 

135 self.nb_tab_names.append(new_tab_name) 

136 

137 index = self.nb.addTab(page, new_tab_name) 

138 self.nb.setCurrentIndex(index) 

139 

140 def display_tab(self, new_tab_name): 

141 """displays the indicated tab""" 

142 tab_no = -1 

143 for i, name in enumerate(self.nb_tab_names): 

144 if new_tab_name == name: 

145 tab_no = i 

146 break 

147 

148 if tab_no < 0 or tab_no >= self.nb.count(): 

149 raise FpdbError(f"invalid tab_no {str(tab_no)}") 

150 else: 

151 self.nb.setCurrentIndex(tab_no) 

152 

153 def dia_about(self, widget, data=None): 

154 QMessageBox.about( 

155 self, 

156 f"FPDB{str(VERSION)}", 

157 "Copyright 2008-2023. See contributors.txt for details" 

158 + "You are free to change, and distribute original or changed versions " 

159 "of fpdb within the rules set out by the license" 

160 + "https://github.com/jejellyroll-fr/fpdb-3" 

161 + "\n" 

162 + "Your config file is: " 

163 + self.config.file, 

164 ) 

165 return 

166 

167 def dia_advanced_preferences(self, widget, data=None): 

168 # force reload of prefs from xml file - needed because HUD could 

169 # have changed file contents 

170 self.load_profile() 

171 if GuiPrefs.GuiPrefs(self.config, self).exec_(): 

172 # save updated config 

173 self.config.save() 

174 self.reload_config() 

175 

176 def dia_database_stats(self, widget, data=None): 

177 self.warning_box( 

178 string=f"Number of Hands: {self.db.getHandCount()}\nNumber of Tourneys: {self.db.getTourneyCount()}\nNumber of TourneyTypes: {self.db.getTourneyTypeCount()}", 

179 diatitle="Database Statistics") 

180 

181 # end def dia_database_stats 

182 

183 @staticmethod 

184 def get_text(widget: QWidget): 

185 """Return text of widget, depending on widget type""" 

186 return widget.currentText() if isinstance(widget, QComboBox) else widget.text() 

187 

188 

189 def dia_hud_preferences(self, widget, data=None): 

190 dia = QDialog(self) 

191 dia.setWindowTitle("Modifying Huds") 

192 dia.resize(1200, 600) 

193 label = QLabel("Please edit your huds.") 

194 dia.setLayout(QVBoxLayout()) 

195 dia.layout().addWidget(label) 

196 label2 = QLabel("Please select the game category for which you want to configure HUD stats:") 

197 dia.layout().addWidget(label2) 

198 self.comboGame = QComboBox() 

199 

200 huds_names = self.config.get_stat_sets() 

201 for hud_name in huds_names: 

202 self.comboGame.addItem(hud_name) 

203 

204 dia.layout().addWidget(self.comboGame) 

205 self.comboGame.setCurrentIndex(1) 

206 selected_hud_name = self.comboGame.currentText() 

207 

208 self.load_profile() # => self.[config, settings] 

209 

210 # HUD column will contain a button that shows favseat and HUD locations. 

211 # TODO: Make it possible to load screenshot to arrange HUD windowlets. 

212 

213 self.table = QGridLayout() 

214 self.table.setSpacing(0) 

215 

216 scrolling_frame = QScrollArea(dia) 

217 dia.layout().addWidget(scrolling_frame) 

218 scrolling_frame.setLayout(self.table) 

219 

220 nb_items = len(self.config.stat_sets[selected_hud_name].stats) 

221 

222 btns = QDialogButtonBox(QDialogButtonBox.Save | QDialogButtonBox.Cancel, dia) 

223 btns.accepted.connect(dia.accept) 

224 btns.rejected.connect(dia.reject) 

225 dia.layout().addWidget(btns) 

226 self.comboGame.currentIndexChanged.connect(self.index_changed) 

227 

228 # Launch Dialog 

229 response = dia.exec_() 

230 

231 # Treat dialog closed event 

232 if self.comboGame.currentIndexChanged and response: 

233 selected_hud_name = self.comboGame.currentText() 

234 # User clicked on "Save" 

235 for y in range(nb_items): 

236 self.config.edit_hud( 

237 selected_hud_name, 

238 self.get_text(self.stat_position_list[y]), 

239 self.get_text(self.stat_name_list[y]), 

240 self.get_text(self.click_list[y]), 

241 self.get_text(self.hudcolor_list[y]), 

242 self.get_text(self.hudprefix_list[y]), 

243 self.get_text(self.hudsuffix_list[y]), 

244 self.get_text(self.popup_list[y]), 

245 self.get_text(self.stat_hicolor_list[y]), 

246 self.get_text(self.stat_hith_list[y]), 

247 self.get_text(self.stat_locolor_list[y]), 

248 self.get_text(self.stat_loth_list[y]), 

249 self.get_text(self.tip_list[y]), 

250 ) 

251 

252 self.config.save() 

253 self.reload_config() 

254 

255 

256 def index_changed(self, index): 

257 # Called when user changes currently selected HUD 

258 log.info("start index_changed") 

259 log.debug(f"index = {index}") 

260 log.debug(f"self.config = {self.config}") 

261 log.debug(f"self.config.stat_sets = {self.config.stat_sets}") 

262 selected_hud_name = self.comboGame.currentText() 

263 log.debug(f"selected_hud_name = {selected_hud_name}") 

264 for i in reversed(range(self.table.count())): 

265 self.table.itemAt(i).widget().deleteLater() 

266 

267 self.table.setSpacing(0) 

268 

269 column_headers = ["coordonate", "stats name", "click", "hudcolor", "hudprefix", "hudsuffix", 

270 "popup", "stat_hicolor", "stat_hith", "stat_locolor", "stat_loth", 

271 "tip"] # todo ("HUD") 

272 

273 for header_number in range(0, len(column_headers)): 

274 label = QLabel(column_headers[header_number]) 

275 label.setAlignment(Qt.AlignCenter) 

276 self.table.addWidget(label, 0, header_number) 

277 

278 # Init lists that will contains QWidgets for each column in table ("stat_position_list" will contain the positions (ex: ["(0,1)", ...])) 

279 self.stat_position_list, self.stat_name_list, self.click_list, self.hudcolor_list, self.hudprefix_list, \ 

280 self.hudsuffix_list, self.popup_list, self.stat_hicolor_list, self.stat_hith_list, self.stat_locolor_list, \ 

281 self.stat_loth_list, self.tip_list = [], [], [], [], [], [], [], [], [], [], [], [] 

282 

283 self.load_profile() 

284 hud_stats = self.config.stat_sets[selected_hud_name] # Configuration.Stat_sets object 

285 y_pos = 1 

286 for position in hud_stats.stats.keys(): 

287 # Column 1: stat position 

288 stat2 = QLabel() 

289 stat2.setText(str(position)) 

290 self.table.addWidget(stat2, y_pos, 0) 

291 self.stat_position_list.append(stat2) 

292 

293 # Column 2: select stat name (between available stats) 

294 # TODO: don't load all stats on each loop ! 

295 if os.name == 'nt': 

296 icoPath = os.path.dirname(__file__) 

297 icoPath = f"{icoPath}\\" 

298 else: 

299 icoPath = "icons/" 

300 stat3 = QComboBox() 

301 stats_cash = self.config.get_gui_cash_stat_params() # Available stats for cash game 

302 for x in range(0, len(stats_cash)): 

303 # print(stats_cash[x][0]) 

304 stat3.addItem(QIcon(f"{icoPath}Letter-C-icon.png"), stats_cash[x][0]) 

305 stats_tour = self.config.get_gui_tour_stat_params() # Available stats for tournament 

306 for x in range(0, len(stats_tour)): 

307 # print(stats_tour[x][0]) 

308 stat3.addItem(QIcon(f"{icoPath}Letter-T-icon.png"), stats_tour[x][0]) 

309 stat3.setCurrentText(str(hud_stats.stats[position].stat_name)) 

310 self.table.addWidget(stat3, y_pos, 1) 

311 self.stat_name_list.append(stat3) 

312 

313 # Column 3: "click" 

314 stat4 = QLineEdit() 

315 stat4.setText(str(hud_stats.stats[position].click)) 

316 self.table.addWidget(stat4, y_pos, 2) 

317 self.click_list.append(stat4) 

318 

319 # Column 4: "hudcolor" 

320 stat5 = QLineEdit() 

321 stat5.setText(str(hud_stats.stats[position].hudcolor)) 

322 self.table.addWidget(stat5, y_pos, 3) 

323 self.hudcolor_list.append(stat5) 

324 

325 # Column 5: "hudprefix" 

326 stat6 = QLineEdit() 

327 stat6.setText(str(hud_stats.stats[position].hudprefix)) 

328 self.table.addWidget(stat6, y_pos, 4) 

329 self.hudprefix_list.append(stat6) 

330 

331 # Column 6: "hudsuffix" 

332 stat7 = QLineEdit() 

333 stat7.setText(str(hud_stats.stats[position].hudsuffix)) 

334 self.table.addWidget(stat7, y_pos, 5) 

335 self.hudsuffix_list.append(stat7) 

336 

337 # Column 7: "popup" 

338 stat8 = QComboBox() 

339 for popup in self.config.popup_windows.keys(): 

340 stat8.addItem(popup) 

341 stat8.setCurrentText(str(hud_stats.stats[position].popup)) 

342 self.table.addWidget(stat8, y_pos, 6) 

343 self.popup_list.append(stat8) 

344 

345 # Column 8: "stat_hicolor" 

346 stat9 = QLineEdit() 

347 stat9.setText(str(hud_stats.stats[position].stat_hicolor)) 

348 self.table.addWidget(stat9, y_pos, 7) 

349 self.stat_hicolor_list.append(stat9) 

350 

351 # Column 9: "stat_hith" 

352 stat10 = QLineEdit() 

353 stat10.setText(str(hud_stats.stats[position].stat_hith)) 

354 self.table.addWidget(stat10, y_pos, 8) 

355 self.stat_hith_list.append(stat10) 

356 

357 # Column 10: "stat_locolor" 

358 stat11 = QLineEdit() 

359 stat11.setText(str(hud_stats.stats[position].stat_locolor)) 

360 self.table.addWidget(stat11, y_pos, 9) 

361 self.stat_locolor_list.append(stat11) 

362 

363 # Column 11: "stat_loth" 

364 stat12 = QLineEdit() 

365 stat12.setText(str(hud_stats.stats[position].stat_loth)) 

366 self.table.addWidget(stat12, y_pos, 10) 

367 self.stat_loth_list.append(stat12) 

368 

369 # Column 12: "tip" 

370 stat13 = QLineEdit() 

371 stat13.setText(str(hud_stats.stats[position].tip)) 

372 self.table.addWidget(stat13, y_pos, 11) 

373 self.tip_list.append(stat13) 

374 # if available_site_names[site_number] in detector.supportedSites: 

375 # pass 

376 

377 y_pos += 1 

378 

379 def dia_import_filters(self, checkState): 

380 dia = QDialog() 

381 dia.setWindowTitle("Skip these games when importing") 

382 dia.setLayout(QVBoxLayout()) 

383 checkboxes = {} 

384 filters = self.config.get_import_parameters()['importFilters'] 

385 for game in Card.games: 

386 checkboxes[game] = QCheckBox(game) 

387 dia.layout().addWidget(checkboxes[game]) 

388 if game in filters: 

389 checkboxes[game].setChecked(True) 

390 btns = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) 

391 dia.layout().addWidget(btns) 

392 btns.accepted.connect(dia.accept) 

393 btns.rejected.connect(dia.reject) 

394 if dia.exec_(): 

395 filterGames = [] 

396 for game, cb in list(checkboxes.items()): 

397 if cb.isChecked(): 

398 filterGames.append(game) 

399 self.config.editImportFilters(",".join(filterGames)) 

400 self.config.save() 

401 

402 def dia_dump_db(self, widget, data=None): 

403 filename = "database-dump.sql" 

404 result = self.db.dumpDatabase() 

405 

406 with open(filename, 'w') as dumpFile: 

407 dumpFile.write(result) 

408 

409 # end def dia_database_stats 

410 

411 def dia_recreate_tables(self, widget, data=None): 

412 """Dialogue that asks user to confirm that he wants to delete and recreate the tables""" 

413 if self.obtain_global_lock("fpdb.dia_recreate_tables"): # returns true if successful 

414 dia_confirm = QMessageBox(QMessageBox.Warning, "Wipe DB", "Confirm deleting and recreating tables", 

415 QMessageBox.Yes | QMessageBox.No, self) 

416 diastring = f"Please confirm that you want to (re-)create the tables. If there already are tables in" \ 

417 f" the database {self.db.database} on {self.db.host}" \ 

418 f" they will be deleted and you will have to re-import your histories.\nThis may take a while." 

419 

420 dia_confirm.setInformativeText(diastring) # todo: make above string with bold for db, host and deleted 

421 response = dia_confirm.exec_() 

422 

423 if response == QMessageBox.Yes: 

424 self.db.recreate_tables() 

425 # find any guibulkimport/guiautoimport windows and clear cache: 

426 for t in self.threads: 

427 if isinstance(t, GuiBulkImport.GuiBulkImport) or isinstance(t, GuiAutoImport.GuiAutoImport): 

428 t.importer.database.resetCache() 

429 self.release_global_lock() 

430 else: 

431 self.release_global_lock() 

432 log.info('User cancelled recreating tables') 

433 else: 

434 self.warning_box("Cannot open Database Maintenance window because other" 

435 " windows have been opened. Re-start fpdb to use this option.") 

436 

437 def dia_recreate_hudcache(self, widget, data=None): 

438 if self.obtain_global_lock("dia_recreate_hudcache"): 

439 self.dia_confirm = QDialog() 

440 self.dia_confirm.setWindowTitle("Confirm recreating HUD cache") 

441 self.dia_confirm.setLayout(QVBoxLayout()) 

442 self.dia_confirm.layout().addWidget(QLabel("Please confirm that you want to re-create the HUD cache.")) 

443 

444 hb1 = QHBoxLayout() 

445 self.h_start_date = QDateEdit(QDate.fromString(self.db.get_hero_hudcache_start(), "yyyy-MM-dd")) 

446 lbl = QLabel(" Hero's cache starts: ") 

447 btn = QPushButton("Cal") 

448 btn.clicked.connect(partial(self.__calendar_dialog, entry=self.h_start_date)) 

449 

450 hb1.addWidget(lbl) 

451 hb1.addStretch() 

452 hb1.addWidget(self.h_start_date) 

453 hb1.addWidget(btn) 

454 self.dia_confirm.layout().addLayout(hb1) 

455 

456 hb2 = QHBoxLayout() 

457 self.start_date = QDateEdit(QDate.fromString(self.db.get_hero_hudcache_start(), "yyyy-MM-dd")) 

458 lbl = QLabel(" Villains' cache starts: ") 

459 btn = QPushButton("Cal") 

460 btn.clicked.connect(partial(self.__calendar_dialog, entry=self.start_date)) 

461 

462 hb2.addWidget(lbl) 

463 hb2.addStretch() 

464 hb2.addWidget(self.start_date) 

465 hb2.addWidget(btn) 

466 self.dia_confirm.layout().addLayout(hb2) 

467 

468 btns = QDialogButtonBox(QDialogButtonBox.Yes | QDialogButtonBox.No) 

469 self.dia_confirm.layout().addWidget(btns) 

470 btns.accepted.connect(self.dia_confirm.accept) 

471 btns.rejected.connect(self.dia_confirm.reject) 

472 

473 response = self.dia_confirm.exec_() 

474 if response: 

475 log.info("Rebuilding HUD Cache ...") 

476 

477 self.db.rebuild_cache(self.h_start_date.date().toString("yyyy-MM-dd"), 

478 self.start_date.date().toString("yyyy-MM-dd")) 

479 else: 

480 log.info('User cancelled rebuilding hud cache') 

481 

482 self.release_global_lock() 

483 else: 

484 self.warning_box("Cannot open Database Maintenance window because other windows have been opened. " 

485 "Re-start fpdb to use this option.") 

486 

487 def dia_rebuild_indexes(self, widget, data=None): 

488 if self.obtain_global_lock("dia_rebuild_indexes"): 

489 self.dia_confirm = QMessageBox(QMessageBox.Warning, 

490 "Rebuild DB", 

491 "Confirm rebuilding database indexes", 

492 QMessageBox.Yes | QMessageBox.No, 

493 self) 

494 diastring = "Please confirm that you want to rebuild the database indexes." 

495 self.dia_confirm.setInformativeText(diastring) 

496 

497 response = self.dia_confirm.exec_() 

498 if response == QMessageBox.Yes: 

499 log.info(" Rebuilding Indexes ... ") 

500 self.db.rebuild_indexes() 

501 

502 log.info(" Cleaning Database ... ") 

503 self.db.vacuumDB() 

504 

505 log.info(" Analyzing Database ... ") 

506 self.db.analyzeDB() 

507 else: 

508 log.info('User cancelled rebuilding db indexes') 

509 

510 self.release_global_lock() 

511 else: 

512 self.warning_box("Cannot open Database Maintenance window because" 

513 " other windows have been opened. Re-start fpdb to use this option.") 

514 

515 def dia_logs(self, widget, data=None): 

516 """opens the log viewer window""" 

517 # remove members from self.threads if close messages received 

518 self.process_close_messages() 

519 

520 viewer = None 

521 for i, t in enumerate(self.threads): 

522 if str(t.__class__) == 'GuiLogView.GuiLogView': 

523 viewer = t 

524 break 

525 

526 if viewer is None: 

527 # print "creating new log viewer" 

528 new_thread = GuiLogView.GuiLogView(self.config, self.window, self.closeq) 

529 self.threads.append(new_thread) 

530 else: 

531 # print "showing existing log viewer" 

532 viewer.get_dialog().present() 

533 

534 # if lock_set: 

535 # self.release_global_lock() 

536 

537 def dia_site_preferences_seat(self, widget, data=None): 

538 dia = QDialog(self) 

539 dia.setWindowTitle("Seat Preferences") 

540 dia.resize(1200, 600) 

541 label = QLabel("Please select your prefered seat.") 

542 dia.setLayout(QVBoxLayout()) 

543 dia.layout().addWidget(label) 

544 

545 self.load_profile() 

546 site_names = self.config.site_ids 

547 available_site_names = [] 

548 for site_name in site_names: 

549 try: 

550 tmp = self.config.supported_sites[site_name].enabled 

551 available_site_names.append(site_name) 

552 except KeyError: 

553 pass 

554 

555 column_headers = ["Site", "2 players:\nbetween 0 & 2", "3 players:\nbetween 0 & 3 ", 

556 "4 players:\nbetween 0 & 4", "5 players:\nbetween 0 & 5", "6 players:\nbetween 0 & 6", 

557 "7 players:\nbetween 0 & 7", "8 players:\nbetween 0 & 8", "9 players:\nbetween 0 & 9", 

558 "10 players:\nbetween 0 & 10"] # todo ("HUD") 

559 # HUD column will contain a button that shows favseat and HUD locations. 

560 # Make it possible to load screenshot to arrange HUD windowlets. 

561 

562 table = QGridLayout() 

563 table.setSpacing(0) 

564 

565 scrolling_frame = QScrollArea(dia) 

566 dia.layout().addWidget(scrolling_frame) 

567 scrolling_frame.setLayout(table) 

568 

569 for header_number in range(0, len(column_headers)): 

570 label = QLabel(column_headers[header_number]) 

571 label.setAlignment(Qt.AlignCenter) 

572 table.addWidget(label, 0, header_number) 

573 

574 history_paths = [] 

575 check_buttons = [] 

576 screen_names = [] 

577 seat2_dict, seat3_dict, seat4_dict, seat5_dict, seat6_dict, seat7_dict, seat8_dict, \ 

578 seat9_dict, seat10_dict = [], [], [], [], [], [], [], [], [] 

579 summary_paths = [] 

580 detector = DetectInstalledSites.DetectInstalledSites() 

581 

582 y_pos = 1 

583 for site_number in range(0, len(available_site_names)): 

584 check_button = QCheckBox(available_site_names[site_number]) 

585 check_button.setChecked(self.config.supported_sites[available_site_names[site_number]].enabled) 

586 table.addWidget(check_button, y_pos, 0) 

587 check_buttons.append(check_button) 

588 hud_seat = self.config.supported_sites[available_site_names[site_number]].fav_seat[2] 

589 

590 # print('hud seat ps:', type(hud_seat), hud_seat) 

591 seat2 = QLineEdit() 

592 

593 seat2.setText(str(self.config.supported_sites[available_site_names[site_number]].fav_seat[2])) 

594 table.addWidget(seat2, y_pos, 1) 

595 seat2_dict.append(seat2) 

596 seat2.textChanged.connect(partial(self.autoenableSite, checkbox=check_buttons[site_number])) 

597 

598 seat3 = QLineEdit() 

599 seat3.setText(str(self.config.supported_sites[available_site_names[site_number]].fav_seat[3])) 

600 table.addWidget(seat3, y_pos, 2) 

601 seat3_dict.append(seat3) 

602 

603 seat4 = QLineEdit() 

604 seat4.setText(str(self.config.supported_sites[available_site_names[site_number]].fav_seat[4])) 

605 table.addWidget(seat4, y_pos, 3) 

606 seat4_dict.append(seat4) 

607 

608 seat5 = QLineEdit() 

609 seat5.setText(str(self.config.supported_sites[available_site_names[site_number]].fav_seat[5])) 

610 table.addWidget(seat5, y_pos, 4) 

611 seat5_dict.append(seat5) 

612 

613 seat6 = QLineEdit() 

614 seat6.setText(str(self.config.supported_sites[available_site_names[site_number]].fav_seat[6])) 

615 table.addWidget(seat6, y_pos, 5) 

616 seat6_dict.append(seat6) 

617 

618 seat7 = QLineEdit() 

619 seat7.setText(str(self.config.supported_sites[available_site_names[site_number]].fav_seat[7])) 

620 table.addWidget(seat7, y_pos, 6) 

621 seat7_dict.append(seat7) 

622 

623 seat8 = QLineEdit() 

624 seat8.setText(str(self.config.supported_sites[available_site_names[site_number]].fav_seat[8])) 

625 table.addWidget(seat8, y_pos, 7) 

626 seat8_dict.append(seat8) 

627 

628 seat9 = QLineEdit() 

629 seat9.setText(str(self.config.supported_sites[available_site_names[site_number]].fav_seat[9])) 

630 table.addWidget(seat9, y_pos, 8) 

631 seat9_dict.append(seat9) 

632 

633 seat10 = QLineEdit() 

634 seat10.setText(str(self.config.supported_sites[available_site_names[site_number]].fav_seat[10])) 

635 table.addWidget(seat10, y_pos, 9) 

636 seat10_dict.append(seat10) 

637 

638 if available_site_names[site_number] in detector.supportedSites: 

639 pass 

640 

641 y_pos += 1 

642 

643 btns = QDialogButtonBox(QDialogButtonBox.Save | QDialogButtonBox.Cancel, dia) 

644 btns.accepted.connect(dia.accept) 

645 btns.rejected.connect(dia.reject) 

646 dia.layout().addWidget(btns) 

647 

648 response = dia.exec_() 

649 if response: 

650 for site_number in range(0, len(available_site_names)): 

651 # print "site %s enabled=%s name=%s" % (available_site_names[site_number], 

652 # check_buttons[site_number].get_active(), screen_names[site_number].get_text(), 

653 # history_paths[site_number].get_text()) 

654 self.config.edit_fav_seat(available_site_names[site_number], 

655 str(check_buttons[site_number].isChecked()), seat2_dict[site_number].text(), 

656 seat3_dict[site_number].text(), seat4_dict[site_number].text(), 

657 seat5_dict[site_number].text(), seat6_dict[site_number].text(), 

658 seat7_dict[site_number].text(), seat8_dict[site_number].text(), 

659 seat9_dict[site_number].text(), seat10_dict[site_number].text()) 

660 

661 self.config.save() 

662 self.reload_config() 

663 

664 def dia_site_preferences(self, widget, data=None): 

665 dia = QDialog(self) 

666 dia.setWindowTitle("Site Preferences") 

667 dia.resize(1200, 600) 

668 label = QLabel("Please select which sites you play on and enter your usernames.") 

669 dia.setLayout(QVBoxLayout()) 

670 dia.layout().addWidget(label) 

671 

672 self.load_profile() 

673 site_names = self.config.site_ids 

674 available_site_names = [] 

675 for site_name in site_names: 

676 try: 

677 tmp = self.config.supported_sites[site_name].enabled 

678 available_site_names.append(site_name) 

679 except KeyError: 

680 pass 

681 

682 column_headers = ["Site", "Detect", "Screen Name", "Hand History Path", "", 

683 "Tournament Summary Path", "", "Favorite seat"] # todo ("HUD") 

684 # HUD column will contain a button that shows favseat and HUD locations. 

685 # Make it possible to load screenshot to arrange HUD windowlets. 

686 

687 table = QGridLayout() 

688 table.setSpacing(0) 

689 

690 scrolling_frame = QScrollArea(dia) 

691 dia.layout().addWidget(scrolling_frame) 

692 scrolling_frame.setLayout(table) 

693 

694 for header_number in range(0, len(column_headers)): 

695 label = QLabel(column_headers[header_number]) 

696 label.setAlignment(Qt.AlignCenter) 

697 table.addWidget(label, 0, header_number) 

698 

699 check_buttons = [] 

700 screen_names = [] 

701 history_paths = [] 

702 summary_paths = [] 

703 detector = DetectInstalledSites.DetectInstalledSites() 

704 

705 y_pos = 1 

706 for site_number in range(0, len(available_site_names)): 

707 check_button = QCheckBox(available_site_names[site_number]) 

708 check_button.setChecked(self.config.supported_sites[available_site_names[site_number]].enabled) 

709 table.addWidget(check_button, y_pos, 0) 

710 check_buttons.append(check_button) 

711 

712 hero = QLineEdit() 

713 hero.setText(self.config.supported_sites[available_site_names[site_number]].screen_name) 

714 table.addWidget(hero, y_pos, 2) 

715 screen_names.append(hero) 

716 hero.textChanged.connect(partial(self.autoenableSite, checkbox=check_buttons[site_number])) 

717 

718 entry = QLineEdit() 

719 entry.setText(self.config.supported_sites[available_site_names[site_number]].HH_path) 

720 table.addWidget(entry, y_pos, 3) 

721 history_paths.append(entry) 

722 

723 choose1 = QPushButton("Browse") 

724 table.addWidget(choose1, y_pos, 4) 

725 choose1.clicked.connect(partial(self.browseClicked, parent=dia, path=history_paths[site_number])) 

726 

727 entry = QLineEdit() 

728 entry.setText(self.config.supported_sites[available_site_names[site_number]].TS_path) 

729 table.addWidget(entry, y_pos, 5) 

730 summary_paths.append(entry) 

731 

732 choose2 = QPushButton("Browse") 

733 table.addWidget(choose2, y_pos, 6) 

734 choose2.clicked.connect(partial(self.browseClicked, parent=dia, path=summary_paths[site_number])) 

735 

736 if available_site_names[site_number] in detector.supportedSites: 

737 button = QPushButton("Detect") 

738 table.addWidget(button, y_pos, 1) 

739 button.clicked.connect(partial(self.detect_clicked, data=(detector, available_site_names[site_number], 

740 screen_names[site_number], 

741 history_paths[site_number], 

742 summary_paths[site_number]))) 

743 y_pos += 1 

744 

745 btns = QDialogButtonBox(QDialogButtonBox.Save | QDialogButtonBox.Cancel, dia) 

746 btns.accepted.connect(dia.accept) 

747 btns.rejected.connect(dia.reject) 

748 dia.layout().addWidget(btns) 

749 

750 response = dia.exec_() 

751 if response: 

752 for site_number in range(0, len(available_site_names)): 

753 # print "site %s enabled=%s name=%s" % (available_site_names[site_number], 

754 # check_buttons[site_number].get_active(), screen_names[site_number].get_text(), 

755 # history_paths[site_number].get_text()) 

756 self.config.edit_site(available_site_names[site_number], str(check_buttons[site_number].isChecked()), 

757 screen_names[site_number].text(), history_paths[site_number].text(), 

758 summary_paths[site_number].text()) 

759 

760 self.config.save() 

761 self.reload_config() 

762 

763 def autoenableSite(self, text, checkbox): 

764 # autoactivate site if something gets typed in the screename field 

765 checkbox.setChecked(True) 

766 

767 def browseClicked(self, widget, parent, path): 

768 """runs when user clicks one of the browse buttons for the TS folder""" 

769 

770 newpath = QFileDialog.getExistingDirectory(parent, "Please choose the path that you want to Auto Import", 

771 path.text()) 

772 if newpath: 

773 path.setText(newpath) 

774 

775 def detect_clicked(self, widget, data): 

776 detector = data[0] 

777 site_name = data[1] 

778 entry_screen_name = data[2] 

779 entry_history_path = data[3] 

780 entry_summary_path = data[4] 

781 if detector.sitestatusdict[site_name]['detected']: 

782 entry_screen_name.setText(detector.sitestatusdict[site_name]['heroname']) 

783 entry_history_path.setText(detector.sitestatusdict[site_name]['hhpath']) 

784 if detector.sitestatusdict[site_name]['tspath']: 

785 entry_summary_path.setText(detector.sitestatusdict[site_name]['tspath']) 

786 

787 def reload_config(self): 

788 if len(self.nb_tab_names) == 1: 

789 # only main tab open, reload profile 

790 self.load_profile() 

791 self.warning_box("Configuration settings have been updated," 

792 " Fpdb needs to be restarted now\n\nClick OK to close Fpdb") 

793 sys.exit() 

794 else: 

795 self.warning_box("Updated preferences have not been loaded because windows are open." 

796 " Re-start fpdb to load them.") 

797 

798 def process_close_messages(self): 

799 # check for close messages 

800 try: 

801 while True: 

802 name = self.closeq.get(False) 

803 for i, t in enumerate(self.threads): 

804 if str(t.__class__) == str(name): 

805 # thread has ended so remove from list: 

806 del self.threads[i] 

807 break 

808 except queue.Empty: 

809 # no close messages on queue, do nothing 

810 pass 

811 

812 def __calendar_dialog(self, widget, entry): 

813 d = QDialog(self.dia_confirm) 

814 d.setWindowTitle('Pick a date') 

815 

816 vb = QVBoxLayout() 

817 d.setLayout(vb) 

818 cal = QCalendarWidget() 

819 vb.addWidget(cal) 

820 

821 btn = QPushButton('Done') 

822 btn.clicked.connect(partial(self.__get_date, calendar=cal, entry=entry, win=d)) 

823 

824 vb.addWidget(btn) 

825 

826 d.exec_() 

827 return 

828 

829 def createMenuBar(self): 

830 mb = self.menuBar() 

831 configMenu = mb.addMenu('Configure') 

832 importMenu = mb.addMenu('Import') 

833 hudMenu = mb.addMenu('HUD') 

834 cashMenu = mb.addMenu('Cash') 

835 tournamentMenu = mb.addMenu('Tournament') 

836 maintenanceMenu = mb.addMenu('Maintenance') 

837 #toolsMenu = mb.addMenu('Tools') 

838 helpMenu = mb.addMenu('Help') 

839 themeMenu = mb.addMenu('Themes') 

840 

841 # Create actions 

842 def makeAction(name, callback, shortcut=None, tip=None): 

843 action = QAction(name, self) 

844 if shortcut: 

845 action.setShortcut(shortcut) 

846 if tip: 

847 action.setToolTip(tip) 

848 action.triggered.connect(callback) 

849 return action 

850 

851 configMenu.addAction(makeAction('Site Settings', self.dia_site_preferences)) 

852 configMenu.addAction(makeAction('Seat Settings', self.dia_site_preferences_seat)) 

853 configMenu.addAction(makeAction('Hud Settings', self.dia_hud_preferences)) 

854 configMenu.addAction( 

855 makeAction('Adv Preferences', self.dia_advanced_preferences, tip='Edit your preferences')) 

856 configMenu.addAction(makeAction('Import filters', self.dia_import_filters)) 

857 configMenu.addSeparator() 

858 configMenu.addAction(makeAction('Close Fpdb', self.quit, 'Ctrl+Q', 'Quit the Program')) 

859 

860 importMenu.addAction(makeAction('Bulk Import', self.tab_bulk_import, 'Ctrl+B')) 

861 hudMenu.addAction(makeAction('HUD and Auto Import', self.tab_auto_import, 'Ctrl+A')) 

862 cashMenu.addAction(makeAction('Graphs', self.tabGraphViewer, 'Ctrl+G')) 

863 cashMenu.addAction(makeAction('Ring Player Stats', self.tab_ring_player_stats, 'Ctrl+P')) 

864 cashMenu.addAction(makeAction('Hand Viewer', self.tab_hand_viewer)) 

865 cashMenu.addAction(makeAction('Session Stats', self.tab_session_stats, 'Ctrl+S')) 

866 tournamentMenu.addAction(makeAction('Tourney Graphs', self.tabTourneyGraphViewer)) 

867 tournamentMenu.addAction(makeAction('Tourney Stats', self.tab_tourney_player_stats, 'Ctrl+T')) 

868 tournamentMenu.addAction(makeAction('Tourney Hand Viewer', self.tab_tourney_viewer_stats)) 

869 maintenanceMenu.addAction(makeAction('Statistics', self.dia_database_stats, 'View Database Statistics')) 

870 maintenanceMenu.addAction(makeAction('Create or Recreate Tables', self.dia_recreate_tables)) 

871 maintenanceMenu.addAction(makeAction('Rebuild HUD Cache', self.dia_recreate_hudcache)) 

872 maintenanceMenu.addAction(makeAction('Rebuild DB Indexes', self.dia_rebuild_indexes)) 

873 maintenanceMenu.addAction(makeAction('Dump Database to Textfile (takes ALOT of time)', self.dia_dump_db)) 

874 

875 #toolsMenu.addAction(makeAction('PokerProTools', self.launch_ppt)) 

876 helpMenu.addAction(makeAction('Log Messages', self.dia_logs, 'Log and Debug Messages')) 

877 helpMenu.addAction(makeAction('Help Tab', self.tab_main_help)) 

878 helpMenu.addSeparator() 

879 helpMenu.addAction(makeAction('Infos', self.dia_about, 'About the program')) 

880 

881 themes = [ 

882 'dark_purple.xml', 'dark_teal.xml', 'dark_blue.xml', 'dark_cyan.xml', 

883 'dark_pink.xml', 'dark_red.xml', 'dark_lime.xml', 'light_purple.xml', 

884 'light_teal.xml', 'light_blue.xml', 'light_cyan.xml', 'light_pink.xml', 

885 'light_red.xml', 'light_lime.xml' 

886 ] 

887 

888 for theme in themes: 

889 themeMenu.addAction(QAction(theme, self, triggered=partial(self.change_theme, theme))) 

890 

891 

892 def load_profile(self, create_db=False): 

893 """Loads profile from the provided path name. 

894 Set: 

895 - self.settings 

896 - self.config 

897 - self.db 

898 """ 

899 self.config = Configuration.Config(file=options.config, dbname=options.dbname) 

900 if self.config.file_error: 

901 self.warning_box(f"There is an error in your config file" 

902 f" {self.config.file}:\n{str(self.config.file_error)}", diatitle="CONFIG FILE ERROR") 

903 sys.exit() 

904 

905 log.info(f"Logfile is {os.path.join(self.config.dir_log, self.config.log_file)}") 

906 log.info(f"load profiles {self.config.example_copy}") 

907 log.info(f"{self.display_config_created_dialogue}") 

908 log.info(f"{self.config.wrongConfigVersion}") 

909 if self.config.example_copy or self.display_config_created_dialogue: 

910 self.info_box("Config file", [ 

911 "Config file has been created at " + self.config.file + ".", 

912 "Enter your screen_name and hand history path in the Site Preferences window" 

913 " (Main menu) before trying to import hands." 

914 ]) 

915 

916 self.display_config_created_dialogue = False 

917 elif self.config.wrongConfigVersion: 

918 diaConfigVersionWarning = QDialog() 

919 diaConfigVersionWarning.setWindowTitle("Strong Warning - Local configuration out of date") 

920 diaConfigVersionWarning.setLayout(QVBoxLayout()) 

921 label = QLabel([ 

922 "\nYour local configuration file needs to be updated." 

923 ]) 

924 diaConfigVersionWarning.layout().addWidget(label) 

925 label = QLabel([ 

926 "\nYour local configuration file needs to be updated.", 

927 "This error is not necessarily fatal but it is strongly recommended that you update the configuration." 

928 ]) 

929 

930 diaConfigVersionWarning.layout().addWidget(label) 

931 label = QLabel([ 

932 "To create a new configuration, see:", 

933 "fpdb.sourceforge.net/apps/mediawiki/fpdb/index.php?title=Reset_Configuration" 

934 ]) 

935 

936 label.setTextInteractionFlags(Qt.TextSelectableByMouse) 

937 diaConfigVersionWarning.layout().addWidget(label) 

938 label = QLabel([ 

939 "A new configuration will destroy all personal settings" 

940 " (hud layout, site folders, screennames, favourite seats).\n" 

941 ]) 

942 

943 diaConfigVersionWarning.layout().addWidget(label) 

944 

945 label = QLabel("To keep existing personal settings, you must edit the local file.") 

946 diaConfigVersionWarning.layout().addWidget(label) 

947 

948 label = QLabel("See the release note for information about the edits needed") 

949 diaConfigVersionWarning.layout().addWidget(label) 

950 

951 btns = QDialogButtonBox(QDialogButtonBox.Ok) 

952 btns.accepted.connect(diaConfigVersionWarning.accept) 

953 diaConfigVersionWarning.layout().addWidget(btns) 

954 

955 diaConfigVersionWarning.exec_() 

956 self.config.wrongConfigVersion = False 

957 

958 self.settings = {} 

959 self.settings['global_lock'] = self.lock 

960 if os.sep == "/": 

961 self.settings['os'] = "linuxmac" 

962 else: 

963 self.settings['os'] = "windows" 

964 

965 self.settings.update({'cl_options': cl_options}) 

966 self.settings.update(self.config.get_db_parameters()) 

967 self.settings.update(self.config.get_import_parameters()) 

968 self.settings.update(self.config.get_default_paths()) 

969 

970 if self.db is not None and self.db.is_connected(): 

971 self.db.disconnect() 

972 

973 self.sql = SQL.Sql(db_server=self.settings['db-server']) 

974 err_msg = None 

975 try: 

976 self.db = Database.Database(self.config, sql=self.sql) 

977 if self.db.get_backend_name() == 'SQLite': 

978 # tell sqlite users where the db file is 

979 log.info(f"Connected to SQLite: {self.db.db_path}") 

980 except Exceptions.FpdbMySQLAccessDenied: 

981 err_msg = "MySQL Server reports: Access denied. Are your permissions set correctly?" 

982 except Exceptions.FpdbMySQLNoDatabase: 

983 err_msg = "MySQL client reports: 2002 or 2003 error." \ 

984 " Unable to connect - Please check that the MySQL service has been started." 

985 

986 except Exceptions.FpdbPostgresqlAccessDenied: 

987 err_msg = "PostgreSQL Server reports: Access denied. Are your permissions set correctly?" 

988 except Exceptions.FpdbPostgresqlNoDatabase: 

989 err_msg = "PostgreSQL client reports: Unable to connect -" \ 

990 "Please check that the PostgreSQL service has been started." 

991 if err_msg is not None: 

992 self.db = None 

993 self.warning_box(err_msg) 

994 if self.db is not None and not self.db.is_connected(): 

995 self.db = None 

996 

997 if self.db is not None and self.db.wrongDbVersion: 

998 diaDbVersionWarning = QMessageBox(QMessageBox.Warning, "Strong Warning - Invalid database version", 

999 "An invalid DB version or missing tables have been detected.", 

1000 QMessageBox.Ok, self) 

1001 diaDbVersionWarning.setInformativeText( 

1002 "This error is not necessarily fatal but it is strongly" 

1003 " recommended that you recreate the tables by using the Database menu." 

1004 "Not doing this will likely lead to misbehaviour including fpdb crashes, corrupt data etc." 

1005 ) 

1006 

1007 diaDbVersionWarning.exec_() 

1008 if self.db is not None and self.db.is_connected(): 

1009 self.statusBar().showMessage(f"Status: Connected to {self.db.get_backend_name()}" 

1010 f" database named {self.db.database} on host {self.db.host}") 

1011 

1012 # rollback to make sure any locks are cleared: 

1013 self.db.rollback() 

1014 

1015 # If the db-version is out of date, don't validate the config 

1016 # otherwise the end user gets bombarded with false messages 

1017 # about every site not existing 

1018 if hasattr(self.db, 'wrongDbVersion'): 

1019 if not self.db.wrongDbVersion: 

1020 self.validate_config() 

1021 

1022 def obtain_global_lock(self, source): 

1023 ret = self.lock.acquire(source=source) # will return false if lock is already held 

1024 if ret: 

1025 log.info(f"Global lock taken by {source}") 

1026 self.lockTakenBy = source 

1027 else: 

1028 log.info(f"Failed to get global lock, it is currently held by {source}") 

1029 return ret 

1030 # need to release it later: 

1031 # self.lock.release() 

1032 

1033 def quit(self, widget, data=None): 

1034 # TODO: can we get some / all of the stuff done in this function to execute on any kind of abort? 

1035 # FIXME get two "quitting normally" messages, following the addition of the self.window.destroy() call 

1036 # ... because self.window.destroy() leads to self.destroy() which calls this! 

1037 if not self.quitting: 

1038 log.info("Quitting normally") 

1039 self.quitting = True 

1040 # TODO: check if current settings differ from profile, if so offer to save or abort 

1041 

1042 if self.db is not None: 

1043 if self.db.backend == self.db.MYSQL_INNODB: 

1044 try: 

1045 import _mysql_exceptions 

1046 if self.db is not None and self.db.is_connected(): 

1047 self.db.disconnect() 

1048 except _mysql_exceptions.OperationalError: # oh, damn, we're already disconnected 

1049 pass 

1050 else: 

1051 if self.db is not None and self.db.is_connected(): 

1052 self.db.disconnect() 

1053 else: 

1054 pass 

1055 # self.statusIcon.set_visible(False) 

1056 QCoreApplication.quit() 

1057 

1058 def release_global_lock(self): 

1059 self.lock.release() 

1060 self.lockTakenBy = None 

1061 log.info("Global lock released.") 

1062 

1063 def tab_auto_import(self, widget, data=None): 

1064 """opens the auto import tab""" 

1065 new_aimp_thread = GuiAutoImport.GuiAutoImport(self.settings, self.config, self.sql, self) 

1066 self.threads.append(new_aimp_thread) 

1067 self.add_and_display_tab(new_aimp_thread, "HUD") 

1068 if options.autoimport: 

1069 new_aimp_thread.startClicked(new_aimp_thread.startButton, "autostart") 

1070 options.autoimport = False 

1071 

1072 def tab_bulk_import(self, widget, data=None): 

1073 """opens a tab for bulk importing""" 

1074 new_import_thread = GuiBulkImport.GuiBulkImport(self.settings, self.config, self.sql, self) 

1075 self.threads.append(new_import_thread) 

1076 self.add_and_display_tab(new_import_thread, "Bulk Import") 

1077 

1078 

1079 

1080 def tab_tourney_import(self, widget, data=None): 

1081 """opens a tab for bulk importing tournament summaries""" 

1082 new_import_thread = GuiTourneyImport.GuiTourneyImport(self.settings, self.config, self.sql, self.window) 

1083 self.threads.append(new_import_thread) 

1084 bulk_tab = new_import_thread.get_vbox() 

1085 self.add_and_display_tab(bulk_tab, "Tournament Results Import") 

1086 

1087 

1088 

1089 # end def tab_import_imap_summaries 

1090 

1091 def tab_ring_player_stats(self, widget, data=None): 

1092 new_ps_thread = GuiRingPlayerStats.GuiRingPlayerStats(self.config, self.sql, self) 

1093 self.threads.append(new_ps_thread) 

1094 self.add_and_display_tab(new_ps_thread, "Ring Player Stats") 

1095 

1096 def tab_tourney_player_stats(self, widget, data=None): 

1097 new_ps_thread = GuiTourneyPlayerStats.GuiTourneyPlayerStats(self.config, self.db, self.sql, self) 

1098 self.threads.append(new_ps_thread) 

1099 self.add_and_display_tab(new_ps_thread, "Tourney Stats") 

1100 

1101 def tab_tourney_viewer_stats(self, widget, data=None): 

1102 new_thread = GuiTourHandViewer.TourHandViewer(self.config, self.sql, self) 

1103 self.threads.append(new_thread) 

1104 self.add_and_display_tab(new_thread, "Tourney Viewer") 

1105 

1106 def tab_positional_stats(self, widget, data=None): 

1107 new_ps_thread = GuiPositionalStats.GuiPositionalStats(self.config, self.sql) 

1108 self.threads.append(new_ps_thread) 

1109 ps_tab = new_ps_thread.get_vbox() 

1110 self.add_and_display_tab(ps_tab, "Positional Stats") 

1111 

1112 def tab_session_stats(self, widget, data=None): 

1113 colors = self.get_theme_colors() 

1114 new_ps_thread = GuiSessionViewer.GuiSessionViewer(self.config, self.sql, self, self, colors=colors) 

1115 self.threads.append(new_ps_thread) 

1116 self.add_and_display_tab(new_ps_thread, "Session Stats") 

1117 

1118 def tab_hand_viewer(self, widget, data=None): 

1119 new_ps_thread = GuiHandViewer.GuiHandViewer(self.config, self.sql, self) 

1120 self.threads.append(new_ps_thread) 

1121 self.add_and_display_tab(new_ps_thread, "Hand Viewer") 

1122 

1123 def tab_main_help(self, widget, data=None): 

1124 """Displays a tab with the main fpdb help screen""" 

1125 mh_tab = QLabel((""" 

1126 Welcome to Fpdb! 

1127 

1128 This program is currently in an alpha-state, so our database format is still sometimes changed. 

1129 You should therefore always keep your hand history files so that you can re-import 

1130 after an update, if necessary. 

1131 

1132 all configuration now happens in HUD_config.xml. 

1133 

1134 This program is free/libre open source software licensed partially under the AGPL3, 

1135 and partially under GPL2 or later. 

1136 The Windows installer package includes code licensed under the MIT license. 

1137 You can find the full license texts in agpl-3.0.txt, gpl-2.0.txt, gpl-3.0.txt 

1138 and mit.txt in the fpdb installation directory.""")) 

1139 self.add_and_display_tab(mh_tab, "Help") 

1140 

1141 def get_theme_colors(self): 

1142 """ 

1143 Returns a dictionary containing the theme colors used in the application. 

1144 

1145 The dictionary contains the following keys: 

1146 - "background": the name of the color used for the background. 

1147 - "foreground": the name of the color used for the foreground. 

1148 - "grid": the name of the color used for the grid. 

1149 - "line_showdown": the name of the color used for the showdown line. 

1150 - "line_nonshowdown": the name of the color used for the non-showdown line. 

1151 - "line_ev": the name of the color used for the event line. 

1152 - "line_hands": the name of the color used for the hands line. 

1153 

1154 Returns: 

1155 dict: A dictionary containing the theme colors. 

1156 """ 

1157 return { 

1158 "background": self.palette().color(QPalette.Window).name(), 

1159 "foreground": self.palette().color(QPalette.WindowText).name(), 

1160 "grid": "#444444", # to customize 

1161 "line_showdown": "#0000FF", 

1162 "line_nonshowdown": "#FF0000", 

1163 "line_ev": "#FFA500", 

1164 "line_hands": "#00FF00", 

1165 'line_up': 'g', 

1166 'line_down': 'r', 

1167 'line_showdown': 'b', 

1168 'line_nonshowdown': 'm', 

1169 'line_ev': 'orange', 

1170 'line_hands': 'c' 

1171 } 

1172 

1173 def tabGraphViewer(self, widget, data=None): 

1174 """opens a graph viewer tab""" 

1175 colors = self.get_theme_colors() 

1176 new_gv_thread = GuiGraphViewer.GuiGraphViewer(self.sql, self.config, self, colors=colors) 

1177 self.threads.append(new_gv_thread) 

1178 self.add_and_display_tab(new_gv_thread, "Graphs") 

1179 

1180 def tabTourneyGraphViewer(self, widget, data=None): 

1181 """opens a graph viewer tab""" 

1182 colors = self.get_theme_colors() 

1183 new_gv_thread = GuiTourneyGraphViewer.GuiTourneyGraphViewer(self.sql, self.config, self, colors=colors) 

1184 self.threads.append(new_gv_thread) 

1185 self.add_and_display_tab(new_gv_thread, "Tourney Graphs") 

1186 

1187 def tabStove(self, widget, data=None): 

1188 """opens a tab for poker stove""" 

1189 thread = GuiStove.GuiStove(self.config, self) 

1190 self.threads.append(thread) 

1191 # tab = thread.get_vbox() 

1192 self.add_and_display_tab(thread, "Stove") 

1193 

1194 def validate_config(self): 

1195 # check if sites in config file are in DB 

1196 for site in self.config.supported_sites: # get site names from config file 

1197 try: 

1198 self.config.get_site_id(site) # and check against list from db 

1199 except KeyError: 

1200 log.warning(f"site {site} missing from db") 

1201 dia = QMessageBox() 

1202 dia.setIcon(QMessageBox.Warning) 

1203 dia.setText("Unknown Site") 

1204 dia.setStandardButtons(QMessageBox.Ok) 

1205 dia.exec_() 

1206 diastring = f"Warning: Unable to find site '{site}'" 

1207 dia.format_secondary_text(diastring) 

1208 dia.run() 

1209 dia.destroy() 

1210 

1211 def info_box(self, str1, str2): 

1212 diapath = QMessageBox(self) 

1213 diapath.setWindowTitle(str1) 

1214 diapath.setText(str2) 

1215 return diapath.exec_() 

1216 

1217 def warning_box(self, string, diatitle="FPDB WARNING"): 

1218 return QMessageBox(QMessageBox.Warning, diatitle, string).exec_() 

1219 

1220 def change_theme(self, theme): 

1221 apply_stylesheet(app, theme=theme) 

1222 

1223 

1224 

1225 def update_title_bar_theme(self): 

1226 # Apply the stylesheet to the custom title bar 

1227 self.custom_title_bar.update_theme() 

1228 

1229 def close_tab(self, index): 

1230 item = self.nb.widget(index) 

1231 self.nb.removeTab(index) 

1232 self.nb_tab_names.pop(index) 

1233 

1234 try: 

1235 self.threads.remove(item) 

1236 except ValueError: 

1237 pass 

1238 

1239 item.deleteLater() 

1240 

1241 def __init__(self): 

1242 super().__init__() 

1243 if sys.platform == 'darwin': 

1244 pass 

1245 else: 

1246 self.setWindowFlags(Qt.FramelessWindowHint) 

1247 cards = os.path.join(Configuration.GRAPHICS_PATH, 'tribal.jpg') 

1248 if os.path.exists(cards): 

1249 self.setWindowIcon(QIcon(cards)) 

1250 set_locale_translation() 

1251 self.lock = interlocks.InterProcessLock(name="fpdb_global_lock") 

1252 self.db = None 

1253 self.status_bar = None 

1254 self.quitting = False 

1255 self.visible = False 

1256 self.threads = [] 

1257 self.closeq = queue.Queue(20) 

1258 

1259 self.oldPos = self.pos() 

1260 

1261 

1262 

1263 if options.initialRun: 

1264 self.display_config_created_dialogue = True 

1265 self.display_site_preferences = True 

1266 else: 

1267 self.display_config_created_dialogue = False 

1268 self.display_site_preferences = False 

1269 

1270 if options.xloc is not None or options.yloc is not None: 

1271 if options.xloc is None: 

1272 options.xloc = 0 

1273 if options.yloc is None: 

1274 options.yloc = 0 

1275 self.move(options.xloc, options.yloc) 

1276 

1277 self.setWindowTitle("Free Poker DB 3") 

1278 defx, defy = 1920, 1080 

1279 sg = QApplication.primaryScreen().availableGeometry() 

1280 if sg.width() < defx: 

1281 defx = sg.width() 

1282 if sg.height() < defy: 

1283 defy = sg.height() 

1284 self.resize(defx, defy) 

1285 

1286 if sys.platform == 'darwin': 

1287 pass 

1288 else: 

1289 # Create custom title bar 

1290 self.custom_title_bar = CustomTitleBar(self) 

1291 # Create central widget and layout 

1292 self.central_widget = QWidget(self) 

1293 self.central_layout = QVBoxLayout(self.central_widget) 

1294 self.central_layout.setContentsMargins(0, 0, 0, 0) 

1295 self.central_layout.setSpacing(0) 

1296 

1297 if sys.platform == 'darwin': 

1298 # Add title bar and menu bar to layout 

1299 self.custom_title_bar = CustomTitleBar(self) 

1300 self.central_layout.addWidget(self.custom_title_bar) 

1301 self.setMenuBar(self.menuBar()) 

1302 else: 

1303 # Add title bar and menu bar to layout 

1304 self.central_layout.addWidget(self.custom_title_bar) 

1305 self.menu_bar = self.menuBar() 

1306 self.central_layout.setMenuBar(self.menu_bar) 

1307 

1308 self.nb = QTabWidget() 

1309 self.nb.setTabsClosable(True) 

1310 self.nb.tabCloseRequested.connect(self.close_tab) 

1311 self.central_layout.addWidget(self.nb) 

1312 self.setCentralWidget(self.central_widget) 

1313 

1314 self.createMenuBar() 

1315 

1316 self.pages = [] 

1317 self.nb_tab_names = [] 

1318 

1319 self.tab_main_help(None, None) 

1320 

1321 if options.minimized: 

1322 self.showMinimized() 

1323 if options.hidden: 

1324 self.hide() 

1325 

1326 if not options.hidden: 

1327 self.show() 

1328 self.visible = True 

1329 

1330 self.load_profile(create_db=True) 

1331 

1332 if self.config.install_method == 'app': 

1333 for site in list(self.config.supported_sites.values()): 

1334 if site.screen_name != "YOUR SCREEN NAME HERE": 

1335 break 

1336 else: 

1337 options.initialRun = True 

1338 self.display_config_created_dialogue = True 

1339 self.display_site_preferences = True 

1340 

1341 if options.initialRun and self.display_site_preferences: 

1342 self.dia_site_preferences(None, None) 

1343 self.display_site_preferences = False 

1344 

1345 if not options.errorsToConsole: 

1346 fileName = os.path.join(self.config.dir_log, 'fpdb-errors.txt') 

1347 log.info(f"Note: error output is being diverted to {self.config.dir_log}. Any major error will be reported there _only_.") 

1348 errorFile = codecs.open(fileName, 'w', 'utf-8') 

1349 sys.stderr = errorFile 

1350 

1351 sys.stderr.write("fpdb starting ...") 

1352 

1353 if options.autoimport: 

1354 self.tab_auto_import(None) 

1355 

1356 

1357 

1358class CustomTitleBar(QWidget): 

1359 def __init__(self, parent=None): 

1360 super().__init__(parent) 

1361 self.setAutoFillBackground(True) 

1362 self.main_window = parent 

1363 

1364 self.title = QLabel("Free Poker DB 3") 

1365 self.title.setAlignment(Qt.AlignCenter) 

1366 

1367 self.btn_minimize = QPushButton("-") 

1368 self.btn_maximize = QPushButton("+") 

1369 self.btn_close = QPushButton("x") 

1370 

1371 button_size = 20 

1372 self.btn_minimize.setFixedSize(button_size, button_size) 

1373 self.btn_maximize.setFixedSize(button_size, button_size) 

1374 self.btn_close.setFixedSize(button_size, button_size) 

1375 

1376 self.btn_minimize.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) 

1377 self.btn_maximize.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) 

1378 self.btn_close.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) 

1379 

1380 self.btn_minimize.clicked.connect(parent.showMinimized) 

1381 self.btn_maximize.clicked.connect(self.toggle_maximize_restore) 

1382 self.btn_close.clicked.connect(parent.close) 

1383 

1384 layout = QHBoxLayout() 

1385 layout.addWidget(self.title) 

1386 layout.addStretch() 

1387 layout.addWidget(self.btn_minimize) 

1388 layout.addWidget(self.btn_maximize) 

1389 layout.addWidget(self.btn_close) 

1390 self.setLayout(layout) 

1391 

1392 self.is_maximized = False 

1393 if sys.platform == 'darwin': 

1394 pass 

1395 else: 

1396 self.moving = False 

1397 self.offset = None 

1398 

1399 def toggle_maximize_restore(self): 

1400 if self.is_maximized: 

1401 self.main_window.showNormal() 

1402 else: 

1403 self.main_window.showMaximized() 

1404 self.is_maximized = not self.is_maximized 

1405 

1406 def update_theme(self): 

1407 self.setStyleSheet(self.main_window.styleSheet()) 

1408 

1409 def mousePressEvent(self, event): 

1410 if event.button() == Qt.LeftButton: 

1411 self.main_window.oldPos = event.globalPos() 

1412 

1413 def mouseMoveEvent(self, event): 

1414 if event.buttons() == Qt.LeftButton: 

1415 delta = QPoint(event.globalPos() - self.main_window.oldPos) 

1416 self.main_window.move(self.main_window.x() + delta.x(), self.main_window.y() + delta.y()) 

1417 self.main_window.oldPos = event.globalPos() 

1418 

1419 

1420 

1421if __name__ == "__main__": 

1422 from qt_material import apply_stylesheet 

1423 import time 

1424 try: 

1425 app = QApplication([]) 

1426 apply_stylesheet(app, theme='dark_purple.xml') 

1427 me = fpdb() 

1428 app.exec_() 

1429 finally: 

1430 profiler.disable() 

1431 s = io.StringIO() 

1432 ps = pstats.Stats(profiler, stream=s).sort_stats('cumulative') 

1433 ps.print_stats() 

1434 

1435 # Use timestamp or process ID for unique filenames 

1436 timestamp = time.strftime("%Y%m%d-%H%M%S") 

1437 results_file = os.path.join(PROFILE_OUTPUT_DIR, f'fpdb_profile_results_{timestamp}.txt') 

1438 profile_file = os.path.join(PROFILE_OUTPUT_DIR, f'fpdb_profile_{timestamp}.prof') 

1439 

1440 with open(results_file, 'w') as f: 

1441 f.write(s.getvalue()) 

1442 

1443 profiler.dump_stats(profile_file)