Example #1
0
class TransferPopup(tk.Frame):
    def __init__(self, master, acc, data_source: Callable,
                 ontransfer: Callable):
        super(TransferPopup, self).__init__(master)
        self.data_source = data_source
        self.acc = acc
        self.chosen_account_id = None
        self.ontransfer = ontransfer

        self.acc_select = None
        self.transfer_info = None

        self.__create_widgets()

    def update(self):
        if self.acc_select:
            self.acc_select.update()
        if self.transfer_info:
            self.transfer_info.update()

    def __on_transfer(self, amount: Decimal):
        self.ontransfer(self.chosen_account_id, amount)

    def __on_select(self, ids):
        if self.transfer_info:
            self.transfer_info.destroy()
            self.transfer_info = None
        self.chosen_account_id = ids[0] if ids else None
        if self.chosen_account_id:
            self.transfer_info = TransferOverview(self, self.acc,
                                                  self.__on_transfer)
            self.transfer_info.pack()

    def __create_widgets(self):
        wrapper = tk.Frame(self)

        self.acc_select = Multiselect(wrapper,
                                      self.data_source,
                                      singleselect=True,
                                      onselect=self.__on_select)
        self.acc_select.pack()

        wrapper.pack(side=tk.LEFT)
Example #2
0
class CategoriesPopup(tk.Frame):
    def __init__(self, master, data_manager: DataManager, onupdate: Callable[[], []] = None,
                 onadd: Callable[[], []] = None, ondelete: Callable[[], []] = None):
        super(CategoriesPopup, self).__init__(master)
        self.data_manager = data_manager
        self.onupdate = onupdate
        self.onadd = onadd
        self.ondelete = ondelete

        self.cat_select = None
        self.add_cat_popup = None
        self.cat_overview = None

        self.__create_widgets()

    def update(self):
        self.cat_select.update()
        if self.cat_overview:
            self.cat_overview.update()

    def __data_source(self):
        return {_id: cat.name for _id, cat in self.data_manager.categories.items()}

    def __reset_add_cat_popup(self, top):
        top.destroy()
        self.add_cat_popup = None

    def __open_add_cat_popup(self):
        if not self.add_cat_popup:
            top = tk.Toplevel(self)
            top.title("Add category")
            top.resizable(False, False)
            top.geometry("300x100")
            top.protocol('WM_DELETE_WINDOW', lambda *args: self.__reset_add_cat_popup(top))
            self.add_cat_popup = AddCategoryPopup(top, self.data_manager, self.onadd)
            self.add_cat_popup.pack(side=tk.LEFT, anchor="nw", padx=5, pady=5)

    def __remove_cat(self, cat_id):
        self.data_manager.remove_category(cat_id)
        if self.ondelete:
            self.ondelete()
        if self.cat_overview:
            self.cat_overview.destroy()
            self.cat_overview = None

    def __open_overview(self, ids):
        if self.cat_overview:
            self.cat_overview.destroy()
            self.cat_overview = None
        cat_id = ids[0] if ids else None
        if cat_id:
            self.cat_overview = CategoryOverview(self, self.data_manager.categories[cat_id], onupdate=self.onupdate,
                                                 ondelete=lambda: self.__remove_cat(cat_id))
            self.cat_overview.pack()

    def __create_widgets(self):
        wrapper = tk.Frame(self)

        self.cat_select = Multiselect(wrapper, self.__data_source, singleselect=True, onselect=self.__open_overview)
        self.cat_select.pack()

        btn = tk.Button(wrapper, text="Add category", command=self.__open_add_cat_popup)
        btn.pack(fill=tk.X)

        wrapper.pack(side=tk.LEFT)
