示例#1
0
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
示例#2
0
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
示例#3
0
    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)
示例#4
0
    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)
示例#5
0
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()