cwidgets 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
cwidgets-0.1.0/LICENSE ADDED
@@ -0,0 +1,67 @@
1
+ GNU GENERAL PUBLIC LICENSE
2
+ Version 3, 29 June 2007
3
+
4
+ Copyright (C) 2026 Mohamed Hédi Bettaieb <hedidouz@gmail.com>
5
+
6
+ This program is free software: you can redistribute it and/or modify
7
+ it under the terms of the GNU General Public License as published by
8
+ the Free Software Foundation, either version 3 of the License, or
9
+ (at your option) any later version.
10
+
11
+ This program is distributed in the hope that it will be useful,
12
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
+ GNU General Public License for more details.
15
+
16
+ You should have received a copy of the GNU General Public License
17
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
18
+
19
+ -------------------------------------------------------------------------------
20
+
21
+ ADDITIONAL TERMS — Attribution Requirement (as permitted by GPL v3, section 7)
22
+
23
+ Any distribution or publication of this software, modified or unmodified,
24
+ must clearly state the original author:
25
+
26
+ Original author : Mohamed Hédi Bettaieb <hedidouz@gmail.com>
27
+ Project : cwidgets
28
+ Created : April-May 2026
29
+ Repository : https://github.com/hedi-bettaieb/cwidgets
30
+
31
+ Removing or falsifying this attribution is a violation of this license.
32
+
33
+ -------------------------------------------------------------------------------
34
+
35
+ FULL LICENSE TEXT
36
+
37
+ The GNU General Public License is a free, copyleft license for software.
38
+
39
+ When we speak of free software, we are referring to freedom, not price.
40
+ Our General Public Licenses are designed to make sure that you have the
41
+ freedom to distribute copies of free software (and charge for them if
42
+ you wish), that you receive source code or can get it if you want it,
43
+ that you can change the software or distribute your pieces of it in new
44
+ free programs, and that you know you can do these things.
45
+
46
+ To protect your rights, we need to prevent others from denying you these
47
+ rights or asking you to surrender the rights. Therefore, you have certain
48
+ responsibilities if you distribute copies of the software, or if you
49
+ modify it: responsibilities to respect the freedom of others.
50
+
51
+ For example, if you distribute copies of such a program, whether gratis
52
+ or for a fee, you must pass on to the recipients the same freedoms that
53
+ you received. You must make sure that they, too, receive or can get the
54
+ source code. And you must show them these terms so they know their rights.
55
+
56
+ Developers that use the GNU GPL protect your rights with two steps:
57
+ (1) assert copyright on the software, and (2) offer you this License
58
+ giving you legal permission to copy, distribute and/or modify it.
59
+
60
+ For the developers' and authors' protection, the GPL clearly explains
61
+ that there is no warranty for this free software. For both users' and
62
+ authors' sake, the GPL requires that modified versions be marked as
63
+ changed, so that their problems will not be attributed erroneously to
64
+ authors of previous versions.
65
+
66
+ For the full official license text, see:
67
+ https://www.gnu.org/licenses/gpl-3.0.txt
@@ -0,0 +1,21 @@
1
+ Metadata-Version: 2.4
2
+ Name: cwidgets
3
+ Version: 0.1.0
4
+ Summary: Accessible custom widgets for PyQt6 and PySide6 — keyboard navigation, NVDA screen reader support, and Win32 RichEdit integration
5
+ Author-email: Mohamed Hédi Bettaieb <hedidouz@gmail.com>
6
+ License-Expression: GPL-3.0-or-later
7
+ Project-URL: Homepage, https://github.com/hedi-bettaieb/cwidgets
8
+ Project-URL: Repository, https://github.com/hedi-bettaieb/cwidgets
9
+ Project-URL: Issues, https://github.com/hedi-bettaieb/cwidgets/issues
10
+ Keywords: pyqt6,pyside6,accessibility,nvda,widgets,screenreader,blind,visually impaired
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Operating System :: Microsoft :: Windows
15
+ Classifier: Topic :: Software Development :: User Interfaces
16
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
17
+ Requires-Python: >=3.10
18
+ Description-Content-Type: text/markdown
19
+ License-File: LICENSE
20
+ Requires-Dist: pywin32; sys_platform == "win32"
21
+ Dynamic: license-file
@@ -0,0 +1,527 @@
1
+ <!DOCTYPE html>
2
+ <html lang="ar" dir="rtl">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <title>دليل المستخدم - مكتبة CWidgets V0.1.0</title>
6
+ </head>
7
+ <body>
8
+
9
+ <h1></h1>
10
+ <p>دليل المستخدم - مكتبة CWidgets V0.1.0a1:</p>
11
+
12
+ <h1 id="introduction">مقدمة</h1>
13
+ <p>CWidgets: قوتك في البرمجة بلا حدود<br>
14
+ حول عوائق Qt إلى فرص إبداعية<br>
15
+ CWidgets هي مكتبة بايثون متخصصة تمنح المطورين المكفوفين السيطرة الكاملة على واجهات PyQt6 وPySide6.<br>
16
+ لقد إنتهى زمن القيود مع QTextEdit، فمنذ الآن، يمكنك تصميم واجهات تحتوي على مربعات تحرير متعددة الأسطر بكل حرية وتوافقية.<br>
17
+ المكتبة توفر للمطور الكفيف توافقية مطلقة فجميع العناصر مصممة لتعمل بسلاسة تامة مع قارئ الشاشة NVDA.<br>
18
+ إستمرارية الإبداع:لا حاجة لتعلم أدوات جديدة بل واصل كتابة أكوادك التي تعودت عليها مع الحفاظ على نفس الدوال والوظائف.<br>
19
+ هندسة ذكية:كل عنصر في CWidgets يرث خصائص عناصر Qt الأصلية، مما يضمن لك أداءً قياسياً مع حلول جذرية لمشاكل التوافقية.<br>
20
+ ما الذي سيتغير في كتابتك لبرامجك بإستخدام نوافذ PyQT6 و PySide6 ؟ .<br>
21
+ كل ما ستحتاجه هو استبدال الحرف الأول فقط (C عوضاً عن Q):<br>
22
+ CTextEdit بدلاً من QTextEdit.<br>
23
+ CButton بدلاً من QPushButton.<br>
24
+ حيث ترمز C إلى Custom أو مخصصة<br>
25
+ مرونة تامة في العمل ، فالمكتبة تدعم بيئة العمل التي تفضلها بنفس الكفاءة:</p>
26
+ <pre>
27
+ مع PySide6:
28
+ from cwidgets.pyside6 import CButton, CLabel, CLineEdit....
29
+
30
+ مع PyQt6:
31
+ from cwidgets.pyqt6 import CButton, CLabel, CLineEdit .....</pre>
32
+ <p>CWidgets: برمج بكل ثقة، وصمم بلا قيود.</p>
33
+
34
+ <h1 id="widgets-list">المكونات المتوفرة :</h1>
35
+ <p>7 عناصر مخصصة متوافقة تماما توفرها لك هذه المكتبة و هي :</p>
36
+ <ol>
37
+ <li>CTextEdit</li>
38
+ <li>CButton</li>
39
+ <li>CLabel</li>
40
+ <li>CLineEdit</li>
41
+ <li>CComboBox</li>
42
+ <li>CListWidget</li>
43
+ <li>CMessageBox</li>
44
+ </ol>
45
+
46
+ <h1 id="installation">التثبيت</h1>
47
+ <pre>pip install cwidgets</pre>
48
+
49
+ <h1 id="show-help">الوصول إلى المساعدة</h1>
50
+ <p>بعد التثبيت، أبدأ بالتعرف على المكتبة و مكوناتها و خصائصها و كيفية إستخدامها من خلال هذه الدوال :</p>
51
+ <pre>
52
+ import cwidgets
53
+
54
+ # قائمة بجميع المكونات المتاحة
55
+ cwidgets.widgets()
56
+
57
+ # قائمة بجميع الأقسام المتاحة
58
+ cwidgets.sections()
59
+
60
+ # فتح الدليل الكامل في المتصفح
61
+ cwidgets.show_help()
62
+ cwidgets.show_help(lang="ar")
63
+
64
+ # فتح أحد المكونات بالتحديد مباشرة
65
+ cwidgets.show_help(lang="ar", goto="CButton")
66
+ cwidgets.show_help(lang="ar", goto="CTextEdit")
67
+
68
+ # فتح قسم محدد مباشرة
69
+ cwidgets.show_help(lang="ar", goto="introduction")
70
+ cwidgets.show_help(lang="ar", goto="installation")
71
+ </pre>
72
+
73
+ <h1 id="common-issues">المشاكل الشائعة التي تم حلها</h1>
74
+
75
+ <p>#CTextEdit : مربع تحرير متعدد الأسطر: يحل CTextEdit المشكلة الأساسية لـ QTextEdit، غير المتوافق مع NVDA.</p>
76
+
77
+ <p>#CComboBox &amp; CListWidget: فصل التنقل عن التفعيل : فصل إجراءات التنقل (الأسهم) عن إجراءات التفعيل (Enter/Space) لتجنب التفعيلات غير المقصودة. التعطيل المتاح: الحفاظ على الإعلان بواسطة NVDA حتى عندما يكون المكون معطلاً.</p>
78
+
79
+ <p>#CButton : التفعيل ب enter, return, space &amp; mouse. توافقية حتى في حالة التعطيل.</p>
80
+
81
+ <p>#CLabel تعزيز توافقية العناوين والملصقات</p>
82
+
83
+ <p>#CLineEdit : يمكنك من إسترجاع النص من داخل العنصر من خلال الضغط enter دون أي سطر كود إضافي.</p>
84
+
85
+ <p>#CMessageBox : محاورة رسائل ذاتية الإنهيار. يمكنك تحديد زمن تختفي بعده المحاورة تلقائيا.</p>
86
+
87
+ <p>#بإستثناء CTextEdit، كل المكونات الأخرى ترث من QT و بالتالي هي تحتفظ بكل خصائصها و وظائفها الأساسية.</p>
88
+
89
+ <h1 id="widgets-details">تفاصيل المكونات :</h1>
90
+ <p>التعريف ،الإنشاء ، الخصائص ، الإستخدام .</p>
91
+
92
+ <h2 id="ctextedit">CTextEdit</h2>
93
+
94
+ <h3>التعريف</h3>
95
+ <p>CTextEdit هو مربع تحرير متعدد الأسطر متوافق تماما مع NVDA .</p>
96
+
97
+ <h3>لماذا — المشكلة الأساسية</h3>
98
+ <ul>
99
+ <li>QTextEdit الأصلي في Qt غير متوافق مع NVDA.</li>
100
+ <li>المطور الكفيف لا يمكنه القراءة أو الكتابة في هذا العنصر .</li>
101
+ </ul>
102
+ <p>هذا هو الحاجز الذي يحول دون المكفوفين و تصميم واجهات QT تحتوي على مربعات تحرير متعددة الأسطر .<br>
103
+ إلى حد ظهور هذه المكتبة لم تعرف حلولا لهذه المشكلة يستخدمها المكفوفون .</p>
104
+
105
+ <h3>الحل — RichEdit Win32 مدمج في Qt</h3>
106
+ <p>الحل الذي أتت به هذه المكتبة هو دمج عنصر تحكم Win32 RichEdit مباشرة في نافذة QT .<br>
107
+ RichEdit Win32 هو إفتراضيا متوافق تماما مع NVDA .<br>
108
+ المرحلة المعقدة في تصميم الحل هي دمجه في نافذة Qt مع الحفاظ على إمكانية الوصول هذه.<br>
109
+ كما ضمنت هذه المكتبة الإستخدام الإفتراضي للأوامر في QT .<br>
110
+ بنية العنصر QTextEdit :<br>
111
+ -QTextEdit<br>
112
+ -EditorStyle — الأنماط، الخط، اللون، المحاذاة<br>
113
+ -RichEdit Win32 — المحرك الأصلي المتوافق</p>
114
+
115
+ <h3>الميزات</h3>
116
+ <ul>
117
+ <li>إمكانية الوصول الكاملة بواسطة NVDA: القراءة، الكتابة، التنقل، التحديد.</li>
118
+ <li>واجهة برمجة تطبيقات متوافقة مع QTextEdit: setText, toPlainText, append, clear, setReadOnly.</li>
119
+ <li>نص منسق: الخط، الحجم، غامق، مائل، اللون، المحاذاة.</li>
120
+ <li>إدارة غير متزامنة: الأنماط والنص في الانتظار إذا لم يكن مقبض Win32 متاحًا بعد.</li>
121
+ <li>التركيز الذكي: استعادة التركيز عند العودة إلى التطبيق.</li>
122
+ </ul>
123
+
124
+ <h3>الاستخدام</h3>
125
+ <pre>
126
+ # PySide6
127
+ from cwidgets.pyside6 import CTextEdit
128
+ # PyQt6
129
+ from cwidgets.pyqt6 import CTextEdit
130
+
131
+ # إنشاء
132
+ self.editor = CTextEdit(self, accessible_name="إسم المربع")
133
+ layout.addWidget(self.editor)
134
+
135
+ # إدراج نص داخل المربع
136
+ self.editor.setText("مرحبًا!")
137
+ # إسترجاع النص من داخل المربع :
138
+ text = self.editor.text()
139
+ text = self.editor.toPlainText()
140
+ #إضافة نص إلى النص الأصلي
141
+ self.editor.append("سطر جديد.")
142
+ #مسح النص لإفراغ المربع
143
+ self.editor.clear()
144
+
145
+ # جعل المربع للقراءة فقط
146
+ self.editor.setReadOnly(True)
147
+ #تعطيل للقراءة فقط ليصبح المربع للكتابة و القراءة
148
+ self.editor.setReadOnly(False)
149
+
150
+ # الخط — QFont أو (str, int, bool, bool)
151
+ self.editor.setFont("Arial", 12, True, False) # الاسم، الحجم، غامق، مائل
152
+
153
+ # الألوان — اسم أو مجموعة RGB
154
+ self.editor.setTextColor("red")
155
+ self.editor.setTextColor((255, 0, 0))
156
+ self.editor.setBackgroundColor("yellow")
157
+
158
+ # المحاذاة
159
+ self.editor.setAlignment("left")
160
+ self.editor.setAlignment("center")
161
+ self.editor.setAlignment("right")</pre>
162
+
163
+ <h3>الألوان المتاحة</h3>
164
+ <pre>
165
+ # الأسماء المدعومة
166
+ "black", "white", "red", "green", "blue", "yellow",
167
+ "cyan", "magenta", "gray", "darkgray", "lightgray",
168
+ "orange", "purple", "violet", "pink", "brown",
169
+ "navy", "teal", "lime", "olive", "maroon",
170
+ "coral", "salmon", "gold", "silver"
171
+
172
+ # أو مجموعة RGB
173
+ (255, 0, 0) # أحمر
174
+ (0, 128, 255) # أزرق فاتح</pre>
175
+
176
+ <h3>العنوان المرئي اختياري</h3>
177
+ <p>accessible_name يُقرأه NVDA ولكنه غير مرئي بصريًا.<br>
178
+ لإضافة عنوان مرئي، أضف CLabel إلى التخطيط قبل المحرر:</p>
179
+ <pre>
180
+ self.editor = CTextEdit(self)
181
+ self.lbl = CLabel("مربع التحرير:", self , self.editor)
182
+ و هنا من الضروري التنبيه أن إضافة CLabel إلى layout يجب أن تسبق إضافة editor حتى يظهر إسم المربع فوق المربع و ليس تحته .
183
+ layout.addWidget(self.lbl)
184
+ layout.addWidget(self.editor)</pre>
185
+
186
+ <h2 id="clabel">CLabel</h2>
187
+
188
+ <h3>التعريف</h3>
189
+ <p>CLabel هو ملصق متوافق مع NVDA، يحل محل QLabel.</p>
190
+
191
+ <h3>لماذا؟</h3>
192
+ <p>QLabel الأصلي غير مرئي لـ NVDA إلا إذا كان مرتبطًا بـ buddy — وفقط عندما يكون هذا Buddy لديه التركيز.</p>
193
+
194
+ <h3>الحل</h3>
195
+ <p>وضعان:<br>
196
+ - الوضع الفردي: متوافق مع NVDA و يتعرف عليه حتى بالتنقل بالتاب .<br>
197
+ - وضع Buddy: عندما يكون CLabel مرتبطا بعنصر آخر .</p>
198
+
199
+ <h3>الميزات</h3>
200
+ <ul>
201
+ <li>تنظيف الاختصارات: &amp;الاسم → "الاسم" لـ NVDA، الاختصار المرئي لـ Qt محفوظ.</li>
202
+ <li>prefix نص تكميلي مثال عندما نريد التعبير عن حالة status .</li>
203
+ <li>المزامنة الإفتراضية :<br>
204
+ تغيير محتوى الملصق .<br>
205
+ setText()<br>
206
+ تغيير النص التكميلي :<br>
207
+ setPrefix()<br>
208
+ يعيدان المزامنة بدون كود إضافي.</li>
209
+ </ul>
210
+
211
+ <h3>الاستخدام</h3>
212
+ <pre>
213
+ # PySide6
214
+ from cwidgets.pyside6 import CLabel, CLineEdit
215
+ # PyQt6
216
+ from cwidgets.pyqt6 import CLabel, CLineEdit
217
+
218
+ # الوضع الفردي — يقرأ NVDA النص و يمكن الوصول إليه بالتاب :
219
+ self.lbl = CLabel("تم حفظ الملف", self)
220
+
221
+ # مع prefix — يعلن NVDA: "حالة تم حفظ الملف"
222
+ self.lbl = CLabel("تم حفظ الملف", self, prefix="حالة")
223
+
224
+ # وضع Buddy — يعلن NVDA الملصق عندما يحصل الحقل على التركيز
225
+ # يجب إضافة الملصق إلى التخطيط قبل حقل الكتابةحتى يظهر الملصق فوق الحقل بصريا
226
+ self.edit = CLineEdit(self)
227
+ self.lbl = CLabel("الاسم:", self, self.edit)
228
+ layout.addWidget(self.lbl)
229
+ layout.addWidget(self.edit)
230
+
231
+ # التحديث الديناميكي
232
+ self.lbl.setText("جارٍ المعالجة")
233
+ self.lbl.setPrefix("خطأ")</pre>
234
+
235
+ <h2 id="cbutton">CButton</h2>
236
+
237
+ <h3>التعريف</h3>
238
+ <p>CButton هو زر يتميز بمزايا إضافية تجعله قابلا للإستخدام دون الحاجة لأسطر كودية يحتاج الزر الأصلي لتلك الأسطر لتفعيل هذه المزايا .</p>
239
+
240
+ <h3>لماذا؟</h3>
241
+ <ul>
242
+ <li>QPushButton الأصلي يقبل فقط Space للتفعيل — Enter و Return يتم تجاهلهما.</li>
243
+ <li>setEnabled(False) يجعل الزر غير مرئي لـ NVDA، حتى مع setAccessibleDescription.</li>
244
+ </ul>
245
+
246
+ <h3>الحل</h3>
247
+ <ul>
248
+ <li>التفعيل يشمل Enter/Return.</li>
249
+ <li>تعطيل الزر يتم مع مواصلة التوافقية .<br>
250
+ الزر يبقى بصريا "في حالة نشطة" لكنه لا يعمل.<br>
251
+ و هذا هو الحل: توفر إمكانية التعطيل مع إستمرار التوافقية عكس QPushButton الذي يختفي عن NVDA لو كان معطلا .</li>
252
+ </ul>
253
+
254
+ <h3>الميزات</h3>
255
+ <ul>
256
+ <li>التفعيل الموسع: Space، Enter، Return، النقر بالماوس.</li>
257
+ <li>التعطيل المتاح: غير قابل للنقر + يعلن NVDA "غير متاح".</li>
258
+ <li>واجهة برمجة تطبيقات مطابقة لـ QPushButton.</li>
259
+ </ul>
260
+
261
+ <h3>الاستخدام</h3>
262
+ <pre>
263
+ # PySide6
264
+ from cwidgets.pyside6 import CButton
265
+ # PyQt6
266
+ from cwidgets.pyqt6 import CButton
267
+
268
+ self.btn = CButton("حفظ", self)
269
+ self.btn.clicked.connect(self.on_click)
270
+
271
+ # تعطيل — يعلن NVDA "غير متاح"، الزر غير قابل للنقر
272
+ self.btn.setEnabled(False)
273
+ self.btn.setEnabled(True)
274
+
275
+ # تغيير العنوان — أصلي Qt
276
+ self.btn.setText("عنوان جديد")
277
+
278
+ # التحقق من الحالة
279
+ if self.btn.isEnabled():
280
+ ...</pre>
281
+
282
+ <h2 id="clineedit">CLineEdit</h2>
283
+
284
+ <h3>التعريف</h3>
285
+ <p>CLineEdit هو حقل إدخال نص متوافق، يحل محل QLineEdit.</p>
286
+
287
+ <h3>لماذا؟</h3>
288
+ <p>QLineEdit يحتاج لسطر كود إضافي لإسترجاع النص .<br>
289
+ CLineEdit يجعل هذا التفعيل تلقائيًا عبر الإشارة validated.</p>
290
+
291
+ <h3>الميزات</h3>
292
+ <ul>
293
+ <li>الإشارة validated تُصدر بالنص الحالي عند كل Enter/Return.</li>
294
+ <li>placeholderText كمعامل — يُعلن بواسطة NVDA عندما يكون الحقل فارغًا.</li>
295
+ <li>العنوان عبر CLabel مع buddy.</li>
296
+ </ul>
297
+
298
+ <h3>الاستخدام</h3>
299
+ <pre>
300
+ # PySide6
301
+ from cwidgets.pyside6 import CLineEdit, CLabel
302
+ # PyQt6
303
+ from cwidgets.pyqt6 import CLineEdit, CLabel
304
+
305
+ # إنشاء الحقل أولاً لـ buddy
306
+ self.edit = CLineEdit(self)
307
+ self.lbl = CLabel("الاسم:", self, self.edit)
308
+ layout.addWidget(self.lbl)
309
+ layout.addWidget(self.edit)
310
+
311
+ # مع نص مبدئي
312
+ self.edit = CLineEdit(self, "القاهرة")
313
+
314
+ # مع نص إرشادي placeholder
315
+ self.edit = CLineEdit(self, placeholderText="أدخل اسمك...")
316
+
317
+ # إشارة validated
318
+ self.edit.validated.connect(self.on_validated)
319
+
320
+ def on_validated(self, text):
321
+ print(text)
322
+
323
+ # إظهار / إخفاء — أصلي Qt
324
+ self.edit.hide()
325
+ self.edit.show()</pre>
326
+
327
+ <h2 id="ccombobox">CComboBox</h2>
328
+
329
+ <h3>التعريف</h3>
330
+ <p>CComboBox هو قائمة منسدلة متوافقة، يحل محل QComboBox.</p>
331
+
332
+ <h3>لماذا؟</h3>
333
+ <p>QComboBox الأصلي يتم التفعيل داخله بواسطة الأسهم .<br>
334
+ و هذا يمثل مشكلة للمكفوفين الذين يتنقلون بالإسهم .</p>
335
+
336
+ <h3>الحل</h3>
337
+ <p>فصل صريح بين التنقل والتفعيل .<br>
338
+ مع المحافظة على التوافقية حتى في حالة تعطيل القائمة .</p>
339
+
340
+ <h3>الميزات</h3>
341
+ <ul>
342
+ <li>التنقل الحر: ↑ ↓ للتنقل بدون تفعيل .</li>
343
+ <li>التفعيل الصريح: Enter، Return، Space → بواسطة إشارة validated.</li>
344
+ <li>الإشارة cleared: تُصدر عندما يتحقق المستخدم من قائمة فارغة.</li>
345
+ <li>التعطيل المتاح: يعلن NVDA "غير متاح".</li>
346
+ <li>العنوان عبر CLabel مع buddy — لا يُنصح باستخدام setAccessibleName لأنه يحل محل الملصق.</li>
347
+ </ul>
348
+
349
+ <h3>الاستخدام</h3>
350
+ <pre>
351
+ # PySide6
352
+ from cwidgets.pyside6 import CComboBox, CLabel, CMessageBox, CButton
353
+ # PyQt6
354
+ from cwidgets.pyqt6 import CComboBox, CLabel, CMessageBox, CButton
355
+
356
+ # إنشاء القائمة أولاً لـ buddy
357
+ self.combo = CComboBox(self)
358
+ self.combo.addItems(["مصر", "تونس", "المغرب"])
359
+ self.lbl = CLabel("قائمة الدول:", self, self.combo)
360
+ layout.addWidget(self.lbl)
361
+ layout.addWidget(self.combo)
362
+
363
+ self.combo.validated.connect(self.on_selection)
364
+ self.combo.cleared.connect(self.on_cleared)
365
+
366
+ # زر يفرغ القائمة
367
+ self.btn_clear = CButton("تفريغ", self)
368
+ self.btn_clear.clicked.connect(self.combo.clear)
369
+
370
+ def on_selection(self):
371
+ text = self.combo.currentText()
372
+ index = self.combo.currentIndex()
373
+ CMessageBox.information(self, "التحديد", f"الدولة: {text}")
374
+
375
+ def on_cleared(self):
376
+ CMessageBox.warning(self, "تحذير", "لا توجد عناصر متاحة في القائمة.")
377
+
378
+ # تعطيل / إعادة تفعيل
379
+ self.combo.setEnabled(False)
380
+ self.combo.setEnabled(True)</pre>
381
+
382
+ <h2 id="clistwidget">CListWidget</h2>
383
+
384
+ <h3>التعريف</h3>
385
+ <p>CListWidget هي قائمة متوافقة ، تحل محل QListWidget.</p>
386
+
387
+ <h3>لماذا؟</h3>
388
+ <p>QListWidget الأصلي يتم التفعيل داخله فورًا عند التنقل باستخدام الأسهم.<br>
389
+ و هذا يمثل عائقا للمكفوفين و تعطيل هذا التفعيل بالأسهم يتطلب كتابة أسطر كودية إضافية .</p>
390
+
391
+ <h3>الحل</h3>
392
+ <ul>
393
+ <li>فصل بين التنقل والتفعيل .</li>
394
+ <li>الحفاظ على إمكانية الوصول في وضع التعطيل.</li>
395
+ </ul>
396
+
397
+ <h3>الميزات</h3>
398
+ <ul>
399
+ <li>التنقل الحر: ↑ ↓ للتنقل بدون تفعيل .</li>
400
+ <li>التفعيل الصريح: Enter، Return، Space.</li>
401
+ <li>في حالة التعطيل : يعلن NVDA "غير متاح".</li>
402
+ <li>العنوان عبر CLabel مع buddy — لا يُنصح باستخدام setAccessibleName لأنه يحل محل الملصق.</li>
403
+ </ul>
404
+
405
+ <h3>الاستخدام</h3>
406
+ <pre>
407
+ # PySide6
408
+ from cwidgets.pyside6 import CListWidget, CLabel
409
+ # PyQt6
410
+ from cwidgets.pyqt6 import CListWidget, CLabel
411
+
412
+ # إنشاء القائمة
413
+ self.list = CListWidget(self)
414
+ self.list.addItems(["العراق", "السعودية", "الكويت"])
415
+ self.lbl = CLabel("قائمة الدول:", self, self.list)
416
+ layout.addWidget(self.lbl)
417
+ layout.addWidget(self.list)
418
+
419
+ # إشارة أصلية Qt — مطابقة لـ QListWidget
420
+ self.list.itemActivated.connect(self.on_item)
421
+
422
+ def on_item(self, item):
423
+ text = item.text()
424
+ row = self.list.currentRow()
425
+ print(row, text)
426
+
427
+ # تعطيل / إعادة تفعيل
428
+ self.list.setEnabled(False)
429
+ self.list.setEnabled(True)
430
+
431
+ # تفريغ — أصلي Qt
432
+ self.list.clear()</pre>
433
+
434
+ <h2 id="cmessagebox">CMessageBox</h2>
435
+
436
+ <h3>التعريف</h3>
437
+ <p>CMessageBox هي محاورة رسائل متوافقة.</p>
438
+
439
+ <h3>لماذا؟</h3>
440
+ <p>QMessageBox الأصلي لا يوفر إغلاقًا تلقائيًا .<br>
441
+ نحتاجه للرسائل التي لا تتطلب تدخلا من المستخدم .</p>
442
+
443
+ <h3>الحل</h3>
444
+ <p>إضافة وضع تغلق فيه الرسالة بعد زمن محدد .</p>
445
+
446
+ <h3>الميزات</h3>
447
+ <ul>
448
+ <li>information: مؤقت أو غير مؤقت — بدون صوت.</li>
449
+ <li>warning: مؤقت أو غير مؤقت — بدون صوت.</li>
450
+ <li>critical: دائمًا غير مؤقت + صوت النظام — إغلاق يدوي إلزامي.</li>
451
+ <li>الإغلاق اليدوي ممكن دائمًا قبل انتهاء المهلة.</li>
452
+ </ul>
453
+
454
+ <h3>الاستخدام</h3>
455
+ <pre>
456
+ # PySide6
457
+ from cwidgets.pyside6 import CMessageBox
458
+ # PyQt6
459
+ from cwidgets.pyqt6 import CMessageBox
460
+
461
+ # معلومات غير مؤقتة
462
+ CMessageBox.information(self, "نجاح", "تم حفظ الملف.")
463
+
464
+ # معلومات مؤقتة — إغلاق تلقائي بعد 3 ثوانٍ
465
+ CMessageBox.information(self, "نجاح", "تم حفظ الملف.", timeout=3000)
466
+
467
+ # تحذير غير مؤقت
468
+ CMessageBox.warning(self, "تحذير", "مساحة القرص غير كافية.")
469
+
470
+ # تحذير مؤقت
471
+ CMessageBox.warning(self, "تحذير", "الاتصال غير مستقر.", timeout=4000)
472
+
473
+ # خطأ — غير مؤقت + صوت — إغلاق يدوي إلزامي
474
+ CMessageBox.critical(self, "خطأ", "الملف غير موجود.")
475
+
476
+ # يُنصح باستخدام المهلة فقط لـ information و warning
477
+ # المهلة غير متاحة لـ critical</pre>
478
+
479
+ <h1 id="best-practices">أفضل الممارسات</h1>
480
+
481
+ <ol>
482
+ <li>العناوين: استخدم دائمًا CLabel مع buddy .</li>
483
+ <pre>
484
+ # غير منصوح به — يحل محل label
485
+ self.combo.setAccessibleName("...")
486
+
487
+ # صحيح
488
+ self.lbl = CLabel("قائمة الدول:", self, self.combo)</pre>
489
+
490
+ <li>التعطيل: setEnabled(False) متاح افتراضيًا على جميع المكونات C*.</li>
491
+
492
+ <li>التفعيل:<br>
493
+ - CLineEdit و CComboBox → إشارة validated<br>
494
+ - CListWidget → إشارة أصلية Qt itemActivated</li>
495
+
496
+ <li>ترتيب الإنشاء مع buddy: قم دائمًا بإنشاء العنصر قبل CLabel الخاص به.</li>
497
+ <pre>
498
+ # صحيح — العنصر يُنشأ قبل الملصق
499
+ self.combo = CComboBox(self)
500
+ self.lbl = CLabel("الدولة:", self, self.combo)</pre>
501
+ </ol>
502
+
503
+ <h1 id="requirements">متطلبات التشغيل</h1>
504
+ <ul>
505
+ <li>PySide6 أو PyQt6</li>
506
+ <li>pywin32 — فقط لـ CTextEdit (دمج Win32 RichEdit)</li>
507
+ </ul>
508
+ <p>المقدمة من المكتبة:<br>
509
+ - validate_parent — التحقق المنتظم من النافذة parent<br>
510
+ - logger — تسجيل الأخطاء (الوحدة "cwidgets")</p>
511
+
512
+ <h1 id="limits">حدود المكتبة</h1>
513
+ <ul>
514
+ <li>CTextEdit يعتمد على Win32 RichEdit — متوافق مع Windows فقط.</li>
515
+ <li>لا يُنصح باستخدام setAccessibleName مع CComboBox و CListWidget لأنه يحل محل buddy CLabel.</li>
516
+ <li>CButton: التظليل الرمادي عبر stylesheet — Windows لا يقوم بتظليل الزر تلقائيًا عند الحفاظ عليه نشطًا لـ NVDA.</li>
517
+ </ul>
518
+
519
+ <h1 id="developer">المطوّر</h1>
520
+ <p>محمد الهادي بالطيب (تونس)</p>
521
+ <p>البريد الإلكتروني : hedidouz@gmail.com</p>
522
+ <p>تاريخ تصميم المكتبة : ماي 2026</p>
523
+
524
+ <h1 id="conclusion">الخاتمة</h1>
525
+ <p>يهدف هذا المشروع إلى جعل تطبيقات Qt متاحة بنسبة 100% للمطورين والمستخدمين المكفوفين، دون التضحية بالإنتاجية أو عادات مطوري Qt.</p>
526
+ </body>
527
+ </html>