Added basic GTK window handling
All checks were successful
checks-impure / test (pull_request) Successful in 1m32s
checks / test (pull_request) Successful in 2m38s

This commit is contained in:
Luis Hebendanz 2023-11-25 01:55:01 +01:00
parent bb7f92f9aa
commit 6df833d59b
6 changed files with 225 additions and 17 deletions

1
.gitignore vendored
View File

@ -10,6 +10,7 @@ result*
/pkgs/clan-cli/clan_cli/webui/assets
/machines
nixos.qcow2
**/*.glade~
# python
__pycache__

View 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

View File

@ -2,8 +2,9 @@
<!-- Generated with glade 3.40.0 -->
<interface>
<requires lib="gtk+" version="3.24"/>
<object class="GtkApplicationWindow" id="main-window">
<template class="main-window" parent="GtkApplicationWindow">
<property name="can-focus">False</property>
<signal name="destroy" handler="onDestroy" swapped="no"/>
<child>
<object class="GtkFixed">
<property name="name">asdasd</property>
@ -19,7 +20,7 @@
<property name="can-focus">True</property>
<property name="receives-default">True</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>
<packing>
<property name="x">21</property>
@ -40,10 +41,25 @@
<property name="y">120</property>
</packing>
</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>
<class name="asdasd"/>
</style>
</object>
</child>
</object>
</template>
</interface>

View File

@ -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()

View 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>

View File

@ -25,4 +25,4 @@ ignore_missing_imports = true
line-length = 88
select = ["E", "F", "I", "N"]
ignore = ["E501", "E402"]
ignore = ["E501", "E402", "N802"]