forked from clan/clan-core
clan-vm-manager: Preserved selection on view change
This commit is contained in:
parent
354291440a
commit
82fb1c36fe
@ -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 = {
|
||||||
|
@ -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"]
|
||||||
|
@ -7,5 +7,7 @@
|
|||||||
"path": "../clan-cli/clan_cli"
|
"path": "../clan-cli/clan_cli"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"settings": {}
|
"settings": {
|
||||||
|
"python.linting.mypyEnabled": true
|
||||||
|
}
|
||||||
}
|
}
|
@ -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")
|
||||||
|
|
||||||
|
@ -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(
|
||||||
|
@ -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 = {
|
||||||
|
@ -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"]
|
||||||
|
Loading…
Reference in New Issue
Block a user