User Guide - CWidgets Library V0.1.2a1:

Introduction

CWidgets: Your Power in Programming Without Limits
Turn Qt obstacles into creative opportunities
CWidgets is a specialized Python library that gives blind developers full control over PyQt6 and PySide6 interfaces.
The era of limitations with QTextEdit is over. From now on, you can design interfaces with multi-line edit boxes with complete freedom and compatibility.
The library provides absolute compatibility for blind developers, as all elements are designed to work seamlessly with the NVDA screen reader.
Continuous creativity: No need to learn new tools, just continue writing the code you're used to while keeping the same functions and features.
Smart engineering: Every element in CWidgets inherits properties from the original Qt elements, ensuring standard performance with radical solutions to compatibility issues.
What will change in how you write your programs using PyQT6 and PySide6 windows?
All you need to do is replace the first letter only (C instead of Q):
CTextEdit instead of QTextEdit.
CButton instead of QPushButton.
Where C stands for Custom or customized.
Complete flexibility in work, as the library supports your preferred working environment with the same efficiency:

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

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

CWidgets: Code with confidence, design without limits.

Available Components:

7 fully compatible custom elements provided by this library:

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

Installation

pip install cwidgets

Accessing Help

After installation, start exploring the library, its components, properties, and how to use them through these functions:

import cwidgets

# List all available components
cwidgets.widgets()

# List all available sections
cwidgets.sections()

# Open the full guide in the browser
cwidgets.show_help()
cwidgets.show_help(lang="en")

# Open a specific component directly
cwidgets.show_help(lang="en", goto="CButton")
cwidgets.show_help(lang="en", goto="CTextEdit")

# Open a specific section directly
cwidgets.show_help(lang="en", goto="introduction")
cwidgets.show_help(lang="en", goto="installation")

Common Issues Resolved

#CTextEdit: Multi-line edit box: CTextEdit solves the fundamental problem of QTextEdit, which is incompatible with NVDA.

#CComboBox & CListWidget: Separation of navigation from activation: Separate navigation actions (arrows) from activation actions (Enter/Space) to avoid unintended activations. Disabling available: Maintain NVDA announcement even when the component is disabled.

#CButton: Activation with ENTER, RETURN, SPACE & mouse. Compatibility even when disabled.

#CLabel: Enhanced compatibility for titles and labels

#CLineEdit: Allows retrieving text from within the element by pressing enter without any additional code.

#CMessageBox: Self-closing message dialogs. You can specify a time after which the dialog disappears automatically.

#Except for CTextEdit, all other components inherit from QT and thus retain all their basic properties and functions.

Component Details:

Definition, creation, properties, usage.

CTextEdit

Definition

CTextEdit is a multi-line edit box fully compatible with NVDA.

Why — The Fundamental Problem

This is the barrier that prevents blind developers from designing QT interfaces containing multi-line edit boxes.
Until the emergence of this library, no solutions to this problem were known to be used by blind developers.

The Solution — Win32 RichEdit Integrated into Qt

The solution provided by this library is to integrate a Win32 RichEdit control directly into the QT window.
Win32 RichEdit is fully compatible with NVDA by default.
The complex phase in designing the solution is integrating it into a Qt window while maintaining this accessibility.
This library also ensured the default use of commands in QT.
QTextEdit element structure:
-QTextEdit
-EditorStyle — Styles, font, color, alignment
-RichEdit Win32 — The original compatible engine

Features

Usage

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

    # Creation
    self.editor = CTextEdit(self, accessible_name="Box name")
    layout.addWidget(self.editor)

    # Insert text into the box
    self.editor.setText("Hello!")
    # Retrieve text from the box:
    text = self.editor.text()
    text = self.editor.toPlainText()
    # Add text to the original text
    self.editor.append("New line.")
    # Clear text to empty the box
    self.editor.clear()

    # Make the box read-only
    self.editor.setReadOnly(True)
    # Disable read-only to make the box writable and readable
    self.editor.setReadOnly(False)

    # Font — QFont or (str, int, bool, bool)
    self.editor.setFont("Arial", 12, True, False)  # Name, size, bold, italic

    # Colors — name or RGB set
    self.editor.setTextColor("red")
    self.editor.setTextColor((255, 0, 0))
    self.editor.setBackgroundColor("yellow")

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

Available Colors

    # Supported names
    "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"

    # or RGB set
    (255, 0, 0)    # red
    (0, 128, 255)  # light blue

Optional Visual Title

accessible_name is read by NVDA but is not visually visible.
To add a visual title, add a CLabel to the layout before the editor:

    self.editor = CTextEdit(self)
    self.lbl    = CLabel("Edit box:", self, self.editor)
    It is essential to note that adding CLabel to the layout must precede adding the editor so that the box name appears above the box and not below it.
    layout.addWidget(self.lbl)
    layout.addWidget(self.editor)

CLabel

Definition

CLabel is an NVDA-compatible label that replaces QLabel.

Why?

The original QLabel is invisible to NVDA unless linked to a buddy — and only when that buddy has focus.

Solution

Two modes:
- Single mode: NVDA-compatible and recognized even with tab navigation.
- Buddy mode: When CLabel is linked to another element.

Features

