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
.get_translation()
29 # Standard Library modules
41 # FreePokerTools modules
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
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
69 log
.info(_("HUD_main starting: using db name = %s") % (db_name
))
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"))
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
88 self
.main_window
= gtk
.Window()
91 self
.main_window
.iconify()
93 self
.main_window
.hide()
95 if options
.xloc
is not None or options
.yloc
is not None:
96 if options
.xloc
is None:
98 if options
.yloc
is None:
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
)
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
)
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.
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"))
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
])
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
196 self
.db_connection
= Database
.Database(self
.config
)
198 # get hero's screen names and player ids
199 self
.hero
, self
.hero_ids
= {}, {}
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
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
216 for site
in self
.config
.get_supported_sites():
217 result
= self
.db_connection
.get_site_id(site
)
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:
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 ..."))
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
)
234 log
.exception(_("db error: skipping %s") % new_hand_id
)
237 if type == "tour": # hand is from a tournament
238 temp_key
= "%s Table %s" % (tour_number
, tab_number
)
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
)
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()
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
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
275 table_name
= "%s %s" % (tour_number
, tab_number
)
276 log
.error(_("HUD create: table name %s not found, skipping.") % table_name
)
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
)
285 log
.error(_('Table "%s" no longer exists') % table_name
)
288 self
.db_connection
.connection
.rollback()
291 self
.hud_dict
[temp_key
].table
.check_table_no(self
.hud_dict
[temp_key
])
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']
301 ######################################################################
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
311 def idle_resize(hud
):
312 gtk
.gdk
.threads_enter()
314 [aw
.update_card_positions() for aw
in hud
.aux_windows
]
317 log
.exception(_("Error resizing HUD for table: %s.") % hud
.table
.title
)
319 gtk
.gdk
.threads_leave()
321 def idle_kill(hud_main
, table
):
322 gtk
.gdk
.threads_enter()
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)
331 log
.exception(_("Error killing HUD for table: %s.") % table
.title
)
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()
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
)
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
:
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()
354 log
.exception(_("Error creating HUD for hand %s.") % new_hand_id
)
356 gtk
.gdk
.threads_leave()
359 def idle_update(hud_main
, new_hand_id
, table_name
, config
):
360 gtk
.gdk
.threads_enter()
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
]
365 log
.exception(_("Error updating HUD for hand %s.") % new_hand_id
)
367 gtk
.gdk
.threads_leave()
370 if __name__
== "__main__":
372 # start the HUD_main object
373 hm
= HUD_main(db_name
= options
.dbname
)
375 # start the event loop