Example #3
0
class Dashboard(tk.Frame):
    def __init__(self, master, data_manager: DataManager,
                 exchange_rates_store: ExchangeRatesStore):
        super(Dashboard, self).__init__(master)
        self.data_manager = data_manager
        self.exchange_rates_store = exchange_rates_store

        self.acc_list = None
        self.cat_list = None
        self.add_trans_popup = None
        self.trans_table = None
        self.accounts_popup = None
        self.categories_popup = None
        self.overview = None

        self.chosen_accounts = set()
        self.chosen_categories = set()

        self.__create_widgets()

    def __update_acc_list(self):
        self.acc_list.update()
        if self.add_trans_popup:
            self.add_trans_popup.update()
        if self.accounts_popup:
            self.accounts_popup.update()

    def __update_cat_list(self):
        self.cat_list.update()
        if self.add_trans_popup:
            self.add_trans_popup.update()
        if self.categories_popup:
            self.categories_popup.update()

    def __on_context_change(self):
        if self.trans_table:
            self.trans_table.update()
        if self.overview:
            self.overview.update()

    def __on_acc_select(self, ids: tuple[int]):
        self.chosen_accounts = set(ids)
        self.__on_context_change()

    def __on_cat_select(self, ids: tuple[int]):
        self.chosen_categories = set(ids)
        self.__on_context_change()

    def __reset_accounts_popup(self, top):
        top.destroy()
        self.accounts_popup = None

    def __on_acc_delete(self):
        self.__update_acc_list()
        self.__on_context_change()

    def __open_accounts_popup(self):
        if not self.accounts_popup:
            top = tk.Toplevel(self)
            top.title("Accounts")
            top.geometry("500x200")
            top.resizable(False, False)
            top.protocol('WM_DELETE_WINDOW',
                         lambda *args: self.__reset_accounts_popup(top))
            self.accounts_popup = AccountsPopup(top,
                                                self.data_manager,
                                                self.exchange_rates_store,
                                                onadd=self.__update_acc_list,
                                                onupdate=self.__on_acc_delete,
                                                ondelete=self.__on_acc_delete)
            self.accounts_popup.pack(side=tk.LEFT, anchor="nw", padx=5, pady=5)

    def __reset_categories_popup(self, top):
        top.destroy()
        self.categories_popup = None

    def __on_cat_delete(self):
        self.__update_cat_list()
        self.__on_context_change()

    def __open_categories_popup(self):
        if not self.categories_popup:
            top = tk.Toplevel(self)
            top.title("Categories")
            top.geometry("500x200")
            top.resizable(False, False)
            top.protocol('WM_DELETE_WINDOW',
                         lambda *args: self.__reset_categories_popup(top))
            self.categories_popup = CategoriesPopup(
                top,
                self.data_manager,
                onadd=self.__update_cat_list,
                onupdate=self.__on_cat_delete,
                ondelete=self.__on_cat_delete)
            self.categories_popup.pack(side=tk.LEFT,
                                       anchor="nw",
                                       padx=5,
                                       pady=5)

    def __reset_add_trans_popup(self, top):
        top.destroy()
        self.add_trans_popup = None

    def __on_trans_add_callback(self):
        self.__on_context_change()
        if self.accounts_popup:
            self.accounts_popup.update()

    def __open_trans_add_popup(self):
        if not self.add_trans_popup:
            top = tk.Toplevel(self)
            top.title("Add transaction")
            top.geometry("300x260")
            top.resizable(False, False)
            top.protocol('WM_DELETE_WINDOW',
                         lambda *args: self.__reset_add_trans_popup(top))
            self.add_trans_popup = AddTransactionPopup(
                top, self.data_manager, self.exchange_rates_store,
                self.__on_trans_add_callback)
            self.add_trans_popup.pack()

    def __trans_data_source(self):
        min_d = datetime.fromisoformat(self.min_date_entry.get())
        max_d = datetime.fromisoformat(self.max_date_entry.get())
        return {
            t.id: t
            for acc in self.data_manager.accounts.values()
            for t in acc.transactions if acc.id in self.chosen_accounts and (
                not t.category or t.category.id in self.chosen_categories)
            and min_d <= t.date <= max_d
        }

    def __create_lists(self, frame):
        def acc_data_source():
            return {
                _id: acc.name
                for _id, acc in self.data_manager.accounts.items()
            }

        def cat_data_source():
            return {
                _id: cat.name
                for _id, cat in self.data_manager.categories.items()
            }

        wrapper = tk.Frame(frame)

        acc_wrapper = tk.Frame(wrapper)
        label = tk.Label(acc_wrapper,
                         text="Accounts",
                         width=Account.NAME_MAX + 2,
                         anchor="w",
                         relief=tk.GROOVE)
        label.pack()
        self.acc_list = Multiselect(acc_wrapper,
                                    acc_data_source,
                                    self.__on_acc_select,
                                    width=Account.NAME_MAX)
        self.acc_list.pack()
        acc_wrapper.pack(side=tk.LEFT)

        cat_wrapper = tk.Frame(wrapper)
        label = tk.Label(cat_wrapper,
                         text="Categories",
                         width=Category.NAME_MAX + 2,
                         anchor="w",
                         relief=tk.GROOVE)
        label.pack()
        self.cat_list = Multiselect(cat_wrapper,
                                    cat_data_source,
                                    self.__on_cat_select,
                                    width=Category.NAME_MAX)
        self.cat_list.pack()
        cat_wrapper.pack(side=tk.LEFT)

        wrapper.pack()

    def __create_navbar(self, frame):
        pack_config = {"side": tk.LEFT, "padx": 2, "anchor": "n"}
        date_entry_config = {
            "width": 12,
            "selectmode": "day",
            "locale": "en_US",
            "date_pattern": "yyyy-mm-dd",
            "showweeknumbers": False
        }

        wrapper = tk.Frame(frame)

        lbl = tk.Label(wrapper, text="DATE MIN: ", relief=tk.GROOVE)
        lbl.pack(**pack_config)

        self.min_date_entry = DateEntry(wrapper,
                                        **date_entry_config,
                                        year=2020,
                                        month=1,
                                        day=1)
        self.min_date_entry.bind("<<DateEntrySelected>>",
                                 lambda *args: self.__on_context_change())
        self.min_date_entry.pack(**pack_config)

        lbl = tk.Label(wrapper, text="DATE MAX: ", relief=tk.GROOVE)
        lbl.pack(**pack_config)

        self.max_date_entry = DateEntry(wrapper, **date_entry_config)
        self.max_date_entry.bind("<<DateEntrySelected>>",
                                 lambda *args: self.__on_context_change())
        self.max_date_entry.pack(**pack_config)

        btn = tk.Button(wrapper,
                        text="Accounts",
                        command=self.__open_accounts_popup)
        btn.pack(**pack_config)

        btn = tk.Button(wrapper,
                        text="Categories",
                        command=self.__open_categories_popup)
        btn.pack(**pack_config)

        btn = tk.Button(wrapper,
                        text="Add transaction",
                        command=self.__open_trans_add_popup)
        btn.pack(**pack_config)

        wrapper.pack(fill=tk.Y)

    def __create_widgets(self):
        frame = tk.Frame()

        wrapper1 = tk.Frame(frame)
        self.__create_lists(wrapper1)
        wrapper1.pack(side=tk.LEFT, anchor="nw")

        wrapper2 = tk.Frame(frame)
        self.__create_navbar(wrapper2)
        self.overview = Overview(wrapper2, self.exchange_rates_store,
                                 self.__trans_data_source)
        self.overview.pack(side=tk.TOP)
        wrapper2.pack(side=tk.LEFT)

        frame.pack(fill=tk.X, padx=10, pady=10)

        wrapper3 = tk.Frame()
        labelframe = tk.LabelFrame(wrapper3, text="Transactions", font=10)
        self.trans_table = TransactionTable(
            labelframe, self.__trans_data_source,
            lambda *args: self.__on_context_change())
        self.trans_table.pack(fill=tk.BOTH, expand=1)
        labelframe.pack(fill=tk.BOTH, expand="yes")
        wrapper3.pack(fill=tk.BOTH, expand="yes", padx=10, pady=10)
