|
|
## **Qt**, un outil complet avec gestion des signaux et internationalisation
|
|
|
Auteur : Sylvain Coulibaly, Août 2022
|
|
|
|
|
|
[TOC]
|
|
|
|
|
|
### Pour bien commencer : installer Qt, Qt Designer et Qt Linguist dans un environnement virtuel
|
|
|
**ref** : openclassroom ; créer un environnement virtuel
|
|
|
:warning: à compléter rappler ici les instructions pour créer et travailler avec un environnement virtuel, en attendant, voir [cette page](Python, gestion des versions de Python des paquets)
|
|
|
|
|
|
Il existe deux possibilités : **PyQt5** et **PySide2** ; le premier dispose de la communauté la plus importante.
|
|
|
La version 6 de PyQt est sortie (ainsi que son équivalent PySide6...) mais là encore il faut noter que la communauté et le volume de tutoriels est encore en faveur de PyQt**5**.
|
|
|
|
|
|
Créer un environnement virtuel avec venv.
|
|
|
|
|
|
Une fois l'environnement activé, y installer **PyQt5**.
|
|
|
- pip install PyQt5
|
|
|
|
|
|
:warning: la procédure d'installation des outils diffère selon les tutos ! Problème de version de python et des paquets, encore et toujours. Voir les posts les plus RECENTS de [ce fil de discussion](https://stackoverflow.com/questions/42090739/pyqt5-how-to-install-run-qt-designer)
|
|
|
, PyQT5 et son "package tools" : vous trouverez des liens pour installer QT Designer et Qt Linguist indépendamment ("standalone") ou via pip... avec deux commandes (alternatives ou complémentaires ?:question:?... )
|
|
|
|
|
|
- pip install pyqt5-tools
|
|
|
- pip install pyqt5 pyqt5-tools
|
|
|
|
|
|
Pour vérifier, il faut chercher `designer.exe` et `linguist.exe`.
|
|
|
Sur mon PC, ils sont das `C:\Users\christine.poulard\Desktop\TestTranslate\Venv\Lib\site-packages\qt5_applications\Qt\bin`.
|
|
|
C'est là que se trouvent également `lrelease`et `lupdate`.
|
|
|
|
|
|
Pour tester l'insertion d'une figure matplotlib :
|
|
|
- pip install matplotlib
|
|
|
|
|
|
A noter que pour les plugins **QGIs** les modules PyQT et QT Designer sont déjà fournis avec QGis, et ce sont eux qu'il faut utiliser. QTDesigner contient d'ailleurs des objets spécifiques à QGis.
|
|
|
|
|
|
### Les objets vus depuis Qt Designer, création et export d'une interface statique
|
|
|
Tous les objets héritent d'une classe QTObject.
|
|
|
|
|
|
Les objets sont nombreux et leur propriétés foisonnantes !
|
|
|
Attention, car une fois le formulaire défini, il est facile d'y revenir et d'AJOUTER des objets, mais en revanche remplacer un type d'objet par un autre va induire de nombreuses manips dans le code.
|
|
|
|
|
|
|
|
|
#### Les objets
|
|
|
Les noms de classes commencent tous par un Q. On peut distinguer, dès l'entrée dans l'interface :
|
|
|
- des objets qui peuvent être définis sans être rattachés à un autre objet : QMainWindow, QWidget ("form"), QMessageBox... Il faut donc commencer par un de ces objets dans QTDesigner.
|
|
|
- des objets qui ne peuvent être créés que dans un conteneur préexistants.
|
|
|
|
|
|
L'objet QMessageBox est l'un des objets qui n'a pas besoin d'un objet préexistant : on peut donc définir une QMessageBox indépendante, et temporaire, pour afficher un message d'erreur.
|
|
|
|
|
|
Cela amène à distinguer :
|
|
|
- les **conteneurs**, sur lesquels on peut "poser" des objets, éventuellement d'autres conteneurs (group box, tool box, tab widget...) ;
|
|
|
- des **objets élémentaires** : boutons (pushbutton, radiobutton, toolbutton...), des formulaires éditables (spinbox, textbox...)
|
|
|
- des **fonctionnalités** qui vont aider à la mise en forme, éventuellement en définissant des objets : layout, splitter, spacers...
|
|
|
|
|
|
On remarque qu'il existe des "doublons" apparents dans une catégorie **Item Views (Model Based)** et **Item Widgets (Item Based)** : TableView/TableWidget, ListView/ListWidget...
|
|
|
Les Objets Q*Widgets sont munis de plus de fonctions, et sont donc plus faciles à mettre en oeuvre.
|
|
|
Ce sont des spécialisations de Q*View. S'il y a besoin de fortement personnaliser, il vaut donc mieux partir de Q*View.
|
|
|
|
|
|
Les **QMainWindow** sont les seuls conteneurs que l'on peut munir de menus et toolbars, avec des QActionbutton dont l'évenement est trigger. A part cette limitation, un QWidget possède exactement les mêmes propriétés.
|
|
|
|
|
|
#### Les propriétés des objets
|
|
|
L'**inspecteur d'objets**, en haut à droite done des informations sur l'objet sélectionné.
|
|
|
En dessous, une liste interminable de propriétés permet de choisir des options : enable / width / color... ces propriétés sont en partie communes aux objets mais d'autres sont spécifiques.
|
|
|
Il faut s'approprier petit à petit les principales propriétés des objets, pour identifier celles qui seront utiles.
|
|
|
|
|
|
#### La mise en page
|
|
|
les boutons "layout" permettent de ranger une sélection multiple en ligne et/ou en colonne dans une grille. Là encore, il faut un peu d'habitude.
|
|
|
Les propriétés des objets permettront de définir ce qui se passe quand le conteneur sera agrrandi ou diminué en cours d'exécution par l'utilisateur : on peut fixer par objet une taille minimale, une taille maximale, et éviter des "blancs".
|
|
|
Les "spacers" agissent comme des intermédiaires entre un objet ou un conteneur et un autre conteneur : ils vont maintenir le premier objet collé à un ou deux côtés (par exemple : l'objet est maintenu aligné à droite avec l'autre).
|
|
|
S'il y a deux spacers opposés, l'effet sera de centrer.*
|
|
|
Un "splitter" doit être défini sur une sélection de 2 objets. Ils seront munis d'une "frontière commune" : quand l'un des objets sera agrandi, l'autre diminuera d'autant en conséquence.
|
|
|
|
|
|
#### L'export : fichier UI
|
|
|
QT Designer génère un fichier binaire **.UI**.
|
|
|
Ce fichier peut être importé dans un projet en tant que fichier UI ou après conversion en script Python :
|
|
|
`python -m PyQt5.uic.pyuic -x nom_ui.ui -o nom_ui.py`
|
|
|
|
|
|
L'outil PyQt5.uic.pyuic est disponible si on a importé pyqt5-tools.
|
|
|
|
|
|
|
|
|
:warning-sign: /!\ ne PAS modifier ce fichier *.py issu de la conversion du *.ui car sinon on ne peut pas repartir du UI sans tout perdre
|
|
|
=> le cas échéant, écrire les modifs ailleurs que dans ce *.py
|
|
|
|
|
|
|
|
|
### Récupérer le fichier et la rendre dynamique : associer des actions aux événements
|
|
|
Il faut ensuite importer le fichier ui ou le script python dans son projet, et le setupper via du code un peu "boilerplate", càd à recopier gentiment.
|
|
|
|
|
|
Notions importantes :
|
|
|
* associer un événement à une action : notion de **signaux**, avec la fonction **emit** et le décorateur **@pyQTslot** pour surcharger la fonction appelée.
|
|
|
* les tooltips définies dans le code (dans QTDesigner aussi ?) permettent de fournir plus d’infos sur les objets / donner des définitions/ décrires les fonctions dispo…
|
|
|
* puisque QT propose un objet QMessageBox, on peut en créer un pour afficher un message d'erreur.
|
|
|
|
|
|
|
|
|
### Internationaliser avec Qt Linguist
|
|
|
**ref** openclassroom traduisez avec Qt Linguist
|
|
|
|
|
|
Il y a trois étapes obligatoires, et une troisième recommandée.
|
|
|
|
|
|
#### Pour résumer :
|
|
|
* étape 1 : dans le code, repérer chaque texte qui devra être traduit
|
|
|
* étape 2, recommandée si certains textes sont employés dans plusieurs modules : regrouper dans un module "constant_strings" la définition de toutes les chaînes récurrentes, avec un nom de variable.
|
|
|
Il suffira ensuite dans chaque script de se référer au nom de variable. Ainsi, la traduction ne sera nécessaire qu'une seule fois, ce qui facilite le travail et assure la cohérence.
|
|
|
* étape 3 : dans Qt Linguist, qui aura récupérer toutes les chaînes à traduire, rentrer manuellement les traductions dans toutes les langues désirées.
|
|
|
* étape 4 : prévoir dans les scripts du projet l'action qui déclenchera le changement de langue.
|
|
|
|
|
|
#### Détail des étapes
|
|
|
##### **étape 1** :
|
|
|
Préciser les chaînes de caractères à traduire à l’aide de la méthode `translate` de QCoreApplication.
|
|
|
```python
|
|
|
from PyQt5.QtCore import QtCoreApplication
|
|
|
_translate = QtCoreApplication.translate # appel via alias méthode spéciale, recommandé
|
|
|
name = _translate("MainView", "Name")
|
|
|
pk = _translate("MainView", "pK", "curvilinear coordinate measured on the flow axis")
|
|
|
```
|
|
|
|
|
|
La méthode `translate`prend quatre arguments, dont deux obligatoires:
|
|
|
* le **Context** est un mot-clef utilisateur, qui servira à QTLinguist pour structurer la base de données
|
|
|
* la chaîne de caractères à traduire
|
|
|
* un commentaire pour aider le traducteur (préciser le contexte, lever une ambiguité...)
|
|
|
* éventuellement, déclarer comment l'expression peut être mise au pluriel : le défaut est n=-1, pas de gestion du pluriel.
|
|
|
|
|
|
```python
|
|
|
variable_name = _translate(Contexte, chaîne de caractères, commentaire pour le traducteur,n=-1)
|
|
|
print (_translate("Avertissements", "attention, c'est un nombre qui est demandé"))
|
|
|
```
|
|
|
|
|
|
##### **étape 2**: créer un module `constant_strings.py` qui sera complété au fur et à mesure de l'écriture du code
|
|
|
|
|
|
Si on ajoute une chaîne (par exemple : "bonjour !") dont on sait qu'elle sera utilisée de manière interchangeable dans plusieurs fichiers, on ajoute un nom de variable:
|
|
|
`MESSAGE_OUVERTURE = _translate("Salutations", "bonjour !")`
|
|
|
|
|
|
Dans chaque script qui utilise l'une de ces chaînes à traduire ainsi "factorisée", on appellera la chaîne via son nom de variable, après avoir importé le fichier py
|
|
|
```python
|
|
|
import constant_strings.py as cs
|
|
|
print(cs.MESSAGE_OUVERTURE) # accès à la chaîne 'traductible' via son nom de variable
|
|
|
```
|
|
|
##### **étape 3**: extraire les informations sur les chaînes à traduire pour QT Linguist
|
|
|
On va créer un fichier .ts (par langue).
|
|
|
La commande qui permet de créer un fichier .ts pour une langue est la suivante :
|
|
|
`pylupdate5 file_1.py ... file_n.py -ts en-fr.ts`
|
|
|
Où :
|
|
|
- file_1.py à file_n.py sont les n fichiers du projet qui contiennent des chaînes de caractères à traduire,
|
|
|
- en-fr.ts est le nom de fichier de traduction (c’est du xml), ici pour indiquer la traduction anglais => français.
|
|
|
N.B : Le tiret du 6 (-) entre en et fr est très fortement conseillé.
|
|
|
|
|
|
On peut ensuite importer chaque fichier .ts dans QT Linguist, et choisir la langue.
|
|
|
Les chaînes à traduire dont organisées grâce aux mot-clés de contexte. On peut faire défiler les expressions à traduire par contexte.
|
|
|
Le traducteur voit les commentaires éventuels du développeur, dans la langue d'origine. Il ne reste plus qu'à remplir les champs nécessaires dans QT Linguist : traduction dans la langue de destination, ici dans la case Translation to français (France), et commentaire éventuel.
|
|
|
|
|
|
|
|
|
![image](uploads/5fbe316bb4923611db98128b6159b4c5/image.png)
|
|
|
|
|
|
- ensuite, exporter un binaire .qm à charger dans l’application
|
|
|
Une fois le .ts entièrement traduit, il ne nous reste plus qu'à le compiler dans le format
|
|
|
final binaire .qm, et à le charger dans l'application.
|
|
|
La commande à utiliser pour la création tu fichier .qm est la suivante :
|
|
|
`lrelease en-fr.ts en-fr.qm`
|
|
|
|
|
|
##### **étape 4** : mettre en place une ‘façon’ de changer la langue de son application
|
|
|
|
|
|
Voir un exemple dans les modules `main_view.py` et `controller.py` dans le dépôt.
|
|
|
L’idée générale consiste à déclencher un **signal** lorsqu’on demande à la langue (en
|
|
|
cliquant sur un bouton ou en sélectionnant un item d’une QComBoBox, …) et ensuite charger
|
|
|
le bon fichier .qm de traduction.
|
|
|
|
|
|
```python
|
|
|
@pyqtSlot(str)
|
|
|
def change_func(self, language):
|
|
|
if language == FRENCH:
|
|
|
self.trans.load("en-fr")
|
|
|
QApplication.instance().installTranslator(self.trans)
|
|
|
elif language == ENGLISH:
|
|
|
QApplication.instance().removeTranslator(self.trans)
|
|
|
``` |
|
|
\ No newline at end of file |