Guide de l'utilisateur - Bibliothèque CWidgets V0.1.2a1 :

Introduction

CWidgets : Votre puissance en programmation sans limites
Transformez les obstacles Qt en opportunités créatives
CWidgets est une bibliothèque Python spécialisée qui donne aux développeurs malvoyants un contrôle total sur les interfaces PyQt6 et PySide6.
L'ère des limitations avec QTextEdit est révolue. Désormais, vous pouvez concevoir des interfaces avec des zones de texte multi-lignes en toute liberté et compatibilité.
La bibliothèque offre une compatibilité absolue pour les développeurs malvoyants, car tous les éléments sont conçus pour fonctionner parfaitement avec le lecteur d'écran NVDA.
Créativité continue : Pas besoin d'apprendre de nouveaux outils, continuez simplement à écrire le code auquel vous êtes habitué tout en conservant les mêmes fonctions et caractéristiques.
Ingénierie intelligente : Chaque élément de CWidgets hérite des propriétés des éléments Qt originaux, garantissant des performances standard avec des solutions radicales aux problèmes de compatibilité.
Qu'est-ce qui changera dans la façon dont vous écrivez vos programmes en utilisant les fenêtres PyQT6 et PySide6 ?
Tout ce que vous devez faire est de remplacer la première lettre seulement (C au lieu de Q) :
CTextEdit au lieu de QTextEdit.
CButton au lieu de QPushButton.
Où C signifie Custom (personnalisé) ou customized.
Flexibilité totale dans le travail, car la bibliothèque supporte votre environnement de travail préféré avec la même efficacité :

Avec PySide6 :
from cwidgets.pyside6 import CButton, CLabel, CLineEdit....

Avec PyQt6 :
from cwidgets.pyqt6 import CButton, CLabel, CLineEdit .....

CWidgets : Codez en toute confiance, concevez sans limites.

Composants disponibles :

7 éléments personnalisés entièrement compatibles fournis par cette bibliothèque :

  1. CTextEdit
  2. CButton
  3. CLabel
  4. CLineEdit
  5. CComboBox
  6. CListWidget
  7. CMessageBox

Installation

pip install cwidgets

Accès à l'aide

Après l'installation, commencez à explorer la bibliothèque, ses composants, propriétés et leur utilisation grâce à ces fonctions :

import cwidgets

# Lister tous les composants disponibles
cwidgets.widgets()

# Lister toutes les sections disponibles
cwidgets.sections()

# Ouvrir le guide complet dans le navigateur
cwidgets.show_help()
cwidgets.show_help(lang="fr")

# Ouvrir un composant spécifique directement
cwidgets.show_help(lang="fr", goto="CButton")
cwidgets.show_help(lang="fr", goto="CTextEdit")

# Ouvrir une section spécifique directement
cwidgets.show_help(lang="fr", goto="introduction")
cwidgets.show_help(lang="fr", goto="installation")

Problèmes courants résolus

#CTextEdit : Zone de texte multi-lignes : CTextEdit résout le problème fondamental de QTextEdit, qui est incompatible avec NVDA.

#CComboBox & CListWidget : Séparation de la navigation et de l'activation : Séparer les actions de navigation (flèches) des actions d'activation (Entrée/Espace) pour éviter les activations involontaires. Désactivation disponible : Maintien de l'annonce par NVDA même lorsque le composant est désactivé.

#CButton : Activation avec ENTRÉE, RETOUR, ESPACE et la souris. Compatibilité même en cas de désactivation.

#CLabel : Compatibilité améliorée pour les titres et étiquettes

#CLineEdit : Permet de récupérer le texte depuis l'élément en appuyant sur Entrée sans aucun code supplémentaire.

#CMessageBox : Boîtes de dialogue de message à fermeture automatique. Vous pouvez spécifier un délai après lequel la boîte de dialogue disparaît automatiquement.

#À l'exception de CTextEdit, tous les autres composants héritent de QT et conservent donc toutes leurs propriétés et fonctions de base.

Détails des composants :

Définition, création, propriétés, utilisation.

CTextEdit

Définition

