# 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 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.run_btn['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(): 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(): tkMessageBox.showwarning( 'Błąd!', ('Podczas wykonywania programu wystąpiły ostrzeżenia ' + '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 finally: self.run_btn['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', '.*')])) 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 __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) 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) # 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()