1 /* This file is (c) 2013 Timon Wong <timon86.wang@gmail.com>
2 * Part of GoldenDict. Licensed under GPLv3 or later, see the LICENSE file */
3
4 #include "texttospeechsource.hh"
5
6 #include <QMessageBox>
7
TextToSpeechSource(QWidget * parent,Config::VoiceEngines voiceEngines)8 TextToSpeechSource::TextToSpeechSource( QWidget * parent,
9 Config::VoiceEngines voiceEngines ):
10 QWidget( parent ),
11 voiceEnginesModel( this, voiceEngines )
12 {
13 ui.setupUi( this );
14
15 SpeechClient::Engines engines = SpeechClient::availableEngines();
16
17 ui.selectedVoiceEngines->setTabKeyNavigation( true );
18 ui.selectedVoiceEngines->setModel( &voiceEnginesModel );
19 ui.selectedVoiceEngines->hideColumn( VoiceEnginesModel::kColumnEngineId );
20 fitSelectedVoiceEnginesColumns();
21 ui.selectedVoiceEngines->setItemDelegateForColumn( VoiceEnginesModel::kColumnEngineName,
22 new VoiceEngineItemDelegate( engines, this ) );
23
24 foreach ( SpeechClient::Engine engine, engines )
25 {
26 ui.availableVoiceEngines->addItem( engine.name, engine.id );
27 }
28
29 if( voiceEngines.count() > 0 )
30 {
31 QModelIndex const &idx = ui.selectedVoiceEngines->model()->index( 0, 0 );
32 if( idx.isValid() )
33 ui.selectedVoiceEngines->setCurrentIndex( idx );
34 }
35
36 adjustSliders();
37
38 connect( ui.volumeSlider, SIGNAL( valueChanged( int ) ),
39 this, SLOT( slidersChanged() ) );
40 connect( ui.rateSlider, SIGNAL( valueChanged( int ) ),
41 this, SLOT( slidersChanged() ) );
42 connect( ui.selectedVoiceEngines->selectionModel(), SIGNAL( selectionChanged( QItemSelection, QItemSelection ) ),
43 this, SLOT( selectionChanged() ) );
44 }
45
slidersChanged()46 void TextToSpeechSource::slidersChanged()
47 {
48 if( ui.selectedVoiceEngines->selectionModel()->hasSelection() )
49 voiceEnginesModel.setEngineParams( ui.selectedVoiceEngines->currentIndex(),
50 ui.volumeSlider->value(),
51 ui.rateSlider->value()) ;
52 }
53
on_addVoiceEngine_clicked()54 void TextToSpeechSource::on_addVoiceEngine_clicked()
55 {
56 if ( ui.availableVoiceEngines->count() == 0 )
57 {
58 QMessageBox::information( this, tr( "No TTS voice available" ),
59 tr( "Cannot find available TTS voice.<br>"
60 "Please make sure that at least one TTS engine installed on your computer already." ) );
61 return;
62 }
63
64 int idx = ui.availableVoiceEngines->currentIndex();
65 if( idx >= 0 )
66 {
67 QString name = ui.availableVoiceEngines->itemText( idx );
68 QString id = ui.availableVoiceEngines->itemData( idx ).toString();
69 voiceEnginesModel.addNewVoiceEngine( id, name, ui.volumeSlider->value(), ui.rateSlider->value() );
70 fitSelectedVoiceEnginesColumns();
71 }
72 }
73
on_removeVoiceEngine_clicked()74 void TextToSpeechSource::on_removeVoiceEngine_clicked()
75 {
76 QModelIndex current = ui.selectedVoiceEngines->currentIndex();
77
78 if ( current.isValid() &&
79 QMessageBox::question( this, tr( "Confirm removal" ),
80 tr( "Remove voice engine <b>%1</b> from the list?" ).arg(
81 voiceEnginesModel.getCurrentVoiceEngines()[ current.row() ].name ),
82 QMessageBox::Ok,
83 QMessageBox::Cancel ) == QMessageBox::Ok )
84 {
85 voiceEnginesModel.removeVoiceEngine( current.row() );
86 }
87 }
88
on_previewVoice_clicked()89 void TextToSpeechSource::on_previewVoice_clicked()
90 {
91 int idx = ui.availableVoiceEngines->currentIndex();
92 if ( idx < 0 )
93 return;
94
95 QString engineId = ui.availableVoiceEngines->itemData( idx ).toString();
96 QString name = ui.availableVoiceEngines->itemText( idx );
97 QString text = ui.previewText->text();
98 int volume = ui.volumeSlider->value();
99 int rate = ui.rateSlider->value();
100
101 SpeechClient * speechClient = new SpeechClient( Config::VoiceEngine( engineId, name, volume, rate ), this );
102
103 connect( speechClient, SIGNAL( started( bool ) ), ui.previewVoice, SLOT( setDisabled( bool ) ) );
104 connect( speechClient, SIGNAL( finished() ), this, SLOT( previewVoiceFinished() ) );
105 connect( speechClient, SIGNAL( finished() ), speechClient, SLOT( deleteLater() ) );
106 speechClient->tell( text );
107 }
108
previewVoiceFinished()109 void TextToSpeechSource::previewVoiceFinished()
110 {
111 ui.previewVoice->setDisabled( false );
112 }
113
fitSelectedVoiceEnginesColumns()114 void TextToSpeechSource::fitSelectedVoiceEnginesColumns()
115 {
116 ui.selectedVoiceEngines->resizeColumnToContents( VoiceEnginesModel::kColumnEnabled );
117 ui.selectedVoiceEngines->resizeColumnToContents( VoiceEnginesModel::kColumnEngineName );
118 ui.selectedVoiceEngines->resizeColumnToContents( VoiceEnginesModel::kColumnIcon );
119 }
120
adjustSliders()121 void TextToSpeechSource::adjustSliders()
122 {
123 QModelIndex const & index = ui.selectedVoiceEngines->currentIndex();
124 if ( index.isValid() )
125 {
126 Config::VoiceEngines const &engines = voiceEnginesModel.getCurrentVoiceEngines();
127 ui.volumeSlider->setValue( engines[ index.row() ].volume );
128 ui.rateSlider->setValue( engines[ index.row() ].rate );
129 return;
130 }
131 ui.volumeSlider->setValue( 50 );
132 ui.rateSlider->setValue( 50 );
133 }
134
selectionChanged()135 void TextToSpeechSource::selectionChanged()
136 {
137 disconnect( ui.volumeSlider, SIGNAL( valueChanged( int ) ),
138 this, SLOT( slidersChanged() ) );
139 disconnect( ui.rateSlider, SIGNAL( valueChanged( int ) ),
140 this, SLOT( slidersChanged() ) );
141
142 adjustSliders();
143
144 connect( ui.volumeSlider, SIGNAL( valueChanged( int ) ),
145 this, SLOT( slidersChanged() ) );
146 connect( ui.rateSlider, SIGNAL( valueChanged( int ) ),
147 this, SLOT( slidersChanged() ) );
148 }
149
VoiceEnginesModel(QWidget * parent,Config::VoiceEngines const & voiceEngines)150 VoiceEnginesModel::VoiceEnginesModel( QWidget * parent,
151 Config::VoiceEngines const & voiceEngines ):
152 QAbstractItemModel( parent ), voiceEngines( voiceEngines )
153 {
154 }
155
removeVoiceEngine(int index)156 void VoiceEnginesModel::removeVoiceEngine( int index )
157 {
158 beginRemoveRows( QModelIndex(), index, index );
159 voiceEngines.erase( voiceEngines.begin() + index );
160 endRemoveRows();
161 }
162
addNewVoiceEngine(QString const & id,QString const & name,int volume,int rate)163 void VoiceEnginesModel::addNewVoiceEngine( QString const & id, QString const & name,
164 int volume, int rate )
165 {
166 if ( id.isEmpty() || name.isEmpty() )
167 return;
168
169 Config::VoiceEngine v;
170 v.enabled = true;
171 v.id = id;
172 v.name = name;
173 v.volume = volume;
174 v.rate = rate;
175
176 beginInsertRows( QModelIndex(), voiceEngines.size(), voiceEngines.size() );
177 voiceEngines.push_back( v );
178 endInsertRows();
179 }
180
index(int row,int column,QModelIndex const &) const181 QModelIndex VoiceEnginesModel::index( int row, int column, QModelIndex const & /*parent*/ ) const
182 {
183 return createIndex( row, column );
184 }
185
parent(QModelIndex const &) const186 QModelIndex VoiceEnginesModel::parent( QModelIndex const & /*parent*/ ) const
187 {
188 return QModelIndex();
189 }
190
flags(QModelIndex const & index) const191 Qt::ItemFlags VoiceEnginesModel::flags( QModelIndex const & index ) const
192 {
193 Qt::ItemFlags result = QAbstractItemModel::flags( index );
194
195 if ( index.isValid() )
196 {
197 switch ( index.column() )
198 {
199 case kColumnEnabled:
200 result |= Qt::ItemIsUserCheckable;
201 break;
202 case kColumnEngineName:
203 case kColumnIcon:
204 result |= Qt::ItemIsEditable;
205 break;
206 }
207 }
208
209 return result;
210 }
211
rowCount(QModelIndex const & parent) const212 int VoiceEnginesModel::rowCount( QModelIndex const & parent ) const
213 {
214 if ( parent.isValid() )
215 return 0;
216 return voiceEngines.size();
217 }
218
columnCount(QModelIndex const & parent) const219 int VoiceEnginesModel::columnCount( QModelIndex const & parent ) const
220 {
221 if ( parent.isValid() )
222 return 0;
223 return kColumnCount;
224 }
225
headerData(int section,Qt::Orientation,int role) const226 QVariant VoiceEnginesModel::headerData( int section, Qt::Orientation /*orientation*/, int role ) const
227 {
228 if ( role == Qt::DisplayRole )
229 {
230 switch ( section )
231 {
232 case kColumnEnabled:
233 return tr( "Enabled" );
234 case kColumnEngineName:
235 return tr( "Name" );
236 case kColumnEngineId:
237 return tr( "Id" );
238 case kColumnIcon:
239 return tr( "Icon" );
240 }
241 }
242
243 return QVariant();
244 }
245
data(QModelIndex const & index,int role) const246 QVariant VoiceEnginesModel::data( QModelIndex const & index, int role ) const
247 {
248 if ( index.row() >= voiceEngines.size() )
249 return QVariant();
250
251 if ( role == Qt::DisplayRole || role == Qt::EditRole )
252 {
253 switch ( index.column() )
254 {
255 case kColumnEngineId:
256 return voiceEngines[ index.row() ].id;
257 case kColumnEngineName:
258 return voiceEngines[ index.row() ].name;
259 case kColumnIcon:
260 return voiceEngines[ index.row() ].iconFilename;
261 default:
262 return QVariant();
263 }
264 }
265
266 if ( role == Qt::CheckStateRole && index.column() == kColumnEnabled )
267 return voiceEngines[ index.row() ].enabled ? Qt::Checked : Qt::Unchecked;
268
269 return QVariant();
270 }
271
setData(QModelIndex const & index,const QVariant & value,int role)272 bool VoiceEnginesModel::setData( QModelIndex const & index, const QVariant & value,
273 int role )
274 {
275 if ( index.row() >= voiceEngines.size() )
276 return false;
277
278 if ( role == Qt::CheckStateRole && index.column() == kColumnEnabled )
279 {
280 voiceEngines[ index.row() ].enabled = !voiceEngines[ index.row() ].enabled;
281 dataChanged( index, index );
282 return true;
283 }
284
285 if ( role == Qt::DisplayRole || role == Qt::EditRole )
286 {
287 switch ( index.column() )
288 {
289 case kColumnEngineId:
290 voiceEngines[ index.row() ].id = value.toString();
291 dataChanged( index, index );
292 return true;
293 case kColumnEngineName:
294 voiceEngines[ index.row() ].name = value.toString();
295 dataChanged( index, index );
296 return true;
297 case kColumnIcon:
298 voiceEngines[ index.row() ].iconFilename = value.toString();
299 dataChanged( index, index );
300 return true;
301 default:
302 return false;
303 }
304 }
305
306 return false;
307 }
308
setEngineParams(QModelIndex idx,int volume,int rate)309 void VoiceEnginesModel::setEngineParams( QModelIndex idx, int volume, int rate )
310 {
311 if ( idx.isValid() )
312 {
313 voiceEngines[ idx.row() ].volume = volume;
314 voiceEngines[ idx.row() ].rate = rate;
315 }
316 }
317
VoiceEngineEditor(SpeechClient::Engines const & engines,QWidget * parent)318 VoiceEngineEditor::VoiceEngineEditor( SpeechClient::Engines const & engines, QWidget * parent ):
319 QComboBox( parent )
320 {
321 foreach ( SpeechClient::Engine engine, engines )
322 {
323 addItem( engine.name, engine.id );
324 }
325 }
326
engineName() const327 QString VoiceEngineEditor::engineName() const
328 {
329 int idx = currentIndex();
330 if ( idx < 0 )
331 return "";
332 return itemText( idx );
333 }
334
engineId() const335 QString VoiceEngineEditor::engineId() const
336 {
337 int idx = currentIndex();
338 if ( idx < 0 )
339 return "";
340 return itemData( idx ).toString();
341 }
342
setEngineId(QString const & engineId)343 void VoiceEngineEditor::setEngineId( QString const & engineId )
344 {
345 // Find index for the id
346 int idx = -1;
347 for ( int i = 0; i < count(); ++i )
348 {
349 if ( engineId == itemData( i ).toString() )
350 {
351 idx = i;
352 break;
353 }
354 }
355 setCurrentIndex( idx );
356 }
357
VoiceEngineItemDelegate(SpeechClient::Engines const & engines,QObject * parent)358 VoiceEngineItemDelegate::VoiceEngineItemDelegate( SpeechClient::Engines const & engines, QObject * parent ) :
359 QStyledItemDelegate( parent ),
360 engines( engines )
361 {
362 }
363
createEditor(QWidget * parent,QStyleOptionViewItem const & option,QModelIndex const & index) const364 QWidget * VoiceEngineItemDelegate::createEditor( QWidget * parent,
365 QStyleOptionViewItem const & option,
366 QModelIndex const & index ) const
367 {
368 if ( index.column() != VoiceEnginesModel::kColumnEngineName )
369 return QStyledItemDelegate::createEditor( parent, option, index );
370 return new VoiceEngineEditor( engines, parent );
371 }
372
setEditorData(QWidget * uncastedEditor,const QModelIndex & index) const373 void VoiceEngineItemDelegate::setEditorData( QWidget * uncastedEditor, const QModelIndex & index ) const
374 {
375 VoiceEngineEditor * editor = qobject_cast< VoiceEngineEditor * >( uncastedEditor );
376 if ( !editor )
377 return;
378
379 int currentRow = index.row();
380 QModelIndex engineIdIndex = index.sibling( currentRow, VoiceEnginesModel::kColumnEngineId );
381 QString engineId = index.model()->data( engineIdIndex ).toString();
382 editor->setEngineId( engineId );
383 }
384
setModelData(QWidget * uncastedEditor,QAbstractItemModel * model,const QModelIndex & index) const385 void VoiceEngineItemDelegate::setModelData( QWidget * uncastedEditor, QAbstractItemModel * model,
386 const QModelIndex & index ) const
387 {
388 VoiceEngineEditor * editor = qobject_cast< VoiceEngineEditor * >( uncastedEditor );
389 if ( !editor )
390 return;
391
392 int currentRow = index.row();
393 QModelIndex engineIdIndex = index.sibling( currentRow, VoiceEnginesModel::kColumnEngineId );
394 QModelIndex engineNameIndex = index.sibling( currentRow, VoiceEnginesModel::kColumnEngineName );
395 model->setData( engineIdIndex, editor->engineId() );
396 model->setData( engineNameIndex, editor->engineName() );
397 }
398