def resize_panels(window, rules): """ Resize GUI elements when the window is resized. """ min_w, min_h = (mod_const.WIN_WIDTH, mod_const.WIN_HEIGHT) width, height = window.size toolbar_h = 60 # toolbar height plus toolbar border # Update toolbar and pane elements panel_w = width if width >= min_w else min_w panel_h = height - toolbar_h if height >= min_h else min_h - toolbar_h logger.debug('setting window size to {W} x {H}'.format(W=width, H=height)) logger.debug('panels will have size to {W} x {H}'.format(W=panel_w, H=panel_h)) window['-CANVAS_WIDTH-'].set_size((panel_w, None)) # Reset size of the home panel window['-HOME_HEIGHT-'].set_size((None, panel_h)) window['-HOME_WIDTH-'].set_size((panel_w, None)) # Update audit rule elements for rule in rules: try: rule.resize_elements(window, (panel_w, panel_h)) except Exception as e: msg = 'failed to resize window - {ERR}'.format(ERR=e) logger.exception(msg) continue
def panel_layout(account_methods, win_size: tuple = None): """ Get the GUI layouts for the configuration-dependant panels. """ if win_size: width, height = win_size else: width, height = (mod_const.WIN_WIDTH, mod_const.WIN_HEIGHT) toolbar_h = 60 # toolbar height plus toolbar border panel_w = width panel_h = height - toolbar_h # Home page action panel panels = [mod_lo.home_screen(size=(panel_w, panel_h))] # Add Audit rule with summary panel for account_method in account_methods: for rule in account_method.rules: msg = 'creating layout for workflow method {ACCT}, rule {RULE}'\ .format(ACCT=account_method.name, RULE=rule.name) logger.debug(msg) panels.append(rule.layout(size=(panel_w, panel_h))) # Layout pane = [ sg.Pane(panels, orientation='horizontal', show_handle=False, border_width=0) ] return pane
def disable(self, window): """ Disable toolbar buttons. Arguments: window (Window): GUI window. """ logger.info('Toolbar: disabling toolbar menus') # Database administration window['-DBMENU-'].update(disabled=True) # User administration self.toggle_menu(window, 'umenu', 'manage accounts', disabled=True) self.toggle_menu(window, 'umenu', 'sign in', disabled=False) self.toggle_menu(window, 'umenu', 'sign out', disabled=True) # Disable settings modification self.toggle_menu(window, 'mmenu', 'settings', disabled=True) # Disable record menus for menu in self.record_items: logger.debug('Toolbar: disabling record menu item {}'.format(menu)) self.toggle_menu(window, 'rmenu', menu, disabled=True) # Disable accounting method menus for menu in self.account_items: logger.debug( 'Toolbar: disabling accounting method menu item {}'.format( menu)) self.toggle_menu(window, 'amenu', menu, disabled=True)
def enable(self, window): """ Enable toolbar buttons. """ admin = user.admin record_menus = self.record_items account_menus = self.account_items logger.info('Toolbar: enabling toolbar menus') # User administration # Enable admin-only privileges if admin is True: # Database administration window['-DBMENU-'].update(disabled=False) # User administration self.toggle_menu(window, 'umenu', 'manage accounts', disabled=False) self.toggle_menu(window, 'umenu', 'sign in', disabled=True) self.toggle_menu(window, 'umenu', 'sign out', disabled=False) # Allow user to modify user-settings self.toggle_menu(window, 'mmenu', 'settings', disabled=False) # Disable record menus for menu in record_menus: user_access = record_menus[menu] if admin is True or user_access in user.access_permissions(): logger.debug( 'Toolbar: enabling record menu item {}'.format(menu)) self.toggle_menu(window, 'rmenu', menu, disabled=False) # Disable accounting method menus for menu in account_menus: user_access = account_menus[menu] if admin is True or user_access in user.access_permissions(): logger.debug( 'Toolbar: enabling accounting method menu item {}'.format( menu)) self.toggle_menu(window, 'amenu', menu, disabled=False)
def main(): """ Main function. """ # Theme default_col = mod_const.DEFAULT_COL action_col = mod_const.ACTION_COL text_col = mod_const.TEXT_COL font = mod_const.MAIN_FONT sg.set_options(element_padding=(0, 0), margins=(0, 0), auto_size_buttons=True, auto_size_text=True, background_color=default_col, element_text_color=text_col, element_background_color=default_col, font=font, input_text_color=text_col, text_color=text_col, text_element_background_color=default_col, input_elements_background_color=action_col, button_color=(text_col, default_col), tooltip_font=(mod_const.TOOLTIP_FONT)) # Original window size logger.debug('determining screen size') root = tk.Tk() screen_w = root.winfo_screenwidth() screen_h = root.winfo_screenheight() root.destroy() del root min_w = int(screen_w * 0.8) min_h = int(screen_h * 0.8) if screen_w >= mod_const.WIN_WIDTH: current_w = mod_const.WIN_WIDTH else: current_w = screen_w if screen_h >= mod_const.WIN_HEIGHT: current_h = mod_const.WIN_HEIGHT else: current_h = screen_h # Load the program configuration record_rules = ConfigurationManager(settings.record_rules) settings.records = record_rules audit_rules = ConfigurationManager(settings.audit_rules) cash_rules = ConfigurationManager(settings.cash_rules) bank_rules = ConfigurationManager(settings.bank_rules) acct_methods = [audit_rules, bank_rules] # Configure GUI layout toolbar = ToolBar([audit_rules, cash_rules, bank_rules], record_rules) layout = [ toolbar.layout(win_size=(current_w, current_h)), panel_layout(acct_methods, win_size=(current_w, current_h)) ] # Element keys and names audit_names = audit_rules.print_rules() cash_names = cash_rules.print_rules() bank_names = bank_rules.print_rules() # Create the menu mapper menu_mapper = {} for rule in [ i for acct_method in (audit_rules, cash_rules, bank_rules) for i in acct_method.rules ]: try: rule_submenu = rule.menu_flags except AttributeError: menu_mapper[rule.menu_title] = rule.name else: if not rule_submenu: menu_mapper[rule.menu_title] = rule.name else: for menu_title in rule_submenu: menu_mapper[menu_title] = rule.name # Event metadata current_rule = None debug_win = None # Initialize main window and login window window = sg.Window('REM Tila (v{VER})'.format(VER=__version__), layout, icon=settings.icon, font=mod_const.MAIN_FONT, size=(current_w, current_h), resizable=True, margins=(0, 0), return_keyboard_events=True) window.finalize() window.maximize() window.set_min_size((min_w, min_h)) screen_w, screen_h = window.get_screen_dimensions() logger.debug('screen size is {W} x {H}'.format(W=screen_w, H=screen_h)) user_image = tk.PhotoImage(data=mod_const.USER_ICON) userin_image = tk.PhotoImage(data=mod_const.USERIN_ICON) acct_rules = [i for acct_method in acct_methods for i in acct_method.rules] resize_panels(window, acct_rules) resized = False # Display the home panel window.refresh() home_panel = current_panel = '-HOME-' window[home_panel].update(visible=True) # Bind keyboard events window = settings.set_shortcuts(window, hk_groups=['Navigation']) for acct_rule in acct_rules: acct_rule.bind_keys(window) # Event Loop logger.info('starting the program') while True: event, values = window.read(timeout=100) # Quit program if event == sg.WIN_CLOSED or values['-MMENU-'] == 'Quit': logger.info('exiting the program') if debug_win: debug_win.close() settings.reload_logger(sys.stdout) break # Resize screen if resized and current_panel != home_panel: if current_rule is not None: if not current_rule.current_panel: resized = False continue try: window[current_rule.current_panel].update(visible=False) except KeyError: logger.error( 'unable to resize program display - current panel {PANEL} is missing from the ' 'display window'.format( PANEL=current_panel.current_rule)) print(current_rule.panel_keys) continue window[current_rule.element_key].update(visible=False) window.refresh() window[current_rule.element_key].update(visible=True) window[current_rule.current_panel].update(visible=True) resized = False continue # Resize screen # Get window dimensions try: win_w, win_h = window.size except AttributeError: continue if win_w != current_w or win_h != current_h: logger.debug('new window size is {W} x {H}'.format(W=win_w, H=win_h)) # Update sizable elements resize_panels(window, acct_rules) current_w, current_h = (win_w, win_h) resized = True continue # User login if values['-UMENU-'] == 'Sign In': # user logs on logger.debug('displaying user login screen') mod_win2.login_window() if user.logged_in is True: # logged on successfully logger.info('user signed in as "{}"'.format(user.uid)) # Switch user icon window['-UMENU-'].Widget.configure(image=userin_image) # Enable permission specific actions and menus toolbar.enable(window) # Update user menu items to include the login name toolbar.update_username(window, user.uid) else: logger.warning( 'failed to login to the program as user {}'.format( user.uid)) continue # User log-off if values['-UMENU-'] == 'Sign Out': # user signs out try: in_progress = current_rule.in_progress except AttributeError: in_progress = False # Confirm sign-out if in_progress: # ask to switch first msg = 'An audit is ongoing. Are you sure you would like to quit without saving?' selection = mod_win2.popup_confirm(msg) if selection == 'OK': # Reset the rule and update the panel current_rule = current_rule.reset_rule(window) current_panel = '-HOME-' else: continue else: # no action being taken so ok to switch without asking msg = 'Are you sure you would like to sign-out?' selection = mod_win2.popup_confirm(msg) if selection == 'Cancel': continue try: current_rule.reset_parameters(window) window[current_rule.element_key].update(visible=False) except AttributeError: pass window['-HOME-'].update(visible=True) current_panel = '-HOME-' # Remove all unsaved record IDs associated with the program instance settings.remove_unsaved_ids() # Reset User attributes logger.info('signing out as user {}'.format(user.uid)) user.logout() # Switch user icon window['-UMENU-'].Widget.configure(image=user_image) # Disable all actions and menus toolbar.disable(window) continue # Display the debug window if not debug_win and values['-MMENU-'] == 'Debug': if settings.log_file: mod_win2.popup_notice( 'The debug window is deactivated when logging to a file. Program logs are ' 'being sent to {}'.format(settings.log_file)) else: debug_win = mod_win2.debug_window() debug_win.finalize() # Reload logger with new log stream settings.reload_logger(debug_win['-OUTPUT-'].TKOut) logger.info('setting log output stream to the debug window') continue elif debug_win: debug_event, debug_value = debug_win.read(timeout=1000) if debug_event in (sg.WIN_CLOSED, '-CANCEL-'): debug_win.close() debug_win = None # Reset logging stream to stdout logger.info('resetting log output stream to system output') settings.reload_logger(sys.stdout) elif debug_event == '-CLEAR-': debug_win['-OUTPUT-'].update('') elif debug_event == '-LEVEL-': # Reload logger with new log level log_level = debug_value['-LEVEL-'] logger.info('resetting logging level to {}'.format(log_level)) settings.reload_logger(debug_win['-OUTPUT-'].TKOut, log_level=log_level) else: debug_win['-OUTPUT-'].expand(expand_x=True, expand_y=True, expand_row=True) # Display the edit settings window if values['-MMENU-'] == 'Settings': mod_win2.edit_settings(win_size=window.size) continue # Display "About" window if values['-MMENU-'] == 'About': mod_win2.about() continue # Display the database update window if event == '-DBMENU-': try: mod_win2.database_importer_window( win_size=window.get_screen_size()) except Exception as e: logger.exception( 'importing records to the database failed - {ERR}'.format( ERR=e)) continue # Pull up an existing database record if event == '-RMENU-': # Get Record Type selection record_type = values['-RMENU-'] logger.info( 'displaying selection window for {TYPE} records'.format( TYPE=record_type)) # Get record entry record_entry = settings.records.fetch_rule(record_type, by_title=True) # Display the import record window table_entry = record_entry.import_table table_entry['RecordType'] = record_entry.name import_table = mod_elem.RecordTable(record_entry.name, table_entry) try: mod_win2.record_import_window(import_table, enable_new=False) except Exception as e: msg = 'record importing failed - {ERR}'.format(ERR=e) mod_win2.popup_error(msg) logger.exception(msg) # raise continue # Activate appropriate accounting workflow method panel selected_menu = values['-AMENU-'] if selected_menu in menu_mapper: selected_rule = menu_mapper[selected_menu] if selected_rule in audit_names: # workflow method is a transaction audit # Obtain the selected rule object current_rule = audit_rules.fetch_rule(selected_rule) # Clear the panel current_rule.reset_rule(window, current=True) # Update panel-in-display window[current_panel].update(visible=False) current_panel = current_rule.element_key window[current_panel].update(visible=True) logger.debug( 'panel in view is {NAME}'.format(NAME=current_rule.name)) # Disable the toolbar toolbar.disable(window) continue elif selected_rule in cash_names: # workflow method is cash reconciliation # Obtain the selected rule object current_rule = cash_rules.fetch_rule(selected_rule) # Get the record entry record_type = current_rule.record_type record_entry = settings.records.fetch_rule(record_type) if not record_entry: msg = 'unable to find a configured record type with name {NAME}'.format( NAME=record_type) logger.warning(msg) continue else: logger.debug('the record type selected is {TYPE}'.format( TYPE=record_type)) # Display the import record window table_entry = record_entry.import_table table_entry['RecordType'] = record_type import_table = mod_elem.RecordTable(current_rule.name, table_entry) try: mod_win2.record_import_window(import_table, enable_new=True) except Exception as e: msg = 'record importing failed - {ERR}'.format(ERR=e) mod_win2.popup_error(msg) logger.exception(msg) continue elif selected_rule in bank_names: # workflow method is bank reconciliation # Obtain the selected rule object current_rule = bank_rules.fetch_rule(selected_rule) # Use the menu flag to find the primary account #try: # acct_name = current_rule.menu_flags[selected_menu] #except KeyError: # acct_name = selected_rule # Fetch the primary account #current_acct = current_rule.fetch_account(acct_name) #current_rule._current_account = current_acct.name #current_rule.current_panel = current_acct.key_lookup('Panel') # Clear the panel current_rule.reset_rule(window, current=True) # Update the panel-in-display and the account panel window[current_panel].update(visible=False) current_panel = current_rule.element_key window[current_panel].update(visible=True) #window[current_rule.current_panel].update(visible=True) # Disable toolbar toolbar.disable(window) logger.debug( 'panel in view is {NAME}'.format(NAME=current_rule.name)) continue # Action events if current_rule and event in current_rule.events(): logger.info('running window event {EVENT} of rule {RULE}'.format( EVENT=event, RULE=current_rule.name)) try: current_rule_name = current_rule.run_event( window, event, values) except Exception as e: msg = 'failed to run window event {EVENT} of rule {RULE} - {ERR}'\ .format(EVENT=event, RULE=current_rule.name, ERR=e) mod_win2.popup_error(msg) logger.exception(msg) continue if current_rule_name is None: # Enable toolbar toolbar.enable(window) # Reset current_rule current_rule = None current_panel = '-HOME-' continue window.close()