#!/usr/bin/python #Copyright 2008 Steffen Jobbagy-Felso #This program is free software: you can redistribute it and/or modify #it under the terms of the GNU Affero General Public License as published by #the Free Software Foundation, version 3 of the License. # #This program is distributed in the hope that it will be useful, #but WITHOUT ANY WARRANTY; without even the implied warranty of #MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #GNU General Public License for more details. # #You should have received a copy of the GNU Affero General Public License #along with this program. If not, see . #In the "official" distribution you can find the license in #agpl-3.0.txt in the docs folder of the package. import threading import pygtk pygtk.require('2.0') import gtk import os from time import time, strftime import fpdb_import import fpdb_db import Filters import FpdbSQLQueries class GuiPositionalStats (threading.Thread): def __init__(self, config, querylist, debug=True): self.debug=debug self.conf=config self.MYSQL_INNODB = 2 self.PGSQL = 3 self.SQLITE = 4 # create new db connection to avoid conflicts with other threads self.db = fpdb_db.fpdb_db() self.db.do_connect(self.conf) self.cursor=self.db.cursor self.sql = querylist settings = {} settings.update(config.get_db_parameters()) settings.update(config.get_tv_parameters()) settings.update(config.get_import_parameters()) settings.update(config.get_default_paths()) filters_display = { "Heroes" : True, "Sites" : True, "Games" : False, "Limits" : True, "LimitSep" : True, "Seats" : True, "SeatSep" : True, "Dates" : True, "Button1" : True, "Button2" : False } self.filters = Filters.Filters(self.db, settings, config, querylist, display = filters_display) self.filters.registerButton1Name("Refresh") self.filters.registerButton1Callback(self.refreshStats) # ToDo: store in config # ToDo: create popup to adjust column config # columns to display, keys match column name returned by sql, values in tuple are: # is column displayed, column heading, xalignment, formatting self.columns = [ ["game", True, "Game", 0.0, "%s"] , ["hand", False, "Hand", 0.0, "%s"] # true not allowed for this line , ["plposition", False, "Posn", 1.0, "%s"] # true not allowed for this line (set in code) , ["n", True, "Hds", 1.0, "%d"] , ["avgseats", True, "Seats", 1.0, "%3.1f"] , ["vpip", True, "VPIP", 1.0, "%3.1f"] , ["pfr", True, "PFR", 1.0, "%3.1f"] , ["pf3", True, "PF3", 1.0, "%3.1f"] , ["steals", True, "Steals", 1.0, "%3.1f"] , ["saw_f", True, "Saw_F", 1.0, "%3.1f"] , ["sawsd", True, "SawSD", 1.0, "%3.1f"] , ["wtsdwsf", True, "WtSDwsF", 1.0, "%3.1f"] , ["wmsd", True, "W$SD", 1.0, "%3.1f"] , ["flafq", True, "FlAFq", 1.0, "%3.1f"] , ["tuafq", True, "TuAFq", 1.0, "%3.1f"] , ["rvafq", True, "RvAFq", 1.0, "%3.1f"] , ["pofafq", False, "PoFAFq", 1.0, "%3.1f"] , ["net", True, "Net($)", 1.0, "%6.2f"] , ["bbper100", True, "bb/100", 1.0, "%4.2f"] , ["rake", True, "Rake($)", 1.0, "%6.2f"] , ["bb100xr", True, "bbxr/100", 1.0, "%4.2f"] , ["variance", True, "Variance", 1.0, "%5.2f"] ] self.stat_table = None self.stats_frame = None self.stats_vbox = None self.main_hbox = gtk.HBox(False, 0) self.main_hbox.show() self.stats_frame = gtk.Frame() self.stats_frame.set_label_align(0.0, 0.0) self.stats_frame.show() self.stats_vbox = gtk.VBox(False, 0) self.stats_vbox.show() # This could be stored in config eventually, or maybe configured in this window somehow. # Each posncols element is the name of a column returned by the sql # query (in lower case) and each posnheads element is the text to use as # the heading in the GUI. Both sequences should be the same length. # To miss columns out remove them from both tuples (the 1st 2 elements should always be included). # To change the heading just edit the second list element as required # If the first list element does not match a query column that pair is ignored self.posncols = ( "game", "avgseats", "plposition", "vpip", "pfr", "pf3", "steals" , "saw_f", "sawsd", "wtsdwsf", "wmsd", "flafq", "tuafq", "rvafq" , "pofafq", "net", "bbper100", "profitperhand", "variance", "n" ) self.posnheads = ( "Game", "Seats", "Posn", "VPIP", "PFR", "PF3", "Steals" , "Saw_F", "SawSD", "WtSDwsF", "W$SD", "FlAFq", "TuAFq", "RvAFq" , "PoFAFq", "Net($)", "bb/100", "$/hand", "Variance", "Hds" ) self.fillStatsFrame(self.stats_vbox) self.stats_frame.add(self.stats_vbox) self.main_hbox.pack_start(self.filters.get_vbox()) self.main_hbox.pack_start(self.stats_frame) def get_vbox(self): """returns the vbox of this thread""" return self.main_hbox def toggleCallback(self, widget, data=None): # print "%s was toggled %s" % (data, ("OFF", "ON")[widget.get_active()]) self.activesite = data print "DEBUG: activesite set to %s" %(self.activesite) def refreshStats(self, widget, data): try: self.stats_vbox.destroy() except AttributeError: pass self.stats_vbox = gtk.VBox(False, 0) self.stats_vbox.show() self.stats_frame.add(self.stats_vbox) self.fillStatsFrame(self.stats_vbox) def fillStatsFrame(self, vbox): sites = self.filters.getSites() heroes = self.filters.getHeroes() siteids = self.filters.getSiteIds() limits = self.filters.getLimits() seats = self.filters.getSeats() dates = self.filters.getDates() sitenos = [] playerids = [] # Which sites are selected? for site in sites: if sites[site] == True: sitenos.append(siteids[site]) self.cursor.execute(self.sql.query['getPlayerId'], (heroes[site],)) result = self.db.cursor.fetchall() if len(result) == 1: playerids.append(result[0][0]) if not sitenos: #Should probably pop up here. print "No sites selected - defaulting to PokerStars" sitenos = [2] if not playerids: print "No player ids found" return if not limits: print "No limits found" return self.createStatsTable(vbox, playerids, sitenos, limits, seats, dates) def createStatsTable(self, vbox, playerids, sitenos, limits, seats, dates): starttime = time() colalias,colshow,colheading,colxalign,colformat = 0,1,2,3,4 row = 0 col = 0 tmp = self.sql.query['playerStatsByPosition'] tmp = self.refineQuery(tmp, playerids, sitenos, limits, seats, dates) self.cursor.execute(tmp) result = self.cursor.fetchall() colnames = [desc[0].lower() for desc in self.cursor.description] liststore = gtk.ListStore(*([str] * len(colnames))) view = gtk.TreeView(model=liststore) view.set_grid_lines(gtk.TREE_VIEW_GRID_LINES_BOTH) vbox.pack_start(view, expand=False, padding=3) # left-aligned cells: textcell = gtk.CellRendererText() # centred cells: textcell50 = gtk.CellRendererText() textcell50.set_property('xalign', 0.5) # right-aligned cells: numcell = gtk.CellRendererText() numcell.set_property('xalign', 1.0) listcols = [] for t in self.posnheads: listcols.append(gtk.TreeViewColumn(self.posnheads[col])) view.append_column(listcols[col]) if col == 0: listcols[col].pack_start(textcell, expand=True) listcols[col].add_attribute(textcell, 'text', col) listcols[col].set_expand(True) elif col in (1, 2): listcols[col].pack_start(textcell50, expand=True) listcols[col].add_attribute(textcell50, 'text', col) listcols[col].set_expand(True) else: listcols[col].pack_start(numcell, expand=True) listcols[col].add_attribute(numcell, 'text', col) listcols[col].set_expand(True) col +=1 # Code below to be used when full column data structures implemented like in player stats: # Create header row eg column: ("game", True, "Game", 0.0, "%s") #for col, column in enumerate(cols_to_show): # if column[colalias] == 'game' and holecards: # s = [x for x in self.columns if x[colalias] == 'hand'][0][colheading] # else: # s = column[colheading] # listcols.append(gtk.TreeViewColumn(s)) # view.append_column(listcols[col]) # if column[colformat] == '%s': # if column[colxalign] == 0.0: # listcols[col].pack_start(textcell, expand=True) # listcols[col].add_attribute(textcell, 'text', col) # else: # listcols[col].pack_start(textcell50, expand=True) # listcols[col].add_attribute(textcell50, 'text', col) # listcols[col].set_expand(True) # else: # listcols[col].pack_start(numcell, expand=True) # listcols[col].add_attribute(numcell, 'text', col) # listcols[col].set_expand(True) # #listcols[col].set_alignment(column[colxalign]) # no effect? rows = len(result) last_game,last_seats,sqlrow = "","",0 while sqlrow < rows: rowprinted=0 treerow = [] avgcol = colnames.index('avgseats') for col,colname in enumerate(self.posncols): if colname in colnames: sqlcol = colnames.index(colname) else: continue if result[sqlrow][sqlcol]: if sqlrow == 0: value = result[sqlrow][sqlcol] rowprinted=1 elif result[sqlrow][0] != last_game: value = ' ' elif 'show' in seats and seats['show'] and result[sqlrow][avgcol] != last_seats: value = ' ' else: value = result[sqlrow][sqlcol] rowprinted=1 else: l = gtk.Label(' ') value = ' ' if value and value != -999: treerow.append(value) else: treerow.append(' ') iter = liststore.append(treerow) last_game = result[sqlrow][0] last_seats = result[sqlrow][avgcol] if rowprinted: sqlrow = sqlrow+1 row = row + 1 # show totals at bottom tmp = self.sql.query['playerStats'] tmp = self.refineQuery(tmp, playerids, sitenos, limits, seats, dates) self.cursor.execute(tmp) result = self.cursor.fetchall() rows = len(result) colnames = [desc[0].lower() for desc in self.cursor.description] # blank row between main stats and totals: col = 0 treerow = [' ' for x in self.posncols] iter = liststore.append(treerow) row = row + 1 for sqlrow in range(rows): treerow = [] for col,colname in enumerate(self.posncols): if colname in colnames: sqlcol = colnames.index(colname) elif colname != "plposition": continue if colname == 'plposition': l = gtk.Label('Totals') value = 'Totals' elif result[sqlrow][sqlcol]: l = gtk.Label(result[sqlrow][sqlcol]) value = result[sqlrow][sqlcol] else: l = gtk.Label(' ') value = ' ' if value and value != -999: treerow.append(value) else: treerow.append(' ') iter = liststore.append(treerow) row = row + 1 vbox.show_all() self.db.db.rollback() print "Positional Stats page displayed in %4.2f seconds" % (time() - starttime) #end def fillStatsFrame(self, vbox): def refineQuery(self, query, playerids, sitenos, limits, seats, dates): if playerids: nametest = str(tuple(playerids)) nametest = nametest.replace("L", "") nametest = nametest.replace(",)",")") query = query.replace("", nametest) else: query = query.replace("", "1 = 2") if seats: query = query.replace('', 'between ' + str(seats['from']) + ' and ' + str(seats['to'])) if 'show' in seats and seats['show']: query = query.replace('', ',hc.activeSeats') query = query.replace('', ',stats.AvgSeats') else: query = query.replace('', '') query = query.replace('', '') else: query = query.replace('', 'between 0 and 100') query = query.replace('', '') query = query.replace('', '') if [x for x in limits if str(x).isdigit()]: blindtest = str(tuple([x for x in limits if str(x).isdigit()])) blindtest = blindtest.replace("L", "") blindtest = blindtest.replace(",)",")") query = query.replace("", "gt.bigBlind in " + blindtest) else: query = query.replace("", "gt.bigBlind = -1 ") groupLevels = "show" not in str(limits) if groupLevels: if self.db.backend == self.MYSQL_INNODB: bigblindselect = """concat('$' ,trim(leading ' ' from case when min(gt.bigBlind) < 100 then format(min(gt.bigBlind)/100.0, 2) else format(min(gt.bigBlind)/100.0, 0) end) ,' - $' ,trim(leading ' ' from case when max(gt.bigBlind) < 100 then format(max(gt.bigBlind)/100.0, 2) else format(max(gt.bigBlind)/100.0, 0) end) ) """ else: bigblindselect = """'$' || trim(leading ' ' from case when min(gt.bigBlind) < 100 then to_char(min(gt.bigBlind)/100.0,'90D00') else to_char(min(gt.bigBlind)/100.0,'999990') end) || ' - $' || trim(leading ' ' from case when max(gt.bigBlind) < 100 then to_char(max(gt.bigBlind)/100.0,'90D00') else to_char(max(gt.bigBlind)/100.0,'999990') end) """ bigblindselect = "cast('' as char)" # avoid odd effects when some posns and/or seats # are missing from some limits (dunno why cast is # needed but it says "unknown type" otherwise?! query = query.replace("", bigblindselect) query = query.replace("", "") query = query.replace("", "-1") query = query.replace("", "-1") else: if self.db.backend == self.MYSQL_INNODB: bigblindselect = """concat('$', trim(leading ' ' from case when gt.bigBlind < 100 then format(gt.bigBlind/100.0, 2) else format(gt.bigBlind/100.0, 0) end ) )""" else: bigblindselect = """'$' || trim(leading ' ' from case when gt.bigBlind < 100 then to_char(gt.bigBlind/100.0,'90D00') else to_char(gt.bigBlind/100.0,'999990') end ) """ query = query.replace("", bigblindselect) query = query.replace("", ",gt.bigBlind") query = query.replace("", "hc.gametypeId") query = query.replace("", "h.gameTypeId") # Filter on dates query = query.replace("", " between '" + dates[0] + "' and '" + dates[1] + "'") #print "query =\n", query return(query) #end def refineQuery(self, query, playerids, sitenos, limits):