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