CTextEdit est une zone de texte multi-lignes entièrement compatible avec NVDA.

Pourquoi — Le problème fondamental

C'est la barrière qui empêche les développeurs malvoyants de concevoir des interfaces QT contenant des zones de texte multi-lignes.
Jusqu'à l'apparition de cette bibliothèque, aucune solution à ce problème n'était connue pour être utilisée par les développeurs malvoyants.

La solution — RichEdit Win32 intégré dans Qt

La solution apportée par cette bibliothèque est d'intégrer un contrôle Win32 RichEdit directement dans la fenêtre QT.
Win32 RichEdit est entièrement compatible avec NVDA par défaut.
La phase complexe dans la conception de la solution est son intégration dans une fenêtre Qt tout en maintenant cette accessibilité.
Cette bibliothèque a également assuré l'utilisation par défaut des commandes dans QT.
Structure de l'élément QTextEdit :
-QTextEdit
-EditorStyle — Styles, police, couleur, alignement
-RichEdit Win32 — Le moteur original compatible

Fonctionnalités

Utilisation

    # PySide6
    from cwidgets.pyside6 import CTextEdit
    # PyQt6
    from cwidgets.pyqt6 import CTextEdit

    # Création
    self.editor = CTextEdit(self, accessible_name="Nom de la zone")
    layout.addWidget(self.editor)

    # Insérer du texte dans la zone
    self.editor.setText("Bonjour !")
    # Récupérer le texte de la zone :
    text = self.editor.text()
    text = self.editor.toPlainText()
    # Ajouter du texte au texte original
    self.editor.append("Nouvelle ligne.")
    # Effacer le texte pour vider la zone
    self.editor.clear()

    # Rendre la zone en lecture seule
    self.editor.setReadOnly(True)
    # Désactiver la lecture seule pour rendre la zone modifiable et lisible
    self.editor.setReadOnly(False)

    # Police — QFont ou (str, int, bool, bool)
    self.editor.setFont("Arial", 12, True, False)  # Nom, taille, gras, italique

    # Couleurs — nom ou ensemble RGB
    self.editor.setTextColor("red")
    self.editor.setTextColor((255, 0, 0))
    self.editor.setBackgroundColor("yellow")

    # Alignement
    self.editor.setAlignment("left")
    self.editor.setAlignment("center")
    self.editor.setAlignment("right")

Couleurs disponibles

    # Noms supportés
    "black", "white", "red", "green", "blue", "yellow",
    "cyan", "magenta", "gray", "darkgray", "lightgray",
    "orange", "purple", "violet", "pink", "brown",
    "navy", "teal", "lime", "olive", "maroon",
    "coral", "salmon", "gold", "silver"

    # ou ensemble RGB
    (255, 0, 0)    # rouge
    (0, 128, 255)  # bleu clair

Titre visuel optionnel

accessible_name est lu par NVDA mais n'est pas visible visuellement.
Pour ajouter un titre visuel, ajoutez un CLabel au layout avant l'éditeur :

    self.editor = CTextEdit(self)
    self.lbl    = CLabel("Zone d'édition :", self, self.editor)
    Il est essentiel de noter que l'ajout de CLabel au layout doit précéder l'ajout de l'éditeur pour que le nom de la zone apparaisse au-dessus de la zone et non en dessous.
    layout.addWidget(self.lbl)
    layout.addWidget(self.editor)

CLabel

Définition

CLabel est une étiquette compatible avec NVDA qui remplace QLabel.

Pourquoi ?

Le QLabel original est invisible pour NVDA sauf s'il est lié à un buddy — et seulement lorsque ce buddy a le focus.

Solution

Deux modes :
- Mode simple : Compatible avec NVDA et reconnu même avec la navigation par tabulation.
- Mode Buddy : Lorsque CLabel est lié à un autre élément.

Fonctionnalités

