Add a 'close' button to the 'Help' tab, just like all the other tabs.
[fpdb-dooglus.git] / pyfpdb / fpdb.pyw
blob5547d740b44ab8131cf6c37f946f2fca572e2707
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 tabButton = gtk.Button()
198 tabButton.connect('clicked', self.remove_tab, (nb, text))
199 #Add a picture on a button
200 self.add_icon_to_button(tabButton)
201 tabBox.pack_start(tabButton, False)
203 # needed, otherwise even calling show_all on the notebook won't
204 # make the hbox contents appear.
205 tabBox.show_all()
206 return eventBox
208 def add_icon_to_button(self, button):
209 iconBox = gtk.HBox(False, 0)
210 image = gtk.Image()
211 image.set_from_stock(gtk.STOCK_CLOSE, gtk.ICON_SIZE_SMALL_TOOLBAR)
212 gtk.Button.set_relief(button, gtk.RELIEF_NONE)
213 settings = gtk.Widget.get_settings(button)
214 (w, h) = gtk.icon_size_lookup_for_settings(settings, gtk.ICON_SIZE_SMALL_TOOLBAR)
215 gtk.Widget.set_size_request(button, w + 4, h + 4)
216 image.show()
217 iconBox.pack_start(image, True, False, 0)
218 button.add(iconBox)
219 iconBox.show()
220 return
222 # Remove a page from the notebook
223 def remove_tab(self, button, data):
224 (nb, text) = data
225 page = -1
226 #print "\n remove_tab: start", text
227 for i, tab in enumerate(self.nb_tab_names):
228 if text == tab:
229 page = i
230 #print " page =", page
231 if page >= 0 and page < self.nb.get_n_pages():
232 #print " removing page", page
233 del self.nb_tab_names[page]
234 nb.remove_page(page)
235 # Need to refresh the widget --
236 # This forces the widget to redraw itself.
237 #nb.queue_draw_area(0,0,-1,-1) needed or not??
239 def delete_event(self, widget, event, data=None):
240 return False
242 def destroy(self, widget, data=None):
243 self.quit(widget)
245 def dia_about(self, widget, data=None):
246 dia = gtk.AboutDialog()
247 dia.set_name("Free Poker Database (FPDB)")
248 dia.set_version(VERSION)
249 dia.set_copyright(_("Copyright 2008-2011, Steffen, Eratosthenes, Carl Gherardi, Eric Blade, _mt, sqlcoder, Bostik, and others"))
250 dia.set_comments(_("You are free to change, and distribute original or changed versions of fpdb within the rules set out by the license"))
251 dia.set_license(_("Please see fpdb's start screen for license information"))
252 dia.set_website("http://fpdb.sourceforge.net/")
254 dia.set_authors(['Steffen', 'Eratosthenes', 'Carl Gherardi',
255 'Eric Blade', '_mt', 'sqlcoder', 'Bostik', _('and others')])
256 dia.set_program_name("Free Poker Database (FPDB)")
258 if (os.name=="posix"):
259 os_text=str(os.uname())
260 elif (os.name=="nt"):
261 import platform
262 os_text=("Windows" + " " + str(platform.win32_ver()))
263 else:
264 os_text="Unknown"
266 import locale
267 nums = [(_('Operating System'), os_text),
268 ('Python', sys.version[0:3]),
269 ('GTK+', '.'.join([str(x) for x in gtk.gtk_version])),
270 ('PyGTK', '.'.join([str(x) for x in gtk.pygtk_version])),
271 ('matplotlib', matplotlib_version),
272 ('numpy', numpy_version),
273 ('sqlite', sqlite_version),
274 (_('fpdb version'), VERSION),
275 (_('database used'), self.settings['db-server']),
276 (_('language'), locale.getdefaultlocale()[0]),
277 (_('character encoding'), locale.getdefaultlocale()[1])
279 versions = gtk.TextBuffer()
280 w = 20 # width used for module names and version numbers
281 versions.set_text('\n'.join([x[0].rjust(w) + ': ' + x[1].ljust(w) for x in nums]))
282 view = gtk.TextView(versions)
283 view.set_editable(False)
284 view.set_justification(gtk.JUSTIFY_CENTER)
285 view.modify_font(pango.FontDescription('monospace 10'))
286 view.show()
287 dia.vbox.pack_end(view, True, True, 2)
289 l = gtk.Label(_("Your config file is: ") + self.config.file)
290 l.set_alignment(0.5, 0.5)
291 l.show()
292 dia.vbox.pack_end(l, True, True, 2)
294 l = gtk.Label(_('Version Information:'))
295 l.set_alignment(0.5, 0.5)
296 l.show()
297 dia.vbox.pack_end(l, True, True, 2)
299 dia.run()
300 dia.destroy()
301 log.debug(_("Threads: "))
302 for t in self.threads:
303 log.debug("........." + str(t.__class__))
305 def dia_advanced_preferences(self, widget, data=None):
306 dia = gtk.Dialog(_("Advanced Preferences"),
307 self.window,
308 gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
309 (gtk.STOCK_CANCEL, gtk.RESPONSE_REJECT,
310 gtk.STOCK_SAVE, gtk.RESPONSE_ACCEPT))
311 dia.set_default_size(700, 500)
313 #force reload of prefs from xml file - needed because HUD could
314 #have changed file contents
315 self.load_profile()
316 prefs = GuiPrefs.GuiPrefs(self.config, self.window, dia.vbox, dia)
317 response = dia.run()
318 if response == gtk.RESPONSE_ACCEPT:
319 # save updated config
320 self.config.save()
321 self.reload_config(dia)
322 else:
323 dia.destroy()
325 def dia_maintain_dbs(self, widget, data=None):
326 #self.warning_box("Unimplemented: Maintain Databases")
327 #return
328 if len(self.tab_names) == 1:
329 if self.obtain_global_lock("dia_maintain_dbs"): # returns true if successful
330 # only main tab has been opened, open dialog
331 dia = gtk.Dialog(_("Maintain Databases"),
332 self.window,
333 gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
334 (gtk.STOCK_CANCEL, gtk.RESPONSE_REJECT,
335 gtk.STOCK_SAVE, gtk.RESPONSE_ACCEPT))
336 dia.set_default_size(700, 320)
338 prefs = GuiDatabase.GuiDatabase(self.config, self.window, dia)
339 response = dia.run()
340 if response == gtk.RESPONSE_ACCEPT:
341 log.info(_('saving updated db data'))
342 # save updated config
343 self.config.save()
344 self.load_profile()
345 for name in self.config.supported_databases: # db_ip/db_user/db_pass/db_server
346 log.info('fpdb: name,desc=' + name + ',' + self.config.supported_databases[name].db_desc)
347 else:
348 log.info(_('guidb response was ') + str(response))
350 self.release_global_lock()
352 dia.destroy()
353 else:
354 self.warning_box(_("Cannot open Database Maintenance window because other windows have been opened. Re-start fpdb to use this option."))
356 def dia_database_stats(self, widget, data=None):
357 self.warning_box(str=_("Number of Hands: ") + str(self.db.getHandCount()) +
358 _("\nNumber of Tourneys: ") + str(self.db.getTourneyCount()) +
359 _("\nNumber of TourneyTypes: ") + str(self.db.getTourneyTypeCount()),
360 diatitle=_("Database Statistics"))
361 #end def dia_database_stats
363 def dia_hud_preferences(self, widget, data=None):
364 """Opens dialog to set parameters (game category, row count, column count) for HUD preferences"""
365 #Note: No point in working on this until the new HUD configuration system is in place
366 self.hud_preferences_rows = None
367 self.hud_preferences_columns = None
368 self.hud_preferences_game = None
370 diaSelections = gtk.Dialog(_("HUD Preferences - choose category"),
371 self.window,
372 gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
373 (gtk.STOCK_OK, gtk.RESPONSE_ACCEPT,
374 gtk.STOCK_CANCEL, gtk.RESPONSE_REJECT))
376 label = gtk.Label(_("Note that this does not existing settings, but overwrites them."))
377 diaSelections.vbox.add(label)
378 label.show()
380 label = gtk.Label(_("Please select the game category for which you want to configure HUD stats:"))
381 diaSelections.vbox.add(label)
382 label.show()
384 comboGame = gtk.combo_box_new_text()
385 comboGame.connect("changed", self.hud_preferences_combo_selection)
386 diaSelections.vbox.add(comboGame)
387 games = self.config.get_supported_games()
388 for game in games:
389 comboGame.append_text(game)
390 comboGame.set_active(0)
391 comboGame.show()
393 comboRows = gtk.combo_box_new_text()
394 comboRows.connect("changed", self.hud_preferences_combo_selection)
395 diaSelections.vbox.add(comboRows)
396 for i in range(1, 8):
397 comboRows.append_text(str(i) + " rows")
398 comboRows.set_active(0)
399 comboRows.show()
401 comboColumns = gtk.combo_box_new_text()
402 comboColumns.connect("changed", self.hud_preferences_combo_selection)
403 diaSelections.vbox.add(comboColumns)
404 for i in range(1, 8):
405 comboColumns.append_text(str(i) + " columns")
406 comboColumns.set_active(0)
407 comboColumns.show()
409 self.load_profile()
410 response = diaSelections.run()
411 diaSelections.destroy()
413 if (response == gtk.RESPONSE_ACCEPT and
414 self.hud_preferences_rows != None and
415 self.hud_preferences_columns != None and
416 self.hud_preferences_game != None):
417 self.dia_hud_preferences_table()
418 #end def dia_hud_preferences
420 def hud_preferences_combo_selection(self, widget):
421 #TODO: remove this and handle it directly in dia_hud_preferences
422 result = widget.get_active_text()
423 if result.endswith(" rows"):
424 self.hud_preferences_rows = int(result[0])
425 elif result.endswith(" columns"):
426 self.hud_preferences_columns = int(result[0])
427 else:
428 self.hud_preferences_game = result
429 #end def hud_preferences_combo_selection
431 def dia_hud_preferences_table(self):
432 """shows dialogue with Table of ComboBoxes to allow choosing of HUD stats"""
433 #TODO: show explanation of what each stat means
434 diaHudTable = gtk.Dialog(_("HUD Preferences - please choose your stats"),
435 self.window,
436 gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
437 (gtk.STOCK_SAVE, gtk.RESPONSE_ACCEPT,
438 gtk.STOCK_CANCEL, gtk.RESPONSE_REJECT))
440 label = gtk.Label(_("Please choose the stats you wish to use in the below table."))
441 diaHudTable.vbox.add(label)
442 label.show()
444 label = gtk.Label(_("Note that you may not select any stat more than once or it will crash."))
445 diaHudTable.vbox.add(label)
446 label.show()
448 label = gtk.Label(_("It is not currently possible to select \"empty\" or anything else to that end."))
449 diaHudTable.vbox.add(label)
450 label.show()
452 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."))
453 diaHudTable.vbox.add(label)
454 label.show()
456 self.hud_preferences_table_contents = []
457 table = gtk.Table(rows=self.hud_preferences_rows + 1, columns=self.hud_preferences_columns + 1, homogeneous=True)
459 statDict = Stats.build_stat_descriptions(Stats)
461 for rowNumber in range(self.hud_preferences_rows + 1):
462 newRow = []
463 for columnNumber in range(self.hud_preferences_columns + 1):
464 if rowNumber == 0:
465 if columnNumber == 0:
466 pass
467 else:
468 label = gtk.Label("column " + str(columnNumber))
469 table.attach(child=label, left_attach=columnNumber,
470 right_attach=columnNumber + 1,
471 top_attach=rowNumber,
472 bottom_attach=rowNumber + 1)
473 label.show()
474 elif columnNumber == 0:
475 label = gtk.Label("row " + str(rowNumber))
476 table.attach(child=label, left_attach=columnNumber,
477 right_attach=columnNumber + 1,
478 top_attach=rowNumber,
479 bottom_attach=rowNumber + 1)
480 label.show()
481 else:
482 comboBox = gtk.combo_box_new_text()
484 for stat in sorted(statDict.values()):
485 comboBox.append_text(stat)
486 comboBox.set_active(0)
488 newRow.append(comboBox)
489 table.attach(child=comboBox, left_attach=columnNumber,
490 right_attach=columnNumber + 1,
491 top_attach=rowNumber,
492 bottom_attach=rowNumber + 1)
494 comboBox.show()
495 if rowNumber != 0:
496 self.hud_preferences_table_contents.append(newRow)
497 diaHudTable.vbox.add(table)
498 table.show()
500 response = diaHudTable.run()
501 diaHudTable.destroy()
503 if response == gtk.RESPONSE_ACCEPT:
504 self.storeNewHudStatConfig(statDict)
505 #end def dia_hud_preferences_table
507 def storeNewHudStatConfig(self, stat_dict):
508 """stores selections made in dia_hud_preferences_table"""
509 self.obtain_global_lock("dia_hud_preferences")
510 statTable = []
511 for row in self.hud_preferences_table_contents:
512 newRow = []
513 for column in row:
514 new_field = column.get_active_text()
515 for attr in stat_dict: #very inefficient, but who cares
516 if new_field == stat_dict[attr]:
517 newRow.append(attr)
518 break
519 statTable.append(newRow)
521 self.config.editStats(self.hud_preferences_game, statTable)
522 self.config.save() # TODO: make it not store in horrible formatting
523 self.reload_config(None)
524 self.release_global_lock()
525 #end def storeNewHudStatConfig
527 def dia_dump_db(self, widget, data=None):
528 filename = "database-dump.sql"
529 result = self.db.dumpDatabase()
531 dumpFile = open(filename, 'w')
532 dumpFile.write(result)
533 dumpFile.close()
534 #end def dia_database_stats
536 def dia_recreate_tables(self, widget, data=None):
537 """Dialogue that asks user to confirm that he wants to delete and recreate the tables"""
538 if self.obtain_global_lock("fpdb.dia_recreate_tables"): # returns true if successful
540 #lock_released = False
541 dia_confirm = gtk.MessageDialog(parent=self.window, flags=gtk.DIALOG_DESTROY_WITH_PARENT, type=gtk.MESSAGE_WARNING,
542 buttons=(gtk.BUTTONS_YES_NO), message_format=_("Confirm deleting and recreating tables"))
543 diastring = _("Please confirm that you want to (re-)create the tables.") \
544 + (_(" 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)) \
545 + _("This may take a while.")
546 dia_confirm.format_secondary_text(diastring) # todo: make above string with bold for db, host and deleted
547 # disable windowclose, do not want the the underlying processing interrupted mid-process
548 dia_confirm.set_deletable(False)
550 response = dia_confirm.run()
551 dia_confirm.destroy()
552 if response == gtk.RESPONSE_YES:
553 #if self.db.backend == self.fdb_lock.fdb.MYSQL_INNODB:
554 # mysql requires locks on all tables or none - easier to release this lock
555 # than lock all the other tables
556 # ToDo: lock all other tables so that lock doesn't have to be released
557 # self.release_global_lock()
558 # lock_released = True
559 self.db.recreate_tables()
560 # find any guibulkimport/guiautoimport windows and clear player cache:
561 for t in self.threads:
562 if isinstance(t, GuiBulkImport.GuiBulkImport) or isinstance(t, GuiAutoImport.GuiAutoImport):
563 t.importer.database.resetPlayerIDs()
564 self.release_global_lock()
565 #else:
566 # for other dbs use same connection as holds global lock
567 # self.fdb_lock.fdb.recreate_tables()
568 elif response == gtk.RESPONSE_NO:
569 self.release_global_lock()
570 print _('User cancelled recreating tables')
571 #if not lock_released:
572 #end def dia_recreate_tables
574 def dia_recreate_hudcache(self, widget, data=None):
575 if self.obtain_global_lock("dia_recreate_hudcache"):
576 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")
577 diastring = _("Please confirm that you want to re-create the HUD cache.")
578 self.dia_confirm.format_secondary_text(diastring)
579 # disable windowclose, do not want the the underlying processing interrupted mid-process
580 self.dia_confirm.set_deletable(False)
582 hb1 = gtk.HBox(True, 1)
583 self.h_start_date = gtk.Entry(max=12)
584 self.h_start_date.set_text(self.db.get_hero_hudcache_start())
585 lbl = gtk.Label(_(" Hero's cache starts: "))
586 btn = gtk.Button()
587 btn.set_image(gtk.image_new_from_stock(gtk.STOCK_INDEX, gtk.ICON_SIZE_BUTTON))
588 btn.connect('clicked', self.__calendar_dialog, self.h_start_date)
590 hb1.pack_start(lbl, expand=True, padding=3)
591 hb1.pack_start(self.h_start_date, expand=True, padding=2)
592 hb1.pack_start(btn, expand=False, padding=3)
593 self.dia_confirm.vbox.add(hb1)
594 hb1.show_all()
596 hb2 = gtk.HBox(True, 1)
597 self.start_date = gtk.Entry(max=12)
598 self.start_date.set_text(self.db.get_hero_hudcache_start())
599 lbl = gtk.Label(_(" Villains' cache starts: "))
600 btn = gtk.Button()
601 btn.set_image(gtk.image_new_from_stock(gtk.STOCK_INDEX, gtk.ICON_SIZE_BUTTON))
602 btn.connect('clicked', self.__calendar_dialog, self.start_date)
604 hb2.pack_start(lbl, expand=True, padding=3)
605 hb2.pack_start(self.start_date, expand=True, padding=2)
606 hb2.pack_start(btn, expand=False, padding=3)
607 self.dia_confirm.vbox.add(hb2)
608 hb2.show_all()
610 response = self.dia_confirm.run()
611 if response == gtk.RESPONSE_YES:
612 lbl = gtk.Label(_(" Rebuilding HUD Cache ... "))
613 self.dia_confirm.vbox.add(lbl)
614 lbl.show()
615 while gtk.events_pending():
616 gtk.main_iteration_do(False)
618 self.db.rebuild_hudcache(self.h_start_date.get_text(), self.start_date.get_text())
619 elif response == gtk.RESPONSE_NO:
620 print _('User cancelled rebuilding hud cache')
622 self.dia_confirm.destroy()
624 self.release_global_lock()
626 def dia_rebuild_indexes(self, widget, data=None):
627 if self.obtain_global_lock("dia_rebuild_indexes"):
628 self.dia_confirm = gtk.MessageDialog(parent=self.window,
629 flags=gtk.DIALOG_DESTROY_WITH_PARENT,
630 type=gtk.MESSAGE_WARNING,
631 buttons=(gtk.BUTTONS_YES_NO),
632 message_format=_("Confirm rebuilding database indexes"))
633 diastring = _("Please confirm that you want to rebuild the database indexes.")
634 self.dia_confirm.format_secondary_text(diastring)
635 # disable windowclose, do not want the the underlying processing interrupted mid-process
636 self.dia_confirm.set_deletable(False)
638 response = self.dia_confirm.run()
639 if response == gtk.RESPONSE_YES:
640 #FIXME these progress messages do not seem to work in *nix
641 lbl = gtk.Label(_(" Rebuilding Indexes ... "))
642 self.dia_confirm.vbox.add(lbl)
643 lbl.show()
644 while gtk.events_pending():
645 gtk.main_iteration_do(False)
646 self.db.rebuild_indexes()
648 lbl.set_text(_(" Cleaning Database ... "))
649 while gtk.events_pending():
650 gtk.main_iteration_do(False)
651 self.db.vacuumDB()
653 lbl.set_text(_(" Analyzing Database ... "))
654 while gtk.events_pending():
655 gtk.main_iteration_do(False)
656 self.db.analyzeDB()
657 elif response == gtk.RESPONSE_NO:
658 print _('User cancelled rebuilding db indexes')
660 self.dia_confirm.destroy()
662 self.release_global_lock()
664 def dia_logs(self, widget, data=None):
665 """opens the log viewer window"""
667 #lock_set = False
668 #if self.obtain_global_lock("dia_logs"):
669 # lock_set = True
671 # remove members from self.threads if close messages received
672 self.process_close_messages()
674 viewer = None
675 for i, t in enumerate(self.threads):
676 if str(t.__class__) == 'GuiLogView.GuiLogView':
677 viewer = t
678 break
680 if viewer is None:
681 #print "creating new log viewer"
682 new_thread = GuiLogView.GuiLogView(self.config, self.window, self.closeq)
683 self.threads.append(new_thread)
684 else:
685 #print "showing existing log viewer"
686 viewer.get_dialog().present()
688 #if lock_set:
689 # self.release_global_lock()
691 def dia_site_preferences(self, widget, data=None):
692 dia = gtk.Dialog(_("Site Preferences"), self.window,
693 gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
694 (gtk.STOCK_SAVE, gtk.RESPONSE_ACCEPT, gtk.STOCK_CANCEL, gtk.RESPONSE_REJECT))
695 label = gtk.Label(_("Please select which sites you play on and enter your usernames."))
696 dia.vbox.add(label)
698 self.load_profile()
699 site_names = self.config.site_ids
700 available_site_names=[]
701 for site_name in site_names:
702 try:
703 tmp = self.config.supported_sites[site_name].enabled
704 available_site_names.append(site_name)
705 except KeyError:
706 pass
708 label = gtk.Label(_(" "))
709 dia.vbox.add(label)
711 column_headers=[_("Site"), _("Screen Name"), _("History Path"), _("Detect")] #TODO , _("Summary Path"), _("HUD")]
712 #HUD column will contain a button that shows favseat and HUD locations. Make it possible to load screenshot to arrange HUD windowlets.
713 table = gtk.Table(rows=len(available_site_names)+1, columns=len(column_headers), homogeneous=False)
714 dia.vbox.add(table)
716 for header_number in range (0, len(column_headers)):
717 label = gtk.Label(column_headers[header_number])
718 table.attach(label, header_number, header_number+1, 0, 1)
720 check_buttons=[]
721 screen_names=[]
722 history_paths=[]
723 detector = DetectInstalledSites.DetectInstalledSites()
725 y_pos=1
726 for site_number in range(0, len(available_site_names)):
727 check_button = gtk.CheckButton(label=available_site_names[site_number])
728 check_button.set_active(self.config.supported_sites[available_site_names[site_number]].enabled)
729 table.attach(check_button, 0, 1, y_pos, y_pos+1)
730 check_buttons.append(check_button)
732 entry = gtk.Entry()
733 entry.set_text(self.config.supported_sites[available_site_names[site_number]].screen_name)
734 table.attach(entry, 1, 2, y_pos, y_pos+1)
735 screen_names.append(entry)
737 entry = gtk.Entry()
738 entry.set_text(self.config.supported_sites[available_site_names[site_number]].HH_path)
739 table.attach(entry, 2, 3, y_pos, y_pos+1)
740 history_paths.append(entry)
742 if available_site_names[site_number] in detector.supportedSites:
743 button = gtk.Button(_("Detect"))
744 table.attach(button, 3, 4, y_pos, y_pos+1)
745 button.connect("clicked", self.detect_clicked, (detector, available_site_names[site_number], screen_names[site_number], history_paths[site_number]))
747 y_pos+=1
749 dia.show_all()
750 response = dia.run()
751 if (response == gtk.RESPONSE_ACCEPT):
752 for site_number in range(0, len(available_site_names)):
753 #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())
754 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())
756 self.config.save()
757 self.reload_config(dia)
759 dia.destroy()
761 def detect_clicked(self, widget, data):
762 detector = data[0]
763 site_name = data[1]
764 entry_screen_name = data[2]
765 entry_history_path = data[3]
766 if detector.sitestatusdict[site_name]['detected']:
767 entry_screen_name.set_text(detector.sitestatusdict[site_name]['heroname'])
768 entry_history_path.set_text(detector.sitestatusdict[site_name]['hhpath'])
770 def reload_config(self, dia):
771 if len(self.nb_tab_names) == 1:
772 # only main tab open, reload profile
773 self.load_profile()
774 if dia: dia.destroy() # destroy prefs before raising warning, otherwise parent is dia rather than self.window
775 self.warning_box(_("If you had previously opened any tabs they cannot use the new settings without restart.")+" "+_("Re-start fpdb to load them."))
776 else:
777 if dia: dia.destroy() # destroy prefs before raising warning, otherwise parent is dia rather than self.window
778 self.warning_box(_("Updated preferences have not been loaded because windows are open.")+" "+_("Re-start fpdb to load them."))
780 def addLogText(self, text):
781 end_iter = self.logbuffer.get_end_iter()
782 self.logbuffer.insert(end_iter, text)
783 self.logview.scroll_to_mark(self.logbuffer.get_insert(), 0)
785 def process_close_messages(self):
786 # check for close messages
787 try:
788 while True:
789 name = self.closeq.get(False)
790 for i, t in enumerate(self.threads):
791 if str(t.__class__) == str(name):
792 # thread has ended so remove from list:
793 del self.threads[i]
794 break
795 except Queue.Empty:
796 # no close messages on queue, do nothing
797 pass
799 def __calendar_dialog(self, widget, entry):
800 # do not alter the modality of the parent
801 # self.dia_confirm.set_modal(False)
802 d = gtk.Window(gtk.WINDOW_TOPLEVEL)
803 d.set_transient_for(self.dia_confirm)
804 d.set_destroy_with_parent(True)
805 d.set_modal(True)
807 d.set_title(_('Pick a date'))
809 vb = gtk.VBox()
810 cal = gtk.Calendar()
811 vb.pack_start(cal, expand=False, padding=0)
813 btn = gtk.Button(_('Done'))
814 btn.connect('clicked', self.__get_date, cal, entry, d)
816 vb.pack_start(btn, expand=False, padding=4)
818 d.add(vb)
819 d.set_position(gtk.WIN_POS_MOUSE)
820 d.show_all()
822 def __get_dates(self):
823 t1 = self.h_start_date.get_text()
824 if t1 == '':
825 t1 = '1970-01-01'
826 t2 = self.start_date.get_text()
827 if t2 == '':
828 t2 = '1970-01-01'
829 return (t1, t2)
831 def __get_date(self, widget, calendar, entry, win):
832 # year and day are correct, month is 0..11
833 (year, month, day) = calendar.get_date()
834 month += 1
835 ds = '%04d-%02d-%02d' % (year, month, day)
836 entry.set_text(ds)
837 win.destroy()
838 self.dia_confirm.set_modal(True)
840 def get_menu(self, window):
841 """returns the menu for this program"""
842 fpdbmenu = """
843 <ui>
844 <menubar name="MenuBar">
845 <menu action="main">
846 <menuitem action="site_preferences"/>
847 <menuitem action="hud_preferences"/>
848 <menuitem action="advanced_preferences"/>
849 <separator/>
850 <menuitem action="Quit"/>
851 </menu>
852 <menu action="import">
853 <menuitem action="bulkimp"/>
854 <menuitem action="tourneyimp"/>
855 <menuitem action="imapimport"/>
856 <menuitem action="autoimp"/>
857 </menu>
858 <menu action="viewers">
859 <menuitem action="autoimp"/>
860 <menuitem action="hud_preferences"/>
861 <menuitem action="graphs"/>
862 <menuitem action="tourneygraphs"/>
863 <menuitem action="ringplayerstats"/>
864 <menuitem action="tourneyplayerstats"/>
865 <menuitem action="tourneyviewer"/>
866 <menuitem action="posnstats"/>
867 <menuitem action="sessionstats"/>
868 <menuitem action="replayer"/>
869 <menuitem action="stove"/>
870 </menu>
871 <menu action="database">
872 <menuitem action="maintaindbs"/>
873 <menuitem action="createtabs"/>
874 <menuitem action="rebuildhudcache"/>
875 <menuitem action="rebuildindexes"/>
876 <menuitem action="databasestats"/>
877 <menuitem action="dumptofile"/>
878 </menu>
879 <menu action="help">
880 <menuitem action="Logs"/>
881 <separator/>
882 <menuitem action="About"/>
883 </menu>
884 </menubar>
885 </ui>"""
887 uimanager = gtk.UIManager()
888 accel_group = uimanager.get_accel_group()
889 actiongroup = gtk.ActionGroup('UIManagerExample')
891 # Create actions
892 actiongroup.add_actions([('main', None, _('_Main')),
893 ('Quit', gtk.STOCK_QUIT, _('_Quit'), None, 'Quit the Program', self.quit),
894 ('site_preferences', None, _('_Site Preferences'), None, 'Site Preferences', self.dia_site_preferences),
895 ('advanced_preferences', None, _('_Advanced Preferences'), _('<control>F'), 'Edit your preferences', self.dia_advanced_preferences),
896 ('import', None, _('_Import')),
897 ('bulkimp', None, _('_Bulk Import'), _('<control>B'), 'Bulk Import', self.tab_bulk_import),
898 ('tourneyimp', None, _('Tournament _Results Import'), _('<control>R'), 'Tournament Results Import', self.tab_tourney_import),
899 ('imapimport', None, _('_Import through eMail/IMAP'), _('<control>I'), 'Import through eMail/IMAP', self.tab_imap_import),
900 ('viewers', None, _('_Viewers')),
901 ('autoimp', None, _('_Auto Import and HUD'), _('<control>A'), 'Auto Import and HUD', self.tab_auto_import),
902 ('hud_preferences', None, _('_HUD Preferences'), _('<control>H'), 'HUD Preferences', self.dia_hud_preferences),
903 ('graphs', None, _('_Graphs'), _('<control>G'), 'Graphs', self.tabGraphViewer),
904 ('tourneygraphs', None, _('Tourney Graphs'), None, 'TourneyGraphs', self.tabTourneyGraphViewer),
905 ('stove', None, _('Stove (preview)'), None, 'Stove', self.tabStove),
906 ('ringplayerstats', None, _('Ring _Player Stats'), _('<control>P'), 'Ring Player Stats ', self.tab_ring_player_stats),
907 ('tourneyplayerstats', None, _('_Tourney Stats'), _('<control>T'), 'Tourney Stats ', self.tab_tourney_player_stats),
908 ('tourneyviewer', None, _('Tourney _Viewer'), None, 'Tourney Viewer)', self.tab_tourney_viewer_stats),
909 ('posnstats', None, _('P_ositional Stats (tabulated view)'), _('<control>O'), 'Positional Stats (tabulated view)', self.tab_positional_stats),
910 ('sessionstats', None, _('Session Stats'), _('<control>S'), 'Session Stats', self.tab_session_stats),
911 ('replayer', None, _('Hand _Replayer (not working yet)'), None, 'Hand Replayer', self.tab_replayer),
912 ('database', None, _('_Database')),
913 ('maintaindbs', None, _('_Maintain Databases'), None, 'Maintain Databases', self.dia_maintain_dbs),
914 ('createtabs', None, _('Create or Recreate _Tables'), None, 'Create or Recreate Tables ', self.dia_recreate_tables),
915 ('rebuildhudcache', None, _('Rebuild HUD Cache'), None, 'Rebuild HUD Cache', self.dia_recreate_hudcache),
916 ('rebuildindexes', None, _('Rebuild DB Indexes'), None, 'Rebuild DB Indexes', self.dia_rebuild_indexes),
917 ('databasestats', None, _('_Statistics'), None, 'View Database Statistics', self.dia_database_stats),
918 ('dumptofile', None, _('Dump Database to Textfile (takes ALOT of time)'), None, 'Dump Database to Textfile (takes ALOT of time)', self.dia_dump_db),
919 ('help', None, _('_Help')),
920 ('Logs', None, _('_Log Messages'), None, 'Log and Debug Messages', self.dia_logs),
921 ('About', None, _('A_bout, License, Copying'), None, 'About the program', self.dia_about),
923 actiongroup.get_action('Quit').set_property('short-label', _('_Quit'))
925 # define keyboard shortcuts alt-1 through alt-0 for switching tabs
926 for key in range(10):
927 accel_group.connect_group(ord('%s' % key), gtk.gdk.MOD1_MASK, gtk.ACCEL_LOCKED, self.switch_to_tab)
929 uimanager.insert_action_group(actiongroup, 0)
930 merge_id = uimanager.add_ui_from_string(fpdbmenu)
932 # Create a MenuBar
933 menubar = uimanager.get_widget('/MenuBar')
934 window.add_accel_group(accel_group)
935 return menubar
936 #end def get_menu
938 def load_profile(self, create_db=False):
939 """Loads profile from the provided path name."""
940 self.config = Configuration.Config(file=options.config, dbname=options.dbname)
941 if self.config.file_error:
942 self.warning_box(_("There is an error in your config file\n") + self.config.file
943 + _("\n\nError is: ") + str(self.config.file_error),
944 diatitle=_("CONFIG FILE ERROR"))
945 sys.exit()
947 log = Configuration.get_logger("logging.conf", "fpdb", log_dir=self.config.dir_log)
948 print (_("Logfile is %s\n") % os.path.join(self.config.dir_log, self.config.log_file))
949 if self.config.example_copy:
950 self.info_box(_("Config file"),
951 _("Config file has been created at %s.") % self.config.file
952 + _("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."))
953 self.settings = {}
954 self.settings['global_lock'] = self.lock
955 if (os.sep == "/"):
956 self.settings['os'] = "linuxmac"
957 else:
958 self.settings['os'] = "windows"
960 self.settings.update({'cl_options': cl_options})
961 self.settings.update(self.config.get_db_parameters())
962 self.settings.update(self.config.get_import_parameters())
963 self.settings.update(self.config.get_default_paths())
965 if self.db is not None and self.db.is_connected():
966 self.db.disconnect()
968 self.sql = SQL.Sql(db_server=self.settings['db-server'])
969 err_msg = None
970 try:
971 self.db = Database.Database(self.config, sql=self.sql)
972 if self.db.get_backend_name() == 'SQLite':
973 # tell sqlite users where the db file is
974 print (_("Connected to SQLite: %s") % self.db.db_path)
975 except Exceptions.FpdbMySQLAccessDenied:
976 err_msg = _("MySQL Server reports: Access denied. Are your permissions set correctly?")
977 except Exceptions.FpdbMySQLNoDatabase:
978 err_msg = _("MySQL client reports: 2002 or 2003 error. Unable to connect - ") \
979 + _("Please check that the MySQL service has been started")
980 except Exceptions.FpdbPostgresqlAccessDenied:
981 err_msg = _("PostgreSQL Server reports: Access denied. Are your permissions set correctly?")
982 except Exceptions.FpdbPostgresqlNoDatabase:
983 err_msg = _("PostgreSQL client reports: Unable to connect - ") \
984 + _("Please check that the PostgreSQL service has been started")
985 if err_msg is not None:
986 self.db = None
987 self.warning_box(err_msg)
988 if self.db is not None and not self.db.is_connected():
989 self.db = None
991 # except FpdbMySQLFailedError:
992 # self.warning_box("Unable to connect to MySQL! Is the MySQL server running?!", "FPDB ERROR")
993 # exit()
994 # except FpdbError:
995 # #print "Failed to connect to %s database with username %s." % (self.settings['db-server'], self.settings['db-user'])
996 # self.warning_box("Failed to connect to %s database with username %s." % (self.settings['db-server'], self.settings['db-user']), "FPDB ERROR")
997 # err = traceback.extract_tb(sys.exc_info()[2])[-1]
998 # print "*** Error: " + err[2] + "(" + str(err[1]) + "): " + str(sys.exc_info()[1])
999 # sys.stderr.write("Failed to connect to %s database with username %s." % (self.settings['db-server'], self.settings['db-user']))
1000 # except:
1001 # #print "Failed to connect to %s database with username %s." % (self.settings['db-server'], self.settings['db-user'])
1002 # self.warning_box("Failed to connect to %s database with username %s." % (self.settings['db-server'], self.settings['db-user']), "FPDB ERROR")
1003 # err = traceback.extract_tb(sys.exc_info()[2])[-1]
1004 # print "*** Error: " + err[2] + "(" + str(err[1]) + "): " + str(sys.exc_info()[1])
1005 # sys.stderr.write("Failed to connect to %s database with username %s." % (self.settings['db-server'], self.settings['db-user']))
1007 if self.db is not None and self.db.wrongDbVersion:
1008 diaDbVersionWarning = gtk.Dialog(title=_("Strong Warning - Invalid database version"),
1009 parent=None, flags=0, buttons=(gtk.STOCK_OK, gtk.RESPONSE_OK))
1011 label = gtk.Label(_("An invalid DB version or missing tables have been detected."))
1012 diaDbVersionWarning.vbox.add(label)
1013 label.show()
1015 label = gtk.Label(_("This error is not necessarily fatal but it is strongly recommended that you recreate the tables by using the Database menu."))
1016 diaDbVersionWarning.vbox.add(label)
1017 label.show()
1019 label = gtk.Label(_("Not doing this will likely lead to misbehaviour including fpdb crashes, corrupt data etc."))
1020 diaDbVersionWarning.vbox.add(label)
1021 label.show()
1023 response = diaDbVersionWarning.run()
1024 diaDbVersionWarning.destroy()
1026 # TODO: This should probably be setup in GUI Init
1027 if self.status_bar is None:
1028 self.status_bar = gtk.Label("")
1029 self.main_vbox.pack_end(self.status_bar, False, True, 0)
1030 self.status_bar.show()
1032 if self.db is not None and self.db.is_connected():
1033 self.status_bar.set_text(_("Status: Connected to %s database named %s on host %s")
1034 % (self.db.get_backend_name(), self.db.database, self.db.host))
1035 # rollback to make sure any locks are cleared:
1036 self.db.rollback()
1038 self.validate_config()
1040 def obtain_global_lock(self, source):
1041 ret = self.lock.acquire(source=source) # will return false if lock is already held
1042 if ret:
1043 print (_("\nGlobal lock taken by %s") % source)
1044 self.lockTakenBy=source
1045 else:
1046 print (_("\nFailed to get global lock, it is currently held by %s") % source)
1047 return ret
1048 # need to release it later:
1049 # self.lock.release()
1051 def quit(self, widget, data=None):
1052 # TODO: can we get some / all of the stuff done in this function to execute on any kind of abort?
1053 #FIXME get two "quitting normally" messages, following the addition of the self.window.destroy() call
1054 # ... because self.window.destroy() leads to self.destroy() which calls this!
1055 if not self.quitting:
1056 print _("Quitting normally")
1057 self.quitting = True
1058 # TODO: check if current settings differ from profile, if so offer to save or abort
1060 if self.db is not None:
1061 if self.db.backend == self.db.MYSQL_INNODB:
1062 try:
1063 import _mysql_exceptions
1064 if self.db is not None and self.db.is_connected():
1065 self.db.disconnect()
1066 except _mysql_exceptions.OperationalError: # oh, damn, we're already disconnected
1067 pass
1068 else:
1069 if self.db is not None and self.db.is_connected():
1070 self.db.disconnect()
1071 else:
1072 pass
1073 self.statusIcon.set_visible(False)
1075 self.window.destroy() # explicitly destroy to allow child windows to close cleanly
1076 gtk.main_quit()
1078 def release_global_lock(self):
1079 self.lock.release()
1080 self.lockTakenBy = None
1081 print _("Global lock released.\n")
1083 def tab_auto_import(self, widget, data=None):
1084 """opens the auto import tab"""
1085 new_aimp_thread = GuiAutoImport.GuiAutoImport(self.settings, self.config, self.sql, self.window)
1086 self.threads.append(new_aimp_thread)
1087 aimp_tab = new_aimp_thread.get_vbox()
1088 self.add_and_display_tab(aimp_tab, _("Auto Import"))
1089 if options.autoimport:
1090 new_aimp_thread.startClicked(new_aimp_thread.startButton, "autostart")
1091 options.autoimport = False
1093 def tab_bulk_import(self, widget, data=None):
1094 """opens a tab for bulk importing"""
1095 new_import_thread = GuiBulkImport.GuiBulkImport(self.settings, self.config, self.sql, self.window)
1096 self.threads.append(new_import_thread)
1097 bulk_tab=new_import_thread.get_vbox()
1098 self.add_and_display_tab(bulk_tab, _("Bulk Import"))
1100 def tab_tourney_import(self, widget, data=None):
1101 """opens a tab for bulk importing tournament summaries"""
1102 new_import_thread = GuiTourneyImport.GuiTourneyImport(self.settings, self.config, self.sql, self.window)
1103 self.threads.append(new_import_thread)
1104 bulk_tab=new_import_thread.get_vbox()
1105 self.add_and_display_tab(bulk_tab, _("Tournament Results Import"))
1107 def tab_imap_import(self, widget, data=None):
1108 new_thread = GuiImapFetcher.GuiImapFetcher(self.config, self.db, self.sql, self.window)
1109 self.threads.append(new_thread)
1110 tab=new_thread.get_vbox()
1111 self.add_and_display_tab(tab, _("eMail Import"))
1112 #end def tab_import_imap_summaries
1114 def tab_ring_player_stats(self, widget, data=None):
1115 new_ps_thread = GuiRingPlayerStats.GuiRingPlayerStats(self.config, self.sql, self.window)
1116 self.threads.append(new_ps_thread)
1117 ps_tab=new_ps_thread.get_vbox()
1118 self.add_and_display_tab(ps_tab, _("Ring Player Stats"))
1120 def tab_tourney_player_stats(self, widget, data=None):
1121 new_ps_thread = GuiTourneyPlayerStats.GuiTourneyPlayerStats(self.config, self.db, self.sql, self.window)
1122 self.threads.append(new_ps_thread)
1123 ps_tab=new_ps_thread.get_vbox()
1124 self.add_and_display_tab(ps_tab, _("Tourney Stats"))
1126 def tab_tourney_viewer_stats(self, widget, data=None):
1127 new_thread = GuiTourneyViewer.GuiTourneyViewer(self.config, self.db, self.sql, self.window)
1128 self.threads.append(new_thread)
1129 tab=new_thread.get_vbox()
1130 self.add_and_display_tab(tab, _("Tourney Viewer"))
1132 def tab_positional_stats(self, widget, data=None):
1133 new_ps_thread = GuiPositionalStats.GuiPositionalStats(self.config, self.sql)
1134 self.threads.append(new_ps_thread)
1135 ps_tab=new_ps_thread.get_vbox()
1136 self.add_and_display_tab(ps_tab, _("Positional Stats"))
1138 def tab_session_stats(self, widget, data=None):
1139 new_ps_thread = GuiSessionViewer.GuiSessionViewer(self.config, self.sql, self.window)
1140 self.threads.append(new_ps_thread)
1141 ps_tab=new_ps_thread.get_vbox()
1142 self.add_and_display_tab(ps_tab, _("Session Stats"))
1144 def tab_replayer(self, widget, data=None):
1145 new_ps_thread = GuiReplayer.GuiReplayer(self.config, self.sql, self.window)
1146 self.threads.append(new_ps_thread)
1147 ps_tab=new_ps_thread.get_vbox()
1148 self.add_and_display_tab(ps_tab, _("Hand Replayer"))
1150 def tab_main_help(self, widget, data=None):
1151 """Displays a tab with the main fpdb help screen"""
1152 mh_tab=gtk.Label(_("""Fpdb needs translators!
1153 If you speak another language and have a few minutes or more to spare get in touch by emailing steffen@schaumburger.info
1155 Welcome to Fpdb!
1156 To be notified of new snapshots and releases go to https://lists.sourceforge.net/lists/listinfo/fpdb-announce and subscribe.
1157 If you want to follow development more closely go to https://lists.sourceforge.net/lists/listinfo/fpdb-main and subscribe.
1159 This program is currently in an alpha-state, so our database format is still sometimes changed.
1160 You should therefore always keep your hand history files so that you can re-import after an update, if necessary.
1162 For documentation please visit our website/wiki at http://fpdb.sourceforge.net/.
1163 If you need help click on Contact - Get Help on our website.
1164 Please note that default.conf is no longer needed nor used, all configuration now happens in HUD_config.xml.
1166 This program is free/libre open source software licensed partially under the AGPL3, and partially under GPL2 or later.
1167 The Windows installer package includes code licensed under the MIT license.
1168 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."""))
1169 self.add_and_display_tab(mh_tab, _("Help"))
1171 def tabGraphViewer(self, widget, data=None):
1172 """opens a graph viewer tab"""
1173 new_gv_thread = GuiGraphViewer.GuiGraphViewer(self.sql, self.config, self.window)
1174 self.threads.append(new_gv_thread)
1175 gv_tab = new_gv_thread.get_vbox()
1176 self.add_and_display_tab(gv_tab, _("Graphs"))
1178 def tabTourneyGraphViewer(self, widget, data=None):
1179 """opens a graph viewer tab"""
1180 new_gv_thread = GuiTourneyGraphViewer.GuiTourneyGraphViewer(self.sql, self.config, self.window)
1181 self.threads.append(new_gv_thread)
1182 gv_tab = new_gv_thread.get_vbox()
1183 self.add_and_display_tab(gv_tab, _("Tourney Graphs"))
1185 def tabStove(self, widget, data=None):
1186 """opens a tab for poker stove"""
1187 thread = GuiStove.GuiStove(self.config, self.window)
1188 self.threads.append(thread)
1189 tab = thread.get_vbox()
1190 self.add_and_display_tab(tab, _("Stove"))
1192 def __init__(self):
1193 # no more than 1 process can this lock at a time:
1194 self.lock = interlocks.InterProcessLock(name="fpdb_global_lock")
1195 self.db = None
1196 self.status_bar = None
1197 self.quitting = False
1198 self.visible = False
1199 self.threads = [] # objects used by tabs - no need for threads, gtk handles it
1200 self.closeq = Queue.Queue(20) # used to signal ending of a thread (only logviewer for now)
1202 # create window, move it to specific location on command line
1203 self.window = gtk.Window(gtk.WINDOW_TOPLEVEL)
1204 if options.xloc is not None or options.yloc is not None:
1205 if options.xloc is None:
1206 options.xloc = 0
1207 if options.yloc is None:
1208 options.yloc = 0
1209 self.window.move(options.xloc, options.yloc)
1211 # connect to required events
1212 self.window.connect("delete_event", self.delete_event)
1213 self.window.connect("destroy", self.destroy)
1214 self.window.set_title("Free Poker DB - v%s" % (VERSION, ))
1215 # set a default x/y size for the window
1216 self.window.set_border_width(1)
1217 defx, defy = 900, 720
1218 sx, sy = gtk.gdk.screen_width(), gtk.gdk.screen_height()
1219 if sx < defx:
1220 defx = sx
1221 if sy < defy:
1222 defy = sy
1223 self.window.set_default_size(defx, defy)
1224 self.window.set_resizable(True)
1226 # main area of window
1227 self.main_vbox = gtk.VBox(False, 1)
1228 self.main_vbox.set_border_width(1)
1229 self.window.add(self.main_vbox)
1230 self.main_vbox.show()
1232 # create our Main Menu Bar
1233 menubar = self.get_menu(self.window)
1234 self.main_vbox.pack_start(menubar, False, True, 0)
1235 menubar.show()
1237 # create a tab bar
1238 self.nb = gtk.Notebook()
1239 self.nb.set_show_tabs(True)
1240 self.nb.show()
1241 self.main_vbox.pack_start(self.nb, True, True, 0)
1242 self.tabs = [] # the event_boxes forming the actual tabs
1243 self.tab_names = [] # names of tabs used since program started, not removed if tab is closed
1244 self.pages = [] # the contents of the page, not removed if tab is closed
1245 self.nb_tab_names = [] # list of tab names currently displayed in notebook
1247 # create the first tab
1248 self.tab_main_help(None, None)
1250 # determine window visibility from command line options
1251 if options.minimized:
1252 self.window.iconify()
1253 if options.hidden:
1254 self.window.hide()
1256 if not options.hidden:
1257 self.window.show()
1258 self.visible = True # Flip on
1260 self.load_profile(create_db=True)
1262 # setup error logging
1263 if not options.errorsToConsole:
1264 fileName = os.path.join(self.config.dir_log, 'fpdb-errors.txt')
1265 print (_("\nNote: error output is being diverted to fpdb-errors.txt and HUD-errors.txt in: %s") % self.config.dir_log) \
1266 + _("\nAny major error will be reported there _only_.\n")
1267 errorFile = open(fileName, 'w', 0)
1268 sys.stderr = errorFile
1270 # set up tray-icon and menu
1271 self.statusIcon = gtk.StatusIcon()
1272 # use getcwd() here instead of sys.path[0] so that py2exe works:
1273 cards = os.path.join(os.getcwd(), '..', 'gfx', 'fpdb-cards.png')
1274 if os.path.exists(cards):
1275 self.statusIcon.set_from_file(cards)
1276 self.window.set_icon_from_file(cards)
1277 elif os.path.exists('/usr/share/pixmaps/fpdb-cards.png'):
1278 self.statusIcon.set_from_file('/usr/share/pixmaps/fpdb-cards.png')
1279 self.window.set_icon_from_file('/usr/share/pixmaps/fpdb-cards.png')
1280 else:
1281 self.statusIcon.set_from_stock(gtk.STOCK_HOME)
1282 self.statusIcon.set_tooltip("Free Poker Database")
1283 self.statusIcon.connect('activate', self.statusicon_activate)
1284 self.statusMenu = gtk.Menu()
1286 # set default menu options
1287 self.addImageToTrayMenu(gtk.STOCK_ABOUT, self.dia_about)
1288 self.addImageToTrayMenu(gtk.STOCK_QUIT, self.quit)
1290 self.statusIcon.connect('popup-menu', self.statusicon_menu, self.statusMenu)
1291 self.statusIcon.set_visible(True)
1293 self.window.connect('window-state-event', self.window_state_event_cb)
1294 sys.stderr.write(_("fpdb starting ..."))
1296 if options.autoimport:
1297 self.tab_auto_import(None)
1299 def addImageToTrayMenu(self, image, event=None):
1300 menuItem = gtk.ImageMenuItem(image)
1301 if event is not None:
1302 menuItem.connect('activate', event)
1303 self.statusMenu.append(menuItem)
1304 menuItem.show()
1305 return menuItem
1307 def addLabelToTrayMenu(self, label, event=None):
1308 menuItem = gtk.MenuItem(label)
1309 if event is not None:
1310 menuItem.connect('activate', event)
1311 self.statusMenu.append(menuItem)
1312 menuItem.show()
1313 return menuItem
1315 def removeFromTrayMenu(self, menuItem):
1316 menuItem.destroy()
1317 menuItem = None
1319 def __iconify(self):
1320 self.visible = False
1321 self.window.set_skip_taskbar_hint(True)
1322 self.window.set_skip_pager_hint(True)
1324 def __deiconify(self):
1325 self.visible = True
1326 self.window.set_skip_taskbar_hint(False)
1327 self.window.set_skip_pager_hint(False)
1329 def window_state_event_cb(self, window, event):
1330 # Deal with iconification first
1331 if event.changed_mask & gtk.gdk.WINDOW_STATE_ICONIFIED:
1332 if event.new_window_state & gtk.gdk.WINDOW_STATE_ICONIFIED:
1333 self.__iconify()
1334 else:
1335 self.__deiconify()
1336 if not event.new_window_state & gtk.gdk.WINDOW_STATE_WITHDRAWN:
1337 return True
1338 # And then the tray icon click
1339 if event.new_window_state & gtk.gdk.WINDOW_STATE_WITHDRAWN:
1340 self.__iconify()
1341 else:
1342 self.__deiconify()
1343 # Tell GTK not to propagate this signal any further
1344 return True
1346 def statusicon_menu(self, widget, button, time, data=None):
1347 # we don't need to pass data here, since we do keep track of most all
1348 # our variables .. the example code that i looked at for this
1349 # didn't use any long scope variables.. which might be an alright
1350 # idea too sometime
1351 if button == 3:
1352 if data:
1353 data.show_all()
1354 data.popup(None, None, None, 3, time)
1355 pass
1357 def statusicon_activate(self, widget, data=None):
1358 # Let's allow the tray icon to toggle window visibility, the way
1359 # most other apps work
1360 if self.visible:
1361 self.window.hide()
1362 else:
1363 self.window.present()
1365 def info_box(self, str1, str2):
1366 diapath = gtk.MessageDialog(parent=self.window, flags=gtk.DIALOG_DESTROY_WITH_PARENT, type=gtk.MESSAGE_INFO,
1367 buttons=(gtk.BUTTONS_OK), message_format=str1)
1368 diapath.format_secondary_text(str2)
1369 response = diapath.run()
1370 diapath.destroy()
1371 return response
1373 def warning_box(self, str, diatitle=_("FPDB WARNING")):
1374 diaWarning = gtk.Dialog(title=diatitle, parent=self.window,
1375 flags=gtk.DIALOG_DESTROY_WITH_PARENT,
1376 buttons=(gtk.STOCK_OK, gtk.RESPONSE_OK))
1378 label = gtk.Label(str)
1379 diaWarning.vbox.add(label)
1380 label.show()
1382 response = diaWarning.run()
1383 diaWarning.destroy()
1384 return response
1386 def validate_config(self):
1387 # check if sites in config file are in DB
1388 for site in self.config.supported_sites: # get site names from config file
1389 try:
1390 self.config.get_site_id(site) # and check against list from db
1391 except KeyError, exc:
1392 log.warning("site %s missing from db" % site)
1393 dia = gtk.MessageDialog(parent=None, flags=0, type=gtk.MESSAGE_WARNING, buttons=(gtk.BUTTONS_OK), message_format=_("Unknown Site"))
1394 diastring = _("Warning:") +" " + _("Unable to find site '%s'") % site
1395 dia.format_secondary_text(diastring)
1396 dia.run()
1397 dia.destroy()
1399 def main(self):
1400 gtk.main()
1401 return 0
1404 if __name__ == "__main__":
1405 me = fpdb()
1406 me.main()