KoSpeaker.cpp
1/*
2* This file is part of the KDE/KOffice project.
3* Copyright (C) 2005, Gary Cramblitt <garycramblitt@comcast.net>
4*
5* @author Gary Cramblitt <garycramblitt@comcast.net>
6* @since KOffice 1.5
7*
8* This library is free software; you can redistribute it and/or
9* modify it under the terms of the GNU Library General Public
10* License as published by the Free Software Foundation; either
11* version 2 of the License, or (at your option) any later version.
12*
13* This library is distributed in the hope that it will be useful,
14* but WITHOUT ANY WARRANTY; without even the implied warranty of
15* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16* Library General Public License for more details.
17*
18* You should have received a copy of the GNU Library General Public License
19* along with this library; see the file COPYING.LIB. If not, write to
20* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
21* Boston, MA 02110-1301, USA.
22*/
23
24// TQt includes.
25#include <tqtimer.h>
26#include <tqcursor.h>
27#include <tqtooltip.h>
28#include <tqwhatsthis.h>
29#include <tqmenubar.h>
30#include <tqlabel.h>
31#include <tqbutton.h>
32#include <tqcombobox.h>
33#include <tqtabbar.h>
34#include <tqgroupbox.h>
35#include <tqlineedit.h>
36#include <tqtextedit.h>
37#include <tqlistview.h>
38#include <tqlistbox.h>
39#include <tqiconview.h>
40#include <tqtable.h>
41#include <tqgridview.h>
42#include <tqregexp.h>
43#include <tqstylesheet.h>
44
45// KDE includes.
46#include <tdeapplication.h>
47#include <tdelocale.h>
48#include <tdeglobal.h>
49#include <dcopclient.h>
50#include <tdeconfig.h>
51#include <ktrader.h>
52#include <kdebug.h>
53
54// KoSpeaker includes.
55#include "KoSpeaker.h"
56#include "KoSpeaker.moc"
57
58// ------------------ KoSpeakerPrivate ------------------------
59
60class KoSpeakerPrivate
61{
62public:
63 KoSpeakerPrivate() :
64 m_versionChecked(false),
65 m_enabled(false),
66 m_speakFlags(0),
67 m_timeout(600),
68 m_timer(0),
69 m_prevPointerWidget(0),
70 m_prevPointerId(-1),
71 m_prevFocusWidget(0),
72 m_prevFocusId(-1),
73 m_prevWidget(0),
74 m_prevId(-1),
75 m_cancelSpeakWidget(false)
76 {}
77
78 // List of text jobs.
79 TQValueList<uint> m_jobNums;
80 // Whether the version of KTTSD has been requested from the daemon.
81 bool m_versionChecked;
82 // KTTSD version string.
83 TQString m_kttsdVersion;
84 // Language code of last spoken text.
85 TQString m_langCode;
86 // Word used before speaking an accelerator letter.
87 TQString m_acceleratorPrefix;
88 // Whether TTS service is available or not.
89 bool m_enabled;
90 // TTS options.
91 uint m_speakFlags;
92 // Timer which implements the polling interval.
93 int m_timeout;
94 TQTimer* m_timer;
95 // Widget and part of widget for 1) last widget under mouse pointer, 2) last widget with focus, and
96 // last widget spoken.
97 TQWidget* m_prevPointerWidget;
98 int m_prevPointerId;
99 TQWidget* m_prevFocusWidget;
100 int m_prevFocusId;
101 TQWidget* m_prevWidget;
102 int m_prevId;
103 // True when cancelSpeakWidget has been called in response to customSpeakWidget signal.
104 bool m_cancelSpeakWidget;
105};
106
107// ------------------ KoSpeaker -------------------------------
108
109KoSpeaker* KoSpeaker::KSpkr = 0L;
110
111KoSpeaker::KoSpeaker()
112{
113 Q_ASSERT(!KSpkr);
114 KSpkr = this;
115 d = new KoSpeakerPrivate();
116 readConfig(TDEGlobal::config());
117}
118
119KoSpeaker::~KoSpeaker()
120{
121 if (d->m_jobNums.count() > 0) {
122 for (int i = d->m_jobNums.count() - 1; i >= 0; i--)
123 removeText(d->m_jobNums[i]);
124 d->m_jobNums.clear();
125 }
126 delete d;
127 KSpkr = 0;
128}
129
130bool KoSpeaker::isEnabled() const { return d->m_enabled; }
131
132void KoSpeaker::probe()
133{
134 d->m_timer->stop();
135 TQWidget* w;
136 TQPoint pos;
137 bool spoke = false;
138 if ( d->m_speakFlags & SpeakFocusWidget ) {
139 w = kapp->focusWidget();
140 if (w) {
141 spoke = maybeSayWidget(w);
142 if (!spoke)
143 emit customSpeakWidget(w, pos, d->m_speakFlags);
144 }
145 }
146 if ( !spoke && d->m_speakFlags & SpeakPointerWidget ) {
147 pos = TQCursor::pos();
148 w = kapp->widgetAt(pos, true);
149 if (w) {
150 if (!maybeSayWidget(w, pos))
151 emit customSpeakWidget(w, pos, d->m_speakFlags);
152 }
153 }
154 d->m_timer->start(d->m_timeout);
155}
156
157void KoSpeaker::queueSpeech(const TQString& msg, const TQString& langCode /*= TQString()*/, bool first /*= true*/)
158{
159 if (!startKttsd()) return;
160 int jobCount = d->m_jobNums.count();
161 if (first && jobCount > 0) {
162 for (int i = jobCount - 1; i >= 0; i--)
163 removeText(d->m_jobNums[i]);
164 d->m_jobNums.clear();
165 jobCount = 0;
166 }
167 TQString s = msg.stripWhiteSpace();
168 if (s.isEmpty()) return;
169 // kdDebug() << "KoSpeaker::queueSpeech: s = [" << s << "]" << endl;
170 // If no language code given, assume desktop setting.
171 TQString languageCode = langCode;
172 if (langCode.isEmpty())
173 languageCode = TDEGlobal::locale()->language();
174 // kdDebug() << "KoSpeaker::queueSpeech:languageCode = " << languageCode << endl;
175 // If KTTSD version is 0.3.5 or later, we can use the appendText method to submit a
176 // single, multi-part text job. Otherwise, must submit separate text jobs.
177 // If language code changes, then must also start a new text job so that it will
178 // be spoken in correct talker.
179 if (getKttsdVersion().isEmpty())
180 d->m_jobNums.append(setText(s, languageCode));
181 else {
182 if ((jobCount == 0) || (languageCode != d->m_langCode))
183 d->m_jobNums.append(setText(s, languageCode));
184 else
185 appendText(s, d->m_jobNums[jobCount-1]);
186 }
187 d->m_langCode = languageCode;
188}
189
191{
192 for (uint i = 0; i < d->m_jobNums.count(); i++)
193 startText(d->m_jobNums[i]);
194}
195
196void KoSpeaker::readConfig(TDEConfig* config)
197{
198 delete d->m_timer;
199 d->m_timer = 0;
200 config->setGroup("TTS");
201 d->m_speakFlags = 0;
202 if (config->readBoolEntry("SpeakPointerWidget", false)) d->m_speakFlags |= SpeakPointerWidget;
203 if (config->readBoolEntry("SpeakFocusWidget", false)) d->m_speakFlags |= SpeakFocusWidget;
204 if (config->readBoolEntry("SpeakTooltips", true)) d->m_speakFlags |= SpeakTooltip;
205 if (config->readBoolEntry("SpeakWhatsThis", false)) d->m_speakFlags |= SpeakWhatsThis;
206 if (config->readBoolEntry("SpeakDisabled", true)) d->m_speakFlags |= SpeakDisabled;
207 if (config->readBoolEntry("SpeakAccelerators", true)) d->m_speakFlags |= SpeakAccelerator;
208 d->m_timeout = config->readNumEntry("PollingInterval", 600);
209 d->m_acceleratorPrefix = config->readEntry("AcceleratorPrefixWord", i18n("Accelerator"));
210 if (d->m_speakFlags & (SpeakPointerWidget | SpeakFocusWidget)) {
211 if (startKttsd()) {
212 d->m_timer = new TQTimer( this );
213 connect( d->m_timer, TQ_SIGNAL(timeout()), this, TQ_SLOT(probe()) );
214 d->m_timer->start( d->m_timeout );
215 }
216 }
217}
218
219bool KoSpeaker::maybeSayWidget(TQWidget* w, const TQPoint& pos /*=TQPoint()*/)
220{
221 if (!w) return false;
222
223 int id = -1;
224 TQString text;
225
226 if (w->inherits("TQViewportWidget")) {
227 w = w->parentWidget();
228 if (!w) return false;
229 }
230
231 // Handle widgets that have multiple parts.
232
233 if ( w->inherits("TQMenuBar") ) {
234 TQMenuBar* menuBar = dynamic_cast<TQMenuBar *>(w);
235 if (pos == TQPoint()) {
236 for (uint i = 0; i < menuBar->count(); ++i)
237 if (menuBar->isItemActive(menuBar->idAt(i))) {
238 id = menuBar->idAt(i);
239 break;
240 }
241 }
242 // TODO: This doesn't work. Need way to figure out the TQMenuItem underneath mouse pointer.
243 // id = menuBarItemAt(menuBar, pos);
244 if ( id != -1 )
245 text = menuBar->text(id);
246 }
247 else
248 if (w->inherits("TQPopupMenu")) {
249 TQPopupMenu* popupMenu = dynamic_cast<TQPopupMenu *>(w);
250 if (pos == TQPoint()) {
251 for (uint i = 0; i < popupMenu->count(); ++i)
252 if (popupMenu->isItemActive(popupMenu->idAt(i))) {
253 id = popupMenu->idAt(i);
254 break;
255 }
256 } else
257 id = popupMenu->idAt(popupMenu->mapFromGlobal(pos));
258 if ( id != -1 )
259 text = popupMenu->text(id);
260 }
261 else
262 if (w->inherits("TQTabBar")) {
263 TQTabBar* tabBar = dynamic_cast<TQTabBar *>(w);
264 TQTab* tab = 0;
265 if (pos == TQPoint())
266 tab = tabBar->tabAt(tabBar->currentTab());
267 else
268 tab = tabBar->selectTab(tabBar->mapFromGlobal(pos));
269 if (tab) {
270 id = tab->identifier();
271 text = tab->text();
272 }
273 }
274 else
275 if (w->inherits("TQListView")) {
276 TQListView* lv = dynamic_cast<TQListView *>(w);
277 TQListViewItem* item = 0;
278 if (pos == TQPoint())
279 item = lv->currentItem();
280 else
281 item = lv->itemAt(lv->viewport()->mapFromGlobal(pos));
282 if (item) {
283 id = lv->itemPos(item);
284 text = item->text(0);
285 for (int col = 1; col < lv->columns(); ++col)
286 if (!item->text(col).isEmpty()) text += ". " + item->text(col);
287 }
288 }
289 else
290 if (w->inherits("TQListBox")) {
291 TQListBox* lb = dynamic_cast<TQListBox *>(w);
292 // tqt docs say coordinates are in "on-screen" coordinates. What does that mean?
293 TQListBoxItem* item = 0;
294 if (pos == TQPoint())
295 item = lb->item(lb->currentItem());
296 else
297 item = lb->itemAt(lb->mapFromGlobal(pos));
298 if (item) {
299 id = lb->index(item);
300 text = item->text();
301 }
302 }
303 else
304 if (w->inherits("TQIconView")) {
305 TQIconView* iv = dynamic_cast<TQIconView *>(w);
306 TQIconViewItem* item = 0;
307 if (pos == TQPoint())
308 item = iv->currentItem();
309 else
310 item = iv->findItem(iv->viewportToContents(iv->viewport()->mapFromGlobal(pos)));
311 if (item) {
312 id = item->index();
313 text = item->text();
314 }
315 }
316 else
317 if (w->inherits("TQTable")) {
318 TQTable* tbl = dynamic_cast<TQTable *>(w);
319 int row = -1;
320 int col = -1;
321 if (pos == TQPoint()) {
322 row = tbl->currentRow();
323 col = tbl->currentColumn();
324 } else {
325 TQPoint p = tbl->viewportToContents(tbl->viewport()->mapFromGlobal(pos));
326 row = tbl->rowAt(p.y());
327 col = tbl->columnAt(p.x());
328 }
329 if (row >= 0 && col >= 0) {
330 id = (row * tbl->numCols()) + col;
331 text = tbl->text(row, col);
332 }
333 }
334 else
335 if (w->inherits("TQGridView")) {
336 TQGridView* gv = dynamic_cast<TQGridView *>(w);
337 // TODO: TQGridView does not have a "current" row or column. Don't think they can even get focus?
338 int row = -1;
339 int col = -1;
340 if (pos != TQPoint()) {
341 TQPoint p = gv->viewportToContents(gv->viewport()->mapFromGlobal(pos));
342 row = gv->rowAt(p.y());
343 col = gv->columnAt(p.x());
344 }
345 if (row >= 0 && col >= 0)
346 id = (row * gv->numCols()) + col;
347 }
348
349 if (pos == TQPoint()) {
350 if ( w == d->m_prevFocusWidget && id == d->m_prevFocusId) return false;
351 d->m_prevFocusWidget = w;
352 d->m_prevFocusId = id;
353 } else {
354 if ( w == d->m_prevPointerWidget && id == d->m_prevPointerId) return false;
355 d->m_prevPointerWidget = w;
356 d->m_prevPointerId = id;
357 }
358 if (w == d->m_prevWidget && id == d->m_prevId) return false;
359 d->m_prevWidget = w;
360 d->m_prevId = id;
361
362 // kdDebug() << " w = " << w << endl;
363
364 d->m_cancelSpeakWidget = false;
365 emit customSpeakNewWidget(w, pos, d->m_speakFlags);
366 if (d->m_cancelSpeakWidget) return true;
367
368 // Handle simple, single-part widgets.
369 if ( w->inherits("TQButton") )
370 text = dynamic_cast<TQButton *>(w)->text();
371 else
372 if (w->inherits("TQComboBox"))
373 text = dynamic_cast<TQComboBox *>(w)->currentText();
374 else
375 if (w->inherits("TQLineEdit"))
376 text = dynamic_cast<TQLineEdit *>(w)->text();
377 else
378 if (w->inherits("TQTextEdit"))
379 text = dynamic_cast<TQTextEdit *>(w)->text();
380 else
381 if (w->inherits("TQLabel"))
382 text = dynamic_cast<TQLabel *>(w)->text();
383 else
384 if (w->inherits("TQGroupBox")) {
385 // TODO: Should calculate this number from font size?
386 if (w->mapFromGlobal(pos).y() < 30)
387 text = dynamic_cast<TQGroupBox *>(w)->title();
388 }
389// else
390// if (w->inherits("TQWhatsThat")) {
391// text = dynamic_cast<TQWhatsThat *>(w)->text();
392// }
393
394 text = text.stripWhiteSpace();
395 if (!text.isEmpty()) {
396 if (text.right(1) == ".")
397 text += " ";
398 else
399 text += ". ";
400 }
401 if (d->m_speakFlags & SpeakTooltip || text.isEmpty()) {
402 // kdDebug() << "pos = " << pos << endl;
403 // TQPoint p = w->mapFromGlobal(pos);
404 // kdDebug() << "p = " << p << endl;
405 TQString t = TQToolTip::textFor(w, pos);
406 t = t.stripWhiteSpace();
407 if (!t.isEmpty()) {
408 if (t.right(1) != ".") t += ".";
409 text += t + " ";
410 }
411 }
412
413 if (d->m_speakFlags & SpeakWhatsThis || text.isEmpty()) {
414 TQString t = TQWhatsThis::textFor(w, pos);
415 t = t.stripWhiteSpace();
416 if (!t.isEmpty()) {
417 if (t.right(1) != ".") t += ".";
418 text += t + " ";
419 }
420 }
421
422 if (d->m_speakFlags & SpeakDisabled) {
423 if (!w->isEnabled())
424 text += i18n("A grayed widget", "Disabled. ");
425 }
426
427 return sayWidget(text);
428}
429
430bool KoSpeaker::sayWidget(const TQString& msg)
431{
432 TQString s = msg;
433 if (d->m_speakFlags & SpeakAccelerator) {
434 int amp = s.find("&");
435 if (amp >= 0) {
436 TQString acc = s.mid(++amp,1);
437 acc = acc.stripWhiteSpace();
438 if (!acc.isEmpty())
439 s += ". " + d->m_acceleratorPrefix + " " + acc + ".";
440 }
441 }
442 s.remove("&");
443 if (TQStyleSheet::mightBeRichText(s)) {
444 // kdDebug() << "richtext" << endl;
445 s.replace(TQRegExp("</?[pbius]>"), "");
446 s.replace(TQRegExp("</?h\\d>"), "");
447 s.replace(TQRegExp("<(br|hr)>"), " ");
448 s.replace(TQRegExp(
449 "</?(qt|center|li|pre|div|span|em|strong|big|small|sub|sup|code|tt|font|nobr|ul|ol|dl|dt)>"), "");
450 s.replace(TQRegExp("</?(table|tr|th|td).*>"), "");
451 s.replace(TQRegExp("</?a\\s.+>"), "");
452 // Replace <img source="small|frame_text"> with "small frame_text image. "
453 s.replace(TQRegExp("<img\\s.*(?:source=|src=)\"([^|\"]+)[|]?([^|\"]*)\">"), "\\1 \\2 image. ");
454 }
455 if (s.isEmpty()) return false;
456 s.replace("Ctrl+", i18n("control plus "));
457 s.replace("Alt+", i18n("alt plus "));
458 s.replace("+", i18n(" plus "));
459 sayScreenReaderOutput(s, "");
460 return true;
461}
462
463// This doesn't work. Anybody know how to find the menu item underneath mouse pointer
464// in a TQMenuBar?
465// int KoSpeaker::menuBarItemAt(TQMenuBar* m, const TQPoint& p)
466// {
467// for (uint i = 0; i < m->count(); i++) {
468// int id = m->idAt(i);
469// TQMenuItem* mi = m->findItem(id);
470// TQWidget* w = mi->widget();
471// if (w->rect().contains(w->mapFromGlobal(p))) return id;
472// }
473// return -1;
474// }
475
477{
478 TDETrader::OfferList offers = TDETrader::self()->query("DCOP/Text-to-Speech", "Name == 'KTTSD'");
479 return (offers.count() > 0);
480}
481
482bool KoSpeaker::startKttsd()
483{
484 DCOPClient *client = kapp->dcopClient();
485 // If KTTSD not running, start it.
486 if (!client->isApplicationRegistered("kttsd"))
487 {
488 TQString error;
489 if (kapp->startServiceByDesktopName("kttsd", TQStringList(), &error)) {
490 kdDebug() << "KoSpeaker::startKttsd: error starting KTTSD service: " << error << endl;
491 d->m_enabled = false;
492 } else
493 d->m_enabled = true;
494 } else
495 d->m_enabled = true;
496 return d->m_enabled;
497}
498
499TQString KoSpeaker::getKttsdVersion()
500{
501 // Determine which version of KTTSD is running. Note that earlier versions of KSpeech interface
502 // did not support version() method, so we must manually marshall this call ourselves.
503 if (d->m_enabled) {
504 if (!d->m_versionChecked) {
505 DCOPClient *client = kapp->dcopClient();
506 TQByteArray data;
507 TQCString replyType;
508 TQByteArray replyData;
509 if ( client->call("kttsd", "KSpeech", "version()", data, replyType, replyData, true) ) {
510 TQDataStream arg(replyData, IO_ReadOnly);
511 arg >> d->m_kttsdVersion;
512 kdDebug() << "KoSpeaker::startKttsd: KTTSD version = " << d->m_kttsdVersion << endl;
513 }
514 d->m_versionChecked = true;
515 }
516 }
517 return d->m_kttsdVersion;
518}
519
520void KoSpeaker::sayScreenReaderOutput(const TQString &msg, const TQString &talker)
521{
522 if (msg.isEmpty()) return;
523 DCOPClient *client = kapp->dcopClient();
524 TQByteArray data;
525 TQCString replyType;
526 TQByteArray replyData;
527 TQDataStream arg(data, IO_WriteOnly);
528 arg << msg << talker;
529 if ( !client->call("kttsd", "KSpeech", "sayScreenReaderOutput(TQString,TQString)",
530 data, replyType, replyData, true) ) {
531 kdDebug() << "KoSpeaker::sayScreenReaderOutput: failed" << endl;
532 }
533}
534
535uint KoSpeaker::setText(const TQString &text, const TQString &talker)
536{
537 if (text.isEmpty()) return 0;
538 DCOPClient *client = kapp->dcopClient();
539 TQByteArray data;
540 TQCString replyType;
541 TQByteArray replyData;
542 TQDataStream arg(data, IO_WriteOnly);
543 arg << text << talker;
544 uint jobNum = 0;
545 if ( !client->call("kttsd", "KSpeech", "setText(TQString,TQString)",
546 data, replyType, replyData, true) ) {
547 kdDebug() << "KoSpeaker::sayText: failed" << endl;
548 } else {
549 TQDataStream arg2(replyData, IO_ReadOnly);
550 arg2 >> jobNum;
551 }
552 return jobNum;
553}
554
555int KoSpeaker::appendText(const TQString &text, uint jobNum /*=0*/)
556{
557 if (text.isEmpty()) return 0;
558 DCOPClient *client = kapp->dcopClient();
559 TQByteArray data;
560 TQCString replyType;
561 TQByteArray replyData;
562 TQDataStream arg(data, IO_WriteOnly);
563 arg << text << jobNum;
564 int partNum = 0;
565 if ( !client->call("kttsd", "KSpeech", "appendText(TQString,uint)",
566 data, replyType, replyData, true) ) {
567 kdDebug() << "KoSpeaker::appendText: failed" << endl;
568 } else {
569 TQDataStream arg2(replyData, IO_ReadOnly);
570 arg2 >> partNum;
571 }
572 return partNum;
573}
574
575void KoSpeaker::startText(uint jobNum /*=0*/)
576{
577 DCOPClient *client = kapp->dcopClient();
578 TQByteArray data;
579 TQCString replyType;
580 TQByteArray replyData;
581 TQDataStream arg(data, IO_WriteOnly);
582 arg << jobNum;
583 if ( !client->call("kttsd", "KSpeech", "startText(uint)",
584 data, replyType, replyData, true) ) {
585 kdDebug() << "KoSpeaker::startText: failed" << endl;
586 }
587}
588
589void KoSpeaker::removeText(uint jobNum /*=0*/)
590{
591 DCOPClient *client = kapp->dcopClient();
592 TQByteArray data;
593 TQCString replyType;
594 TQByteArray replyData;
595 TQDataStream arg(data, IO_WriteOnly);
596 arg << jobNum;
597 if ( !client->call("kttsd", "KSpeech", "removeText(uint)",
598 data, replyType, replyData, true) ) {
599 kdDebug() << "KoSpeaker::removeText: failed" << endl;
600 }
601}
602
This file is part of the KDE/KOffice project.
bool isApplicationRegistered(const TQCString &remApp)
bool call(const TQCString &remApp, const TQCString &remObj, const TQCString &remFun, const TQByteArray &data, TQCString &replyType, TQByteArray &replyData, bool useEventLoop, int timeout, bool forceRemote)
KoSpeaker is a singleton object that provides Text-to-Speech services for KOffice applications.
Definition KoSpeaker.h:64
bool sayWidget(const TQString &msg)
Speak a msg that came from a widget, such as the widget's text label, tool tip, etc.
void customSpeakWidget(TQWidget *w, const TQPoint &p, uint flags)
This signal is emitted each polling interval when KoSpeaker did not speak the widget (either because ...
void customSpeakNewWidget(TQWidget *w, const TQPoint &p, uint flags)
This signal is emitted whenever a new widget has received focus or the mouse pointer has moved to a n...
void readConfig(TDEConfig *config)
Reads configuration options from config object and starts TTS if screen reader capability is requeste...
void startSpeech()
Start speaking queued text job (if any).
void queueSpeech(const TQString &msg, const TQString &langCode=TQString(), bool first=true)
Queue a msg as a speech text job.
bool isEnabled() const
Returns true if TTS services are available.
@ SpeakWhatsThis
Speak Whats This if available.
Definition KoSpeaker.h:75
@ SpeakTooltip
Speak tooltip if available.
Definition KoSpeaker.h:76
@ SpeakDisabled
Say 'disabled' if not enabled.
Definition KoSpeaker.h:78
@ SpeakPointerWidget
Speak widget under mouse pointer.
Definition KoSpeaker.h:74
@ SpeakFocusWidget
Speak widget with focus.
Definition KoSpeaker.h:73
@ SpeakAccelerator
Speak accelerator.
Definition KoSpeaker.h:77
bool maybeSayWidget(TQWidget *w, const TQPoint &pos=TQPoint())
Given a widget w and its pos screen coordinates, tries to extract the text of the widget and speak it...
static bool isKttsdInstalled()
Returns whether the KTTSD deamon is installed in the system.