mirror of
https://github.com/xiaoyifang/goldendict-ng.git
synced 2024-12-03 15:34:06 +00:00
use QTextToSpeech module to play tts.
fix code smells
This commit is contained in:
parent
e0831bf36c
commit
2407cf2a88
|
@ -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)
|
||||
|
|
48
config.cc
48
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 );
|
||||
}
|
||||
}
|
||||
|
|
44
config.hh
44
config.hh
|
@ -13,6 +13,7 @@
|
|||
#include <QSet>
|
||||
#include <QMetaType>
|
||||
#include "ex.hh"
|
||||
#include <QLocale>
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
#include <QRect>
|
||||
|
@ -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
|
||||
|
|
|
@ -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 ) {
|
||||
|
|
11
sources.cc
11
sources.cc
|
@ -5,7 +5,6 @@
|
|||
#include <QFileDialog>
|
||||
#include <QMessageBox>
|
||||
#include <QStandardItemModel>
|
||||
#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
|
||||
|
|
|
@ -12,9 +12,7 @@
|
|||
#include <QItemDelegate>
|
||||
#include <QItemEditorFactory>
|
||||
|
||||
#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;
|
||||
|
|
33
sources.ui
33
sources.ui
|
@ -33,7 +33,7 @@
|
|||
</property>
|
||||
<widget class="QWidget" name="filesTab">
|
||||
<attribute name="icon">
|
||||
<iconset resource="resources.qrc">
|
||||
<iconset>
|
||||
<normaloff>:/icons/folders.svg</normaloff>:/icons/folders.svg</iconset>
|
||||
</attribute>
|
||||
<attribute name="title">
|
||||
|
@ -96,7 +96,7 @@
|
|||
</widget>
|
||||
<widget class="QWidget" name="tab">
|
||||
<attribute name="icon">
|
||||
<iconset resource="resources.qrc">
|
||||
<iconset>
|
||||
<normaloff>:/icons/folder-sound.svg</normaloff>:/icons/folder-sound.svg</iconset>
|
||||
</attribute>
|
||||
<attribute name="title">
|
||||
|
@ -152,7 +152,7 @@
|
|||
</widget>
|
||||
<widget class="QWidget" name="tab_2">
|
||||
<attribute name="icon">
|
||||
<iconset resource="resources.qrc">
|
||||
<iconset>
|
||||
<normaloff>:/icons/icon32_hunspell.png</normaloff>:/icons/icon32_hunspell.png</iconset>
|
||||
</attribute>
|
||||
<attribute name="title">
|
||||
|
@ -220,7 +220,7 @@ of the appropriate groups to use them.</string>
|
|||
</widget>
|
||||
<widget class="QWidget" name="mediaWikisTab">
|
||||
<attribute name="icon">
|
||||
<iconset resource="resources.qrc">
|
||||
<iconset>
|
||||
<normaloff>:/icons/icon32_wiki.png</normaloff>:/icons/icon32_wiki.png</iconset>
|
||||
</attribute>
|
||||
<attribute name="title">
|
||||
|
@ -279,7 +279,7 @@ of the appropriate groups to use them.</string>
|
|||
</widget>
|
||||
<widget class="QWidget" name="tab_4">
|
||||
<attribute name="icon">
|
||||
<iconset resource="resources.qrc">
|
||||
<iconset>
|
||||
<normaloff>:/icons/internet.svg</normaloff>:/icons/internet.svg</iconset>
|
||||
</attribute>
|
||||
<attribute name="title">
|
||||
|
@ -343,7 +343,7 @@ of the appropriate groups to use them.</string>
|
|||
</widget>
|
||||
<widget class="QWidget" name="dictdTab">
|
||||
<attribute name="icon">
|
||||
<iconset resource="resources.qrc">
|
||||
<iconset>
|
||||
<normaloff>:/icons/network.svg</normaloff>:/icons/network.svg</iconset>
|
||||
</attribute>
|
||||
<attribute name="title">
|
||||
|
@ -399,7 +399,7 @@ of the appropriate groups to use them.</string>
|
|||
</widget>
|
||||
<widget class="QWidget" name="tab_6">
|
||||
<attribute name="icon">
|
||||
<iconset resource="resources.qrc">
|
||||
<iconset>
|
||||
<normaloff>:/icons/programs.svg</normaloff>:/icons/programs.svg</iconset>
|
||||
</attribute>
|
||||
<attribute name="title">
|
||||
|
@ -458,7 +458,7 @@ of the appropriate groups to use them.</string>
|
|||
</widget>
|
||||
<widget class="QWidget" name="tab_lingua">
|
||||
<attribute name="icon">
|
||||
<iconset resource="resources.qrc">
|
||||
<iconset>
|
||||
<normaloff>:/icons/lingualibre.svg</normaloff>:/icons/lingualibre.svg</iconset>
|
||||
</attribute>
|
||||
<attribute name="title">
|
||||
|
@ -526,7 +526,7 @@ Full list of availiable languages can be found <a href="https://linguali
|
|||
</widget>
|
||||
<widget class="QWidget" name="tab_5">
|
||||
<attribute name="icon">
|
||||
<iconset resource="resources.qrc">
|
||||
<iconset>
|
||||
<normaloff>:/icons/forvo.png</normaloff>:/icons/forvo.png</iconset>
|
||||
</attribute>
|
||||
<attribute name="title">
|
||||
|
@ -674,7 +674,7 @@ Full list of availiable languages can be found <a href="https://linguali
|
|||
</widget>
|
||||
<widget class="QWidget" name="tab_3">
|
||||
<attribute name="icon">
|
||||
<iconset resource="resources.qrc">
|
||||
<iconset>
|
||||
<normaloff>:/icons/transliteration.png</normaloff>:/icons/transliteration.png</iconset>
|
||||
</attribute>
|
||||
<attribute name="title">
|
||||
|
@ -692,7 +692,7 @@ Full list of availiable languages can be found <a href="https://linguali
|
|||
<string>Greek transliteration</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset resource="flags.qrc">
|
||||
<iconset>
|
||||
<normaloff>:/flags/gr.png</normaloff>:/flags/gr.png</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
|
@ -703,7 +703,7 @@ Full list of availiable languages can be found <a href="https://linguali
|
|||
<string>Russian transliteration</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset resource="flags.qrc">
|
||||
<iconset>
|
||||
<normaloff>:/flags/ru.png</normaloff>:/flags/ru.png</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
|
@ -714,7 +714,7 @@ Full list of availiable languages can be found <a href="https://linguali
|
|||
<string>German transliteration</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset resource="flags.qrc">
|
||||
<iconset>
|
||||
<normaloff>:/flags/de.png</normaloff>:/flags/de.png</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
|
@ -725,7 +725,7 @@ Full list of availiable languages can be found <a href="https://linguali
|
|||
<string>Belarusian transliteration</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset resource="flags.qrc">
|
||||
<iconset>
|
||||
<normaloff>:/flags/by.png</normaloff>:/flags/by.png</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
|
@ -857,9 +857,6 @@ Not implemented yet in GoldenDict.</string>
|
|||
<tabstop>removeSoundDir</tabstop>
|
||||
<tabstop>paths</tabstop>
|
||||
</tabstops>
|
||||
<resources>
|
||||
<include location="flags.qrc"/>
|
||||
<include location="resources.qrc"/>
|
||||
</resources>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
||||
|
|
53
speechclient.cc
Normal file
53
speechclient.cc
Normal file
|
@ -0,0 +1,53 @@
|
|||
#include "speechclient.hh"
|
||||
|
||||
#include <QtCore>
|
||||
|
||||
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);
|
||||
}
|
|
@ -3,6 +3,8 @@
|
|||
|
||||
#include <QObject>
|
||||
#include "config.hh"
|
||||
#include <QTextToSpeech>
|
||||
#include <memory>
|
||||
|
||||
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<Engine> 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__
|
||||
|
|
|
@ -1,191 +0,0 @@
|
|||
#include "speechclient.hh"
|
||||
|
||||
#include <QtCore>
|
||||
#include <AppKit/NSSpeechSynthesizer.h>
|
||||
#include <Foundation/NSArray.h>
|
||||
#include <Foundation/NSError.h>
|
||||
#include <Foundation/NSString.h>
|
||||
#include <Foundation/NSAutoreleasePool.h>
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
|
@ -1,144 +0,0 @@
|
|||
#include "speechclient.hh"
|
||||
|
||||
#include <windows.h>
|
||||
#include "speechhlp.hh"
|
||||
|
||||
#include <QtCore>
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
191
speechhlp.cc
191
speechhlp.cc
|
@ -1,191 +0,0 @@
|
|||
#define WINVER 0x0500 // At least WinXP required
|
||||
#include <windows.h>
|
||||
#include <limits.h>
|
||||
|
||||
#include "speechhlp.hh"
|
||||
#include <string>
|
||||
#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;
|
||||
}
|
27
speechhlp.hh
27
speechhlp.hh
|
@ -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__
|
247
sphelper.hh
247
sphelper.hh
|
@ -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
|
|
@ -43,9 +43,7 @@
|
|||
|
||||
#include <QBuffer>
|
||||
|
||||
#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 ) )
|
||||
|
|
|
@ -2,8 +2,9 @@
|
|||
* Part of GoldenDict. Licensed under GPLv3 or later, see the LICENSE file */
|
||||
|
||||
#include "texttospeechsource.hh"
|
||||
|
||||
#include <QVariant>
|
||||
#include <QMessageBox>
|
||||
#include <memory>
|
||||
|
||||
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<QString,QVariant> 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() );
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
};
|
||||
|
|
|
@ -109,11 +109,20 @@
|
|||
</item>
|
||||
<item>
|
||||
<widget class="QSlider" name="rateSlider">
|
||||
<property name="minimum">
|
||||
<number>-10</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>100</number>
|
||||
<number>10</number>
|
||||
</property>
|
||||
<property name="pageStep">
|
||||
<number>2</number>
|
||||
</property>
|
||||
<property name="value">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="sliderPosition">
|
||||
<number>50</number>
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="tracking">
|
||||
<bool>false</bool>
|
||||
|
|
|
@ -38,7 +38,7 @@ public:
|
|||
|
||||
VoiceEnginesDictionary( Config::VoiceEngine const & voiceEngine ):
|
||||
Dictionary::Class(
|
||||
toMd5( voiceEngine.id.toUtf8() ),
|
||||
toMd5( voiceEngine.name.toUtf8() ),
|
||||
vector< string >() ),
|
||||
voiceEngine( voiceEngine )
|
||||
{
|
||||
|
|
Loading…
Reference in a new issue