Coverage for fpdb.pyw: 0%
923 statements
« prev ^ index » next coverage.py v7.6.1, created at 2024-09-28 16:41 +0000
« 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 -*-
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
22# import qdarkstyle
23if os.name == 'nt':
24 pass
26import codecs
27import Options
28from functools import partial
30cl_options = '.'.join(sys.argv[1:])
31(options, argv) = Options.fpdb_options()
32from L10n import set_locale_translation
33import logging
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)
45import interlocks
46from Exceptions import *
48# these imports not required in this module, imported here to report version in About dialog
49import numpy
51numpy_version = numpy.__version__
52import sqlite3
54sqlite3_version = sqlite3.version
55sqlite_version = sqlite3.sqlite_version
57import DetectInstalledSites
58import GuiPrefs
59import GuiLogView
60# import GuiDatabase
61import GuiBulkImport
62#import GuiTourneyImport
64import GuiRingPlayerStats
65import GuiTourneyPlayerStats
66import 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
81#import api, app
82import cProfile
83import pstats
84import io
87PROFILE_OUTPUT_DIR = os.path.join(os.path.expanduser("~"), "fpdb_profiles")
88os.makedirs(PROFILE_OUTPUT_DIR, exist_ok=True)
90profiler = cProfile.Profile()
91profiler.enable()
95Configuration.set_logfile("fpdb-log.txt")
97log = logging.getLogger("fpdb")
99try:
100 assert not hasattr(sys, 'frozen') # We're surely not in a git repo if this fails
101 import subprocess
103 VERSION = subprocess.Popen(["git", "describe", "--tags", "--dirty"], stdout=subprocess.PIPE).communicate()[0]
104 VERSION = VERSION[:-1]
105except Exception:
106 VERSION = "3.0.0alpha"
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])
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
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
132 if not used_before:
133 page = new_page
134 self.pages.append(new_page)
135 self.nb_tab_names.append(new_tab_name)
137 index = self.nb.addTab(page, new_tab_name)
138 self.nb.setCurrentIndex(index)
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
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)
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
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()
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")
181 # end def dia_database_stats
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()
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()
200 huds_names = self.config.get_stat_sets()
201 for hud_name in huds_names:
202 self.comboGame.addItem(hud_name)
204 dia.layout().addWidget(self.comboGame)
205 self.comboGame.setCurrentIndex(1)
206 selected_hud_name = self.comboGame.currentText()
208 self.load_profile() # => self.[config, settings]
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.
213 self.table = QGridLayout()
214 self.table.setSpacing(0)
216 scrolling_frame = QScrollArea(dia)
217 dia.layout().addWidget(scrolling_frame)
218 scrolling_frame.setLayout(self.table)
220 nb_items = len(self.config.stat_sets[selected_hud_name].stats)
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)
228 # Launch Dialog
229 response = dia.exec_()
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 )
252 self.config.save()
253 self.reload_config()
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()
267 self.table.setSpacing(0)
269 column_headers = ["coordonate", "stats name", "click", "hudcolor", "hudprefix", "hudsuffix",
270 "popup", "stat_hicolor", "stat_hith", "stat_locolor", "stat_loth",
271 "tip"] # todo ("HUD")
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)
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 = [], [], [], [], [], [], [], [], [], [], [], []
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)
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)
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)
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)
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)
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)
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)
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)
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)
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)
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)
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
377 y_pos += 1
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()
402 def dia_dump_db(self, widget, data=None):
403 filename = "database-dump.sql"
404 result = self.db.dumpDatabase()
406 with open(filename, 'w') as dumpFile:
407 dumpFile.write(result)
409 # end def dia_database_stats
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."
420 dia_confirm.setInformativeText(diastring) # todo: make above string with bold for db, host and deleted
421 response = dia_confirm.exec_()
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.")
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."))
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))
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)
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))
462 hb2.addWidget(lbl)
463 hb2.addStretch()
464 hb2.addWidget(self.start_date)
465 hb2.addWidget(btn)
466 self.dia_confirm.layout().addLayout(hb2)
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)
473 response = self.dia_confirm.exec_()
474 if response:
475 log.info("Rebuilding HUD Cache ...")
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')
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.")
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)
497 response = self.dia_confirm.exec_()
498 if response == QMessageBox.Yes:
499 log.info(" Rebuilding Indexes ... ")
500 self.db.rebuild_indexes()
502 log.info(" Cleaning Database ... ")
503 self.db.vacuumDB()
505 log.info(" Analyzing Database ... ")
506 self.db.analyzeDB()
507 else:
508 log.info('User cancelled rebuilding db indexes')
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.")
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()
520 viewer = None
521 for i, t in enumerate(self.threads):
522 if str(t.__class__) == 'GuiLogView.GuiLogView':
523 viewer = t
524 break
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()
534 # if lock_set:
535 # self.release_global_lock()
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)
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
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.
562 table = QGridLayout()
563 table.setSpacing(0)
565 scrolling_frame = QScrollArea(dia)
566 dia.layout().addWidget(scrolling_frame)
567 scrolling_frame.setLayout(table)
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)
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()
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]
590 # print('hud seat ps:', type(hud_seat), hud_seat)
591 seat2 = QLineEdit()
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]))
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)
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)
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)
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)
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)
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)
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)
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)
638 if available_site_names[site_number] in detector.supportedSites:
639 pass
641 y_pos += 1
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)
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())
661 self.config.save()
662 self.reload_config()
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)
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
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.
687 table = QGridLayout()
688 table.setSpacing(0)
690 scrolling_frame = QScrollArea(dia)
691 dia.layout().addWidget(scrolling_frame)
692 scrolling_frame.setLayout(table)
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)
699 check_buttons = []
700 screen_names = []
701 history_paths = []
702 summary_paths = []
703 detector = DetectInstalledSites.DetectInstalledSites()
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)
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]))
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)
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]))
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)
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]))
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
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)
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())
760 self.config.save()
761 self.reload_config()
763 def autoenableSite(self, text, checkbox):
764 # autoactivate site if something gets typed in the screename field
765 checkbox.setChecked(True)
767 def browseClicked(self, widget, parent, path):
768 """runs when user clicks one of the browse buttons for the TS folder"""
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)
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'])
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.")
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
812 def __calendar_dialog(self, widget, entry):
813 d = QDialog(self.dia_confirm)
814 d.setWindowTitle('Pick a date')
816 vb = QVBoxLayout()
817 d.setLayout(vb)
818 cal = QCalendarWidget()
819 vb.addWidget(cal)
821 btn = QPushButton('Done')
822 btn.clicked.connect(partial(self.__get_date, calendar=cal, entry=entry, win=d))
824 vb.addWidget(btn)
826 d.exec_()
827 return
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')
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
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'))
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))
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'))
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 ]
888 for theme in themes:
889 themeMenu.addAction(QAction(theme, self, triggered=partial(self.change_theme, theme)))
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()
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 ])
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 ])
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 ])
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 ])
943 diaConfigVersionWarning.layout().addWidget(label)
945 label = QLabel("To keep existing personal settings, you must edit the local file.")
946 diaConfigVersionWarning.layout().addWidget(label)
948 label = QLabel("See the release note for information about the edits needed")
949 diaConfigVersionWarning.layout().addWidget(label)
951 btns = QDialogButtonBox(QDialogButtonBox.Ok)
952 btns.accepted.connect(diaConfigVersionWarning.accept)
953 diaConfigVersionWarning.layout().addWidget(btns)
955 diaConfigVersionWarning.exec_()
956 self.config.wrongConfigVersion = False
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"
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())
970 if self.db is not None and self.db.is_connected():
971 self.db.disconnect()
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."
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
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 )
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}")
1012 # rollback to make sure any locks are cleared:
1013 self.db.rollback()
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()
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()
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
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()
1058 def release_global_lock(self):
1059 self.lock.release()
1060 self.lockTakenBy = None
1061 log.info("Global lock released.")
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
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")
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")
1089 # end def tab_import_imap_summaries
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")
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")
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")
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")
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")
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")
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!
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.
1132 all configuration now happens in HUD_config.xml.
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")
1141 def get_theme_colors(self):
1142 """
1143 Returns a dictionary containing the theme colors used in the application.
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.
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 }
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")
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")
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")
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()
1211 def info_box(self, str1, str2):
1212 diapath = QMessageBox(self)
1213 diapath.setWindowTitle(str1)
1214 diapath.setText(str2)
1215 return diapath.exec_()
1217 def warning_box(self, string, diatitle="FPDB WARNING"):
1218 return QMessageBox(QMessageBox.Warning, diatitle, string).exec_()
1220 def change_theme(self, theme):
1221 apply_stylesheet(app, theme=theme)
1225 def update_title_bar_theme(self):
1226 # Apply the stylesheet to the custom title bar
1227 self.custom_title_bar.update_theme()
1229 def close_tab(self, index):
1230 item = self.nb.widget(index)
1231 self.nb.removeTab(index)
1232 self.nb_tab_names.pop(index)
1234 try:
1235 self.threads.remove(item)
1236 except ValueError:
1237 pass
1239 item.deleteLater()
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)
1259 self.oldPos = self.pos()
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
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)
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)
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)
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)
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)
1314 self.createMenuBar()
1316 self.pages = []
1317 self.nb_tab_names = []
1319 self.tab_main_help(None, None)
1321 if options.minimized:
1322 self.showMinimized()
1323 if options.hidden:
1324 self.hide()
1326 if not options.hidden:
1327 self.show()
1328 self.visible = True
1330 self.load_profile(create_db=True)
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
1341 if options.initialRun and self.display_site_preferences:
1342 self.dia_site_preferences(None, None)
1343 self.display_site_preferences = False
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
1351 sys.stderr.write("fpdb starting ...")
1353 if options.autoimport:
1354 self.tab_auto_import(None)
1358class CustomTitleBar(QWidget):
1359 def __init__(self, parent=None):
1360 super().__init__(parent)
1361 self.setAutoFillBackground(True)
1362 self.main_window = parent
1364 self.title = QLabel("Free Poker DB 3")
1365 self.title.setAlignment(Qt.AlignCenter)
1367 self.btn_minimize = QPushButton("-")
1368 self.btn_maximize = QPushButton("+")
1369 self.btn_close = QPushButton("x")
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)
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)
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)
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)
1392 self.is_maximized = False
1393 if sys.platform == 'darwin':
1394 pass
1395 else:
1396 self.moving = False
1397 self.offset = None
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
1406 def update_theme(self):
1407 self.setStyleSheet(self.main_window.styleSheet())
1409 def mousePressEvent(self, event):
1410 if event.button() == Qt.LeftButton:
1411 self.main_window.oldPos = event.globalPos()
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()
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()
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')
1440 with open(results_file, 'w') as f:
1441 f.write(s.getvalue())
1443 profiler.dump_stats(profile_file)