5.10. Ajout de la prise en charge de l'interface utilisateur graphique (GUI) Add-on
Cette section décrit comment ajouter un support à l'interface utilisateur graphique (GUI) de votre module complémentaire en effectuant les étapes de haut niveau suivantes :
- Définir les attributs requis pour la classe Normalspoke
-
Définir les méthodes
__init__
etinitialize
-
Définir les méthodes
refresh
,apply
etexecute
-
Définir les propriétés
status
etready
,completed
etmandatory
Conditions préalables
- Votre module complémentaire inclut la prise en charge de Kickstart. Voir la structure des modules complémentaires Anaconda.
-
Installez les paquets anaconda-widgets et anaconda-widgets-devel, qui contiennent des widgets Gtk spécifiques à
Anaconda
, tels queSpokeWindow
.
Procédure
- Créez les modules suivants avec toutes les définitions requises pour ajouter la prise en charge de l'interface utilisateur graphique (GUI) Add-on, conformément aux exemples suivants.
Exemple 5.4. Définition des attributs requis pour la classe Normalspoke :
# will never be translated _ = lambda x: x N_ = lambda x: x # the path to addons is in sys.path so we can import things from org_fedora_hello_world from org_fedora_hello_world.gui.categories.hello_world import HelloWorldCategory from pyanaconda.ui.gui.spokes import NormalSpoke # export only the spoke, no helper functions, classes or constants all = ["HelloWorldSpoke"] class HelloWorldSpoke(FirstbootSpokeMixIn, NormalSpoke): """ Class for the Hello world spoke. This spoke will be in the Hello world category and thus on the Summary hub. It is a very simple example of a unit for the Anaconda's graphical user interface. Since it is also inherited form the FirstbootSpokeMixIn, it will also appear in the Initial Setup (successor of the Firstboot tool). :see: pyanaconda.ui.common.UIObject :see: pyanaconda.ui.common.Spoke :see: pyanaconda.ui.gui.GUIObject :see: pyanaconda.ui.common.FirstbootSpokeMixIn :see: pyanaconda.ui.gui.spokes.NormalSpoke """ # class attributes defined by API # # list all top-level objects from the .glade file that should be exposed # to the spoke or leave empty to extract everything builderObjects = ["helloWorldSpokeWindow", "buttonImage"] # the name of the main window widget mainWidgetName = "helloWorldSpokeWindow" # name of the .glade file in the same directory as this source uiFile = "hello_world.glade" # category this spoke belongs to category = HelloWorldCategory # spoke icon (will be displayed on the hub) # preferred are the -symbolic icons as these are used in Anaconda's spokes icon = "face-cool-symbolic" # title of the spoke (will be displayed on the hub) title = N_("_HELLO WORLD")
L'attribut __all__
exporte la classe spoke
, suivie des premières lignes de sa définition, y compris les définitions des attributs mentionnés précédemment dans les caractéristiques de base du module d'extension GUI. Les valeurs de ces attributs font référence aux widgets définis dans le fichier com_example_hello_world/gui/spokes/hello.glade
. Deux autres attributs notables sont présents :
-
category
dont la valeur est importée de la classeHelloWorldCategory
du modulecom_example_hello_world.gui.categories
. L'attributHelloWorldCategory
indique que le chemin d'accès aux modules complémentaires se trouve danssys.path
, de sorte que les valeurs peuvent être importées à partir du paquetcom_example_hello_world
. L'attributcategory
fait partie du nomN_ function
, qui marque la chaîne pour la traduction, mais renvoie la version non traduite de la chaîne, car la traduction a lieu à un stade ultérieur. -
title
qui contient un trait de soulignement dans sa définition. Le trait de soulignement de l'attributtitle
marque le début du titre lui-même et permet d'atteindre le rayon en utilisant le raccourci clavierAlt H
.
Ce qui suit généralement l'en-tête de la définition de la classe et les définitions de la classe attributes
est le constructeur qui initialise une instance de la classe. Dans le cas des objets de l'interface graphique Anaconda, il existe deux méthodes d'initialisation d'une nouvelle instance : la méthode __init__
et la méthode initialize
.
La raison pour laquelle il existe deux fonctions de ce type est que les objets GUI peuvent être créés en mémoire à un moment donné et entièrement initialisés à un autre moment, l'initialisation de spoke
pouvant prendre beaucoup de temps. Par conséquent, la méthode __init__
ne doit appeler que la méthode __init__
du parent et, par exemple, initialiser les attributs non GUI. D'autre part, la méthode initialize
qui est appelée lors de l'initialisation de l'interface graphique de l'installateur doit terminer l'initialisation complète des rayons.
Dans l'exemple Hello World add-on
, définissez ces deux méthodes comme suit. Notez le nombre et la description des arguments transmis à la méthode __init__
.
Exemple 5.5. Définition des méthodes __init__
et initialize :
def __init__(self, data, storage, payload): """ :see: pyanaconda.ui.common.Spoke.init :param data: data object passed to every spoke to load/store data from/to it :type data: pykickstart.base.BaseHandler :param storage: object storing storage-related information (disks, partitioning, bootloader, etc.) :type storage: blivet.Blivet :param payload: object storing packaging-related information :type payload: pyanaconda.packaging.Payload """ NormalSpoke.init(self, data, storage, payload) self._hello_world_module = HELLO_WORLD.get_proxy() def initialize(self): """ The initialize method that is called after the instance is created. The difference between init and this method is that this may take a long time and thus could be called in a separate thread. :see: pyanaconda.ui.common.UIObject.initialize """ NormalSpoke.initialize(self) self._entry = self.builder.get_object("textLines") self._reverse = self.builder.get_object("reverseCheckButton")
Le paramètre data transmis à la méthode __init__
est la représentation arborescente en mémoire du fichier Kickstart où toutes les données sont stockées. Dans l'une des méthodes __init__
des ancêtres, il est stocké dans l'attribut self.data
, ce qui permet à toutes les autres méthodes de la classe de lire et de modifier la structure.
Le module storage object
n'est plus utilisable depuis RHEL9. Si votre module complémentaire doit interagir avec la configuration du stockage, utilisez le module Storage DBus
.
La classe HelloWorldData ayant déjà été définie dans l'exemple du module complémentaire Hello World, il existe déjà un sous-arbre dans self.data pour ce module complémentaire. Sa racine, une instance de la classe, est disponible à l'adresse self.data.addons.com_example_hello_world
.
Une autre action de l'ancêtre __init__
est d'initialiser une instance de GtkBuilder avec le fichier spokeâs .glade
et de le stocker en tant que self.builder
. La méthode initialize
l'utilise pour obtenir le fichier GtkTextEntry
utilisé pour afficher et modifier le texte de la section don du fichier kickstart.
Les méthodes __init__
et initialize
sont toutes deux importantes lors de la création du rayon. Cependant, le rôle principal du rayon est d'être visité par un utilisateur qui souhaite modifier ou revoir les valeurs du rayon. Pour ce faire, trois autres méthodes sont disponibles :
-
refresh
- appelée lorsque le rayon est sur le point d'être visité ; cette méthode rafraîchit l'état du rayon, principalement ses éléments d'interface utilisateur, pour s'assurer que les données affichées correspondent aux structures de données internes et, par conséquent, pour s'assurer que les valeurs actuelles stockées dans la structure self.data sont affichées. -
apply
- appelée lorsque le rayon est quitté et utilisée pour stocker les valeurs des éléments de l'interface utilisateur dans la structureself.data
. -
execute
- appelé lorsque les utilisateurs quittent le rayon et utilisé pour effectuer tout changement d'exécution basé sur le nouvel état du rayon.
Ces fonctions sont mises en œuvre dans l'exemple de module complémentaire Hello World de la manière suivante :
Exemple 5.6. Définition des méthodes d'actualisation, d'application et d'exécution
def refresh(self): """ The refresh method that is called every time the spoke is displayed. It should update the UI elements according to the contents of internal data structures. :see: pyanaconda.ui.common.UIObject.refresh """ lines = self._hello_world_module.Lines self._entry.get_buffer().set_text("".join(lines)) reverse = self._hello_world_module.Reverse self._reverse.set_active(reverse) def apply(self): """ The apply method that is called when user leaves the spoke. It should update the D-Bus service with values set in the GUI elements. """ buf = self._entry.get_buffer() text = buf.get_text(buf.get_start_iter(), buf.get_end_iter(), True) lines = text.splitlines(True) self._hello_world_module.SetLines(lines) self._hello_world_module.SetReverse(self._reverse.get_active()) def execute(self): """ The execute method that is called when the spoke is exited. It is supposed to do all changes to the runtime environment according to the values set in the GUI elements. """ # nothing to do here pass
Vous pouvez utiliser plusieurs méthodes supplémentaires pour contrôler l'état des rayons :
-
ready
- détermine si le rayon est prêt à être visité ; si la valeur est "False", le rayonspoke
n'est pas accessible, par exemple, le rayonPackage Selection
avant qu'une source de paquets ne soit configurée. -
completed
- détermine si le rayon a été achevé. -
mandatory
- détermine si le rayon est obligatoire ou non, par exemple le rayonInstallation Destination
, qui doit toujours être visité, même si vous voulez utiliser le partitionnement automatique.
Tous ces attributs doivent être déterminés de manière dynamique en fonction de l'état actuel du processus d'installation.
Vous trouverez ci-dessous un exemple de mise en œuvre de ces méthodes dans le module complémentaire Hello World, qui exige qu'une certaine valeur soit définie dans l'attribut text de la classe HelloWorldData
:
Exemple 5.7. Définir les méthodes "prêt", "complété" et "obligatoire
@property
def ready(self):
"""
The ready property
reports whether the spoke is ready, that is, can be visited
or not. The spoke is made (in)sensitive based on the returned value of the ready
property.
:rtype: bool
"""
# this spoke is always ready
return True
@property
def mandatory(self):
"""
The mandatory property that tells whether the spoke is mandatory to be
completed to continue in the installation process.
:rtype: bool
"""
# this is an optional spoke that is not mandatory to be completed
return False
Une fois ces propriétés définies, le rayon peut contrôler son accessibilité et son exhaustivité, mais il ne peut pas fournir un résumé des valeurs configurées à l'intérieur - vous devez visiter le rayon pour voir comment il est configuré, ce qui peut ne pas être souhaité. C'est pourquoi il existe une propriété supplémentaire appelée status
. Cette propriété contient une seule ligne de texte avec un bref résumé des valeurs configurées, qui peut ensuite être affiché dans le hub sous le titre du rayon.
La propriété "status" est définie comme suit dans l'exemple de module complémentaire Hello World
:
Exemple 5.8. Définition de la propriété status
@property def status(self): """ The status property that is a brief string describing the state of the spoke. It should describe whether all values are set and if possible also the values themselves. The returned value will appear on the hub below the spoke's title. :rtype: str """ lines = self._hello_world_module.Lines if not lines: return _("No text added") elif self._hello_world_module.Reverse: return _("Text set with {} lines to reverse").format(len(lines)) else: return _("Text set with {} lines").format(len(lines))
Après avoir défini toutes les propriétés décrites dans les exemples, le module complémentaire permet d'afficher une interface utilisateur graphique (GUI) ainsi que Kickstart.
L'exemple présenté ici est très simple et ne contient aucun contrôle ; des connaissances en programmation Python Gtk sont nécessaires pour développer un rayon fonctionnel et interactif dans l'interface graphique.
Une restriction notable est que chaque rayon doit avoir sa propre fenêtre principale - une instance du widget SpokeWindow
. Ce widget, ainsi que d'autres widgets spécifiques à Anaconda, se trouvent dans le paquetage anaconda-widgets
. Vous trouverez d'autres fichiers nécessaires au développement de modules complémentaires avec prise en charge de l'interface graphique, tels que les définitions de Glade
, dans le paquetage anaconda-widgets-devel
.
Une fois que votre module de support de l'interface graphique contient toutes les méthodes nécessaires, vous pouvez continuer avec la section suivante pour ajouter le support de l'interface utilisateur textuelle, ou vous pouvez continuer avec Déployer et tester un module complémentaire Anaconda et tester le module complémentaire.