diff --git a/pkgs/clan-cli/clan_cli/history/add.py b/pkgs/clan-cli/clan_cli/history/add.py index 7efa9612c..c753b6f3b 100644 --- a/pkgs/clan-cli/clan_cli/history/add.py +++ b/pkgs/clan-cli/clan_cli/history/add.py @@ -9,6 +9,7 @@ from clan_cli.flakes.inspect import FlakeConfig, inspect_flake from ..clan_uri import ClanURI from ..dirs import user_history_file +from ..errors import ClanError from ..locked_open import read_history_file, write_history_file @@ -53,11 +54,11 @@ def list_history() -> list[HistoryEntry]: try: parsed = read_history_file() for i, p in enumerate(parsed.copy()): - parsed[i] = merge_dicts(p, p["settings"]) + # Everything from the settings dict is merged into the flake dict, and can override existing values + parsed[i] = merge_dicts(p, p.get("settings", {})) logs = [HistoryEntry(**p) for p in parsed] except (json.JSONDecodeError, TypeError) as ex: - print("Failed to load history. Invalid JSON.") - print(f"{user_history_file()}: {ex}") + raise ClanError(f"History file at {user_history_file()} is corrupted") from ex return logs diff --git a/pkgs/clan-vm-manager/clan_vm_manager/models.py b/pkgs/clan-vm-manager/clan_vm_manager/models.py index 2605a6797..0e7b2b248 100644 --- a/pkgs/clan-vm-manager/clan_vm_manager/models.py +++ b/pkgs/clan-vm-manager/clan_vm_manager/models.py @@ -4,10 +4,13 @@ from pathlib import Path from typing import Any import gi -from clan_cli import history +from clan_cli.history.list import list_history + +from .errors.show_error import show_error_dialog gi.require_version("GdkPixbuf", "2.0") +from clan_cli.errors import ClanError from gi.repository import GdkPixbuf from clan_vm_manager import assets @@ -58,30 +61,34 @@ class VM: description: str | None = None +# TODO: How to handle incompatible / corrupted history file. Delete it? # start/end indexes can be used optionally for pagination def get_initial_vms( running_vms: list[str], start: int = 0, end: int | None = None ) -> list[VM]: vm_list = [] - # Execute `clan flakes add ` to democlan for this to work - for entry in history.list.list_history(): - icon = assets.loc / "placeholder.jpeg" - if entry.flake.icon is not None: - icon = entry.flake.icon + try: + # Execute `clan flakes add ` to democlan for this to work + for entry in list_history(): + icon = assets.loc / "placeholder.jpeg" + if entry.flake.icon is not None: + icon = entry.flake.icon - status = False - if entry.flake.flake_url in running_vms: - status = True + status = False + if entry.flake.flake_url in running_vms: + status = True - base = VMBase( - icon=icon, - name=entry.flake.clan_name, - url=entry.flake.flake_url, - status=status, - _flake_attr=entry.flake.flake_attr, - ) - vm_list.append(VM(base=base)) + base = VMBase( + icon=icon, + name=entry.flake.clan_name, + url=entry.flake.flake_url, + status=status, + _flake_attr=entry.flake.flake_attr, + ) + vm_list.append(VM(base=base)) + except ClanError as e: + show_error_dialog(e) # start/end slices can be used for pagination return vm_list[start:end] 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 ddefe9d86..b7448dc7e 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 @@ -1,9 +1,10 @@ from collections.abc import Callable -from gi.repository import GdkPixbuf, Gtk +from gi.repository import Gdk, GdkPixbuf, Gtk from ..interfaces import Callbacks from ..models import VMBase +from .context_menu import VmMenu class ClanEditForm(Gtk.ListBox): @@ -97,7 +98,6 @@ class ClanList(Gtk.Box): self.set_selected = set_selected self.show_toolbar = show_toolbar self.cbs = cbs - self.show_join = cbs.show_join self.selected_vm: VMBase | None = selected_vm @@ -228,6 +228,8 @@ class ClanListView(Gtk.Box): self.vms: list[VMBase] = vms self.on_select_row = on_select_row self.on_double_click = on_double_click + self.context_menu: VmMenu | None = None + store_types = VMBase.name_to_type_map().values() self.list_store = Gtk.ListStore(*store_types) @@ -241,6 +243,7 @@ class ClanListView(Gtk.Box): selection = self.tree_view.get_selection() selection.connect("changed", self._on_select_row) self.tree_view.connect("row-activated", self._on_double_click) + self.tree_view.connect("button-press-event", self._on_button_pressed) self.set_border_width(10) self.add(self.tree_view) @@ -272,12 +275,29 @@ class ClanListView(Gtk.Box): vm = VMBase(*model[row]) self.on_select_row(vm) + def _on_button_pressed( + self, tree_view: Gtk.TreeView, event: Gdk.EventButton + ) -> None: + if self.context_menu: + self.context_menu.destroy() + self.context_menu = None + + if event.button == 3: + path, column, x, y = tree_view.get_path_at_pos(event.x, event.y) + if path is not None: + vm = VMBase(*self.list_store[path[0]]) + print(event) + print(f"Right click on {vm.url}") + self.context_menu = VmMenu(vm) + self.context_menu.popup_at_pointer(event) + def _on_double_click( self, tree_view: Gtk.TreeView, path: Gtk.TreePath, column: Gtk.TreeViewColumn ) -> None: # Get the selection object of the tree view selection = tree_view.get_selection() model, row = selection.get_selected() + if row is not None: vm = VMBase(*model[row]) self.on_double_click(vm) diff --git a/pkgs/clan-vm-manager/clan_vm_manager/ui/context_menu.py b/pkgs/clan-vm-manager/clan_vm_manager/ui/context_menu.py new file mode 100644 index 000000000..fa288274b --- /dev/null +++ b/pkgs/clan-vm-manager/clan_vm_manager/ui/context_menu.py @@ -0,0 +1,39 @@ +import gi + +gi.require_version("Gtk", "3.0") +from gi.repository import Gtk + +from ..models import VMBase + + +class VmMenu(Gtk.Menu): + def __init__(self, vm: VMBase) -> None: + super().__init__() + self.vm = vm + self.menu_items = [ + ("Start", self.start_vm), + ("Stop", self.stop_vm), + ("Edit", self.edit_vm), + ("Remove", self.remove_vm), + ("Write to USB", self.write_to_usb), + ] + for item in self.menu_items: + menu_item = Gtk.MenuItem(label=item[0]) + menu_item.connect("activate", item[1]) + self.append(menu_item) + self.show_all() + + def start_vm(self, widget: Gtk.Widget) -> None: + print("start_vm") + + def stop_vm(self, widget: Gtk.Widget) -> None: + print("stop_vm") + + def edit_vm(self, widget: Gtk.Widget) -> None: + print("edit_vm") + + def remove_vm(self, widget: Gtk.Widget) -> None: + print("remove_vm") + + def write_to_usb(self, widget: Gtk.Widget) -> None: + print("write_to_usb") diff --git a/pkgs/clan-vm-manager/clan_vm_manager/windows/overview.py b/pkgs/clan-vm-manager/clan_vm_manager/windows/overview.py index d4d844275..0f2d2737e 100644 --- a/pkgs/clan-vm-manager/clan_vm_manager/windows/overview.py +++ b/pkgs/clan-vm-manager/clan_vm_manager/windows/overview.py @@ -33,6 +33,7 @@ class OverviewWindow(Gtk.ApplicationWindow): set_selected=self.set_selected, selected_vm=None, ) + # Add named stacks self.stack.add_titled(clan_list, "list", "List") self.stack.add_titled(