Merge pull request 'Added windows folder' (#624) from Qubasa-main into main
This commit is contained in:
@@ -20,14 +20,6 @@ class ClanScheme(Enum):
|
|||||||
def __str__(self) -> str:
|
def __str__(self) -> str:
|
||||||
return f"HTTP({self.url})" # The __str__ method returns a custom string representation
|
return f"HTTP({self.url})" # The __str__ method returns a custom string representation
|
||||||
|
|
||||||
@member
|
|
||||||
@dataclass
|
|
||||||
class HTTPS:
|
|
||||||
url: str # The url field holds the HTTPS URL
|
|
||||||
|
|
||||||
def __str__(self) -> str:
|
|
||||||
return f"HTTPS({self.url})" # The __str__ method returns a custom string representation
|
|
||||||
|
|
||||||
@member
|
@member
|
||||||
@dataclass
|
@dataclass
|
||||||
class FILE:
|
class FILE:
|
||||||
@@ -78,19 +70,30 @@ class ClanURI:
|
|||||||
self._components = self._components._replace(query=new_query)
|
self._components = self._components._replace(query=new_query)
|
||||||
self.params = ClanParameters(**params)
|
self.params = ClanParameters(**params)
|
||||||
|
|
||||||
# Use the match statement to check the scheme and create a ClanScheme member with the value
|
comb = (
|
||||||
match self._components.scheme:
|
self._components.scheme,
|
||||||
case "http":
|
self._components.netloc,
|
||||||
|
self._components.path,
|
||||||
|
self._components.params,
|
||||||
|
self._components.query,
|
||||||
|
self._components.fragment,
|
||||||
|
)
|
||||||
|
|
||||||
|
match comb:
|
||||||
|
case ("http" | "https", _, _, _, _, _):
|
||||||
self.scheme = ClanScheme.HTTP.value(self._components.geturl()) # type: ignore
|
self.scheme = ClanScheme.HTTP.value(self._components.geturl()) # type: ignore
|
||||||
case "https":
|
case ("file", "", path, "", "", "") | ("", "", path, "", "", ""): # type: ignore
|
||||||
self.scheme = ClanScheme.HTTPS.value(self._components.geturl()) # type: ignore
|
self.scheme = ClanScheme.FILE.value(Path(path)) # type: ignore
|
||||||
case "file":
|
|
||||||
self.scheme = ClanScheme.FILE.value(Path(self._components.path)) # type: ignore
|
|
||||||
case _:
|
case _:
|
||||||
raise ClanError(f"Unsupported scheme: {self._components.scheme}")
|
raise ClanError(f"Unsupported uri components: {comb}")
|
||||||
|
|
||||||
|
def get_internal(self) -> str:
|
||||||
|
return self._nested_uri
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_path(cls, path: Path, params: ClanParameters) -> Self: # noqa
|
def from_path(cls, path: Path, params: ClanParameters) -> Self: # noqa
|
||||||
urlparams = urllib.parse.urlencode(params.__dict__)
|
urlparams = urllib.parse.urlencode(params.__dict__)
|
||||||
|
return cls(f"clan://{path}?{urlparams}")
|
||||||
|
|
||||||
return cls(f"clan://file://{path}?{urlparams}")
|
def __str__(self) -> str:
|
||||||
|
return f"ClanURI({self._components.geturl()})"
|
||||||
|
|||||||
@@ -82,6 +82,13 @@ def nix_eval(flags: list[str]) -> list[str]:
|
|||||||
return default_flags + flags
|
return default_flags + flags
|
||||||
|
|
||||||
|
|
||||||
|
def nix_metadata(flake: str) -> dict[str, Any]:
|
||||||
|
cmd = nix_command(["flake", "metadata", "--json", flake])
|
||||||
|
proc = subprocess.run(cmd, check=True, text=True, stdout=subprocess.PIPE)
|
||||||
|
data = json.loads(proc.stdout)
|
||||||
|
return data
|
||||||
|
|
||||||
|
|
||||||
@deal.raises(ClanError)
|
@deal.raises(ClanError)
|
||||||
def nix_shell(packages: list[str], cmd: list[str]) -> list[str]:
|
def nix_shell(packages: list[str], cmd: list[str]) -> list[str]:
|
||||||
# we cannot use nix-shell inside the nix sandbox
|
# we cannot use nix-shell inside the nix sandbox
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ from pathlib import Path
|
|||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from clan_cli.clan_uri import ClanScheme, ClanURI
|
from clan_cli.clan_uri import ClanParameters, ClanScheme, ClanURI
|
||||||
from clan_cli.errors import ClanError
|
from clan_cli.errors import ClanError
|
||||||
|
|
||||||
|
|
||||||
@@ -17,7 +17,7 @@ def test_local_uri() -> None:
|
|||||||
|
|
||||||
|
|
||||||
def test_unsupported_schema() -> None:
|
def test_unsupported_schema() -> None:
|
||||||
with pytest.raises(ClanError, match="Unsupported scheme: ftp"):
|
with pytest.raises(ClanError, match="Unsupported uri components: .*"):
|
||||||
# Create a ClanURI object from an unsupported URI
|
# Create a ClanURI object from an unsupported URI
|
||||||
ClanURI("clan://ftp://ftp.example.com")
|
ClanURI("clan://ftp://ftp.example.com")
|
||||||
|
|
||||||
@@ -27,12 +27,24 @@ def test_is_remote() -> None:
|
|||||||
uri = ClanURI("clan://https://example.com")
|
uri = ClanURI("clan://https://example.com")
|
||||||
|
|
||||||
match uri.scheme:
|
match uri.scheme:
|
||||||
case ClanScheme.HTTPS.value(url):
|
case ClanScheme.HTTP.value(url):
|
||||||
assert url == "https://example.com" # type: ignore
|
assert url == "https://example.com" # type: ignore
|
||||||
case _:
|
case _:
|
||||||
assert False
|
assert False
|
||||||
|
|
||||||
|
|
||||||
|
def test_direct_local_path() -> None:
|
||||||
|
# Create a ClanURI object from a remote URI
|
||||||
|
uri = ClanURI("clan://~/Downloads")
|
||||||
|
assert uri.get_internal() == "~/Downloads"
|
||||||
|
|
||||||
|
|
||||||
|
def test_direct_local_path2() -> None:
|
||||||
|
# Create a ClanURI object from a remote URI
|
||||||
|
uri = ClanURI("clan:///home/user/Downloads")
|
||||||
|
assert uri.get_internal() == "/home/user/Downloads"
|
||||||
|
|
||||||
|
|
||||||
def test_remote_with_clanparams() -> None:
|
def test_remote_with_clanparams() -> None:
|
||||||
# Create a ClanURI object from a remote URI with parameters
|
# Create a ClanURI object from a remote URI with parameters
|
||||||
uri = ClanURI("clan://https://example.com")
|
uri = ClanURI("clan://https://example.com")
|
||||||
@@ -40,19 +52,47 @@ def test_remote_with_clanparams() -> None:
|
|||||||
assert uri.params.flake_attr == "defaultVM"
|
assert uri.params.flake_attr == "defaultVM"
|
||||||
|
|
||||||
match uri.scheme:
|
match uri.scheme:
|
||||||
case ClanScheme.HTTPS.value(url):
|
case ClanScheme.HTTP.value(url):
|
||||||
assert url == "https://example.com" # type: ignore
|
assert url == "https://example.com" # type: ignore
|
||||||
case _:
|
case _:
|
||||||
assert False
|
assert False
|
||||||
|
|
||||||
|
|
||||||
|
def test_from_path_with_custom() -> None:
|
||||||
|
# Create a ClanURI object from a remote URI with parameters
|
||||||
|
uri_str = Path("/home/user/Downloads")
|
||||||
|
params = ClanParameters(flake_attr="myVM")
|
||||||
|
uri = ClanURI.from_path(uri_str, params)
|
||||||
|
assert uri.params.flake_attr == "myVM"
|
||||||
|
|
||||||
|
match uri.scheme:
|
||||||
|
case ClanScheme.FILE.value(path):
|
||||||
|
assert path == Path("/home/user/Downloads") # type: ignore
|
||||||
|
case _:
|
||||||
|
assert False
|
||||||
|
|
||||||
|
|
||||||
|
def test_from_path_with_default() -> None:
|
||||||
|
# Create a ClanURI object from a remote URI with parameters
|
||||||
|
uri_str = Path("/home/user/Downloads")
|
||||||
|
params = ClanParameters()
|
||||||
|
uri = ClanURI.from_path(uri_str, params)
|
||||||
|
assert uri.params.flake_attr == "defaultVM"
|
||||||
|
|
||||||
|
match uri.scheme:
|
||||||
|
case ClanScheme.FILE.value(path):
|
||||||
|
assert path == Path("/home/user/Downloads") # type: ignore
|
||||||
|
case _:
|
||||||
|
assert False
|
||||||
|
|
||||||
|
|
||||||
def test_remote_with_all_params() -> None:
|
def test_remote_with_all_params() -> None:
|
||||||
# Create a ClanURI object from a remote URI with parameters
|
# Create a ClanURI object from a remote URI with parameters
|
||||||
uri = ClanURI("clan://https://example.com?flake_attr=myVM&password=1234")
|
uri = ClanURI("clan://https://example.com?flake_attr=myVM&password=1234")
|
||||||
assert uri.params.flake_attr == "myVM"
|
assert uri.params.flake_attr == "myVM"
|
||||||
|
|
||||||
match uri.scheme:
|
match uri.scheme:
|
||||||
case ClanScheme.HTTPS.value(url):
|
case ClanScheme.HTTP.value(url):
|
||||||
assert url == "https://example.com?password=1234" # type: ignore
|
assert url == "https://example.com?password=1234" # type: ignore
|
||||||
case _:
|
case _:
|
||||||
assert False
|
assert False
|
||||||
|
|||||||
@@ -1,23 +1,7 @@
|
|||||||
import argparse
|
import argparse
|
||||||
|
|
||||||
from .app import Application
|
from .windows.join import register_join_parser
|
||||||
|
from .windows.overview import register_overview_parser, show_overview
|
||||||
|
|
||||||
def join_command(args: argparse.Namespace) -> None:
|
|
||||||
print("Joining the flake")
|
|
||||||
print(args.clan_uri)
|
|
||||||
app = Application()
|
|
||||||
return app.run()
|
|
||||||
|
|
||||||
|
|
||||||
def register_join_parser(parser: argparse.ArgumentParser) -> None:
|
|
||||||
parser.add_argument("clan_uri", type=str, help="clan URI to join")
|
|
||||||
parser.set_defaults(func=join_command)
|
|
||||||
|
|
||||||
|
|
||||||
def start_app(args: argparse.Namespace) -> None:
|
|
||||||
app = Application()
|
|
||||||
return app.run()
|
|
||||||
|
|
||||||
|
|
||||||
def main() -> None:
|
def main() -> None:
|
||||||
@@ -31,7 +15,9 @@ def main() -> None:
|
|||||||
)
|
)
|
||||||
register_join_parser(subparser.add_parser("join", help="join a clan"))
|
register_join_parser(subparser.add_parser("join", help="join a clan"))
|
||||||
|
|
||||||
|
register_overview_parser(subparser.add_parser("overview", help="overview screen"))
|
||||||
|
|
||||||
# Executed when no command is given
|
# Executed when no command is given
|
||||||
parser.set_defaults(func=start_app)
|
parser.set_defaults(func=show_overview)
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
args.func(args)
|
args.func(args)
|
||||||
|
|||||||
@@ -1,110 +1,31 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
import sys
|
|
||||||
from typing import Any
|
|
||||||
|
|
||||||
import gi
|
import gi
|
||||||
|
|
||||||
from .models import VMBase
|
|
||||||
|
|
||||||
gi.require_version("Gtk", "3.0")
|
gi.require_version("Gtk", "3.0")
|
||||||
from gi.repository import Gio, Gtk
|
from gi.repository import Gio, Gtk
|
||||||
|
|
||||||
from .constants import constants
|
from .constants import constants
|
||||||
from .ui.clan_join_page import ClanJoinPage
|
|
||||||
from .ui.clan_select_list import ClanEdit, ClanList
|
|
||||||
|
|
||||||
|
|
||||||
class MainWindow(Gtk.ApplicationWindow):
|
|
||||||
def __init__(self, application: Gtk.Application) -> None:
|
|
||||||
super().__init__(application=application)
|
|
||||||
# Initialize the main window
|
|
||||||
self.set_title("cLAN Manager")
|
|
||||||
self.connect("delete-event", self.on_quit)
|
|
||||||
self.set_default_size(800, 600)
|
|
||||||
|
|
||||||
vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=6, expand=True)
|
|
||||||
self.add(vbox)
|
|
||||||
|
|
||||||
# Add a notebook layout
|
|
||||||
# https://python-gtk-3-tutorial.readthedocs.io/en/latest/layout.html#notebook
|
|
||||||
self.notebook = Gtk.Notebook()
|
|
||||||
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(
|
|
||||||
ClanJoinPage(stack=self.remount_list_view), "join", "Join"
|
|
||||||
)
|
|
||||||
self.stack.add_titled(
|
|
||||||
ClanEdit(remount_list=self.remount_list_view, selected_vm=None),
|
|
||||||
"edit",
|
|
||||||
"Edit",
|
|
||||||
)
|
|
||||||
|
|
||||||
vbox.add(self.stack)
|
|
||||||
|
|
||||||
# Must be called AFTER all components were added
|
|
||||||
self.show_all()
|
|
||||||
|
|
||||||
def set_selected(self, sel: VMBase | None) -> None:
|
|
||||||
self.selected_vm = sel
|
|
||||||
|
|
||||||
if self.selected_vm:
|
|
||||||
print(f"APP selected + {self.selected_vm.name}")
|
|
||||||
|
|
||||||
def remount_list_view(self) -> None:
|
|
||||||
widget = self.stack.get_child_by_name("list")
|
|
||||||
print("Remounting ClanListView")
|
|
||||||
if widget:
|
|
||||||
widget.destroy()
|
|
||||||
|
|
||||||
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 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(remount_list=self.remount_list_view, selected_vm=self.selected_vm),
|
|
||||||
"edit",
|
|
||||||
"Edit",
|
|
||||||
)
|
|
||||||
self.show_all()
|
|
||||||
self.stack.set_visible_child_name("edit")
|
|
||||||
|
|
||||||
def on_quit(self, *args: Any) -> None:
|
|
||||||
Gio.Application.quit(self.get_application())
|
|
||||||
|
|
||||||
|
|
||||||
class Application(Gtk.Application):
|
class Application(Gtk.Application):
|
||||||
def __init__(self) -> None:
|
def __init__(self, window: Gtk.ApplicationWindow) -> None:
|
||||||
super().__init__(
|
super().__init__(
|
||||||
application_id=constants["APPID"], flags=Gio.ApplicationFlags.FLAGS_NONE
|
application_id=constants["APPID"], flags=Gio.ApplicationFlags.FLAGS_NONE
|
||||||
)
|
)
|
||||||
self.init_style()
|
self.init_style()
|
||||||
|
self.window = window
|
||||||
|
|
||||||
def do_startup(self) -> None:
|
def do_startup(self) -> None:
|
||||||
Gtk.Application.do_startup(self)
|
Gtk.Application.do_startup(self)
|
||||||
Gtk.init(sys.argv)
|
Gtk.init()
|
||||||
|
|
||||||
def do_activate(self) -> None:
|
def do_activate(self) -> None:
|
||||||
win = self.props.active_window
|
win = self.props.active_window
|
||||||
if not win:
|
if not win:
|
||||||
# win = SwitchTreeView(application=self)
|
win = self.window
|
||||||
win = MainWindow(application=self)
|
win.set_application(self)
|
||||||
win.present()
|
win.present()
|
||||||
|
|
||||||
# TODO: For css styling
|
# TODO: For css styling
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from enum import Enum
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
@@ -13,22 +12,12 @@ from gi.repository import GdkPixbuf
|
|||||||
from clan_vm_manager import assets
|
from clan_vm_manager import assets
|
||||||
|
|
||||||
|
|
||||||
class Status(Enum):
|
|
||||||
OFF = "Off"
|
|
||||||
RUNNING = "Running"
|
|
||||||
# SUSPENDED = "Suspended"
|
|
||||||
# UNKNOWN = "Unknown"
|
|
||||||
|
|
||||||
def __str__(self) -> str:
|
|
||||||
return self.value
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
@dataclass(frozen=True)
|
||||||
class VMBase:
|
class VMBase:
|
||||||
icon: Path | GdkPixbuf.Pixbuf
|
icon: Path | GdkPixbuf.Pixbuf
|
||||||
name: str
|
name: str
|
||||||
url: str
|
url: str
|
||||||
status: Status
|
status: bool
|
||||||
_path: Path
|
_path: Path
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@@ -38,7 +27,7 @@ class VMBase:
|
|||||||
"Icon": GdkPixbuf.Pixbuf,
|
"Icon": GdkPixbuf.Pixbuf,
|
||||||
"Name": str,
|
"Name": str,
|
||||||
"URL": str,
|
"URL": str,
|
||||||
"Status": str,
|
"Online": bool,
|
||||||
"_Path": str,
|
"_Path": str,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -53,7 +42,7 @@ class VMBase:
|
|||||||
"Icon": str(self.icon),
|
"Icon": str(self.icon),
|
||||||
"Name": self.name,
|
"Name": self.name,
|
||||||
"URL": self.url,
|
"URL": self.url,
|
||||||
"Status": str(self.status),
|
"Online": self.status,
|
||||||
"_Path": str(self._path),
|
"_Path": str(self._path),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -92,7 +81,7 @@ def get_initial_vms(start: int = 0, end: int | None = None) -> list[VM]:
|
|||||||
name="Cybernet Clan",
|
name="Cybernet Clan",
|
||||||
url="clan://cybernet.lol",
|
url="clan://cybernet.lol",
|
||||||
_path=Path(__file__).parent.parent / "test_democlan",
|
_path=Path(__file__).parent.parent / "test_democlan",
|
||||||
status=Status.RUNNING,
|
status=False,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
VM(
|
VM(
|
||||||
@@ -101,7 +90,7 @@ def get_initial_vms(start: int = 0, end: int | None = None) -> list[VM]:
|
|||||||
name="Zenith Clan",
|
name="Zenith Clan",
|
||||||
url="clan://zenith.lol",
|
url="clan://zenith.lol",
|
||||||
_path=Path(__file__).parent.parent / "test_democlan",
|
_path=Path(__file__).parent.parent / "test_democlan",
|
||||||
status=Status.OFF,
|
status=False,
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
VM(
|
VM(
|
||||||
@@ -110,7 +99,7 @@ def get_initial_vms(start: int = 0, end: int | None = None) -> list[VM]:
|
|||||||
name="Firestorm Clan",
|
name="Firestorm Clan",
|
||||||
url="clan://firestorm.lol",
|
url="clan://firestorm.lol",
|
||||||
_path=Path(__file__).parent.parent / "test_democlan",
|
_path=Path(__file__).parent.parent / "test_democlan",
|
||||||
status=Status.OFF,
|
status=False,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
VM(
|
VM(
|
||||||
@@ -119,7 +108,7 @@ def get_initial_vms(start: int = 0, end: int | None = None) -> list[VM]:
|
|||||||
name="Placeholder Clan",
|
name="Placeholder Clan",
|
||||||
url="clan://demo.lol",
|
url="clan://demo.lol",
|
||||||
_path=Path(__file__).parent.parent / "test_democlan",
|
_path=Path(__file__).parent.parent / "test_democlan",
|
||||||
status=Status.OFF,
|
status=True,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
@@ -132,7 +121,7 @@ def get_initial_vms(start: int = 0, end: int | None = None) -> list[VM]:
|
|||||||
"name": "Demo Clan",
|
"name": "Demo Clan",
|
||||||
"url": "clan://demo.lol",
|
"url": "clan://demo.lol",
|
||||||
"_path": entry.path,
|
"_path": entry.path,
|
||||||
"status": Status.OFF,
|
"status": False,
|
||||||
}
|
}
|
||||||
vms.append(VM(base=VMBase(**new_vm)))
|
vms.append(VM(base=VMBase(**new_vm)))
|
||||||
|
|
||||||
|
|||||||
@@ -74,9 +74,9 @@ class ClanList(Gtk.Box):
|
|||||||
Is the composition of
|
Is the composition of
|
||||||
the ClanListToolbar
|
the ClanListToolbar
|
||||||
the clanListView
|
the clanListView
|
||||||
# ------------------------#
|
# ------------------------ #
|
||||||
# - Tools <Join> < Edit> #
|
# - Tools <Start> <Stop> < Edit> #
|
||||||
# ------------------------#
|
# ------------------------ #
|
||||||
# - List Items
|
# - List Items
|
||||||
# - <...>
|
# - <...>
|
||||||
# ------------------------#
|
# ------------------------#
|
||||||
@@ -89,12 +89,14 @@ class ClanList(Gtk.Box):
|
|||||||
remount_edit: Callable[[], None],
|
remount_edit: Callable[[], None],
|
||||||
set_selected: Callable[[VMBase | None], None],
|
set_selected: Callable[[VMBase | None], None],
|
||||||
selected_vm: VMBase | None,
|
selected_vm: VMBase | None,
|
||||||
|
show_toolbar: bool = True,
|
||||||
) -> None:
|
) -> None:
|
||||||
super().__init__(orientation=Gtk.Orientation.VERTICAL, expand=True)
|
super().__init__(orientation=Gtk.Orientation.VERTICAL, expand=True)
|
||||||
|
|
||||||
self.remount_edit_view = remount_edit
|
self.remount_edit_view = remount_edit
|
||||||
self.remount_list_view = remount_list
|
self.remount_list_view = remount_list
|
||||||
self.set_selected = set_selected
|
self.set_selected = set_selected
|
||||||
|
self.show_toolbar = show_toolbar
|
||||||
|
|
||||||
# TODO: We should use somekind of useState hook here.
|
# TODO: We should use somekind of useState hook here.
|
||||||
# that updates the list of VMs when the user changes something
|
# that updates the list of VMs when the user changes something
|
||||||
@@ -109,9 +111,10 @@ class ClanList(Gtk.Box):
|
|||||||
"on_stop_clicked": self.on_stop_clicked,
|
"on_stop_clicked": self.on_stop_clicked,
|
||||||
"on_edit_clicked": self.on_edit_clicked,
|
"on_edit_clicked": self.on_edit_clicked,
|
||||||
}
|
}
|
||||||
self.toolbar = ClanListToolbar(**button_hooks)
|
if show_toolbar:
|
||||||
self.toolbar.set_is_selected(self.selected_vm is not None)
|
self.toolbar = ClanListToolbar(**button_hooks)
|
||||||
self.add(self.toolbar)
|
self.toolbar.set_is_selected(self.selected_vm is not None)
|
||||||
|
self.add(self.toolbar)
|
||||||
|
|
||||||
self.list_hooks = {
|
self.list_hooks = {
|
||||||
"on_select_row": self.on_select_vm,
|
"on_select_row": self.on_select_vm,
|
||||||
@@ -134,10 +137,11 @@ class ClanList(Gtk.Box):
|
|||||||
|
|
||||||
def on_select_vm(self, vm: VMBase) -> None:
|
def on_select_vm(self, vm: VMBase) -> None:
|
||||||
print(f"on_select_vm: {vm.name}")
|
print(f"on_select_vm: {vm.name}")
|
||||||
if vm is None:
|
if self.show_toolbar:
|
||||||
self.toolbar.set_is_selected(False)
|
if vm is None:
|
||||||
else:
|
self.toolbar.set_is_selected(False)
|
||||||
self.toolbar.set_is_selected(True)
|
else:
|
||||||
|
self.toolbar.set_is_selected(True)
|
||||||
|
|
||||||
self.set_selected(vm)
|
self.set_selected(vm)
|
||||||
self.selected_vm = vm
|
self.selected_vm = vm
|
||||||
@@ -153,10 +157,14 @@ class ClanListToolbar(Gtk.Toolbar):
|
|||||||
) -> None:
|
) -> None:
|
||||||
super().__init__(orientation=Gtk.Orientation.HORIZONTAL)
|
super().__init__(orientation=Gtk.Orientation.HORIZONTAL)
|
||||||
|
|
||||||
self.start_button = Gtk.ToolButton(label="Join")
|
self.start_button = Gtk.ToolButton(label="Start")
|
||||||
self.start_button.connect("clicked", on_start_clicked)
|
self.start_button.connect("clicked", on_start_clicked)
|
||||||
self.add(self.start_button)
|
self.add(self.start_button)
|
||||||
|
|
||||||
|
self.stop_button = Gtk.ToolButton(label="Stop")
|
||||||
|
self.stop_button.connect("clicked", on_stop_clicked)
|
||||||
|
self.add(self.stop_button)
|
||||||
|
|
||||||
self.edit_button = Gtk.ToolButton(label="Edit")
|
self.edit_button = Gtk.ToolButton(label="Edit")
|
||||||
self.edit_button.connect("clicked", on_edit_clicked)
|
self.edit_button.connect("clicked", on_edit_clicked)
|
||||||
self.add(self.edit_button)
|
self.add(self.edit_button)
|
||||||
@@ -165,9 +173,11 @@ class ClanListToolbar(Gtk.Toolbar):
|
|||||||
if s:
|
if s:
|
||||||
self.edit_button.set_sensitive(True)
|
self.edit_button.set_sensitive(True)
|
||||||
self.start_button.set_sensitive(True)
|
self.start_button.set_sensitive(True)
|
||||||
|
self.stop_button.set_sensitive(True)
|
||||||
else:
|
else:
|
||||||
self.edit_button.set_sensitive(False)
|
self.edit_button.set_sensitive(False)
|
||||||
self.start_button.set_sensitive(False)
|
self.start_button.set_sensitive(False)
|
||||||
|
self.stop_button.set_sensitive(False)
|
||||||
|
|
||||||
|
|
||||||
class ClanEditToolbar(Gtk.Toolbar):
|
class ClanEditToolbar(Gtk.Toolbar):
|
||||||
@@ -224,7 +234,6 @@ class ClanListView(Gtk.Box):
|
|||||||
return
|
return
|
||||||
selection = self.tree_view.get_selection()
|
selection = self.tree_view.get_selection()
|
||||||
idx = self.find_vm(vm)
|
idx = self.find_vm(vm)
|
||||||
print(f"Set selected vm: {vm.name} at {idx}")
|
|
||||||
selection.select_path(idx)
|
selection.select_path(idx)
|
||||||
|
|
||||||
def insertVM(self, vm: VMBase) -> None:
|
def insertVM(self, vm: VMBase) -> None:
|
||||||
@@ -239,7 +248,6 @@ class ClanListView(Gtk.Box):
|
|||||||
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])
|
||||||
print(f"Selected {vm.name}")
|
|
||||||
self.on_select_row(vm)
|
self.on_select_row(vm)
|
||||||
|
|
||||||
def _on_double_click(
|
def _on_double_click(
|
||||||
@@ -259,13 +267,18 @@ def setColRenderers(tree_view: Gtk.TreeView) -> None:
|
|||||||
|
|
||||||
if key.startswith("_"):
|
if key.startswith("_"):
|
||||||
continue
|
continue
|
||||||
match gtype:
|
|
||||||
case GdkPixbuf.Pixbuf:
|
if issubclass(gtype, GdkPixbuf.Pixbuf):
|
||||||
renderer = Gtk.CellRendererPixbuf()
|
renderer = Gtk.CellRendererPixbuf()
|
||||||
col = Gtk.TreeViewColumn(key, renderer, pixbuf=idx)
|
col = Gtk.TreeViewColumn(key, renderer, pixbuf=idx)
|
||||||
case str: # noqa
|
elif issubclass(gtype, bool):
|
||||||
renderer = Gtk.CellRendererText()
|
renderer = Gtk.CellRendererToggle()
|
||||||
col = Gtk.TreeViewColumn(key, renderer, text=idx)
|
col = Gtk.TreeViewColumn(key, renderer, active=idx)
|
||||||
|
elif issubclass(gtype, str):
|
||||||
|
renderer = Gtk.CellRendererText()
|
||||||
|
col = Gtk.TreeViewColumn(key, renderer, text=idx)
|
||||||
|
else:
|
||||||
|
raise Exception(f"Unknown type: {gtype}")
|
||||||
|
|
||||||
# CommonSetup for all columns
|
# CommonSetup for all columns
|
||||||
if col:
|
if col:
|
||||||
|
|||||||
48
pkgs/clan-vm-manager/clan_vm_manager/windows/join.py
Normal file
48
pkgs/clan-vm-manager/clan_vm_manager/windows/join.py
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
import argparse
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
import gi
|
||||||
|
from clan_cli.clan_uri import ClanURI
|
||||||
|
|
||||||
|
from ..app import Application
|
||||||
|
|
||||||
|
gi.require_version("Gtk", "3.0")
|
||||||
|
from gi.repository import Gio, Gtk
|
||||||
|
|
||||||
|
|
||||||
|
class JoinWindow(Gtk.ApplicationWindow):
|
||||||
|
def __init__(self) -> None:
|
||||||
|
super().__init__()
|
||||||
|
# Initialize the main window
|
||||||
|
self.set_title("cLAN Manager")
|
||||||
|
self.connect("delete-event", self.on_quit)
|
||||||
|
self.set_default_size(800, 600)
|
||||||
|
|
||||||
|
vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=6, expand=True)
|
||||||
|
self.add(vbox)
|
||||||
|
|
||||||
|
# Add a notebook layout
|
||||||
|
# https://python-gtk-3-tutorial.readthedocs.io/en/latest/layout.html#notebook
|
||||||
|
self.notebook = Gtk.Notebook()
|
||||||
|
self.stack = Gtk.Stack()
|
||||||
|
|
||||||
|
self.stack.add_titled(Gtk.Label("Join cLan"), "join", "Join")
|
||||||
|
|
||||||
|
vbox.add(self.stack)
|
||||||
|
|
||||||
|
# Must be called AFTER all components were added
|
||||||
|
self.show_all()
|
||||||
|
|
||||||
|
def on_quit(self, *args: Any) -> None:
|
||||||
|
Gio.Application.quit(self.get_application())
|
||||||
|
|
||||||
|
|
||||||
|
def show_join(args: argparse.Namespace) -> None:
|
||||||
|
print(f"Joining clan {args.clan_uri}")
|
||||||
|
app = Application(JoinWindow())
|
||||||
|
return app.run()
|
||||||
|
|
||||||
|
|
||||||
|
def register_join_parser(parser: argparse.ArgumentParser) -> None:
|
||||||
|
parser.add_argument("clan_uri", type=ClanURI, help="clan URI to join")
|
||||||
|
parser.set_defaults(func=show_join)
|
||||||
97
pkgs/clan-vm-manager/clan_vm_manager/windows/overview.py
Normal file
97
pkgs/clan-vm-manager/clan_vm_manager/windows/overview.py
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
from typing import Any
|
||||||
|
|
||||||
|
import gi
|
||||||
|
|
||||||
|
from ..models import VMBase
|
||||||
|
|
||||||
|
gi.require_version("Gtk", "3.0")
|
||||||
|
import argparse
|
||||||
|
|
||||||
|
from gi.repository import Gio, Gtk
|
||||||
|
|
||||||
|
from ..app import Application
|
||||||
|
from ..ui.clan_join_page import ClanJoinPage
|
||||||
|
from ..ui.clan_select_list import ClanEdit, ClanList
|
||||||
|
|
||||||
|
|
||||||
|
class OverviewWindow(Gtk.ApplicationWindow):
|
||||||
|
def __init__(self) -> None:
|
||||||
|
super().__init__()
|
||||||
|
# Initialize the main window
|
||||||
|
self.set_title("cLAN Manager")
|
||||||
|
self.connect("delete-event", self.on_quit)
|
||||||
|
self.set_default_size(800, 600)
|
||||||
|
|
||||||
|
vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=6, expand=True)
|
||||||
|
self.add(vbox)
|
||||||
|
|
||||||
|
# Add a notebook layout
|
||||||
|
# https://python-gtk-3-tutorial.readthedocs.io/en/latest/layout.html#notebook
|
||||||
|
self.notebook = Gtk.Notebook()
|
||||||
|
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(
|
||||||
|
ClanJoinPage(stack=self.remount_list_view), "join", "Join"
|
||||||
|
)
|
||||||
|
self.stack.add_titled(
|
||||||
|
ClanEdit(remount_list=self.remount_list_view, selected_vm=None),
|
||||||
|
"edit",
|
||||||
|
"Edit",
|
||||||
|
)
|
||||||
|
|
||||||
|
vbox.add(self.stack)
|
||||||
|
|
||||||
|
# Must be called AFTER all components were added
|
||||||
|
self.show_all()
|
||||||
|
|
||||||
|
def set_selected(self, sel: VMBase | None) -> None:
|
||||||
|
self.selected_vm = sel
|
||||||
|
|
||||||
|
if self.selected_vm:
|
||||||
|
print(f"APP selected + {self.selected_vm.name}")
|
||||||
|
|
||||||
|
def remount_list_view(self) -> None:
|
||||||
|
widget = self.stack.get_child_by_name("list")
|
||||||
|
print("Remounting ClanListView")
|
||||||
|
if widget:
|
||||||
|
widget.destroy()
|
||||||
|
|
||||||
|
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 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(remount_list=self.remount_list_view, selected_vm=self.selected_vm),
|
||||||
|
"edit",
|
||||||
|
"Edit",
|
||||||
|
)
|
||||||
|
self.show_all()
|
||||||
|
self.stack.set_visible_child_name("edit")
|
||||||
|
|
||||||
|
def on_quit(self, *args: Any) -> None:
|
||||||
|
Gio.Application.quit(self.get_application())
|
||||||
|
|
||||||
|
|
||||||
|
def show_overview(args: argparse.Namespace) -> None:
|
||||||
|
app = Application(OverviewWindow())
|
||||||
|
return app.run()
|
||||||
|
|
||||||
|
|
||||||
|
def register_overview_parser(parser: argparse.ArgumentParser) -> None:
|
||||||
|
parser.set_defaults(func=show_overview)
|
||||||
Reference in New Issue
Block a user