DEV Community

Hedi bettaieb
Hedi bettaieb

Posted on

QTextEdit is Finally Accessible in Qt (PyQt6 & PySide6)

Blind Python Developers: QTextEdit is Finally Accessible in Qt (PyQt6 & PySide6)

Introduction

cwidgets is a specialized Python library that enhances NVDA accessibility
for PyQt6 and PySide6 interfaces.

It provides a complete alternative to QTextEdit — natively inaccessible with NVDA —
allowing blind Python developers to build interfaces containing multi-line text editors
with full NVDA compatibility.

The library supports both Qt environments:


python
# PySide6
from cwidgets.pyside6 import CTextEdit

# PyQt6
from cwidgets.pyqt6 import CTextEdit
The Problem
QTextEdit, Qt's native multi-line text editor, has serious limitations with NVDA:
Content is announced twice during navigation
Impossible to distinguish the current line from a new line
Unpredictable behavior during editing
No reliable feedback on cursor position
These limitations make QTextEdit unusable in practice for a blind developer. QLineEdit is accessible but single-line — it cannot replace QTextEdit.
Result: blind developers using PyQt6 or PySide6 had no reliable solution for integrating a multi-line text editor into their applications.
The Solution — CTextEdit
cwidgets is a Python library that solves this problem by directly integrating the native Win32 RICHEDIT20W control inside a Qt window.
RichEdit is the engine used by WordPad and many Windows applications. It is natively and fully compatible with NVDA — correct reading, precise navigation, reliable line-by-line announcement.
The complex part was integrating it into Qt while maintaining:
Full NVDA accessibility
Standard Qt API (setText, toPlainText, append, clear...)
PyQt6 and PySide6 compatibility
Tab navigation between Qt widgets
Focus restoration after Alt+Tab
All of this is handled internally by cwidgets. You use it exactly like QTextEdit.
Installation
pip install cwidgets
Requirements:
Python 3.10+
Windows (Win32 RichEdit dependency)
PyQt6 or PySide6
pywin32 (installed automatically)
Help and Documentation
After installation, explore the library directly from your code:
import cwidgets

# List all available components
cwidgets.widgets()

# List all available documentation sections
cwidgets.sections()

# Open the full guide in the browser
cwidgets.show_help()
cwidgets.show_help(lang="fr")   # French
cwidgets.show_help(lang="ar")   # Arabic

# Open CTextEdit documentation directly
cwidgets.show_help(lang="en", goto="CTextEdit")

# Explore CTextEdit API from code
cwidgets.ctextedit.show()          # method names only
cwidgets.ctextedit.show_details()  # names + descriptions
Using CTextEdit
# PySide6
from cwidgets.pyside6 import CTextEdit, CLabel

# PyQt6
from cwidgets.pyqt6 import CTextEdit, CLabel
Creation
self.editor = CTextEdit(self, accessible_name="Notes")
layout.addWidget(self.editor)
Content
# Set content
self.editor.setText("Hello from cwidgets!")

# Get content
text = self.editor.toPlainText()
text = self.editor.text()           # alias for toPlainText()

# Append text at the end
self.editor.append("New line.")

# Insert at cursor position
self.editor.insertPlainText("Inserted text\n")

# Insert HTML — tags are stripped
self.editor.insertHtml("<p>Hello <b>world</b></p>")

# Clear content
self.editor.clear()
Selection
self.editor.selectAll()
text = self.editor.selectedText()   # returns "" if no selection
count = self.editor.lineCount()
Properties
self.editor.setReadOnly(True)
self.editor.setReadOnly(False)
state = self.editor.isReadOnly()
Formatting
# Font
self.editor.setFont("Arial", 12, True, False)   # name, size, bold, italic

# Text color — name or (R, G, B)
self.editor.setTextColor("blue")
self.editor.setTextColor((0, 0, 255))

# Background color
self.editor.setBackgroundColor("yellow")

# Alignment
self.editor.setAlignment("left")
self.editor.setAlignment("center")
self.editor.setAlignment("right")
Clipboard
# Ctrl+C/X/V/Z/Y shortcuts work natively via keyboard.
# These methods allow programmatic use, e.g. via a button.

self.editor.copy()
self.editor.cut()
self.editor.paste()
self.editor.undo()
self.editor.redo()
Signals
self.editor.textChanged.connect(self.on_text_changed)
self.editor.cursorPositionChanged.connect(self.on_cursor_changed)
self.editor.selectionChanged.connect(self.on_selection_changed)

def on_text_changed(self):
    print(self.editor.toPlainText())

def on_cursor_changed(self):
    print("Cursor moved")

def on_selection_changed(self):
    print(self.editor.selectedText())
Visual Title with CLabel
self.editor = CTextEdit(self, accessible_name="Description")
self.lbl    = CLabel("Text area:", self, self.editor)
layout.addWidget(self.lbl)
layout.addWidget(self.editor)
Behavior in Layouts
CTextEdit has a minimum size of 50x50 pixels. When sharing a QHBoxLayout with other widgets, use stretch=1:
layout = QHBoxLayout()
layout.addWidget(self.list_widget, 1)
layout.addWidget(self.editor, 1)
Other Widgets
cwidgets also enhances NVDA accessibility for six additional Qt widgets: CButton, CLabel, CLineEdit, CComboBox, CListWidget and CMessageBox. These widgets preserve the complete native Qt API while fixing known NVDA accessibility issues.
Links
PyPI: https://pypi.org/project/cwidgets/
GitHub: https://github.com/hedi-bettaieb/cwidgets
Install: pip install cwidgets

About the Author
Mohamed Hédi Bettaieb — Tunisia
A blind developer who built this library because he needed it himself.
All widgets and all APIs have been personally tested by the developer and by a group of blind Python developers.
Enter fullscreen mode Exit fullscreen mode

Top comments (0)