usawa

Signed, immutable accounting.
Log | Files | Refs | Submodules | LICENSE

main_window.py (5211B)


      1 import logging
      2 from usawa.crypto import DemoWallet
      3 from usawa.gui.components.passphrase_dialog import (
      4     PASSPHRASE_DIALOG_CSS,
      5     PassphraseDialog,
      6 )
      7 from .core.entry_service import EntryService
      8 from usawa.storage.ledger_repository import LedgerRepository
      9 from gi.repository import Adw, Gtk, Gio, GLib, Gdk
     10 from usawa.gui.controllers.entry_controller import EntryController
     11 from usawa.gui.views.entry_list_view import EntryListView
     12 from datetime import datetime
     13 import usawa.error
     14 
     15 logg = logging.getLogger("gui.mainwindow")
     16 
     17 
     18 class UsawaMainWindow(Adw.ApplicationWindow):
     19 
     20     def __init__(self, application, ctx, **kwargs):
     21         super().__init__(application=application, **kwargs)
     22 
     23         self.set_title("Usawa")
     24         self.set_default_size(1000, 600)
     25 
     26         toolbar_view = Adw.ToolbarView()
     27         self.set_content(toolbar_view)
     28 
     29         header = Adw.HeaderBar()
     30 
     31         menu_button = self._create_menu_button()
     32         header.pack_end(menu_button)
     33 
     34         toolbar_view.add_top_bar(header)
     35 
     36         self.toast_overlay = Adw.ToastOverlay()
     37         toolbar_view.set_content(self.toast_overlay)
     38 
     39         self.ctx = ctx
     40 
     41         self.nav_view = Adw.NavigationView()
     42         self.toast_overlay.set_child(self.nav_view)
     43 
     44         self._setup_actions()
     45         GLib.idle_add(self._check_wallet_status)
     46 
     47     def _create_menu_button(self):
     48         menu = Gio.Menu()
     49         menu.append("Export Ledger", "app.export-ledger")
     50 
     51         menu_button = Gtk.MenuButton()
     52         menu_button.set_icon_name("open-menu-symbolic")
     53         menu_button.set_menu_model(menu)
     54         menu_button.set_tooltip_text("Main menu")
     55 
     56         return menu_button
     57 
     58     def _setup_actions(self):
     59         export_action = Gio.SimpleAction.new("export-ledger", None)
     60         export_action.connect("activate", lambda a, p: self._on_export_clicked())
     61         self.get_application().add_action(export_action)
     62 
     63     def _on_export_clicked(self):
     64         file_dialog = Gtk.FileDialog()
     65         file_dialog.set_title("Export Ledger to XML")
     66         default_name = f"ledger_export_{datetime.now().strftime('%Y%m%d_%H%M%S')}.xml"
     67         file_dialog.set_initial_name(default_name)
     68 
     69         filters = Gio.ListStore.new(Gtk.FileFilter)
     70 
     71         xml_filter = Gtk.FileFilter()
     72         xml_filter.set_name("XML Files")
     73         xml_filter.add_pattern("*.xml")
     74         filters.append(xml_filter)
     75 
     76         file_dialog.set_filters(filters)
     77         file_dialog.set_default_filter(xml_filter)
     78 
     79         file_dialog.save(parent=self, callback=self._on_export_file_selected)
     80 
     81     def _on_export_file_selected(self, dialog, result):
     82         try:
     83             file = dialog.save_finish(result)
     84             if file:
     85                 file_path = file.get_path()
     86                 success, error_msg = self.entry_controller.export_ledger(file_path)
     87 
     88                 if success:
     89                     self._show_success_toast(f"Exported to {file_path}")
     90                 else:
     91                     self._show_error_dialog("Export Failed", error_msg)
     92         except Exception as e:
     93             logg.debug(f"Export cancelled: {e}")
     94 
     95     def _show_success_toast(self, message):
     96         toast = Adw.Toast.new(message)
     97         toast.set_timeout(3)
     98         self.toast_overlay.add_toast(toast)
     99 
    100     def _create_entry_list_page(self):
    101         page = Adw.NavigationPage(title="Ledger Entries", tag="entry-list")
    102         entries = []
    103         self.entry_list_view = EntryListView(
    104             ctx=self.ctx,
    105             nav_view=self.nav_view,
    106             entry_controller=self.entry_controller,
    107             entries=entries,
    108             refresh_callback=self.refresh_entries,
    109             toast_overlay=self.toast_overlay,
    110         )
    111         self.entry_list_view._load_entries()
    112         page.set_child(self.entry_list_view)
    113 
    114         return page
    115 
    116     def refresh_entries(self):
    117         logg.info("MainWindow refreshing entries")
    118         self.entry_list_view._load_entries()
    119 
    120     def _check_wallet_status(self):
    121         try:
    122             self.ctx.load_wallet(replace=True, signing=True)
    123             self._init_with_wallet()
    124         except usawa.error.VerifyError:
    125             dialog = PassphraseDialog(
    126                 # store=self.ctx.keystore,
    127                 ctx=self.ctx,
    128                 wallet_class=DemoWallet,
    129                 on_success=self._init_with_wallet,
    130                 on_cancel=self._on_wallet_cancelled,
    131             )
    132             dialog.present(self)
    133 
    134     def _init_with_wallet(self):
    135         repository = LedgerRepository(self.ctx)
    136         entry_service = EntryService(repository=repository)
    137         self.entry_controller = EntryController(
    138             ctx=self.ctx, entry_service=entry_service
    139         )
    140         self.entry_controller.add_entry_created_listener(self.refresh_entries)
    141 
    142         entry_list_page = self._create_entry_list_page()
    143         self.nav_view.add(entry_list_page)
    144 
    145     def _on_wallet_cancelled(self):
    146         self.get_application().quit()
    147 
    148     def _load_css(self):
    149         css_provider = Gtk.CssProvider()
    150         css_provider.load_from_string(PASSPHRASE_DIALOG_CSS)
    151         Gtk.StyleContext.add_provider_for_display(
    152             Gdk.Display.get_default(),
    153             css_provider,
    154             Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION,
    155         )