diff --git a/formatter.nix b/formatter.nix index 89a68165..eb0870e5 100644 --- a/formatter.nix +++ b/formatter.nix @@ -23,6 +23,7 @@ treefmt.programs.mypy.enable = true; treefmt.programs.mypy.directories = { "pkgs/clan-cli".extraPythonPackages = self'.packages.clan-cli.pytestDependencies; + "pkgs/clan-vm-manager".extraPythonPackages = self'.packages.clan-vm-manager.propagatedBuildInputs; }; treefmt.settings.formatter.nix = { diff --git a/pkgs/clan-cli/pyproject.toml b/pkgs/clan-cli/pyproject.toml index 05ce0a0a..2d503306 100644 --- a/pkgs/clan-cli/pyproject.toml +++ b/pkgs/clan-cli/pyproject.toml @@ -55,5 +55,5 @@ ignore_missing_imports = true [tool.ruff] target-version = "py311" line-length = 88 -select = [ "E", "F", "I", "U", "N", "RUF", "ANN", "A" ] +select = [ "E", "F", "I", "N", "RUF", "ANN", "A" ] ignore = ["E501", "E402", "ANN101", "ANN401", "A003"] diff --git a/pkgs/clan-vm-manager/clan-vm-manager.code-workspace b/pkgs/clan-vm-manager/clan-vm-manager.code-workspace index 4f0e7e1b..50ec088e 100644 --- a/pkgs/clan-vm-manager/clan-vm-manager.code-workspace +++ b/pkgs/clan-vm-manager/clan-vm-manager.code-workspace @@ -7,5 +7,7 @@ "path": "../clan-cli/clan_cli" } ], - "settings": {} + "settings": { + "python.linting.mypyEnabled": true + } } \ No newline at end of file diff --git a/pkgs/clan-vm-manager/clan_vm_manager/app.py b/pkgs/clan-vm-manager/clan_vm_manager/app.py index 3c9b7ec3..18a17757 100644 --- a/pkgs/clan-vm-manager/clan_vm_manager/app.py +++ b/pkgs/clan-vm-manager/clan_vm_manager/app.py @@ -16,7 +16,7 @@ from .ui.clan_select_list import ClanEdit, ClanList class ClanJoinPage(Gtk.Box): - def __init__(self, stack: Gtk.Stack) -> None: + def __init__(self, *, stack: Gtk.Stack) -> None: super().__init__() self.page = Gtk.Box( orientation=Gtk.Orientation.VERTICAL, spacing=6, expand=True @@ -51,12 +51,22 @@ class MainWindow(Gtk.ApplicationWindow): self.stack = Gtk.Stack() # self.stack_switcher = Gtk.StackSwitcher() + self.list_hooks = { + "remount_list": self.remount_list_view, + "remount_edit": self.remount_edit_view, + "set_selected": self.set_selected, + } + clan_list = ClanList(**self.list_hooks, selected_vm=None) # type: ignore # Add named stacks + self.stack.add_titled(clan_list, "list", "List") self.stack.add_titled( - ClanList(self.show_list, self.show_edit, self.set_selected), "list", "List" + ClanJoinPage(stack=self.remount_list_view), "join", "Join" + ) + self.stack.add_titled( + ClanEdit(remount_list=self.remount_list_view, selected_vm=None), + "edit", + "Edit", ) - self.stack.add_titled(ClanJoinPage(self.show_list), "join", "Join") - self.stack.add_titled(ClanEdit(self.show_list, None), "edit", "Edit") vbox.add(self.stack) @@ -64,28 +74,31 @@ class MainWindow(Gtk.ApplicationWindow): self.show_all() def set_selected(self, sel: VMBase | None) -> None: - self.selected = sel - print(f"APP selected + {self.selected}") + self.selected_vm = sel + print(f"APP selected + {self.selected_vm}") - def show_list(self) -> None: + def remount_list_view(self) -> None: widget = self.stack.get_child_by_name("list") print("Remounting ClanListView") if widget: widget.destroy() - self.stack.add_titled( - ClanList(self.show_list, self.show_edit, self.set_selected), "list", "List" - ) + clan_list = ClanList(**self.list_hooks, selected_vm=self.selected_vm) # type: ignore + self.stack.add_titled(clan_list, "list", "List") self.show_all() self.stack.set_visible_child_name("list") - def show_edit(self) -> None: + def remount_edit_view(self) -> None: print("Remounting ClanEdit") widget = self.stack.get_child_by_name("edit") if widget: widget.destroy() - self.stack.add_titled(ClanEdit(self.show_list, self.selected), "edit", "Edit") + self.stack.add_titled( + ClanEdit(remount_list=self.remount_list_view, selected_vm=self.selected_vm), + "edit", + "Edit", + ) self.show_all() self.stack.set_visible_child_name("edit") diff --git a/pkgs/clan-vm-manager/clan_vm_manager/ui/clan_select_list.py b/pkgs/clan-vm-manager/clan_vm_manager/ui/clan_select_list.py index 50af0ebf..f8bc576d 100644 --- a/pkgs/clan-vm-manager/clan_vm_manager/ui/clan_select_list.py +++ b/pkgs/clan-vm-manager/clan_vm_manager/ui/clan_select_list.py @@ -6,7 +6,7 @@ from ..models import VMBase, get_initial_vms class ClanEditForm(Gtk.ListBox): - def __init__(self, selected: VMBase | None) -> None: + def __init__(self, *, selected: VMBase | None) -> None: super().__init__() self.page = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, expand=True) self.set_border_width(10) @@ -47,11 +47,13 @@ class ClanEditForm(Gtk.ListBox): class ClanEdit(Gtk.Box): - def __init__(self, show_list: Callable[[], None], selected: VMBase | None) -> None: + def __init__( + self, *, remount_list: Callable[[], None], selected_vm: VMBase | None + ) -> None: super().__init__(orientation=Gtk.Orientation.VERTICAL, expand=True) - self.show_list = show_list - self.selected = selected + self.show_list = remount_list + self.selected = selected_vm button_hooks = { "on_save_clicked": self.on_save, @@ -59,7 +61,7 @@ class ClanEdit(Gtk.Box): self.toolbar = ClanEditToolbar(**button_hooks) self.add(self.toolbar) - self.add(ClanEditForm(self.selected)) + self.add(ClanEditForm(selected=self.selected)) def on_save(self, widget: Gtk.Widget) -> None: print("Save clicked saving values") @@ -82,14 +84,16 @@ class ClanList(Gtk.Box): def __init__( self, - show_list: Callable[[], None], - show_edit: Callable[[], None], + *, + remount_list: Callable[[], None], + remount_edit: Callable[[], None], set_selected: Callable[[VMBase | None], None], + selected_vm: VMBase | None, ) -> None: super().__init__(orientation=Gtk.Orientation.VERTICAL, expand=True) - self.show_edit = show_edit - self.show_list = show_list + self.remount_edit_view = remount_edit + self.remount_list_view = remount_list self.set_selected = set_selected # TODO: We should use somekind of useState hook here. @@ -98,7 +102,7 @@ class ClanList(Gtk.Box): # self.list_store.set_value(self.list_store.get_iter(path), 3, "new value") # self.list_store[path][3] = "new_value" # This class needs to take ownership of the data because it has access to the listStore only - self.selected_vm: VMBase | None = None + self.selected_vm: VMBase | None = selected_vm button_hooks = { "on_start_clicked": self.on_start_clicked, @@ -106,27 +110,27 @@ class ClanList(Gtk.Box): "on_edit_clicked": self.on_edit_clicked, } self.toolbar = ClanListToolbar(**button_hooks) - self.toolbar.set_is_selected(False) + self.toolbar.set_is_selected(self.selected_vm is not None) self.add(self.toolbar) self.list_hooks = { "on_select_row": self.on_select_vm, } - self.add(ClanListView(**self.list_hooks)) + self.add(ClanListView(**self.list_hooks, selected_vm=selected_vm)) def on_start_clicked(self, widget: Gtk.Widget) -> None: print("Start clicked") if self.selected_vm: self.selected_vm.run() # Call this to reload - self.show_list() + self.remount_list_view() def on_stop_clicked(self, widget: Gtk.Widget) -> None: print("Stop clicked") def on_edit_clicked(self, widget: Gtk.Widget) -> None: print("Edit clicked") - self.show_edit() + self.remount_edit_view() def on_select_vm(self, vm: VMBase) -> None: print(f"on_select_vm: {vm}") @@ -186,9 +190,8 @@ class ClanListView(Gtk.Box): def __init__( self, *, - # vms: list[VMBase], on_select_row: Callable[[VMBase], None], - # on_double_click: Callable[[VMBase], None], + selected_vm: VMBase | None, ) -> None: super().__init__(expand=True) self.vms: list[VMBase] = [vm.base for vm in get_initial_vms()] @@ -202,6 +205,7 @@ class ClanListView(Gtk.Box): setColRenderers(self.tree_view) + self.set_selected_vm(selected_vm) selection = self.tree_view.get_selection() selection.connect("changed", self._on_select_row) self.tree_view.connect("row-activated", self._on_double_click) @@ -209,6 +213,20 @@ class ClanListView(Gtk.Box): self.set_border_width(10) self.add(self.tree_view) + def find_vm(self, vm: VMBase) -> int: + for idx, row in enumerate(self.list_store): + if row[1] == vm.name: # TODO: Change to path + return idx + return -1 + + def set_selected_vm(self, vm: VMBase | None) -> None: + if vm is None: + return + selection = self.tree_view.get_selection() + idx = self.find_vm(vm) + print(f"Set selected vm: {vm.name} at {idx}") + selection.select_path(idx) + def insertVM(self, vm: VMBase) -> None: values = list(vm.list_data().values()) values[0] = GdkPixbuf.Pixbuf.new_from_file_at_scale( diff --git a/pkgs/clan-vm-manager/default.nix b/pkgs/clan-vm-manager/default.nix index 9d460573..3b870aed 100644 --- a/pkgs/clan-vm-manager/default.nix +++ b/pkgs/clan-vm-manager/default.nix @@ -10,6 +10,7 @@ , gobject-introspection , clan-cli , makeDesktopItem +, mypy , ipdb }: let @@ -34,7 +35,7 @@ python3.pkgs.buildPythonApplication { ]; buildInputs = [ spice-gtk gtk3 gnome.adwaita-icon-theme ]; - propagatedBuildInputs = [ ipdb pygobject3 clan-cli ]; + propagatedBuildInputs = [ mypy ipdb pygobject3 clan-cli ]; # also re-expose dependencies so we test them in CI passthru.tests = { diff --git a/pkgs/clan-vm-manager/pyproject.toml b/pkgs/clan-vm-manager/pyproject.toml index 3308211a..b73b66b1 100644 --- a/pkgs/clan-vm-manager/pyproject.toml +++ b/pkgs/clan-vm-manager/pyproject.toml @@ -21,8 +21,12 @@ no_implicit_optional = true module = "gi.*" ignore_missing_imports = true +[[tool.mypy.overrides]] +module = "clan_cli.*" +ignore_missing_imports = true + [tool.ruff] target-version = "py311" line-length = 88 -select = [ "E", "F", "I", "U", "N", "RUF", "ANN", "A" ] +select = [ "E", "F", "I", "N", "RUF", "ANN", "A" ] ignore = ["E501", "E402", "N802", "ANN101", "ANN401", "A003"]