diff options
| -rw-r--r-- | .gitignore | 3 | ||||
| -rw-r--r-- | README.md | 66 | ||||
| -rw-r--r-- | examples/Main.qml | 26 | ||||
| -rw-r--r-- | examples/hello_quick.py | 22 | ||||
| -rw-r--r-- | examples/hello_widgets.py | 43 | ||||
| -rw-r--r-- | flake.lock | 27 | ||||
| -rw-r--r-- | flake.nix | 45 |
7 files changed, 232 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..84c7d25 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +__pycache__/ +result +.direnv/ diff --git a/README.md b/README.md new file mode 100644 index 0000000..826a6c1 --- /dev/null +++ b/README.md @@ -0,0 +1,66 @@ +# Qt for Python development environment + +Environnement PySide6 (binding Qt officiel) sur Python 3.13, versions +alignées avec le C++ de `~/projects/qt-dev` (Qt 6.11). + +## Entrer dans l'environnement + +```bash +cd ~/projects/qt-python-dev +nix develop +``` + +## Lancer les exemples + +```bash +python examples/hello_widgets.py # app Widgets (signals/slots) +python examples/hello_quick.py # app QML/Qt Quick +``` + +Pas de compilation : Python + QML chargé à l'exécution. Itération +instantanée — c'est l'avantage principal sur le C++. + +## Outils disponibles + +| Commande | Rôle | +|----------|------| +| `designer` | éditeur de fichiers .ui | +| `uic -g python fichier.ui -o ui_fichier.py` | compiler un .ui en module Python | +| `rcc -g python ressources.qrc -o rc.py` | compiler des ressources | +| `linguist` | traduction (.ts) | +| `assistant` | doc offline | + +Note : le paquet nixpkgs ne fournit pas les wrappers `pyside6-uic`/ +`pyside6-rcc` de PyPI — mais `uic`/`rcc` de qtbase (sur le PATH du +shell) génèrent du Python nativement avec `-g python`, c'est le même +résultat. + +Workflow Designer typique : dessiner dans `designer`, générer avec +`uic -g python`, importer la classe générée dans son code. + +## Documentation + +- Doc Qt offline dans `$QT_DOCS` (enregistrer dans Assistant : + Préférences → Documentation → Ajouter). +- Doc PySide6 spécifique (API Python) : https://doc.qt.io/qtforpython-6/ +- La doc C++ reste utile : l'API Python est un mapping quasi 1:1. + +## PySide6 vs PyQt6 + +Les deux sont dans nixpkgs (`python3Packages.pyside6` / `.pyqt6`), +mêmes versions. PySide6 choisi ici : binding **officiel** Qt, licence +LGPL (PyQt6 est GPL/commercial). API quasi identique — migration +triviale dans les deux sens (`Signal` vs `pyqtSignal`, etc.). + +## Notes NixOS + +- Les **plugins de plateforme** sont trouvés automatiquement (chemins + compilés dans le Qt de nixpkgs), mais **pas les imports QML** : sans + le `QML_IMPORT_PATH` exporté par le shellHook, tout `import QtQuick` + échoue avec « module is not installed ». +- **Ne pas faire `pip install pyside6`** dans un venv sur NixOS : le + wheel PyPI embarque ses propres libs Qt liées contre une glibc/ABI + générique — segfaults et conflits garantis. Toujours passer par + `python3Packages.pyside6`. +- Session Wayland GNOME : fonctionne nativement ; forcer X11 si besoin + avec `QT_QPA_PLATFORM=xcb`. diff --git a/examples/Main.qml b/examples/Main.qml new file mode 100644 index 0000000..ed85a81 --- /dev/null +++ b/examples/Main.qml @@ -0,0 +1,26 @@ +import QtQuick +import QtQuick.Controls + +ApplicationWindow { + visible: true + width: 320 + height: 200 + title: "Hello PySide6 Quick" + + property int count: 0 + + Column { + anchors.centerIn: parent + spacing: 12 + + Text { + text: "Clics : " + count + anchors.horizontalCenter: parent.horizontalCenter + } + + Button { + text: "Cliquer" + onClicked: count++ + } + } +} diff --git a/examples/hello_quick.py b/examples/hello_quick.py new file mode 100644 index 0000000..0ffa9f2 --- /dev/null +++ b/examples/hello_quick.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python3 +"""Minimal PySide6 Qt Quick app: loads Main.qml next to this script.""" + +import sys +from pathlib import Path + +from PySide6.QtGui import QGuiApplication +from PySide6.QtQml import QQmlApplicationEngine + + +def main() -> int: + app = QGuiApplication(sys.argv) + + engine = QQmlApplicationEngine() + engine.objectCreationFailed.connect(lambda: sys.exit(1)) + engine.load(Path(__file__).parent / "Main.qml") + + return app.exec() + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/examples/hello_widgets.py b/examples/hello_widgets.py new file mode 100644 index 0000000..926fff6 --- /dev/null +++ b/examples/hello_widgets.py @@ -0,0 +1,43 @@ +#!/usr/bin/env python3 +"""Minimal PySide6 Widgets app: a counter demonstrating signals/slots.""" + +import sys + +from PySide6.QtWidgets import ( + QApplication, + QLabel, + QPushButton, + QVBoxLayout, + QWidget, +) + + +def main() -> int: + app = QApplication(sys.argv) + + window = QWidget() + window.setWindowTitle("Hello PySide6") + + label = QLabel("Clics : 0") + button = QPushButton("Cliquer") + + count = 0 + + def on_click() -> None: + nonlocal count + count += 1 + label.setText(f"Clics : {count}") + + button.clicked.connect(on_click) + + layout = QVBoxLayout(window) + layout.addWidget(label) + layout.addWidget(button) + + window.resize(240, 100) + window.show() + return app.exec() + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..458bca5 --- /dev/null +++ b/flake.lock @@ -0,0 +1,27 @@ +{ + "nodes": { + "nixpkgs": { + "locked": { + "lastModified": 1783389287, + "narHash": "sha256-0xIy4dVLqq47rA+mRy0hXDfjhQd4E5PoIns/RmB7nR4=", + "owner": "Nixos", + "repo": "nixpkgs", + "rev": "0ad6f47ea4fe188f4bc8f0380f93ae8523337c6c", + "type": "github" + }, + "original": { + "owner": "Nixos", + "ref": "nixos-26.05", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "nixpkgs": "nixpkgs" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..40e09a0 --- /dev/null +++ b/flake.nix @@ -0,0 +1,45 @@ +{ + description = "Qt for Python development environment (PySide6, nixos-26.05)"; + + inputs = { + nixpkgs.url = "github:Nixos/nixpkgs/nixos-26.05"; + }; + + outputs = + { self, nixpkgs }: + let + system = "x86_64-linux"; + pkgs = nixpkgs.legacyPackages.${system}; + + python = pkgs.python3.withPackages ( + p: with p; [ + pyside6 + ] + ); + in + { + formatter.${system} = pkgs.nixfmt; + + devShells.${system}.default = pkgs.mkShell { + packages = [ + python + pkgs.qt6.qttools # designer, linguist, assistant + pkgs.qt6.qtdoc # offline documentation for Assistant + ]; + + # Platform plugins are found automatically (compiled-in store + # paths), but QML imports are NOT: without this export, any + # "import QtQuick" fails with "module is not installed". + shellHook = '' + # uic/rcc (with Python codegen via -g python) live in libexec, + # not bin, so they are not on PATH by default. + export PATH=${pkgs.qt6.qtbase}/libexec:$PATH + export QML_IMPORT_PATH=${pkgs.qt6.qtdeclarative}/lib/qt-6/qml + export QML2_IMPORT_PATH=$QML_IMPORT_PATH + export QT_DOCS=${pkgs.qt6.qtdoc}/share/doc + echo "PySide6 $(python -c 'import PySide6; print(PySide6.__version__)') sur Python $(python -V | cut -d' ' -f2)" + echo "Exemples dans ./examples — voir README.md" + ''; + }; + }; +} |
