diff --git a/CMakeLists.txt b/CMakeLists.txt index b9bc1f4d5..d8aa16b3d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -19,6 +19,13 @@ include(ECMAddTests) find_package(Qt5 CONFIG REQUIRED COMPONENTS Core DBus Test Widgets PrintSupport Svg Qml Quick) +find_package(Qt5 OPTIONAL_COMPONENTS TextToSpeech) +if (NOT Qt5TextToSpeech_FOUND) + message(STATUS "Qt5TextToSpeech not found, speech features will be disabled") +else() + add_definitions(-DHAVE_SPEECH) +endif() + find_package(KF5 REQUIRED COMPONENTS Activities Archive @@ -253,10 +260,14 @@ set(okularpart_SRCS ui/toc.cpp ui/tocmodel.cpp ui/toolaction.cpp -# ui/tts.cpp ui/videowidget.cpp ) +if (Qt5TextToSpeech_FOUND) + set(okularpart_SRCS ${okularpart_SRCS} + ui/tts.cpp) +endif() + ki18n_wrap_ui(okularpart_SRCS conf/dlgaccessibilitybase.ui conf/dlgeditorbase.ui @@ -268,7 +279,6 @@ ki18n_wrap_ui(okularpart_SRCS kconfig_add_kcfg_files(okularpart_SRCS conf/settings.kcfgc ) -message("KF5: Enable ui/tts.cpp again") #qt5_add_dbus_interfaces(okularpart_SRCS ${KDE4_DBUS_INTERFACES_DIR}/org.kde.KSpeech.xml) add_library(okularpart MODULE ${okularpart_SRCS}) @@ -276,6 +286,9 @@ generate_export_header(okularpart BASE_NAME okularpart) target_link_libraries(okularpart okularcore Qt5::Svg KF5::Parts ${MATH_LIB} Phonon::phonon4qt5 KF5::Solid) +if (Qt5TextToSpeech_FOUND) + target_link_libraries(okularpart Qt5::TextToSpeech) +endif() install(TARGETS okularpart DESTINATION ${PLUGIN_INSTALL_DIR}) diff --git a/conf/okular.kcfg b/conf/okular.kcfg index 55f5cdc6d..cd1d733f4 100644 --- a/conf/okular.kcfg +++ b/conf/okular.kcfg @@ -95,7 +95,7 @@ - + true diff --git a/part.cpp b/part.cpp index 395b0e413..aa1decb5d 100644 --- a/part.cpp +++ b/part.cpp @@ -304,7 +304,7 @@ m_cliPresentation(false), m_cliPrint(false), m_embedMode(detectEmbedMode(parentW } } Okular::Settings::instance( configFileName ); - + numberOfParts++; if (numberOfParts == 1) { QDBusConnection::sessionBus().registerObject("/okular", this, QDBusConnection::ExportScriptableSlots); @@ -506,10 +506,11 @@ m_cliPresentation(false), m_cliPrint(false), m_embedMode(detectEmbedMode(parentW // keep us informed when the user changes settings connect( Okular::Settings::self(), SIGNAL(configChanged()), this, SLOT(slotNewConfig()) ); - // [SPEECH] check for KTTSD presence and usability - const KService::Ptr kttsd = KService::serviceByDesktopName("kttsd"); - Okular::Settings::setUseKTTSD( kttsd ); +#ifdef HAVE_SPEECH + // [SPEECH] check for TTS presence and usability + Okular::Settings::setUseTTS( true ); Okular::Settings::self()->save(); +#endif rebuildBookmarkMenu( false ); @@ -1708,7 +1709,7 @@ void Part::slotFileDirty( const QString& path ) void Part::slotDoFileDirty() { bool tocReloadPrepared = false; - + // do the following the first time the file is reloaded if ( m_viewportDirty.pageNumber == -1 ) { @@ -1725,7 +1726,7 @@ void Part::slotDoFileDirty() // store if presentation view was open m_wasPresentationOpen = ((PresentationWidget*)m_presentationWidget != 0); - + // preserves the toc state after reload m_toc->prepareForReload(); tocReloadPrepared = true; @@ -1743,13 +1744,13 @@ void Part::slotDoFileDirty() { m_viewportDirty.pageNumber = -1; - if ( tocReloadPrepared ) + if ( tocReloadPrepared ) { m_toc->rollbackReload(); } return; } - + if ( tocReloadPrepared ) m_toc->finishReload(); @@ -1783,7 +1784,7 @@ void Part::slotDoFileDirty() } else { - // start watching the file again (since we dropped it on close) + // start watching the file again (since we dropped it on close) addFileToWatcher( m_watcher, localFilePath() ); m_dirtyHandler->start( 750 ); } @@ -1796,14 +1797,14 @@ void Part::updateViewActions() if ( opened ) { m_gotoPage->setEnabled( m_document->pages() > 1 ); - + // Check if you are at the beginning or not if (m_document->currentPage() != 0) { m_beginningOfDocument->setEnabled( true ); m_prevPage->setEnabled( true ); } - else + else { if (m_pageView->verticalScrollBar()->value() != 0) { @@ -1818,7 +1819,7 @@ void Part::updateViewActions() // The document is at the first page, you can go to a page before m_prevPage->setEnabled( false ); } - + if (m_document->pages() == m_document->currentPage() + 1 ) { // If you are at the end, disable go to next page @@ -1828,13 +1829,13 @@ void Part::updateViewActions() // If you are the end of the page of the last document, you can't go to the last page m_endOfDocument->setEnabled( false ); } - else + else { // Otherwise you can move to the endif m_endOfDocument->setEnabled( true ); } } - else + else { // If you are not at the end, enable go to next page m_nextPage->setEnabled( true ); @@ -2936,7 +2937,7 @@ void Part::rebuildBookmarkMenu( bool unplugActions ) m_bookmarkActions.append( a ); } plugActionList( "bookmarks_currentdocument", m_bookmarkActions ); - + if (factory()) { const QList clients(factory()->clients()); diff --git a/ui/pageview.cpp b/ui/pageview.cpp index f1886d3e5..89a298430 100644 --- a/ui/pageview.cpp +++ b/ui/pageview.cpp @@ -70,7 +70,9 @@ #include "pageviewannotator.h" #include "priorities.h" #include "toolaction.h" -//#include "tts.h" +#ifdef HAVE_SPEECH +#include "tts.h" +#endif #include "videowidget.h" #include "core/action.h" #include "core/area.h" @@ -119,7 +121,9 @@ public: PageViewPrivate( PageView *qq ); FormWidgetsController* formWidgetsController(); -// OkularTTS* tts(); +#ifdef HAVE_SPEECH + OkularTTS* tts(); +#endif QString selectedText() const; // the document, pageviewItems and the 'visible cache' @@ -176,7 +180,9 @@ public: PageViewMessage * messageWindow; // in pageviewutils.h bool m_formsVisible; FormWidgetsController *formsWidgetController; -// OkularTTS * m_tts; +#ifdef HAVE_SPEECH + OkularTTS * m_tts; +#endif QTimer * refreshTimer; int refreshPage; @@ -227,6 +233,9 @@ public: PageViewPrivate::PageViewPrivate( PageView *qq ) : q( qq ) +#ifdef HAVE_SPEECH + , m_tts( 0 ) +#endif { } @@ -244,22 +253,22 @@ FormWidgetsController* PageViewPrivate::formWidgetsController() return formsWidgetController; } -//OkularTTS* PageViewPrivate::tts() -//{ -// if ( !m_tts ) -// { -// m_tts = new OkularTTS( q ); -// if ( aSpeakStop ) -// { -// QObject::connect( m_tts, SIGNAL(hasSpeechs(bool)), -// aSpeakStop, SLOT(setEnabled(bool)) ); -// QObject::connect( m_tts, SIGNAL(errorMessage(QString)), -// q, SLOT(errorMessage(QString)) ); -// } -// } +#ifdef HAVE_SPEECH +OkularTTS* PageViewPrivate::tts() +{ + if ( !m_tts ) + { + m_tts = new OkularTTS( q ); + if ( aSpeakStop ) + { + QObject::connect( m_tts, SIGNAL(isSpeaking(bool)), + aSpeakStop, SLOT(setEnabled(bool)) ); + } + } -// return m_tts; -//} + return m_tts; +} +#endif /* PageView. What's in this file? -> quick overview. @@ -305,7 +314,9 @@ PageView::PageView( QWidget *parent, Okular::Document *document ) d->messageWindow = new PageViewMessage(this); d->m_formsVisible = false; d->formsWidgetController = 0; -// d->m_tts = 0; +#ifdef HAVE_SPEECH + d->m_tts = 0; +#endif d->refreshTimer = 0; d->refreshPage = -1; d->aRotateClockwise = 0; @@ -410,8 +421,10 @@ PageView::PageView( QWidget *parent, Okular::Document *document ) PageView::~PageView() { -// if ( d->m_tts ) -// d->m_tts->stopAllSpeechs(); +#ifdef HAVE_SPEECH + if ( d->m_tts ) + d->m_tts->stopAllSpeechs(); +#endif // delete the local storage structure @@ -1074,12 +1087,14 @@ void PageView::updateActionState( bool haspages, bool documentChanged, bool hasf } d->aToggleAnnotator->setEnabled( allowAnnotations ); } +#ifdef HAVE_SPEECH if ( d->aSpeakDoc ) { - const bool enablettsactions = haspages ? Okular::Settings::useKTTSD() : false; + const bool enablettsactions = haspages ? Okular::Settings::useTTS() : false; d->aSpeakDoc->setEnabled( enablettsactions ); d->aSpeakPage->setEnabled( enablettsactions ); } +#endif if (d->aMouseMagnifier) d->aMouseMagnifier->setEnabled(d->document->supportsTiles()); } @@ -2600,7 +2615,7 @@ void PageView::mouseReleaseEvent( QMouseEvent * e ) textToClipboard->setEnabled( false ); textToClipboard->setText( i18n("Copy forbidden by DRM") ); } - if ( Okular::Settings::useKTTSD() ) + if ( Okular::Settings::useTTS() ) speakText = menu.addAction( QIcon::fromTheme("text-speak"), i18n( "Speak Text" ) ); if ( copyAllowed ) { @@ -2665,11 +2680,13 @@ void PageView::mouseReleaseEvent( QMouseEvent * e ) if ( cb->supportsSelection() ) cb->setText( selectedText, QClipboard::Selection ); } +#ifdef HAVE_SPEECH else if ( choice == speakText ) { - // [2] speech selection using KTTSD -// d->tts()->say( selectedText ); + // [2] speech selection using TTS + d->tts()->say( selectedText ); } +#endif } } // clear widget selection and invalidate rect @@ -2850,10 +2867,10 @@ void PageView::mouseReleaseEvent( QMouseEvent * e ) { QMenu menu( this ); QAction *textToClipboard = menu.addAction( QIcon::fromTheme( "edit-copy" ), i18n( "Copy Text" ) ); - QAction *speakText = 0; QAction *httpLink = 0; -// if ( Okular::Settings::useKTTSD() ) -// speakText = menu.addAction( QIcon::fromTheme( "text-speak" ), i18n( "Speak Text" ) ); + QAction *speakText = 0; + if ( Okular::Settings::useTTS() ) + speakText = menu.addAction( QIcon::fromTheme( "text-speak" ), i18n( "Speak Text" ) ); if ( !d->document->isAllowed( Okular::AllowCopy ) ) { textToClipboard->setEnabled( false ); @@ -2875,11 +2892,13 @@ void PageView::mouseReleaseEvent( QMouseEvent * e ) { if ( choice == textToClipboard ) copyTextSelection(); +#ifdef HAVE_SPEECH else if ( choice == speakText ) { const QString text = d->selectedText(); -// d->tts()->say( text ); + d->tts()->say( text ); } +#endif else if ( choice == httpLink ) new KRun( QUrl( url ), this ); } @@ -4938,40 +4957,42 @@ void PageView::slotRefreshPage() Q_ARG( int, req ) ); } +#ifdef HAVE_SPEECH void PageView::slotSpeakDocument() { -// QString text; -// QVector< PageViewItem * >::const_iterator it = d->items.constBegin(), itEnd = d->items.constEnd(); -// for ( ; it < itEnd; ++it ) -// { -// Okular::RegularAreaRect * area = textSelectionForItem( *it ); -// text.append( (*it)->page()->text( area ) ); -// text.append( '\n' ); -// delete area; -// } + QString text; + QVector< PageViewItem * >::const_iterator it = d->items.constBegin(), itEnd = d->items.constEnd(); + for ( ; it < itEnd; ++it ) + { + Okular::RegularAreaRect * area = textSelectionForItem( *it ); + text.append( (*it)->page()->text( area ) ); + text.append( '\n' ); + delete area; + } -// d->tts()->say( text ); + d->tts()->say( text ); } void PageView::slotSpeakCurrentPage() { -// const int currentPage = d->document->viewport().pageNumber; + const int currentPage = d->document->viewport().pageNumber; -// PageViewItem *item = d->items.at( currentPage ); -// Okular::RegularAreaRect * area = textSelectionForItem( item ); -// const QString text = item->page()->text( area ); -// delete area; + PageViewItem *item = d->items.at( currentPage ); + Okular::RegularAreaRect * area = textSelectionForItem( item ); + const QString text = item->page()->text( area ); + delete area; -// d->tts()->say( text ); + d->tts()->say( text ); } void PageView::slotStopSpeaks() { -// if ( !d->m_tts ) -// return; + if ( !d->m_tts ) + return; -// d->m_tts->stopAllSpeechs(); + d->m_tts->stopAllSpeechs(); } +#endif void PageView::slotAction( Okular::Action *action ) { diff --git a/ui/pageview.h b/ui/pageview.h index 4f423d72a..f9676feda 100644 --- a/ui/pageview.h +++ b/ui/pageview.h @@ -238,9 +238,11 @@ Q_OBJECT void slotToggleForms(); void slotFormChanged( int pageNumber ); void slotRefreshPage(); +#ifdef HAVE_SPEECH void slotSpeakDocument(); void slotSpeakCurrentPage(); void slotStopSpeaks(); +#endif void slotAction( Okular::Action *action ); void externalKeyPressEvent( QKeyEvent *e ); void slotAnnotationWindowDestroyed( QObject *window ); diff --git a/ui/tts.cpp b/ui/tts.cpp index 6e39e2013..8901e0961 100644 --- a/ui/tts.cpp +++ b/ui/tts.cpp @@ -12,80 +12,35 @@ #include #include -#include -#include -#include - -#include "kspeechinterface.h" +#include /* Private storage. */ class OkularTTS::Private { public: Private( OkularTTS *qq ) - : q( qq ), kspeech( 0 ) - , watcher( "org.kde.kttsd", QDBusConnection::sessionBus(), QDBusServiceWatcher::WatchForUnregistration ) + : q( qq ), speech( new QTextToSpeech ) { } - void setupIface(); - void teardownIface(); - - OkularTTS *q; - org::kde::KSpeech* kspeech; - QSet< int > jobs; - QDBusServiceWatcher watcher; -}; - -void OkularTTS::Private::setupIface() -{ - if ( kspeech ) - return; - - // If KTTSD not running, start it. - QDBusReply reply = QDBusConnection::sessionBus().interface()->isServiceRegistered( "org.kde.kttsd" ); - bool kttsdactive = false; - if ( reply.isValid() ) - kttsdactive = reply.value(); - if ( !kttsdactive ) + ~Private() { - QString error; - if ( KToolInvocation::startServiceByDesktopName( "kttsd", QStringList(), &error ) ) - { - emit q->errorMessage( i18n( "Starting Jovie Text-to-Speech service Failed: %1", error ) ); - } - else - { - kttsdactive = true; - } + delete speech; + speech = 0; } - if ( kttsdactive ) - { - // creating the connection to the kspeech interface - kspeech = new org::kde::KSpeech( "org.kde.kttsd", "/KSpeech", QDBusConnection::sessionBus() ); - kspeech->setParent( q ); - kspeech->setApplicationName( "Okular" ); - connect(kspeech, &org::kde::KSpeech::jobStateChanged, q, &OkularTTS::slotJobStateChanged); - } -} - -void OkularTTS::Private::teardownIface() -{ - delete kspeech; - kspeech = 0; -} + OkularTTS *q; + QTextToSpeech *speech; +}; OkularTTS::OkularTTS( QObject *parent ) : QObject( parent ), d( new Private( this ) ) { - connect(&d->watcher, &QDBusServiceWatcher::serviceUnregistered, this, &OkularTTS::slotServiceUnregistered); + connect( d->speech, &QTextToSpeech::stateChanged, this, &OkularTTS::slotSpeechStateChanged); } OkularTTS::~OkularTTS() { - disconnect( &d->watcher, 0, this, 0 ); - delete d; } @@ -94,50 +49,24 @@ void OkularTTS::say( const QString &text ) if ( text.isEmpty() ) return; - d->setupIface(); - if ( d->kspeech ) - { - QDBusReply< int > reply = d->kspeech->say( text, KSpeech::soPlainText ); - if ( reply.isValid() ) - { - d->jobs.insert( reply.value() ); - emit hasSpeechs( true ); - } - } + d->speech->say( text ); } void OkularTTS::stopAllSpeechs() { - if ( !d->kspeech ) + if ( !d->speech ) return; - d->kspeech->removeAllJobs(); -} - -void OkularTTS::slotServiceUnregistered( const QString &service ) -{ - if ( service == QLatin1String( "org.kde.kttsd" ) ) - { - d->teardownIface(); - } + d->speech->stop(); } -void OkularTTS::slotJobStateChanged( const QString &appId, int jobNum, int state ) +void OkularTTS::slotSpeechStateChanged(QTextToSpeech::State state) { - // discard non ours job - if ( appId != QDBusConnection::sessionBus().baseService() || !d->kspeech ) - return; - - switch ( state ) - { - case KSpeech::jsDeleted: - d->jobs.remove( jobNum ); - emit hasSpeechs( !d->jobs.isEmpty() ); - break; - case KSpeech::jsFinished: - d->kspeech->removeJob( jobNum ); - break; - } + if (state == QTextToSpeech::Speaking) + emit isSpeaking(true); + else + emit isSpeaking(false); } #include "moc_tts.cpp" + diff --git a/ui/tts.h b/ui/tts.h index cb990e97b..8c0fdf6e4 100644 --- a/ui/tts.h +++ b/ui/tts.h @@ -11,6 +11,7 @@ #define _TTS_H_ #include +#include class OkularTTS : public QObject { @@ -22,13 +23,11 @@ class OkularTTS : public QObject void say( const QString &text ); void stopAllSpeechs(); - signals: - void hasSpeechs( bool has ); - void errorMessage( const QString &message ); + public slots: + void slotSpeechStateChanged(QTextToSpeech::State state); - private slots: - void slotServiceUnregistered( const QString& ); - void slotJobStateChanged( const QString &appId, int jobNum, int state ); + signals: + void isSpeaking( bool speaking ); private: // private storage