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