Usage

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

    # Single mode — NVDA reads the text and can be accessed via tab:
    self.lbl = CLabel("File saved", self)

    # With prefix — NVDA announces: "status File saved"
    self.lbl = CLabel("File saved", self, prefix="status")

    # Buddy mode — NVDA announces the label when the field gets focus
    # The label must be added to the layout before the edit field so that the label appears above the field visually
    self.edit = CLineEdit(self)
    self.lbl  = CLabel("Name:", self, self.edit)
    layout.addWidget(self.lbl)
    layout.addWidget(self.edit)

    # Dynamic update
    self.lbl.setText("Processing")
    self.lbl.setPrefix("Error")

CButton

Definition

CButton is a button with additional features that make it usable without needing extra code lines that the original button would require to enable these features.

Why?

Solution

Features

Usage

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

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

    # Disable — NVDA announces "unavailable", button is unclickable
    self.btn.setEnabled(False)
    self.btn.setEnabled(True)

    # Change title — original Qt
    self.btn.setText("New title")

    # Check status
    if self.btn.isEnabled():
        ...

CLineEdit

Definition

CLineEdit is a compatible text input field that replaces QLineEdit.

Why?

QLineEdit requires an additional line of code to retrieve text.
CLineEdit makes this activation automatic via the validated signal.

Features

Usage

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

    # Create the field first for buddy
    self.edit = CLineEdit(self)
    self.lbl  = CLabel("Name:", self, self.edit)
    layout.addWidget(self.lbl)
    layout.addWidget(self.edit)

    # With initial text
    self.edit = CLineEdit(self, "Cairo")

    # With placeholder text
    self.edit = CLineEdit(self, placeholderText="Enter your name...")

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

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

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

CComboBox

Definition

CComboBox is a compatible dropdown list that replaces QComboBox.

Why?

The original QComboBox is activated internally by arrows.
This is a problem for blind users who navigate with arrows.

Solution

Explicit separation between navigation and activation.
While maintaining compatibility even when the list is disabled.

Features

Usage

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

    # Create the list first for buddy
    self.combo = CComboBox(self)
    self.combo.addItems(["Egypt", "Tunisia", "Morocco"])
    self.lbl = CLabel("Country list:", 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)

    # Button to clear the list
    self.btn_clear = CButton("Clear", self)
    self.btn_clear.clicked.connect(self.combo.clear)

    def on_selection(self):
        text  = self.combo.currentText()
        index = self.combo.currentIndex()
        CMessageBox.information(self, "Selection", f"Country: {text}")

    def on_cleared(self):
        CMessageBox.warning(self, "Warning", "No items available in the list.")

    # Disable / re-enable
    self.combo.setEnabled(False)
    self.combo.setEnabled(True)

CListWidget

Definition

CListWidget is a compatible list that replaces QListWidget.

Why?

The original QListWidget is activated immediately when navigating using arrows.
This is an obstacle for blind users, and disabling this activation with arrows requires writing additional code lines.

Solution

Features

Usage

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

    # Create the list
    self.list = CListWidget(self)
    self.list.addItems(["Iraq", "Saudi Arabia", "Kuwait"])
    self.lbl = CLabel("Country list:", self, self.list)
    layout.addWidget(self.lbl)
    layout.addWidget(self.list)

    # Original Qt signal — matching QListWidget
    self.list.itemActivated.connect(self.on_item)

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

    # Disable / re-enable
    self.list.setEnabled(False)
    self.list.setEnabled(True)

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

CMessageBox

Definition

CMessageBox is a compatible message dialog.

Why?

The original QMessageBox does not provide automatic closing.
It is needed for messages that do not require user intervention.

Solution

Adding a mode where the message closes after a specified time.

Features

Usage

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

    # Non-timed information
    CMessageBox.information(self, "Success", "File saved.")

    # Timed information — auto-close after 3 seconds
    CMessageBox.information(self, "Success", "File saved.", timeout=3000)

    # Non-timed warning
    CMessageBox.warning(self, "Warning", "Insufficient disk space.")

    # Timed warning
    CMessageBox.warning(self, "Warning", "Unstable connection.", timeout=4000)

    # Error — non-timed + sound — manual closing mandatory
    CMessageBox.critical(self, "Error", "File not found.")

    # It is recommended to use timeout only for information and warning
    # Timeout is not available for critical

Best Practices

  1. Titles: Always use CLabel with buddy.
  2.     # Not recommended — replaces label
        self.combo.setAccessibleName("...")
    
        # Correct
        self.lbl = CLabel("Country list:", self, self.combo)
  3. Disabling: setEnabled(False) is available by default on all C* components.
  4. Activation:
    - CLineEdit and CComboBox → validated signal
    - CListWidget → original Qt signal itemActivated
  5. Creation order with buddy: Always create the element before its CLabel.
  6.     # Correct — element is created before the label
        self.combo = CComboBox(self)
        self.lbl   = CLabel("Country:", self, self.combo)

System Requirements

Provided by the library:
- validate_parent — Regular parent window check
- logger — Error logging (module "cwidgets")

Library Limitations

Developer

Mohamed Hédi Bettaieb (Tunisia)

Contact: hedidouz@gmail.com

Date: May 2026

Conclusion

This project aims to make Qt applications 100% accessible to blind developers and users, without sacrificing productivity or Qt developers' habits.