00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024
00025 #include <qtimer.h>
00026 #include <qcursor.h>
00027 #include <qtooltip.h>
00028 #include <qwhatsthis.h>
00029 #include <qmenubar.h>
00030 #include <qlabel.h>
00031 #include <qbutton.h>
00032 #include <qcombobox.h>
00033 #include <qtabbar.h>
00034 #include <qgroupbox.h>
00035 #include <qlineedit.h>
00036 #include <qtextedit.h>
00037 #include <qlistview.h>
00038 #include <qlistbox.h>
00039 #include <qiconview.h>
00040 #include <qtable.h>
00041 #include <qgridview.h>
00042 #include <qregexp.h>
00043 #include <qstylesheet.h>
00044
00045
00046 #include <kapplication.h>
00047 #include <klocale.h>
00048 #include <kglobal.h>
00049 #include <dcopclient.h>
00050 #include <kconfig.h>
00051 #include <ktrader.h>
00052 #include <kdebug.h>
00053
00054
00055 #include "KoSpeaker.h"
00056 #include "KoSpeaker.moc"
00057
00058
00059
00060 class KoSpeakerPrivate
00061 {
00062 public:
00063 KoSpeakerPrivate() :
00064 m_versionChecked(false),
00065 m_enabled(false),
00066 m_speakFlags(0),
00067 m_timeout(600),
00068 m_timer(0),
00069 m_prevPointerWidget(0),
00070 m_prevPointerId(-1),
00071 m_prevFocusWidget(0),
00072 m_prevFocusId(-1),
00073 m_prevWidget(0),
00074 m_prevId(-1),
00075 m_cancelSpeakWidget(false)
00076 {}
00077
00078
00079 QValueList<uint> m_jobNums;
00080
00081 bool m_versionChecked;
00082
00083 QString m_kttsdVersion;
00084
00085 QString m_langCode;
00086
00087 QString m_acceleratorPrefix;
00088
00089 bool m_enabled;
00090
00091 uint m_speakFlags;
00092
00093 int m_timeout;
00094 QTimer* m_timer;
00095
00096
00097 QWidget* m_prevPointerWidget;
00098 int m_prevPointerId;
00099 QWidget* m_prevFocusWidget;
00100 int m_prevFocusId;
00101 QWidget* m_prevWidget;
00102 int m_prevId;
00103
00104 bool m_cancelSpeakWidget;
00105 };
00106
00107
00108
00109 KoSpeaker* KoSpeaker::KSpkr = 0L;
00110
00111 KoSpeaker::KoSpeaker()
00112 {
00113 Q_ASSERT(!KSpkr);
00114 KSpkr = this;
00115 d = new KoSpeakerPrivate();
00116 readConfig(KGlobal::config());
00117 }
00118
00119 KoSpeaker::~KoSpeaker()
00120 {
00121 if (d->m_jobNums.count() > 0) {
00122 for (int i = d->m_jobNums.count() - 1; i >= 0; i--)
00123 removeText(d->m_jobNums[i]);
00124 d->m_jobNums.clear();
00125 }
00126 delete d;
00127 KSpkr = 0;
00128 }
00129
00130 bool KoSpeaker::isEnabled() const { return d->m_enabled; }
00131
00132 void KoSpeaker::probe()
00133 {
00134 d->m_timer->stop();
00135 QWidget* w;
00136 QPoint pos;
00137 bool spoke = false;
00138 if ( d->m_speakFlags & SpeakFocusWidget ) {
00139 w = kapp->focusWidget();
00140 if (w) {
00141 spoke = maybeSayWidget(w);
00142 if (!spoke)
00143 emit customSpeakWidget(w, pos, d->m_speakFlags);
00144 }
00145 }
00146 if ( !spoke && d->m_speakFlags & SpeakPointerWidget ) {
00147 pos = QCursor::pos();
00148 w = kapp->widgetAt(pos, true);
00149 if (w) {
00150 if (!maybeSayWidget(w, pos))
00151 emit customSpeakWidget(w, pos, d->m_speakFlags);
00152 }
00153 }
00154 d->m_timer->start(d->m_timeout);
00155 }
00156
00157 void KoSpeaker::queueSpeech(const QString& msg, const QString& langCode , bool first )
00158 {
00159 if (!startKttsd()) return;
00160 int jobCount = d->m_jobNums.count();
00161 if (first && jobCount > 0) {
00162 for (int i = jobCount - 1; i >= 0; i--)
00163 removeText(d->m_jobNums[i]);
00164 d->m_jobNums.clear();
00165 jobCount = 0;
00166 }
00167 QString s = msg.stripWhiteSpace();
00168 if (s.isEmpty()) return;
00169
00170
00171 QString languageCode = langCode;
00172 if (langCode.isEmpty())
00173 languageCode = KGlobal::locale()->language();
00174
00175
00176
00177
00178
00179 if (getKttsdVersion().isEmpty())
00180 d->m_jobNums.append(setText(s, languageCode));
00181 else {
00182 if ((jobCount == 0) || (languageCode != d->m_langCode))
00183 d->m_jobNums.append(setText(s, languageCode));
00184 else
00185 appendText(s, d->m_jobNums[jobCount-1]);
00186 }
00187 d->m_langCode = languageCode;
00188 }
00189
00190 void KoSpeaker::startSpeech()
00191 {
00192 for (uint i = 0; i < d->m_jobNums.count(); i++)
00193 startText(d->m_jobNums[i]);
00194 }
00195
00196 void KoSpeaker::readConfig(KConfig* config)
00197 {
00198 delete d->m_timer;
00199 d->m_timer = 0;
00200 config->setGroup("TTS");
00201 d->m_speakFlags = 0;
00202 if (config->readBoolEntry("SpeakPointerWidget", false)) d->m_speakFlags |= SpeakPointerWidget;
00203 if (config->readBoolEntry("SpeakFocusWidget", false)) d->m_speakFlags |= SpeakFocusWidget;
00204 if (config->readBoolEntry("SpeakTooltips", true)) d->m_speakFlags |= SpeakTooltip;
00205 if (config->readBoolEntry("SpeakWhatsThis", false)) d->m_speakFlags |= SpeakWhatsThis;
00206 if (config->readBoolEntry("SpeakDisabled", true)) d->m_speakFlags |= SpeakDisabled;
00207 if (config->readBoolEntry("SpeakAccelerators", true)) d->m_speakFlags |= SpeakAccelerator;
00208 d->m_timeout = config->readNumEntry("PollingInterval", 600);
00209 d->m_acceleratorPrefix = config->readEntry("AcceleratorPrefixWord", i18n("Accelerator"));
00210 if (d->m_speakFlags & (SpeakPointerWidget | SpeakFocusWidget)) {
00211 if (startKttsd()) {
00212 d->m_timer = new QTimer( this );
00213 connect( d->m_timer, SIGNAL(timeout()), this, SLOT(probe()) );
00214 d->m_timer->start( d->m_timeout );
00215 }
00216 }
00217 }
00218
00219 bool KoSpeaker::maybeSayWidget(QWidget* w, const QPoint& pos )
00220 {
00221 if (!w) return false;
00222
00223 int id = -1;
00224 QString text;
00225
00226 if (w->inherits("QViewportWidget")) {
00227 w = w->parentWidget();
00228 if (!w) return false;
00229 }
00230
00231
00232
00233 if ( w->inherits("QMenuBar") ) {
00234 QMenuBar* menuBar = dynamic_cast<QMenuBar *>(w);
00235 if (pos == QPoint()) {
00236 for (uint i = 0; i < menuBar->count(); ++i)
00237 if (menuBar->isItemActive(menuBar->idAt(i))) {
00238 id = menuBar->idAt(i);
00239 break;
00240 }
00241 }
00242
00243
00244 if ( id != -1 )
00245 text = menuBar->text(id);
00246 }
00247 else
00248 if (w->inherits("QPopupMenu")) {
00249 QPopupMenu* popupMenu = dynamic_cast<QPopupMenu *>(w);
00250 if (pos == QPoint()) {
00251 for (uint i = 0; i < popupMenu->count(); ++i)
00252 if (popupMenu->isItemActive(popupMenu->idAt(i))) {
00253 id = popupMenu->idAt(i);
00254 break;
00255 }
00256 } else
00257 id = popupMenu->idAt(popupMenu->mapFromGlobal(pos));
00258 if ( id != -1 )
00259 text = popupMenu->text(id);
00260 }
00261 else
00262 if (w->inherits("QTabBar")) {
00263 QTabBar* tabBar = dynamic_cast<QTabBar *>(w);
00264 QTab* tab = 0;
00265 if (pos == QPoint())
00266 tab = tabBar->tabAt(tabBar->currentTab());
00267 else
00268 tab = tabBar->selectTab(tabBar->mapFromGlobal(pos));
00269 if (tab) {
00270 id = tab->identifier();
00271 text = tab->text();
00272 }
00273 }
00274 else
00275 if (w->inherits("QListView")) {
00276 QListView* lv = dynamic_cast<QListView *>(w);
00277 QListViewItem* item = 0;
00278 if (pos == QPoint())
00279 item = lv->currentItem();
00280 else
00281 item = lv->itemAt(lv->viewport()->mapFromGlobal(pos));
00282 if (item) {
00283 id = lv->itemPos(item);
00284 text = item->text(0);
00285 for (int col = 1; col < lv->columns(); ++col)
00286 if (!item->text(col).isEmpty()) text += ". " + item->text(col);
00287 }
00288 }
00289 else
00290 if (w->inherits("QListBox")) {
00291 QListBox* lb = dynamic_cast<QListBox *>(w);
00292
00293 QListBoxItem* item = 0;
00294 if (pos == QPoint())
00295 item = lb->item(lb->currentItem());
00296 else
00297 item = lb->itemAt(lb->mapFromGlobal(pos));
00298 if (item) {
00299 id = lb->index(item);
00300 text = item->text();
00301 }
00302 }
00303 else
00304 if (w->inherits("QIconView")) {
00305 QIconView* iv = dynamic_cast<QIconView *>(w);
00306 QIconViewItem* item = 0;
00307 if (pos == QPoint())
00308 item = iv->currentItem();
00309 else
00310 item = iv->findItem(iv->viewportToContents(iv->viewport()->mapFromGlobal(pos)));
00311 if (item) {
00312 id = item->index();
00313 text = item->text();
00314 }
00315 }
00316 else
00317 if (w->inherits("QTable")) {
00318 QTable* tbl = dynamic_cast<QTable *>(w);
00319 int row = -1;
00320 int col = -1;
00321 if (pos == QPoint()) {
00322 row = tbl->currentRow();
00323 col = tbl->currentColumn();
00324 } else {
00325 QPoint p = tbl->viewportToContents(tbl->viewport()->mapFromGlobal(pos));
00326 row = tbl->rowAt(p.y());
00327 col = tbl->columnAt(p.x());
00328 }
00329 if (row >= 0 && col >= 0) {
00330 id = (row * tbl->numCols()) + col;
00331 text = tbl->text(row, col);
00332 }
00333 }
00334 else
00335 if (w->inherits("QGridView")) {
00336 QGridView* gv = dynamic_cast<QGridView *>(w);
00337
00338 int row = -1;
00339 int col = -1;
00340 if (pos != QPoint()) {
00341 QPoint p = gv->viewportToContents(gv->viewport()->mapFromGlobal(pos));
00342 row = gv->rowAt(p.y());
00343 col = gv->columnAt(p.x());
00344 }
00345 if (row >= 0 && col >= 0)
00346 id = (row * gv->numCols()) + col;
00347 }
00348
00349 if (pos == QPoint()) {
00350 if ( w == d->m_prevFocusWidget && id == d->m_prevFocusId) return false;
00351 d->m_prevFocusWidget = w;
00352 d->m_prevFocusId = id;
00353 } else {
00354 if ( w == d->m_prevPointerWidget && id == d->m_prevPointerId) return false;
00355 d->m_prevPointerWidget = w;
00356 d->m_prevPointerId = id;
00357 }
00358 if (w == d->m_prevWidget && id == d->m_prevId) return false;
00359 d->m_prevWidget = w;
00360 d->m_prevId = id;
00361
00362
00363
00364 d->m_cancelSpeakWidget = false;
00365 emit customSpeakNewWidget(w, pos, d->m_speakFlags);
00366 if (d->m_cancelSpeakWidget) return true;
00367
00368
00369 if ( w->inherits("QButton") )
00370 text = dynamic_cast<QButton *>(w)->text();
00371 else
00372 if (w->inherits("QComboBox"))
00373 text = dynamic_cast<QComboBox *>(w)->currentText();
00374 else
00375 if (w->inherits("QLineEdit"))
00376 text = dynamic_cast<QLineEdit *>(w)->text();
00377 else
00378 if (w->inherits("QTextEdit"))
00379 text = dynamic_cast<QTextEdit *>(w)->text();
00380 else
00381 if (w->inherits("QLabel"))
00382 text = dynamic_cast<QLabel *>(w)->text();
00383 else
00384 if (w->inherits("QGroupBox")) {
00385
00386 if (w->mapFromGlobal(pos).y() < 30)
00387 text = dynamic_cast<QGroupBox *>(w)->title();
00388 }
00389
00390
00391
00392
00393
00394 text = text.stripWhiteSpace();
00395 if (!text.isEmpty()) {
00396 if (text.right(1) == ".")
00397 text += " ";
00398 else
00399 text += ". ";
00400 }
00401 if (d->m_speakFlags & SpeakTooltip || text.isEmpty()) {
00402
00403
00404
00405 QString t = QToolTip::textFor(w, pos);
00406 t = t.stripWhiteSpace();
00407 if (!t.isEmpty()) {
00408 if (t.right(1) != ".") t += ".";
00409 text += t + " ";
00410 }
00411 }
00412
00413 if (d->m_speakFlags & SpeakWhatsThis || text.isEmpty()) {
00414 QString t = QWhatsThis::textFor(w, pos);
00415 t = t.stripWhiteSpace();
00416 if (!t.isEmpty()) {
00417 if (t.right(1) != ".") t += ".";
00418 text += t + " ";
00419 }
00420 }
00421
00422 if (d->m_speakFlags & SpeakDisabled) {
00423 if (!w->isEnabled())
00424 text += i18n("A grayed widget", "Disabled. ");
00425 }
00426
00427 return sayWidget(text);
00428 }
00429
00430 bool KoSpeaker::sayWidget(const QString& msg)
00431 {
00432 QString s = msg;
00433 if (d->m_speakFlags & SpeakAccelerator) {
00434 int amp = s.find("&");
00435 if (amp >= 0) {
00436 QString acc = s.mid(++amp,1);
00437 acc = acc.stripWhiteSpace();
00438 if (!acc.isEmpty())
00439 s += ". " + d->m_acceleratorPrefix + " " + acc + ".";
00440 }
00441 }
00442 s.remove("&");
00443 if (QStyleSheet::mightBeRichText(s)) {
00444
00445 s.replace(QRegExp("</?[pbius]>"), "");
00446 s.replace(QRegExp("</?h\\d>"), "");
00447 s.replace(QRegExp("<(br|hr)>"), " ");
00448 s.replace(QRegExp(
00449 "</?(qt|center|li|pre|div|span|em|strong|big|small|sub|sup|code|tt|font|nobr|ul|ol|dl|dt)>"), "");
00450 s.replace(QRegExp("</?(table|tr|th|td).*>"), "");
00451 s.replace(QRegExp("</?a\\s.+>"), "");
00452
00453 s.replace(QRegExp("<img\\s.*(?:source=|src=)\"([^|\"]+)[|]?([^|\"]*)\">"), "\\1 \\2 image. ");
00454 }
00455 if (s.isEmpty()) return false;
00456 s.replace("Ctrl+", i18n("control plus "));
00457 s.replace("Alt+", i18n("alt plus "));
00458 s.replace("+", i18n(" plus "));
00459 sayScreenReaderOutput(s, "");
00460 return true;
00461 }
00462
00463
00464
00465
00466
00467
00468
00469
00470
00471
00472
00473
00474
00475
00476 bool KoSpeaker::isKttsdInstalled()
00477 {
00478 KTrader::OfferList offers = KTrader::self()->query("DCOP/Text-to-Speech", "Name == 'KTTSD'");
00479 return (offers.count() > 0);
00480 }
00481
00482 bool KoSpeaker::startKttsd()
00483 {
00484 DCOPClient *client = kapp->dcopClient();
00485
00486 if (!client->isApplicationRegistered("kttsd"))
00487 {
00488 QString error;
00489 if (kapp->startServiceByDesktopName("kttsd", QStringList(), &error)) {
00490 kdDebug() << "KoSpeaker::startKttsd: error starting KTTSD service: " << error << endl;
00491 d->m_enabled = false;
00492 } else
00493 d->m_enabled = true;
00494 } else
00495 d->m_enabled = true;
00496 return d->m_enabled;
00497 }
00498
00499 QString KoSpeaker::getKttsdVersion()
00500 {
00501
00502
00503 if (d->m_enabled) {
00504 if (!d->m_versionChecked) {
00505 DCOPClient *client = kapp->dcopClient();
00506 QByteArray data;
00507 QCString replyType;
00508 QByteArray replyData;
00509 if ( client->call("kttsd", "KSpeech", "version()", data, replyType, replyData, true) ) {
00510 QDataStream arg(replyData, IO_ReadOnly);
00511 arg >> d->m_kttsdVersion;
00512 kdDebug() << "KoSpeaker::startKttsd: KTTSD version = " << d->m_kttsdVersion << endl;
00513 }
00514 d->m_versionChecked = true;
00515 }
00516 }
00517 return d->m_kttsdVersion;
00518 }
00519
00520 void KoSpeaker::sayScreenReaderOutput(const QString &msg, const QString &talker)
00521 {
00522 if (msg.isEmpty()) return;
00523 DCOPClient *client = kapp->dcopClient();
00524 QByteArray data;
00525 QCString replyType;
00526 QByteArray replyData;
00527 QDataStream arg(data, IO_WriteOnly);
00528 arg << msg << talker;
00529 if ( !client->call("kttsd", "KSpeech", "sayScreenReaderOutput(QString,QString)",
00530 data, replyType, replyData, true) ) {
00531 kdDebug() << "KoSpeaker::sayScreenReaderOutput: failed" << endl;
00532 }
00533 }
00534
00535 uint KoSpeaker::setText(const QString &text, const QString &talker)
00536 {
00537 if (text.isEmpty()) return 0;
00538 DCOPClient *client = kapp->dcopClient();
00539 QByteArray data;
00540 QCString replyType;
00541 QByteArray replyData;
00542 QDataStream arg(data, IO_WriteOnly);
00543 arg << text << talker;
00544 uint jobNum = 0;
00545 if ( !client->call("kttsd", "KSpeech", "setText(QString,QString)",
00546 data, replyType, replyData, true) ) {
00547 kdDebug() << "KoSpeaker::sayText: failed" << endl;
00548 } else {
00549 QDataStream arg2(replyData, IO_ReadOnly);
00550 arg2 >> jobNum;
00551 }
00552 return jobNum;
00553 }
00554
00555 int KoSpeaker::appendText(const QString &text, uint jobNum )
00556 {
00557 if (text.isEmpty()) return 0;
00558 DCOPClient *client = kapp->dcopClient();
00559 QByteArray data;
00560 QCString replyType;
00561 QByteArray replyData;
00562 QDataStream arg(data, IO_WriteOnly);
00563 arg << text << jobNum;
00564 int partNum = 0;
00565 if ( !client->call("kttsd", "KSpeech", "appendText(QString,uint)",
00566 data, replyType, replyData, true) ) {
00567 kdDebug() << "KoSpeaker::appendText: failed" << endl;
00568 } else {
00569 QDataStream arg2(replyData, IO_ReadOnly);
00570 arg2 >> partNum;
00571 }
00572 return partNum;
00573 }
00574
00575 void KoSpeaker::startText(uint jobNum )
00576 {
00577 DCOPClient *client = kapp->dcopClient();
00578 QByteArray data;
00579 QCString replyType;
00580 QByteArray replyData;
00581 QDataStream arg(data, IO_WriteOnly);
00582 arg << jobNum;
00583 if ( !client->call("kttsd", "KSpeech", "startText(uint)",
00584 data, replyType, replyData, true) ) {
00585 kdDebug() << "KoSpeaker::startText: failed" << endl;
00586 }
00587 }
00588
00589 void KoSpeaker::removeText(uint jobNum )
00590 {
00591 DCOPClient *client = kapp->dcopClient();
00592 QByteArray data;
00593 QCString replyType;
00594 QByteArray replyData;
00595 QDataStream arg(data, IO_WriteOnly);
00596 arg << jobNum;
00597 if ( !client->call("kttsd", "KSpeech", "removeText(uint)",
00598 data, replyType, replyData, true) ) {
00599 kdDebug() << "KoSpeaker::removeText: failed" << endl;
00600 }
00601 }
00602