Mac-specific: Implement text-to-speech feature under Mac OS X

This commit is contained in:
Abs62 2013-05-07 17:39:35 +04:00
parent e45bcfc9ce
commit 882dd57781
6 changed files with 213 additions and 9 deletions

View file

@ -24,13 +24,16 @@
#ifdef Q_OS_WIN32 #ifdef Q_OS_WIN32
#include <windows.h> #include <windows.h>
#include "speechclient.hh"
#include <QPainter> #include <QPainter>
#endif #endif
#include <QBuffer> #include <QBuffer>
#if defined( Q_OS_WIN32 ) || defined( Q_OS_MACX )
#include "speechclient.hh"
#endif
using std::map; using std::map;
using std::list; using std::list;
@ -912,7 +915,7 @@ void ArticleView::openLink( QUrl const & url, QUrl const & ref,
if ( url.scheme() == "gdtts" ) if ( url.scheme() == "gdtts" )
{ {
// TODO: Port TTS // TODO: Port TTS
#ifdef Q_OS_WIN32 #if defined( Q_OS_WIN32 ) || defined( Q_OS_MACX )
// Text to speech // Text to speech
QString md5Id = url.queryItemValue( "engine" ); QString md5Id = url.queryItemValue( "engine" );
QString text( url.path().mid( 1 ) ); QString text( url.path().mid( 1 ) );

View file

@ -126,7 +126,8 @@ mac {
LIBS += -Lmaclibs/lib -framework AppKit -framework Carbon LIBS += -Lmaclibs/lib -framework AppKit -framework Carbon
OBJECTIVE_SOURCES += lionsupport.mm \ OBJECTIVE_SOURCES += lionsupport.mm \
machotkeywrapper.mm \ machotkeywrapper.mm \
macmouseover.mm macmouseover.mm \
speechclient_mac.mm
ICON = icons/macicon.icns ICON = icons/macicon.icns
QMAKE_POST_LINK = mkdir -p GoldenDict.app/Contents/Frameworks & \ QMAKE_POST_LINK = mkdir -p GoldenDict.app/Contents/Frameworks & \
cp -nR maclibs/lib/ GoldenDict.app/Contents/Frameworks/ & \ cp -nR maclibs/lib/ GoldenDict.app/Contents/Frameworks/ & \
@ -364,7 +365,11 @@ win32 {
} }
mac { mac {
HEADERS += macmouseover.hh HEADERS += macmouseover.hh \
texttospeechsource.hh \
speechclient.hh
FORMS += texttospeechsource.ui
SOURCES += texttospeechsource.cc
} }
RESOURCES += resources.qrc \ RESOURCES += resources.qrc \

View file

@ -2,6 +2,7 @@
#include <AppKit/NSTouch.h> #include <AppKit/NSTouch.h>
#include <AppKit/NSEvent.h> #include <AppKit/NSEvent.h>
#include <AppKit/NSScreen.h> #include <AppKit/NSScreen.h>
#include <Foundation/NSAutoreleasePool.h>
const int mouseOverInterval = 300; const int mouseOverInterval = 300;
@ -129,7 +130,11 @@ void MacMouseOver::handlePosition()
Mutex::Lock _( mouseMutex ); Mutex::Lock _( mouseMutex );
QString strToTranslate; QString strToTranslate;
NSAutoreleasePool * pool = [ [ NSAutoreleasePool alloc ] init ];
CGPoint pt = carbonScreenPointFromCocoaScreenPoint( [NSEvent mouseLocation] ); CGPoint pt = carbonScreenPointFromCocoaScreenPoint( [NSEvent mouseLocation] );
[ pool drain ];
CFArrayRef names = 0; CFArrayRef names = 0;
AXUIElementRef elem = 0; AXUIElementRef elem = 0;

View file

@ -10,7 +10,7 @@
Sources::Sources( QWidget * parent, Config::Class const & cfg): Sources::Sources( QWidget * parent, Config::Class const & cfg):
QWidget( parent ), QWidget( parent ),
#ifdef Q_OS_WIN32 #if defined( Q_OS_WIN32 ) || defined( Q_OS_MACX )
textToSpeechSource( NULL ), textToSpeechSource( NULL ),
#endif #endif
itemDelegate( new QItemDelegate( this ) ), itemDelegate( new QItemDelegate( this ) ),
@ -95,7 +95,7 @@ Sources::Sources( QWidget * parent, Config::Class const & cfg):
ui.forvoLanguageCodes->setText( forvo.languageCodes ); ui.forvoLanguageCodes->setText( forvo.languageCodes );
// Text to speech // Text to speech
#ifdef Q_OS_WIN32 #if defined( Q_OS_WIN32 ) || defined( Q_OS_MACX )
textToSpeechSource = new TextToSpeechSource( this, cfg.voiceEngines ); textToSpeechSource = new TextToSpeechSource( this, cfg.voiceEngines );
ui.tabWidget->addTab( textToSpeechSource, tr( "Text to Speech" ) ); ui.tabWidget->addTab( textToSpeechSource, tr( "Text to Speech" ) );
#endif #endif
@ -288,7 +288,7 @@ void Sources::on_removeProgram_clicked()
Config::VoiceEngines Sources::getVoiceEngines() const Config::VoiceEngines Sources::getVoiceEngines() const
{ {
#ifdef Q_OS_WIN32 #if defined( Q_OS_WIN32 ) || defined( Q_OS_MACX )
if ( !textToSpeechSource ) if ( !textToSpeechSource )
return Config::VoiceEngines(); return Config::VoiceEngines();
return textToSpeechSource->getVoiceEnginesModel().getCurrentVoiceEngines(); return textToSpeechSource->getVoiceEnginesModel().getCurrentVoiceEngines();

View file

@ -12,7 +12,7 @@
#include <QItemDelegate> #include <QItemDelegate>
#include <QItemEditorFactory> #include <QItemEditorFactory>
#ifdef Q_OS_WIN32 #if defined( Q_OS_WIN32 ) || defined( Q_OS_MACX )
#include "texttospeechsource.hh" #include "texttospeechsource.hh"
#endif #endif
@ -251,7 +251,7 @@ signals:
private: private:
Ui::Sources ui; Ui::Sources ui;
#ifdef Q_OS_WIN32 #if defined( Q_OS_WIN32 ) || defined( Q_OS_MACX )
TextToSpeechSource *textToSpeechSource; TextToSpeechSource *textToSpeechSource;
#endif #endif

191
speechclient_mac.mm Normal file
View file

@ -0,0 +1,191 @@
#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();
}
}