Example #4
0
class AccountsPopup(tk.Frame):
    def __init__(self,
                 master,
                 data_manager: DataManager,
                 exchange_rates_store,
                 onupdate: Callable[[], []] = None,
                 onadd: Callable[[], []] = None,
                 ondelete: Callable[[], []] = None):
        super(AccountsPopup, self).__init__(master)
        self.data_manager = data_manager
        self.exchange_rates_store = exchange_rates_store
        self.onupdate = onupdate
        self.onadd = onadd
        self.ondelete = ondelete

        self.acc_select = None
        self.add_acc_popup = None
        self.acc_overview = None

        self.__create_widgets()

    def update(self):
        self.acc_select.update()
        if self.acc_overview:
            self.acc_overview.update()

    def __data_source(self):
        return {
            _id: acc.name
            for _id, acc in self.data_manager.accounts.items()
        }

    def __transfer_data_source(self, acc_id):
        return {
            _id: acc.name
            for _id, acc in self.data_manager.accounts.items() if _id != acc_id
        }

    def __reset_add_acc_popup(self, top):
        top.destroy()
        self.add_acc_popup = None

    def __open_add_acc_popup(self):
        if not self.add_acc_popup:
            top = tk.Toplevel(self)
            top.title("Add account")
            top.geometry("300x120")
            top.resizable(False, False)
            top.protocol('WM_DELETE_WINDOW',
                         lambda *args: self.__reset_add_acc_popup(top))
            self.add_acc_popup = AddAccountPopup(top, self.data_manager,
                                                 self.onadd)
            self.add_acc_popup.pack(side=tk.LEFT, anchor="nw", padx=5, pady=5)

    def __remove_acc(self, acc_id):
        self.data_manager.remove_account(acc_id)
        if self.ondelete:
            self.ondelete()
        if self.acc_overview:
            self.acc_overview.destroy()
            self.acc_overview = None

    def __on_transfer(self, _from, to_id, amount):
        _to = self.data_manager.accounts[to_id]
        rate = self.exchange_rates_store.get(_from.currency, _to.currency)
        _from.transfer(_to, amount, rate)
        if self.onupdate:
            self.onupdate()

    def __open_overview(self, ids):
        if self.acc_overview:
            self.acc_overview.destroy()
            self.acc_overview = None
        acc_id = ids[0] if ids else None
        if acc_id:
            acc = self.data_manager.accounts[acc_id]
            self.acc_overview = AccountOverview(
                self,
                acc,
                onupdate=self.onupdate,
                ondelete=lambda: self.__remove_acc(acc_id),
                transfer_data_source=self.__transfer_data_source,
                ontransfer=self.__on_transfer)
            self.acc_overview.pack()

    def __create_widgets(self):
        wrapper = tk.Frame(self)

        self.acc_select = Multiselect(wrapper,
                                      self.__data_source,
                                      singleselect=True,
                                      onselect=self.__open_overview)
        self.acc_select.pack()

        btn = tk.Button(wrapper,
                        text="Add account",
                        command=self.__open_add_acc_popup)
        btn.pack(fill=tk.X)

        wrapper.pack(side=tk.LEFT)