Utilisation

    # PySide6
    from cwidgets.pyside6 import CLabel, CLineEdit
    # PyQt6
    from cwidgets.pyqt6 import CLabel, CLineEdit

    # Mode simple — NVDA lit le texte et peut être accédé via tabulation :
    self.lbl = CLabel("Fichier enregistré", self)

    # Avec prefix — NVDA annonce : "statut Fichier enregistré"
    self.lbl = CLabel("Fichier enregistré", self, prefix="statut")

    # Mode Buddy — NVDA annonce l'étiquette lorsque le champ obtient le focus
    # L'étiquette doit être ajoutée au layout avant le champ de saisie pour que l'étiquette apparaisse visuellement au-dessus du champ
    self.edit = CLineEdit(self)
    self.lbl  = CLabel("Nom :", self, self.edit)
    layout.addWidget(self.lbl)
    layout.addWidget(self.edit)

    # Mise à jour dynamique
    self.lbl.setText("Traitement en cours")
    self.lbl.setPrefix("Erreur")

CButton

Définition

CButton est un bouton avec des fonctionnalités supplémentaires qui le rendent utilisable sans avoir besoin de lignes de code supplémentaires que le bouton original nécessiterait pour activer ces fonctionnalités.

Pourquoi ?

Solution

Fonctionnalités

Utilisation

    # PySide6
    from cwidgets.pyside6 import CButton
    # PyQt6
    from cwidgets.pyqt6 import CButton

    self.btn = CButton("Enregistrer", self)
    self.btn.clicked.connect(self.on_click)

    # Désactiver — NVDA annonce "indisponible", le bouton n'est pas cliquable
    self.btn.setEnabled(False)
    self.btn.setEnabled(True)

    # Changer le titre — Qt original
    self.btn.setText("Nouveau titre")

    # Vérifier l'état
    if self.btn.isEnabled():
        ...

CLineEdit

Définition

CLineEdit est un champ de saisie de texte compatible qui remplace QLineEdit.

Pourquoi ?

QLineEdit nécessite une ligne de code supplémentaire pour récupérer le texte.
CLineEdit rend cette activation automatique via le signal validated.

Fonctionnalités

Utilisation

    # PySide6
    from cwidgets.pyside6 import CLineEdit, CLabel
    # PyQt6
    from cwidgets.pyqt6 import CLineEdit, CLabel

    # Créer le champ en premier pour le buddy
    self.edit = CLineEdit(self)
    self.lbl  = CLabel("Nom :", self, self.edit)
    layout.addWidget(self.lbl)
    layout.addWidget(self.edit)

    # Avec texte initial
    self.edit = CLineEdit(self, "Le Caire")

    # Avec texte d'espace réservé
    self.edit = CLineEdit(self, placeholderText="Entrez votre nom...")

    # Signal validated
    self.edit.validated.connect(self.on_validated)

    def on_validated(self, text):
        print(text)

    # Afficher/masquer — Qt original
    self.edit.hide()
    self.edit.show()

CComboBox

Définition

CComboBox est une liste déroulante compatible qui remplace QComboBox.

Pourquoi ?

Le QComboBox original est activé en interne par les flèches.
C'est un problème pour les utilisateurs malvoyants qui naviguent avec les flèches.

Solution

Séparation explicite entre la navigation et l'activation.
Tout en maintenant la compatibilité même lorsque la liste est désactivée.

Fonctionnalités

Utilisation

    # PySide6
    from cwidgets.pyside6 import CComboBox, CLabel, CMessageBox, CButton
    # PyQt6
    from cwidgets.pyqt6 import CComboBox, CLabel, CMessageBox, CButton

    # Créer la liste en premier pour le buddy
    self.combo = CComboBox(self)
    self.combo.addItems(["Égypte", "Tunisie", "Maroc"])
    self.lbl = CLabel("Liste des pays :", self, self.combo)
    layout.addWidget(self.lbl)
    layout.addWidget(self.combo)

    self.combo.validated.connect(self.on_selection)
    self.combo.cleared.connect(self.on_cleared)

    # Bouton pour vider la liste
    self.btn_clear = CButton("Vider", self)
    self.btn_clear.clicked.connect(self.combo.clear)

    def on_selection(self):
        text  = self.combo.currentText()
        index = self.combo.currentIndex()
        CMessageBox.information(self, "Sélection", f"Pays : {text}")

    def on_cleared(self):
        CMessageBox.warning(self, "Avertissement", "Aucun élément disponible dans la liste.")

    # Désactiver / réactiver
    self.combo.setEnabled(False)
    self.combo.setEnabled(True)

