1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
|
# encoding=utf-8
"""
Bidding data for JFR Pary result pages - GUI.
Graphical user interface to insert HTML tables with bidding data into traveller
files generated by JFR Pary.
"""
import Tkinter as tk
import tkFileDialog
import tkMessageBox
import Queue
import logging as log
import os
import threading
class BiddingGUI(tk.Frame):
"""GUI frame class."""
# Tk variable to store tournament result file path
__tour_filename = None
# Tk variable to store BWS file path
__bws_filename = None
def run_bidding_data(self):
"""
Run the parser and do all the actual work.
"On-click" event for analysis start button.
Sanitizes input parameters, handles warning/error messages,
imports main module (from CLI script) and runs it.
"""
self.queue(self.run_btn.__setitem__, 'state', tk.DISABLED)
try:
# reset error/warning count and log output field
self.__gui_logger.reset_counts()
# check for input parameter paths
if not os.path.exists(self.__bws_filename.get()):
raise Exception('BWS file not found')
if not os.path.exists(self.__tour_filename.get()):
raise Exception('Tournament results file not found')
# do the magic
from bidding_data import JFRBidding
parser = JFRBidding(
bws_file=self.__bws_filename.get(),
file_prefix=self.__tour_filename.get())
parser.write_bidding_tables()
parser.write_bidding_scripts()
parser.write_bidding_links()
# inform of any warnings/errors that might have occuerd
if self.__gui_logger.errors():
self.queue(tkMessageBox.showerror,
'Błąd!',
('Podczas wykonywania programu wystąpiły błędy ' +
'w liczbie: %d\n' +
'Sprawdź dziennik logów')
% self.__gui_logger.errors())
elif self.__gui_logger.warnings():
self.queue(tkMessageBox.showwarning,
'Uwaga!',
('Podczas wykonywania programu wystąpiły ' +
'ostrzeżenia w liczbie: %d\n' +
'Sprawdź dziennik logów')
% self.__gui_logger.warnings())
else:
self.queue(
tkMessageBox.showinfo, 'Cudownie!', 'Wszystko wporzo.')
except Exception as ex:
# JFRBidding errors are logged
# (and notified of after entire execution),
# other exceptions should halt execution and display error message
log.getLogger('root').error(ex)
self.queue(tkMessageBox.showerror, 'Błąd!', ex)
raise
finally:
self.queue(self.run_btn.__setitem__, 'state', tk.NORMAL)
def tour_select(self):
"""
Allow for tournament file selection.
"On-click" event for tournament select button.
Displays file selection dialog for tournament file and stores user's
choice in Tk variable.
"""
self.__tour_filename.set(tkFileDialog.askopenfilename(
title='Wybierz główny plik wyników turnieju',
filetypes=[('HTML files', '.htm*'), ('all files', '.*')]))
def bws_select(self):
"""
Allow for BWS file selection.
"On-click" event for BWS select button.
Displays file selection dialog for tournament file and stores user's
choice in Tk variable.
"""
self.__bws_filename.set(tkFileDialog.askopenfilename(
title='Wybierz plik z danymi licytacji',
filetypes=[('BWS files', '.bws'), ('all files', '.*')]))
__queue = None
def queue(self, callback, *args, **kwargs):
"""Add message (function call) to GUI interaction queue."""
if self.__queue is None:
self.__queue = Queue.Queue()
self.__queue.put((callback, args, kwargs))
def process_queue(self):
"""Process GUI interaction queue from other threads."""
if self.__queue is None:
self.__queue = Queue.Queue()
try:
callback, args, kwargs = self.__queue.get_nowait()
except Queue.Empty:
self.master.after(100, self.process_queue)
else:
callback(*args, **kwargs)
self.master.after(10, self.process_queue)
def __init__(self, master=None):
"""
Construct the frame.
Initializes window appearence, controls, layout and logging facility.
"""
tk.Frame.__init__(self, master)
# bind Tk variables to input parameter paths
self.__tour_filename = tk.StringVar(master=self)
self.__bws_filename = tk.StringVar(master=self)
# set window title and icon
self.master.title('JBBD - JFR/BWS bidding data')
self.__set_icon(self.__icon_data)
# create controls
self.__create_widgets()
# and align them within a layout
# second column and fourth row should expand
self.__configure_grid_cells([1], [3])
# main frame should fill entire application window
self.pack(expand=1, fill=tk.BOTH)
# finally, set logging up
self.__configure_logging()
# fire up interthread queue
self.after(100, self.process_queue)
def __configure_grid_cells(self, columns, rows):
"""Set expand with window resize for cells of layout grid."""
for column in columns:
self.columnconfigure(column, weight=1)
for row in rows:
self.rowconfigure(row, weight=1)
def __set_icon(self, data):
"""Set app icon from base64-encoded data."""
# pylint: disable=protected-access
img = tk.PhotoImage(data=data, master=self.master)
# protected access is necessary since Tkinter does not expose
# the wm.iconphoto call
self.master.tk.call('wm', 'iconphoto', self.master._w, img)
def __dispatch_run_button_action(self):
"""Dispatch main button action asynchronously."""
run_thread = threading.Thread(target=self.run_bidding_data)
run_thread.start()
def __create_widgets(self):
"""
Create main application window controls and align them on grid.
Grid has 6 columns (so that 1/2, 1/3 or 1-4-1 layouts are configurable.
"""
# label for tournament file selection
tour_label = tk.Label(
self, text='Plik turnieju:')
# text field for tournament file path
tour_entry = tk.Entry(
self, state=tk.DISABLED, textvariable=self.__tour_filename)
# tournament selection button
tour_select_btn = tk.Button(
self, text='Szukaj', command=self.tour_select)
# first row, label aligned to the right, text field expands
tour_label.grid(row=0, column=0, sticky=tk.E)
tour_entry.grid(row=0, column=1, columnspan=4, sticky=tk.E+tk.W)
tour_select_btn.grid(row=0, column=5)
# label for BWS file selection
bws_label = tk.Label(
self, text='BWS:')
# text field for BWS file path
bws_entry = tk.Entry(
self, state=tk.DISABLED, textvariable=self.__bws_filename)
# BWS selection button
bws_select_btn = tk.Button(
self, text='Szukaj', command=self.bws_select)
# second row, label aligned to the right, text field expands
bws_label.grid(row=1, column=0, sticky=tk.E)
bws_entry.grid(row=1, column=1, columnspan=4, sticky=tk.E+tk.W)
bws_select_btn.grid(row=1, column=5)
# main command button
self.run_btn = tk.Button(
self, text='No to sru!', height=3,
command=self.__dispatch_run_button_action)
# application exit button
quit_btn = tk.Button(
self, text='Koniec tego dobrego', command=self.quit)
# third row, leftmost 2/3 of window width, fills entire cell
self.run_btn.grid(
row=2, column=0, columnspan=4, sticky=tk.N+tk.S+tk.E+tk.W)
# third row, rightmost 1/3 of window width
quit_btn.grid(row=2, column=4, columnspan=2)
# vertical scrollbar for log output field
log_scroll_y = tk.Scrollbar(self, orient=tk.VERTICAL)
# horizontal scrollbar for log output field
log_scroll_x = tk.Scrollbar(self, orient=tk.HORIZONTAL)
# log field, bound (both ways) to scrollbars
self.log_field = tk.Text(
self, height=5, width=80, wrap=tk.NONE,
xscrollcommand=log_scroll_x.set,
yscrollcommand=log_scroll_y.set)
log_scroll_x['command'] = self.log_field.xview
log_scroll_y['command'] = self.log_field.yview
# fourth row, entries window width, expands with window
self.log_field.grid(
row=3, column=0, columnspan=6, sticky=tk.N+tk.S+tk.E+tk.W)
# scrollbars to the right and to the bottom of the field
log_scroll_y.grid(row=3, column=6, sticky=tk.N+tk.S)
log_scroll_x.grid(row=4, column=0, columnspan=6, sticky=tk.E+tk.W)
def __configure_logging(self):
"""Set up logging facility, bound to log output field."""
class GUILogHandler(log.Handler):
"""Log handler which allows output to Tk Text widget."""
def __init__(self, text):
"""Construct the handler, provided TExt widget to bind to."""
log.Handler.__init__(self)
self.text = text
def emit(self, record):
"""Output the message."""
msg = self.format(record)
# Append message to the Text widget, at the end."""
self.text.master.queue(self.text.insert, tk.END, msg + '\n')
# scroll to the bottom, afterwards
self.text.master.queue(self.text.yview, tk.END)
def handle(self, record):
"""Handle log message record (count errors/warnings)."""
log.Handler.handle(self, record)
if record.levelname == 'WARNING':
self.__warning_count += 1
if record.levelname == 'ERROR':
self.__error_count += 1
# message stats, for summary purposes
__warning_count = 0
__error_count = 0
def warnings(self):
"""Return number of accumulated warnings."""
return self.__warning_count
def errors(self):
"""Return number of accumulated errors."""
return self.__error_count
def reset_counts(self):
"""Reset stats and log output."""
self.__warning_count = 0
self.__error_count = 0
self.text.master.queue(self.text.delete, 1.0, tk.END)
# disable default logging limits/thresholds
log.basicConfig(
level=log.NOTSET,
streamhandler=log.NullHandler)
# set up GUI logging
self.__gui_logger = GUILogHandler(self.log_field)
self.__gui_logger.setLevel(log.INFO)
self.__gui_logger.setFormatter(log.Formatter(
'%(levelname)-8s %(name)-8s %(message)s'))
# register GUI handler
log.getLogger().addHandler(self.__gui_logger)
# remove default (console) handler
log.getLogger().removeHandler(log.getLogger().handlers[0])
# embedded image data for app icon
__icon_data = """R0lGODlhIAAgAOeRAAAAAAQEBAUFBQYGBggICAAAcQAAcgAAcwAAdAAAdQA
AdgAAdwAAeAAAeQEBegsLEQQEewUFew4ODgYGfA8PDwgIfQoKfgoKfxMTEwsLfxQUIQ0NgBERghISghQ
UgxQUhBUVhRYWhhkZhhkZhxoajxsbhx4eiSgoKCIijCQkjC0tRzAwMCoqkSsrkSwskCwskjExkzU1lzg
4ljg4mUFBQTs7mjw8mUJChEhISEVFn0hIolBQVUpKo01NoVBQgU9Pok9PplVVqlhYp1pakllZp1lZq1t
bklpaqF1dlFxcqV5eq19fqmBgsGFhsGJirGJisWNjsWVlrWVlrmZmrmhotG5ubmlpvG1tsHFxuXR0tXR
0u3V1u3V1vHV1vXZ2vHd3vXh4vXt7xnt7x3x8x3x8yH19wH5+u4GBvIKCyoaGv4eHwJSUxpWVx5ubzp6
e0p+fzKWl26io0aur0q+v1Lq62ry83L+/3cDA3sHB3sbG4MbG4cnJycjI4svL49TU6NfX6tjY69nZ69v
b7ODg7ubm8urq9O3t9e7u9u/v9vPz+Pf3+/n5/Pr6/Pz8/Pz8/f39/v7+/v/////////////////////
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
//////////////////////yH5BAEKAP8ALAAAAAAgACAAQAj+AP9JAECwYMEAGP4pXMiwIUOCaMo0qeE
FCpMcQZAAOOGwY0OCkUJGcsQmCyGRKEMSWYCgT8qQBlH6gVBhykuRSTzchFlQDJgXMbQILaLFCEGPSP+
B3MmU6Z6YfBJAmBOFQYOrCXokQplTZKAINiDxXNq0bEqDKrqQWcv2ioajSTsStPLhyYwvLKxaMAogrlw
AYoQKFhzmgcHDiAsqBWC2sciYd+pESnG1wYFBN7uuQZDmbMFIetREklGZwRKRbwogOLDAMoLXCFoLiRT
TcWOCBBLr3o2YwEDeff06JMhFy5cvgrdo8QFX+EKCY3SAmGGcRQYsQ5o7X0xiRIgLVBS1MLiQ4Ebw7Qo
JBobCQ8uTFiiAaASwAv1iOG3y69fvZgeO/wAGGCANAgAwAFm2NVWbbYQAUshOMdmBSCRCXGUAHjslIYI
ZBsjxElQuRHJGa1cp8MdLXUVCxwJKoAQVAnkcQmIDDMgQ0iOMMALJETrluIghG5SgCG2f1SFIJDBcxcA
PKMWxAQccTOBAB1BWyYEURCLYmBMmMLVggl4WVEUkjZRp5plopmlmJFUcRQFwcB5GwT8BAQA7"""
def main():
"""Entry point for application - spawn main window."""
app = BiddingGUI()
app.mainloop()
if __name__ == '__main__':
main()
|