Carbon Poker offers a 5 card stud game that wasn't listed here. It's not available...
[fpdb-dooglus.git] / pyfpdb / fpdb.pyw
blob7ce513e662c63bfa3a2cd203e243f28fed339045
1 #!/usr/bin/env python
2 # -*- coding: utf-8 -*-
4 #Copyright 2008-2011 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.
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.
14 #You should have received a copy of the GNU Affero General Public License
15 #along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #In the "official" distribution you can find the license in agpl-3.0.txt.
17 import L10n
18 _ = L10n.get_translation()
20 import os
21 import sys
22 import re
23 import Queue
25 # if path is set to use an old version of python look for a new one:
26 # (does this work in linux?)
27 if os.name == 'nt' and sys.version[0:3] not in ('2.5', '2.6', '2.7') and '-r' not in sys.argv:
28 #print "old path =", os.environ['PATH']
29 dirs = re.split(os.pathsep, os.environ['PATH'])
30 # remove any trailing / or \ chars from dirs:
31 dirs = [re.sub('[\\/]$', '', p) for p in dirs]
32 # remove any dirs containing 'python' apart from those ending in 'python25', 'python26' or 'python':
33 dirs = [p for p in dirs if not re.search('python', p, re.I) or re.search('python25$', p, re.I) or re.search('python26$', p, re.I) or re.search('python27$', p, re.I)]
34 tmppath = ";".join(dirs)
35 #print "new path =", tmppath
36 if re.search('python', tmppath, re.I):
37 os.environ['PATH'] = tmppath
38 print "Python " + sys.version[0:3] + _(' - press return to continue\n')
39 sys.stdin.readline()
40 if os.name == 'nt':
41 os.execvpe('pythonw.exe', ('pythonw.exe', 'fpdb.pyw', '-r'), os.environ)
42 else:
43 os.execvpe('python', ('python', 'fpdb.pyw', '-r'), os.environ)
44 else:
45 print _("\npython 2.5-2.7 not found, please install python 2.5, 2.6 or 2.7 for fpdb\n")
46 raw_input(_("Press ENTER to continue."))
47 exit()
48 else:
49 pass
50 #print "debug - not changing path"
52 if os.name == 'nt':
53 import win32api
54 import win32con
56 print "Python " + sys.version[0:3] + '...'
58 import traceback
59 import threading
60 import Options
61 import string
62 cl_options = string.join(sys.argv[1:])
63 (options, argv) = Options.fpdb_options()
65 import logging
66 import logging.config
67 log = logging.getLogger("fpdb")
69 import pygtk
70 pygtk.require('2.0')
71 import gtk
72 import pango
74 import interlocks
76 # these imports not required in this module, imported here to report version in About dialog
77 import matplotlib
78 matplotlib_version = matplotlib.__version__
79 import numpy
80 numpy_version = numpy.__version__
81 import sqlite3
82 sqlite3_version = sqlite3.version
83 sqlite_version = sqlite3.sqlite_version
85 import DetectInstalledSites
86 import GuiPrefs
87 import GuiLogView
88 import GuiDatabase
89 import GuiBulkImport
90 import GuiTourneyImport
91 import GuiImapFetcher
92 import GuiRingPlayerStats
93 import GuiTourneyPlayerStats
94 import GuiTourneyViewer
95 import GuiPositionalStats
96 import GuiAutoImport
97 import GuiGraphViewer
98 import GuiTourneyGraphViewer
99 import GuiSessionViewer
100 import GuiReplayer
101 try:
102 import GuiStove
103 except:
104 print _("GuiStove not found. If you want to use it please install pypoker-eval.")
105 import SQL
106 import Database
107 import Configuration
108 import Exceptions
109 import Stats
111 VERSION = "0.25 + git"
113 class fpdb:
114 def tab_clicked(self, widget, tab_name):
115 """called when a tab button is clicked to activate that tab"""
116 self.display_tab(tab_name)
118 def add_and_display_tab(self, new_tab, new_tab_name):
119 """just calls the component methods"""
120 self.add_tab(new_tab, new_tab_name)
121 self.display_tab(new_tab_name)
123 def add_tab(self, new_page, new_tab_name):
124 """adds a tab, namely creates the button and displays it and appends all the relevant arrays"""
125 for name in self.nb_tab_names: # todo: check this is valid
126 if name == new_tab_name:
127 return # if tab already exists, just go to it
129 used_before = False
130 for i, name in enumerate(self.tab_names):
131 if name == new_tab_name:
132 used_before = True
133 event_box = self.tabs[i]
134 page = self.pages[i]
135 break
137 if not used_before:
138 event_box = self.create_custom_tab(new_tab_name, self.nb)
139 page = new_page
140 self.pages.append(new_page)
141 self.tabs.append(event_box)
142 self.tab_names.append(new_tab_name)
144 #self.nb.append_page(new_page, gtk.Label(new_tab_name))
145 self.nb.append_page(page, event_box)
146 self.nb_tab_names.append(new_tab_name)
147 page.show()
149 def display_tab(self, new_tab_name):
150 """displays the indicated tab"""
151 tab_no = -1
152 for i, name in enumerate(self.nb_tab_names):
153 if new_tab_name == name:
154 tab_no = i
155 break
157 if tab_no < 0 or tab_no >= self.nb.get_n_pages():
158 raise FpdbError("invalid tab_no " + str(tab_no))
159 else:
160 self.nb.set_current_page(tab_no)
162 def switch_to_tab(self, accel_group, acceleratable, keyval, modifier):
163 tab = keyval - ord('0')
164 if (tab == 0): tab = 10
165 tab = tab - 1
166 if (tab < len(self.nb_tab_names)):
167 self.display_tab(self.nb_tab_names[tab])
169 def create_custom_tab(self, text, nb):
170 #create a custom tab for notebook containing a
171 #label and a button with STOCK_ICON
172 eventBox = gtk.EventBox()
173 tabBox = gtk.HBox(False, 2)
174 tabLabel = gtk.Label(text)
175 tabBox.pack_start(tabLabel, False)
176 eventBox.add(tabBox)
178 # fixme: force background state to fix problem where STATE_ACTIVE
179 # tab labels are black in some gtk themes, and therefore unreadable
180 # This behaviour is probably a bug in libwimp.dll or pygtk, but
181 # need to force background to avoid issues with menu labels being
182 # unreadable
184 # gtk.STATE_ACTIVE is a displayed, but not selected tab
185 # gtk.STATE_NORMAL is a displayed, selected, focussed tab
186 # gtk.STATE_INSENSITIVE is an inactive tab
187 # Insensitive/base is chosen as the background colour, because
188 # although not perfect, it seems to be the least instrusive.
189 baseNormStyle = eventBox.get_style().base[gtk.STATE_INSENSITIVE]
190 try:
191 gtk.gdk.color_parse(str(baseNormStyle))
192 if baseNormStyle:
193 eventBox.modify_bg(gtk.STATE_ACTIVE, gtk.gdk.color_parse(str(baseNormStyle)))
194 except:
195 pass
197 if nb.get_n_pages() > 0:
198 tabButton = gtk.Button()
200 tabButton.connect('clicked', self.remove_tab, (nb, text))
201 #Add a picture on a button
202 self.add_icon_to_button(tabButton)
203 tabBox.pack_start(tabButton, False)
205 # needed, otherwise even calling show_all on the notebook won't
206 # make the hbox contents appear.
207 tabBox.show_all()
208 return eventBox
210 def add_icon_to_button(self, button):
211 iconBox = gtk.HBox(False, 0)
212 image = gtk.Image()
213 image.set_from_stock(gtk.STOCK_CLOSE, gtk.ICON_SIZE_SMALL_TOOLBAR)
214 gtk.Button.set_relief(button, gtk.RELIEF_NONE)
215 settings = gtk.Widget.get_settings(button)
216 (w, h) = gtk.icon_size_lookup_for_settings(settings, gtk.ICON_SIZE_SMALL_TOOLBAR)
217 gtk.Widget.set_size_request(button, w + 4, h + 4)
218 image.show()
219 iconBox.pack_start(image, True, False, 0)
220 button.add(iconBox)
221 iconBox.show()
222 return
224 # Remove a page from the notebook
225 def remove_tab(self, button, data):
226 (nb, text) = data
227 page = -1
228 #print "\n remove_tab: start", text
229 for i, tab in enumerate(self.nb_tab_names):
230 if text == tab:
231 page = i
232 #print " page =", page
233 if page >= 0 and page < self.nb.get_n_pages():
234 #print " removing page", page
235 del self.nb_tab_names[page]
236 nb.remove_page(page)
237 # Need to refresh the widget --
238 # This forces the widget to redraw itself.
239 #nb.queue_draw_area(0,0,-1,-1) needed or not??
241 def delete_event(self, widget, event, data=None):
242 return False
244 def destroy(self, widget, data=None):
245 self.quit(widget)
247 def dia_about(self, widget, data=None):
248 dia = gtk.AboutDialog()
249 dia.set_name("Free Poker Database (FPDB)")
250 dia.set_version(VERSION)
251 dia.set_copyright(_("Copyright 2008-2011, Steffen, Eratosthenes, Carl Gherardi, Eric Blade, _mt, sqlcoder, Bostik, and others"))
252 dia.set_comments(_("You are free to change, and distribute original or changed versions of fpdb within the rules set out by the license"))
253 dia.set_license(_("Please see fpdb's start screen for license information"))
254 dia.set_website("http://fpdb.sourceforge.net/")
256 dia.set_authors(['Steffen', 'Eratosthenes', 'Carl Gherardi',
257 'Eric Blade', '_mt', 'sqlcoder', 'Bostik', _('and others')])
258 dia.set_program_name("Free Poker Database (FPDB)")
260 if (os.name=="posix"):
261 os_text=str(os.uname())
262 elif (os.name=="nt"):
263 import platform
264 os_text=("Windows" + " " + str(platform.win32_ver()))
265 else:
266 os_text="Unknown"
268 import locale
269 nums = [(_('Operating System'), os_text),
270 ('Python', sys.version[0:3]),
271 ('GTK+', '.'.join([str(x) for x in gtk.gtk_version])),
272 ('PyGTK', '.'.join([str(x) for x in gtk.pygtk_version])),
273 ('matplotlib', matplotlib_version),
274 ('numpy', numpy_version),
275 ('sqlite', sqlite_version),
276 (_('fpdb version'), VERSION),
277 (_('database used'), self.settings['db-server']),
278 (_('language'), locale.getdefaultlocale()[0]),
279 (_('character encoding'), locale.getdefaultlocale()[1])
281 versions = gtk.TextBuffer()
282 w = 20 # width used for module names and version numbers
283 versions.set_text('\n'.join([x[0].rjust(w) + ': ' + x[1].ljust(w) for x in nums]))
284 view = gtk.TextView(versions)
285 view.set_editable(False)
286 view.set_justification(gtk.JUSTIFY_CENTER)
287 view.modify_font(pango.FontDescription('monospace 10'))
288 view.show()
289 dia.vbox.pack_end(view, True, True, 2)
291 l = gtk.Label(_("Your config file is: ") + self.config.file)
292 l.set_alignment(0.5, 0.5)
293 l.show()
294 dia.vbox.pack_end(l, True, True, 2)
296 l = gtk.Label(_('Version Information:'))
297 l.set_alignment(0.5, 0.5)
298 l.show()
299 dia.vbox.pack_end(l, True, True, 2)
301 dia.run()
302 dia.destroy()
303 log.debug(_("Threads: "))
304 for t in self.threads:
305 log.debug("........." + str(t.__class__))
307 def dia_advanced_preferences(self, widget, data=None):
308 dia = gtk.Dialog(_("Advanced Preferences"),
309 self.window,
310 gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
311 (gtk.STOCK_CANCEL, gtk.RESPONSE_REJECT,
312 gtk.STOCK_SAVE, gtk.RESPONSE_ACCEPT))
313 dia.set_default_size(700, 500)
315 #force reload of prefs from xml file - needed because HUD could
316 #have changed file contents
317 self.load_profile()
318 prefs = GuiPrefs.GuiPrefs(self.config, self.window, dia.vbox, dia)
319 response = dia.run()
320 if response == gtk.RESPONSE_ACCEPT:
321 # save updated config
322 self.config.save()
323 self.reload_config(dia)
324 else:
325 dia.destroy()
327 def dia_maintain_dbs(self, widget, data=None):
328 #self.warning_box("Unimplemented: Maintain Databases")
329 #return
330 if len(self.tab_names) == 1:
331 if self.obtain_global_lock("dia_maintain_dbs"): # returns true if successful
332 # only main tab has been opened, open dialog
333 dia = gtk.Dialog(_("Maintain Databases"),
334 self.window,
335 gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
336 (gtk.STOCK_CANCEL, gtk.RESPONSE_REJECT,
337 gtk.STOCK_SAVE, gtk.RESPONSE_ACCEPT))
338 dia.set_default_size(700, 320)
340 prefs = GuiDatabase.GuiDatabase(self.config, self.window, dia)
341 response = dia.run()
342 if response == gtk.RESPONSE_ACCEPT:
343 log.info(_('saving updated db data'))
344 # save updated config
345 self.config.save()
346 self.load_profile()
347 for name in self.config.supported_databases: # db_ip/db_user/db_pass/db_server
348 log.info('fpdb: name,desc=' + name + ',' + self.config.supported_databases[name].db_desc)
349 else:
350 log.info(_('guidb response was ') + str(response))
352 self.release_global_lock()
354 dia.destroy()
355 else:
356 self.warning_box(_("Cannot open Database Maintenance window because other windows have been opened. Re-start fpdb to use this option."))
358 def dia_database_stats(self, widget, data=None):
359 self.warning_box(str=_("Number of Hands: ") + str(self.db.getHandCount()) +
360 _("\nNumber of Tourneys: ") + str(self.db.getTourneyCount()) +
361 _("\nNumber of TourneyTypes: ") + str(self.db.getTourneyTypeCount()),
362 diatitle=_("Database Statistics"))
363 #end def dia_database_stats
365 def dia_hud_preferences(self, widget, data=None):
366 """Opens dialog to set parameters (game category, row count, column count) for HUD preferences"""
367 #Note: No point in working on this until the new HUD configuration system is in place
368 self.hud_preferences_rows = None
369 self.hud_preferences_columns = None
370 self.hud_preferences_game = None
372 diaSelections = gtk.Dialog(_("HUD Preferences - choose category"),
373 self.window,
374 gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
375 (gtk.STOCK_OK, gtk.RESPONSE_ACCEPT,
376 gtk.STOCK_CANCEL, gtk.RESPONSE_REJECT))
378 label = gtk.Label(_("Note that this does not existing settings, but overwrites them."))
379 diaSelections.vbox.add(label)
380 label.show()
382 label = gtk.Label(_("Please select the game category for which you want to configure HUD stats:"))
383 diaSelections.vbox.add(label)
384 label.show()
386 comboGame = gtk.combo_box_new_text()
387 comboGame.connect("changed", self.hud_preferences_combo_selection)
388 diaSelections.vbox.add(comboGame)
389 games = self.config.get_supported_games()
390 for game in games:
391 comboGame.append_text(game)
392 comboGame.set_active(0)
393 comboGame.show()
395 comboRows = gtk.combo_box_new_text()
396 comboRows.connect("changed", self.hud_preferences_combo_selection)
397 diaSelections.vbox.add(comboRows)
398 for i in range(1, 8):
399 comboRows.append_text(str(i) + " rows")
400 comboRows.set_active(0)
401 comboRows.show()
403 comboColumns = gtk.combo_box_new_text()
404 comboColumns.connect("changed", self.hud_preferences_combo_selection)
405 diaSelections.vbox.add(comboColumns)
406 for i in range(1, 8):
407 comboColumns.append_text(str(i) + " columns")
408 comboColumns.set_active(0)
409 comboColumns.show()
411 self.load_profile()
412 response = diaSelections.run()
413 diaSelections.destroy()
415 if (response == gtk.RESPONSE_ACCEPT and
416 self.hud_preferences_rows != None and
417 self.hud_preferences_columns != None and
418 self.hud_preferences_game != None):
419 self.dia_hud_preferences_table()
420 #end def dia_hud_preferences
422 def hud_preferences_combo_selection(self, widget):
423 #TODO: remove this and handle it directly in dia_hud_preferences
424 result = widget.get_active_text()
425 if result.endswith(" rows"):
426 self.hud_preferences_rows = int(result[0])
427 elif result.endswith(" columns"):
428 self.hud_preferences_columns = int(result[0])
429 else:
430 self.hud_preferences_game = result
431 #end def hud_preferences_combo_selection
433 def dia_hud_preferences_table(self):
434 """shows dialogue with Table of ComboBoxes to allow choosing of HUD stats"""
435 #TODO: show explanation of what each stat means
436 diaHudTable = gtk.Dialog(_("HUD Preferences - please choose your stats"),
437 self.window,
438 gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
439 (gtk.STOCK_SAVE, gtk.RESPONSE_ACCEPT,
440 gtk.STOCK_CANCEL, gtk.RESPONSE_REJECT))
442 label = gtk.Label(_("Please choose the stats you wish to use in the below table."))
443 diaHudTable.vbox.add(label)
444 label.show()
446 label = gtk.Label(_("Note that you may not select any stat more than once or it will crash."))
447 diaHudTable.vbox.add(label)
448 label.show()
450 label = gtk.Label(_("It is not currently possible to select \"empty\" or anything else to that end."))
451 diaHudTable.vbox.add(label)
452 label.show()
454 label = gtk.Label(_("To configure things like colouring you will still have to use the Advanced Preferences dialogue or manually edit your HUD_config.xml."))
455 diaHudTable.vbox.add(label)
456 label.show()
458 self.hud_preferences_table_contents = []
459 table = gtk.Table(rows=self.hud_preferences_rows + 1, columns=self.hud_preferences_columns + 1, homogeneous=True)
461 statDict = Stats.build_stat_descriptions(Stats)
463 for rowNumber in range(self.hud_preferences_rows + 1):
464 newRow = []
465 for columnNumber in range(self.hud_preferences_columns + 1):
466 if rowNumber == 0:
467 if columnNumber == 0:
468 pass
469 else:
470 label = gtk.Label("column " + str(columnNumber))
471 table.attach(child=label, left_attach=columnNumber,
472 right_attach=columnNumber + 1,
473 top_attach=rowNumber,
474 bottom_attach=rowNumber + 1)
475 label.show()
476 elif columnNumber == 0:
477 label = gtk.Label("row " + str(rowNumber))
478 table.attach(child=label, left_attach=columnNumber,
479 right_attach=columnNumber + 1,
480 top_attach=rowNumber,
481 bottom_attach=rowNumber + 1)
482 label.show()
483 else:
484 comboBox = gtk.combo_box_new_text()
486 for stat in sorted(statDict.values()):
487 comboBox.append_text(stat)
488 comboBox.set_active(0)
490 newRow.append(comboBox)
491 table.attach(child=comboBox, left_attach=columnNumber,
492 right_attach=columnNumber + 1,
493 top_attach=rowNumber,
494 bottom_attach=rowNumber + 1)
496 comboBox.show()
497 if rowNumber != 0:
498 self.hud_preferences_table_contents.append(newRow)
499 diaHudTable.vbox.add(table)
500 table.show()
502 response = diaHudTable.run()
503 diaHudTable.destroy()
505 if response == gtk.RESPONSE_ACCEPT:
506 self.storeNewHudStatConfig(statDict)
507 #end def dia_hud_preferences_table
509 def storeNewHudStatConfig(self, stat_dict):
510 """stores selections made in dia_hud_preferences_table"""
511 self.obtain_global_lock("dia_hud_preferences")
512 statTable = []
513 for row in self.hud_preferences_table_contents:
514 newRow = []
515 for column in row:
516 new_field = column.get_active_text()
517 for attr in stat_dict: #very inefficient, but who cares
518 if new_field == stat_dict[attr]:
519 newRow.append(attr)
520 break
521 statTable.append(newRow)
523 self.config.editStats(self.hud_preferences_game, statTable)
524 self.config.save() # TODO: make it not store in horrible formatting
525 self.reload_config(None)
526 self.release_global_lock()
527 #end def storeNewHudStatConfig
529 def dia_dump_db(self, widget, data=None):
530 filename = "database-dump.sql"
531 result = self.db.dumpDatabase()
533 dumpFile = open(filename, 'w')
534 dumpFile.write(result)
535 dumpFile.close()
536 #end def dia_database_stats
538 def dia_recreate_tables(self, widget, data=None):
539 """Dialogue that asks user to confirm that he wants to delete and recreate the tables"""
540 if self.obtain_global_lock("fpdb.dia_recreate_tables"): # returns true if successful
542 #lock_released = False
543 dia_confirm = gtk.MessageDialog(parent=self.window, flags=gtk.DIALOG_DESTROY_WITH_PARENT, type=gtk.MESSAGE_WARNING,
544 buttons=(gtk.BUTTONS_YES_NO), message_format=_("Confirm deleting and recreating tables"))
545 diastring = _("Please confirm that you want to (re-)create the tables.") \
546 + (_(" If there already are tables in the database %s on %s they will be deleted and you will have to re-import your histories.\n") % (self.db.database, self.db.host)) \
547 + _("This may take a while.")
548 dia_confirm.format_secondary_text(diastring) # todo: make above string with bold for db, host and deleted
549 # disable windowclose, do not want the the underlying processing interrupted mid-process
550 dia_confirm.set_deletable(False)
552 response = dia_confirm.run()
553 dia_confirm.destroy()
554 if response == gtk.RESPONSE_YES:
555 #if self.db.backend == self.fdb_lock.fdb.MYSQL_INNODB:
556 # mysql requires locks on all tables or none - easier to release this lock
557 # than lock all the other tables
558 # ToDo: lock all other tables so that lock doesn't have to be released
559 # self.release_global_lock()
560 # lock_released = True
561 self.db.recreate_tables()
562 # find any guibulkimport/guiautoimport windows and clear player cache:
563 for t in self.threads:
564 if isinstance(t, GuiBulkImport.GuiBulkImport) or isinstance(t, GuiAutoImport.GuiAutoImport):
565 t.importer.database.resetPlayerIDs()
566 self.release_global_lock()
567 #else:
568 # for other dbs use same connection as holds global lock
569 # self.fdb_lock.fdb.recreate_tables()
570 elif response == gtk.RESPONSE_NO:
571 self.release_global_lock()
572 print _('User cancelled recreating tables')
573 #if not lock_released:
574 #end def dia_recreate_tables
576 def dia_recreate_hudcache(self, widget, data=None):
577 if self.obtain_global_lock("dia_recreate_hudcache"):
578 self.dia_confirm = gtk.MessageDialog(parent=self.window, flags=gtk.DIALOG_DESTROY_WITH_PARENT, type=gtk.MESSAGE_WARNING, buttons=(gtk.BUTTONS_YES_NO), message_format="Confirm recreating HUD cache")
579 diastring = _("Please confirm that you want to re-create the HUD cache.")
580 self.dia_confirm.format_secondary_text(diastring)
581 # disable windowclose, do not want the the underlying processing interrupted mid-process
582 self.dia_confirm.set_deletable(False)
584 hb1 = gtk.HBox(True, 1)
585 self.h_start_date = gtk.Entry(max=12)
586 self.h_start_date.set_text(self.db.get_hero_hudcache_start())
587 lbl = gtk.Label(_(" Hero's cache starts: "))
588 btn = gtk.Button()
589 btn.set_image(gtk.image_new_from_stock(gtk.STOCK_INDEX, gtk.ICON_SIZE_BUTTON))
590 btn.connect('clicked', self.__calendar_dialog, self.h_start_date)
592 hb1.pack_start(lbl, expand=True, padding=3)
593 hb1.pack_start(self.h_start_date, expand=True, padding=2)
594 hb1.pack_start(btn, expand=False, padding=3)
595 self.dia_confirm.vbox.add(hb1)
596 hb1.show_all()
598 hb2 = gtk.HBox(True, 1)
599 self.start_date = gtk.Entry(max=12)
600 self.start_date.set_text(self.db.get_hero_hudcache_start())
601 lbl = gtk.Label(_(" Villains' cache starts: "))
602 btn = gtk.Button()
603 btn.set_image(gtk.image_new_from_stock(gtk.STOCK_INDEX, gtk.ICON_SIZE_BUTTON))
604 btn.connect('clicked', self.__calendar_dialog, self.start_date)
606 hb2.pack_start(lbl, expand=True, padding=3)
607 hb2.pack_start(self.start_date, expand=True, padding=2)
608 hb2.pack_start(btn, expand=False, padding=3)
609 self.dia_confirm.vbox.add(hb2)
610 hb2.show_all()
612 response = self.dia_confirm.run()
613 if response == gtk.RESPONSE_YES:
614 lbl = gtk.Label(_(" Rebuilding HUD Cache ... "))
615 self.dia_confirm.vbox.add(lbl)
616 lbl.show()
617 while gtk.events_pending():
618 gtk.main_iteration_do(False)
620 self.db.rebuild_hudcache(self.h_start_date.get_text(), self.start_date.get_text())
621 elif response == gtk.RESPONSE_NO:
622 print _('User cancelled rebuilding hud cache')
624 self.dia_confirm.destroy()
626 self.release_global_lock()
628 def dia_rebuild_indexes(self, widget, data=None):
629 if self.obtain_global_lock("dia_rebuild_indexes"):
630 self.dia_confirm = gtk.MessageDialog(parent=self.window,
631 flags=gtk.DIALOG_DESTROY_WITH_PARENT,
632 type=gtk.MESSAGE_WARNING,
633 buttons=(gtk.BUTTONS_YES_NO),
634 message_format=_("Confirm rebuilding database indexes"))
635 diastring = _("Please confirm that you want to rebuild the database indexes.")
636 self.dia_confirm.format_secondary_text(diastring)
637 # disable windowclose, do not want the the underlying processing interrupted mid-process
638 self.dia_confirm.set_deletable(False)
640 response = self.dia_confirm.run()
641 if response == gtk.RESPONSE_YES:
642 #FIXME these progress messages do not seem to work in *nix
643 lbl = gtk.Label(_(" Rebuilding Indexes ... "))
644 self.dia_confirm.vbox.add(lbl)
645 lbl.show()
646 while gtk.events_pending():
647 gtk.main_iteration_do(False)
648 self.db.rebuild_indexes()
650 lbl.set_text(_(" Cleaning Database ... "))
651 while gtk.events_pending():
652 gtk.main_iteration_do(False)
653 self.db.vacuumDB()
655 lbl.set_text(_(" Analyzing Database ... "))
656 while gtk.events_pending():
657 gtk.main_iteration_do(False)
658 self.db.analyzeDB()
659 elif response == gtk.RESPONSE_NO:
660 print _('User cancelled rebuilding db indexes')
662 self.dia_confirm.destroy()
664 self.release_global_lock()
666 def dia_logs(self, widget, data=None):
667 """opens the log viewer window"""
669 #lock_set = False
670 #if self.obtain_global_lock("dia_logs"):
671 # lock_set = True
673 # remove members from self.threads if close messages received
674 self.process_close_messages()
676 viewer = None
677 for i, t in enumerate(self.threads):
678 if str(t.__class__) == 'GuiLogView.GuiLogView':
679 viewer = t
680 break
682 if viewer is None:
683 #print "creating new log viewer"
684 new_thread = GuiLogView.GuiLogView(self.config, self.window, self.closeq)
685 self.threads.append(new_thread)
686 else:
687 #print "showing existing log viewer"
688 viewer.get_dialog().present()
690 #if lock_set:
691 # self.release_global_lock()
693 def dia_site_preferences(self, widget, data=None):
694 dia = gtk.Dialog(_("Site Preferences"), self.window,
695 gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
696 (gtk.STOCK_SAVE, gtk.RESPONSE_ACCEPT, gtk.STOCK_CANCEL, gtk.RESPONSE_REJECT))
697 label = gtk.Label(_("Please select which sites you play on and enter your usernames."))
698 dia.vbox.add(label)
700 self.load_profile()
701 site_names = self.config.site_ids
702 available_site_names=[]
703 for site_name in site_names:
704 try:
705 tmp = self.config.supported_sites[site_name].enabled
706 available_site_names.append(site_name)
707 except KeyError:
708 pass
710 label = gtk.Label(_(" "))
711 dia.vbox.add(label)
713 column_headers=[_("Site"), _("Screen Name"), _("History Path"), _("Detect")] #TODO , _("Summary Path"), _("HUD")]
714 #HUD column will contain a button that shows favseat and HUD locations. Make it possible to load screenshot to arrange HUD windowlets.
715 table = gtk.Table(rows=len(available_site_names)+1, columns=len(column_headers), homogeneous=False)
716 dia.vbox.add(table)
718 for header_number in range (0, len(column_headers)):
719 label = gtk.Label(column_headers[header_number])
720 table.attach(label, header_number, header_number+1, 0, 1)
722 check_buttons=[]
723 screen_names=[]
724 history_paths=[]
725 detector = DetectInstalledSites.DetectInstalledSites()
727 y_pos=1
728 for site_number in range(0, len(available_site_names)):
729 check_button = gtk.CheckButton(label=available_site_names[site_number])
730 check_button.set_active(self.config.supported_sites[available_site_names[site_number]].enabled)
731 table.attach(check_button, 0, 1, y_pos, y_pos+1)
732 check_buttons.append(check_button)
734 entry = gtk.Entry()
735 entry.set_text(self.config.supported_sites[available_site_names[site_number]].screen_name)
736 table.attach(entry, 1, 2, y_pos, y_pos+1)
737 screen_names.append(entry)
739 entry = gtk.Entry()
740 entry.set_text(self.config.supported_sites[available_site_names[site_number]].HH_path)
741 table.attach(entry, 2, 3, y_pos, y_pos+1)
742 history_paths.append(entry)
744 if available_site_names[site_number] in detector.supportedSites:
745 button = gtk.Button(_("Detect"))
746 table.attach(button, 3, 4, y_pos, y_pos+1)
747 button.connect("clicked", self.detect_clicked, (detector, available_site_names[site_number], screen_names[site_number], history_paths[site_number]))
749 y_pos+=1
751 dia.show_all()
752 response = dia.run()
753 if (response == gtk.RESPONSE_ACCEPT):
754 for site_number in range(0, len(available_site_names)):
755 #print "site %s enabled=%s name=%s" % (available_site_names[site_number], check_buttons[site_number].get_active(), screen_names[site_number].get_text(), history_paths[site_number].get_text())
756 self.config.edit_site(available_site_names[site_number], str(check_buttons[site_number].get_active()), screen_names[site_number].get_text(), history_paths[site_number].get_text())
758 self.config.save()
759 self.reload_config(dia)
761 dia.destroy()
763 def detect_clicked(self, widget, data):
764 detector = data[0]
765 site_name = data[1]
766 entry_screen_name = data[2]
767 entry_history_path = data[3]
768 if detector.sitestatusdict[site_name]['detected']:
769 entry_screen_name.set_text(detector.sitestatusdict[site_name]['heroname'])
770 entry_history_path.set_text(detector.sitestatusdict[site_name]['hhpath'])
772 def reload_config(self, dia):
773 if len(self.nb_tab_names) == 1:
774 # only main tab open, reload profile
775 self.load_profile()
776 if dia: dia.destroy() # destroy prefs before raising warning, otherwise parent is dia rather than self.window
777 self.warning_box(_("If you had previously opened any tabs they cannot use the new settings without restart.")+" "+_("Re-start fpdb to load them."))
778 else:
779 if dia: dia.destroy() # destroy prefs before raising warning, otherwise parent is dia rather than self.window
780 self.warning_box(_("Updated preferences have not been loaded because windows are open.")+" "+_("Re-start fpdb to load them."))
782 def addLogText(self, text):
783 end_iter = self.logbuffer.get_end_iter()
784 self.logbuffer.insert(end_iter, text)
785 self.logview.scroll_to_mark(self.logbuffer.get_insert(), 0)
787 def process_close_messages(self):
788 # check for close messages
789 try:
790 while True:
791 name = self.closeq.get(False)
792 for i, t in enumerate(self.threads):
793 if str(t.__class__) == str(name):
794 # thread has ended so remove from list:
795 del self.threads[i]
796 break
797 except Queue.Empty:
798 # no close messages on queue, do nothing
799 pass
801 def __calendar_dialog(self, widget, entry):
802 # do not alter the modality of the parent
803 # self.dia_confirm.set_modal(False)
804 d = gtk.Window(gtk.WINDOW_TOPLEVEL)
805 d.set_transient_for(self.dia_confirm)
806 d.set_destroy_with_parent(True)
807 d.set_modal(True)
809 d.set_title(_('Pick a date'))
811 vb = gtk.VBox()
812 cal = gtk.Calendar()
813 vb.pack_start(cal, expand=False, padding=0)
815 btn = gtk.Button(_('Done'))
816 btn.connect('clicked', self.__get_date, cal, entry, d)
818 vb.pack_start(btn, expand=False, padding=4)
820 d.add(vb)
821 d.set_position(gtk.WIN_POS_MOUSE)
822 d.show_all()
824 def __get_dates(self):
825 t1 = self.h_start_date.get_text()
826 if t1 == '':
827 t1 = '1970-01-01'
828 t2 = self.start_date.get_text()
829 if t2 == '':
830 t2 = '1970-01-01'
831 return (t1, t2)
833 def __get_date(self, widget, calendar, entry, win):
834 # year and day are correct, month is 0..11
835 (year, month, day) = calendar.get_date()
836 month += 1
837 ds = '%04d-%02d-%02d' % (year, month, day)
838 entry.set_text(ds)
839 win.destroy()
840 self.dia_confirm.set_modal(True)
842 def get_menu(self, window):
843 """returns the menu for this program"""
844 fpdbmenu = """
845 <ui>
846 <menubar name="MenuBar">
847 <menu action="main">
848 <menuitem action="site_preferences"/>
849 <menuitem action="hud_preferences"/>
850 <menuitem action="advanced_preferences"/>
851 <separator/>
852 <menuitem action="Quit"/>
853 </menu>
854 <menu action="import">
855 <menuitem action="bulkimp"/>
856 <menuitem action="tourneyimp"/>
857 <menuitem action="imapimport"/>
858 <menuitem action="autoimp"/>
859 </menu>
860 <menu action="viewers">
861 <menuitem action="autoimp"/>
862 <menuitem action="hud_preferences"/>
863 <menuitem action="graphs"/>
864 <menuitem action="tourneygraphs"/>
865 <menuitem action="ringplayerstats"/>
866 <menuitem action="tourneyplayerstats"/>
867 <menuitem action="tourneyviewer"/>
868 <menuitem action="posnstats"/>
869 <menuitem action="sessionstats"/>
870 <menuitem action="replayer"/>
871 <menuitem action="stove"/>
872 </menu>
873 <menu action="database">
874 <menuitem action="maintaindbs"/>
875 <menuitem action="createtabs"/>
876 <menuitem action="rebuildhudcache"/>
877 <menuitem action="rebuildindexes"/>
878 <menuitem action="databasestats"/>
879 <menuitem action="dumptofile"/>
880 </menu>
881 <menu action="help">
882 <menuitem action="Logs"/>
883 <separator/>
884 <menuitem action="About"/>
885 </menu>
886 </menubar>
887 </ui>"""
889 uimanager = gtk.UIManager()
890 accel_group = uimanager.get_accel_group()
891 actiongroup = gtk.ActionGroup('UIManagerExample')
893 # Create actions
894 actiongroup.add_actions([('main', None, _('_Main')),
895 ('Quit', gtk.STOCK_QUIT, _('_Quit'), None, 'Quit the Program', self.quit),
896 ('site_preferences', None, _('_Site Preferences'), None, 'Site Preferences', self.dia_site_preferences),
897 ('advanced_preferences', None, _('_Advanced Preferences'), _('<control>F'), 'Edit your preferences', self.dia_advanced_preferences),
898 ('import', None, _('_Import')),
899 ('bulkimp', None, _('_Bulk Import'), _('<control>B'), 'Bulk Import', self.tab_bulk_import),
900 ('tourneyimp', None, _('Tournament _Results Import'), _('<control>R'), 'Tournament Results Import', self.tab_tourney_import),
901 ('imapimport', None, _('_Import through eMail/IMAP'), _('<control>I'), 'Import through eMail/IMAP', self.tab_imap_import),
902 ('viewers', None, _('_Viewers')),
903 ('autoimp', None, _('_Auto Import and HUD'), _('<control>A'), 'Auto Import and HUD', self.tab_auto_import),
904 ('hud_preferences', None, _('_HUD Preferences'), _('<control>H'), 'HUD Preferences', self.dia_hud_preferences),
905 ('graphs', None, _('_Graphs'), _('<control>G'), 'Graphs', self.tabGraphViewer),
906 ('tourneygraphs', None, _('Tourney Graphs'), None, 'TourneyGraphs', self.tabTourneyGraphViewer),
907 ('stove', None, _('Stove (preview)'), None, 'Stove', self.tabStove),
908 ('ringplayerstats', None, _('Ring _Player Stats'), _('<control>P'), 'Ring Player Stats ', self.tab_ring_player_stats),
909 ('tourneyplayerstats', None, _('_Tourney Stats'), _('<control>T'), 'Tourney Stats ', self.tab_tourney_player_stats),
910 ('tourneyviewer', None, _('Tourney _Viewer'), None, 'Tourney Viewer)', self.tab_tourney_viewer_stats),
911 ('posnstats', None, _('P_ositional Stats (tabulated view)'), _('<control>O'), 'Positional Stats (tabulated view)', self.tab_positional_stats),
912 ('sessionstats', None, _('Session Stats'), _('<control>S'), 'Session Stats', self.tab_session_stats),
913 ('replayer', None, _('Hand _Replayer (not working yet)'), None, 'Hand Replayer', self.tab_replayer),
914 ('database', None, _('_Database')),
915 ('maintaindbs', None, _('_Maintain Databases'), None, 'Maintain Databases', self.dia_maintain_dbs),
916 ('createtabs', None, _('Create or Recreate _Tables'), None, 'Create or Recreate Tables ', self.dia_recreate_tables),
917 ('rebuildhudcache', None, _('Rebuild HUD Cache'), None, 'Rebuild HUD Cache', self.dia_recreate_hudcache),
918 ('rebuildindexes', None, _('Rebuild DB Indexes'), None, 'Rebuild DB Indexes', self.dia_rebuild_indexes),
919 ('databasestats', None, _('_Statistics'), None, 'View Database Statistics', self.dia_database_stats),
920 ('dumptofile', None, _('Dump Database to Textfile (takes ALOT of time)'), None, 'Dump Database to Textfile (takes ALOT of time)', self.dia_dump_db),
921 ('help', None, _('_Help')),
922 ('Logs', None, _('_Log Messages'), None, 'Log and Debug Messages', self.dia_logs),
923 ('About', None, _('A_bout, License, Copying'), None, 'About the program', self.dia_about),
925 actiongroup.get_action('Quit').set_property('short-label', _('_Quit'))
927 # define keyboard shortcuts alt-1 through alt-0 for switching tabs
928 for key in range(10):
929 accel_group.connect_group(ord('%s' % key), gtk.gdk.MOD1_MASK, gtk.ACCEL_LOCKED, self.switch_to_tab)
931 uimanager.insert_action_group(actiongroup, 0)
932 merge_id = uimanager.add_ui_from_string(fpdbmenu)
934 # Create a MenuBar
935 menubar = uimanager.get_widget('/MenuBar')
936 window.add_accel_group(accel_group)
937 return menubar
938 #end def get_menu
940 def load_profile(self, create_db=False):
941 """Loads profile from the provided path name."""
942 self.config = Configuration.Config(file=options.config, dbname=options.dbname)
943 if self.config.file_error:
944 self.warning_box(_("There is an error in your config file\n") + self.config.file
945 + _("\n\nError is: ") + str(self.config.file_error),
946 diatitle=_("CONFIG FILE ERROR"))
947 sys.exit()
949 log = Configuration.get_logger("logging.conf", "fpdb", log_dir=self.config.dir_log)
950 print (_("Logfile is %s\n") % os.path.join(self.config.dir_log, self.config.log_file))
951 if self.config.example_copy:
952 self.info_box(_("Config file"),
953 _("Config file has been created at %s.") % self.config.file
954 + _("Edit your screen_name and hand history path in the supported_sites section of the Advanced Preferences window (Main menu) before trying to import hands."))
955 self.settings = {}
956 self.settings['global_lock'] = self.lock
957 if (os.sep == "/"):
958 self.settings['os'] = "linuxmac"
959 else:
960 self.settings['os'] = "windows"
962 self.settings.update({'cl_options': cl_options})
963 self.settings.update(self.config.get_db_parameters())
964 self.settings.update(self.config.get_import_parameters())
965 self.settings.update(self.config.get_default_paths())
967 if self.db is not None and self.db.is_connected():
968 self.db.disconnect()
970 self.sql = SQL.Sql(db_server=self.settings['db-server'])
971 err_msg = None
972 try:
973 self.db = Database.Database(self.config, sql=self.sql)
974 if self.db.get_backend_name() == 'SQLite':
975 # tell sqlite users where the db file is
976 print (_("Connected to SQLite: %s") % self.db.db_path)
977 except Exceptions.FpdbMySQLAccessDenied:
978 err_msg = _("MySQL Server reports: Access denied. Are your permissions set correctly?")
979 except Exceptions.FpdbMySQLNoDatabase:
980 err_msg = _("MySQL client reports: 2002 or 2003 error. Unable to connect - ") \
981 + _("Please check that the MySQL service has been started")
982 except Exceptions.FpdbPostgresqlAccessDenied:
983 err_msg = _("PostgreSQL Server reports: Access denied. Are your permissions set correctly?")
984 except Exceptions.FpdbPostgresqlNoDatabase:
985 err_msg = _("PostgreSQL client reports: Unable to connect - ") \
986 + _("Please check that the PostgreSQL service has been started")
987 if err_msg is not None:
988 self.db = None
989 self.warning_box(err_msg)
990 if self.db is not None and not self.db.is_connected():
991 self.db = None
993 # except FpdbMySQLFailedError:
994 # self.warning_box("Unable to connect to MySQL! Is the MySQL server running?!", "FPDB ERROR")
995 # exit()
996 # except FpdbError:
997 # #print "Failed to connect to %s database with username %s." % (self.settings['db-server'], self.settings['db-user'])
998 # self.warning_box("Failed to connect to %s database with username %s." % (self.settings['db-server'], self.settings['db-user']), "FPDB ERROR")
999 # err = traceback.extract_tb(sys.exc_info()[2])[-1]
1000 # print "*** Error: " + err[2] + "(" + str(err[1]) + "): " + str(sys.exc_info()[1])
1001 # sys.stderr.write("Failed to connect to %s database with username %s." % (self.settings['db-server'], self.settings['db-user']))
1002 # except:
1003 # #print "Failed to connect to %s database with username %s." % (self.settings['db-server'], self.settings['db-user'])
1004 # self.warning_box("Failed to connect to %s database with username %s." % (self.settings['db-server'], self.settings['db-user']), "FPDB ERROR")
1005 # err = traceback.extract_tb(sys.exc_info()[2])[-1]
1006 # print "*** Error: " + err[2] + "(" + str(err[1]) + "): " + str(sys.exc_info()[1])
1007 # sys.stderr.write("Failed to connect to %s database with username %s." % (self.settings['db-server'], self.settings['db-user']))
1009 if self.db is not None and self.db.wrongDbVersion:
1010 diaDbVersionWarning = gtk.Dialog(title=_("Strong Warning - Invalid database version"),
1011 parent=None, flags=0, buttons=(gtk.STOCK_OK, gtk.RESPONSE_OK))
1013 label = gtk.Label(_("An invalid DB version or missing tables have been detected."))
1014 diaDbVersionWarning.vbox.add(label)
1015 label.show()
1017 label = gtk.Label(_("This error is not necessarily fatal but it is strongly recommended that you recreate the tables by using the Database menu."))
1018 diaDbVersionWarning.vbox.add(label)
1019 label.show()
1021 label = gtk.Label(_("Not doing this will likely lead to misbehaviour including fpdb crashes, corrupt data etc."))
1022 diaDbVersionWarning.vbox.add(label)
1023 label.show()
1025 response = diaDbVersionWarning.run()
1026 diaDbVersionWarning.destroy()
1028 # TODO: This should probably be setup in GUI Init
1029 if self.status_bar is None:
1030 self.status_bar = gtk.Label("")
1031 self.main_vbox.pack_end(self.status_bar, False, True, 0)
1032 self.status_bar.show()
1034 if self.db is not None and self.db.is_connected():
1035 self.status_bar.set_text(_("Status: Connected to %s database named %s on host %s")
1036 % (self.db.get_backend_name(), self.db.database, self.db.host))
1037 # rollback to make sure any locks are cleared:
1038 self.db.rollback()
1040 self.validate_config()
1042 def obtain_global_lock(self, source):
1043 ret = self.lock.acquire(source=source) # will return false if lock is already held
1044 if ret:
1045 print (_("\nGlobal lock taken by %s") % source)
1046 self.lockTakenBy=source
1047 else:
1048 print (_("\nFailed to get global lock, it is currently held by %s") % source)
1049 return ret
1050 # need to release it later:
1051 # self.lock.release()
1053 def quit(self, widget, data=None):
1054 # TODO: can we get some / all of the stuff done in this function to execute on any kind of abort?
1055 #FIXME get two "quitting normally" messages, following the addition of the self.window.destroy() call
1056 # ... because self.window.destroy() leads to self.destroy() which calls this!
1057 if not self.quitting:
1058 print _("Quitting normally")
1059 self.quitting = True
1060 # TODO: check if current settings differ from profile, if so offer to save or abort
1062 if self.db is not None:
1063 if self.db.backend == self.db.MYSQL_INNODB:
1064 try:
1065 import _mysql_exceptions
1066 if self.db is not None and self.db.is_connected():
1067 self.db.disconnect()
1068 except _mysql_exceptions.OperationalError: # oh, damn, we're already disconnected
1069 pass
1070 else:
1071 if self.db is not None and self.db.is_connected():
1072 self.db.disconnect()
1073 else:
1074 pass
1075 self.statusIcon.set_visible(False)
1077 self.window.destroy() # explicitly destroy to allow child windows to close cleanly
1078 gtk.main_quit()
1080 def release_global_lock(self):
1081 self.lock.release()
1082 self.lockTakenBy = None
1083 print _("Global lock released.\n")
1085 def tab_auto_import(self, widget, data=None):
1086 """opens the auto import tab"""
1087 new_aimp_thread = GuiAutoImport.GuiAutoImport(self.settings, self.config, self.sql, self.window)
1088 self.threads.append(new_aimp_thread)
1089 aimp_tab = new_aimp_thread.get_vbox()
1090 self.add_and_display_tab(aimp_tab, _("Auto Import"))
1091 if options.autoimport:
1092 new_aimp_thread.startClicked(new_aimp_thread.startButton, "autostart")
1093 options.autoimport = False
1095 def tab_bulk_import(self, widget, data=None):
1096 """opens a tab for bulk importing"""
1097 new_import_thread = GuiBulkImport.GuiBulkImport(self.settings, self.config, self.sql, self.window)
1098 self.threads.append(new_import_thread)
1099 bulk_tab=new_import_thread.get_vbox()
1100 self.add_and_display_tab(bulk_tab, _("Bulk Import"))
1102 def tab_tourney_import(self, widget, data=None):
1103 """opens a tab for bulk importing tournament summaries"""
1104 new_import_thread = GuiTourneyImport.GuiTourneyImport(self.settings, self.config, self.sql, self.window)
1105 self.threads.append(new_import_thread)
1106 bulk_tab=new_import_thread.get_vbox()
1107 self.add_and_display_tab(bulk_tab, _("Tournament Results Import"))
1109 def tab_imap_import(self, widget, data=None):
1110 new_thread = GuiImapFetcher.GuiImapFetcher(self.config, self.db, self.sql, self.window)
1111 self.threads.append(new_thread)
1112 tab=new_thread.get_vbox()
1113 self.add_and_display_tab(tab, _("eMail Import"))
1114 #end def tab_import_imap_summaries
1116 def tab_ring_player_stats(self, widget, data=None):
1117 new_ps_thread = GuiRingPlayerStats.GuiRingPlayerStats(self.config, self.sql, self.window)
1118 self.threads.append(new_ps_thread)
1119 ps_tab=new_ps_thread.get_vbox()
1120 self.add_and_display_tab(ps_tab, _("Ring Player Stats"))
1122 def tab_tourney_player_stats(self, widget, data=None):
1123 new_ps_thread = GuiTourneyPlayerStats.GuiTourneyPlayerStats(self.config, self.db, self.sql, self.window)
1124 self.threads.append(new_ps_thread)
1125 ps_tab=new_ps_thread.get_vbox()
1126 self.add_and_display_tab(ps_tab, _("Tourney Stats"))
1128 def tab_tourney_viewer_stats(self, widget, data=None):
1129 new_thread = GuiTourneyViewer.GuiTourneyViewer(self.config, self.db, self.sql, self.window)
1130 self.threads.append(new_thread)
1131 tab=new_thread.get_vbox()
1132 self.add_and_display_tab(tab, _("Tourney Viewer"))
1134 def tab_positional_stats(self, widget, data=None):
1135 new_ps_thread = GuiPositionalStats.GuiPositionalStats(self.config, self.sql)
1136 self.threads.append(new_ps_thread)
1137 ps_tab=new_ps_thread.get_vbox()
1138 self.add_and_display_tab(ps_tab, _("Positional Stats"))
1140 def tab_session_stats(self, widget, data=None):
1141 new_ps_thread = GuiSessionViewer.GuiSessionViewer(self.config, self.sql, self.window)
1142 self.threads.append(new_ps_thread)
1143 ps_tab=new_ps_thread.get_vbox()
1144 self.add_and_display_tab(ps_tab, _("Session Stats"))
1146 def tab_replayer(self, widget, data=None):
1147 new_ps_thread = GuiReplayer.GuiReplayer(self.config, self.sql, self.window)
1148 self.threads.append(new_ps_thread)
1149 ps_tab=new_ps_thread.get_vbox()
1150 self.add_and_display_tab(ps_tab, _("Hand Replayer"))
1152 def tab_main_help(self, widget, data=None):
1153 """Displays a tab with the main fpdb help screen"""
1154 mh_tab=gtk.Label(_("""Fpdb needs translators!
1155 If you speak another language and have a few minutes or more to spare get in touch by emailing steffen@schaumburger.info
1157 Welcome to Fpdb!
1158 To be notified of new snapshots and releases go to https://lists.sourceforge.net/lists/listinfo/fpdb-announce and subscribe.
1159 If you want to follow development more closely go to https://lists.sourceforge.net/lists/listinfo/fpdb-main and subscribe.
1161 This program is currently in an alpha-state, so our database format is still sometimes changed.
1162 You should therefore always keep your hand history files so that you can re-import after an update, if necessary.
1164 For documentation please visit our website/wiki at http://fpdb.sourceforge.net/.
1165 If you need help click on Contact - Get Help on our website.
1166 Please note that default.conf is no longer needed nor used, all configuration now happens in HUD_config.xml.
1168 This program is free/libre open source software licensed partially under the AGPL3, and partially under GPL2 or later.
1169 The Windows installer package includes code licensed under the MIT license.
1170 You can find the full license texts in agpl-3.0.txt, gpl-2.0.txt, gpl-3.0.txt and mit.txt in the fpdb installation directory."""))
1171 self.add_and_display_tab(mh_tab, _("Help"))
1173 def tabGraphViewer(self, widget, data=None):
1174 """opens a graph viewer tab"""
1175 new_gv_thread = GuiGraphViewer.GuiGraphViewer(self.sql, self.config, self.window)
1176 self.threads.append(new_gv_thread)
1177 gv_tab = new_gv_thread.get_vbox()
1178 self.add_and_display_tab(gv_tab, _("Graphs"))
1180 def tabTourneyGraphViewer(self, widget, data=None):
1181 """opens a graph viewer tab"""
1182 new_gv_thread = GuiTourneyGraphViewer.GuiTourneyGraphViewer(self.sql, self.config, self.window)
1183 self.threads.append(new_gv_thread)
1184 gv_tab = new_gv_thread.get_vbox()
1185 self.add_and_display_tab(gv_tab, _("Tourney Graphs"))
1187 def tabStove(self, widget, data=None):
1188 """opens a tab for poker stove"""
1189 thread = GuiStove.GuiStove(self.config, self.window)
1190 self.threads.append(thread)
1191 tab = thread.get_vbox()
1192 self.add_and_display_tab(tab, _("Stove"))
1194 def __init__(self):
1195 # no more than 1 process can this lock at a time:
1196 self.lock = interlocks.InterProcessLock(name="fpdb_global_lock")
1197 self.db = None
1198 self.status_bar = None
1199 self.quitting = False
1200 self.visible = False
1201 self.threads = [] # objects used by tabs - no need for threads, gtk handles it
1202 self.closeq = Queue.Queue(20) # used to signal ending of a thread (only logviewer for now)
1204 # create window, move it to specific location on command line
1205 self.window = gtk.Window(gtk.WINDOW_TOPLEVEL)
1206 if options.xloc is not None or options.yloc is not None:
1207 if options.xloc is None:
1208 options.xloc = 0
1209 if options.yloc is None:
1210 options.yloc = 0
1211 self.window.move(options.xloc, options.yloc)
1213 # connect to required events
1214 self.window.connect("delete_event", self.delete_event)
1215 self.window.connect("destroy", self.destroy)
1216 self.window.set_title("Free Poker DB - v%s" % (VERSION, ))
1217 # set a default x/y size for the window
1218 self.window.set_border_width(1)
1219 defx, defy = 900, 720
1220 sx, sy = gtk.gdk.screen_width(), gtk.gdk.screen_height()
1221 if sx < defx:
1222 defx = sx
1223 if sy < defy:
1224 defy = sy
1225 self.window.set_default_size(defx, defy)
1226 self.window.set_resizable(True)
1228 # main area of window
1229 self.main_vbox = gtk.VBox(False, 1)
1230 self.main_vbox.set_border_width(1)
1231 self.window.add(self.main_vbox)
1232 self.main_vbox.show()
1234 # create our Main Menu Bar
1235 menubar = self.get_menu(self.window)
1236 self.main_vbox.pack_start(menubar, False, True, 0)
1237 menubar.show()
1239 # create a tab bar
1240 self.nb = gtk.Notebook()
1241 self.nb.set_show_tabs(True)
1242 self.nb.show()
1243 self.main_vbox.pack_start(self.nb, True, True, 0)
1244 self.tabs = [] # the event_boxes forming the actual tabs
1245 self.tab_names = [] # names of tabs used since program started, not removed if tab is closed
1246 self.pages = [] # the contents of the page, not removed if tab is closed
1247 self.nb_tab_names = [] # list of tab names currently displayed in notebook
1249 # create the first tab
1250 self.tab_main_help(None, None)
1252 # determine window visibility from command line options
1253 if options.minimized:
1254 self.window.iconify()
1255 if options.hidden:
1256 self.window.hide()
1258 if not options.hidden:
1259 self.window.show()
1260 self.visible = True # Flip on
1262 self.load_profile(create_db=True)
1264 # setup error logging
1265 if not options.errorsToConsole:
1266 fileName = os.path.join(self.config.dir_log, 'fpdb-errors.txt')
1267 print (_("\nNote: error output is being diverted to fpdb-errors.txt and HUD-errors.txt in: %s") % self.config.dir_log) \
1268 + _("\nAny major error will be reported there _only_.\n")
1269 errorFile = open(fileName, 'w', 0)
1270 sys.stderr = errorFile
1272 # set up tray-icon and menu
1273 self.statusIcon = gtk.StatusIcon()
1274 # use getcwd() here instead of sys.path[0] so that py2exe works:
1275 cards = os.path.join(os.getcwd(), '..', 'gfx', 'fpdb-cards.png')
1276 if os.path.exists(cards):
1277 self.statusIcon.set_from_file(cards)
1278 self.window.set_icon_from_file(cards)
1279 elif os.path.exists('/usr/share/pixmaps/fpdb-cards.png'):
1280 self.statusIcon.set_from_file('/usr/share/pixmaps/fpdb-cards.png')
1281 self.window.set_icon_from_file('/usr/share/pixmaps/fpdb-cards.png')
1282 else:
1283 self.statusIcon.set_from_stock(gtk.STOCK_HOME)
1284 self.statusIcon.set_tooltip("Free Poker Database")
1285 self.statusIcon.connect('activate', self.statusicon_activate)
1286 self.statusMenu = gtk.Menu()
1288 # set default menu options
1289 self.addImageToTrayMenu(gtk.STOCK_ABOUT, self.dia_about)
1290 self.addImageToTrayMenu(gtk.STOCK_QUIT, self.quit)
1292 self.statusIcon.connect('popup-menu', self.statusicon_menu, self.statusMenu)
1293 self.statusIcon.set_visible(True)
1295 self.window.connect('window-state-event', self.window_state_event_cb)
1296 sys.stderr.write(_("fpdb starting ..."))
1298 if options.autoimport:
1299 self.tab_auto_import(None)
1301 def addImageToTrayMenu(self, image, event=None):
1302 menuItem = gtk.ImageMenuItem(image)
1303 if event is not None:
1304 menuItem.connect('activate', event)
1305 self.statusMenu.append(menuItem)
1306 menuItem.show()
1307 return menuItem
1309 def addLabelToTrayMenu(self, label, event=None):
1310 menuItem = gtk.MenuItem(label)
1311 if event is not None:
1312 menuItem.connect('activate', event)
1313 self.statusMenu.append(menuItem)
1314 menuItem.show()
1315 return menuItem
1317 def removeFromTrayMenu(self, menuItem):
1318 menuItem.destroy()
1319 menuItem = None
1321 def __iconify(self):
1322 self.visible = False
1323 self.window.set_skip_taskbar_hint(True)
1324 self.window.set_skip_pager_hint(True)
1326 def __deiconify(self):
1327 self.visible = True
1328 self.window.set_skip_taskbar_hint(False)
1329 self.window.set_skip_pager_hint(False)
1331 def window_state_event_cb(self, window, event):
1332 # Deal with iconification first
1333 if event.changed_mask & gtk.gdk.WINDOW_STATE_ICONIFIED:
1334 if event.new_window_state & gtk.gdk.WINDOW_STATE_ICONIFIED:
1335 self.__iconify()
1336 else:
1337 self.__deiconify()
1338 if not event.new_window_state & gtk.gdk.WINDOW_STATE_WITHDRAWN:
1339 return True
1340 # And then the tray icon click
1341 if event.new_window_state & gtk.gdk.WINDOW_STATE_WITHDRAWN:
1342 self.__iconify()
1343 else:
1344 self.__deiconify()
1345 # Tell GTK not to propagate this signal any further
1346 return True
1348 def statusicon_menu(self, widget, button, time, data=None):
1349 # we don't need to pass data here, since we do keep track of most all
1350 # our variables .. the example code that i looked at for this
1351 # didn't use any long scope variables.. which might be an alright
1352 # idea too sometime
1353 if button == 3:
1354 if data:
1355 data.show_all()
1356 data.popup(None, None, None, 3, time)
1357 pass
1359 def statusicon_activate(self, widget, data=None):
1360 # Let's allow the tray icon to toggle window visibility, the way
1361 # most other apps work
1362 if self.visible:
1363 self.window.hide()
1364 else:
1365 self.window.present()
1367 def info_box(self, str1, str2):
1368 diapath = gtk.MessageDialog(parent=self.window, flags=gtk.DIALOG_DESTROY_WITH_PARENT, type=gtk.MESSAGE_INFO,
1369 buttons=(gtk.BUTTONS_OK), message_format=str1)
1370 diapath.format_secondary_text(str2)
1371 response = diapath.run()
1372 diapath.destroy()
1373 return response
1375 def warning_box(self, str, diatitle=_("FPDB WARNING")):
1376 diaWarning = gtk.Dialog(title=diatitle, parent=self.window,
1377 flags=gtk.DIALOG_DESTROY_WITH_PARENT,
1378 buttons=(gtk.STOCK_OK, gtk.RESPONSE_OK))
1380 label = gtk.Label(str)
1381 diaWarning.vbox.add(label)
1382 label.show()
1384 response = diaWarning.run()
1385 diaWarning.destroy()
1386 return response
1388 def validate_config(self):
1389 # check if sites in config file are in DB
1390 for site in self.config.supported_sites: # get site names from config file
1391 try:
1392 self.config.get_site_id(site) # and check against list from db
1393 except KeyError, exc:
1394 log.warning("site %s missing from db" % site)
1395 dia = gtk.MessageDialog(parent=None, flags=0, type=gtk.MESSAGE_WARNING, buttons=(gtk.BUTTONS_OK), message_format=_("Unknown Site"))
1396 diastring = _("Warning:") +" " + _("Unable to find site '%s'") % site
1397 dia.format_secondary_text(diastring)
1398 dia.run()
1399 dia.destroy()
1401 def main(self):
1402 gtk.main()
1403 return 0
1406 if __name__ == "__main__":
1407 me = fpdb()
1408 me.main()