1 | /* This file is part of the KDE project |
2 | * Copyright (C) 2010 Sebastian Sauer <sebsauer@kdab.com> |
3 | * |
4 | * This library is free software; you can redistribute it and/or |
5 | * modify it under the terms of the GNU Library General Public |
6 | * License as published by the Free Software Foundation; either |
7 | * version 2 of the License, or (at your option) any later version. |
8 | * |
9 | * This library is distributed in the hope that it will be useful, |
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
12 | * Library General Public License for more details. |
13 | * |
14 | * You should have received a copy of the GNU Library General Public License |
15 | * along with this library; see the file COPYING.LIB. If not, write to |
16 | * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, |
17 | * Boston, MA 02110-1301, USA. |
18 | */ |
19 | |
20 | #include "kaccessibleapp.h" |
21 | #include "kaccessibleinterface.h" |
22 | |
23 | #include <QMainWindow> |
24 | #include <QMenu> |
25 | #include <QLayout> |
26 | #include <QTimer> |
27 | #include <QStack> |
28 | #include <QTextStream> |
29 | #include <QLabel> |
30 | #include <QCheckBox> |
31 | #include <QTreeWidget> |
32 | #include <QTreeWidgetItem> |
33 | #include <QClipboard> |
34 | #include <QMutex> |
35 | #include <QMutexLocker> |
36 | #include <QDBusConnection> |
37 | #include <QDBusInterface> |
38 | #include <QDBusPendingCall> |
39 | #include <QDBusArgument> |
40 | #include <QDBusMetaType> |
41 | #include <kmainwindow.h> |
42 | #include <kconfig.h> |
43 | #include <kcombobox.h> |
44 | #include <kconfiggroup.h> |
45 | #include <klocale.h> |
46 | #include <kicon.h> |
47 | #include <kaboutdata.h> |
48 | #include <kcmdlineargs.h> |
49 | #include <kinputdialog.h> |
50 | #include <kaction.h> |
51 | #include <ktoggleaction.h> |
52 | #include <kactioncollection.h> |
53 | #include <ksystemtrayicon.h> |
54 | #include <kdebug.h> |
55 | #include <kpagewidget.h> |
56 | #include <kpagewidgetmodel.h> |
57 | |
58 | #if defined(SPEECHD_FOUND) |
59 | #include <libspeechd.h> |
60 | #endif |
61 | |
62 | Q_GLOBAL_STATIC(Speaker, speaker) |
63 | |
64 | class Speaker::Private |
65 | { |
66 | public: |
67 | bool m_isSpeaking; |
68 | int m_voiceType; |
69 | QStack< QPair<QString,Speaker::Priority> > m_sayStack; |
70 | QMutex m_mutex; |
71 | #if defined(SPEECHD_FOUND) |
72 | SPDConnection *m_connection; |
73 | #endif |
74 | explicit Private() |
75 | : m_isSpeaking(false) |
76 | , m_voiceType(1) |
77 | #if defined(SPEECHD_FOUND) |
78 | , m_connection(0) |
79 | #endif |
80 | { |
81 | } |
82 | #if defined(SPEECHD_FOUND) |
83 | static void speechdCallback(size_t msg_id, size_t client_id, SPDNotificationType state) |
84 | { |
85 | Q_UNUSED(msg_id); |
86 | Q_UNUSED(client_id); |
87 | switch(state) { |
88 | case SPD_EVENT_BEGIN: |
89 | Speaker::instance()->setSpeaking(true); |
90 | break; |
91 | case SPD_EVENT_END: |
92 | Speaker::instance()->setSpeaking(false); |
93 | QTimer::singleShot(0, Speaker::instance(), SLOT(sayNext())); |
94 | break; |
95 | case SPD_EVENT_CANCEL: |
96 | Speaker::instance()->setSpeaking(false); |
97 | Speaker::instance()->clearSayStack(); |
98 | break; |
99 | case SPD_EVENT_PAUSE: |
100 | break; |
101 | case SPD_EVENT_RESUME: |
102 | break; |
103 | case SPD_EVENT_INDEX_MARK: |
104 | break; |
105 | } |
106 | } |
107 | #endif |
108 | }; |
109 | |
110 | Speaker::Speaker() |
111 | : d(new Private) |
112 | { |
113 | } |
114 | |
115 | Speaker::~Speaker() |
116 | { |
117 | disconnect(); |
118 | delete d; |
119 | } |
120 | |
121 | Speaker* Speaker::instance() |
122 | { |
123 | return speaker(); |
124 | } |
125 | |
126 | bool Speaker::isConnected() const |
127 | { |
128 | #if defined(SPEECHD_FOUND) |
129 | return d->m_connection; |
130 | #else |
131 | return true; |
132 | #endif |
133 | } |
134 | |
135 | void Speaker::disconnect() |
136 | { |
137 | #if defined(SPEECHD_FOUND) |
138 | if(d->m_connection) { |
139 | spd_set_notification_off(d->m_connection, SPD_BEGIN); |
140 | spd_set_notification_off(d->m_connection, SPD_END); |
141 | spd_set_notification_off(d->m_connection, SPD_CANCEL); |
142 | spd_set_notification_off(d->m_connection, SPD_PAUSE); |
143 | spd_set_notification_off(d->m_connection, SPD_RESUME); |
144 | d->m_connection->callback_begin = d->m_connection->callback_end = d->m_connection->callback_cancel = d->m_connection->callback_pause = d->m_connection->callback_resume = 0; |
145 | spd_cancel_all(d->m_connection); |
146 | spd_close(d->m_connection); |
147 | d->m_connection = 0; |
148 | d->m_isSpeaking = false; |
149 | d->m_sayStack.clear(); |
150 | } |
151 | #endif |
152 | } |
153 | |
154 | bool Speaker::reconnect() |
155 | { |
156 | disconnect(); |
157 | |
158 | #if defined(SPEECHD_FOUND) |
159 | d->m_connection = spd_open("kaccessible" , "main" , NULL, SPD_MODE_THREADED); //SPD_MODE_SINGLE); |
160 | if( ! d->m_connection) { |
161 | kWarning() << "Failed to connect with speech-dispatcher" ; |
162 | return false; |
163 | } |
164 | |
165 | d->m_connection->callback_begin = d->m_connection->callback_end = d->m_connection->callback_cancel = d->m_connection->callback_pause = d->m_connection->callback_resume = Private::speechdCallback; |
166 | spd_set_notification_on(d->m_connection, SPD_BEGIN); |
167 | spd_set_notification_on(d->m_connection, SPD_END); |
168 | spd_set_notification_on(d->m_connection, SPD_CANCEL); |
169 | spd_set_notification_on(d->m_connection, SPD_PAUSE); |
170 | spd_set_notification_on(d->m_connection, SPD_RESUME); |
171 | #endif |
172 | |
173 | setVoiceType(d->m_voiceType); |
174 | return true; |
175 | } |
176 | |
177 | bool Speaker::isSpeaking() const |
178 | { |
179 | return d->m_isSpeaking; |
180 | } |
181 | |
182 | void Speaker::setSpeaking(bool speaking) |
183 | { |
184 | d->m_isSpeaking = speaking; |
185 | } |
186 | |
187 | void Speaker::cancel() |
188 | { |
189 | QMutexLocker locker(&d->m_mutex); |
190 | d->m_sayStack.clear(); |
191 | #if defined(SPEECHD_FOUND) |
192 | if(d->m_connection) { |
193 | spd_cancel_all(d->m_connection); |
194 | } |
195 | #endif |
196 | } |
197 | |
198 | bool Speaker::say(const QString& text, Priority priority) |
199 | { |
200 | QMutexLocker locker(&d->m_mutex); |
201 | if(!isConnected()) |
202 | return false; |
203 | d->m_sayStack.push( QPair<QString,Speaker::Priority>(text,priority) ); |
204 | if(!d->m_isSpeaking) |
205 | QTimer::singleShot(0, this, SLOT(sayNext())); |
206 | return true; |
207 | } |
208 | |
209 | void Speaker::sayNext() |
210 | { |
211 | QMutexLocker locker(&d->m_mutex); |
212 | if(d->m_sayStack.isEmpty()) { |
213 | return; |
214 | } |
215 | QPair<QString,Speaker::Priority> p = d->m_sayStack.pop(); |
216 | #if defined(SPEECHD_FOUND) |
217 | if(d->m_connection) { |
218 | SPDPriority spdpriority = (SPDPriority) p.second; |
219 | int msg_id = spd_say(d->m_connection, spdpriority, p.first.toUtf8().data()); |
220 | if(msg_id == -1) { |
221 | kWarning() << "Failed to say text=" << p.first; |
222 | } |
223 | } |
224 | #else |
225 | //QDBusInterface iface("org.kde.jovie","/KSpeech"); |
226 | //iface.asyncCall("say", text, 0); |
227 | #endif |
228 | } |
229 | |
230 | char** Speaker::modules() const |
231 | { |
232 | #if defined(SPEECHD_FOUND) |
233 | if(d->m_connection) |
234 | return spd_list_modules(d->m_connection); |
235 | #endif |
236 | return NULL; |
237 | } |
238 | |
239 | char** Speaker::voices() const |
240 | { |
241 | #if defined(SPEECHD_FOUND) |
242 | if(d->m_connection) |
243 | return spd_list_voices(d->m_connection); |
244 | #endif |
245 | return NULL; |
246 | } |
247 | |
248 | QStringList Speaker::languages() const |
249 | { |
250 | QStringList result; |
251 | #if defined(SPEECHD_FOUND) |
252 | if(d->m_connection) { |
253 | SPDVoice** voices = spd_list_synthesis_voices(d->m_connection); |
254 | while(voices && voices[0]) { |
255 | const QString lng = QString::fromLatin1(voices[0]->language); |
256 | if(!lng.isEmpty() && !result.contains(lng)) result.append(lng); |
257 | ++voices; |
258 | } |
259 | } |
260 | #endif |
261 | return result; |
262 | } |
263 | |
264 | int Speaker::voiceType() const |
265 | { |
266 | return d->m_voiceType; |
267 | } |
268 | |
269 | void Speaker::setVoiceType(int type) |
270 | { |
271 | d->m_voiceType = type; |
272 | #if defined(SPEECHD_FOUND) |
273 | if(d->m_connection) { |
274 | spd_set_voice_type_all(d->m_connection, (SPDVoiceType) type); |
275 | } |
276 | #endif |
277 | } |
278 | |
279 | void Speaker::clearSayStack() |
280 | { |
281 | d->m_sayStack.clear(); |
282 | } |
283 | |
284 | class Adaptor::Private |
285 | { |
286 | public: |
287 | bool m_speechEnabled; |
288 | explicit Private() : m_speechEnabled(false) {} |
289 | }; |
290 | |
291 | Adaptor::Adaptor(QObject *parent) |
292 | : QObject(parent) |
293 | , d(new Private) |
294 | { |
295 | KConfig config(QLatin1String( "kaccessibleapp" )); |
296 | KConfigGroup group = config.group("Main" ); |
297 | d->m_speechEnabled = group.readEntry("SpeechEnabled" , d->m_speechEnabled); |
298 | |
299 | const int prevVoiceType = Speaker::instance()->voiceType(); |
300 | const int newVoiceType = group.readEntry("VoiceType" , prevVoiceType); |
301 | if(prevVoiceType != newVoiceType) |
302 | Speaker::instance()->setVoiceType(newVoiceType); |
303 | } |
304 | |
305 | Adaptor::~Adaptor() |
306 | { |
307 | delete d; |
308 | } |
309 | |
310 | void Adaptor::setFocusChanged(const KAccessibleInterface& iface) |
311 | { |
312 | int px = -1; |
313 | int py = -1; |
314 | QRect r = iface.rect; |
315 | emit focusChanged(px, py, r.x(), r.y(), r.width(), r.height()); |
316 | |
317 | emit notified(QAccessible::Focus, iface); |
318 | |
319 | QString text = iface.name; |
320 | /* |
321 | if(!iface.accelerator.isEmpty()) { |
322 | QString s = iface.accelerator; |
323 | s = s.replace("+"," "); |
324 | text += " " + s; |
325 | } |
326 | */ |
327 | sayText(text); |
328 | } |
329 | |
330 | void Adaptor::setValueChanged(const KAccessibleInterface& iface) |
331 | { |
332 | sayText(iface.value); |
333 | } |
334 | |
335 | void Adaptor::setAlert(const KAccessibleInterface& iface) |
336 | { |
337 | Speaker::instance()->cancel(); |
338 | sayText(iface.name, int(Speaker::Message)); |
339 | } |
340 | |
341 | void Adaptor::sayText(const QString& text, int priority) |
342 | { |
343 | if(d->m_speechEnabled && !text.isEmpty() && (Speaker::instance()->isConnected() || Speaker::instance()->reconnect())) { |
344 | Speaker::instance()->say(text, Speaker::Priority(priority)); |
345 | } |
346 | } |
347 | |
348 | bool Adaptor::speechEnabled() const |
349 | { |
350 | return d->m_speechEnabled; |
351 | } |
352 | |
353 | void Adaptor::setSpeechEnabled(bool enabled) |
354 | { |
355 | if(d->m_speechEnabled == enabled) |
356 | return; |
357 | |
358 | d->m_speechEnabled = enabled; |
359 | |
360 | KConfig config(QLatin1String( "kaccessibleapp" )); |
361 | KConfigGroup group = config.group("Main" ); |
362 | group.writeEntry("SpeechEnabled" , d->m_speechEnabled); |
363 | |
364 | if(!d->m_speechEnabled) { |
365 | Speaker::instance()->cancel(); |
366 | } |
367 | |
368 | emit speechEnabledChanged(d->m_speechEnabled); |
369 | } |
370 | |
371 | int Adaptor::voiceType() const |
372 | { |
373 | return Speaker::instance()->voiceType(); |
374 | } |
375 | |
376 | void Adaptor::setVoiceType(int type) |
377 | { |
378 | if(type == Speaker::instance()->voiceType()) |
379 | return; |
380 | |
381 | KConfig config(QLatin1String( "kaccessibleapp" )); |
382 | KConfigGroup group = config.group("Main" ); |
383 | group.writeEntry("VoiceType" , type); |
384 | |
385 | Speaker::instance()->setVoiceType(type); |
386 | } |
387 | |
388 | class KAccessibleApp::Private |
389 | { |
390 | public: |
391 | Adaptor *m_adaptor; |
392 | QMap<QString, KAction*> m_collection; |
393 | explicit Private() : m_adaptor(0) {} |
394 | ~Private() { qDeleteAll(m_collection); } |
395 | }; |
396 | |
397 | KAccessibleApp::KAccessibleApp() |
398 | : KUniqueApplication() |
399 | , d(new Private) |
400 | { |
401 | qDBusRegisterMetaType<KAccessibleInterface>(); |
402 | |
403 | setWindowIcon(KIcon(QLatin1String( "preferences-desktop-accessibility" ))); |
404 | setQuitOnLastWindowClosed(false); |
405 | |
406 | d->m_adaptor = new Adaptor(this); |
407 | if( ! QDBusConnection::sessionBus().registerObject(QLatin1String( "/Adaptor" ), d->m_adaptor, QDBusConnection::ExportAllContents)) { |
408 | kWarning() << "Unable to register KAccessibleApp to dbus" ; |
409 | QTimer::singleShot(0, this, SLOT(quit())); |
410 | } else { |
411 | KToggleAction* enableScreenreaderAction = new KToggleAction(this); |
412 | enableScreenreaderAction->setText(i18n("Enable Screenreader" )); |
413 | enableScreenreaderAction->setIcon(KIcon(QLatin1String( "text-speak" ))); |
414 | enableScreenreaderAction->setChecked(d->m_adaptor->speechEnabled()); |
415 | connect(enableScreenreaderAction, SIGNAL(triggered(bool)), this, SLOT(enableScreenreader(bool))); |
416 | connect(d->m_adaptor, SIGNAL(speechEnabledChanged(bool)), enableScreenreaderAction, SLOT(setChecked(bool))); |
417 | d->m_collection.insert(QLatin1String( "enableScreenreader" ), enableScreenreaderAction); |
418 | |
419 | KAction* speakTextAction = new KAction(this); |
420 | speakTextAction->setText(i18n("Speak Text..." )); |
421 | speakTextAction->setIcon(KIcon(QLatin1String( "text-plain" ))); |
422 | connect(speakTextAction, SIGNAL(triggered(bool)), this, SLOT(speakText())); |
423 | d->m_collection.insert(QLatin1String( "speakText" ), speakTextAction); |
424 | |
425 | KAction* speakClipboardAction = new KAction(this); |
426 | speakClipboardAction->setText(i18n("Speak Clipboard" )); |
427 | speakClipboardAction->setIcon(KIcon(QLatin1String( "klipper" ))); |
428 | connect(speakClipboardAction, SIGNAL(triggered(bool)), this, SLOT(speakClipboard())); |
429 | d->m_collection.insert(QLatin1String( "speakClipboard" ), speakClipboardAction); |
430 | } |
431 | } |
432 | |
433 | KAccessibleApp::~KAccessibleApp() |
434 | { |
435 | delete d; |
436 | } |
437 | |
438 | Adaptor* KAccessibleApp::adaptor() const |
439 | { |
440 | return d->m_adaptor; |
441 | } |
442 | |
443 | KAction* KAccessibleApp::action(const QString &name) const |
444 | { |
445 | return d->m_collection.contains(name) ? d->m_collection[name] : 0; |
446 | } |
447 | |
448 | void KAccessibleApp::enableScreenreader(bool enabled) |
449 | { |
450 | d->m_adaptor->setSpeechEnabled(enabled); |
451 | } |
452 | |
453 | void KAccessibleApp::speakClipboard() |
454 | { |
455 | const QString text = clipboard()->text(); |
456 | if(!text.isEmpty()) { |
457 | //Speaker::instance()->cancel(); |
458 | Speaker::instance()->say(text); |
459 | } |
460 | } |
461 | |
462 | void KAccessibleApp::speakText() |
463 | { |
464 | const QString text = KInputDialog::getText(i18n("Speak Text" ), i18n("Type the text and press OK to speak the text." )); |
465 | if(!text.isEmpty()) { |
466 | //Speaker::instance()->cancel(); |
467 | Speaker::instance()->say(text); |
468 | } |
469 | } |
470 | |
471 | /// class for the icon shown in the systemtray. |
472 | class SystemTray : public KSystemTrayIcon |
473 | { |
474 | public: |
475 | explicit SystemTray(KAccessibleApp *app, QWidget* parent) |
476 | : KSystemTrayIcon(QLatin1String( "preferences-desktop-accessibility" ), parent) |
477 | { |
478 | //QAction *titleAction = contextMenuTitle(); |
479 | //titleAction->setText(i18n("Accessibility Bridge")); |
480 | //titleAction->setIcon(KIcon("preferences-desktop-accessibility")); |
481 | //setContextMenuTitle(titleAction); |
482 | |
483 | //QAction* fileQuitAction = actionCollection()->action("file_quit"); |
484 | //if(fileQuitAction) delete actionCollection()->takeAction(fileQuitAction); |
485 | |
486 | foreach(const QString &name, QStringList() << QLatin1String( "enableScreenreader" ) << QLatin1String( "speakText" ) << QLatin1String( "speakClipboard" )) |
487 | if(KAction* action = app->action(name)) |
488 | contextMenu()->addAction(action); |
489 | |
490 | //QMenu *popup = dynamic_cast<QMenu*>( KAccessibleApp::App->factory()->container("systemtray_actions", KAccessibleApp::App) ); |
491 | //if (popup) contextMenu()->addActions( popup->actions() ); |
492 | } |
493 | virtual ~SystemTray() |
494 | { |
495 | } |
496 | protected: |
497 | bool event(QEvent *event) |
498 | { |
499 | /* |
500 | if( event->type() == QEvent::ToolTip ) { |
501 | QHelpEvent *helpEvent = static_cast<QHelpEvent*>(event); |
502 | QToolTip::showText(helpEvent->globalPos(), QString("<b>Accessibility Bridge</b><br>%1").arg(tooltip)); |
503 | //QToolTip::hideText(); |
504 | } |
505 | */ |
506 | return KSystemTrayIcon::event(event); |
507 | } |
508 | }; |
509 | |
510 | class MainWindow::Private |
511 | { |
512 | public: |
513 | KAccessibleApp *m_app; |
514 | Adaptor *m_adaptor; |
515 | SystemTray *m_systemtray; |
516 | KPageWidget *m_pageTab; |
517 | KPageWidgetModel *m_pageModel; |
518 | KComboBox* m_voiceTypeCombo; |
519 | QTreeWidget *m_logs; |
520 | bool m_hideMainWin; |
521 | bool m_logEnabled; |
522 | |
523 | explicit Private(KAccessibleApp *app) : m_app(app), m_adaptor(app->adaptor()), m_systemtray(0), m_pageTab(0), m_pageModel(0), m_voiceTypeCombo(0), m_logs(0), m_hideMainWin(false), m_logEnabled(false) {} |
524 | |
525 | void addPage(QWidget* page, const QIcon& iconset, const QString& label) |
526 | { |
527 | QWidget* p = dynamic_cast<QWidget*>(page); |
528 | if (!p) return; |
529 | KPageWidgetItem* item = m_pageModel->addPage(p, QLatin1String( "" )); //p->objectName()); |
530 | item->setName(label); |
531 | item->setHeader(QLatin1String( "" )); // hide the header |
532 | item->setIcon(KIcon(iconset)); |
533 | QModelIndex index = m_pageModel->index(item); |
534 | Q_ASSERT(index.isValid()); |
535 | m_pageModel->setData(index, QVariant(), Qt::DisplayRole); |
536 | m_pageModel->setData(index, QVariant(), KPageModel::HeaderRole); |
537 | //pages.append(page); |
538 | //page->plugGenericActions(this, SLOT(pageActionsChanged(KMLDonkeyPage*))); |
539 | } |
540 | }; |
541 | |
542 | MainWindow::MainWindow(KAccessibleApp *app) |
543 | : KMainWindow() |
544 | , d(new Private(app)) |
545 | { |
546 | KConfig config(QLatin1String( "kaccessibleapp" )); |
547 | KConfigGroup group = config.group("Main" ); |
548 | d->m_logEnabled = group.readEntry("LogEnabled" , d->m_logEnabled); |
549 | |
550 | const int prevVoiceType = Speaker::instance()->voiceType(); |
551 | const int newVoiceType = group.readEntry("VoiceType" , prevVoiceType); |
552 | if(prevVoiceType != newVoiceType) |
553 | Speaker::instance()->setVoiceType(newVoiceType); |
554 | |
555 | d->m_systemtray = new SystemTray(app, this); |
556 | d->m_systemtray->show(); |
557 | |
558 | d->m_pageTab = new KPageWidget(this); |
559 | d->m_pageTab->setFaceType( KPageView::Tabbed ); //Auto,Plain,List,Tree,Tabbed |
560 | d->m_pageTab->layout()->setMargin(0); |
561 | |
562 | d->m_pageModel = new KPageWidgetModel(d->m_pageTab); |
563 | d->m_pageTab->setModel(d->m_pageModel); |
564 | |
565 | QWidget* readerPage = new QWidget(d->m_pageTab); |
566 | QGridLayout* readerLayout = new QGridLayout(readerPage); |
567 | readerLayout->setMargin(0); |
568 | readerPage->setLayout(readerLayout); |
569 | QCheckBox *enableReader = new QCheckBox(i18n("Enable Screenreader" )); |
570 | readerLayout->addWidget(enableReader,0,0,1,2); |
571 | #if defined(SPEECHD_FOUND) |
572 | enableReader->setChecked(d->m_adaptor->speechEnabled()); |
573 | connect(d->m_adaptor, SIGNAL(speechEnabledChanged(bool)), enableReader, SLOT(setChecked(bool))); |
574 | connect(enableReader, SIGNAL(stateChanged(int)), this, SLOT(enableReaderChanged(int))); |
575 | |
576 | QLabel *voiceTypeLabel = new QLabel(i18n("Voice Type:" ), readerPage); |
577 | readerLayout->addWidget(voiceTypeLabel,1,0); |
578 | d->m_voiceTypeCombo = new KComboBox(this); |
579 | voiceTypeLabel->setBuddy(d->m_voiceTypeCombo); |
580 | d->m_voiceTypeCombo->addItem(i18n("Male 1" ), SPD_MALE1); |
581 | d->m_voiceTypeCombo->addItem(i18n("Male 2" ), SPD_MALE2); |
582 | d->m_voiceTypeCombo->addItem(i18n("Male 3" ), SPD_MALE3); |
583 | d->m_voiceTypeCombo->addItem(i18n("Female 1" ), SPD_FEMALE1); |
584 | d->m_voiceTypeCombo->addItem(i18n("Female 2" ), SPD_FEMALE2); |
585 | d->m_voiceTypeCombo->addItem(i18n("Female 3" ), SPD_FEMALE3); |
586 | d->m_voiceTypeCombo->addItem(i18n("Boy" ), SPD_CHILD_MALE); |
587 | d->m_voiceTypeCombo->addItem(i18n("Girl" ), SPD_CHILD_FEMALE); |
588 | for(int i = 0; i < d->m_voiceTypeCombo->count(); ++i) |
589 | if(d->m_voiceTypeCombo->itemData(i).toInt() == Speaker::instance()->voiceType()) |
590 | d->m_voiceTypeCombo->setCurrentIndex(i); |
591 | connect(d->m_voiceTypeCombo, SIGNAL(activated(int)), this, SLOT(voiceTypeChanged(int))); |
592 | readerLayout->addWidget(d->m_voiceTypeCombo,1,1); |
593 | |
594 | readerLayout->setRowStretch(2,1); |
595 | #else |
596 | enableReader->setEnabled(false); |
597 | readerLayout->setRowStretch(1,1); |
598 | #endif |
599 | readerLayout->setColumnStretch(2,1); |
600 | d->addPage(readerPage, KIcon(QLatin1String( "text-speak" )), i18n("Screenreader" )); |
601 | |
602 | QWidget* logsPage = new QWidget(d->m_pageTab); |
603 | QVBoxLayout* logsLayout = new QVBoxLayout(logsPage); |
604 | logsLayout->setMargin(0); |
605 | logsPage->setLayout(logsLayout); |
606 | QCheckBox *enableLogsCheckbox = new QCheckBox(i18n("Enable Logs" )); |
607 | logsLayout->addWidget(enableLogsCheckbox); |
608 | d->m_logs = new QTreeWidget(logsPage); |
609 | d->m_logs->setColumnCount(10); |
610 | d->m_logs->setHeaderLabels(QStringList() << i18n("Reason" ) << i18n("Type" ) << i18n("Class" ) << i18n("Name" ) << i18n("Value" ) << i18n("Accelerator" ) << i18n("State" ) << i18n("Rect" ) << i18n("Object" ) << i18n("Description" )); |
611 | d->m_logs->setRootIsDecorated(false); |
612 | d->m_logs->setEnabled(false); |
613 | if(d->m_logEnabled) { |
614 | enableLogsCheckbox->setChecked(Qt::Checked); |
615 | enableLogs(Qt::Checked); |
616 | } |
617 | connect(enableLogsCheckbox, SIGNAL(stateChanged(int)), this, SLOT(enableLogs(int))); |
618 | logsLayout->addWidget(d->m_logs); |
619 | d->addPage(logsPage, KIcon(QLatin1String( "view-list-text" )), i18n("Logs" )); |
620 | |
621 | setCentralWidget(d->m_pageTab); |
622 | resize(QSize(460, 320).expandedTo(minimumSizeHint())); |
623 | setAutoSaveSettings(); |
624 | } |
625 | |
626 | MainWindow::~MainWindow() |
627 | { |
628 | delete d; |
629 | } |
630 | |
631 | // #### WRONG, show() is not virtual in QWidget (since Qt 4.0) |
632 | void MainWindow::show() |
633 | { |
634 | if (!d->m_hideMainWin) { |
635 | d->m_hideMainWin = false; |
636 | KMainWindow::show(); |
637 | } |
638 | } |
639 | |
640 | // Deprecated, won't be called in KF5. Replace with closeEvent, possibly. |
641 | bool MainWindow::queryExit() |
642 | { |
643 | d->m_hideMainWin = isHidden(); |
644 | return true; |
645 | } |
646 | |
647 | bool MainWindow::queryClose() |
648 | { |
649 | if (!QObject::sender()) { // ### Looks very fishy, what is this really testing for? |
650 | hide(); |
651 | return false; |
652 | } |
653 | return true; |
654 | } |
655 | |
656 | void MainWindow::notified(int reason, const KAccessibleInterface& iface) |
657 | { |
658 | QTreeWidgetItem *root = d->m_logs->invisibleRootItem(); |
659 | if(root->childCount() > 1000) delete root->takeChild(0); |
660 | |
661 | QTreeWidgetItem *child = new QTreeWidgetItem(root); |
662 | child->setText(1, iface.className); |
663 | child->setText(2, iface.name); |
664 | child->setText(3, iface.value); |
665 | child->setText(4, iface.accelerator); |
666 | child->setText(5, stateToString(iface.state)); |
667 | child->setText(6, QString(QLatin1String("%1,%2,%3,%4" )).arg(iface.rect.x()).arg(iface.rect.y()).arg(iface.rect.width()).arg(iface.rect.height())); |
668 | child->setText(7, iface.objectName); |
669 | child->setText(8, iface.description); |
670 | d->m_logs->scrollToItem(child); |
671 | } |
672 | |
673 | void MainWindow::enableLogs(int state) |
674 | { |
675 | const bool logEnabled = state == Qt::Checked; |
676 | if(logEnabled) { |
677 | connect(d->m_app->adaptor(), SIGNAL(notified(int,KAccessibleInterface)), this, SLOT(notified(int,KAccessibleInterface))); |
678 | } else { |
679 | disconnect(d->m_app->adaptor(), SIGNAL(notified(int,KAccessibleInterface)), this, SLOT(notified(int,KAccessibleInterface))); |
680 | d->m_logs->clear(); |
681 | } |
682 | |
683 | d->m_logs->setEnabled(logEnabled); |
684 | |
685 | if(d->m_logEnabled != logEnabled) { |
686 | d->m_logEnabled = logEnabled; |
687 | KConfig config(QLatin1String( "kaccessibleapp" )); |
688 | KConfigGroup group = config.group("Main" ); |
689 | group.writeEntry("LogEnabled" , d->m_logEnabled); |
690 | } |
691 | } |
692 | |
693 | void MainWindow::enableReaderChanged(int state) |
694 | { |
695 | d->m_adaptor->setSpeechEnabled(state == Qt::Checked); |
696 | } |
697 | |
698 | void MainWindow::voiceTypeChanged(int index) |
699 | { |
700 | d->m_adaptor->setVoiceType(d->m_voiceTypeCombo->itemData(index).toInt()); |
701 | } |
702 | |
703 | int main(int argc, char *argv[]) |
704 | { |
705 | KAboutData aboutData("kaccessibleapp" , "" , |
706 | ki18n("KDE Accessible" ), "0.4" , |
707 | ki18n("KDE Accessible" ), KAboutData::License_GPL, |
708 | ki18n("(c) 2010, 2011 Sebastian Sauer" )); |
709 | aboutData.addAuthor(ki18n("Sebastian Sauer" ), ki18n("Maintainer" ), "sebastian.sauer@kdab.com" ); |
710 | KCmdLineArgs::init(argc, argv, &aboutData); |
711 | KUniqueApplication::addCmdLineOptions(); |
712 | if (!KUniqueApplication::start()) { |
713 | fprintf(stderr, "kaccessibleapp is already running!\n" ); |
714 | return 0; |
715 | } |
716 | |
717 | KAccessibleApp app; |
718 | |
719 | MainWindow window(&app); |
720 | //window.show(); |
721 | |
722 | return app.exec(); |
723 | } |
724 | |