summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/bidding_data_gui.py242
1 files changed, 172 insertions, 70 deletions
diff --git a/src/bidding_data_gui.py b/src/bidding_data_gui.py
index 606a3a9..ebc789c 100644
--- a/src/bidding_data_gui.py
+++ b/src/bidding_data_gui.py
@@ -1,91 +1,48 @@
# 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 logging as log
-
import os
class BiddingGUI(tk.Frame):
- __tour_filename = None
- __bws_filename = None
-
- def __init__(self, master=None):
- tk.Frame.__init__(self, master)
- self.__tour_filename = tk.StringVar(master=self)
- self.__bws_filename = tk.StringVar(master=self)
- self.master.title('JBBD - JFR/BWS bidding data')
- self.set_icon(self.master)
- self.create_widgets()
- self.configure_grid_cells([1], [3])
- self.pack(expand=1, fill=tk.BOTH)
-
- def configure_grid_cells(self, columns, rows):
- for column in columns:
- self.columnconfigure(column, weight=1)
- for row in rows:
- self.rowconfigure(row, weight=1)
-
- def set_icon(self, master):
- # pylint: disable=protected-access
- img = tk.PhotoImage(data=self.__icon_data, master=master)
- master.tk.call('wm', 'iconphoto', master._w, img)
-
- def tour_select(self):
- self.__tour_filename.set(tkFileDialog.askopenfilename(
- title='Wybierz główny plik wyników turnieju',
- filetypes=[('HTML files', '.htm*'), ('all files', '.*')]))
-
- def bws_select(self):
- self.__bws_filename.set(tkFileDialog.askopenfilename(
- title='Wybierz plik z danymi licytacji',
- filetypes=[('BWS files', '.bws'), ('all files', '.*')]))
-
- class GUILogHandler(log.Handler):
- def __init__(self, text):
- log.Handler.__init__(self)
- self.text = text
-
- def emit(self, record):
- msg = self.format(record)
-
- def append():
- self.text.insert(tk.END, msg + '\n')
- self.text.yview(tk.END)
- self.text.after(0, append)
+ """GUI frame class."""
- def handle(self, record):
- log.Handler.handle(self, record)
- if record.levelname == 'WARNING':
- self.__warning_count += 1
- if record.levelname == 'ERROR':
- self.__error_count += 1
-
- __warning_count = 0
- __error_count = 0
-
- def warnings(self):
- return self.__warning_count
-
- def errors(self):
- return self.__error_count
-
- def reset_counts(self):
- self.__warning_count = 0
- self.__error_count = 0
+ # 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.
+ """
try:
- self.log_field.delete(1.0, tk.END)
+ # 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')
- self.__gui_logger.reset_counts()
+
+ # do the magic
from bidding_data import JFRBidding
parser = JFRBidding(
bws_file=self.__bws_filename.get(),
@@ -93,6 +50,8 @@ class BiddingGUI(tk.Frame):
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():
tkMessageBox.showerror(
'Błąd!',
@@ -106,62 +65,204 @@ class BiddingGUI(tk.Frame):
'w liczbie: %d\n' +
'Sprawdź dziennik logów') % self.__gui_logger.warnings())
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)
tkMessageBox.showerror('Błąd!', ex)
raise
- def create_widgets(self):
+ 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', '.*')]))
+
+ 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()
+
+ 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 __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
run_btn = tk.Button(
self, text='No to sru!', height=3, command=self.run_bidding_data)
+ # 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
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)
+
+ def append():
+ """Append message to the Text widget, at the end."""
+ self.text.insert(tk.END, msg + '\n')
+ # scroll to the bottom, afterwards
+ self.text.yview(tk.END)
+
+ # call log output asynchronously
+ self.text.after(0, append)
+
+ 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.delete(1.0, tk.END)
+
+ # disable default logging limits/thresholds
log.basicConfig(
level=log.NOTSET,
streamhandler=log.NullHandler)
- self.__gui_logger = self.GUILogHandler(self.log_field)
+ # 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
@@ -186,6 +287,7 @@ PKMWxAQccTOBAB1BWyYEURCLYmBMmMLVggl4WVEUkjZRp5plopmlmJFUcRQFwcB5GwT8BAQA7"""
def main():
+ """Entry point for application - spawn main window."""
app = BiddingGUI()
app.mainloop()