forked from clan/clan-core
Added basic GTK window handling
This commit is contained in:
parent
bb7f92f9aa
commit
6df833d59b
1
.gitignore
vendored
1
.gitignore
vendored
@ -10,6 +10,7 @@ result*
|
|||||||
/pkgs/clan-cli/clan_cli/webui/assets
|
/pkgs/clan-cli/clan_cli/webui/assets
|
||||||
/machines
|
/machines
|
||||||
nixos.qcow2
|
nixos.qcow2
|
||||||
|
**/*.glade~
|
||||||
|
|
||||||
# python
|
# python
|
||||||
__pycache__
|
__pycache__
|
||||||
|
61
pkgs/clan-vm-manager/README.md
Normal file
61
pkgs/clan-vm-manager/README.md
Normal file
@ -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
|
@ -2,8 +2,9 @@
|
|||||||
<!-- Generated with glade 3.40.0 -->
|
<!-- Generated with glade 3.40.0 -->
|
||||||
<interface>
|
<interface>
|
||||||
<requires lib="gtk+" version="3.24"/>
|
<requires lib="gtk+" version="3.24"/>
|
||||||
<object class="GtkApplicationWindow" id="main-window">
|
<template class="main-window" parent="GtkApplicationWindow">
|
||||||
<property name="can-focus">False</property>
|
<property name="can-focus">False</property>
|
||||||
|
<signal name="destroy" handler="onDestroy" swapped="no"/>
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkFixed">
|
<object class="GtkFixed">
|
||||||
<property name="name">asdasd</property>
|
<property name="name">asdasd</property>
|
||||||
@ -19,7 +20,7 @@
|
|||||||
<property name="can-focus">True</property>
|
<property name="can-focus">True</property>
|
||||||
<property name="receives-default">True</property>
|
<property name="receives-default">True</property>
|
||||||
<property name="image-position">top</property>
|
<property name="image-position">top</property>
|
||||||
<signal name="clicked" handler="on_help_button_clicked" object="coffee_label" swapped="no"/>
|
<signal name="clicked" handler="onButtonPressed" swapped="no"/>
|
||||||
</object>
|
</object>
|
||||||
<packing>
|
<packing>
|
||||||
<property name="x">21</property>
|
<property name="x">21</property>
|
||||||
@ -40,10 +41,25 @@
|
|||||||
<property name="y">120</property>
|
<property name="y">120</property>
|
||||||
</packing>
|
</packing>
|
||||||
</child>
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkButton" id="next_button">
|
||||||
|
<property name="label" translatable="yes">Next</property>
|
||||||
|
<property name="width-request">79</property>
|
||||||
|
<property name="height-request">39</property>
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can-focus">True</property>
|
||||||
|
<property name="receives-default">True</property>
|
||||||
|
<signal name="clicked" handler="onNextButtonPressed" swapped="no"/>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="x">355</property>
|
||||||
|
<property name="y">190</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
<style>
|
<style>
|
||||||
<class name="asdasd"/>
|
<class name="asdasd"/>
|
||||||
</style>
|
</style>
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
</object>
|
</template>
|
||||||
</interface>
|
</interface>
|
||||||
|
@ -1,19 +1,105 @@
|
|||||||
# !/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
import argparse # noqa
|
import argparse
|
||||||
from pathlib import Path # noqa
|
from pathlib import Path
|
||||||
|
|
||||||
import gi # noqa
|
import gi
|
||||||
|
|
||||||
gi.require_version("Gtk", "3.0") # noqa
|
gi.require_version("Gtk", "3.0")
|
||||||
from gi.repository import Gtk # noqa
|
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:
|
def start_app(args: argparse.Namespace) -> None:
|
||||||
builder = Gtk.Builder()
|
app = Application()
|
||||||
glade_file = Path(__file__).parent / "app.glade"
|
app.run()
|
||||||
builder.add_from_file(str(glade_file))
|
|
||||||
window = builder.get_object("main-window")
|
|
||||||
window.show_all()
|
|
||||||
|
|
||||||
Gtk.main()
|
|
||||||
|
44
pkgs/clan-vm-manager/clan_vm_manager/second.glade
Normal file
44
pkgs/clan-vm-manager/clan_vm_manager/second.glade
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!-- Generated with glade 3.40.0 -->
|
||||||
|
<interface>
|
||||||
|
<requires lib="gtk+" version="3.24"/>
|
||||||
|
<template class="second-window" parent="GtkApplicationWindow">
|
||||||
|
<property name="can-focus">False</property>
|
||||||
|
<signal name="destroy" handler="onDestroy" swapped="no"/>
|
||||||
|
<child>
|
||||||
|
<object class="GtkFixed">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can-focus">False</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkButton" id="back_button">
|
||||||
|
<property name="label" translatable="yes">go back</property>
|
||||||
|
<property name="width-request">100</property>
|
||||||
|
<property name="height-request">80</property>
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can-focus">True</property>
|
||||||
|
<property name="receives-default">True</property>
|
||||||
|
<signal name="clicked" handler="onBackButtonPressed" swapped="no"/>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="x">348</property>
|
||||||
|
<property name="y">201</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkButton">
|
||||||
|
<property name="label" translatable="yes">button</property>
|
||||||
|
<property name="width-request">100</property>
|
||||||
|
<property name="height-request">80</property>
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can-focus">True</property>
|
||||||
|
<property name="receives-default">True</property>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="x">71</property>
|
||||||
|
<property name="y">53</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
</template>
|
||||||
|
</interface>
|
@ -25,4 +25,4 @@ ignore_missing_imports = true
|
|||||||
line-length = 88
|
line-length = 88
|
||||||
|
|
||||||
select = ["E", "F", "I", "N"]
|
select = ["E", "F", "I", "N"]
|
||||||
ignore = ["E501", "E402"]
|
ignore = ["E501", "E402", "N802"]
|
||||||
|
Loading…
Reference in New Issue
Block a user