Coverage for fpdb.pyw: 0%
899 statements
« prev ^ index » next coverage.py v7.6.4, created at 2024-11-07 02:19 +0000
« prev ^ index » next coverage.py v7.6.4, created at 2024-11-07 02:19 +0000
1#!/usr/bin/env python
2# -*- coding: utf-8 -*-
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.
19import os
20import sys
21import queue
23# import qdarkstyle
24if os.name == "nt":
25 pass
27import codecs
28import Options
29from functools import partial
30from L10n import set_locale_translation
31import logging
33from PyQt5.QtCore import QCoreApplication, QDate, Qt, QPoint
34from PyQt5.QtGui import QIcon, QPalette
35from PyQt5.QtWidgets import QWidget, QLabel, QPushButton, QHBoxLayout, QSizePolicy
36from PyQt5.QtWidgets import (
37 QAction,
38 QApplication,
39 QCalendarWidget,
40 QCheckBox,
41 QDateEdit,
42 QDialog,
43 QDialogButtonBox,
44 QFileDialog,
45 QGridLayout,
46 QLineEdit,
47 QMainWindow,
48 QMessageBox,
49 QScrollArea,
50 QTabWidget,
51 QVBoxLayout,
52 QComboBox,
53)
55import DetectInstalledSites
56import GuiPrefs
57import GuiLogView
59# import GuiDatabase
60import GuiBulkImport
61# import GuiTourneyImport
63import GuiRingPlayerStats
64import GuiTourneyPlayerStats
66# import GuiPositionalStats
67import GuiAutoImport
68import GuiGraphViewer
69import GuiTourneyGraphViewer
70import GuiSessionViewer
71import GuiHandViewer
72import GuiTourHandViewer
73# import GuiOddsCalc
74# import GuiStove
76import SQL
77import Database
78import Configuration
79import Card
80import Exceptions
82# import api, app
83import cProfile
84import pstats
85import io
86import interlocks
87from Exceptions import FpdbError
89import sqlite3
91# these imports not required in this module, imported here to report version in About dialog
92import numpy
94cl_options = ".".join(sys.argv[1:])
95(options, argv) = Options.fpdb_options()
98numpy_version = numpy.__version__
101sqlite3_version = sqlite3.version
102sqlite_version = sqlite3.sqlite_version
105PROFILE_OUTPUT_DIR = os.path.join(os.path.expanduser("~"), "fpdb_profiles")
106os.makedirs(PROFILE_OUTPUT_DIR, exist_ok=True)
108profiler = cProfile.Profile()
109profiler.enable()
112Configuration.set_logfile("fpdb-log.txt")
114log = logging.getLogger("fpdb")
116try:
117 assert not hasattr(sys, "frozen") # We're surely not in a git repo if this fails
118 import subprocess
120 VERSION = subprocess.Popen(["git", "describe", "--tags", "--dirty"], stdout=subprocess.PIPE).communicate()[0]
121 VERSION = VERSION[:-1]
122except Exception:
123 VERSION = "3.0.0alpha"
126class fpdb(QMainWindow):
127 # def launch_ppt(self):
128 # path = os.getcwd()
129 # if os.name == "nt":
130 # pathcomp = f"{path}\pyfpdb\ppt\p2.jar"
131 # else:
132 # pathcomp = f"{path}/ppt/p2.jar"
133 # subprocess.call(["java", "-jar", pathcomp])
135 def add_and_display_tab(self, new_page, new_tab_name):
136 """adds a tab, namely creates the button and displays it and appends all the relevant arrays"""
137 for name in self.nb_tab_names: # todo: check this is valid
138 if name == new_tab_name:
139 self.display_tab(new_tab_name)
140 return # if tab already exists, just go to it
142 used_before = False
143 for i, name in enumerate(self.nb_tab_names):
144 if name == new_tab_name:
145 used_before = True
146 page = self.pages[i]
147 break
149 if not used_before:
150 page = new_page
151 self.pages.append(new_page)
152 self.nb_tab_names.append(new_tab_name)
154 index = self.nb.addTab(page, new_tab_name)
155 self.nb.setCurrentIndex(index)
157 def display_tab(self, new_tab_name):
158 """displays the indicated tab"""
159 tab_no = -1
160 for i, name in enumerate(self.nb_tab_names):
161 if new_tab_name == name:
162 tab_no = i
163 break
165 if tab_no < 0 or tab_no >= self.nb.count():
166 raise FpdbError(f"invalid tab_no {str(tab_no)}")
167 else:
168 self.nb.setCurrentIndex(tab_no)
170 def dia_about(self, widget, data=None):
171 QMessageBox.about(
172 self,
173 f"FPDB{str(VERSION)}",
174 "Copyright 2008-2023. See contributors.txt for details"
175 + "You are free to change, and distribute original or changed versions "
176 "of fpdb within the rules set out by the license"
177 + "https://github.com/jejellyroll-fr/fpdb-3"
178 + "\n"
179 + "Your config file is: "
180 + self.config.file,
181 )
182 return
184 def dia_advanced_preferences(self, widget, data=None):
185 # force reload of prefs from xml file - needed because HUD could
186 # have changed file contents
187 self.load_profile()
188 if GuiPrefs.GuiPrefs(self.config, self).exec_():
189 # save updated config
190 self.config.save()
191 self.reload_config()
193 def dia_database_stats(self, widget, data=None):
194 self.warning_box(
195 string=f"Number of Hands: {self.db.getHandCount()}\nNumber of Tourneys: {self.db.getTourneyCount()}\nNumber of TourneyTypes: {self.db.getTourneyTypeCount()}",
196 diatitle="Database Statistics",
197 )
199 # end def dia_database_stats
201 @staticmethod
202 def get_text(widget: QWidget):
203 """Return text of widget, depending on widget type"""
204 return widget.currentText() if isinstance(widget, QComboBox) else widget.text()
206 def dia_hud_preferences(self, widget, data=None):
207 dia = QDialog(self)
208 dia.setWindowTitle("Modifying Huds")
209 dia.resize(1200, 600)
210 label = QLabel("Please edit your huds.")
211 dia.setLayout(QVBoxLayout())
212 dia.layout().addWidget(label)
213 label2 = QLabel("Please select the game category for which you want to configure HUD stats:")
214 dia.layout().addWidget(label2)
215 self.comboGame = QComboBox()
217 huds_names = self.config.get_stat_sets()
218 for hud_name in huds_names:
219 self.comboGame.addItem(hud_name)
221 dia.layout().addWidget(self.comboGame)
222 self.comboGame.setCurrentIndex(1)
223 selected_hud_name = self.comboGame.currentText()
225 self.load_profile() # => self.[config, settings]
227 # HUD column will contain a button that shows favseat and HUD locations.
228 # TODO: Make it possible to load screenshot to arrange HUD windowlets.
230 self.table = QGridLayout()
231 self.table.setSpacing(0)
233 scrolling_frame = QScrollArea(dia)
234 dia.layout().addWidget(scrolling_frame)
235 scrolling_frame.setLayout(self.table)
237 nb_items = len(self.config.stat_sets[selected_hud_name].stats)
239 btns = QDialogButtonBox(QDialogButtonBox.Save | QDialogButtonBox.Cancel, dia)
240 btns.accepted.connect(dia.accept)
241 btns.rejected.connect(dia.reject)
242 dia.layout().addWidget(btns)
243 self.comboGame.currentIndexChanged.connect(self.index_changed)
245 # Launch Dialog
246 response = dia.exec_()
248 # Treat dialog closed event
249 if self.comboGame.currentIndexChanged and response:
250 selected_hud_name = self.comboGame.currentText()
251 # User clicked on "Save"
252 for y in range(nb_items):
253 self.config.edit_hud(
254 selected_hud_name,
255 self.get_text(self.stat_position_list[y]),
256 self.get_text(self.stat_name_list[y]),
257 self.get_text(self.click_list[y]),
258 self.get_text(self.hudcolor_list[y]),
259 self.get_text(self.hudprefix_list[y]),
260 self.get_text(self.hudsuffix_list[y]),
261 self.get_text(self.popup_list[y]),
262 self.get_text(self.stat_hicolor_list[y]),
263 self.get_text(self.stat_hith_list[y]),
264 self.get_text(self.stat_locolor_list[y]),
265 self.get_text(self.stat_loth_list[y]),
266 self.get_text(self.tip_list[y]),
267 )
269 self.config.save()
270 self.reload_config()
272 def index_changed(self, index):
273 # Called when user changes currently selected HUD
274 log.info("start index_changed")
275 log.debug(f"index = {index}")
276 log.debug(f"self.config = {self.config}")
277 log.debug(f"self.config.stat_sets = {self.config.stat_sets}")
278 selected_hud_name = self.comboGame.currentText()
279 log.debug(f"selected_hud_name = {selected_hud_name}")
280 for i in reversed(range(self.table.count())):
281 self.table.itemAt(i).widget().deleteLater()
283 self.table.setSpacing(0)
285 column_headers = [
286 "coordonate",
287 "stats name",
288 "click",
289 "hudcolor",
290 "hudprefix",
291 "hudsuffix",
292 "popup",
293 "stat_hicolor",
294 "stat_hith",
295 "stat_locolor",
296 "stat_loth",
297 "tip",
298 ] # todo ("HUD")
300 for header_number in range(0, len(column_headers)):
301 label = QLabel(column_headers[header_number])
302 label.setAlignment(Qt.AlignCenter)
303 self.table.addWidget(label, 0, header_number)
305 # Init lists that will contains QWidgets for each column in table ("stat_position_list" will contain the positions (ex: ["(0,1)", ...]))
306 (
307 self.stat_position_list,
308 self.stat_name_list,
309 self.click_list,
310 self.hudcolor_list,
311 self.hudprefix_list,
312 self.hudsuffix_list,
313 self.popup_list,
314 self.stat_hicolor_list,
315 self.stat_hith_list,
316 self.stat_locolor_list,
317 self.stat_loth_list,
318 self.tip_list,
319 ) = [], [], [], [], [], [], [], [], [], [], [], []
321 self.load_profile()
322 hud_stats = self.config.stat_sets[selected_hud_name] # Configuration.Stat_sets object
323 y_pos = 1
324 for position in hud_stats.stats.keys():
325 # Column 1: stat position
326 stat2 = QLabel()
327 stat2.setText(str(position))
328 self.table.addWidget(stat2, y_pos, 0)
329 self.stat_position_list.append(stat2)
331 # Column 2: select stat name (between available stats)
332 # TODO: don't load all stats on each loop !
333 if os.name == "nt":
334 icoPath = os.path.dirname(__file__)
335 icoPath = f"{icoPath}\\"
336 else:
337 icoPath = "icons/"
338 stat3 = QComboBox()
339 stats_cash = self.config.get_gui_cash_stat_params() # Available stats for cash game
340 for x in range(0, len(stats_cash)):
341 # print(stats_cash[x][0])
342 stat3.addItem(QIcon(f"{icoPath}Letter-C-icon.png"), stats_cash[x][0])
343 stats_tour = self.config.get_gui_tour_stat_params() # Available stats for tournament
344 for x in range(0, len(stats_tour)):
345 # print(stats_tour[x][0])
346 stat3.addItem(QIcon(f"{icoPath}Letter-T-icon.png"), stats_tour[x][0])
347 stat3.setCurrentText(str(hud_stats.stats[position].stat_name))
348 self.table.addWidget(stat3, y_pos, 1)
349 self.stat_name_list.append(stat3)
351 # Column 3: "click"
352 stat4 = QLineEdit()
353 stat4.setText(str(hud_stats.stats[position].click))
354 self.table.addWidget(stat4, y_pos, 2)
355 self.click_list.append(stat4)
357 # Column 4: "hudcolor"
358 stat5 = QLineEdit()
359 stat5.setText(str(hud_stats.stats[position].hudcolor))
360 self.table.addWidget(stat5, y_pos, 3)
361 self.hudcolor_list.append(stat5)
363 # Column 5: "hudprefix"
364 stat6 = QLineEdit()
365 stat6.setText(str(hud_stats.stats[position].hudprefix))
366 self.table.addWidget(stat6, y_pos, 4)
367 self.hudprefix_list.append(stat6)
369 # Column 6: "hudsuffix"
370 stat7 = QLineEdit()
371 stat7.setText(str(hud_stats.stats[position].hudsuffix))
372 self.table.addWidget(stat7, y_pos, 5)
373 self.hudsuffix_list.append(stat7)
375 # Column 7: "popup"
376 stat8 = QComboBox()
377 for popup in self.config.popup_windows.keys():
378 stat8.addItem(popup)
379 stat8.setCurrentText(str(hud_stats.stats[position].popup))
380 self.table.addWidget(stat8, y_pos, 6)
381 self.popup_list.append(stat8)
383 # Column 8: "stat_hicolor"
384 stat9 = QLineEdit()
385 stat9.setText(str(hud_stats.stats[position].stat_hicolor))
386 self.table.addWidget(stat9, y_pos, 7)
387 self.stat_hicolor_list.append(stat9)
389 # Column 9: "stat_hith"
390 stat10 = QLineEdit()
391 stat10.setText(str(hud_stats.stats[position].stat_hith))
392 self.table.addWidget(stat10, y_pos, 8)
393 self.stat_hith_list.append(stat10)
395 # Column 10: "stat_locolor"
396 stat11 = QLineEdit()
397 stat11.setText(str(hud_stats.stats[position].stat_locolor))
398 self.table.addWidget(stat11, y_pos, 9)
399 self.stat_locolor_list.append(stat11)
401 # Column 11: "stat_loth"
402 stat12 = QLineEdit()
403 stat12.setText(str(hud_stats.stats[position].stat_loth))
404 self.table.addWidget(stat12, y_pos, 10)
405 self.stat_loth_list.append(stat12)
407 # Column 12: "tip"
408 stat13 = QLineEdit()
409 stat13.setText(str(hud_stats.stats[position].tip))
410 self.table.addWidget(stat13, y_pos, 11)
411 self.tip_list.append(stat13)
412 # if available_site_names[site_number] in detector.supportedSites:
413 # pass
415 y_pos += 1
417 def dia_import_filters(self, checkState):
418 dia = QDialog()
419 dia.setWindowTitle("Skip these games when importing")
420 dia.setLayout(QVBoxLayout())
421 checkboxes = {}
422 filters = self.config.get_import_parameters()["importFilters"]
423 for game in Card.games:
424 checkboxes[game] = QCheckBox(game)
425 dia.layout().addWidget(checkboxes[game])
426 if game in filters:
427 checkboxes[game].setChecked(True)
428 btns = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
429 dia.layout().addWidget(btns)
430 btns.accepted.connect(dia.accept)
431 btns.rejected.connect(dia.reject)
432 if dia.exec_():
433 filterGames = []
434 for game, cb in list(checkboxes.items()):
435 if cb.isChecked():
436 filterGames.append(game)
437 self.config.editImportFilters(",".join(filterGames))
438 self.config.save()
440 def dia_dump_db(self, widget, data=None):
441 filename = "database-dump.sql"
442 result = self.db.dumpDatabase()
444 with open(filename, "w") as dumpFile:
445 dumpFile.write(result)
447 # end def dia_database_stats
449 def dia_recreate_tables(self, widget, data=None):
450 """Dialogue that asks user to confirm that he wants to delete and recreate the tables"""
451 if self.obtain_global_lock("fpdb.dia_recreate_tables"): # returns true if successful
452 dia_confirm = QMessageBox(
453 QMessageBox.Warning,
454 "Wipe DB",
455 "Confirm deleting and recreating tables",
456 QMessageBox.Yes | QMessageBox.No,
457 self,
458 )
459 diastring = (
460 f"Please confirm that you want to (re-)create the tables. If there already are tables in"
461 f" the database {self.db.database} on {self.db.host}"
462 f" they will be deleted and you will have to re-import your histories.\nThis may take a while."
463 )
465 dia_confirm.setInformativeText(diastring) # todo: make above string with bold for db, host and deleted
466 response = dia_confirm.exec_()
468 if response == QMessageBox.Yes:
469 self.db.recreate_tables()
470 # find any guibulkimport/guiautoimport windows and clear cache:
471 for t in self.threads:
472 if isinstance(t, GuiBulkImport.GuiBulkImport) or isinstance(t, GuiAutoImport.GuiAutoImport):
473 t.importer.database.resetCache()
474 self.release_global_lock()
475 else:
476 self.release_global_lock()
477 log.info("User cancelled recreating tables")
478 else:
479 self.warning_box(
480 "Cannot open Database Maintenance window because other"
481 " windows have been opened. Re-start fpdb to use this option."
482 )
484 def dia_recreate_hudcache(self, widget, data=None):
485 if self.obtain_global_lock("dia_recreate_hudcache"):
486 self.dia_confirm = QDialog()
487 self.dia_confirm.setWindowTitle("Confirm recreating HUD cache")
488 self.dia_confirm.setLayout(QVBoxLayout())
489 self.dia_confirm.layout().addWidget(QLabel("Please confirm that you want to re-create the HUD cache."))
491 hb1 = QHBoxLayout()
492 self.h_start_date = QDateEdit(QDate.fromString(self.db.get_hero_hudcache_start(), "yyyy-MM-dd"))
493 lbl = QLabel(" Hero's cache starts: ")
494 btn = QPushButton("Cal")
495 btn.clicked.connect(partial(self.__calendar_dialog, entry=self.h_start_date))
497 hb1.addWidget(lbl)
498 hb1.addStretch()
499 hb1.addWidget(self.h_start_date)
500 hb1.addWidget(btn)
501 self.dia_confirm.layout().addLayout(hb1)
503 hb2 = QHBoxLayout()
504 self.start_date = QDateEdit(QDate.fromString(self.db.get_hero_hudcache_start(), "yyyy-MM-dd"))
505 lbl = QLabel(" Villains' cache starts: ")
506 btn = QPushButton("Cal")
507 btn.clicked.connect(partial(self.__calendar_dialog, entry=self.start_date))
509 hb2.addWidget(lbl)
510 hb2.addStretch()
511 hb2.addWidget(self.start_date)
512 hb2.addWidget(btn)
513 self.dia_confirm.layout().addLayout(hb2)
515 btns = QDialogButtonBox(QDialogButtonBox.Yes | QDialogButtonBox.No)
516 self.dia_confirm.layout().addWidget(btns)
517 btns.accepted.connect(self.dia_confirm.accept)
518 btns.rejected.connect(self.dia_confirm.reject)
520 response = self.dia_confirm.exec_()
521 if response:
522 log.info("Rebuilding HUD Cache ...")
524 self.db.rebuild_cache(
525 self.h_start_date.date().toString("yyyy-MM-dd"), self.start_date.date().toString("yyyy-MM-dd")
526 )
527 else:
528 log.info("User cancelled rebuilding hud cache")
530 self.release_global_lock()
531 else:
532 self.warning_box(
533 "Cannot open Database Maintenance window because other windows have been opened. "
534 "Re-start fpdb to use this option."
535 )
537 def dia_rebuild_indexes(self, widget, data=None):
538 if self.obtain_global_lock("dia_rebuild_indexes"):
539 self.dia_confirm = QMessageBox(
540 QMessageBox.Warning,
541 "Rebuild DB",
542 "Confirm rebuilding database indexes",
543 QMessageBox.Yes | QMessageBox.No,
544 self,
545 )
546 diastring = "Please confirm that you want to rebuild the database indexes."
547 self.dia_confirm.setInformativeText(diastring)
549 response = self.dia_confirm.exec_()
550 if response == QMessageBox.Yes:
551 log.info(" Rebuilding Indexes ... ")
552 self.db.rebuild_indexes()
554 log.info(" Cleaning Database ... ")
555 self.db.vacuumDB()
557 log.info(" Analyzing Database ... ")
558 self.db.analyzeDB()
559 else:
560 log.info("User cancelled rebuilding db indexes")
562 self.release_global_lock()
563 else:
564 self.warning_box(
565 "Cannot open Database Maintenance window because"
566 " other windows have been opened. Re-start fpdb to use this option."
567 )
569 def dia_logs(self, widget, data=None):
570 """opens the log viewer window"""
571 # remove members from self.threads if close messages received
572 self.process_close_messages()
574 viewer = None
575 for i, t in enumerate(self.threads):
576 if str(t.__class__) == "GuiLogView.GuiLogView":
577 viewer = t
578 break
580 if viewer is None:
581 # print "creating new log viewer"
582 new_thread = GuiLogView.GuiLogView(self.config, self.window, self.closeq)
583 self.threads.append(new_thread)
584 else:
585 # print "showing existing log viewer"
586 viewer.get_dialog().present()
588 # if lock_set:
589 # self.release_global_lock()
591 def dia_site_preferences_seat(self, widget, data=None):
592 dia = QDialog(self)
593 dia.setWindowTitle("Seat Preferences")
594 dia.resize(1200, 600)
595 label = QLabel("Please select your prefered seat.")
596 dia.setLayout(QVBoxLayout())
597 dia.layout().addWidget(label)
599 self.load_profile()
600 site_names = self.config.site_ids
601 available_site_names = []
602 for site_name in site_names:
603 try:
604 self.config.supported_sites[site_name].enabled
605 available_site_names.append(site_name)
606 except KeyError:
607 pass
609 column_headers = [
610 "Site",
611 "2 players:\nbetween 0 & 2",
612 "3 players:\nbetween 0 & 3 ",
613 "4 players:\nbetween 0 & 4",
614 "5 players:\nbetween 0 & 5",
615 "6 players:\nbetween 0 & 6",
616 "7 players:\nbetween 0 & 7",
617 "8 players:\nbetween 0 & 8",
618 "9 players:\nbetween 0 & 9",
619 "10 players:\nbetween 0 & 10",
620 ] # todo ("HUD")
621 # HUD column will contain a button that shows favseat and HUD locations.
622 # Make it possible to load screenshot to arrange HUD windowlets.
624 table = QGridLayout()
625 table.setSpacing(0)
627 scrolling_frame = QScrollArea(dia)
628 dia.layout().addWidget(scrolling_frame)
629 scrolling_frame.setLayout(table)
631 for header_number in range(0, len(column_headers)):
632 label = QLabel(column_headers[header_number])
633 label.setAlignment(Qt.AlignCenter)
634 table.addWidget(label, 0, header_number)
636 # history_paths = []
637 check_buttons = []
638 # screen_names = []
639 seat2_dict, seat3_dict, seat4_dict, seat5_dict, seat6_dict, seat7_dict, seat8_dict, seat9_dict, seat10_dict = (
640 [],
641 [],
642 [],
643 [],
644 [],
645 [],
646 [],
647 [],
648 [],
649 )
650 # summary_paths = []
651 detector = DetectInstalledSites.DetectInstalledSites()
653 y_pos = 1
654 for site_number in range(0, len(available_site_names)):
655 check_button = QCheckBox(available_site_names[site_number])
656 check_button.setChecked(self.config.supported_sites[available_site_names[site_number]].enabled)
657 table.addWidget(check_button, y_pos, 0)
658 check_buttons.append(check_button)
659 # hud_seat = self.config.supported_sites[available_site_names[site_number]].fav_seat[2]
661 # print('hud seat ps:', type(hud_seat), hud_seat)
662 seat2 = QLineEdit()
664 seat2.setText(str(self.config.supported_sites[available_site_names[site_number]].fav_seat[2]))
665 table.addWidget(seat2, y_pos, 1)
666 seat2_dict.append(seat2)
667 seat2.textChanged.connect(partial(self.autoenableSite, checkbox=check_buttons[site_number]))
669 seat3 = QLineEdit()
670 seat3.setText(str(self.config.supported_sites[available_site_names[site_number]].fav_seat[3]))
671 table.addWidget(seat3, y_pos, 2)
672 seat3_dict.append(seat3)
674 seat4 = QLineEdit()
675 seat4.setText(str(self.config.supported_sites[available_site_names[site_number]].fav_seat[4]))
676 table.addWidget(seat4, y_pos, 3)
677 seat4_dict.append(seat4)
679 seat5 = QLineEdit()
680 seat5.setText(str(self.config.supported_sites[available_site_names[site_number]].fav_seat[5]))
681 table.addWidget(seat5, y_pos, 4)
682 seat5_dict.append(seat5)
684 seat6 = QLineEdit()
685 seat6.setText(str(self.config.supported_sites[available_site_names[site_number]].fav_seat[6]))
686 table.addWidget(seat6, y_pos, 5)
687 seat6_dict.append(seat6)
689 seat7 = QLineEdit()
690 seat7.setText(str(self.config.supported_sites[available_site_names[site_number]].fav_seat[7]))
691 table.addWidget(seat7, y_pos, 6)
692 seat7_dict.append(seat7)
694 seat8 = QLineEdit()
695 seat8.setText(str(self.config.supported_sites[available_site_names[site_number]].fav_seat[8]))
696 table.addWidget(seat8, y_pos, 7)
697 seat8_dict.append(seat8)
699 seat9 = QLineEdit()
700 seat9.setText(str(self.config.supported_sites[available_site_names[site_number]].fav_seat[9]))
701 table.addWidget(seat9, y_pos, 8)
702 seat9_dict.append(seat9)
704 seat10 = QLineEdit()
705 seat10.setText(str(self.config.supported_sites[available_site_names[site_number]].fav_seat[10]))
706 table.addWidget(seat10, y_pos, 9)
707 seat10_dict.append(seat10)
709 if available_site_names[site_number] in detector.supportedSites:
710 pass
712 y_pos += 1
714 btns = QDialogButtonBox(QDialogButtonBox.Save | QDialogButtonBox.Cancel, dia)
715 btns.accepted.connect(dia.accept)
716 btns.rejected.connect(dia.reject)
717 dia.layout().addWidget(btns)
719 response = dia.exec_()
720 if response:
721 for site_number in range(0, len(available_site_names)):
722 # print "site %s enabled=%s name=%s" % (available_site_names[site_number],
723 # check_buttons[site_number].get_active(), screen_names[site_number].get_text(),
724 # history_paths[site_number].get_text())
725 self.config.edit_fav_seat(
726 available_site_names[site_number],
727 str(check_buttons[site_number].isChecked()),
728 seat2_dict[site_number].text(),
729 seat3_dict[site_number].text(),
730 seat4_dict[site_number].text(),
731 seat5_dict[site_number].text(),
732 seat6_dict[site_number].text(),
733 seat7_dict[site_number].text(),
734 seat8_dict[site_number].text(),
735 seat9_dict[site_number].text(),
736 seat10_dict[site_number].text(),
737 )
739 self.config.save()
740 self.reload_config()
742 def dia_site_preferences(self, widget, data=None):
743 dia = QDialog(self)
744 dia.setWindowTitle("Site Preferences")
745 dia.resize(1200, 600)
746 label = QLabel("Please select which sites you play on and enter your usernames.")
747 dia.setLayout(QVBoxLayout())
748 dia.layout().addWidget(label)
750 self.load_profile()
751 site_names = self.config.site_ids
752 available_site_names = []
753 for site_name in site_names:
754 try:
755 self.config.supported_sites[site_name].enabled
756 available_site_names.append(site_name)
757 except KeyError:
758 pass
760 column_headers = [
761 "Site",
762 "Detect",
763 "Screen Name",
764 "Hand History Path",
765 "",
766 "Tournament Summary Path",
767 "",
768 "Favorite seat",
769 ] # todo ("HUD")
770 # HUD column will contain a button that shows favseat and HUD locations.
771 # Make it possible to load screenshot to arrange HUD windowlets.
773 table = QGridLayout()
774 table.setSpacing(0)
776 scrolling_frame = QScrollArea(dia)
777 dia.layout().addWidget(scrolling_frame)
778 scrolling_frame.setLayout(table)
780 for header_number in range(0, len(column_headers)):
781 label = QLabel(column_headers[header_number])
782 label.setAlignment(Qt.AlignCenter)
783 table.addWidget(label, 0, header_number)
785 check_buttons = []
786 screen_names = []
787 history_paths = []
788 summary_paths = []
789 detector = DetectInstalledSites.DetectInstalledSites()
791 y_pos = 1
792 for site_number in range(0, len(available_site_names)):
793 check_button = QCheckBox(available_site_names[site_number])
794 check_button.setChecked(self.config.supported_sites[available_site_names[site_number]].enabled)
795 table.addWidget(check_button, y_pos, 0)
796 check_buttons.append(check_button)
798 hero = QLineEdit()
799 hero.setText(self.config.supported_sites[available_site_names[site_number]].screen_name)
800 table.addWidget(hero, y_pos, 2)
801 screen_names.append(hero)
802 hero.textChanged.connect(partial(self.autoenableSite, checkbox=check_buttons[site_number]))
804 entry = QLineEdit()
805 entry.setText(self.config.supported_sites[available_site_names[site_number]].HH_path)
806 table.addWidget(entry, y_pos, 3)
807 history_paths.append(entry)
809 choose1 = QPushButton("Browse")
810 table.addWidget(choose1, y_pos, 4)
811 choose1.clicked.connect(partial(self.browseClicked, parent=dia, path=history_paths[site_number]))
813 entry = QLineEdit()
814 entry.setText(self.config.supported_sites[available_site_names[site_number]].TS_path)
815 table.addWidget(entry, y_pos, 5)
816 summary_paths.append(entry)
818 choose2 = QPushButton("Browse")
819 table.addWidget(choose2, y_pos, 6)
820 choose2.clicked.connect(partial(self.browseClicked, parent=dia, path=summary_paths[site_number]))
822 if available_site_names[site_number] in detector.supportedSites:
823 button = QPushButton("Detect")
824 table.addWidget(button, y_pos, 1)
825 button.clicked.connect(
826 partial(
827 self.detect_clicked,
828 data=(
829 detector,
830 available_site_names[site_number],
831 screen_names[site_number],
832 history_paths[site_number],
833 summary_paths[site_number],
834 ),
835 )
836 )
837 y_pos += 1
839 btns = QDialogButtonBox(QDialogButtonBox.Save | QDialogButtonBox.Cancel, dia)
840 btns.accepted.connect(dia.accept)
841 btns.rejected.connect(dia.reject)
842 dia.layout().addWidget(btns)
844 response = dia.exec_()
845 if response:
846 for site_number in range(0, len(available_site_names)):
847 # print "site %s enabled=%s name=%s" % (available_site_names[site_number],
848 # check_buttons[site_number].get_active(), screen_names[site_number].get_text(),
849 # history_paths[site_number].get_text())
850 self.config.edit_site(
851 available_site_names[site_number],
852 str(check_buttons[site_number].isChecked()),
853 screen_names[site_number].text(),
854 history_paths[site_number].text(),
855 summary_paths[site_number].text(),
856 )
858 self.config.save()
859 self.reload_config()
861 def autoenableSite(self, text, checkbox):
862 # autoactivate site if something gets typed in the screename field
863 checkbox.setChecked(True)
865 def browseClicked(self, widget, parent, path):
866 """runs when user clicks one of the browse buttons for the TS folder"""
868 newpath = QFileDialog.getExistingDirectory(
869 parent, "Please choose the path that you want to Auto Import", path.text()
870 )
871 if newpath:
872 path.setText(newpath)
874 def detect_clicked(self, widget, data):
875 detector = data[0]
876 site_name = data[1]
877 entry_screen_name = data[2]
878 entry_history_path = data[3]
879 entry_summary_path = data[4]
880 if detector.sitestatusdict[site_name]["detected"]:
881 entry_screen_name.setText(detector.sitestatusdict[site_name]["heroname"])
882 entry_history_path.setText(detector.sitestatusdict[site_name]["hhpath"])
883 if detector.sitestatusdict[site_name]["tspath"]:
884 entry_summary_path.setText(detector.sitestatusdict[site_name]["tspath"])
886 def reload_config(self):
887 if len(self.nb_tab_names) == 1:
888 # only main tab open, reload profile
889 self.load_profile()
890 self.warning_box(
891 "Configuration settings have been updated," " Fpdb needs to be restarted now\n\nClick OK to close Fpdb"
892 )
893 sys.exit()
894 else:
895 self.warning_box(
896 "Updated preferences have not been loaded because windows are open." " Re-start fpdb to load them."
897 )
899 def process_close_messages(self):
900 # check for close messages
901 try:
902 while True:
903 name = self.closeq.get(False)
904 for i, t in enumerate(self.threads):
905 if str(t.__class__) == str(name):
906 # thread has ended so remove from list:
907 del self.threads[i]
908 break
909 except queue.Empty:
910 # no close messages on queue, do nothing
911 pass
913 def __calendar_dialog(self, widget, entry):
914 d = QDialog(self.dia_confirm)
915 d.setWindowTitle("Pick a date")
917 vb = QVBoxLayout()
918 d.setLayout(vb)
919 cal = QCalendarWidget()
920 vb.addWidget(cal)
922 btn = QPushButton("Done")
923 btn.clicked.connect(partial(self.__get_date, calendar=cal, entry=entry, win=d))
925 vb.addWidget(btn)
927 d.exec_()
928 return
930 def createMenuBar(self):
931 mb = self.menuBar()
932 configMenu = mb.addMenu("Configure")
933 importMenu = mb.addMenu("Import")
934 hudMenu = mb.addMenu("HUD")
935 cashMenu = mb.addMenu("Cash")
936 tournamentMenu = mb.addMenu("Tournament")
937 maintenanceMenu = mb.addMenu("Maintenance")
938 # toolsMenu = mb.addMenu('Tools')
939 helpMenu = mb.addMenu("Help")
940 themeMenu = mb.addMenu("Themes")
942 # Create actions
943 def makeAction(name, callback, shortcut=None, tip=None):
944 action = QAction(name, self)
945 if shortcut:
946 action.setShortcut(shortcut)
947 if tip:
948 action.setToolTip(tip)
949 action.triggered.connect(callback)
950 return action
952 configMenu.addAction(makeAction("Site Settings", self.dia_site_preferences))
953 configMenu.addAction(makeAction("Seat Settings", self.dia_site_preferences_seat))
954 configMenu.addAction(makeAction("Hud Settings", self.dia_hud_preferences))
955 configMenu.addAction(makeAction("Adv Preferences", self.dia_advanced_preferences, tip="Edit your preferences"))
956 configMenu.addAction(makeAction("Import filters", self.dia_import_filters))
957 configMenu.addSeparator()
958 configMenu.addAction(makeAction("Close Fpdb", self.quit, "Ctrl+Q", "Quit the Program"))
960 importMenu.addAction(makeAction("Bulk Import", self.tab_bulk_import, "Ctrl+B"))
961 hudMenu.addAction(makeAction("HUD and Auto Import", self.tab_auto_import, "Ctrl+A"))
962 cashMenu.addAction(makeAction("Graphs", self.tabGraphViewer, "Ctrl+G"))
963 cashMenu.addAction(makeAction("Ring Player Stats", self.tab_ring_player_stats, "Ctrl+P"))
964 cashMenu.addAction(makeAction("Hand Viewer", self.tab_hand_viewer))
965 cashMenu.addAction(makeAction("Session Stats", self.tab_session_stats, "Ctrl+S"))
966 tournamentMenu.addAction(makeAction("Tourney Graphs", self.tabTourneyGraphViewer))
967 tournamentMenu.addAction(makeAction("Tourney Stats", self.tab_tourney_player_stats, "Ctrl+T"))
968 tournamentMenu.addAction(makeAction("Tourney Hand Viewer", self.tab_tourney_viewer_stats))
969 maintenanceMenu.addAction(makeAction("Statistics", self.dia_database_stats, "View Database Statistics"))
970 maintenanceMenu.addAction(makeAction("Create or Recreate Tables", self.dia_recreate_tables))
971 maintenanceMenu.addAction(makeAction("Rebuild HUD Cache", self.dia_recreate_hudcache))
972 maintenanceMenu.addAction(makeAction("Rebuild DB Indexes", self.dia_rebuild_indexes))
973 maintenanceMenu.addAction(makeAction("Dump Database to Textfile (takes ALOT of time)", self.dia_dump_db))
975 # toolsMenu.addAction(makeAction('PokerProTools', self.launch_ppt))
976 helpMenu.addAction(makeAction("Log Messages", self.dia_logs, "Log and Debug Messages"))
977 helpMenu.addAction(makeAction("Help Tab", self.tab_main_help))
978 helpMenu.addSeparator()
979 helpMenu.addAction(makeAction("Infos", self.dia_about, "About the program"))
981 themes = [
982 "dark_purple.xml",
983 "dark_teal.xml",
984 "dark_blue.xml",
985 "dark_cyan.xml",
986 "dark_pink.xml",
987 "dark_red.xml",
988 "dark_lime.xml",
989 "light_purple.xml",
990 "light_teal.xml",
991 "light_blue.xml",
992 "light_cyan.xml",
993 "light_pink.xml",
994 "light_red.xml",
995 "light_lime.xml",
996 ]
998 for theme in themes:
999 themeMenu.addAction(QAction(theme, self, triggered=partial(self.change_theme, theme)))
1001 def load_profile(self, create_db=False):
1002 """Loads profile from the provided path name.
1003 Set:
1004 - self.settings
1005 - self.config
1006 - self.db
1007 """
1008 self.config = Configuration.Config(file=options.config, dbname=options.dbname)
1009 if self.config.file_error:
1010 self.warning_box(
1011 f"There is an error in your config file" f" {self.config.file}:\n{str(self.config.file_error)}",
1012 diatitle="CONFIG FILE ERROR",
1013 )
1014 sys.exit()
1016 log.info(f"Logfile is {os.path.join(self.config.dir_log, self.config.log_file)}")
1017 log.info(f"load profiles {self.config.example_copy}")
1018 log.info(f"{self.display_config_created_dialogue}")
1019 log.info(f"{self.config.wrongConfigVersion}")
1020 if self.config.example_copy or self.display_config_created_dialogue:
1021 self.info_box(
1022 "Config file",
1023 [
1024 "Config file has been created at " + self.config.file + ".",
1025 "Enter your screen_name and hand history path in the Site Preferences window"
1026 " (Main menu) before trying to import hands.",
1027 ],
1028 )
1030 self.display_config_created_dialogue = False
1031 elif self.config.wrongConfigVersion:
1032 diaConfigVersionWarning = QDialog()
1033 diaConfigVersionWarning.setWindowTitle("Strong Warning - Local configuration out of date")
1034 diaConfigVersionWarning.setLayout(QVBoxLayout())
1035 label = QLabel(["\nYour local configuration file needs to be updated."])
1036 diaConfigVersionWarning.layout().addWidget(label)
1037 label = QLabel(
1038 [
1039 "\nYour local configuration file needs to be updated.",
1040 "This error is not necessarily fatal but it is strongly recommended that you update the configuration.",
1041 ]
1042 )
1044 diaConfigVersionWarning.layout().addWidget(label)
1045 label = QLabel(
1046 [
1047 "To create a new configuration, see:",
1048 "fpdb.sourceforge.net/apps/mediawiki/fpdb/index.php?title=Reset_Configuration",
1049 ]
1050 )
1052 label.setTextInteractionFlags(Qt.TextSelectableByMouse)
1053 diaConfigVersionWarning.layout().addWidget(label)
1054 label = QLabel(
1055 [
1056 "A new configuration will destroy all personal settings"
1057 " (hud layout, site folders, screennames, favourite seats).\n"
1058 ]
1059 )
1061 diaConfigVersionWarning.layout().addWidget(label)
1063 label = QLabel("To keep existing personal settings, you must edit the local file.")
1064 diaConfigVersionWarning.layout().addWidget(label)
1066 label = QLabel("See the release note for information about the edits needed")
1067 diaConfigVersionWarning.layout().addWidget(label)
1069 btns = QDialogButtonBox(QDialogButtonBox.Ok)
1070 btns.accepted.connect(diaConfigVersionWarning.accept)
1071 diaConfigVersionWarning.layout().addWidget(btns)
1073 diaConfigVersionWarning.exec_()
1074 self.config.wrongConfigVersion = False
1076 self.settings = {}
1077 self.settings["global_lock"] = self.lock
1078 if os.sep == "/":
1079 self.settings["os"] = "linuxmac"
1080 else:
1081 self.settings["os"] = "windows"
1083 self.settings.update({"cl_options": cl_options})
1084 self.settings.update(self.config.get_db_parameters())
1085 self.settings.update(self.config.get_import_parameters())
1086 self.settings.update(self.config.get_default_paths())
1088 if self.db is not None and self.db.is_connected():
1089 self.db.disconnect()
1091 self.sql = SQL.Sql(db_server=self.settings["db-server"])
1092 err_msg = None
1093 try:
1094 self.db = Database.Database(self.config, sql=self.sql)
1095 if self.db.get_backend_name() == "SQLite":
1096 # tell sqlite users where the db file is
1097 log.info(f"Connected to SQLite: {self.db.db_path}")
1098 except Exceptions.FpdbMySQLAccessDenied:
1099 err_msg = "MySQL Server reports: Access denied. Are your permissions set correctly?"
1100 except Exceptions.FpdbMySQLNoDatabase:
1101 err_msg = (
1102 "MySQL client reports: 2002 or 2003 error."
1103 " Unable to connect - Please check that the MySQL service has been started."
1104 )
1106 except Exceptions.FpdbPostgresqlAccessDenied:
1107 err_msg = "PostgreSQL Server reports: Access denied. Are your permissions set correctly?"
1108 except Exceptions.FpdbPostgresqlNoDatabase:
1109 err_msg = (
1110 "PostgreSQL client reports: Unable to connect -"
1111 "Please check that the PostgreSQL service has been started."
1112 )
1113 if err_msg is not None:
1114 self.db = None
1115 self.warning_box(err_msg)
1116 if self.db is not None and not self.db.is_connected():
1117 self.db = None
1119 if self.db is not None and self.db.wrongDbVersion:
1120 diaDbVersionWarning = QMessageBox(
1121 QMessageBox.Warning,
1122 "Strong Warning - Invalid database version",
1123 "An invalid DB version or missing tables have been detected.",
1124 QMessageBox.Ok,
1125 self,
1126 )
1127 diaDbVersionWarning.setInformativeText(
1128 "This error is not necessarily fatal but it is strongly"
1129 " recommended that you recreate the tables by using the Database menu."
1130 "Not doing this will likely lead to misbehaviour including fpdb crashes, corrupt data etc."
1131 )
1133 diaDbVersionWarning.exec_()
1134 if self.db is not None and self.db.is_connected():
1135 self.statusBar().showMessage(
1136 f"Status: Connected to {self.db.get_backend_name()}"
1137 f" database named {self.db.database} on host {self.db.host}"
1138 )
1140 # rollback to make sure any locks are cleared:
1141 self.db.rollback()
1143 # If the db-version is out of date, don't validate the config
1144 # otherwise the end user gets bombarded with false messages
1145 # about every site not existing
1146 if hasattr(self.db, "wrongDbVersion"):
1147 if not self.db.wrongDbVersion:
1148 self.validate_config()
1150 def obtain_global_lock(self, source):
1151 ret = self.lock.acquire(source=source) # will return false if lock is already held
1152 if ret:
1153 log.info(f"Global lock taken by {source}")
1154 self.lockTakenBy = source
1155 else:
1156 log.info(f"Failed to get global lock, it is currently held by {source}")
1157 return ret
1158 # need to release it later:
1159 # self.lock.release()
1161 def quit(self, widget, data=None):
1162 # TODO: can we get some / all of the stuff done in this function to execute on any kind of abort?
1163 # FIXME get two "quitting normally" messages, following the addition of the self.window.destroy() call
1164 # ... because self.window.destroy() leads to self.destroy() which calls this!
1165 if not self.quitting:
1166 log.info("Quitting normally")
1167 self.quitting = True
1168 # TODO: check if current settings differ from profile, if so offer to save or abort
1170 if self.db is not None:
1171 if self.db.backend == self.db.MYSQL_INNODB:
1172 try:
1173 import _mysql_exceptions
1175 if self.db is not None and self.db.is_connected():
1176 self.db.disconnect()
1177 except _mysql_exceptions.OperationalError: # oh, damn, we're already disconnected
1178 pass
1179 else:
1180 if self.db is not None and self.db.is_connected():
1181 self.db.disconnect()
1182 else:
1183 pass
1184 # self.statusIcon.set_visible(False)
1185 QCoreApplication.quit()
1187 def release_global_lock(self):
1188 self.lock.release()
1189 self.lockTakenBy = None
1190 log.info("Global lock released.")
1192 def tab_auto_import(self, widget, data=None):
1193 """opens the auto import tab"""
1194 new_aimp_thread = GuiAutoImport.GuiAutoImport(self.settings, self.config, self.sql, self)
1195 self.threads.append(new_aimp_thread)
1196 self.add_and_display_tab(new_aimp_thread, "HUD")
1197 if options.autoimport:
1198 new_aimp_thread.startClicked(new_aimp_thread.startButton, "autostart")
1199 options.autoimport = False
1201 def tab_bulk_import(self, widget, data=None):
1202 """opens a tab for bulk importing"""
1203 new_import_thread = GuiBulkImport.GuiBulkImport(self.settings, self.config, self.sql, self)
1204 self.threads.append(new_import_thread)
1205 self.add_and_display_tab(new_import_thread, "Bulk Import")
1207 # def tab_tourney_import(self, widget, data=None):
1208 # """opens a tab for bulk importing tournament summaries"""
1209 # new_import_thread = GuiTourneyImport.GuiTourneyImport(self.settings, self.config, self.sql, self.window)
1210 # self.threads.append(new_import_thread)
1211 # bulk_tab = new_import_thread.get_vbox()
1212 # self.add_and_display_tab(bulk_tab, "Tournament Results Import")
1214 # end def tab_import_imap_summaries
1216 def tab_ring_player_stats(self, widget, data=None):
1217 new_ps_thread = GuiRingPlayerStats.GuiRingPlayerStats(self.config, self.sql, self)
1218 self.threads.append(new_ps_thread)
1219 self.add_and_display_tab(new_ps_thread, "Ring Player Stats")
1221 def tab_tourney_player_stats(self, widget, data=None):
1222 new_ps_thread = GuiTourneyPlayerStats.GuiTourneyPlayerStats(self.config, self.db, self.sql, self)
1223 self.threads.append(new_ps_thread)
1224 self.add_and_display_tab(new_ps_thread, "Tourney Stats")
1226 def tab_tourney_viewer_stats(self, widget, data=None):
1227 new_thread = GuiTourHandViewer.TourHandViewer(self.config, self.sql, self)
1228 self.threads.append(new_thread)
1229 self.add_and_display_tab(new_thread, "Tourney Viewer")
1231 # def tab_positional_stats(self, widget, data=None):
1232 # new_ps_thread = GuiPositionalStats.GuiPositionalStats(self.config, self.sql)
1233 # self.threads.append(new_ps_thread)
1234 # ps_tab = new_ps_thread.get_vbox()
1235 # self.add_and_display_tab(ps_tab, "Positional Stats")
1237 def tab_session_stats(self, widget, data=None):
1238 colors = self.get_theme_colors()
1239 new_ps_thread = GuiSessionViewer.GuiSessionViewer(self.config, self.sql, self, self, colors=colors)
1240 self.threads.append(new_ps_thread)
1241 self.add_and_display_tab(new_ps_thread, "Session Stats")
1243 def tab_hand_viewer(self, widget, data=None):
1244 new_ps_thread = GuiHandViewer.GuiHandViewer(self.config, self.sql, self)
1245 self.threads.append(new_ps_thread)
1246 self.add_and_display_tab(new_ps_thread, "Hand Viewer")
1248 def tab_main_help(self, widget, data=None):
1249 """Displays a tab with the main fpdb help screen"""
1250 mh_tab = QLabel(
1251 (
1252 """
1253 Welcome to Fpdb!
1255 This program is currently in an alpha-state, so our database format is still sometimes changed.
1256 You should therefore always keep your hand history files so that you can re-import
1257 after an update, if necessary.
1259 all configuration now happens in HUD_config.xml.
1261 This program is free/libre open source software licensed partially under the AGPL3,
1262 and partially under GPL2 or later.
1263 The Windows installer package includes code licensed under the MIT license.
1264 You can find the full license texts in agpl-3.0.txt, gpl-2.0.txt, gpl-3.0.txt
1265 and mit.txt in the fpdb installation directory."""
1266 )
1267 )
1268 self.add_and_display_tab(mh_tab, "Help")
1270 def get_theme_colors(self):
1271 """
1272 Returns a dictionary containing the theme colors used in the application.
1274 The dictionary contains the following keys:
1275 - "background": the name of the color used for the background.
1276 - "foreground": the name of the color used for the foreground.
1277 - "grid": the name of the color used for the grid.
1278 - "line_showdown": the name of the color used for the showdown line.
1279 - "line_nonshowdown": the name of the color used for the non-showdown line.
1280 - "line_ev": the name of the color used for the event line.
1281 - "line_hands": the name of the color used for the hands line.
1283 Returns:
1284 dict: A dictionary containing the theme colors.
1285 """
1286 return {
1287 "background": self.palette().color(QPalette.Window).name(),
1288 "foreground": self.palette().color(QPalette.WindowText).name(),
1289 "grid": "#444444", # to customize
1290 "line_up": "g",
1291 "line_down": "r",
1292 "line_showdown": "b",
1293 "line_nonshowdown": "m",
1294 "line_ev": "orange",
1295 "line_hands": "c",
1296 }
1298 def tabGraphViewer(self, widget, data=None):
1299 """opens a graph viewer tab"""
1300 colors = self.get_theme_colors()
1301 new_gv_thread = GuiGraphViewer.GuiGraphViewer(self.sql, self.config, self, colors=colors)
1302 self.threads.append(new_gv_thread)
1303 self.add_and_display_tab(new_gv_thread, "Graphs")
1305 def tabTourneyGraphViewer(self, widget, data=None):
1306 """opens a graph viewer tab"""
1307 colors = self.get_theme_colors()
1308 new_gv_thread = GuiTourneyGraphViewer.GuiTourneyGraphViewer(self.sql, self.config, self, colors=colors)
1309 self.threads.append(new_gv_thread)
1310 self.add_and_display_tab(new_gv_thread, "Tourney Graphs")
1312 # def tabStove(self, widget, data=None):
1313 # """opens a tab for poker stove"""
1314 # thread = GuiStove.GuiStove(self.config, self)
1315 # self.threads.append(thread)
1316 # # tab = thread.get_vbox()
1317 # self.add_and_display_tab(thread, "Stove")
1319 def validate_config(self):
1320 # check if sites in config file are in DB
1321 for site in self.config.supported_sites: # get site names from config file
1322 try:
1323 self.config.get_site_id(site) # and check against list from db
1324 except KeyError:
1325 log.warning(f"site {site} missing from db")
1326 dia = QMessageBox()
1327 dia.setIcon(QMessageBox.Warning)
1328 dia.setText("Unknown Site")
1329 dia.setStandardButtons(QMessageBox.Ok)
1330 dia.exec_()
1331 diastring = f"Warning: Unable to find site '{site}'"
1332 dia.format_secondary_text(diastring)
1333 dia.run()
1334 dia.destroy()
1336 def info_box(self, str1, str2):
1337 diapath = QMessageBox(self)
1338 diapath.setWindowTitle(str1)
1339 diapath.setText(str2)
1340 return diapath.exec_()
1342 def warning_box(self, string, diatitle="FPDB WARNING"):
1343 return QMessageBox(QMessageBox.Warning, diatitle, string).exec_()
1345 def change_theme(self, theme):
1346 apply_stylesheet(app, theme=theme)
1348 def update_title_bar_theme(self):
1349 # Apply the stylesheet to the custom title bar
1350 self.custom_title_bar.update_theme()
1352 def close_tab(self, index):
1353 item = self.nb.widget(index)
1354 self.nb.removeTab(index)
1355 self.nb_tab_names.pop(index)
1357 try:
1358 self.threads.remove(item)
1359 except ValueError:
1360 pass
1362 item.deleteLater()
1364 def __init__(self):
1365 super().__init__()
1366 if sys.platform == "darwin":
1367 pass
1368 else:
1369 self.setWindowFlags(Qt.FramelessWindowHint)
1370 cards = os.path.join(Configuration.GRAPHICS_PATH, "tribal.jpg")
1371 if os.path.exists(cards):
1372 self.setWindowIcon(QIcon(cards))
1373 set_locale_translation()
1374 self.lock = interlocks.InterProcessLock(name="fpdb_global_lock")
1375 self.db = None
1376 self.status_bar = None
1377 self.quitting = False
1378 self.visible = False
1379 self.threads = []
1380 self.closeq = queue.Queue(20)
1382 self.oldPos = self.pos()
1384 if options.initialRun:
1385 self.display_config_created_dialogue = True
1386 self.display_site_preferences = True
1387 else:
1388 self.display_config_created_dialogue = False
1389 self.display_site_preferences = False
1391 if options.xloc is not None or options.yloc is not None:
1392 if options.xloc is None:
1393 options.xloc = 0
1394 if options.yloc is None:
1395 options.yloc = 0
1396 self.move(options.xloc, options.yloc)
1398 self.setWindowTitle("Free Poker DB 3")
1399 defx, defy = 1920, 1080
1400 sg = QApplication.primaryScreen().availableGeometry()
1401 if sg.width() < defx:
1402 defx = sg.width()
1403 if sg.height() < defy:
1404 defy = sg.height()
1405 self.resize(defx, defy)
1407 if sys.platform == "darwin":
1408 pass
1409 else:
1410 # Create custom title bar
1411 self.custom_title_bar = CustomTitleBar(self)
1412 # Create central widget and layout
1413 self.central_widget = QWidget(self)
1414 self.central_layout = QVBoxLayout(self.central_widget)
1415 self.central_layout.setContentsMargins(0, 0, 0, 0)
1416 self.central_layout.setSpacing(0)
1418 if sys.platform == "darwin":
1419 # Add title bar and menu bar to layout
1420 self.custom_title_bar = CustomTitleBar(self)
1421 self.central_layout.addWidget(self.custom_title_bar)
1422 self.setMenuBar(self.menuBar())
1423 else:
1424 # Add title bar and menu bar to layout
1425 self.central_layout.addWidget(self.custom_title_bar)
1426 self.menu_bar = self.menuBar()
1427 self.central_layout.setMenuBar(self.menu_bar)
1429 self.nb = QTabWidget()
1430 self.nb.setTabsClosable(True)
1431 self.nb.tabCloseRequested.connect(self.close_tab)
1432 self.central_layout.addWidget(self.nb)
1433 self.setCentralWidget(self.central_widget)
1435 self.createMenuBar()
1437 self.pages = []
1438 self.nb_tab_names = []
1440 self.tab_main_help(None, None)
1442 if options.minimized:
1443 self.showMinimized()
1444 if options.hidden:
1445 self.hide()
1447 if not options.hidden:
1448 self.show()
1449 self.visible = True
1451 self.load_profile(create_db=True)
1453 if self.config.install_method == "app":
1454 for site in list(self.config.supported_sites.values()):
1455 if site.screen_name != "YOUR SCREEN NAME HERE":
1456 break
1457 else:
1458 options.initialRun = True
1459 self.display_config_created_dialogue = True
1460 self.display_site_preferences = True
1462 if options.initialRun and self.display_site_preferences:
1463 self.dia_site_preferences(None, None)
1464 self.display_site_preferences = False
1466 if not options.errorsToConsole:
1467 fileName = os.path.join(self.config.dir_log, "fpdb-errors.txt")
1468 log.info(
1469 f"Note: error output is being diverted to {self.config.dir_log}. Any major error will be reported there _only_."
1470 )
1471 errorFile = codecs.open(fileName, "w", "utf-8")
1472 sys.stderr = errorFile
1474 sys.stderr.write("fpdb starting ...")
1476 if options.autoimport:
1477 self.tab_auto_import(None)
1480class CustomTitleBar(QWidget):
1481 def __init__(self, parent=None):
1482 super().__init__(parent)
1483 self.setAutoFillBackground(True)
1484 self.main_window = parent
1486 self.title = QLabel("Free Poker DB 3")
1487 self.title.setAlignment(Qt.AlignCenter)
1489 self.btn_minimize = QPushButton("-")
1490 self.btn_maximize = QPushButton("+")
1491 self.btn_close = QPushButton("x")
1493 button_size = 20
1494 self.btn_minimize.setFixedSize(button_size, button_size)
1495 self.btn_maximize.setFixedSize(button_size, button_size)
1496 self.btn_close.setFixedSize(button_size, button_size)
1498 self.btn_minimize.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
1499 self.btn_maximize.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
1500 self.btn_close.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
1502 self.btn_minimize.clicked.connect(parent.showMinimized)
1503 self.btn_maximize.clicked.connect(self.toggle_maximize_restore)
1504 self.btn_close.clicked.connect(parent.close)
1506 layout = QHBoxLayout()
1507 layout.addWidget(self.title)
1508 layout.addStretch()
1509 layout.addWidget(self.btn_minimize)
1510 layout.addWidget(self.btn_maximize)
1511 layout.addWidget(self.btn_close)
1512 self.setLayout(layout)
1514 self.is_maximized = False
1515 if sys.platform == "darwin":
1516 pass
1517 else:
1518 self.moving = False
1519 self.offset = None
1521 def toggle_maximize_restore(self):
1522 if self.is_maximized:
1523 self.main_window.showNormal()
1524 else:
1525 self.main_window.showMaximized()
1526 self.is_maximized = not self.is_maximized
1528 def update_theme(self):
1529 self.setStyleSheet(self.main_window.styleSheet())
1531 def mousePressEvent(self, event):
1532 if event.button() == Qt.LeftButton:
1533 self.main_window.oldPos = event.globalPos()
1535 def mouseMoveEvent(self, event):
1536 if event.buttons() == Qt.LeftButton:
1537 delta = QPoint(event.globalPos() - self.main_window.oldPos)
1538 self.main_window.move(self.main_window.x() + delta.x(), self.main_window.y() + delta.y())
1539 self.main_window.oldPos = event.globalPos()
1542if __name__ == "__main__":
1543 from qt_material import apply_stylesheet
1544 import time
1546 try:
1547 Configuration.get_config("HUD_config.xml", True)
1548 app = QApplication([])
1549 apply_stylesheet(app, theme="dark_purple.xml")
1550 me = fpdb()
1551 app.exec_()
1552 finally:
1553 profiler.disable()
1554 s = io.StringIO()
1555 ps = pstats.Stats(profiler, stream=s).sort_stats("cumulative")
1556 ps.print_stats()
1558 # Use timestamp or process ID for unique filenames
1559 timestamp = time.strftime("%Y%m%d-%H%M%S")
1560 results_file = os.path.join(PROFILE_OUTPUT_DIR, f"fpdb_profile_results_{timestamp}.txt")
1561 profile_file = os.path.join(PROFILE_OUTPUT_DIR, f"fpdb_profile_{timestamp}.prof")
1563 with open(results_file, "w") as f:
1564 f.write(s.getvalue())
1566 profiler.dump_stats(profile_file)