cd1d4df96d
Add site name to list of windows being HUDed, fix weird call convention to the "del" operator, which is not a function. call clean_title() on hud titles being killed, so we actually kill (deep) tables and such HUD: Add function "kill_hud_menu", which is now called from the Kill This HUD menu option, to avoid a potential loop in kill_hud() Record the handle of the HUD's destroy signal, use it to un-register, before performing a kill, also avoiding potential loop there Do not allow kill_hud() to be called twice in the same HUD object, return doing nothing if deleted is already set on it (that should totally solve the loop problem)
534 lines
22 KiB
Python
534 lines
22 KiB
Python
#!/usr/bin/env python
|
|
"""Hud.py
|
|
|
|
Create and manage the hud overlays.
|
|
"""
|
|
# Copyright 2008, Ray E. Barker
|
|
|
|
#
|
|
# This program is free software; you can redistribute it and/or modify
|
|
# it under the terms of the GNU General Public License as published by
|
|
# the Free Software Foundation; either version 2 of the License, or
|
|
# (at your option) any later version.
|
|
#
|
|
# 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 General Public License
|
|
# along with this program; if not, write to the Free Software
|
|
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
|
|
########################################################################
|
|
# Standard Library modules
|
|
import os
|
|
import sys
|
|
|
|
# pyGTK modules
|
|
import pygtk
|
|
import gtk
|
|
import pango
|
|
import gobject
|
|
|
|
# win32 modules -- only imported on windows systems
|
|
if os.name == 'nt':
|
|
import win32gui
|
|
import win32con
|
|
import win32api
|
|
|
|
# FreePokerTools modules
|
|
import Tables # needed for testing only
|
|
import Configuration
|
|
import Stats
|
|
import Mucked
|
|
import Database
|
|
import HUD_main
|
|
|
|
class Hud:
|
|
|
|
def __init__(self, table, max, poker_game, config, db_connection):
|
|
self.table = table
|
|
self.config = config
|
|
self.poker_game = poker_game
|
|
self.max = max
|
|
self.db_connection = db_connection
|
|
self.deleted = False
|
|
self.stacked = True
|
|
self.site = table.site
|
|
self.colors = config.get_default_colors(self.table.site)
|
|
|
|
self.stat_windows = {}
|
|
self.popup_windows = {}
|
|
self.aux_windows = []
|
|
|
|
(font, font_size) = config.get_default_font(self.table.site)
|
|
if font == None:
|
|
font = "Sans"
|
|
if font_size == None:
|
|
font_size = "8"
|
|
|
|
self.font = pango.FontDescription(font + " " + font_size)
|
|
|
|
# do we need to add some sort of condition here for dealing with a request for a font that doesn't exist?
|
|
|
|
# Set up a main window for this this instance of the HUD
|
|
self.main_window = gtk.Window()
|
|
self.main_window.set_gravity(gtk.gdk.GRAVITY_STATIC)
|
|
self.main_window.set_title(table.name + " FPDBHUD")
|
|
self.main_window.destroyhandler = self.main_window.connect("destroy", self.kill_hud)
|
|
self.main_window.set_decorated(False)
|
|
self.main_window.set_opacity(self.colors["hudopacity"])
|
|
|
|
self.ebox = gtk.EventBox()
|
|
self.label = gtk.Label("FPDB Menu (Right Click)\nLeft-drag to move")
|
|
|
|
self.backgroundcolor = gtk.gdk.color_parse(self.colors['hudbgcolor'])
|
|
self.foregroundcolor = gtk.gdk.color_parse(self.colors['hudfgcolor'])
|
|
|
|
self.label.modify_bg(gtk.STATE_NORMAL, self.backgroundcolor)
|
|
self.label.modify_fg(gtk.STATE_NORMAL, self.foregroundcolor)
|
|
|
|
self.main_window.add(self.ebox)
|
|
self.ebox.add(self.label)
|
|
|
|
self.ebox.modify_bg(gtk.STATE_NORMAL, self.backgroundcolor)
|
|
self.ebox.modify_fg(gtk.STATE_NORMAL, self.foregroundcolor)
|
|
|
|
self.main_window.move(self.table.x, self.table.y)
|
|
|
|
# A popup menu for the main window
|
|
self.menu = gtk.Menu()
|
|
self.item1 = gtk.MenuItem('Kill this HUD')
|
|
self.menu.append(self.item1)
|
|
self.item1.connect("activate", self.kill_hud_menu)
|
|
self.item1.show()
|
|
|
|
self.item2 = gtk.MenuItem('Save Layout')
|
|
self.menu.append(self.item2)
|
|
self.item2.connect("activate", self.save_layout)
|
|
self.item2.show()
|
|
|
|
self.item3 = gtk.MenuItem('Reposition Stats')
|
|
self.menu.append(self.item3)
|
|
self.item3.connect("activate", self.reposition_windows)
|
|
self.item3.show()
|
|
|
|
self.item4 = gtk.MenuItem('Debug Stat Windows')
|
|
self.menu.append(self.item4)
|
|
self.item4.connect("activate", self.debug_stat_windows)
|
|
self.item4.show()
|
|
|
|
self.ebox.connect_object("button-press-event", self.on_button_press, self.menu)
|
|
|
|
self.main_window.show_all()
|
|
|
|
# TODO: fold all uses of this type of 'topify' code into a single function, if the differences between the versions don't
|
|
# create adverse effects?
|
|
|
|
if os.name == 'nt':
|
|
self.topify_window(self.main_window)
|
|
else:
|
|
self.main_window.parentgdkhandle = gtk.gdk.window_foreign_new(self.table.number) # gets a gdk handle for poker client
|
|
self.main_window.gdkhandle = gtk.gdk.window_foreign_new(self.main_window.window.xid) # gets a gdk handle for the hud table window
|
|
self.main_window.gdkhandle.set_transient_for(self.main_window.parentgdkhandle) #
|
|
|
|
def update_table_position(self):
|
|
if os.name == 'nt':
|
|
if not win32gui.IsWindow(self.table.number):
|
|
self.kill_hud()
|
|
return False
|
|
# anyone know how to do this in unix, or better yet, trap the X11 error that is triggered when executing the get_origin() for a closed window?
|
|
|
|
(x, y) = self.main_window.parentgdkhandle.get_origin()
|
|
if self.table.x != x or self.table.y != y:
|
|
self.table.x = x
|
|
self.table.y = y
|
|
self.main_window.move(x, y)
|
|
adj = self.adj_seats(self.hand, self.config)
|
|
loc = self.config.get_locations(self.table.site, self.max)
|
|
for i in range(1, self.max + 1):
|
|
(x, y) = loc[adj[i]]
|
|
if i in self.stat_windows:
|
|
self.stat_windows[i].relocate(x, y)
|
|
return True
|
|
|
|
def on_button_press(self, widget, event):
|
|
if event.button == 1:
|
|
self.main_window.begin_move_drag(event.button, int(event.x_root), int(event.y_root), event.time)
|
|
return True
|
|
if event.button == 3:
|
|
widget.popup(None, None, None, event.button, event.time)
|
|
return True
|
|
return False
|
|
|
|
def kill_hud(self, *args):
|
|
if self.deleted:
|
|
return # no killing self twice.
|
|
for k in self.stat_windows:
|
|
self.stat_windows[k].window.destroy()
|
|
self.deleted = True
|
|
self.main_window.disconnect(self.main_window.destroyhandler) # so we don't potentially infiniteloop in here, right
|
|
self.main_window.destroy()
|
|
HUD_main.HUD_removed(self.table.name)
|
|
|
|
def kill_hud_menu(self, *args):
|
|
self.main_window.destroy()
|
|
|
|
def reposition_windows(self, *args):
|
|
for w in self.stat_windows:
|
|
self.stat_windows[w].window.move(self.stat_windows[w].x, self.stat_windows[w].y)
|
|
return True
|
|
|
|
def debug_stat_windows(self, *args):
|
|
print self.table, "\n", self.main_window.window.get_transient_for()
|
|
for w in self.stat_windows:
|
|
print self.stat_windows[w].window.window.get_transient_for()
|
|
|
|
def save_layout(self, *args):
|
|
new_layout = [(0, 0)] * self.max
|
|
for sw in self.stat_windows:
|
|
loc = self.stat_windows[sw].window.get_position()
|
|
new_loc = (loc[0] - self.table.x, loc[1] - self.table.y)
|
|
new_layout[self.stat_windows[sw].adj - 1] = new_loc
|
|
self.config.edit_layout(self.table.site, self.max, locations = new_layout)
|
|
self.config.save()
|
|
|
|
def adj_seats(self, hand, config):
|
|
adj = range(0, self.max + 1) # default seat adjustments = no adjustment
|
|
# does the user have a fav_seat?
|
|
try:
|
|
if int(config.supported_sites[self.table.site].layout[self.max].fav_seat) > 0:
|
|
fav_seat = config.supported_sites[self.table.site].layout[self.max].fav_seat
|
|
# db_connection = Database.Database(config, self.db_name, 'temp')
|
|
actual_seat = self.db_connection.get_actual_seat(hand, config.supported_sites[self.table.site].screen_name)
|
|
# db_connection.close_connection()
|
|
for i in range(0, self.max + 1):
|
|
j = actual_seat + i
|
|
if j > self.max: j = j - self.max
|
|
adj[j] = fav_seat + i
|
|
if adj[j] > self.max: adj[j] = adj[j] - self.max
|
|
except:
|
|
pass
|
|
return adj
|
|
|
|
def create(self, hand, config):
|
|
# update this hud, to the stats and players as of "hand"
|
|
# hand is the hand id of the most recent hand played at this table
|
|
#
|
|
# this method also manages the creating and destruction of stat
|
|
# windows via calls to the Stat_Window class
|
|
|
|
adj = self.adj_seats(hand, config)
|
|
loc = self.config.get_locations(self.table.site, self.max)
|
|
|
|
# create the stat windows
|
|
for i in range(1, self.max + 1):
|
|
(x, y) = loc[adj[i]]
|
|
if i in self.stat_windows:
|
|
self.stat_windows[i].relocate(x, y)
|
|
else:
|
|
self.stat_windows[i] = Stat_Window(game = config.supported_games[self.poker_game],
|
|
parent = self,
|
|
table = self.table,
|
|
x = x,
|
|
y = y,
|
|
seat = i,
|
|
adj = adj[i],
|
|
player_id = 'fake',
|
|
font = self.font)
|
|
|
|
self.stats = []
|
|
for i in range(0, config.supported_games[self.poker_game].rows + 1):
|
|
row_list = [''] * config.supported_games[self.poker_game].cols
|
|
self.stats.append(row_list)
|
|
for stat in config.supported_games[self.poker_game].stats:
|
|
self.stats[config.supported_games[self.poker_game].stats[stat].row] \
|
|
[config.supported_games[self.poker_game].stats[stat].col] = \
|
|
config.supported_games[self.poker_game].stats[stat].stat_name
|
|
|
|
game_params = config.get_game_parameters(self.poker_game)
|
|
if not game_params['aux'] == "":
|
|
aux_params = config.get_aux_parameters(game_params['aux'])
|
|
self.aux_windows.append(eval("%s.%s(gtk.Window(), self, config, aux_params)" % (aux_params['module'], aux_params['class'])))
|
|
|
|
if os.name == "nt":
|
|
gobject.timeout_add(500, self.update_table_position)
|
|
|
|
def update(self, hand, config, stat_dict):
|
|
self.hand = hand # this is the last hand, so it is available later
|
|
self.stat_dict = stat_dict # so this is available for popups, etc
|
|
self.update_table_position()
|
|
self.stat_dict = stat_dict
|
|
|
|
for s in stat_dict:
|
|
try:
|
|
self.stat_windows[stat_dict[s]['seat']].player_id = stat_dict[s]['player_id']
|
|
except: # omg, we have more seats than stat windows .. damn poker sites with incorrect max seating info .. let's force 10 here
|
|
self.max = 10
|
|
self.create(hand, config)
|
|
self.stat_windows[stat_dict[s]['seat']].player_id = stat_dict[s]['player_id']
|
|
|
|
for r in range(0, config.supported_games[self.poker_game].rows):
|
|
for c in range(0, config.supported_games[self.poker_game].cols):
|
|
this_stat = config.supported_games[self.poker_game].stats[self.stats[r][c]]
|
|
number = Stats.do_stat(stat_dict, player = stat_dict[s]['player_id'], stat = self.stats[r][c])
|
|
statstring = this_stat.hudprefix + str(number[1]) + this_stat.hudsuffix
|
|
|
|
if this_stat.hudcolor != "":
|
|
self.label.modify_fg(gtk.STATE_NORMAL, gtk.gdk.color_parse(self.colors['hudfgcolor']))
|
|
self.stat_windows[stat_dict[s]['seat']].label[r][c].modify_fg(gtk.STATE_NORMAL, gtk.gdk.color_parse(this_stat.hudcolor))
|
|
|
|
self.stat_windows[stat_dict[s]['seat']].label[r][c].set_text(statstring)
|
|
if statstring != "xxx": # is there a way to tell if this particular stat window is visible already, or no?
|
|
self.stat_windows[stat_dict[s]['seat']].window.show_all()
|
|
# self.reposition_windows()
|
|
tip = stat_dict[s]['screen_name'] + "\n" + number[5] + "\n" + \
|
|
number[3] + ", " + number[4]
|
|
Stats.do_tip(self.stat_windows[stat_dict[s]['seat']].e_box[r][c], tip)
|
|
# for m in self.aux_windows:
|
|
# m.update_data(hand)
|
|
# m.update_gui(hand)
|
|
|
|
def topify_window(self, window):
|
|
"""Set the specified gtk window to stayontop in MS Windows."""
|
|
|
|
def windowEnumerationHandler(hwnd, resultList):
|
|
'''Callback for win32gui.EnumWindows() to generate list of window handles.'''
|
|
resultList.append((hwnd, win32gui.GetWindowText(hwnd)))
|
|
|
|
unique_name = 'unique name for finding this window'
|
|
real_name = window.get_title()
|
|
window.set_title(unique_name)
|
|
tl_windows = []
|
|
win32gui.EnumWindows(windowEnumerationHandler, tl_windows)
|
|
|
|
for w in tl_windows:
|
|
if w[1] == unique_name:
|
|
self.main_window.parentgdkhandle = gtk.gdk.window_foreign_new(long(self.table.number))
|
|
self.main_window.gdkhandle = gtk.gdk.window_foreign_new(w[0])
|
|
self.main_window.gdkhandle.set_transient_for(self.main_window.parentgdkhandle)
|
|
|
|
style = win32gui.GetWindowLong(self.table.number, win32con.GWL_EXSTYLE)
|
|
style |= win32con.WS_CLIPCHILDREN
|
|
win32gui.SetWindowLong(self.table.number, win32con.GWL_EXSTYLE, style)
|
|
window.set_title(real_name)
|
|
|
|
class Stat_Window:
|
|
|
|
def button_press_cb(self, widget, event, *args):
|
|
# This handles all callbacks from button presses on the event boxes in
|
|
# the stat windows. There is a bit of an ugly kludge to separate single-
|
|
# and double-clicks.
|
|
|
|
if event.button == 3: # right button event
|
|
Popup_window(widget, self)
|
|
|
|
if event.button == 2: # middle button event
|
|
self.window.hide()
|
|
|
|
if event.button == 1: # left button event
|
|
# TODO: make position saving save sizes as well?
|
|
if event.state & gtk.gdk.SHIFT_MASK:
|
|
self.window.begin_resize_drag(gtk.gdk.WINDOW_EDGE_SOUTH_EAST, event.button, int(event.x_root), int(event.y_root), event.time)
|
|
else:
|
|
self.window.begin_move_drag(event.button, int(event.x_root), int(event.y_root), event.time)
|
|
|
|
def relocate(self, x, y):
|
|
self.x = x + self.table.x
|
|
self.y = y + self.table.y
|
|
self.window.move(self.x, self.y)
|
|
|
|
def __init__(self, parent, game, table, seat, adj, x, y, player_id, font):
|
|
self.parent = parent # Hud object that this stat window belongs to
|
|
self.game = game # Configuration object for the curren
|
|
self.table = table # Table object where this is going
|
|
self.seat = seat # seat number of his player
|
|
self.adj = adj # the adjusted seat number for this player
|
|
self.x = x + table.x # table.x and y are the location of the table
|
|
self.y = y + table.y # x and y are the location relative to table.x & y
|
|
self.player_id = player_id # looks like this isn't used ;)
|
|
self.sb_click = 0 # used to figure out button clicks
|
|
|
|
self.window = gtk.Window()
|
|
self.window.set_decorated(0)
|
|
self.window.set_gravity(gtk.gdk.GRAVITY_STATIC)
|
|
|
|
self.window.set_title("%s" % seat)
|
|
self.window.set_property("skip-taskbar-hint", True)
|
|
self.window.set_transient_for(parent.main_window)
|
|
|
|
self.grid = gtk.Table(rows = self.game.rows, columns = self.game.cols, homogeneous = False)
|
|
self.window.add(self.grid)
|
|
|
|
self.e_box = []
|
|
self.frame = []
|
|
self.label = []
|
|
for r in range(self.game.rows):
|
|
self.e_box.append([])
|
|
self.label.append([])
|
|
for c in range(self.game.cols):
|
|
self.e_box[r].append( gtk.EventBox() )
|
|
|
|
self.e_box[r][c].modify_bg(gtk.STATE_NORMAL, parent.backgroundcolor)
|
|
self.e_box[r][c].modify_fg(gtk.STATE_NORMAL, parent.foregroundcolor)
|
|
|
|
Stats.do_tip(self.e_box[r][c], 'stuff')
|
|
self.grid.attach(self.e_box[r][c], c, c+1, r, r+1, xpadding = 0, ypadding = 0)
|
|
self.label[r].append( gtk.Label('xxx') )
|
|
|
|
self.label[r][c].modify_bg(gtk.STATE_NORMAL, parent.backgroundcolor)
|
|
self.label[r][c].modify_fg(gtk.STATE_NORMAL, parent.foregroundcolor)
|
|
|
|
self.e_box[r][c].add(self.label[r][c])
|
|
self.e_box[r][c].connect("button_press_event", self.button_press_cb)
|
|
self.label[r][c].modify_font(font)
|
|
|
|
self.window.set_opacity(parent.colors['hudopacity'])
|
|
|
|
self.window.move(self.x, self.y)
|
|
|
|
self.window.hide()
|
|
|
|
def destroy(*args): # call back for terminating the main eventloop
|
|
gtk.main_quit()
|
|
|
|
class Popup_window:
|
|
def __init__(self, parent, stat_window):
|
|
self.sb_click = 0
|
|
|
|
# create the popup window
|
|
self.window = gtk.Window()
|
|
self.window.set_decorated(0)
|
|
self.window.set_gravity(gtk.gdk.GRAVITY_STATIC)
|
|
self.window.set_title("popup")
|
|
self.window.set_property("skip-taskbar-hint", True)
|
|
self.window.set_transient_for(parent.get_toplevel())
|
|
|
|
self.window.set_position(gtk.WIN_POS_CENTER_ON_PARENT)
|
|
|
|
self.ebox = gtk.EventBox()
|
|
self.ebox.connect("button_press_event", self.button_press_cb)
|
|
self.lab = gtk.Label("stuff\nstuff\nstuff")
|
|
|
|
# need an event box so we can respond to clicks
|
|
self.window.add(self.ebox)
|
|
self.ebox.add(self.lab)
|
|
|
|
self.ebox.modify_bg(gtk.STATE_NORMAL, stat_window.parent.backgroundcolor)
|
|
self.ebox.modify_fg(gtk.STATE_NORMAL, stat_window.parent.foregroundcolor)
|
|
self.window.modify_bg(gtk.STATE_NORMAL, stat_window.parent.backgroundcolor)
|
|
self.window.modify_fg(gtk.STATE_NORMAL, stat_window.parent.foregroundcolor)
|
|
self.lab.modify_bg(gtk.STATE_NORMAL, stat_window.parent.backgroundcolor)
|
|
self.lab.modify_fg(gtk.STATE_NORMAL, stat_window.parent.foregroundcolor)
|
|
|
|
# figure out the row, col address of the click that activated the popup
|
|
row = 0
|
|
col = 0
|
|
for r in range(0, stat_window.game.rows):
|
|
for c in range(0, stat_window.game.cols):
|
|
if stat_window.e_box[r][c] == parent:
|
|
row = r
|
|
col = c
|
|
break
|
|
|
|
# figure out what popup format we're using
|
|
popup_format = "default"
|
|
for stat in stat_window.game.stats:
|
|
if stat_window.game.stats[stat].row == row and stat_window.game.stats[stat].col == col:
|
|
popup_format = stat_window.game.stats[stat].popup
|
|
break
|
|
|
|
# get the list of stats to be presented from the config
|
|
stat_list = []
|
|
for w in stat_window.parent.config.popup_windows:
|
|
if w == popup_format:
|
|
stat_list = stat_window.parent.config.popup_windows[w].pu_stats
|
|
break
|
|
|
|
# get a database connection
|
|
# db_connection = Database.Database(stat_window.parent.config, stat_window.parent.db_name, 'temp')
|
|
|
|
# calculate the stat_dict and then create the text for the pu
|
|
# stat_dict = db_connection.get_stats_from_hand(stat_window.parent.hand, stat_window.player_id)
|
|
# stat_dict = self.db_connection.get_stats_from_hand(stat_window.parent.hand)
|
|
# db_connection.close_connection()
|
|
stat_dict = stat_window.parent.stat_dict
|
|
pu_text = ""
|
|
for s in stat_list:
|
|
number = Stats.do_stat(stat_dict, player = int(stat_window.player_id), stat = s)
|
|
pu_text += number[3] + "\n"
|
|
|
|
self.lab.set_text(pu_text)
|
|
self.window.show_all()
|
|
|
|
self.window.set_transient_for(stat_window.window)
|
|
|
|
if os.name == 'nt':
|
|
self.topify_window(self.window)
|
|
|
|
def button_press_cb(self, widget, event, *args):
|
|
# This handles all callbacks from button presses on the event boxes in
|
|
# the popup windows. There is a bit of an ugly kludge to separate single-
|
|
# and double-clicks. This is the same code as in the Stat_window class
|
|
if event.button == 1: # left button event
|
|
pass
|
|
|
|
if event.button == 2: # middle button event
|
|
pass
|
|
|
|
if event.button == 3: # right button event
|
|
self.window.destroy()
|
|
|
|
def toggle_decorated(self, widget):
|
|
top = widget.get_toplevel()
|
|
(x, y) = top.get_position()
|
|
|
|
if top.get_decorated():
|
|
top.set_decorated(0)
|
|
top.move(x, y)
|
|
else:
|
|
top.set_decorated(1)
|
|
top.move(x, y)
|
|
|
|
def topify_window(self, window):
|
|
"""Set the specified gtk window to stayontop in MS Windows."""
|
|
|
|
def windowEnumerationHandler(hwnd, resultList):
|
|
'''Callback for win32gui.EnumWindows() to generate list of window handles.'''
|
|
resultList.append((hwnd, win32gui.GetWindowText(hwnd)))
|
|
|
|
unique_name = 'unique name for finding this window'
|
|
real_name = window.get_title()
|
|
window.set_title(unique_name)
|
|
tl_windows = []
|
|
win32gui.EnumWindows(windowEnumerationHandler, tl_windows)
|
|
|
|
for w in tl_windows:
|
|
if w[1] == unique_name:
|
|
win32gui.SetWindowPos(w[0], win32con.HWND_TOPMOST, 0, 0, 0, 0, win32con.SWP_NOMOVE|win32con.SWP_NOSIZE)
|
|
|
|
window.set_title(real_name)
|
|
|
|
if __name__== "__main__":
|
|
main_window = gtk.Window()
|
|
main_window.connect("destroy", destroy)
|
|
label = gtk.Label('Fake main window, blah blah, blah\nblah, blah')
|
|
main_window.add(label)
|
|
main_window.show_all()
|
|
|
|
c = Configuration.Config()
|
|
#tables = Tables.discover(c)
|
|
t = Tables.discover_table_by_name(c, "Motorway")
|
|
if t is None:
|
|
print "Table not found."
|
|
db = Database.Database(c, 'fpdb', 'holdem')
|
|
|
|
# for t in tables:
|
|
win = Hud(t, 10, 'holdem', c, db)
|
|
win.create(1, c)
|
|
# t.get_details()
|
|
win.update(8300, db, c)
|
|
|
|
gtk.main()
|