use QTextToSpeech module to play tts.

fix code smells
This commit is contained in:
Xiao YiFang 2022-07-31 09:19:50 +08:00 committed by xiaoyifang
parent e0831bf36c
commit 2407cf2a88
19 changed files with 292 additions and 1031 deletions

View file

@ -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)

View file

@ -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 );
}
}

View file

@ -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

View file

@ -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 ) {

View file

@ -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

View file

@ -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;

View file

@ -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 &lt;a href=&quot;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 &lt;a href=&quot;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 &lt;a href=&quot;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 &lt;a href=&quot;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 &lt;a href=&quot;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 &lt;a href=&quot;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
View 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);
}

View file

@ -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__

View file

@ -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();
}
}

View file

@ -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();
}
}

View file

@ -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;
}

View file

@ -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__

View file

@ -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

View file

@ -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 ) )

View file

@ -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() );
}

View file

@ -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();
};

View file

@ -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>

View file

@ -38,7 +38,7 @@ public:
VoiceEnginesDictionary( Config::VoiceEngine const & voiceEngine ):
Dictionary::Class(
toMd5( voiceEngine.id.toUtf8() ),
toMd5( voiceEngine.name.toUtf8() ),
vector< string >() ),
voiceEngine( voiceEngine )
{