2 # -*- coding: utf-8 -*-
4 # Copyright 2008-2011, Ray E. Barker
6 # This program is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; either version 2 of the License, or
9 # (at your option) any later version.
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
20 ########################################################################
24 Main for FreePokerTools HUD.
27 _
= L10n
.init_translation()
29 # Standard Library modules
42 # FreePokerTools modules
48 (options
, argv
) = Options
.fpdb_options()
50 # get the correct module for the current os
51 if sys
.platform
[0:5] == 'linux':
52 import XTables
as Tables
53 elif sys
.platform
== 'darwin':
54 import OSXTables
as Tables
55 else: # This is bad--figure out the values for the various windows flavors
57 import WinTables
as Tables
59 # get config and set up logger
60 Configuration
.set_logfile("HUD-log.txt")
61 c
= Configuration
.Config(file=options
.config
, dbname
=options
.dbname
)
62 log
= logging
.getLogger("hud")
64 class HUD_main(object):
65 """A main() object to own both the read_stdin thread and the gui."""
66 # This class mainly provides state for controlling the multiple HUDs.
68 def __init__(self
, db_name
='fpdb'):
69 self
.db_name
= db_name
71 log
.info(_("HUD_main starting") + ": " + _("Using db name = %s") % (db_name
))
74 if not options
.errorsToConsole
:
75 fileName
= os
.path
.join(self
.config
.dir_log
, 'HUD-errors.txt')
76 log
.info(_("Note: error output is being diverted to %s.") % fileName
)
77 log
.info(_("Any major error will be reported there _only_."))
78 errorFile
= open(fileName
, 'w', 0)
79 sys
.stderr
= errorFile
80 log
.info(_("HUD_main starting"))
83 self
.hud_params
= self
.config
.get_hud_ui_parameters()
85 # a thread to read stdin
86 gobject
.threads_init() # this is required
87 thread
.start_new_thread(self
.read_stdin
, ()) # starts the thread
90 self
.main_window
= gtk
.Window()
93 self
.main_window
.iconify()
95 self
.main_window
.hide()
97 if options
.xloc
is not None or options
.yloc
is not None:
98 if options
.xloc
is None:
100 if options
.yloc
is None:
102 self
.main_window
.move(options
.xloc
,options
.yloc
)
103 self
.main_window
.connect("client_moved", self
.client_moved
)
104 self
.main_window
.connect("client_resized", self
.client_resized
)
105 self
.main_window
.connect("client_destroyed", self
.client_destroyed
)
106 self
.main_window
.connect("game_changed", self
.game_changed
)
107 self
.main_window
.connect("table_changed", self
.table_changed
)
108 self
.main_window
.connect("destroy", self
.destroy
)
110 self
.label
= gtk
.Label(_('Closing this window will exit from the HUD.'))
111 self
.vb
.add(self
.label
)
112 self
.main_window
.add(self
.vb
)
113 self
.main_window
.set_title("HUD Main Window")
114 cards
= os
.path
.join(os
.getcwd(), '..','gfx','fpdb-cards.png')
115 if os
.path
.exists(cards
):
116 self
.main_window
.set_icon_from_file(cards
)
117 elif os
.path
.exists('/usr/share/pixmaps/fpdb-cards.png'):
118 self
.main_window
.set_icon_from_file('/usr/share/pixmaps/fpdb-cards.png')
120 if not options
.hidden
:
121 self
.main_window
.show_all()
122 gobject
.timeout_add(800, self
.check_tables
)
125 log
.exception(_("Error initializing main_window"))
126 gtk
.main_quit() # we're hosed, just terminate
128 def client_moved(self
, widget
, hud
):
129 log
.debug("client_moved event")
130 hud
.up_update_table_position()
132 def client_resized(self
, widget
, hud
):
133 log
.debug("client_resized event")
134 gobject
.idle_add(idle_resize
, hud
)
136 def client_destroyed(self
, widget
, hud
): # call back for terminating the main eventloop
137 log
.debug("client_destroyed event")
138 self
.kill_hud(None, hud
.table
.key
)
140 def game_changed(self
, widget
, hud
):
141 print "hud_main: " + _("Game changed.")
143 def table_changed(self
, widget
, hud
):
144 print "hud_main: " + _("Table changed")
145 self
.kill_hud(None, hud
.table
.key
)
147 def destroy(self
, *args
): # call back for terminating the main eventloop
148 log
.info(_("Quitting normally"))
151 def kill_hud(self
, event
, table
):
152 log
.debug("kill_hud event")
153 gobject
.idle_add(idle_kill
, self
, table
)
155 def check_tables(self
):
156 for hud
in self
.hud_dict
.keys():
157 self
.hud_dict
[hud
].table
.check_table(self
.hud_dict
[hud
])
160 def create_HUD(self
, new_hand_id
, table
, temp_key
, max, poker_game
, type, stat_dict
, cards
):
161 """type is "ring" or "tour" used to set hud_params"""
163 self
.hud_dict
[temp_key
] = Hud
.Hud(self
, table
, max, poker_game
, self
.config
, self
.db_connection
)
164 self
.hud_dict
[temp_key
].table_name
= temp_key
165 self
.hud_dict
[temp_key
].stat_dict
= stat_dict
166 self
.hud_dict
[temp_key
].cards
= cards
167 table
.hud
= self
.hud_dict
[temp_key
]
169 # set agg_bb_mult so that aggregate_tour and aggregate_ring can be ignored,
170 # agg_bb_mult == 1 means no aggregation after these if statements:
171 if type == "tour" and self
.hud_params
['aggregate_tour'] == False:
172 self
.hud_dict
[temp_key
].hud_params
['agg_bb_mult'] = 1
173 elif type == "ring" and self
.hud_params
['aggregate_ring'] == False:
174 self
.hud_dict
[temp_key
].hud_params
['agg_bb_mult'] = 1
175 if type == "tour" and self
.hud_params
['h_aggregate_tour'] == False:
176 self
.hud_dict
[temp_key
].hud_params
['h_agg_bb_mult'] = 1
177 elif type == "ring" and self
.hud_params
['h_aggregate_ring'] == False:
178 self
.hud_dict
[temp_key
].hud_params
['h_agg_bb_mult'] = 1
179 # sqlcoder: I forget why these are set to true (aren't they ignored from now on?)
180 # but I think it's needed:
181 self
.hud_params
['aggregate_ring'] = True
182 self
.hud_params
['h_aggregate_ring'] = True
183 # so maybe the tour ones should be set as well? does this fix the bug I see mentioned?
184 self
.hud_params
['aggregate_tour'] = True
185 self
.hud_params
['h_aggregate_tour'] = True
187 [aw
.update_data(new_hand_id
, self
.db_connection
) for aw
in self
.hud_dict
[temp_key
].aux_windows
]
188 gobject
.idle_add(idle_create
, self
, new_hand_id
, table
, temp_key
, max, poker_game
, type, stat_dict
, cards
)
190 def update_HUD(self
, new_hand_id
, table_name
, config
):
191 """Update a HUD gui from inside the non-gui read_stdin thread."""
192 gobject
.idle_add(idle_update
, self
, new_hand_id
, table_name
, config
)
194 def read_stdin(self
): # This is the thread function
195 """Do all the non-gui heavy lifting for the HUD program."""
197 # This db connection is for the read_stdin thread only. It should not
198 # be passed to HUDs for use in the gui thread. HUD objects should not
199 # need their own access to the database, but should open their own
201 self
.db_connection
= Database
.Database(self
.config
)
203 # get hero's screen names and player ids
204 self
.hero
, self
.hero_ids
= {}, {}
207 while 1: # wait for a new hand number on stdin
208 new_hand_id
= sys
.stdin
.readline()
209 new_hand_id
= string
.rstrip(new_hand_id
)
210 log
.debug(_("Received hand no %s") % new_hand_id
)
211 if new_hand_id
== "": # blank line means quit
213 break # this thread is not always killed immediately with gtk.main_quit()
215 # This block cannot be hoisted outside the while loop, because it would
216 # cause a problem when auto importing into an empty db.
218 # FIXME: This doesn't work in the case of the player playing on 2
219 # sites at once (???) Eratosthenes
221 for site
in self
.config
.get_supported_sites():
222 result
= self
.db_connection
.get_site_id(site
)
224 site_id
= result
[0][0]
225 self
.hero
[site_id
] = self
.config
.supported_sites
[site
].screen_name
226 self
.hero_ids
[site_id
] = self
.db_connection
.get_player_id(self
.config
, site
, self
.hero
[site_id
])
227 if self
.hero_ids
[site_id
] is not None:
230 self
.hero_ids
[site_id
] = -1
232 # get basic info about the new hand from the db
233 # if there is a db error, complain, skip hand, and proceed
234 log
.info("HUD_main.read_stdin: " + _("Hand processing starting."))
236 (table_name
, max, poker_game
, type, site_id
, site_name
, num_seats
, tour_number
, tab_number
) = \
237 self
.db_connection
.get_table_info(new_hand_id
)
239 log
.exception(_("database error: skipping %s") % new_hand_id
)
242 if type == "tour": # hand is from a tournament
243 temp_key
= "%s Table %s" % (tour_number
, tab_number
)
245 temp_key
= table_name
247 # Update an existing HUD
248 if temp_key
in self
.hud_dict
:
249 # get stats using hud's specific params and get cards
250 self
.db_connection
.init_hud_stat_vars( self
.hud_dict
[temp_key
].hud_params
['hud_days']
251 , self
.hud_dict
[temp_key
].hud_params
['h_hud_days'])
252 stat_dict
= self
.db_connection
.get_stats_from_hand(new_hand_id
, type, self
.hud_dict
[temp_key
].hud_params
,
253 self
.hero_ids
[site_id
], num_seats
)
256 self
.hud_dict
[temp_key
].stat_dict
= stat_dict
257 except KeyError: # HUD instance has been killed off, key is stale
258 log
.error(_('%s was not found') % ("hud_dict[%s]" % temp_key
))
259 log
.error(_('will not send hand'))
260 # Unlocks table, copied from end of function
261 self
.db_connection
.connection
.rollback()
264 self
.hud_dict
[temp_key
].cards
= self
.get_cards(new_hand_id
)
265 [aw
.update_data(new_hand_id
, self
.db_connection
) for aw
in self
.hud_dict
[temp_key
].aux_windows
]
266 self
.update_HUD(new_hand_id
, temp_key
, self
.config
)
268 # Or create a new HUD
270 # get stats using default params--also get cards
271 self
.db_connection
.init_hud_stat_vars( self
.hud_params
['hud_days'], self
.hud_params
['h_hud_days'] )
272 stat_dict
= self
.db_connection
.get_stats_from_hand(new_hand_id
, type, self
.hud_params
,
273 self
.hero_ids
[site_id
], num_seats
)
274 cards
= self
.get_cards(new_hand_id
)
275 table_kwargs
= dict(table_name
=table_name
, tournament
=tour_number
, table_number
=tab_number
)
276 tablewindow
= Tables
.Table(self
.config
, site_name
, **table_kwargs
)
277 if tablewindow
.number
is None:
278 # If no client window is found on the screen, complain and continue
280 table_name
= "%s %s" % (tour_number
, tab_number
)
281 log
.error(_("HUD create: table name %s not found, skipping.") % table_name
)
283 tablewindow
.key
= temp_key
284 tablewindow
.max = max
285 tablewindow
.site
= site_name
286 # Test that the table window still exists
287 if hasattr(tablewindow
, 'number'):
288 self
.create_HUD(new_hand_id
, tablewindow
, temp_key
, max, poker_game
, type, stat_dict
, cards
)
290 log
.error(_('Table "%s" no longer exists') % table_name
)
293 self
.db_connection
.connection
.rollback()
296 self
.hud_dict
[temp_key
].table
.check_table_no(self
.hud_dict
[temp_key
])
300 def get_cards(self
, new_hand_id
):
301 cards
= self
.db_connection
.get_cards(new_hand_id
)
302 comm_cards
= self
.db_connection
.get_common_cards(new_hand_id
)
303 if comm_cards
!= {}: # stud!
304 cards
['common'] = comm_cards
['common']
306 ######################################################################
309 # These are passed to the event loop by the non-gui thread to do
310 # gui things in a thread-safe way. They are passed to the event
311 # loop using the gobject.idle_add() function.
313 # A general rule for gtk is that only 1 thread should be messing
316 def idle_resize(hud
):
317 gtk
.gdk
.threads_enter()
319 [aw
.update_card_positions() for aw
in hud
.aux_windows
]
322 log
.exception(_("Error resizing HUD for table: %s.") % hud
.table
.title
)
324 gtk
.gdk
.threads_leave()
326 def idle_kill(hud_main
, table
):
327 gtk
.gdk
.threads_enter()
329 if table
in hud_main
.hud_dict
:
330 hud_main
.vb
.remove(hud_main
.hud_dict
[table
].tablehudlabel
)
331 hud_main
.hud_dict
[table
].main_window
.destroy()
332 hud_main
.hud_dict
[table
].kill()
333 del(hud_main
.hud_dict
[table
])
334 hud_main
.main_window
.resize(1, 1)
336 log
.exception(_("Error killing HUD for table: %s.") % table
.title
)
338 gtk
.gdk
.threads_leave()
340 def idle_create(hud_main
, new_hand_id
, table
, temp_key
, max, poker_game
, type, stat_dict
, cards
):
342 gtk
.gdk
.threads_enter()
344 if table
.gdkhandle
is not None: # on windows this should already be set
345 table
.gdkhandle
= gtk
.gdk
.window_foreign_new(table
.number
)
346 newlabel
= gtk
.Label("%s - %s" % (table
.site
, temp_key
))
347 hud_main
.vb
.add(newlabel
)
349 hud_main
.main_window
.resize_children()
351 hud_main
.hud_dict
[temp_key
].tablehudlabel
= newlabel
352 hud_main
.hud_dict
[temp_key
].create(new_hand_id
, hud_main
.config
, stat_dict
, cards
)
353 for m
in hud_main
.hud_dict
[temp_key
].aux_windows
:
355 m
.update_gui(new_hand_id
)
356 hud_main
.hud_dict
[temp_key
].update(new_hand_id
, hud_main
.config
)
357 hud_main
.hud_dict
[temp_key
].reposition_windows()
359 log
.exception(_("Error creating HUD for hand %s.") % new_hand_id
)
361 gtk
.gdk
.threads_leave()
364 def idle_update(hud_main
, new_hand_id
, table_name
, config
):
365 gtk
.gdk
.threads_enter()
367 hud_main
.hud_dict
[table_name
].update(new_hand_id
, config
)
368 [aw
.update_gui(new_hand_id
) for aw
in hud_main
.hud_dict
[table_name
].aux_windows
]
370 log
.exception(_("Error updating HUD for hand %s.") % new_hand_id
)
372 gtk
.gdk
.threads_leave()
375 if __name__
== "__main__":
377 # start the HUD_main object
378 hm
= HUD_main(db_name
= options
.dbname
)
380 # start the event loop