vm-manager: Added right click context menu
This commit is contained in:
@@ -9,8 +9,9 @@ from clan_cli.flakes.inspect import FlakeConfig, inspect_flake
|
|||||||
|
|
||||||
from ..clan_uri import ClanURI
|
from ..clan_uri import ClanURI
|
||||||
from ..dirs import user_history_file
|
from ..dirs import user_history_file
|
||||||
from ..locked_open import read_history_file, write_history_file
|
|
||||||
from ..errors import ClanError
|
from ..errors import ClanError
|
||||||
|
from ..locked_open import read_history_file, write_history_file
|
||||||
|
|
||||||
|
|
||||||
class EnhancedJSONEncoder(json.JSONEncoder):
|
class EnhancedJSONEncoder(json.JSONEncoder):
|
||||||
def default(self, o: Any) -> Any:
|
def default(self, o: Any) -> Any:
|
||||||
@@ -53,7 +54,8 @@ def list_history() -> list[HistoryEntry]:
|
|||||||
try:
|
try:
|
||||||
parsed = read_history_file()
|
parsed = read_history_file()
|
||||||
for i, p in enumerate(parsed.copy()):
|
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]
|
logs = [HistoryEntry(**p) for p in parsed]
|
||||||
except (json.JSONDecodeError, TypeError) as ex:
|
except (json.JSONDecodeError, TypeError) as ex:
|
||||||
raise ClanError(f"History file at {user_history_file()} is corrupted") from ex
|
raise ClanError(f"History file at {user_history_file()} is corrupted") from ex
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ from .errors.show_error import show_error_dialog
|
|||||||
|
|
||||||
gi.require_version("GdkPixbuf", "2.0")
|
gi.require_version("GdkPixbuf", "2.0")
|
||||||
|
|
||||||
|
from clan_cli.errors import ClanError
|
||||||
from gi.repository import GdkPixbuf
|
from gi.repository import GdkPixbuf
|
||||||
|
|
||||||
from clan_vm_manager import assets
|
from clan_vm_manager import assets
|
||||||
@@ -60,6 +61,7 @@ class VM:
|
|||||||
description: str | None = None
|
description: str | None = None
|
||||||
|
|
||||||
|
|
||||||
|
# TODO: How to handle incompatible / corrupted history file. Delete it?
|
||||||
# start/end indexes can be used optionally for pagination
|
# start/end indexes can be used optionally for pagination
|
||||||
def get_initial_vms(
|
def get_initial_vms(
|
||||||
running_vms: list[str], start: int = 0, end: int | None = None
|
running_vms: list[str], start: int = 0, end: int | None = None
|
||||||
@@ -85,7 +87,7 @@ def get_initial_vms(
|
|||||||
_flake_attr=entry.flake.flake_attr,
|
_flake_attr=entry.flake.flake_attr,
|
||||||
)
|
)
|
||||||
vm_list.append(VM(base=base))
|
vm_list.append(VM(base=base))
|
||||||
except Exception as e:
|
except ClanError as e:
|
||||||
show_error_dialog(e)
|
show_error_dialog(e)
|
||||||
|
|
||||||
# start/end slices can be used for pagination
|
# start/end slices can be used for pagination
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
from collections.abc import Callable
|
from collections.abc import Callable
|
||||||
|
|
||||||
from gi.repository import GdkPixbuf, Gtk
|
from gi.repository import Gdk, GdkPixbuf, Gtk
|
||||||
|
|
||||||
from ..interfaces import Callbacks
|
from ..interfaces import Callbacks
|
||||||
from ..models import VMBase
|
from ..models import VMBase
|
||||||
|
from .context_menu import VmMenu
|
||||||
|
|
||||||
|
|
||||||
class ClanEditForm(Gtk.ListBox):
|
class ClanEditForm(Gtk.ListBox):
|
||||||
@@ -97,7 +98,6 @@ class ClanList(Gtk.Box):
|
|||||||
self.set_selected = set_selected
|
self.set_selected = set_selected
|
||||||
self.show_toolbar = show_toolbar
|
self.show_toolbar = show_toolbar
|
||||||
self.cbs = cbs
|
self.cbs = cbs
|
||||||
|
|
||||||
self.show_join = cbs.show_join
|
self.show_join = cbs.show_join
|
||||||
|
|
||||||
self.selected_vm: VMBase | None = selected_vm
|
self.selected_vm: VMBase | None = selected_vm
|
||||||
@@ -228,6 +228,8 @@ class ClanListView(Gtk.Box):
|
|||||||
self.vms: list[VMBase] = vms
|
self.vms: list[VMBase] = vms
|
||||||
self.on_select_row = on_select_row
|
self.on_select_row = on_select_row
|
||||||
self.on_double_click = on_double_click
|
self.on_double_click = on_double_click
|
||||||
|
self.context_menu: VmMenu | None = None
|
||||||
|
|
||||||
store_types = VMBase.name_to_type_map().values()
|
store_types = VMBase.name_to_type_map().values()
|
||||||
|
|
||||||
self.list_store = Gtk.ListStore(*store_types)
|
self.list_store = Gtk.ListStore(*store_types)
|
||||||
@@ -241,6 +243,7 @@ class ClanListView(Gtk.Box):
|
|||||||
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)
|
||||||
|
self.tree_view.connect("button-press-event", self._on_button_pressed)
|
||||||
|
|
||||||
self.set_border_width(10)
|
self.set_border_width(10)
|
||||||
self.add(self.tree_view)
|
self.add(self.tree_view)
|
||||||
@@ -272,12 +275,29 @@ class ClanListView(Gtk.Box):
|
|||||||
vm = VMBase(*model[row])
|
vm = VMBase(*model[row])
|
||||||
self.on_select_row(vm)
|
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(
|
def _on_double_click(
|
||||||
self, tree_view: Gtk.TreeView, path: Gtk.TreePath, column: Gtk.TreeViewColumn
|
self, tree_view: Gtk.TreeView, path: Gtk.TreePath, column: Gtk.TreeViewColumn
|
||||||
) -> None:
|
) -> None:
|
||||||
# Get the selection object of the tree view
|
# Get the selection object of the tree view
|
||||||
selection = tree_view.get_selection()
|
selection = tree_view.get_selection()
|
||||||
model, row = selection.get_selected()
|
model, row = selection.get_selected()
|
||||||
|
|
||||||
if row is not None:
|
if row is not None:
|
||||||
vm = VMBase(*model[row])
|
vm = VMBase(*model[row])
|
||||||
self.on_double_click(vm)
|
self.on_double_click(vm)
|
||||||
|
|||||||
39
pkgs/clan-vm-manager/clan_vm_manager/ui/context_menu.py
Normal file
39
pkgs/clan-vm-manager/clan_vm_manager/ui/context_menu.py
Normal file
@@ -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")
|
||||||
Reference in New Issue
Block a user