2 # -*- coding: utf-8 -*-
4 #Copyright 2010-2011 Maxime Grandchamp
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.
20 # This code once was in GuiReplayer.py and was split up in this and the former by zarturo.
23 _
= L10n
.get_translation()
43 pp
= pprint
.PrettyPrinter(indent
=4)
45 # The ListView renderer data function requires a function signature of
46 # renderer_cell_func(tree_column, cell, model, tree_iter, data)
47 # Placing the function into the Replayer object changes the call singature
48 # card_images has been made global to facilitate this.
51 card_images
= 53 * [0]
53 def card_renderer_cell_func(tree_column
, cell
, model
, tree_iter
, data
):
57 coldata
= model
.get_value(tree_iter
, col
)
58 if coldata
== None or coldata
== '':
60 coldata
= coldata
.replace("'","")
61 coldata
= coldata
.replace("[","")
62 coldata
= coldata
.replace("]","")
63 coldata
= coldata
.replace("'","")
64 coldata
= coldata
.replace(",","")
65 #print "DEBUG: coldata: %s" % (coldata)
66 cards
= [Card
.encodeCard(c
) for c
in coldata
.split(' ')]
69 #print "DEBUG: cards: %s" % cards
70 pixbuf
= gtk
.gdk
.Pixbuf(gtk
.gdk
.COLORSPACE_RGB
, True, 8, card_width
* n_cards
, card_height
)
72 x
= 0 # x coord where the next card starts in scratch
74 if card
== None or card
==0:
75 card_images
[0].copy_area(0, 0, card_width
, card_height
, pixbuf
, x
, 0)
77 card_images
[card
].copy_area(0, 0, card_width
, card_height
, pixbuf
, x
, 0)
79 cell
.set_property('pixbuf', pixbuf
)
82 # This function is a duplicate of 'ledger_style_render_func' in GuiRingPlayerStats
83 # TODO: Pull generic cell formatting functions into something common.
84 def cash_renderer_cell_func(tree_column
, cell
, model
, tree_iter
, data
):
86 coldata
= model
.get_value(tree_iter
, col
)
88 coldata
= coldata
.replace("-", "")
89 coldata
= "(%s)" %(coldata)
90 cell
.set_property('foreground', 'red')
92 cell
.set_property('foreground', 'darkgreen')
93 cell
.set_property('text', coldata
)
95 def reset_style_render_func(tree_column
, cell
, model
, iter, data
):
96 cell
.set_property('foreground', None)
97 cell
.set_property('text', model
.get_value(iter, data
))
101 def __init__(self
, config
, querylist
, mainwin
, options
= None, debug
=True):
104 self
.main_window
= mainwin
107 self
.date_from
= None
110 # These are temporary variables until it becomes possible
111 # to select() a Hand object from the database
112 self
.site
="PokerStars"
114 self
.db
= Database
.Database(self
.config
, sql
=self
.sql
)
117 filters_display
= { "Heroes" : True,
136 self
.filters
= Filters
.Filters(self
.db
, self
.config
, self
.sql
, display
= filters_display
)
137 self
.filters
.registerButton1Name(_("Load Hands"))
138 self
.filters
.registerButton1Callback(self
.loadHands
)
139 self
.filters
.registerCardsCallback(self
.filter_cards_cb
)
140 #self.filters.registerButton2Name(_("temp"))
141 #self.filters.registerButton2Callback(self.temp())
143 # hierarchy: self.mainHBox / self.hpane / self.handsVBox / self.area
145 self
.mainHBox
= gtk
.HBox(False, 0)
148 self
.leftPanelBox
= self
.filters
.get_vbox()
150 self
.hpane
= gtk
.HPaned()
151 self
.hpane
.pack1(self
.leftPanelBox
)
152 self
.mainHBox
.add(self
.hpane
)
154 self
.handsVBox
= gtk
.VBox(False, 0)
155 self
.handsVBox
.show()
157 self
.hpane
.pack2(self
.handsVBox
)
162 self
.deck_image
= "Cards01.png" #FIXME: read from config (requires deck to be defined somewhere appropriate
163 self
.tableImage
= None
164 self
.playerBackdrop
= None
165 self
.cardImages
= None
166 #NOTE: There are two caches of card images as I haven't found a way to
167 # replicate the copy_area() function from Pixbuf in the Pixmap class
168 # cardImages is used for the tables display card_images is used for the
169 # table display. Sooner or later we should probably use one or the other.
170 card_images
= self
.init_card_images(config
)
172 def init_card_images(self
, config
):
173 suits
= ('s', 'h', 'd', 'c')
174 ranks
= (14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2)
175 pb
= gtk
.gdk
.pixbuf_new_from_file(config
.execution_path(self
.deck_image
))
177 for j
in range(0, 13):
178 for i
in range(0, 4):
179 loc
= Card
.cardFromValueSuit(ranks
[j
], suits
[i
])
180 card_images
[loc
] = gtk
.gdk
.Pixbuf(gtk
.gdk
.COLORSPACE_RGB
, pb
.get_has_alpha(), pb
.get_bits_per_sample(), 30, 42)
181 pb
.copy_area(30*j
, 42*i
, 30, 42, card_images
[loc
], 0, 0)
182 card_images
[0] = gtk
.gdk
.Pixbuf(gtk
.gdk
.COLORSPACE_RGB
, pb
.get_has_alpha(), pb
.get_bits_per_sample(), 30, 42)
183 pb
.copy_area(30*13, 0, 30, 42, card_images
[0], 0, 0)
186 def loadHands(self
, button
, userdata
):
187 hand_ids
= self
.get_hand_ids_from_date_range(self
.filters
.getDates()[0], self
.filters
.getDates()[1])
188 self
.reload_hands(hand_ids
)
190 def get_hand_ids_from_date_range(self
, start
, end
, save_date
= False):
191 """Returns the handids in the given date range and in the filters.
192 Set save_data to true if you want to keep the start and end date if no other date is specified through the filters by the user."""
195 self
.date_from
= start
198 if start
!= self
.filters
.MIN_DATE
: #if date is ever changed by the user previously saved dates are deleted
199 self
.date_from
= None
200 if end
!= self
.filters
.MAX_DATE
:
203 if self
.date_from
!= None and start
== self
.filters
.MIN_DATE
:
204 start
= self
.date_from
206 if self
.date_to
!= None and end
== self
.filters
.MAX_DATE
:
209 q
= self
.db
.sql
.query
['handsInRange']
210 q
= q
.replace('<datetest>', "between '" + start
+ "' and '" + end
+ "'")
211 q
= self
.filters
.replace_placeholders_with_filter_values(q
)
213 c
= self
.db
.get_cursor()
216 return [r
[0] for r
in c
.fetchall()]
218 def rankedhand(self
, hand
, game
):
219 ranks
= {'0':0, '2':2, '3':3, '4':4, '5':5, '6':6, '7':7, '8':8, '9':9, 'T':10, 'J':11, 'Q':12, 'K':13, 'A':14}
220 suits
= {'x':0, 's':1, 'c':2, 'd':3, 'h':4}
223 card1
= ranks
[hand
[0]]
224 card2
= ranks
[hand
[3]]
225 suit1
= suits
[hand
[1]]
226 suit2
= suits
[hand
[4]]
228 (card1
, card2
) = (card2
, card1
)
229 (suit1
, suit2
) = (suit2
, suit1
)
232 return card1
* 14 * 14 + card2
* 14 + suit1
236 def sorthand(self
, model
, iter1
, iter2
):
237 hand1
= self
.hands
[int(model
.get_value(iter1
, self
.colnum
['HandId']))]
238 hand2
= self
.hands
[int(model
.get_value(iter2
, self
.colnum
['HandId']))]
239 base1
= hand1
.gametype
['base']
240 base2
= hand2
.gametype
['base']
246 cat1
= hand1
.gametype
['category']
247 cat2
= hand2
.gametype
['category']
253 a
= self
.rankedhand(model
.get_value(iter1
, 0), hand1
.gametype
['category'])
254 b
= self
.rankedhand(model
.get_value(iter2
, 0), hand2
.gametype
['category'])
263 def sort_float(self
, model
, iter1
, iter2
, col
):
264 a
= float(model
.get_value(iter1
, col
))
265 b
= float(model
.get_value(iter2
, col
))
274 def sort_pos(self
, model
, iter1
, iter2
, col
):
275 a
= self
.__get
_sortable
_int
_from
_pos
__(model
.get_value(iter1
, col
))
276 b
= self
.__get
_sortable
_int
_from
_pos
__(model
.get_value(iter2
, col
))
285 def __get_sortable_int_from_pos__(self
, pos
):
293 def reload_hands(self
, handids
):
295 for handid
in handids
:
296 self
.hands
[handid
] = self
.importhand(handid
)
299 def refreshHands(self
):
301 self
.handsWindow
.destroy()
304 self
.handsWindow
= gtk
.ScrolledWindow(hadjustment
=None, vadjustment
=None)
305 self
.handsWindow
.set_policy(gtk
.POLICY_AUTOMATIC
, gtk
.POLICY_AUTOMATIC
)
306 self
.handsVBox
.pack_end(self
.handsWindow
)
308 # Dict of colnames and their column idx in the model/ListStore
322 self
.liststore
= gtk
.ListStore(*([str] * len(self
.colnum
)))
323 self
.view
= gtk
.TreeView()
324 self
.view
.set_grid_lines(gtk
.TREE_VIEW_GRID_LINES_BOTH
)
325 self
.handsWindow
.add(self
.view
)
327 #self.viewfilter = self.liststore.filter_new() #if a filter is used, the sorting doesnt work anymore!! As GtkTreeModelFilter does NOT implement GtkTreeSortable
328 #self.view.set_model(self.viewfilter)
329 self
.view
.set_model(self
.liststore
)
330 textcell
= gtk
.CellRendererText()
331 numcell
= gtk
.CellRendererText()
332 numcell
.set_property('xalign', 1.0)
333 pixbuf
= gtk
.CellRendererPixbuf()
334 pixbuf
.set_property('xalign', 0.0)
336 self
.view
.insert_column_with_data_func(-1, 'Stakes', textcell
, reset_style_render_func
,self
.colnum
['Stakes'])
337 self
.view
.insert_column_with_data_func(-1, 'Pos', textcell
, reset_style_render_func
,self
.colnum
['Pos'])
338 self
.view
.insert_column_with_data_func(-1, 'Street 0', pixbuf
, card_renderer_cell_func
, self
.colnum
['Street0'])
339 self
.view
.insert_column_with_data_func(-1, 'Action 0', textcell
, reset_style_render_func
,self
.colnum
['Action0'])
340 self
.view
.insert_column_with_data_func(-1, 'Street 1-4', pixbuf
, card_renderer_cell_func
, self
.colnum
['Street1-4'])
341 self
.view
.insert_column_with_data_func(-1, 'Action 1-4', textcell
, reset_style_render_func
,self
.colnum
['Action1-4'])
342 self
.view
.insert_column_with_data_func(-1, 'Won', numcell
, reset_style_render_func
, self
.colnum
['Won'])
343 self
.view
.insert_column_with_data_func(-1, 'Bet', numcell
, reset_style_render_func
, self
.colnum
['Bet'])
344 self
.view
.insert_column_with_data_func(-1, 'Net', numcell
, cash_renderer_cell_func
, self
.colnum
['Net'])
345 self
.view
.insert_column_with_data_func(-1, 'Game', textcell
, reset_style_render_func
,self
.colnum
['Game'])
347 self
.liststore
.set_sort_func(self
.colnum
['Street0'], self
.sorthand
)
348 self
.liststore
.set_sort_func(self
.colnum
['Pos'], self
.sort_pos
, self
.colnum
['Pos'])
349 self
.liststore
.set_sort_func(self
.colnum
['Net'], self
.sort_float
, self
.colnum
['Net'])
350 self
.liststore
.set_sort_func(self
.colnum
['Bet'], self
.sort_float
, self
.colnum
['Bet'])
351 self
.view
.get_column(self
.colnum
['Street0']).set_sort_column_id(self
.colnum
['Street0'])
352 self
.view
.get_column(self
.colnum
['Net']).set_sort_column_id(self
.colnum
['Net'])
353 self
.view
.get_column(self
.colnum
['Bet']).set_sort_column_id(self
.colnum
['Bet'])
354 self
.view
.get_column(self
.colnum
['Pos']).set_sort_column_id(self
.colnum
['Pos'])
356 #selection = self.view.get_selection()
357 #selection.set_select_function(self.select_hand, None, True) #listen on selection (single click)
358 self
.view
.connect('row-activated', self
.row_activated
) #listen to double klick
360 for handid
, hand
in self
.hands
.items():
361 hero
= self
.filters
.getHeroes()[hand
.sitename
]
363 if hero
in hand
.collectees
.keys():
364 won
= hand
.collectees
[hero
]
366 if hero
in hand
.pot
.committed
.keys():
367 bet
= hand
.pot
.committed
[hero
]
369 pos
= hand
.get_player_position(hero
)
370 gt
= hand
.gametype
['category']
372 if hand
.gametype
['base'] == 'hold':
374 board
.extend(hand
.board
['FLOP'])
375 board
.extend(hand
.board
['TURN'])
376 board
.extend(hand
.board
['RIVER'])
378 pre_actions
= hand
.get_actions_short(hero
, 'PREFLOP')
380 if 'F' not in pre_actions
: #if player hasen't folded preflop
381 post_actions
= hand
.get_actions_short_streets(hero
, 'FLOP', 'TURN', 'RIVER')
383 row
= [hand
.getStakesAsString(), pos
, hand
.join_holecards(hero
), pre_actions
, ' '.join(board
), post_actions
, str(won
), str(bet
),
384 str(net
), gt
, handid
]
386 elif hand
.gametype
['base'] == 'stud':
387 third
= " ".join(hand
.holecards
['THIRD'][hero
][0]) + " " + " ".join(hand
.holecards
['THIRD'][hero
][1])
388 #ugh - fix the stud join_holecards function so we can retrieve sanely
390 later_streets
.extend(hand
.holecards
['FOURTH'] [hero
][0])
391 later_streets
.extend(hand
.holecards
['FIFTH'] [hero
][0])
392 later_streets
.extend(hand
.holecards
['SIXTH'] [hero
][0])
393 later_streets
.extend(hand
.holecards
['SEVENTH'][hero
][0])
395 pre_actions
= hand
.get_actions_short(hero
, 'THIRD')
397 if 'F' not in pre_actions
:
398 post_actions
= hand
.get_actions_short_streets(hero
, 'FOURTH', 'FIFTH', 'SIXTH', 'SEVENTH')
400 row
= [hand
.getStakesAsString(), pos
, third
, pre_actions
, ' '.join(later_streets
), post_actions
, str(won
), str(bet
), str(net
),
403 elif hand
.gametype
['base'] == 'draw':
404 row
= [hand
.getStakesAsString(), pos
, hand
.join_holecards(hero
,street
='DEAL'), hand
.get_actions_short(hero
, 'DEAL'), None, None,
405 str(won
), str(bet
), str(net
), gt
, handid
]
407 if self
.is_row_in_card_filter(row
):
408 self
.liststore
.append(row
)
409 #self.viewfilter.set_visible_func(self.viewfilter_visible_cb)
410 self
.handsWindow
.show_all()
412 def filter_cards_cb(self
, card
):
414 #self.viewfilter.refilter() #As the sorting doesnt work if this is used, a refresh is needed.
416 def is_row_in_card_filter(self
, row
):
417 """ Returns true if the cards of the given row are in the card filter """
418 #Does work but all cards that should NOT be displayed have to be clicked.
419 card_filter
= self
.filters
.getCards()
420 hcs
= row
[self
.colnum
['Street0']].split(' ')
422 if '0x' in hcs
: #if cards are unknown return True
425 gt
= row
[self
.colnum
['Game']]
427 if gt
not in ('holdem', 'omahahi', 'omahahilo'): return True
428 # Holdem: Compare the real start cards to the selected filter (ie. AhKh = AKs)
429 value1
= Card
.card_map
[hcs
[0][0]]
430 value2
= Card
.card_map
[hcs
[1][0]]
431 idx
= Card
.twoStartCards(value1
, hcs
[0][1], value2
, hcs
[1][1])
432 abbr
= Card
.twoStartCardString(idx
)
433 return False if card_filter
[abbr
] == False else True
435 #def select_hand(self, selection, model, path, is_selected, userdata): #function head for single click event
436 def row_activated(self
, view
, path
, column
):
437 model
= view
.get_model()
438 hand
= self
.hands
[int(model
.get_value(model
.get_iter(path
), self
.colnum
['HandId']))]
439 if hand
.gametype
['currency']=="USD": #TODO: check if there are others ..
441 elif hand
.gametype
['currency']=="EUR":
444 currency
= hand
.gametype
['currency']
446 replayer
= GuiReplayer
.GuiReplayer(self
.config
, self
.sql
, self
.main_window
)
448 replayer
.currency
= currency
449 replayer
.play_hand(hand
)
454 """returns the vbox of this thread"""
458 def importhand(self
, handid
=1):
460 # We need at least sitename, gametype, handid
461 # for the Hand.__init__
463 ####### Shift this section in Database.py for all to use ######
464 q
= self
.sql
.query
['get_gameinfo_from_hid']
465 q
= q
.replace('%s', self
.sql
.query
['placeholder'])
467 c
= self
.db
.get_cursor()
469 c
.execute(q
, (handid
,))
471 gametype
= {'category':res
[1],'base':res
[2],'type':res
[3],'limitType':res
[4],'hilo':res
[5],'sb':res
[6],'bb':res
[7], 'currency':res
[10]}
472 #FIXME: smallbet and bigbet are res[8] and res[9] respectively
473 ###### End section ########
474 if gametype
['base'] == 'hold':
475 h
= HoldemOmahaHand(config
= self
.config
, hhc
= None, sitename
=res
[0], gametype
= gametype
, handText
=None, builtFrom
= "DB", handid
=handid
)
476 elif gametype
['base'] == 'stud':
477 h
= StudHand(config
= self
.config
, hhc
= None, sitename
=res
[0], gametype
= gametype
, handText
=None, builtFrom
= "DB", handid
=handid
)
478 elif gametype
['base'] == 'draw':
479 h
= DrawHand(config
= self
.config
, hhc
= None, sitename
=res
[0], gametype
= gametype
, handText
=None, builtFrom
= "DB", handid
=handid
)
480 h
.select(self
.db
, handid
)
484 #This code would use pango markup instead of pix for the cards and renderers
486 def refreshHands(self, handids):
488 for handid in handids:
489 self.hands[handid] = self.importhand(handid)
492 self.handsWindow.destroy()
495 self.handsWindow = gtk.ScrolledWindow(hadjustment=None, vadjustment=None)
496 self.handsWindow.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
497 self.handsVBox.pack_end(self.handsWindow)
508 # Dict of colnames and their column idx in the model/ListStore
519 self.liststore = gtk.ListStore(*cols)
520 self.view = gtk.TreeView()
521 self.view.set_grid_lines(gtk.TREE_VIEW_GRID_LINES_BOTH)
522 self.handsWindow.add(self.view)
524 self.viewfilter = self.liststore.filter_new()
525 self.view.set_model(self.viewfilter)
526 text = gtk.CellRendererText()
528 self.view.insert_column_with_attributes(-1, 'Street 0', text, markup = self.colnum['Street0'])
529 self.view.insert_column_with_attributes(-1, 'Street 1', text, markup = self.colnum['Street1'])
530 self.view.insert_column_with_attributes(-1, 'Street 2', text, markup = self.colnum['Street2'])
531 self.view.insert_column_with_attributes(-1, 'Street 3', text, markup = self.colnum['Street3'])
532 self.view.insert_column_with_attributes(-1, 'Street 4', text, markup = self.colnum['Street4'])
533 self.view.insert_column_with_attributes(-1, '+/-', text, markup = self.colnum['+/-'])
534 self.view.insert_column_with_attributes(-1, 'Game', text, text = self.colnum['Game'])
536 self.liststore.set_sort_func(self.colnum['Street0'], self.sorthand)
537 self.liststore.set_sort_func(self.colnum['+/-'], self.sort_float)
538 self.view.get_column(self.colnum['Street0']).set_sort_column_id(self.colnum['Street0'])
539 self.view.get_column(self.colnum['+/-']).set_sort_column_id(self.colnum['+/-'])
541 selection = self.view.get_selection()
542 selection.set_select_function(self.select_hand, None, True)
544 for handid, hand in self.hands.items():
545 hero = self.filters.getHeroes()[hand.sitename]
547 if hero in hand.collectees.keys():
548 won = hand.collectees[hero]
550 if hero in hand.pot.committed.keys():
551 bet = hand.pot.committed[hero]
552 net = self.get_net_pango_markup(won - bet)
554 gt = hand.gametype['category']
556 if hand.gametype['base'] == 'hold':
557 hole = hand.get_cards_pango_markup(hand.holecards['PREFLOP'][hero][1])
558 flop = hand.get_cards_pango_markup(hand.board["FLOP"])
559 turn = hand.get_cards_pango_markup(hand.board["TURN"])
560 river = hand.get_cards_pango_markup(hand.board["RIVER"])
561 row = [hole, flop, turn, river, None, net, gt, handid]
562 elif hand.gametype['base'] == 'stud':
563 third = hand.get_cards_pango_markup(hand.holecards['THIRD'][hero][0]) + " " + hand.get_cards_pango_markup(hand.holecards['THIRD'][hero][1])
564 #ugh - fix the stud join_holecards function so we can retrieve sanely
565 fourth = hand.get_cards_pango_markup(hand.holecards['FOURTH'] [hero][0])
566 fifth = hand.get_cards_pango_markup(hand.holecards['FIFTH'] [hero][0])
567 sixth = hand.get_cards_pango_markup(hand.holecards['SIXTH'] [hero][0])
568 seventh = hand.get_cards_pango_markup(hand.holecards['SEVENTH'][hero][0])
569 row = [third, fourth, fifth, sixth, seventh, net, gt, handid]
570 elif hand.gametype['base'] == 'draw':
571 row = [hand.get_cards_pango_markup(hand.holecards['DEAL'][hero][0]), None, None, None, None, net, gt, handid]
572 #print "DEBUG: row: %s" % row
573 self.liststore.append(row)
574 self.viewfilter.set_visible_func(self.viewfilter_visible_cb)
575 self.handsWindow.show_all()
577 def get_net_pango_markup(self, net):
578 """Pango marks up the +/- value ... putting negative values in () and coloring them red.
579 used instead of cash_renderer_cell_func because the render function renders the foreground of all columns and not just the one needed """
581 ret = '<span foreground="red">(%s)</span>' %(net*-1)