mirror of
https://github.com/xiaoyifang/goldendict-ng.git
synced 2024-11-27 15:24:05 +00:00
Mac-specific: Implement text-to-speech feature under Mac OS X
This commit is contained in:
parent
e45bcfc9ce
commit
882dd57781
|
@ -24,13 +24,16 @@
|
|||
|
||||
#ifdef Q_OS_WIN32
|
||||
#include <windows.h>
|
||||
#include "speechclient.hh"
|
||||
|
||||
#include <QPainter>
|
||||
#endif
|
||||
|
||||
#include <QBuffer>
|
||||
|
||||
#if defined( Q_OS_WIN32 ) || defined( Q_OS_MACX )
|
||||
#include "speechclient.hh"
|
||||
#endif
|
||||
|
||||
using std::map;
|
||||
using std::list;
|
||||
|
||||
|
@ -912,7 +915,7 @@ void ArticleView::openLink( QUrl const & url, QUrl const & ref,
|
|||
if ( url.scheme() == "gdtts" )
|
||||
{
|
||||
// TODO: Port TTS
|
||||
#ifdef Q_OS_WIN32
|
||||
#if defined( Q_OS_WIN32 ) || defined( Q_OS_MACX )
|
||||
// Text to speech
|
||||
QString md5Id = url.queryItemValue( "engine" );
|
||||
QString text( url.path().mid( 1 ) );
|
||||
|
|
|
@ -126,7 +126,8 @@ mac {
|
|||
LIBS += -Lmaclibs/lib -framework AppKit -framework Carbon
|
||||
OBJECTIVE_SOURCES += lionsupport.mm \
|
||||
machotkeywrapper.mm \
|
||||
macmouseover.mm
|
||||
macmouseover.mm \
|
||||
speechclient_mac.mm
|
||||
ICON = icons/macicon.icns
|
||||
QMAKE_POST_LINK = mkdir -p GoldenDict.app/Contents/Frameworks & \
|
||||
cp -nR maclibs/lib/ GoldenDict.app/Contents/Frameworks/ & \
|
||||
|
@ -364,7 +365,11 @@ win32 {
|
|||
}
|
||||
|
||||
mac {
|
||||
HEADERS += macmouseover.hh
|
||||
HEADERS += macmouseover.hh \
|
||||
texttospeechsource.hh \
|
||||
speechclient.hh
|
||||
FORMS += texttospeechsource.ui
|
||||
SOURCES += texttospeechsource.cc
|
||||
}
|
||||
|
||||
RESOURCES += resources.qrc \
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
#include <AppKit/NSTouch.h>
|
||||
#include <AppKit/NSEvent.h>
|
||||
#include <AppKit/NSScreen.h>
|
||||
#include <Foundation/NSAutoreleasePool.h>
|
||||
|
||||
const int mouseOverInterval = 300;
|
||||
|
||||
|
@ -129,7 +130,11 @@ void MacMouseOver::handlePosition()
|
|||
Mutex::Lock _( mouseMutex );
|
||||
|
||||
QString strToTranslate;
|
||||
|
||||
NSAutoreleasePool * pool = [ [ NSAutoreleasePool alloc ] init ];
|
||||
CGPoint pt = carbonScreenPointFromCocoaScreenPoint( [NSEvent mouseLocation] );
|
||||
[ pool drain ];
|
||||
|
||||
CFArrayRef names = 0;
|
||||
|
||||
AXUIElementRef elem = 0;
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
|
||||
Sources::Sources( QWidget * parent, Config::Class const & cfg):
|
||||
QWidget( parent ),
|
||||
#ifdef Q_OS_WIN32
|
||||
#if defined( Q_OS_WIN32 ) || defined( Q_OS_MACX )
|
||||
textToSpeechSource( NULL ),
|
||||
#endif
|
||||
itemDelegate( new QItemDelegate( this ) ),
|
||||
|
@ -95,7 +95,7 @@ Sources::Sources( QWidget * parent, Config::Class const & cfg):
|
|||
ui.forvoLanguageCodes->setText( forvo.languageCodes );
|
||||
|
||||
// Text to speech
|
||||
#ifdef Q_OS_WIN32
|
||||
#if defined( Q_OS_WIN32 ) || defined( Q_OS_MACX )
|
||||
textToSpeechSource = new TextToSpeechSource( this, cfg.voiceEngines );
|
||||
ui.tabWidget->addTab( textToSpeechSource, tr( "Text to Speech" ) );
|
||||
#endif
|
||||
|
@ -288,7 +288,7 @@ void Sources::on_removeProgram_clicked()
|
|||
|
||||
Config::VoiceEngines Sources::getVoiceEngines() const
|
||||
{
|
||||
#ifdef Q_OS_WIN32
|
||||
#if defined( Q_OS_WIN32 ) || defined( Q_OS_MACX )
|
||||
if ( !textToSpeechSource )
|
||||
return Config::VoiceEngines();
|
||||
return textToSpeechSource->getVoiceEnginesModel().getCurrentVoiceEngines();
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
#include <QItemDelegate>
|
||||
#include <QItemEditorFactory>
|
||||
|
||||
#ifdef Q_OS_WIN32
|
||||
#if defined( Q_OS_WIN32 ) || defined( Q_OS_MACX )
|
||||
#include "texttospeechsource.hh"
|
||||
#endif
|
||||
|
||||
|
@ -251,7 +251,7 @@ signals:
|
|||
private:
|
||||
Ui::Sources ui;
|
||||
|
||||
#ifdef Q_OS_WIN32
|
||||
#if defined( Q_OS_WIN32 ) || defined( Q_OS_MACX )
|
||||
TextToSpeechSource *textToSpeechSource;
|
||||
#endif
|
||||
|
||||
|
|
191
speechclient_mac.mm
Normal file
191
speechclient_mac.mm
Normal 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();
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue