clan-vm-manager: Preserved selection on view change
All checks were successful
checks-impure / test (pull_request) Successful in 1m26s
checks / test (pull_request) Successful in 2m3s

This commit is contained in:
Luis Hebendanz 2023-12-04 14:29:05 +01:00
parent 354291440a
commit 82fb1c36fe
7 changed files with 71 additions and 32 deletions

View File

@ -23,6 +23,7 @@
treefmt.programs.mypy.enable = true; treefmt.programs.mypy.enable = true;
treefmt.programs.mypy.directories = { treefmt.programs.mypy.directories = {
"pkgs/clan-cli".extraPythonPackages = self'.packages.clan-cli.pytestDependencies; "pkgs/clan-cli".extraPythonPackages = self'.packages.clan-cli.pytestDependencies;
"pkgs/clan-vm-manager".extraPythonPackages = self'.packages.clan-vm-manager.propagatedBuildInputs;
}; };
treefmt.settings.formatter.nix = { treefmt.settings.formatter.nix = {

View File

@ -55,5 +55,5 @@ ignore_missing_imports = true
[tool.ruff] [tool.ruff]
target-version = "py311" target-version = "py311"
line-length = 88 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"] ignore = ["E501", "E402", "ANN101", "ANN401", "A003"]

View File

@ -7,5 +7,7 @@
"path": "../clan-cli/clan_cli" "path": "../clan-cli/clan_cli"
} }
], ],
"settings": {} "settings": {
"python.linting.mypyEnabled": true
}
} }

View File

@ -16,7 +16,7 @@ from .ui.clan_select_list import ClanEdit, ClanList
class ClanJoinPage(Gtk.Box): class ClanJoinPage(Gtk.Box):
def __init__(self, stack: Gtk.Stack) -> None: def __init__(self, *, stack: Gtk.Stack) -> None:
super().__init__() super().__init__()
self.page = Gtk.Box( self.page = Gtk.Box(
orientation=Gtk.Orientation.VERTICAL, spacing=6, expand=True orientation=Gtk.Orientation.VERTICAL, spacing=6, expand=True
@ -51,12 +51,22 @@ class MainWindow(Gtk.ApplicationWindow):
self.stack = Gtk.Stack() self.stack = Gtk.Stack()
# self.stack_switcher = Gtk.StackSwitcher() # 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 # Add named stacks
self.stack.add_titled(clan_list, "list", "List")
self.stack.add_titled( 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) vbox.add(self.stack)
@ -64,28 +74,31 @@ class MainWindow(Gtk.ApplicationWindow):
self.show_all() self.show_all()
def set_selected(self, sel: VMBase | None) -> None: def set_selected(self, sel: VMBase | None) -> None:
self.selected = sel self.selected_vm = sel
print(f"APP selected + {self.selected}") 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") widget = self.stack.get_child_by_name("list")
print("Remounting ClanListView") print("Remounting ClanListView")
if widget: if widget:
widget.destroy() widget.destroy()
self.stack.add_titled( clan_list = ClanList(**self.list_hooks, selected_vm=self.selected_vm) # type: ignore
ClanList(self.show_list, self.show_edit, self.set_selected), "list", "List" self.stack.add_titled(clan_list, "list", "List")
)
self.show_all() self.show_all()
self.stack.set_visible_child_name("list") self.stack.set_visible_child_name("list")
def show_edit(self) -> None: def remount_edit_view(self) -> None:
print("Remounting ClanEdit") print("Remounting ClanEdit")
widget = self.stack.get_child_by_name("edit") widget = self.stack.get_child_by_name("edit")
if widget: if widget:
widget.destroy() 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.show_all()
self.stack.set_visible_child_name("edit") self.stack.set_visible_child_name("edit")

View File

@ -6,7 +6,7 @@ from ..models import VMBase, get_initial_vms
class ClanEditForm(Gtk.ListBox): class ClanEditForm(Gtk.ListBox):
def __init__(self, selected: VMBase | None) -> None: def __init__(self, *, selected: VMBase | None) -> None:
super().__init__() super().__init__()
self.page = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, expand=True) self.page = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, expand=True)
self.set_border_width(10) self.set_border_width(10)
@ -47,11 +47,13 @@ class ClanEditForm(Gtk.ListBox):
class ClanEdit(Gtk.Box): 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) super().__init__(orientation=Gtk.Orientation.VERTICAL, expand=True)
self.show_list = show_list self.show_list = remount_list
self.selected = selected self.selected = selected_vm
button_hooks = { button_hooks = {
"on_save_clicked": self.on_save, "on_save_clicked": self.on_save,
@ -59,7 +61,7 @@ class ClanEdit(Gtk.Box):
self.toolbar = ClanEditToolbar(**button_hooks) self.toolbar = ClanEditToolbar(**button_hooks)
self.add(self.toolbar) self.add(self.toolbar)
self.add(ClanEditForm(self.selected)) self.add(ClanEditForm(selected=self.selected))
def on_save(self, widget: Gtk.Widget) -> None: def on_save(self, widget: Gtk.Widget) -> None:
print("Save clicked saving values") print("Save clicked saving values")
@ -82,14 +84,16 @@ class ClanList(Gtk.Box):
def __init__( def __init__(
self, self,
show_list: Callable[[], None], *,
show_edit: Callable[[], None], remount_list: Callable[[], None],
remount_edit: Callable[[], None],
set_selected: Callable[[VMBase | None], None], set_selected: Callable[[VMBase | None], None],
selected_vm: VMBase | None,
) -> None: ) -> None:
super().__init__(orientation=Gtk.Orientation.VERTICAL, expand=True) super().__init__(orientation=Gtk.Orientation.VERTICAL, expand=True)
self.show_edit = show_edit self.remount_edit_view = remount_edit
self.show_list = show_list self.remount_list_view = remount_list
self.set_selected = set_selected self.set_selected = set_selected
# TODO: We should use somekind of useState hook here. # 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.set_value(self.list_store.get_iter(path), 3, "new value")
# self.list_store[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 # 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 = { button_hooks = {
"on_start_clicked": self.on_start_clicked, "on_start_clicked": self.on_start_clicked,
@ -106,27 +110,27 @@ class ClanList(Gtk.Box):
"on_edit_clicked": self.on_edit_clicked, "on_edit_clicked": self.on_edit_clicked,
} }
self.toolbar = ClanListToolbar(**button_hooks) 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.add(self.toolbar)
self.list_hooks = { self.list_hooks = {
"on_select_row": self.on_select_vm, "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: def on_start_clicked(self, widget: Gtk.Widget) -> None:
print("Start clicked") print("Start clicked")
if self.selected_vm: if self.selected_vm:
self.selected_vm.run() self.selected_vm.run()
# Call this to reload # Call this to reload
self.show_list() self.remount_list_view()
def on_stop_clicked(self, widget: Gtk.Widget) -> None: def on_stop_clicked(self, widget: Gtk.Widget) -> None:
print("Stop clicked") print("Stop clicked")
def on_edit_clicked(self, widget: Gtk.Widget) -> None: def on_edit_clicked(self, widget: Gtk.Widget) -> None:
print("Edit clicked") print("Edit clicked")
self.show_edit() self.remount_edit_view()
def on_select_vm(self, vm: VMBase) -> None: def on_select_vm(self, vm: VMBase) -> None:
print(f"on_select_vm: {vm}") print(f"on_select_vm: {vm}")
@ -186,9 +190,8 @@ class ClanListView(Gtk.Box):
def __init__( def __init__(
self, self,
*, *,
# vms: list[VMBase],
on_select_row: Callable[[VMBase], None], on_select_row: Callable[[VMBase], None],
# on_double_click: Callable[[VMBase], None], selected_vm: VMBase | None,
) -> None: ) -> None:
super().__init__(expand=True) super().__init__(expand=True)
self.vms: list[VMBase] = [vm.base for vm in get_initial_vms()] self.vms: list[VMBase] = [vm.base for vm in get_initial_vms()]
@ -202,6 +205,7 @@ class ClanListView(Gtk.Box):
setColRenderers(self.tree_view) setColRenderers(self.tree_view)
self.set_selected_vm(selected_vm)
selection = self.tree_view.get_selection() selection = self.tree_view.get_selection()
selection.connect("changed", self._on_select_row) selection.connect("changed", self._on_select_row)
self.tree_view.connect("row-activated", self._on_double_click) self.tree_view.connect("row-activated", self._on_double_click)
@ -209,6 +213,20 @@ class ClanListView(Gtk.Box):
self.set_border_width(10) self.set_border_width(10)
self.add(self.tree_view) 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: def insertVM(self, vm: VMBase) -> None:
values = list(vm.list_data().values()) values = list(vm.list_data().values())
values[0] = GdkPixbuf.Pixbuf.new_from_file_at_scale( values[0] = GdkPixbuf.Pixbuf.new_from_file_at_scale(

View File

@ -10,6 +10,7 @@
, gobject-introspection , gobject-introspection
, clan-cli , clan-cli
, makeDesktopItem , makeDesktopItem
, mypy
, ipdb , ipdb
}: }:
let let
@ -34,7 +35,7 @@ python3.pkgs.buildPythonApplication {
]; ];
buildInputs = [ spice-gtk gtk3 gnome.adwaita-icon-theme ]; 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 # also re-expose dependencies so we test them in CI
passthru.tests = { passthru.tests = {

View File

@ -21,8 +21,12 @@ no_implicit_optional = true
module = "gi.*" module = "gi.*"
ignore_missing_imports = true ignore_missing_imports = true
[[tool.mypy.overrides]]
module = "clan_cli.*"
ignore_missing_imports = true
[tool.ruff] [tool.ruff]
target-version = "py311" target-version = "py311"
line-length = 88 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"] ignore = ["E501", "E402", "N802", "ANN101", "ANN401", "A003"]