#!/usr/bin/python # -*- coding: utf-8 -*- #Copyright 2010 Steffen Schaumburg #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. #import traceback import threading import pygtk pygtk.require('2.0') import gtk #import os #import sys from time import time, strftime #import Card #import fpdb_import #import Database import Charset import TourneyFilters class GuiTourneyPlayerStats (threading.Thread): def __init__(self, config, db, sql, mainwin, debug=True): self.conf = config self.db = db self.sql = sql self.main_window = mainwin self.debug = debug self.liststore = [] # gtk.ListStore[] stores the contents of the grids self.listcols = [] # gtk.TreeViewColumn[][] stores the columns in the grids filters_display = { "Heroes" : False, "Sites" : True, #"Games" : True, #"Limits" : True, #"LimitSep" : True, #"LimitType" : True, #"Type" : True, "Seats" : True, #"SeatSep" : True, "Dates" : True, #"Groups" : True, #"GroupsAll" : True, #"Button1" : True, "Button2" : True} self.stats_frame = None self.stats_vbox = None self.detailFilters = [] # the data used to enhance the sql select self.main_hbox = gtk.HPaned() self.filters = TourneyFilters.TourneyFilters(self.db, self.conf, self.sql, display = filters_display) #self.filters.registerButton1Name("_Filters") #self.filters.registerButton1Callback(self.showDetailFilter) self.filters.registerButton2Name("_Refresh Stats") self.filters.registerButton2Callback(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, celltype self.columns = [ ["tourneyTypeId", True, "TTypeId", 0.0, "%s", "str"] , ["tourney", False, "Tourney", 0.0, "%s", "str"] # true not allowed for this line #, ["pname", False, "Name", 0.0, "%s", "str"] # true not allowed for this line (set in code) , ["tourneyCount", True, "#", 1.0, "%1.0f", "str"] , ["1st", False, "1st", 1.0, "%3.1f", "str"] , ["2nd", True, "2nd", 1.0, "%3.1f", "str"] , ["3rd", True, "3rd", 1.0, "%3.1f", "str"] , ["unknownRank", True, "unknown", 1.0, "%3.1f", "str"] , ["itm", True, "ITM", 1.0, "%2.2f", "str"] , ["roi", True, "ROI", 1.0, "%3.1f", "str"] , ["profitPerTourney", True, "$/T", 1.0, "%3.1f", "str"]] self.stats_frame = gtk.Frame() self.stats_frame.show() self.stats_vbox = gtk.VPaned() self.stats_vbox.show() self.stats_frame.add(self.stats_vbox) # self.fillStatsFrame(self.stats_vbox) #self.main_hbox.pack_start(self.filters.get_vbox()) #self.main_hbox.pack_start(self.stats_frame, expand=True, fill=True) self.main_hbox.pack1(self.filters.get_vbox()) self.main_hbox.pack2(self.stats_frame) self.main_hbox.show() #end def __init__ def addGrid(self, vbox, query, flags, tourneyTypes, playerids, sitenos, seats, dates): counter = 0 row = 0 sqlrow = 0 if not flags: holecards,grid = False,0 else: holecards,grid = flags[0],flags[2] tmp = self.sql.query[query] tmp = self.refineQuery(tmp, flags, tourneyTypes, playerids, sitenos, seats, dates) self.cursor.execute(tmp) result = self.cursor.fetchall() colnames = [desc[0].lower() for desc in self.cursor.description] # pre-fetch some constant values: self.cols_to_show = [x for x in self.columns if x[colshow]] hgametypeid_idx = colnames.index('hgametypeid') assert len(self.liststore) == grid, "len(self.liststore)="+str(len(self.liststore))+" grid-1="+str(grid) self.liststore.append( gtk.ListStore(*([str] * len(self.cols_to_show))) ) view = gtk.TreeView(model=self.liststore[grid]) view.set_grid_lines(gtk.TREE_VIEW_GRID_LINES_BOTH) #vbox.pack_start(view, expand=False, padding=3) vbox.add(view) textcell = gtk.CellRendererText() textcell50 = gtk.CellRendererText() textcell50.set_property('xalign', 0.5) numcell = gtk.CellRendererText() numcell.set_property('xalign', 1.0) assert len(self.listcols) == grid self.listcols.append( [] ) # Create header row eg column: ("game", True, "Game", 0.0, "%s") for col, column in enumerate(self.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] self.listcols[grid].append(gtk.TreeViewColumn(s)) view.append_column(self.listcols[grid][col]) if column[colformat] == '%s': if column[colxalign] == 0.0: self.listcols[grid][col].pack_start(textcell, expand=True) self.listcols[grid][col].add_attribute(textcell, 'text', col) cellrend = textcell else: self.listcols[grid][col].pack_start(textcell50, expand=True) self.listcols[grid][col].add_attribute(textcell50, 'text', col) cellrend = textcell50 self.listcols[grid][col].set_expand(True) else: self.listcols[grid][col].pack_start(numcell, expand=True) self.listcols[grid][col].add_attribute(numcell, 'text', col) self.listcols[grid][col].set_expand(True) cellrend = numcell #self.listcols[grid][col].set_alignment(column[colxalign]) # no effect? self.listcols[grid][col].set_clickable(True) self.listcols[grid][col].connect("clicked", self.sortcols, (col,grid)) if col == 0: self.listcols[grid][col].set_sort_order(gtk.SORT_DESCENDING) self.listcols[grid][col].set_sort_indicator(True) if column[coltype] == 'cash': self.listcols[grid][col].set_cell_data_func(numcell, self.ledger_style_render_func) else: self.listcols[grid][col].set_cell_data_func(cellrend, self.reset_style_render_func) rows = len(result) # +1 for title row while sqlrow < rows: treerow = [] for col,column in enumerate(self.cols_to_show): if column[colalias] in colnames: value = result[sqlrow][colnames.index(column[colalias])] if column[colalias] == 'plposition': if value == 'B': value = 'BB' elif value == 'S': value = 'SB' elif value == '0': value = 'Btn' else: if column[colalias] == 'game': if holecards: value = Card.twoStartCardString( result[sqlrow][hgametypeid_idx] ) else: minbb = result[sqlrow][colnames.index('minbigblind')] maxbb = result[sqlrow][colnames.index('maxbigblind')] value = result[sqlrow][colnames.index('limittype')] + ' ' \ + result[sqlrow][colnames.index('category')].title() + ' ' \ + result[sqlrow][colnames.index('name')] + ' $' if 100 * int(minbb/100.0) != minbb: value += '%.2f' % (minbb/100.0) else: value += '%.0f' % (minbb/100.0) if minbb != maxbb: if 100 * int(maxbb/100.0) != maxbb: value += ' - $' + '%.2f' % (maxbb/100.0) else: value += ' - $' + '%.0f' % (maxbb/100.0) else: continue if value and value != -999: treerow.append(column[colformat] % value) else: treerow.append(' ') iter = self.liststore[grid].append(treerow) #print treerow sqlrow += 1 row += 1 vbox.show_all() #end def addGrid def createStatsTable(self, vbox, tourneyTypes, playerids, sitenos, seats, dates): startTime = time() show_detail = True # Scrolled window for summary table swin = gtk.ScrolledWindow(hadjustment=None, vadjustment=None) swin.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) swin.show() vbox.pack1(swin) # Display summary table at top of page # 3rd parameter passes extra flags, currently includes: # holecards - whether to display card breakdown (True/False) # numhands - min number hands required when displaying all players # gridnum - index for grid data structures flags = [False, self.filters.getNumTourneys(), 0] self.addGrid(swin, 'playerDetailedStats', flags, tourneyTypes, playerids ,sitenos, seats, dates) if 'allplayers' in groups and groups['allplayers']: # can't currently do this combination so skip detailed table show_detail = False if show_detail: # Separator vbox2 = gtk.VBox(False, 0) heading = gtk.Label(self.filterText['handhead']) heading.show() vbox2.pack_start(heading, expand=False, padding=3) # Scrolled window for detailed table (display by hand) swin = gtk.ScrolledWindow(hadjustment=None, vadjustment=None) swin.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) swin.show() vbox2.pack_start(swin, expand=True, padding=3) vbox.pack2(vbox2) vbox2.show() # Detailed table flags[0] = True flags[2] = 1 self.addGrid(swin, 'playerDetailedStats', flags, playerids, sitenos, seats, dates) self.db.rollback() print "Stats page displayed in %4.2f seconds" % (time() - startTime) #end def createStatsTable def fillStatsFrame(self, vbox): tourneyTypes = self.filters.getTourneyTypes() #tourneys = self.tourneys.getTourneys() sites = self.filters.getSites() heroes = self.filters.getHeroes() siteids = self.filters.getSiteIds() 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]) _hname = Charset.to_utf8(heroes[site]) result = self.db.get_player_id(self.conf, site, _hname) if result is not None: playerids.append(int(result)) 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 self.createStatsTable(vbox, tourneyTypes, playerids, sitenos, seats, dates) #end def fillStatsFrame def get_vbox(self): """returns the vbox of this thread""" return self.main_hbox #end def get_vbox def refineQuery(self, query, flags, tourneyTypes, playerids, sitenos, seats, dates): having = '' holecards = flags[0] numTourneys = flags[1] #if 'allplayers' in groups and groups['allplayers']: nametest = "(hp.playerId)" pname = "p.name" # set flag in self.columns to show player name column [x for x in self.columns if x[0] == 'pname'][0][1] = True if numTourneys: having = ' and count(1) > %d ' % (numTourneys,) query = query.replace("", nametest) query = query.replace("", pname) query = query.replace("", having) gametest = "" q = [] for m in self.filters.display.items(): if m[0] == 'Games' and m[1]: for n in games: if games[n]: q.append(n) if len(q) > 0: gametest = str(tuple(q)) gametest = gametest.replace("L", "") gametest = gametest.replace(",)",")") gametest = gametest.replace("u'","'") gametest = "and gt.category in %s" % gametest else: gametest = "and gt.category IS NULL" query = query.replace("", gametest) sitetest = "" q = [] for m in self.filters.display.items(): if m[0] == 'Sites' and m[1]: for n in sitenos: q.append(n) if len(q) > 0: sitetest = str(tuple(q)) sitetest = sitetest.replace("L", "") sitetest = sitetest.replace(",)",")") sitetest = sitetest.replace("u'","'") sitetest = "and gt.siteId in %s" % sitetest else: sitetest = "and gt.siteId IS NULL" query = query.replace("", sitetest) if seats: query = query.replace('', 'between ' + str(seats['from']) + ' and ' + str(seats['to'])) if 'show' in seats and seats['show']: query = query.replace('', ',h.seats') query = query.replace('', ',h.seats') else: query = query.replace('', '') query = query.replace('', '') else: query = query.replace('', 'between 0 and 100') query = query.replace('', '') query = query.replace('', '') lims = [int(x) for x in limits if x.isdigit()] potlims = [int(x[0:-2]) for x in limits if len(x) > 2 and x[-2:] == 'pl'] nolims = [int(x[0:-2]) for x in limits if len(x) > 2 and x[-2:] == 'nl'] bbtest = "and ( (gt.limitType = 'fl' and gt.bigBlind in " # and ( (limit and bb in()) or (nolimit and bb in ()) ) if lims: blindtest = str(tuple(lims)) blindtest = blindtest.replace("L", "") blindtest = blindtest.replace(",)",")") bbtest = bbtest + blindtest + ' ) ' else: bbtest = bbtest + '(-1) ) ' bbtest = bbtest + " or (gt.limitType = 'pl' and gt.bigBlind in " if potlims: blindtest = str(tuple(potlims)) blindtest = blindtest.replace("L", "") blindtest = blindtest.replace(",)",")") bbtest = bbtest + blindtest + ' ) ' else: bbtest = bbtest + '(-1) ) ' bbtest = bbtest + " or (gt.limitType = 'nl' and gt.bigBlind in " if nolims: blindtest = str(tuple(nolims)) blindtest = blindtest.replace("L", "") blindtest = blindtest.replace(",)",")") bbtest = bbtest + blindtest + ' ) )' else: bbtest = bbtest + '(-1) ) )' if type == 'ring': bbtest = bbtest + " and gt.type = 'ring' " elif type == 'tour': bbtest = " and gt.type = 'tour' " query = query.replace("", bbtest) if holecards: # re-use level variables for hole card query query = query.replace("", "hp.startcards") query = query.replace("" , ",case when floor((hp.startcards-1)/13) >= mod((hp.startcards-1),13) then hp.startcards + 0.1 " + " else 13*mod((hp.startcards-1),13) + floor((hp.startcards-1)/13) + 1 " + " end desc ") else: query = query.replace("", "") groupLevels = "show" not in str(limits) if groupLevels: query = query.replace("", "p.name") # need to use p.name for sqlite posn stats to work else: query = query.replace("", "h.gameTypeId") # process self.detailFilters (a list of tuples) flagtest = '' #self.detailFilters = [('h.seats', 5, 6)] # for debug if self.detailFilters: for f in self.detailFilters: if len(f) == 3: # X between Y and Z flagtest += ' and %s between %s and %s ' % (f[0], str(f[1]), str(f[2])) query = query.replace("", flagtest) # allow for differences in sql cast() function: if self.db.backend == self.MYSQL_INNODB: query = query.replace("", 'signed ') else: query = query.replace("", '') # Filter on dates query = query.replace("", " between '" + dates[0] + "' and '" + dates[1] + "'") # Group by position? if groups['posn']: #query = query.replace("", "case hp.position when '0' then 'Btn' else hp.position end") query = query.replace("", "hp.position") # set flag in self.columns to show posn column [x for x in self.columns if x[0] == 'plposition'][0][1] = True else: query = query.replace("", "gt.base") # unset flag in self.columns to hide posn column [x for x in self.columns if x[0] == 'plposition'][0][1] = False #print "query =\n", query return(query) #end def refineQuery def refreshStats(self, widget, data): self.last_pos = self.stats_vbox.get_position() try: self.stats_vbox.destroy() except AttributeError: pass self.liststore = [] self.listcols = [] #self.stats_vbox = gtk.VBox(False, 0) self.stats_vbox = gtk.VPaned() self.stats_vbox.show() self.stats_frame.add(self.stats_vbox) self.fillStatsFrame(self.stats_vbox) if self.last_pos > 0: self.stats_vbox.set_position(self.last_pos) #end def refreshStats #end class GuiTourneyPlayerStats