diff --git a/CMakeLists.txt b/CMakeLists.txt index 3dfeba00..f72e9809 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -44,6 +44,7 @@ find_package(Qt6 REQUIRED COMPONENTS Widgets Svg Xml + TextToSpeech ) if (APPLE) @@ -148,6 +149,7 @@ set(UI_SRC preferences.ui scanpopup.ui sources.ui + texttospeechsource.ui ) if(WITH_EPWING_SUPPORT) set(EPWING_SUPPORT_SRC @@ -167,28 +169,12 @@ if (APPLE) machotkeywrapper.mm macmouseover.hh macmouseover.mm - speechclient.hh - speechclient_mac.mm - texttospeechsource.cc - texttospeechsource.hh - texttospeechsource.ui + src/platform/gd_clipboard.cpp src/platform/gd_clipboard.h ) endif () -if(MSVC) - set(WIN_SRC - guids.c # ONLY RELATED TO TTS - speechclient.hh - speechclient_win.cc - speechhlp.cc - speechhlp.hh - texttospeechsource.cc - texttospeechsource.hh - texttospeechsource.ui - ) -endif() if(WITH_FFMPEG_PLAYER) set(FFMPEG_SUPPORT_SRC @@ -201,7 +187,6 @@ endif() set(PROJECT_SOURCES ${UI_SRC} ${MAC_SRC} - ${WIN_SRC} ${QRC_RESOURCES} ${EPWING_SUPPORT_SRC} ${FFMPEG_SUPPORT_SRC} @@ -404,7 +389,6 @@ set(PROJECT_SOURCES sounddir.hh sources.cc sources.hh - sphelper.hh splitfile.cc splitfile.hh sptr.hh @@ -465,6 +449,10 @@ set(PROJECT_SOURCES src/ui/articleview.cpp src/ui/articleview.h src/ui/ftssearchpanel.cpp src/ui/ftssearchpanel.h src/ui/searchpanel.cpp src/ui/searchpanel.h + speechclient.hh + speechclient.cc + texttospeechsource.cc + texttospeechsource.hh ) qt_add_executable(${CMAKE_PROJECT_NAME} @@ -494,6 +482,7 @@ target_link_libraries(${CMAKE_PROJECT_NAME} PRIVATE Qt6::WebEngineWidgets Qt6::Widgets Qt6::Svg + Qt6::TextToSpeech ) if(APPLE) diff --git a/config.cc b/config.cc index fc4b0db9..bdd510a6 100644 --- a/config.cc +++ b/config.cc @@ -841,21 +841,24 @@ Class load() { QDomNodeList nl = ves.toElement().elementsByTagName( "voiceEngine" ); - for ( int x = 0; x < nl.length(); ++x ) - { + for( int x = 0; x < nl.length(); ++x ) { QDomElement ve = nl.item( x ).toElement(); VoiceEngine v; - v.enabled = ve.attribute( "enabled" ) == "1"; - v.id = ve.attribute( "id" ); - v.name = ve.attribute( "name" ); + v.enabled = ve.attribute( "enabled" ) == "1"; + v.engine_name = ve.attribute( "engine_name" ); + v.name = ve.attribute( "name" ); + v.voice_name = ve.attribute( "voice_name" ); + v.locale = QLocale( ve.attribute( "locale" ) ); v.iconFilename = ve.attribute( "icon" ); - v.volume = ve.attribute( "volume", "50" ).toInt(); - if( v.volume < 0 || v.volume > 100 ) + v.volume = ve.attribute( "volume", "50" ).toInt(); + if ( ( v.volume < 0 ) || ( v.volume > 100 ) ) { v.volume = 50; - v.rate = ve.attribute( "rate", "50" ).toInt(); - if( v.rate < 0 || v.rate > 100 ) - v.rate = 50; + } + v.rate = ve.attribute( "rate", "0" ).toInt(); + if ( ( v.rate < -10 ) || ( v.rate > 10 ) ) { + v.rate = 0; + } c.voiceEngines.push_back( v ); } } @@ -1656,33 +1659,40 @@ void save( Class const & c ) QDomNode ves = dd.createElement( "voiceEngines" ); root.appendChild( ves ); - for ( VoiceEngines::const_iterator i = c.voiceEngines.begin(); i != c.voiceEngines.end(); ++i ) - { + for( const auto & voiceEngine : c.voiceEngines ) { QDomElement v = dd.createElement( "voiceEngine" ); ves.appendChild( v ); - QDomAttr id = dd.createAttribute( "id" ); - id.setValue( i->id ); + QDomAttr id = dd.createAttribute( "engine_name" ); + id.setValue( voiceEngine.engine_name ); v.setAttributeNode( id ); + QDomAttr locale = dd.createAttribute( "locale" ); + locale.setValue( voiceEngine.locale.name() ); + v.setAttributeNode( locale ); + QDomAttr name = dd.createAttribute( "name" ); - name.setValue( i->name ); + name.setValue( voiceEngine.name ); v.setAttributeNode( name ); + QDomAttr voice_name = dd.createAttribute( "voice_name" ); + voice_name.setValue( voiceEngine.voice_name ); + v.setAttributeNode( voice_name ); + QDomAttr enabled = dd.createAttribute( "enabled" ); - enabled.setValue( i->enabled ? "1" : "0" ); + enabled.setValue( voiceEngine.enabled ? "1" : "0" ); v.setAttributeNode( enabled ); QDomAttr icon = dd.createAttribute( "icon" ); - icon.setValue( i->iconFilename ); + icon.setValue( voiceEngine.iconFilename ); v.setAttributeNode( icon ); QDomAttr volume = dd.createAttribute( "volume" ); - volume.setValue( QString::number( i->volume ) ); + volume.setValue( QString::number( voiceEngine.volume ) ); v.setAttributeNode( volume ); QDomAttr rate = dd.createAttribute( "rate" ); - rate.setValue( QString::number( i->rate ) ); + rate.setValue( QString::number( voiceEngine.rate ) ); v.setAttributeNode( rate ); } } diff --git a/config.hh b/config.hh index 5b7151f6..69db5d0f 100644 --- a/config.hh +++ b/config.hh @@ -13,6 +13,7 @@ #include #include #include "ex.hh" +#include #ifdef Q_OS_WIN #include @@ -639,32 +640,37 @@ typedef QVector< Program > Programs; struct VoiceEngine { bool enabled; - QString id; + //engine name. + QString engine_name; QString name; + //voice name. + QString voice_name; QString iconFilename; - int volume; // 0-100 allowed - int rate; // 0-100 allowed + QLocale locale; + int volume; // 0~1 allowed + int rate; // -1 ~ 1 allowed - VoiceEngine(): enabled( false ) - , volume( 50 ) - , rate( 50 ) - {} - VoiceEngine( QString id_, QString name_, int volume_, int rate_ ): - enabled( false ) - , id( id_ ) - , name( name_ ) - , volume( volume_ ) - , rate( rate_ ) + VoiceEngine(): + enabled( false ), + volume( 50 ), + rate( 0 ) + { + } + VoiceEngine( QString engine_nane_, QString name_, QString voice_name_, QLocale locale_, int volume_, int rate_ ): + enabled( false ), + engine_name( engine_nane_ ), + name( name_ ), + voice_name( voice_name_ ), + locale( locale_ ), + volume( volume_ ), + rate( rate_ ) {} bool operator == ( VoiceEngine const & other ) const { - return enabled == other.enabled && - id == other.id && - name == other.name && - iconFilename == other.iconFilename && - volume == other.volume && - rate == other.rate; + return enabled == other.enabled && engine_name == other.engine_name && name == other.name + && voice_name == other.voice_name && locale == other.locale && iconFilename == other.iconFilename + && volume == other.volume && rate == other.rate; } bool operator != ( VoiceEngine const & other ) const diff --git a/goldendict.pro b/goldendict.pro index 21325e3a..82adc7c6 100644 --- a/goldendict.pro +++ b/goldendict.pro @@ -45,7 +45,8 @@ QT += core \ webchannel\ printsupport \ help \ - concurrent + concurrent \ + texttospeech greaterThan(QT_MAJOR_VERSION, 5): QT += webenginecore core5compat @@ -246,8 +247,7 @@ mac { } OBJECTIVE_SOURCES += machotkeywrapper.mm \ - macmouseover.mm \ - speechclient_mac.mm + macmouseover.mm ICON = icons/macicon.icns QMAKE_INFO_PLIST = myInfo.plist @@ -544,27 +544,17 @@ SOURCES += folding.cc \ src/ui/ftssearchpanel.cpp \ src/ui/searchpanel.cpp -win32 { - FORMS += texttospeechsource.ui - SOURCES += guids.c \ - speechclient_win.cc \ - texttospeechsource.cc \ - speechhlp.cc - HEADERS += texttospeechsource.hh \ - sapi.hh \ - sphelper.hh \ - speechclient.hh \ - speechhlp.hh -} +#speech to text +FORMS += texttospeechsource.ui +SOURCES += speechclient.cc \ + texttospeechsource.cc +HEADERS += texttospeechsource.hh \ + speechclient.hh mac { HEADERS += macmouseover.hh \ - texttospeechsource.hh \ - speechclient.hh \ src/platform/gd_clipboard.h - FORMS += texttospeechsource.ui - SOURCES += texttospeechsource.cc \ - src/platform/gd_clipboard.cpp + SOURCES += src/platform/gd_clipboard.cpp } unix:!mac { @@ -573,8 +563,8 @@ unix:!mac { } - HEADERS += wildcard.hh - SOURCES += wildcard.cc +HEADERS += wildcard.hh +SOURCES += wildcard.cc CONFIG( zim_support ) { diff --git a/sources.cc b/sources.cc index ca758ed3..e4c5ca67 100644 --- a/sources.cc +++ b/sources.cc @@ -5,7 +5,6 @@ #include #include #include -#include "gddebug.hh" #ifdef MAKE_CHINESE_CONVERSION_SUPPORT #include "chineseconversion.hh" @@ -17,9 +16,7 @@ Sources::Sources( QWidget * parent, Config::Class const & cfg): #ifdef MAKE_CHINESE_CONVERSION_SUPPORT chineseConversion( new ChineseConversion( this, cfg.transliteration.chinese ) ), #endif -#if defined( Q_OS_WIN32 ) || defined( Q_OS_MAC ) - textToSpeechSource( NULL ), -#endif + textToSpeechSource( nullptr ), itemDelegate( new QItemDelegate( this ) ), itemEditorFactory( new QItemEditorFactory() ), mediawikisModel( this, cfg.mediawikis ), @@ -124,10 +121,8 @@ Sources::Sources( QWidget * parent, Config::Class const & cfg): ui.forvoLanguageCodes->setText( forvo.languageCodes ); // Text to speech -#if defined( Q_OS_WIN32 ) || defined( Q_OS_MAC ) textToSpeechSource = new TextToSpeechSource( this, cfg.voiceEngines ); ui.tabWidget->addTab( textToSpeechSource, QIcon(":/icons/text2speech.svg"), tr( "Text to Speech" ) ); -#endif if ( Config::isPortableVersion() ) { @@ -341,13 +336,9 @@ void Sources::on_removeProgram_clicked() Config::VoiceEngines Sources::getVoiceEngines() const { -#if defined( Q_OS_WIN32 ) || defined( Q_OS_MAC ) if ( !textToSpeechSource ) return Config::VoiceEngines(); return textToSpeechSource->getVoiceEnginesModel().getCurrentVoiceEngines(); -#else - return Config::VoiceEngines(); -#endif } Config::Hunspell Sources::getHunspell() const diff --git a/sources.hh b/sources.hh index 76a6e841..0560ee9a 100644 --- a/sources.hh +++ b/sources.hh @@ -12,9 +12,7 @@ #include #include -#if defined( Q_OS_WIN32 ) || defined( Q_OS_MAC ) #include "texttospeechsource.hh" -#endif #ifdef MAKE_CHINESE_CONVERSION_SUPPORT // Forward declaration @@ -295,9 +293,7 @@ private: ChineseConversion *chineseConversion; #endif -#if defined( Q_OS_WIN32 ) || defined( Q_OS_MAC ) TextToSpeechSource *textToSpeechSource; -#endif QItemDelegate * itemDelegate; QItemEditorFactory * itemEditorFactory; diff --git a/sources.ui b/sources.ui index f93684d8..98dd49e7 100644 --- a/sources.ui +++ b/sources.ui @@ -33,7 +33,7 @@ - + :/icons/folders.svg:/icons/folders.svg @@ -96,7 +96,7 @@ - + :/icons/folder-sound.svg:/icons/folder-sound.svg @@ -152,7 +152,7 @@ - + :/icons/icon32_hunspell.png:/icons/icon32_hunspell.png @@ -220,7 +220,7 @@ of the appropriate groups to use them. - + :/icons/icon32_wiki.png:/icons/icon32_wiki.png @@ -279,7 +279,7 @@ of the appropriate groups to use them. - + :/icons/internet.svg:/icons/internet.svg @@ -343,7 +343,7 @@ of the appropriate groups to use them. - + :/icons/network.svg:/icons/network.svg @@ -399,7 +399,7 @@ of the appropriate groups to use them. - + :/icons/programs.svg:/icons/programs.svg @@ -458,7 +458,7 @@ of the appropriate groups to use them. - + :/icons/lingualibre.svg:/icons/lingualibre.svg @@ -526,7 +526,7 @@ Full list of availiable languages can be found <a href="https://linguali - + :/icons/forvo.png:/icons/forvo.png @@ -674,7 +674,7 @@ Full list of availiable languages can be found <a href="https://linguali - + :/icons/transliteration.png:/icons/transliteration.png @@ -692,7 +692,7 @@ Full list of availiable languages can be found <a href="https://linguali Greek transliteration - + :/flags/gr.png:/flags/gr.png @@ -703,7 +703,7 @@ Full list of availiable languages can be found <a href="https://linguali Russian transliteration - + :/flags/ru.png:/flags/ru.png @@ -714,7 +714,7 @@ Full list of availiable languages can be found <a href="https://linguali German transliteration - + :/flags/de.png:/flags/de.png @@ -725,7 +725,7 @@ Full list of availiable languages can be found <a href="https://linguali Belarusian transliteration - + :/flags/by.png:/flags/by.png @@ -857,9 +857,6 @@ Not implemented yet in GoldenDict. removeSoundDir paths - - - - + diff --git a/speechclient.cc b/speechclient.cc new file mode 100644 index 00000000..72a732ee --- /dev/null +++ b/speechclient.cc @@ -0,0 +1,53 @@ +#include "speechclient.hh" + +#include + +SpeechClient::SpeechClient( Config::VoiceEngine const & e, QObject * parent ): + QObject( parent ), + internalData( std::make_unique< InternalData >( e ) ) +{ +} + + +SpeechClient::Engines SpeechClient::availableEngines() +{ + Engines engines; + const auto innerEngines = QTextToSpeech::availableEngines(); + + for( const auto & engine_name : innerEngines ) { + std::unique_ptr< QTextToSpeech > sp( std::make_unique< QTextToSpeech >( engine_name ) ); + const QVector< QLocale > locales = sp->availableLocales(); + for( const QLocale & locale : locales ) { + //on some platforms ,change the locale will change voices too. + sp->setLocale( locale ); + for( const QVoice & voice : sp->availableVoices() ) { + QString name( QString( "%4 - %3 %1 (%2)" ) + .arg( QLocale::languageToString( locale.language() ), + ( QLocale::countryToString( locale.country() ) ), + voice.name(), + engine_name ) ); + SpeechClient::Engine engine( Config::VoiceEngine( engine_name, name, voice.name(), locale, 50, 0 ) ); + engines.push_back( engine ); + } + } + } + + return engines; +} + +bool SpeechClient::tell( QString const & text, int volume, int rate ) +{ + if( internalData->sp->state() != QTextToSpeech::Ready ) + return false; + + internalData->sp->setVolume( volume / 100.0 ); + internalData->sp->setRate( rate / 10.0 ); + + internalData->sp->say( text ); + return true; +} + +bool SpeechClient::tell( QString const & text ) +{ + return tell(text, internalData->engine.volume, internalData->engine.rate); +} diff --git a/speechclient.hh b/speechclient.hh index da97adff..82a5e8c6 100644 --- a/speechclient.hh +++ b/speechclient.hh @@ -3,6 +3,8 @@ #include #include "config.hh" +#include +#include class SpeechClient: public QObject { @@ -12,42 +14,65 @@ public: struct Engine { - QString id; + //engine name + QString engine_name; QString name; - // Volume and rate may vary from 0 to 100 + //voice name + QString voice_name; + QString locale; + // Volume vary from 0~1 and rate vary from -1 to 1 int volume; int rate; - Engine( Config::VoiceEngine const & e ) : - id( e.id ) - , name( e.name ) - , volume( e.volume ) - , rate( e.rate ) - {} + explicit Engine( Config::VoiceEngine const & e ): + engine_name( e.engine_name ), + name( e.name ), + voice_name( e.voice_name ), + locale( e.locale.name() ), + volume( e.volume ), + rate( e.rate ) + { + } }; - typedef QList Engines; + struct InternalData + { + explicit InternalData( Config::VoiceEngine const & e ): + sp( std::make_unique< QTextToSpeech >( e.engine_name ) ), + engine( e ) + { + sp->setLocale( e.locale ); + auto voices = sp->availableVoices(); + for( const auto & voice : voices ) { + if( voice.name() == e.voice_name ) { + sp->setVoice( voice ); - SpeechClient( Config::VoiceEngine const & e, QObject * parent = 0L ); - virtual ~SpeechClient(); + break; + } + } + + sp->setVolume( e.volume / 100.0 ); + sp->setRate( e.rate / 10.0 ); + } + + std::unique_ptr< QTextToSpeech > sp; + Engine engine; + }; + + using Engines = QList< Engine >; + + explicit SpeechClient( Config::VoiceEngine const & e, QObject * parent = nullptr ); static Engines availableEngines(); - const Engine & engine() const; + bool tell( QString const & text, int volume, int rate ); + bool tell( QString const & text ); - bool tell( QString const & text, int volume = -1, int rate = -1 ); - bool say( QString const & text, int volume = -1, int rate = -1 ); - -signals: + signals: void started( bool ok ); void finished(); -protected: - virtual void timerEvent( QTimerEvent * evt ); - -private: - struct InternalData; - InternalData * internalData; - + private: + std::unique_ptr< InternalData > internalData; }; #endif // __SPEECHCLIENT_HH_INCLUDED__ diff --git a/speechclient_mac.mm b/speechclient_mac.mm deleted file mode 100644 index 92db6c53..00000000 --- a/speechclient_mac.mm +++ /dev/null @@ -1,191 +0,0 @@ -#include "speechclient.hh" - -#include -#include -#include -#include -#include -#include - -static QString NSStringToQString(const NSString * nsstr ) -{ - return QString::fromUtf8( [ nsstr UTF8String ] ); -} - -static NSString * QStringToNSString( QString const & qstr, bool needAlloc = false ) -{ - if( needAlloc ) - return [ [ NSString alloc ] initWithUTF8String : qstr.toUtf8().data() ]; - return [ NSString stringWithUTF8String : qstr.toUtf8().data() ]; -} - -struct SpeechClient::InternalData -{ - InternalData( Config::VoiceEngine const & e ): - waitingFinish( false ) - , engine( e ) - , oldVolume( -1 ) - , oldRate( -1 ) - , stringToPlay( nil ) - { - NSAutoreleasePool * pool = [ [ NSAutoreleasePool alloc ] init ]; - sp = [ [ NSSpeechSynthesizer alloc ] initWithVoice : QStringToNSString( e.id ) ]; - [ pool drain ]; - } - - ~InternalData() - { - [ sp release ]; - if( stringToPlay != nil ) - [ stringToPlay release ]; - } - - NSSpeechSynthesizer * sp; - bool waitingFinish; - SpeechClient::Engine engine; - float oldVolume; - float oldRate; - QString oldMode; - NSString * stringToPlay; -}; - -SpeechClient::SpeechClient( Config::VoiceEngine const & e, QObject * parent ): - QObject( parent ), - internalData( new InternalData( e ) ) -{ -} - -SpeechClient::~SpeechClient() -{ - delete internalData; -} - -SpeechClient::Engines SpeechClient::availableEngines() -{ - Engines engines; - NSAutoreleasePool * pool = [ [ NSAutoreleasePool alloc ] init ]; - - NSArray * voices = [ NSSpeechSynthesizer availableVoices ]; - int voicesNum = [ voices count ]; - for( int i = 0; i < voicesNum; i++ ) - { - QString id = NSStringToQString( [ voices objectAtIndex : i ] ); - QString name; - int n = id.lastIndexOf( '.' ); - if( n >= 0 ) - name = id.right( id.size() - n - 1 ); - else - name = id; - engines.push_back( SpeechClient::Engine( Config::VoiceEngine( - id, name, 50, 50 ) ) ); - } - - [ pool drain ]; - return engines; -} - -const SpeechClient::Engine & SpeechClient::engine() const -{ - return internalData->engine; -} - -bool SpeechClient::tell( QString const & text, int volume, int rate ) -{ - if( !internalData->sp || [ NSSpeechSynthesizer isAnyApplicationSpeaking ] ) - return false; - - if ( internalData->waitingFinish ) - return false; - - if( volume < 0 ) - volume = engine().volume; - if( rate < 0 ) - rate = engine().rate; - - NSAutoreleasePool * pool = [ [ NSAutoreleasePool alloc ] init ]; - - internalData->oldVolume = [ internalData->sp volume ]; - [ internalData->sp setVolume : ( volume / 100.0 ) ]; - - internalData->oldRate = [ internalData->sp rate ]; - [ internalData->sp setRate : ( rate * 2.0 + 100 ) ]; - - NSError * err = nil; - NSString * oldMode = [ internalData->sp objectForProperty : NSSpeechInputModeProperty error : &err ]; - if( err == nil || [ err code ] == 0 ) - { - internalData->oldMode = NSStringToQString( oldMode ); - [ internalData->sp setObject : NSSpeechModeText forProperty : NSSpeechInputModeProperty error : &err ]; - } - else - internalData->oldMode.clear(); - - internalData->stringToPlay = QStringToNSString( text, true ); - - bool ok = [ internalData->sp startSpeakingString : internalData->stringToPlay ]; - - emit started( ok ); - - if ( ok ) - { - internalData->waitingFinish = true; - startTimer( 50 ); - } - else - { - if( internalData->stringToPlay != nil ) - [ internalData->stringToPlay release ]; - internalData->stringToPlay = nil; - emit finished(); - } - - [ pool drain ]; - - return ok; -} - -bool SpeechClient::say( QString const & text, int volume, int rate ) -{ -(void) text; -(void) volume; -(void) rate; - return false; -} - -void SpeechClient::timerEvent( QTimerEvent * evt ) -{ - QObject::timerEvent( evt ); - - if ( !internalData->waitingFinish ) - return; - - if ( ![ internalData->sp isSpeaking ] ) - { - killTimer( evt->timerId() ) ; - internalData->waitingFinish = false; - - NSAutoreleasePool * pool = [ [ NSAutoreleasePool alloc ] init ]; - - if( internalData->oldVolume >=0 ) - [ internalData->sp setVolume : internalData->oldVolume ]; - if( internalData->oldRate >=0 ) - [ internalData->sp setRate : internalData->oldRate ]; - internalData->oldVolume = -1; - internalData->oldRate = -1; - - NSError * err; - if( !internalData->oldMode.isEmpty() ) - [ internalData->sp setObject : QStringToNSString( internalData->oldMode ) - forProperty : NSSpeechInputModeProperty error : &err ]; - - internalData->oldMode.clear(); - - if( internalData->stringToPlay != nil ) - [ internalData->stringToPlay release ]; - internalData->stringToPlay = nil; - - [ pool drain ]; - - emit finished(); - } -} diff --git a/speechclient_win.cc b/speechclient_win.cc deleted file mode 100644 index 5b5be4d0..00000000 --- a/speechclient_win.cc +++ /dev/null @@ -1,144 +0,0 @@ -#include "speechclient.hh" - -#include -#include "speechhlp.hh" - -#include - -struct SpeechClient::InternalData -{ - InternalData( Config::VoiceEngine const & e ): - waitingFinish( false ) - , engine( e ) - , oldVolume( -1 ) - , oldRate( -1 ) - { - sp = speechCreate( e.id.toStdWString().c_str() ); - } - - ~InternalData() - { - speechDestroy( sp ); - } - - SpeechHelper sp; - bool waitingFinish; - Engine engine; - int oldVolume; - int oldRate; -}; - -SpeechClient::SpeechClient( Config::VoiceEngine const & e, QObject * parent ): - QObject( parent ), - internalData( new InternalData( e ) ) -{ -} - -SpeechClient::~SpeechClient() -{ - delete internalData; -} - -static bool enumEngines( void * /* token */, - const wchar_t * id, - const wchar_t * name, - void * userData ) -{ - SpeechClient::Engines * pEngines = ( SpeechClient::Engines * )userData; - SpeechClient::Engine engine( Config::VoiceEngine( - QString::fromWCharArray( id ), - QString::fromWCharArray( name ), - 50, 50 ) ); - pEngines->push_back( engine ); - return true; -} - -SpeechClient::Engines SpeechClient::availableEngines() -{ - Engines engines; - speechEnumerateAvailableEngines( enumEngines, &engines ); - return engines; -} - -const SpeechClient::Engine & SpeechClient::engine() const -{ - return internalData->engine; -} - -bool SpeechClient::tell( QString const & text, int volume, int rate ) -{ - if ( !speechAvailable( internalData->sp ) ) - return false; - - if ( internalData->waitingFinish ) - return false; - - if( volume < 0 ) - volume = engine().volume; - if( rate < 0 ) - rate = engine().rate; - - internalData->oldVolume = setSpeechVolume( internalData->sp, volume ); - internalData->oldRate = setSpeechRate( internalData->sp, rate ); - - bool ok = speechTell( internalData->sp, text.toStdWString().c_str() ); - - emit started( ok ); - - if ( ok ) - { - internalData->waitingFinish = true; - startTimer( 50 ); - } - else - { - emit finished(); - } - return ok; -} - -bool SpeechClient::say( QString const & text, int volume, int rate ) -{ - if ( !speechAvailable( internalData->sp ) ) - return false; - - if( volume < 0 ) - volume = engine().volume; - if( rate < 0 ) - rate = engine().rate; - - int oldVolume = setSpeechVolume( internalData->sp, volume ); - int oldRate = setSpeechRate( internalData->sp, rate ); - - bool ok = speechSay( internalData->sp, text.toStdWString().c_str() ); - - if( oldVolume >=0 ) - setSpeechVolume( internalData->sp, oldVolume ); - if( oldRate >=0 ) - setSpeechRate( internalData->sp, oldRate ); - - return ok; -} - -void SpeechClient::timerEvent( QTimerEvent * evt ) -{ - QObject::timerEvent( evt ); - - if ( !internalData->waitingFinish ) - return; - - if ( speechTellFinished( internalData->sp ) ) - { - killTimer( evt->timerId() ) ; - internalData->waitingFinish = false; - - if( internalData->oldVolume >=0 ) - setSpeechVolume( internalData->sp, internalData->oldVolume ); - if( internalData->oldRate >=0 ) - setSpeechRate( internalData->sp, internalData->oldRate ); - internalData->oldVolume = -1; - internalData->oldRate = -1; - - emit finished(); - } -} diff --git a/speechhlp.cc b/speechhlp.cc deleted file mode 100644 index 3bfbd25b..00000000 --- a/speechhlp.cc +++ /dev/null @@ -1,191 +0,0 @@ -#define WINVER 0x0500 // At least WinXP required -#include -#include - -#include "speechhlp.hh" -#include -#include "sapi.hh" -#include "sphelper.hh" - -using std::wstring; - -struct _SpeechHelper -{ - ISpVoice * voice; - wstring engineId; - wstring engineName; - bool willInvokeCoUninitialize; - - _SpeechHelper() : - willInvokeCoUninitialize(false) - { - HRESULT hr; - hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED); - willInvokeCoUninitialize = (hr != RPC_E_CHANGED_MODE); - CoCreateInstance(CLSID_SpVoice, NULL, CLSCTX_INPROC_SERVER, IID_ISpVoice, (void**)&voice); - } - - ~_SpeechHelper() - { - if (voice) - voice->Release(); - - if (willInvokeCoUninitialize) - CoUninitialize(); - } -}; - -SpeechHelper speechCreate(const wchar_t *engineId) -{ - SpeechHelper sp = new _SpeechHelper(); - HRESULT hr; - ISpObjectToken * spToken; - - hr = SpGetTokenFromId(engineId, &spToken); - - if (SUCCEEDED(hr)) - { - if (SUCCEEDED(hr) && sp->voice) - { - sp->voice->SetVoice(spToken); - - WCHAR * engineName = NULL; - SpGetDescription( spToken, &engineName ); - sp->engineId = engineId; - if (engineName) - { - sp->engineName = engineName; - CoTaskMemFree(engineName); - } - } - - spToken->Release(); - } - - return sp; -} - -void speechDestroy(SpeechHelper sp) -{ - delete sp; -} - -bool speechAvailable(SpeechHelper sp) -{ - if (!sp) - return false; - - return !!(sp->voice); -} - -void speechEnumerateAvailableEngines(EnumerateCallback callback, void *userData) -{ - HRESULT hr; - IEnumSpObjectTokens * enumSpTokens = NULL; - ULONG count = 0; - bool next = true; - hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED); - bool willInvokeCoUninitialize = (hr != RPC_E_CHANGED_MODE); - hr = SpEnumTokens(SPCAT_VOICES, NULL, NULL, &enumSpTokens); - if (SUCCEEDED(hr)) - hr = enumSpTokens->GetCount(&count); - - for (ULONG i = 0; i < count && next; i++) - { - ISpObjectToken * spToken = NULL; - WCHAR * engineName = NULL; - WCHAR * engineId = NULL; - - if (SUCCEEDED(hr)) - hr = enumSpTokens->Next(1, &spToken, NULL); - if (SUCCEEDED(hr)) - hr = SpGetDescription(spToken, &engineName); - if (SUCCEEDED(hr)) - hr = spToken->GetId(&engineId); - - if (SUCCEEDED(hr)) - next = callback(spToken, engineId, engineName, userData); - - if( spToken ) - spToken->Release(); - - if (engineName) - CoTaskMemFree(engineName); - if (engineId) - CoTaskMemFree(engineId); - } - - if( enumSpTokens ) - enumSpTokens->Release(); - - if (willInvokeCoUninitialize) - CoUninitialize(); -} - -const wchar_t * speechEngineId(SpeechHelper sp) -{ - if (!sp) - return NULL; - - return sp->engineId.c_str(); -} - -const wchar_t * speechEngineName(SpeechHelper sp) -{ - if (!sp) - return NULL; - - return sp->engineName.c_str(); -} - -bool speechTell(SpeechHelper sp, const wchar_t *text) -{ - if (!sp || !sp->voice || !text) - return false; - - HRESULT hr = sp->voice->Speak(text, SPF_ASYNC | SPF_IS_NOT_XML, 0); - return !!SUCCEEDED(hr); -} - -bool speechTellFinished(SpeechHelper sp) -{ - if (!sp || !sp->voice) - return true; - - SPVOICESTATUS es; - sp->voice->GetStatus(&es, NULL); - return es.dwRunningState == SPRS_DONE; -} - -bool speechSay(SpeechHelper sp, const wchar_t *text) -{ - if (!sp || !sp->voice || !text) - return false; - - HRESULT hr = sp->voice->Speak(text, SPF_IS_NOT_XML, 0); - return !!SUCCEEDED(hr); -} - -int setSpeechVolume( SpeechHelper sp, int newVolume ) -{ - if( !sp || !sp->voice || newVolume < 0 || newVolume > 100 ) - return -1; - unsigned short oldVolume; - HRESULT hr = sp->voice->GetVolume( &oldVolume ); - if( !SUCCEEDED( hr ) ) - return -1; - sp->voice->SetVolume( (unsigned short) newVolume ); - return oldVolume; -} - -int setSpeechRate( SpeechHelper sp, int newRate ) -{ - if( !sp || !sp->voice || newRate < 0 || newRate > 100 ) - return -1; - long oldRate; - HRESULT hr = sp->voice->GetRate( &oldRate ); - if( !SUCCEEDED( hr ) ) - return -1; - sp->voice->SetRate( ( newRate - 50 ) / 5 ); - return oldRate * 5 + 50; -} diff --git a/speechhlp.hh b/speechhlp.hh deleted file mode 100644 index 2e7eed54..00000000 --- a/speechhlp.hh +++ /dev/null @@ -1,27 +0,0 @@ -#ifndef __SPEECHHLP_H__ -#define __SPEECHHLP_H__ - -#ifdef __cplusplus -extern "C" { -#endif - -typedef struct _SpeechHelper *SpeechHelper; -typedef bool (*EnumerateCallback)(void *token, const wchar_t *id, const wchar_t *name, void *userData); - -SpeechHelper speechCreate(const wchar_t *engineId); -void speechDestroy(SpeechHelper sp); -bool speechAvailable(SpeechHelper sp); -void speechEnumerateAvailableEngines(EnumerateCallback callback, void *userData); -const wchar_t * speechEngineId(SpeechHelper sp); -const wchar_t * speechEngineName(SpeechHelper sp); -bool speechTell(SpeechHelper sp, const wchar_t *text); -bool speechTellFinished(SpeechHelper sp); -bool speechSay(SpeechHelper sp, const wchar_t *text); -int setSpeechVolume( SpeechHelper sp, int newVolume ); -int setSpeechRate( SpeechHelper sp, int newRate ); - -#ifdef __cplusplus -} -#endif - -#endif // __SPEECHHLP_H__ diff --git a/sphelper.hh b/sphelper.hh deleted file mode 100644 index 6543efb7..00000000 --- a/sphelper.hh +++ /dev/null @@ -1,247 +0,0 @@ -#ifndef __SPHELPER_HH_INCLUDED__ -#define __SPHELPER_HH_INCLUDED__ - -#ifndef SR_LOCALIZED_DESCRIPTION -#define SR_LOCALIZED_DESCRIPTION L"Description" -#endif - -#ifndef REG_MUI_STRING_TRUNCATE -#define REG_MUI_STRING_TRUNCATE 0x00000001 -#endif - -#ifndef SPERR_NOT_FOUND -#define FACILITY_SAPI FACILITY_ITF -#define SAPI_ERROR_BASE 0x5000 -#define MAKE_SAPI_HRESULT(sev, err) MAKE_HRESULT(sev, FACILITY_SAPI, err) -#define MAKE_SAPI_ERROR(err) MAKE_SAPI_HRESULT(SEVERITY_ERROR, err + SAPI_ERROR_BASE) -#define SPERR_NOT_FOUND MAKE_SAPI_ERROR(0x03a) -#endif - -#ifdef _SAPI_VER -#undef _SAPI_VER -#endif -#define _SAPI_VER 0x053 - -inline void SpHexFromUlong(WCHAR * psz, ULONG ul) -{ - // If for some reason we cannot convert a number, set it to 0 - - if (_ultow(ul, psz, 16)) - { - psz[0] = L'0'; - psz[1] = 0; - } -} - -inline HRESULT SpGetTokenFromId( - const WCHAR * pszTokenId, - ISpObjectToken ** ppToken, - BOOL fCreateIfNotExist = FALSE) -{ - HRESULT hr; - ISpObjectToken * cpToken; - hr = CoCreateInstance(CLSID_SpObjectToken, NULL, CLSCTX_INPROC_SERVER, - IID_ISpObjectToken, (void**)&cpToken); - - if (SUCCEEDED(hr)) - { - hr = cpToken->SetId(NULL, pszTokenId, fCreateIfNotExist); - if (SUCCEEDED(hr)) - { - *ppToken = cpToken; - } - else - cpToken->Release(); - } - - return hr; -} - -inline HRESULT SpGetCategoryFromId( - const WCHAR * pszCategoryId, - ISpObjectTokenCategory ** ppCategory, - BOOL fCreateIfNotExist = FALSE) -{ - HRESULT hr; - - ISpObjectTokenCategory * cpTokenCategory; - hr = CoCreateInstance(CLSID_SpObjectTokenCategory, NULL, CLSCTX_INPROC_SERVER, - IID_ISpObjectTokenCategory, (void**)&cpTokenCategory ); - - if (SUCCEEDED(hr)) - { - hr = cpTokenCategory->SetId(pszCategoryId, fCreateIfNotExist); - if (SUCCEEDED(hr)) - { - *ppCategory = cpTokenCategory; - } - else - cpTokenCategory->Release(); - } - - return hr; -} - -HRESULT SpEnumTokens( - const WCHAR * pszCategoryId, - const WCHAR * pszReqAttribs, - const WCHAR * pszOptAttribs, - IEnumSpObjectTokens ** ppEnum) -{ - HRESULT hr = S_OK; - - ISpObjectTokenCategory * cpCategory; - hr = SpGetCategoryFromId(pszCategoryId, &cpCategory); - - if (SUCCEEDED(hr)) - { - hr = cpCategory->EnumTokens( - pszReqAttribs, - pszOptAttribs, - ppEnum); - cpCategory->Release(); - } - - return hr; -} - -inline HRESULT SpGetDescription(ISpObjectToken * pObjToken, WCHAR ** ppszDescription, LANGID Language = GetUserDefaultUILanguage()) -{ - WCHAR szLangId[10]; - HRESULT hr = S_OK; - - if (ppszDescription == NULL) - { - return E_POINTER; - } - *ppszDescription = NULL; - -#if _SAPI_VER >= 0x053 - WCHAR* pRegKeyPath = 0; - WCHAR* pszTemp = 0; - HKEY Handle = NULL; - - // Windows Vista does not encourage localized strings in the registry - // When running on Windows Vista query the localized engine name from a resource dll - OSVERSIONINFO ver; - ver.dwOSVersionInfoSize = sizeof( ver ); - - if( ( ::GetVersionEx( &ver ) == TRUE ) && ( ver.dwMajorVersion >= 6 ) ) - { - // If we reach this code we are running under Windows Vista - HMODULE hmodAdvapi32Dll = NULL; - typedef HRESULT (WINAPI* LPFN_RegLoadMUIStringW)(HKEY, LPCWSTR, LPWSTR, DWORD, LPDWORD, DWORD, LPCWSTR); - LPFN_RegLoadMUIStringW pfnRegLoadMUIStringW = NULL; - - // Delay bind with RegLoadMUIStringW since this function is not supported on previous versions of advapi32.dll - // RegLoadMUIStringW is supported only on advapi32.dll that ships with Windows Vista and above - // Calling RegLoadMUIStringW directly makes the loader try to resolve the function reference at load time which breaks, - // hence we manually load advapi32.dll, query for the function pointer and invoke it. - hmodAdvapi32Dll = ::LoadLibrary(TEXT("advapi32.dll")); - if(hmodAdvapi32Dll) - { - pfnRegLoadMUIStringW = (LPFN_RegLoadMUIStringW) ::GetProcAddress(hmodAdvapi32Dll, "RegLoadMUIStringW"); - if (!pfnRegLoadMUIStringW) - { - // This should not happen in Vista - // _ASSERT (pfnRegLoadMUIStringW); - hr = TYPE_E_DLLFUNCTIONNOTFOUND; - } - } - else - { - hr = HRESULT_FROM_WIN32(ERROR_DLL_NOT_FOUND); - } - - if (SUCCEEDED(hr)) - { - hr = pObjToken->GetId(&pszTemp); - } - - if (SUCCEEDED(hr)) - { - LONG lErrorCode = ERROR_SUCCESS; - - pRegKeyPath = wcschr(pszTemp, L'\\'); // Find the first occurrence of '\\' in the absolute registry key path - if(pRegKeyPath) - { - *pRegKeyPath = L'\0'; - pRegKeyPath++; // pRegKeyPath now points to the path to the recognizer token under the HKLM or HKCR hive - *ppszDescription = 0; - - // Open the registry key for read and get the handle - if (wcsncmp(pszTemp, L"HKEY_LOCAL_MACHINE", MAX_PATH) == 0) - { - lErrorCode = RegOpenKeyExW(HKEY_LOCAL_MACHINE, pRegKeyPath, 0, KEY_QUERY_VALUE, &Handle); - } - else if (wcsncmp(pszTemp, L"HKEY_CURRENT_USER", MAX_PATH) == 0) - { - lErrorCode = RegOpenKeyExW(HKEY_CURRENT_USER, pRegKeyPath, 0, KEY_QUERY_VALUE, &Handle); - } - else - { - lErrorCode = ERROR_BAD_ARGUMENTS; - } - - // Use MUI RegLoadMUIStringW API to load the localized string - if(ERROR_SUCCESS == lErrorCode) - { - *ppszDescription = (WCHAR*) CoTaskMemAlloc(MAX_PATH * sizeof(WCHAR)); // This should be enough memory to allocate the localized Engine Name - lErrorCode = (*pfnRegLoadMUIStringW) (Handle, SR_LOCALIZED_DESCRIPTION, *ppszDescription, MAX_PATH * sizeof(WCHAR), NULL, REG_MUI_STRING_TRUNCATE, NULL); - } - } - else - { - // pRegKeyPath should never be 0 if we are querying for relative hkey path - lErrorCode = ERROR_BAD_ARGUMENTS; - } - - hr = HRESULT_FROM_WIN32(lErrorCode); - } - - // Close registry key handle - if(Handle) - { - RegCloseKey(Handle); - } - // Free memory allocated to locals - if(pszTemp) - { - CoTaskMemFree(pszTemp); - } - if (hmodAdvapi32Dll) - { - ::FreeLibrary(hmodAdvapi32Dll); - } - } - else - { - hr = HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED); - } - - // If running on OSes released before Windows Vista query the localized string from the registry - // If RegLoadMUIStringW failed to retrieved the localized Engine name retrieve the localized string from the fallback (Default) attribute -#else - hr = E_FAIL; -#endif // _SAPI_VER >= 0x053 - if (FAILED(hr)) - { - // Free memory allocated above if necessary - if (*ppszDescription != NULL) - { - CoTaskMemFree(*ppszDescription); - *ppszDescription = NULL; - } - - SpHexFromUlong(szLangId, Language); - hr = pObjToken->GetStringValue(szLangId, ppszDescription); - if (hr == SPERR_NOT_FOUND) - { - hr = pObjToken->GetStringValue(NULL, ppszDescription); - } - } - - return hr; -} - -#endif diff --git a/src/ui/articleview.cpp b/src/ui/articleview.cpp index 1db536b3..5a45bf68 100644 --- a/src/ui/articleview.cpp +++ b/src/ui/articleview.cpp @@ -43,9 +43,7 @@ #include -#if defined( Q_OS_WIN32 ) || defined( Q_OS_MAC ) #include "speechclient.hh" -#endif #include "globalbroadcaster.h" using std::map; @@ -1402,30 +1400,22 @@ void ArticleView::openLink( QUrl const & url, QUrl const & ref, QString const & tr( "The referenced audio program doesn't exist." ) ); } else - if ( url.scheme() == "gdtts" ) - { -// TODO: Port TTS -#if defined( Q_OS_WIN32 ) || defined( Q_OS_MAC ) + if ( url.scheme() == "gdtts" ) { // Text to speech QString md5Id = Utils::Url::queryItemValue( url, "engine" ); QString text( url.path().mid( 1 ) ); - for ( Config::VoiceEngines::const_iterator i = cfg.voiceEngines.begin(); - i != cfg.voiceEngines.end(); ++i ) - { - QString itemMd5Id = QString( QCryptographicHash::hash( - i->id.toUtf8(), - QCryptographicHash::Md5 ).toHex() ); + for( const auto & voiceEngine : cfg.voiceEngines ) { + QString itemMd5Id = + QString( QCryptographicHash::hash( voiceEngine.name.toUtf8(), QCryptographicHash::Md5 ).toHex() ); - if ( itemMd5Id == md5Id ) - { - SpeechClient * speechClient = new SpeechClient( *i, this ); + if( itemMd5Id == md5Id ) { + SpeechClient * speechClient = new SpeechClient( voiceEngine, this ); connect( speechClient, SIGNAL( finished() ), speechClient, SLOT( deleteLater() ) ); speechClient->tell( text ); break; } } -#endif } else if ( isExternalLink( url ) ) diff --git a/texttospeechsource.cc b/texttospeechsource.cc index 21500658..35d1fa58 100644 --- a/texttospeechsource.cc +++ b/texttospeechsource.cc @@ -2,8 +2,9 @@ * Part of GoldenDict. Licensed under GPLv3 or later, see the LICENSE file */ #include "texttospeechsource.hh" - +#include #include +#include TextToSpeechSource::TextToSpeechSource( QWidget * parent, Config::VoiceEngines voiceEngines ): @@ -16,14 +17,18 @@ TextToSpeechSource::TextToSpeechSource( QWidget * parent, ui.selectedVoiceEngines->setTabKeyNavigation( true ); ui.selectedVoiceEngines->setModel( &voiceEnginesModel ); - ui.selectedVoiceEngines->hideColumn( VoiceEnginesModel::kColumnEngineId ); + ui.selectedVoiceEngines->hideColumn( VoiceEnginesModel::kColumnEngineName ); fitSelectedVoiceEnginesColumns(); - ui.selectedVoiceEngines->setItemDelegateForColumn( VoiceEnginesModel::kColumnEngineName, + ui.selectedVoiceEngines->setItemDelegateForColumn( VoiceEnginesModel::kColumnEngineDName, new VoiceEngineItemDelegate( engines, this ) ); foreach ( SpeechClient::Engine engine, engines ) { - ui.availableVoiceEngines->addItem( engine.name, engine.id ); + QMap map; + map[ "engine_name" ] = engine.engine_name; + map[ "locale" ] = engine.locale; + map["voice_name"] = engine.voice_name; + ui.availableVoiceEngines->addItem( engine.name, QVariant(map) ); } if( voiceEngines.count() > 0 ) @@ -65,8 +70,11 @@ void TextToSpeechSource::on_addVoiceEngine_clicked() if( idx >= 0 ) { QString name = ui.availableVoiceEngines->itemText( idx ); - QString id = ui.availableVoiceEngines->itemData( idx ).toString(); - voiceEnginesModel.addNewVoiceEngine( id, name, ui.volumeSlider->value(), ui.rateSlider->value() ); + auto map = ui.availableVoiceEngines->itemData( idx ).toMap(); + QString engine_name = map["engine_name"].toString(); + QString locale = map["locale"].toString(); + QString voice_name = map["voice_name"].toString(); + voiceEnginesModel.addNewVoiceEngine( engine_name, QLocale(locale), name, voice_name, ui.volumeSlider->value(), ui.rateSlider->value() ); fitSelectedVoiceEnginesColumns(); } } @@ -92,17 +100,17 @@ void TextToSpeechSource::on_previewVoice_clicked() if ( idx < 0 ) return; - QString engineId = ui.availableVoiceEngines->itemData( idx ).toString(); + auto map = ui.availableVoiceEngines->itemData( idx ).toMap(); + QString engineId = map["engine_name"].toString(); + QString locale = map["locale"].toString(); QString name = ui.availableVoiceEngines->itemText( idx ); QString text = ui.previewText->text(); int volume = ui.volumeSlider->value(); int rate = ui.rateSlider->value(); - SpeechClient * speechClient = new SpeechClient( Config::VoiceEngine( engineId, name, volume, rate ), this ); - - connect( speechClient, SIGNAL( started( bool ) ), ui.previewVoice, SLOT( setDisabled( bool ) ) ); - connect( speechClient, SIGNAL( finished() ), this, SLOT( previewVoiceFinished() ) ); - connect( speechClient, SIGNAL( finished() ), speechClient, SLOT( deleteLater() ) ); + speechClient = std::make_unique< SpeechClient >( + Config::VoiceEngine( engineId, name, map[ "voice_name" ].toString(), QLocale( locale ), volume, rate ), + this ); speechClient->tell( text ); } @@ -114,7 +122,7 @@ void TextToSpeechSource::previewVoiceFinished() void TextToSpeechSource::fitSelectedVoiceEnginesColumns() { ui.selectedVoiceEngines->resizeColumnToContents( VoiceEnginesModel::kColumnEnabled ); - ui.selectedVoiceEngines->resizeColumnToContents( VoiceEnginesModel::kColumnEngineName ); + ui.selectedVoiceEngines->resizeColumnToContents( VoiceEnginesModel::kColumnEngineDName ); ui.selectedVoiceEngines->resizeColumnToContents( VoiceEnginesModel::kColumnIcon ); } @@ -129,7 +137,7 @@ void TextToSpeechSource::adjustSliders() return; } ui.volumeSlider->setValue( 50 ); - ui.rateSlider->setValue( 50 ); + ui.rateSlider->setValue( 0 ); } void TextToSpeechSource::selectionChanged() @@ -160,18 +168,20 @@ void VoiceEnginesModel::removeVoiceEngine( int index ) endRemoveRows(); } -void VoiceEnginesModel::addNewVoiceEngine( QString const & id, QString const & name, - int volume, int rate ) +void VoiceEnginesModel::addNewVoiceEngine( + QString const & engine_name, QLocale locale, QString const & name, QString const & voice_name, int volume, int rate ) { - if ( id.isEmpty() || name.isEmpty() ) + if( engine_name.isEmpty() || name.isEmpty() ) return; Config::VoiceEngine v; - v.enabled = true; - v.id = id; - v.name = name; + v.enabled = true; + v.engine_name = engine_name; + v.locale = locale; + v.name = name; v.volume = volume; v.rate = rate; + v.voice_name = voice_name; beginInsertRows( QModelIndex(), voiceEngines.size(), voiceEngines.size() ); voiceEngines.push_back( v ); @@ -199,7 +209,7 @@ Qt::ItemFlags VoiceEnginesModel::flags( QModelIndex const & index ) const case kColumnEnabled: result |= Qt::ItemIsUserCheckable; break; - case kColumnEngineName: + case kColumnEngineDName: case kColumnIcon: result |= Qt::ItemIsEditable; break; @@ -231,9 +241,9 @@ QVariant VoiceEnginesModel::headerData( int section, Qt::Orientation /*orientati { case kColumnEnabled: return tr( "Enabled" ); - case kColumnEngineName: + case kColumnEngineDName: return tr( "Name" ); - case kColumnEngineId: + case kColumnEngineName: return tr( "Id" ); case kColumnIcon: return tr( "Icon" ); @@ -250,11 +260,10 @@ QVariant VoiceEnginesModel::data( QModelIndex const & index, int role ) const if ( role == Qt::DisplayRole || role == Qt::EditRole ) { - switch ( index.column() ) - { - case kColumnEngineId: - return voiceEngines[ index.row() ].id; + switch ( index.column() ) { case kColumnEngineName: + return voiceEngines[ index.row() ].engine_name; + case kColumnEngineDName: return voiceEngines[ index.row() ].name; case kColumnIcon: return voiceEngines[ index.row() ].iconFilename; @@ -284,13 +293,12 @@ bool VoiceEnginesModel::setData( QModelIndex const & index, const QVariant & val if ( role == Qt::DisplayRole || role == Qt::EditRole ) { - switch ( index.column() ) - { - case kColumnEngineId: - voiceEngines[ index.row() ].id = value.toString(); + switch ( index.column() ) { + case kColumnEngineName: + voiceEngines[ index.row() ].engine_name = value.toString(); dataChanged( index, index ); return true; - case kColumnEngineName: + case kColumnEngineDName: voiceEngines[ index.row() ].name = value.toString(); dataChanged( index, index ); return true; @@ -320,7 +328,7 @@ VoiceEngineEditor::VoiceEngineEditor( SpeechClient::Engines const & engines, QWi { foreach ( SpeechClient::Engine engine, engines ) { - addItem( engine.name, engine.id ); + addItem( engine.name, engine.engine_name ); } } @@ -365,7 +373,7 @@ QWidget * VoiceEngineItemDelegate::createEditor( QWidget * parent, QStyleOptionViewItem const & option, QModelIndex const & index ) const { - if ( index.column() != VoiceEnginesModel::kColumnEngineName ) + if( index.column() != VoiceEnginesModel::kColumnEngineDName ) return QStyledItemDelegate::createEditor( parent, option, index ); return new VoiceEngineEditor( engines, parent ); } @@ -376,9 +384,9 @@ void VoiceEngineItemDelegate::setEditorData( QWidget * uncastedEditor, const QMo if ( !editor ) return; - int currentRow = index.row(); - QModelIndex engineIdIndex = index.sibling( currentRow, VoiceEnginesModel::kColumnEngineId ); - QString engineId = index.model()->data( engineIdIndex ).toString(); + int currentRow = index.row(); + QModelIndex engineIdIndex = index.sibling( currentRow, VoiceEnginesModel::kColumnEngineName ); + QString engineId = index.model()->data( engineIdIndex ).toString(); editor->setEngineId( engineId ); } @@ -386,12 +394,12 @@ void VoiceEngineItemDelegate::setModelData( QWidget * uncastedEditor, QAbstractI const QModelIndex & index ) const { VoiceEngineEditor * editor = qobject_cast< VoiceEngineEditor * >( uncastedEditor ); - if ( !editor ) + if( !editor ) return; - int currentRow = index.row(); - QModelIndex engineIdIndex = index.sibling( currentRow, VoiceEnginesModel::kColumnEngineId ); - QModelIndex engineNameIndex = index.sibling( currentRow, VoiceEnginesModel::kColumnEngineName ); + int currentRow = index.row(); + QModelIndex engineIdIndex = index.sibling( currentRow, VoiceEnginesModel::kColumnEngineName ); + QModelIndex engineNameIndex = index.sibling( currentRow, VoiceEnginesModel::kColumnEngineDName ); model->setData( engineIdIndex, editor->engineId() ); model->setData( engineNameIndex, editor->engineName() ); } diff --git a/texttospeechsource.hh b/texttospeechsource.hh index aea4fdc6..7af23597 100644 --- a/texttospeechsource.hh +++ b/texttospeechsource.hh @@ -17,37 +17,33 @@ class VoiceEnginesModel: public QAbstractItemModel Q_OBJECT public: + enum { + kColumnEnabled = 0, + kColumnEngineName, + kColumnEngineDName, + kColumnIcon, + kColumnCount + }; - enum { - kColumnEnabled = 0, - kColumnEngineId, - kColumnEngineName, - kColumnIcon, - kColumnCount - }; + VoiceEnginesModel( QWidget * parent, Config::VoiceEngines const & voiceEngines ); - VoiceEnginesModel( QWidget * parent, Config::VoiceEngines const & voiceEngines ); + void removeVoiceEngine( int index ); + void addNewVoiceEngine( QString const & engine_name, QLocale locale, QString const & name, QString const & voice_name, int volume, int rate ); - void removeVoiceEngine( int index ); - void addNewVoiceEngine( QString const & id, QString const & name, - int volume, int rate ); + Config::VoiceEngines const & getCurrentVoiceEngines() const { return voiceEngines; } + void setEngineParams( QModelIndex idx, int volume, int rate ); - Config::VoiceEngines const & getCurrentVoiceEngines() const - { return voiceEngines; } - void setEngineParams( QModelIndex idx, int volume, int rate ); - - QModelIndex index( int row, int column, QModelIndex const & parent ) const; - QModelIndex parent( QModelIndex const & parent ) const; - Qt::ItemFlags flags( QModelIndex const & index ) const; - int rowCount( QModelIndex const & parent ) const; - int columnCount( QModelIndex const & parent ) const; - QVariant headerData( int section, Qt::Orientation orientation, int role ) const; - QVariant data( QModelIndex const & index, int role ) const; - bool setData( QModelIndex const & index, const QVariant & value, int role ); + QModelIndex index( int row, int column, QModelIndex const & parent ) const override; + QModelIndex parent( QModelIndex const & parent ) const override; + Qt::ItemFlags flags( QModelIndex const & index ) const override; + int rowCount( QModelIndex const & parent ) const override; + int columnCount( QModelIndex const & parent ) const override; + QVariant headerData( int section, Qt::Orientation orientation, int role ) const override; + QVariant data( QModelIndex const & index, int role ) const override; + bool setData( QModelIndex const & index, const QVariant & value, int role ) override; private: - - Config::VoiceEngines voiceEngines; + Config::VoiceEngines voiceEngines; }; class VoiceEngineEditor: public QComboBox @@ -55,10 +51,10 @@ class VoiceEngineEditor: public QComboBox Q_OBJECT public: - VoiceEngineEditor( SpeechClient::Engines const & engines, QWidget * parent = 0 ); + VoiceEngineEditor( SpeechClient::Engines const & engines, QWidget * parent = nullptr ); -public: - QString engineName() const; + + QString engineName() const; QString engineId() const; void setEngineId( QString const & engineId ); }; @@ -67,17 +63,16 @@ class VoiceEngineItemDelegate: public QStyledItemDelegate { Q_OBJECT -public: - VoiceEngineItemDelegate( SpeechClient::Engines const & engines, QObject * parent = 0 ); + public: + VoiceEngineItemDelegate( SpeechClient::Engines const & engines, QObject * parent = nullptr ); - virtual QWidget * createEditor( QWidget *parent, - QStyleOptionViewItem const & option, - QModelIndex const & index ) const; - virtual void setEditorData( QWidget *uncastedEditor, const QModelIndex & index ) const; - virtual void setModelData( QWidget *uncastedEditor, QAbstractItemModel * model, - const QModelIndex & index ) const; + QWidget * + createEditor( QWidget * parent, QStyleOptionViewItem const & option, QModelIndex const & index ) const override; + virtual void setEditorData( QWidget * uncastedEditor, const QModelIndex & index ) const override; + virtual void + setModelData( QWidget * uncastedEditor, QAbstractItemModel * model, const QModelIndex & index ) const override; -private: + private: SpeechClient::Engines engines; }; @@ -103,6 +98,8 @@ private: Ui::TextToSpeechSource ui; VoiceEnginesModel voiceEnginesModel; + std::unique_ptr< SpeechClient > speechClient; + void fitSelectedVoiceEnginesColumns(); void adjustSliders(); }; diff --git a/texttospeechsource.ui b/texttospeechsource.ui index 09a4874a..8b7975f9 100644 --- a/texttospeechsource.ui +++ b/texttospeechsource.ui @@ -109,11 +109,20 @@ + + -10 + - 100 + 10 + + + 2 + + + 0 - 50 + 0 false diff --git a/voiceengines.cc b/voiceengines.cc index 76132091..259c8be6 100644 --- a/voiceengines.cc +++ b/voiceengines.cc @@ -38,7 +38,7 @@ public: VoiceEnginesDictionary( Config::VoiceEngine const & voiceEngine ): Dictionary::Class( - toMd5( voiceEngine.id.toUtf8() ), + toMd5( voiceEngine.name.toUtf8() ), vector< string >() ), voiceEngine( voiceEngine ) {