CListWidget

Définition

CListWidget est une liste compatible qui remplace QListWidget.

Pourquoi ?

Le QListWidget original est activé immédiatement lors de la navigation avec les flèches.
C'est un obstacle pour les utilisateurs malvoyants, et désactiver cette activation avec les flèches nécessite l'écriture de lignes de code supplémentaires.

Solution

Fonctionnalités

Utilisation

    # PySide6
    from cwidgets.pyside6 import CListWidget, CLabel
    # PyQt6
    from cwidgets.pyqt6 import CListWidget, CLabel

    # Créer la liste
    self.list = CListWidget(self)
    self.list.addItems(["Irak", "Arabie Saoudite", "Koweït"])
    self.lbl = CLabel("Liste des pays :", self, self.list)
    layout.addWidget(self.lbl)
    layout.addWidget(self.list)

    # Signal Qt original — correspondant à QListWidget
    self.list.itemActivated.connect(self.on_item)

    def on_item(self, item):
        text = item.text()
        row  = self.list.currentRow()
        print(row, text)

    # Désactiver / réactiver
    self.list.setEnabled(False)
    self.list.setEnabled(True)

    # Vider — Qt original
    self.list.clear()

CMessageBox

Définition

CMessageBox est une boîte de dialogue de message compatible.

Pourquoi ?

Le QMessageBox original ne fournit pas de fermeture automatique.
Il est nécessaire pour les messages qui ne nécessitent pas d'intervention de l'utilisateur.

Solution

Ajout d'un mode où le message se ferme après un temps spécifié.

Fonctionnalités

Utilisation

    # PySide6
    from cwidgets.pyside6 import CMessageBox
    # PyQt6
    from cwidgets.pyqt6 import CMessageBox

    # Information non temporisée
    CMessageBox.information(self, "Succès", "Fichier enregistré.")

    # Information temporisée — fermeture automatique après 3 secondes
    CMessageBox.information(self, "Succès", "Fichier enregistré.", timeout=3000)

    # Avertissement non temporisé
    CMessageBox.warning(self, "Avertissement", "Espace disque insuffisant.")

    # Avertissement temporisé
    CMessageBox.warning(self, "Avertissement", "Connexion instable.", timeout=4000)

    # Erreur — non temporisé + son — fermeture manuelle obligatoire
    CMessageBox.critical(self, "Erreur", "Fichier introuvable.")

    # Il est recommandé d'utiliser le délai uniquement pour information et warning
    # Le délai n'est pas disponible pour critical

Meilleures pratiques

  1. Titres : Utilisez toujours CLabel avec buddy.
  2.     # Non recommandé — remplace le label
        self.combo.setAccessibleName("...")
    
        # Correct
        self.lbl = CLabel("Liste des pays :", self, self.combo)
  3. Désactivation : setEnabled(False) est disponible par défaut sur tous les composants C*.
  4. Activation :
    - CLineEdit et CComboBox → signal validated
    - CListWidget → signal Qt original itemActivated
  5. Ordre de création avec buddy : Créez toujours l'élément avant son CLabel.
  6.     # Correct — l'élément est créé avant l'étiquette
        self.combo = CComboBox(self)
        self.lbl   = CLabel("Pays :", self, self.combo)

Configuration requise

Fournis par la bibliothèque :
- validate_parent — Vérification régulière de la fenêtre parent
- logger — Journalisation des erreurs (module "cwidgets")

Limitations de la bibliothèque

Développeur

Mohamed Hédi Bettaieb (Tunisie)

Contact : hedidouz@gmail.com

Date de conception : Mai 2026

Conclusion

Ce projet vise à rendre les applications Qt accessibles à 100% pour les développeurs et utilisateurs malvoyants, sans sacrifier la productivité ou les habitudes des développeurs Qt.