Expanded my entry in contributors.txt.
[fpdb-dooglus.git] / pyfpdb / HUD_main.pyw
bloba20722d943b4985909c1dfd8b7f395dacf11e791
1 #!/usr/bin/env python
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 ########################################################################
22 """Hud_main.py
24 Main for FreePokerTools HUD.
25 """
26 import L10n
27 _ = L10n.get_translation()
29 # Standard Library modules
30 import sys
31 import os
32 import traceback
33 import thread
34 import time
35 import string
37 # pyGTK modules
38 import gtk
39 import gobject
41 # FreePokerTools modules
42 import Configuration
43 import Database
44 import Hud
45 import Options
47 (options, argv) = Options.fpdb_options()
49 # get the correct module for the current os
50 if sys.platform == 'linux2':
51 import XTables as Tables
52 elif sys.platform == 'darwin':
53 import OSXTables as Tables
54 else: # This is bad--figure out the values for the various windows flavors
55 is_windows = True
56 import WinTables as Tables
58 # get config and set up logger
59 c = Configuration.Config(file=options.config, dbname=options.dbname)
60 log = Configuration.get_logger("logging.conf", "hud", log_dir=c.dir_log, log_file='HUD-log.txt')
62 class HUD_main(object):
63 """A main() object to own both the read_stdin thread and the gui."""
64 # This class mainly provides state for controlling the multiple HUDs.
66 def __init__(self, db_name='fpdb'):
67 self.db_name = db_name
68 self.config = c
69 log.info(_("HUD_main starting: using db name = %s") % (db_name))
71 try:
72 if not options.errorsToConsole:
73 fileName = os.path.join(self.config.dir_log, 'HUD-errors.txt')
74 log.info(_("Note: error output is being diverted to:") + fileName)
75 log.info(_("Any major error will be reported there _only_."))
76 errorFile = open(fileName, 'w', 0)
77 sys.stderr = errorFile
78 log.info(_("HUD_main starting"))
80 self.hud_dict = {}
81 self.hud_params = self.config.get_hud_ui_parameters()
83 # a thread to read stdin
84 gobject.threads_init() # this is required
85 thread.start_new_thread(self.read_stdin, ()) # starts the thread
87 # a main window
88 self.main_window = gtk.Window()
90 if options.minimized:
91 self.main_window.iconify()
92 if options.hidden:
93 self.main_window.hide()
95 if options.xloc is not None or options.yloc is not None:
96 if options.xloc is None:
97 options.xloc = 0
98 if options.yloc is None:
99 options.yloc = 0
100 self.main_window.move(options.xloc,options.yloc)
101 self.main_window.connect("client_moved", self.client_moved)
102 self.main_window.connect("client_resized", self.client_resized)
103 self.main_window.connect("client_destroyed", self.client_destroyed)
104 self.main_window.connect("game_changed", self.game_changed)
105 self.main_window.connect("table_changed", self.table_changed)
106 self.main_window.connect("destroy", self.destroy)
107 self.vb = gtk.VBox()
108 self.label = gtk.Label(_('Closing this window will exit from the HUD.'))
109 self.vb.add(self.label)
110 self.main_window.add(self.vb)
111 self.main_window.set_title("HUD Main Window")
112 cards = os.path.join(os.getcwd(), '..','gfx','fpdb-cards.png')
113 if os.path.exists(cards):
114 self.main_window.set_icon_from_file(cards)
115 elif os.path.exists('/usr/share/pixmaps/fpdb-cards.png'):
116 self.main_window.set_icon_from_file('/usr/share/pixmaps/fpdb-cards.png')
118 if not options.hidden:
119 self.main_window.show_all()
120 gobject.timeout_add(800, self.check_tables)
122 except:
123 log.exception(_("Error initializing main_window"))
124 gtk.main_quit() # we're hosed, just terminate
126 def client_moved(self, widget, hud):
127 hud.up_update_table_position()
129 def client_resized(self, widget, hud):
130 #TODO Don't forget to get rid of this.
131 if not is_windows:
132 gigobject.idle_add(idle_resize, hud)
134 def client_destroyed(self, widget, hud): # call back for terminating the main eventloop
135 self.kill_hud(None, hud.table.key)
137 def game_changed(self, widget, hud):
138 print _("hud_main: Game changed.")
140 def table_changed(self, widget, hud):
141 self.kill_hud(None, hud.table.key)
143 def destroy(self, *args): # call back for terminating the main eventloop
144 log.info(_("Quitting normally"))
145 gtk.main_quit()
147 def kill_hud(self, event, table):
148 gobject.idle_add(idle_kill, self, table)
150 def check_tables(self):
151 for hud in self.hud_dict.keys():
152 self.hud_dict[hud].table.check_table(self.hud_dict[hud])
153 return True
155 def create_HUD(self, new_hand_id, table, temp_key, max, poker_game, type, stat_dict, cards):
156 """type is "ring" or "tour" used to set hud_params"""
158 self.hud_dict[temp_key] = Hud.Hud(self, table, max, poker_game, self.config, self.db_connection)
159 self.hud_dict[temp_key].table_name = temp_key
160 self.hud_dict[temp_key].stat_dict = stat_dict
161 self.hud_dict[temp_key].cards = cards
162 table.hud = self.hud_dict[temp_key]
164 # set agg_bb_mult so that aggregate_tour and aggregate_ring can be ignored,
165 # agg_bb_mult == 1 means no aggregation after these if statements:
166 if type == "tour" and self.hud_params['aggregate_tour'] == False:
167 self.hud_dict[temp_key].hud_params['agg_bb_mult'] = 1
168 elif type == "ring" and self.hud_params['aggregate_ring'] == False:
169 self.hud_dict[temp_key].hud_params['agg_bb_mult'] = 1
170 if type == "tour" and self.hud_params['h_aggregate_tour'] == False:
171 self.hud_dict[temp_key].hud_params['h_agg_bb_mult'] = 1
172 elif type == "ring" and self.hud_params['h_aggregate_ring'] == False:
173 self.hud_dict[temp_key].hud_params['h_agg_bb_mult'] = 1
174 # sqlcoder: I forget why these are set to true (aren't they ignored from now on?)
175 # but I think it's needed:
176 self.hud_params['aggregate_ring'] = True
177 self.hud_params['h_aggregate_ring'] = True
178 # so maybe the tour ones should be set as well? does this fix the bug I see mentioned?
179 self.hud_params['aggregate_tour'] = True
180 self.hud_params['h_aggregate_tour'] = True
182 [aw.update_data(new_hand_id, self.db_connection) for aw in self.hud_dict[temp_key].aux_windows]
183 gobject.idle_add(idle_create, self, new_hand_id, table, temp_key, max, poker_game, type, stat_dict, cards)
185 def update_HUD(self, new_hand_id, table_name, config):
186 """Update a HUD gui from inside the non-gui read_stdin thread."""
187 gobject.idle_add(idle_update, self, new_hand_id, table_name, config)
189 def read_stdin(self): # This is the thread function
190 """Do all the non-gui heavy lifting for the HUD program."""
192 # This db connection is for the read_stdin thread only. It should not
193 # be passed to HUDs for use in the gui thread. HUD objects should not
194 # need their own access to the database, but should open their own
195 # if it is required.
196 self.db_connection = Database.Database(self.config)
198 # get hero's screen names and player ids
199 self.hero, self.hero_ids = {}, {}
200 found = False
202 while 1: # wait for a new hand number on stdin
203 new_hand_id = sys.stdin.readline()
204 new_hand_id = string.rstrip(new_hand_id)
205 log.debug(_("Received hand no %s") % new_hand_id)
206 if new_hand_id == "": # blank line means quit
207 self.destroy()
208 break # this thread is not always killed immediately with gtk.main_quit()
210 # This block cannot be hoisted outside the while loop, because it would
211 # cause a problem when auto importing into an empty db.
213 # FIXME: This doesn't work in the case of the player playing on 2
214 # sites at once (???) Eratosthenes
215 if not found:
216 for site in self.config.get_supported_sites():
217 result = self.db_connection.get_site_id(site)
218 if result:
219 site_id = result[0][0]
220 self.hero[site_id] = self.config.supported_sites[site].screen_name
221 self.hero_ids[site_id] = self.db_connection.get_player_id(self.config, site, self.hero[site_id])
222 if self.hero_ids[site_id] is not None:
223 found = True
224 else:
225 self.hero_ids[site_id] = -1
227 # get basic info about the new hand from the db
228 # if there is a db error, complain, skip hand, and proceed
229 log.info(_("HUD_main.read_stdin: hand processing starting ..."))
230 try:
231 (table_name, max, poker_game, type, site_id, site_name, num_seats, tour_number, tab_number) = \
232 self.db_connection.get_table_info(new_hand_id)
233 except Exception:
234 log.exception(_("db error: skipping %s") % new_hand_id)
235 continue
237 if type == "tour": # hand is from a tournament
238 temp_key = "%s Table %s" % (tour_number, tab_number)
239 else:
240 temp_key = table_name
242 # Update an existing HUD
243 if temp_key in self.hud_dict:
244 # get stats using hud's specific params and get cards
245 self.db_connection.init_hud_stat_vars( self.hud_dict[temp_key].hud_params['hud_days']
246 , self.hud_dict[temp_key].hud_params['h_hud_days'])
247 stat_dict = self.db_connection.get_stats_from_hand(new_hand_id, type, self.hud_dict[temp_key].hud_params,
248 self.hero_ids[site_id], num_seats)
250 try:
251 self.hud_dict[temp_key].stat_dict = stat_dict
252 except KeyError: # HUD instance has been killed off, key is stale
253 log.error(_('hud_dict[%s] was not found') % temp_key)
254 log.error(_('will not send hand'))
255 # Unlocks table, copied from end of function
256 self.db_connection.connection.rollback()
257 return
259 self.hud_dict[temp_key].cards = self.get_cards(new_hand_id)
260 [aw.update_data(new_hand_id, self.db_connection) for aw in self.hud_dict[temp_key].aux_windows]
261 self.update_HUD(new_hand_id, temp_key, self.config)
263 # Or create a new HUD
264 else:
265 # get stats using default params--also get cards
266 self.db_connection.init_hud_stat_vars( self.hud_params['hud_days'], self.hud_params['h_hud_days'] )
267 stat_dict = self.db_connection.get_stats_from_hand(new_hand_id, type, self.hud_params,
268 self.hero_ids[site_id], num_seats)
269 cards = self.get_cards(new_hand_id)
270 table_kwargs = dict(table_name=table_name, tournament=tour_number, table_number=tab_number)
271 tablewindow = Tables.Table(self.config, site_name, **table_kwargs)
272 if tablewindow.number is None:
273 # If no client window is found on the screen, complain and continue
274 if type == "tour":
275 table_name = "%s %s" % (tour_number, tab_number)
276 log.error(_("HUD create: table name %s not found, skipping.") % table_name)
277 else:
278 tablewindow.key = temp_key
279 tablewindow.max = max
280 tablewindow.site = site_name
281 # Test that the table window still exists
282 if hasattr(tablewindow, 'number'):
283 self.create_HUD(new_hand_id, tablewindow, temp_key, max, poker_game, type, stat_dict, cards)
284 else:
285 log.error(_('Table "%s" no longer exists') % table_name)
286 return
288 self.db_connection.connection.rollback()
289 if type == "tour":
290 try:
291 self.hud_dict[temp_key].table.check_table_no(self.hud_dict[temp_key])
292 except KeyError:
293 pass
295 def get_cards(self, new_hand_id):
296 cards = self.db_connection.get_cards(new_hand_id)
297 comm_cards = self.db_connection.get_common_cards(new_hand_id)
298 if comm_cards != {}: # stud!
299 cards['common'] = comm_cards['common']
300 return cards
301 ######################################################################
302 # idle FUNCTIONS
304 # These are passed to the event loop by the non-gui thread to do
305 # gui things in a thread-safe way. They are passed to the event
306 # loop using the gobject.idle_add() function.
308 # A general rule for gtk is that only 1 thread should be messing
309 # with the gui.
311 def idle_resize(hud):
312 gtk.gdk.threads_enter()
313 try:
314 [aw.update_card_positions() for aw in hud.aux_windows]
315 hud.resize_windows()
316 except:
317 log.exception(_("Error resizing HUD for table: %s.") % hud.table.title)
318 finally:
319 gtk.gdk.threads_leave()
321 def idle_kill(hud_main, table):
322 gtk.gdk.threads_enter()
323 try:
324 if table in hud_main.hud_dict:
325 hud_main.vb.remove(hud_main.hud_dict[table].tablehudlabel)
326 hud_main.hud_dict[table].main_window.destroy()
327 hud_main.hud_dict[table].kill()
328 del(hud_main.hud_dict[table])
329 hud_main.main_window.resize(1, 1)
330 except:
331 log.exception(_("Error killing HUD for table: %s.") % table.title)
332 finally:
333 gtk.gdk.threads_leave()
335 def idle_create(hud_main, new_hand_id, table, temp_key, max, poker_game, type, stat_dict, cards):
337 gtk.gdk.threads_enter()
338 try:
339 if table.gdkhandle is not None: # on windows this should already be set
340 table.gdkhandle = gtk.gdk.window_foreign_new(table.number)
341 newlabel = gtk.Label("%s - %s" % (table.site, temp_key))
342 hud_main.vb.add(newlabel)
343 newlabel.show()
344 hud_main.main_window.resize_children()
346 hud_main.hud_dict[temp_key].tablehudlabel = newlabel
347 hud_main.hud_dict[temp_key].create(new_hand_id, hud_main.config, stat_dict, cards)
348 for m in hud_main.hud_dict[temp_key].aux_windows:
349 m.create()
350 m.update_gui(new_hand_id)
351 hud_main.hud_dict[temp_key].update(new_hand_id, hud_main.config)
352 hud_main.hud_dict[temp_key].reposition_windows()
353 except:
354 log.exception(_("Error creating HUD for hand %s.") % new_hand_id)
355 finally:
356 gtk.gdk.threads_leave()
357 return False
359 def idle_update(hud_main, new_hand_id, table_name, config):
360 gtk.gdk.threads_enter()
361 try:
362 hud_main.hud_dict[table_name].update(new_hand_id, config)
363 [aw.update_gui(new_hand_id) for aw in hud_main.hud_dict[table_name].aux_windows]
364 except:
365 log.exception(_("Error updating HUD for hand %s.") % new_hand_id)
366 finally:
367 gtk.gdk.threads_leave()
368 return False
370 if __name__== "__main__":
372 # start the HUD_main object
373 hm = HUD_main(db_name = options.dbname)
375 # start the event loop
376 gtk.main()