diff --git a/.gitignore b/.gitignore
index e7f41543..222f9bef 100644
--- a/.gitignore
+++ b/.gitignore
@@ -10,6 +10,7 @@ result*
/pkgs/clan-cli/clan_cli/webui/assets
/machines
nixos.qcow2
+**/*.glade~
# python
__pycache__
diff --git a/pkgs/clan-vm-manager/README.md b/pkgs/clan-vm-manager/README.md
new file mode 100644
index 00000000..f20a57b7
--- /dev/null
+++ b/pkgs/clan-vm-manager/README.md
@@ -0,0 +1,61 @@
+## Developing GTK3 Applications
+
+Here we will document on how to develop GTK3 application UI in python. First we want to setup
+an example code base to look into. In this case gnome-music.
+
+## Setup gnome-music as code reference
+
+gnome-music does not use glade
+
+Clone gnome-music and check out the tag v40.0
+[gnome-music](https://github.com/GNOME/gnome-music/tree/40.0)
+
+```bash
+git clone git@github.com:GNOME/gnome-music.git && cd gnome-music && git checkout 40.0
+```
+
+Checkout nixpkgs version `468cb5980b56d348979488a74a9b5de638400160` for the correct gnome-music devshell then execute:
+
+```bash
+
+nix develop /home/username/Projects/nixpkgs#gnome.gnome-music
+```
+
+Look into the file `gnome-music.in` which bootstraps the application.
+
+## Setup gnu-cash as reference
+
+Gnucash uses glade with complex UI
+Setup gnucash
+
+```bash
+git clone git@github.com:Gnucash/gnucash.git
+git checkout ed4921271c863c7f6e0c800e206b25ac6e9ba4da
+
+cd nixpkgs
+git checkout 015739d7bffa7da4e923978040a2f7cba6af3270
+nix develop /home/username/Projects/nixpkgs#gnucash
+mkdir build && cd build
+cmake ..
+cd ..
+make
+```
+
+### Glade
+
+Make sure to check the 'composit' box in glade in the GtkApplicationWindow to be able to
+import the glade file through GTK template
+
+## Links
+
+- Another python glade project [syncthing-gtk](https://github.com/kozec/syncthing-gtk)
+
+- Other python glade project [linuxcnc](https://github.com/podarok/linuxcnc/tree/master)
+
+- Install [Glade UI Toolbuilder](https://gitlab.gnome.org/GNOME/glade)
+
+- To understand GTK3 Components look into the [Python GTK3 Tutorial](https://python-gtk-3-tutorial.readthedocs.io/en/latest/search.html?q=ApplicationWindow&check_keywords=yes&area=default)
+
+- Also look into [PyGObject](https://pygobject.readthedocs.io/en/latest/guide/gtk_template.html) to know more about threading and async etc.
+- [GI Python API](https://lazka.github.io/pgi-docs/#Gtk-4.0/classes/ApplicationWindow.html#Gtk.ApplicationWindow)
+- https://developer.gnome.org/documentation/tutorials/application.html
diff --git a/pkgs/clan-vm-manager/clan_vm_manager/app.glade b/pkgs/clan-vm-manager/clan_vm_manager/app.glade
index 65a645d0..e42a9b31 100644
--- a/pkgs/clan-vm-manager/clan_vm_manager/app.glade
+++ b/pkgs/clan-vm-manager/clan_vm_manager/app.glade
@@ -2,8 +2,9 @@
-
-
+
diff --git a/pkgs/clan-vm-manager/clan_vm_manager/app.py b/pkgs/clan-vm-manager/clan_vm_manager/app.py
index 0122b9df..9b6f76db 100644
--- a/pkgs/clan-vm-manager/clan_vm_manager/app.py
+++ b/pkgs/clan-vm-manager/clan_vm_manager/app.py
@@ -1,19 +1,105 @@
-# !/usr/bin/env python3
+#!/usr/bin/env python3
-import argparse # noqa
-from pathlib import Path # noqa
+import argparse
+from pathlib import Path
-import gi # noqa
+import gi
-gi.require_version("Gtk", "3.0") # noqa
-from gi.repository import Gtk # noqa
+gi.require_version("Gtk", "3.0")
+from gi.repository import Gio, Gtk
+
+glade_dir = Path(__file__).parent
+
+
+#
+# 1. our .glade file (may contain paths)
+#
+@Gtk.Template.from_file(glade_dir / "app.glade")
+class AppWindow(Gtk.ApplicationWindow):
+ #
+ # 2. the GtkApplicationWindow class
+ #
+ __gtype_name__ = "main-window"
+
+ #
+ # 3. the Button name we saved above
+ #
+ help_button: Gtk.Button = Gtk.Template.Child()
+ next_button: Gtk.Button = Gtk.Template.Child()
+
+ @Gtk.Template.Callback()
+ def onDestroy(self, _):
+ Gio.Application.quit(self.get_application())
+
+ #
+ # 4. the signal handler name we saved above
+ #
+ @Gtk.Template.Callback()
+ def onButtonPressed(self, widget, **_kwargs):
+ assert self.help_button == widget
+ print(widget.get_label())
+
+ @Gtk.Template.Callback()
+ def onNextButtonPressed(self, widget, **_kwargs):
+ assert self.next_button == widget
+ # Hide the first window
+ self.hide()
+
+ # Show the second window
+ self.get_application().window2.show_all()
+
+
+# Decorate the second window class with the template
+@Gtk.Template.from_file(glade_dir / "second.glade")
+class SecondWindow(Gtk.ApplicationWindow):
+ #
+ # the GtkApplicationWindow class
+ #
+ __gtype_name__ = "second-window"
+
+ # import the button from the template with name 'back_button'
+ back_button: Gtk.Button = Gtk.Template.Child()
+
+ @Gtk.Template.Callback()
+ def onDestroy(self, _):
+ Gio.Application.quit(self.get_application())
+
+ #
+ # 'onBackButtonPressed' is the name of the signal handler we saved in glade
+ #
+ @Gtk.Template.Callback()
+ def onBackButtonPressed(self, widget, **_kwargs):
+ assert self.back_button == widget
+ # Hide the second window
+ self.hide()
+ # Show the first window
+ self.get_application().window1.show_all()
+
+
+class Application(Gtk.Application):
+ def __init__(self, *args, **kwargs):
+ super().__init__(
+ *args,
+ application_id="clan.lol.Gtk1",
+ flags=Gio.ApplicationFlags.FLAGS_NONE,
+ **kwargs,
+ )
+ self.window = None
+
+ def do_activate(self):
+ # Load the first window from the template
+ self.window1 = AppWindow(application=self)
+ # Add the first window to the application
+ self.add_window(self.window1)
+ # Show the first window
+ self.window1.show_all()
+
+ # Load the second window from the template
+ self.window2 = SecondWindow(application=self)
+ # Add the second window to the application
+ self.add_window(self.window2)
def start_app(args: argparse.Namespace) -> None:
- builder = Gtk.Builder()
- glade_file = Path(__file__).parent / "app.glade"
- builder.add_from_file(str(glade_file))
- window = builder.get_object("main-window")
- window.show_all()
-
- Gtk.main()
+ app = Application()
+ app.run()
diff --git a/pkgs/clan-vm-manager/clan_vm_manager/second.glade b/pkgs/clan-vm-manager/clan_vm_manager/second.glade
new file mode 100644
index 00000000..d4f556fd
--- /dev/null
+++ b/pkgs/clan-vm-manager/clan_vm_manager/second.glade
@@ -0,0 +1,44 @@
+
+
+
+
+
+ False
+
+
+
+ True
+ False
+
+
+ go back
+ 100
+ 80
+ True
+ True
+ True
+
+
+
+ 348
+ 201
+
+
+
+
+ button
+ 100
+ 80
+ True
+ True
+ True
+
+
+ 71
+ 53
+
+
+
+
+
+
diff --git a/pkgs/clan-vm-manager/pyproject.toml b/pkgs/clan-vm-manager/pyproject.toml
index 0ec43fce..77276d4a 100644
--- a/pkgs/clan-vm-manager/pyproject.toml
+++ b/pkgs/clan-vm-manager/pyproject.toml
@@ -25,4 +25,4 @@ ignore_missing_imports = true
line-length = 88
select = ["E", "F", "I", "N"]
-ignore = ["E501", "E402"]
+ignore = ["E501", "E402", "N802"]