from textual.app import App, ComposeResult from textual.binding import Binding from textual.widgets import Header, Footer, DataTable, Input from textual.containers import Horizontal, Vertical from textual.coordinate import Coordinate from textual.screen import Screen from .db import DatabaseManager, Category, Activity class ListScreen(Screen): def __init__(self, db_manager): self.db_manager = db_manager super().__init__() def compose(self) -> ComposeResult: """create child widgets for the app.""" yield Header() with Vertical(): yield DataTable() with Horizontal(): yield Input(id="filter") yield Footer() def action_refresh(self) -> None: self._refresh() def action_sort(self) -> None: self.table.cursor_type = "column" def on_data_table_column_selected(self, event): self.sort = (event.column_key,) event.data_table.sort(*self.sort) event.data_table.cursor_type = "row" def action_filter(self) -> None: filter_input = self.query_one("#filter") filter_input.display = True self._refresh(filter_input.value) filter_input.focus() def on_input_submitted(self, event): self.table.focus() def action_cancelfilter(self) -> None: filter_input = self.query_one("#filter") filter_input.display = False filter_input.clear() self.table.focus() self._refresh() def on_input_changed(self, event): self._refresh(event.value) class ActivitiesScreen(ListScreen): BINDINGS = [ ("q", "quit", "Quit"), ("s", "sort", "Sort"), ("r", "refresh", "Refresh"), ("/", "filter", "Search"), ("d", "delete", "Delete activity"), ("f", "move_facts", "Move facts"), Binding(key="escape", action="cancelfilter", show=False), ] def _refresh(self, filter_query=None): self.table.clear() # List activities with the count of facts activities = Activity.list(self.db_manager, filter_query) self.table.add_rows( [ [ activity.category_id, activity.category_name, activity.id, activity.name, activity.facts_count, ] for activity in activities ] ) self.table.sort(*self.sort) def on_mount(self) -> None: self.table = self.query_one(DataTable) self.table.cursor_type = "row" self.columns = self.table.add_columns( "category id", "category", "activity id", "activity", "entries" ) self.sort = (self.columns[1], self.columns[3]) self._refresh() def action_delete(self) -> None: # get the keys for the row and column under the cursor. row_key, _ = self.table.coordinate_to_cell_key(self.table.cursor_coordinate) activity_id = self.table.get_cell_at( Coordinate(self.table.cursor_coordinate.row, 2), ) activity = Activity.get_by_id(self.db_manager, activity_id) activity.delete() # supply the row key to `remove_row` to delete the row. self.table.remove_row(row_key) def action_move_facts(self) -> None: row_idx: int = self.table.cursor_row row_cells = self.table.get_row_at(row_idx) self.move_from_activity = Activity.get_by_id(self.db_manager, row_cells[2]) for col_idx, cell_value in enumerate(row_cells): cell_coordinate = Coordinate(row_idx, col_idx) self.table.update_cell_at( cell_coordinate, f"[red]{cell_value}[/red]", ) self.table.move_cursor(row=self.table.cursor_coordinate[0] + 1) def on_data_table_row_selected(self, event): if getattr(self, "move_from_activity", None) is not None: move_to_activity = Activity.get_by_id( self.db_manager, self.table.get_cell_at(Coordinate(event.cursor_row, 2)) ) self.move_from_activity.move_facts(move_to_activity) filter_input = self.query_one("#filter") self._refresh(filter_input.value) del self.move_from_activity class CategoriesScreen(ListScreen): BINDINGS = [ ("q", "quit", "Quit"), ("s", "sort", "Sort"), ("r", "refresh", "Refresh"), ("/", "filter", "Search"), ("d", "delete", "Delete category"), Binding(key="escape", action="cancelfilter", show=False), ] def _refresh(self, filter_query=None): self.table.clear() categories = Category.list( self.db_manager, filter_query=filter_query ) self.table.add_rows( [ [ category.id, category.name, category.activity_count, ] for category in categories ] ) self.table.sort(self.sort) def on_mount(self) -> None: self.table = self.query_one(DataTable) self.table.cursor_type = "row" self.columns = self.table.add_columns("category id", "category", "activities") self.sort = self.columns[1] self._refresh() def action_delete(self) -> None: # get the keys for the row and column under the cursor. row_key, _ = self.table.coordinate_to_cell_key(self.table.cursor_coordinate) category_id = self.table.get_cell_at( Coordinate(self.table.cursor_coordinate.row, 0), ) category = Category.get_by_id(self.db_manager, category_id) category.delete() # supply the row key to `remove_row` to delete the row. self.table.remove_row(row_key) class HamsterToolsApp(App): CSS_PATH = "app.tcss" BINDINGS = [ ("a", "switch_mode('activities')", "Activities"), ("c", "switch_mode('categories')", "Categories"), ("q", "quit", "Quit"), ] def __init__(self): self.db_manager = DatabaseManager("hamster-testing.db") self.MODES = { "categories": CategoriesScreen(self.db_manager), "activities": ActivitiesScreen(self.db_manager) } super().__init__() def on_mount(self) -> None: self.switch_mode("activities") def action_quit(self) -> None: self.exit() self.db_manager.close()