1
2 #include "optionsdetailed.h"
3 #include "config.h"
4 #include "core/codecplugin.h"
5 #include "core/codecwidget.h"
6 #include "outputdirectory.h"
7 #include "global.h"
8
9 #include <QApplication>
10 #include <QLayout>
11 #include <QBoxLayout>
12 #include <QLabel>
13
14 #include <KLocale>
15 #include <QFrame>
16 #include <QChar>
17 #include <KIcon>
18 #include <KComboBox>
19 #include <KPushButton>
20 #include <KInputDialog>
21 #include <KMessageBox>
22 #include <QFile>
23 #include <KStandardDirs>
24 #include <QMenu>
25 #include <QToolButton>
26
27 #define setShown setVisible
28
29 //
30 // class OptionsDetailed
31 //
32 ////////////////////
33
OptionsDetailed(Config * _config,QWidget * parent)34 OptionsDetailed::OptionsDetailed( Config* _config, QWidget* parent )
35 : QWidget( parent ),
36 config( _config )
37 {
38 const int fontHeight = QFontMetrics(QApplication::font()).boundingRect("M").size().height();
39
40 int gridRow = 0;
41 grid = new QGridLayout( this );
42
43 QHBoxLayout *topBox = new QHBoxLayout();
44 grid->addLayout( topBox, 0, 0 );
45
46 QLabel *lFormat = new QLabel( i18n("Format:"), this );
47 topBox->addWidget( lFormat );
48 cFormat = new KComboBox( this );
49 topBox->addWidget( cFormat );
50 cFormat->addItems( config->pluginLoader()->formatList(PluginLoader::Encode,PluginLoader::CompressionType(PluginLoader::InferiorQuality|PluginLoader::Lossy|PluginLoader::Lossless|PluginLoader::Hybrid)) );
51 connect( cFormat, SIGNAL(activated(const QString&)), this, SLOT(formatChanged(const QString&)) );
52 // connect( cFormat, SIGNAL(activated(const QString&)), this, SLOT(somethingChanged()) );
53
54 topBox->addStretch();
55
56 lPlugin = new QLabel( i18n("Use Plugin:"), this );
57 topBox->addWidget( lPlugin );
58 cPlugin = new KComboBox( this );
59 topBox->addWidget( cPlugin );
60 cPlugin->setSizeAdjustPolicy( QComboBox::AdjustToContents );
61 connect( cPlugin, SIGNAL(activated(const QString&)), this, SLOT(encoderChanged(const QString&)) );
62 connect( cPlugin, SIGNAL(activated(const QString&)), this, SLOT(somethingChanged()) );
63 pConfigurePlugin = new KPushButton( KIcon("configure"), "", this );
64 pConfigurePlugin->setFixedSize( cPlugin->sizeHint().height(), cPlugin->sizeHint().height() );
65 pConfigurePlugin->setFlat( true );
66 topBox->addWidget( pConfigurePlugin );
67 topBox->setStretchFactor( pConfigurePlugin, 1 );
68 connect( pConfigurePlugin, SIGNAL(clicked()), this, SLOT(configurePlugin()) );
69
70 // draw a horizontal line
71 QFrame *lineFrame = new QFrame( this );
72 lineFrame->setFrameShape( QFrame::HLine );
73 lineFrame->setFrameShadow( QFrame::Sunken );
74 lineFrame->setFixedHeight( fontHeight );
75 grid->addWidget( lineFrame, 1, 0 );
76
77 // prepare the plugin widget
78 wPlugin = 0;
79 grid->setRowStretch( 2, 1 );
80 grid->setRowMinimumHeight( 2, 20 );
81 gridRow = 3;
82
83 // draw a horizontal line
84 lineFrame = new QFrame( this );
85 lineFrame->setFrameShape( QFrame::HLine );
86 lineFrame->setFrameShadow( QFrame::Sunken );
87 lineFrame->setFixedHeight( fontHeight );
88 grid->addWidget( lineFrame, gridRow++, 0 );
89
90 int filterCount = 0;
91 foreach( const QString& pluginName, config->data.backends.enabledFilters )
92 {
93 FilterPlugin *plugin = qobject_cast<FilterPlugin*>(config->pluginLoader()->backendPluginByName(pluginName));
94 if( !plugin )
95 continue;
96
97 FilterWidget *widget = plugin->newFilterWidget();
98 if( !widget )
99 continue;
100
101 wFilter.insert( widget, plugin );
102 connect( widget, SIGNAL(optionsChanged()), this, SLOT(somethingChanged()) );
103 grid->addWidget( widget, gridRow++, 0 );
104 widget->show();
105 filterCount++;
106 }
107 if( filterCount > 0 )
108 {
109 // draw a horizontal line
110 lineFrame = new QFrame( this );
111 lineFrame->setFrameShape( QFrame::HLine );
112 lineFrame->setFrameShadow( QFrame::Sunken );
113 lineFrame->setFixedHeight( fontHeight );
114 grid->addWidget( lineFrame, gridRow++, 0 );
115 }
116
117 // the output directory
118 QHBoxLayout *middleBox = new QHBoxLayout( );
119 grid->addLayout( middleBox, gridRow++, 0 );
120
121 QLabel *lOutput = new QLabel( i18n("Destination:"), this );
122 middleBox->addWidget( lOutput );
123 outputDirectory = new OutputDirectory( config, this );
124 middleBox->addWidget( outputDirectory );
125
126 QHBoxLayout *bottomBox = new QHBoxLayout();
127 grid->addLayout( bottomBox, gridRow++, 0 );
128
129 cReplayGain = new QCheckBox( i18n("Calculate Replay Gain tags"), this );
130 bottomBox->addWidget( cReplayGain );
131 //connect( cReplayGain, SIGNAL(toggled(bool)), this, SLOT(somethingChanged()) );
132 bottomBox->addStretch();
133 lEstimSize = new QLabel( QString(QChar(8776))+"? B / min." );
134 lEstimSize->hide(); // hide for now because most plugins report inaccurate data
135 bottomBox->addWidget( lEstimSize );
136 pProfileSave = new KPushButton( KIcon("document-save"), "", this );
137 bottomBox->addWidget( pProfileSave );
138 pProfileSave->setFixedWidth( pProfileSave->height() );
139 pProfileSave->setToolTip( i18n("Save current options as a profile") );
140 connect( pProfileSave, SIGNAL(clicked()), this, SLOT(saveCustomProfile()) );
141 pProfileLoad = new QToolButton( this );
142 bottomBox->addWidget( pProfileLoad );
143 pProfileLoad->setIcon( KIcon("document-open") );
144 pProfileLoad->setPopupMode( QToolButton::InstantPopup );
145 pProfileLoad->setFixedWidth( pProfileLoad->height() );
146 pProfileLoad->setToolTip( i18n("Load saved profiles") );
147 }
148
~OptionsDetailed()149 OptionsDetailed::~OptionsDetailed()
150 {}
151
init()152 void OptionsDetailed::init()
153 {
154 updateProfiles();
155
156 cFormat->setCurrentIndex( 0 );
157 formatChanged( cFormat->currentText() );
158 }
159
160 // QSize OptionsDetailed::sizeHint()
161 // {
162 // return size_hint;
163 // }
164
resetFilterOptions()165 void OptionsDetailed::resetFilterOptions()
166 {
167 for( int i=0; i<wFilter.size(); i++ )
168 {
169 FilterWidget *widget = wFilter.keys().at(i);
170 if( widget )
171 {
172 widget->setCurrentFilterOptions( 0 );
173 }
174 }
175 }
176
setReplayGainChecked(bool enabled)177 void OptionsDetailed::setReplayGainChecked( bool enabled )
178 {
179 cReplayGain->setChecked(enabled);
180 }
181
isReplayGainEnabled(QString * toolTip)182 bool OptionsDetailed::isReplayGainEnabled( QString *toolTip )
183 {
184 if( toolTip )
185 *toolTip = cReplayGain->toolTip();
186
187 return cReplayGain->isEnabled();
188 }
189
isReplayGainChecked()190 bool OptionsDetailed::isReplayGainChecked()
191 {
192 return cReplayGain->isChecked();
193 }
194
getCurrentPlugin()195 CodecPlugin *OptionsDetailed::getCurrentPlugin()
196 {
197 return currentPlugin;
198 }
199
200 //
201 // class private slots
202 //
203
updateProfiles()204 void OptionsDetailed::updateProfiles()
205 {
206 if( pProfileLoad->menu() )
207 pProfileLoad->menu()->deleteLater();
208
209 QMenu *menu = new QMenu( this );
210 foreach( const QString& profile, config->customProfiles() )
211 {
212 menu->addAction( profile, this, SLOT(loadCustomProfileButtonClicked()) );
213 }
214
215 pProfileLoad->setMenu( menu );
216 pProfileLoad->setShown( config->customProfiles().count() > 0 );
217 }
218
formatChanged(const QString & format)219 void OptionsDetailed::formatChanged( const QString& format )
220 {
221 const QString oldEncoder = cPlugin->currentText();
222
223 cPlugin->clear();
224 //if( format != "wav" ) // TODO make it nicer if wav is selected
225 for( int i=0; i<config->data.backends.codecs.count(); i++ )
226 {
227 if( config->data.backends.codecs.at(i).codecName == format )
228 {
229 cPlugin->addItems( config->data.backends.codecs.at(i).encoders );
230 }
231 }
232 cPlugin->setCurrentIndex( 0 );
233
234 if( cPlugin->currentText() != oldEncoder )
235 {
236 encoderChanged( cPlugin->currentText() );
237 }
238 else if( wPlugin )
239 {
240 wPlugin->setCurrentFormat( cFormat->currentText() );
241 }
242
243 lPlugin->setShown( format != "wav" );
244 cPlugin->setShown( format != "wav" );
245 pConfigurePlugin->setShown( format != "wav" );
246 if( wPlugin )
247 wPlugin->setShown( format != "wav" );
248
249 QStringList errorList;
250 cReplayGain->setEnabled( config->pluginLoader()->canReplayGain(cFormat->currentText(),currentPlugin,&errorList) );
251 if( !cReplayGain->isEnabled() )
252 {
253 QPalette notificationPalette = cReplayGain->palette();
254 notificationPalette.setColor( QPalette::Disabled, QPalette::WindowText, QColor(174,127,130) );
255 cReplayGain->setPalette( notificationPalette );
256
257 if( !errorList.isEmpty() )
258 {
259 errorList.prepend( i18n("Replay Gain is not supported for the %1 file format.\nPossible solutions are listed below.",cFormat->currentText()) );
260 }
261 else
262 {
263 errorList += i18n("Replay Gain is not supported for the %1 file format.\nPlease check your distribution's package manager in order to install an additional Replay Gain plugin.",cFormat->currentText());
264 }
265 cReplayGain->setToolTip( errorList.join("\n\n") );
266 }
267 else
268 {
269 cReplayGain->setToolTip( i18n("Replay Gain tags can tell your music player how loud a track is\nso it can adjust the volume to play all tracks with equal loudness.") );
270 }
271
272 somethingChanged();
273 }
274
encoderChanged(const QString & encoder)275 void OptionsDetailed::encoderChanged( const QString& encoder )
276 {
277 CodecPlugin *plugin = qobject_cast<CodecPlugin*>(config->pluginLoader()->backendPluginByName( encoder ));
278 if( !plugin )
279 {
280 // TODO leads to crashes
281 // KMessageBox::error( this, i18n("Sorry, this shouldn't happen.\n\nPlease report this bug and attach the following error message:\n\nOptionsDetailed::encoderChanged; PluginLoader::codecPluginByName returned 0 for encoder: '%1'").arg(encoder), i18n("Internal error") );
282 return;
283 }
284 if( wPlugin )
285 {
286 grid->removeWidget( wPlugin );
287 disconnect( wPlugin, SIGNAL(optionsChanged()), 0, 0 );
288 wPlugin = currentPlugin->deleteCodecWidget( wPlugin );
289 }
290 currentPlugin = plugin;
291 wPlugin = plugin->newCodecWidget();
292 if( wPlugin )
293 {
294 connect( wPlugin, SIGNAL(optionsChanged()), this, SLOT(somethingChanged()) );
295 qobject_cast<CodecWidget*>(wPlugin)->setCurrentFormat( cFormat->currentText() );
296 if( plugin->lastConversionOptions() )
297 {
298 wPlugin->setCurrentConversionOptions( plugin->lastConversionOptions() );
299 }
300 grid->addWidget( wPlugin, 2, 0 );
301 }
302
303 pConfigurePlugin->setEnabled( plugin->isConfigSupported(BackendPlugin::Encoder,"") );
304
305 if( pConfigurePlugin->isEnabled() )
306 pConfigurePlugin->setToolTip( i18n("Configure %1 ...",encoder) );
307 else
308 pConfigurePlugin->setToolTip( "" );
309 }
310
somethingChanged()311 void OptionsDetailed::somethingChanged()
312 {
313 int dataRate = 0;
314
315 if( wPlugin )
316 dataRate = wPlugin->currentDataRate();
317
318 if( dataRate > 0 )
319 {
320 const QString dataRateString = Global::prettyNumber(dataRate,"B");
321 lEstimSize->setText( QString(QChar(8776))+" "+dataRateString+" / min." );
322 lEstimSize->setToolTip( i18n("Using the current conversion options will create files with approximately %1 per minute.").arg(dataRateString) );
323 }
324 else
325 {
326 lEstimSize->setText( QString(QChar(8776))+" ? B / min." );
327 lEstimSize->setToolTip( "" );
328 }
329
330 emit currentDataRateChanged( dataRate );
331 }
332
configurePlugin()333 void OptionsDetailed::configurePlugin()
334 {
335 CodecPlugin *plugin = qobject_cast<CodecPlugin*>(config->pluginLoader()->backendPluginByName( cPlugin->currentText() ));
336
337 if( plugin )
338 {
339 plugin->showConfigDialog( BackendPlugin::Encoder, "", this );
340 }
341 }
342
currentConversionOptions(bool saveLastUsed)343 ConversionOptions *OptionsDetailed::currentConversionOptions( bool saveLastUsed )
344 {
345 if( wPlugin && currentPlugin )
346 {
347 ConversionOptions *conversionOptions = wPlugin->currentConversionOptions();
348 if( conversionOptions )
349 {
350 conversionOptions->codecName = cFormat->currentText();
351 if( conversionOptions->codecName != "wav" )
352 conversionOptions->pluginName = currentPlugin->name();
353 else
354 conversionOptions->pluginName = "";
355 conversionOptions->profile = wPlugin->currentProfile();
356 conversionOptions->outputDirectoryMode = outputDirectory->mode();
357 conversionOptions->outputDirectory = outputDirectory->directory();
358 conversionOptions->outputFilesystem = outputDirectory->filesystem();
359 conversionOptions->replaygain = cReplayGain->isEnabled() && cReplayGain->isChecked();
360
361 for( int i=0; i<wFilter.size(); i++ )
362 {
363 FilterWidget *widget = wFilter.keys().at(i);
364 FilterPlugin *plugin = wFilter.values().at(i);
365 if( widget && plugin )
366 {
367 FilterOptions *filterOptions = widget->currentFilterOptions();
368 if( filterOptions )
369 {
370 filterOptions->pluginName = plugin->name();
371 conversionOptions->filterOptions.append( filterOptions );
372 }
373 }
374 }
375
376 if( saveLastUsed )
377 {
378 config->data.general.lastProfile = currentProfile();
379 saveCustomProfile( true );
380 config->data.general.lastFormat = cFormat->currentText();
381 }
382
383 return conversionOptions;
384 }
385 }
386
387 return 0;
388 }
389
setCurrentConversionOptions(const ConversionOptions * conversionOptions)390 bool OptionsDetailed::setCurrentConversionOptions( const ConversionOptions *conversionOptions )
391 {
392 if( !conversionOptions )
393 return false;
394
395 cFormat->setCurrentIndex( cFormat->findText(conversionOptions->codecName) );
396 formatChanged( cFormat->currentText() );
397 if( conversionOptions->codecName != "wav" )
398 {
399 cPlugin->setCurrentIndex( cPlugin->findText(conversionOptions->pluginName) );
400 encoderChanged( cPlugin->currentText() );
401 }
402 outputDirectory->setMode( (OutputDirectory::Mode)conversionOptions->outputDirectoryMode );
403 outputDirectory->setDirectory( conversionOptions->outputDirectory );
404 cReplayGain->setChecked( conversionOptions->replaygain );
405
406 bool succeeded = true;
407
408 if( conversionOptions->codecName == "wav" )
409 succeeded = true;
410 else if( wPlugin )
411 succeeded = wPlugin->setCurrentConversionOptions( conversionOptions );
412 else
413 succeeded = false;
414
415 QStringList usedFilter;
416 foreach( const FilterOptions *filterOptions, conversionOptions->filterOptions )
417 {
418 bool filterSucceeded = false;
419 for( int i=0; i<wFilter.size(); i++ )
420 {
421 FilterWidget *widget = wFilter.keys().at(i);
422 FilterPlugin *plugin = wFilter.values().at(i);
423 if( widget && plugin && filterOptions->pluginName == plugin->name() )
424 {
425 filterSucceeded = widget->setCurrentFilterOptions( filterOptions );
426 usedFilter.append( filterOptions->pluginName );
427 break;
428 }
429 }
430 if( !filterSucceeded )
431 succeeded = false;
432 }
433 // if a filter is disabled, its FilterOptions is 0 thus it won't be added to ConversionOptions, but we need to update the widget so it won't show false data
434 for( int i=0; i<wFilter.size(); i++ )
435 {
436 FilterWidget *widget = wFilter.keys().at(i);
437 FilterPlugin *plugin = wFilter.values().at(i);
438 if( widget && plugin && !usedFilter.contains(plugin->name()) )
439 {
440 widget->setCurrentFilterOptions( 0 );
441 }
442 }
443
444 return succeeded;
445 }
446
saveCustomProfile(bool lastUsed)447 bool OptionsDetailed::saveCustomProfile( bool lastUsed )
448 {
449 if( wPlugin && currentPlugin )
450 {
451 QString profileName;
452 if( lastUsed )
453 {
454 profileName = "soundkonverter_last_used";
455 }
456 else
457 {
458 bool ok;
459 profileName = KInputDialog::getText( i18n("New profile"), i18n("Enter a name for the new profile:"), "", &ok );
460 if( !ok )
461 return false;
462 }
463
464 if( profileName.isEmpty() )
465 {
466 KMessageBox::information( this, i18n("You cannot save a profile without a name."), i18n("Profile name is empty") );
467 return false;
468 }
469
470 QStringList profiles;
471 profiles += i18n("Very low");
472 profiles += i18n("Low");
473 profiles += i18n("Medium");
474 profiles += i18n("High");
475 profiles += i18n("Very high");
476 profiles += i18n("Lossless");
477 profiles += i18n("Hybrid");
478 profiles += i18n("Last used");
479 profiles += "Last used";
480 profiles += i18n("User defined");
481 if( !lastUsed )
482 profiles += "soundkonverter_last_used";
483
484 if( profiles.contains(profileName) )
485 {
486 KMessageBox::information( this, i18n("You cannot overwrite the built-in profiles."), i18n("Profile already exists") );
487 return false;
488 }
489
490 QDomDocument list("soundkonverter_profilelist");
491 QDomElement root;
492 bool profileFound = false;
493
494 QFile listFile( KStandardDirs::locateLocal("data","soundkonverter/profiles.xml") );
495 if( listFile.open( QIODevice::ReadOnly ) )
496 {
497 if( list.setContent( &listFile ) )
498 {
499 root = list.documentElement();
500 if( root.nodeName() == "soundkonverter" && root.attribute("type") == "profilelist" )
501 {
502 QDomNodeList conversionOptionsElements = root.elementsByTagName("conversionOptions");
503 for( int i=0; i<conversionOptionsElements.count(); i++ )
504 {
505 if( conversionOptionsElements.at(i).toElement().attribute("profileName") == profileName )
506 {
507 int ret;
508 if( lastUsed )
509 ret = KMessageBox::Yes;
510 else
511 ret = KMessageBox::questionYesNo( this, i18n("A profile with this name already exists.\n\nDo you want to overwrite the existing one?"), i18n("Profile already exists") );
512
513 if( ret == KMessageBox::Yes )
514 {
515 ConversionOptions *conversionOptions = currentConversionOptions( false );
516 delete config->data.profiles[profileName];
517 config->data.profiles[profileName] = conversionOptions;
518 root.removeChild(conversionOptionsElements.at(i));
519 QDomElement profileElement = conversionOptions->toXml(list);
520 profileElement.setAttribute("profileName",profileName);
521 root.appendChild(profileElement);
522 profileFound = true;
523 break;
524 }
525 else
526 {
527 return false;
528 }
529 }
530 }
531 }
532 }
533 listFile.close();
534 }
535
536 if( listFile.open( QIODevice::WriteOnly ) )
537 {
538 if( list.childNodes().isEmpty() )
539 {
540 root = list.createElement("soundkonverter");
541 root.setAttribute("type","profilelist");
542 list.appendChild(root);
543 }
544
545 if( !profileFound )
546 {
547 ConversionOptions *conversionOptions = currentConversionOptions( false );
548 config->data.profiles[profileName] = conversionOptions;
549 QDomElement profileElement = conversionOptions->toXml(list);
550 profileElement.setAttribute("profileName",profileName);
551 root.appendChild(profileElement);
552 }
553
554 updateProfiles();
555 emit customProfilesEdited();
556
557 QTextStream stream(&listFile);
558 stream << list.toString();
559 listFile.close();
560
561 return true;
562 }
563 else
564 {
565 return false;
566 }
567 }
568 else
569 {
570 return false;
571 }
572 }
573
loadCustomProfileButtonClicked()574 void OptionsDetailed::loadCustomProfileButtonClicked()
575 {
576 const QString profile = qobject_cast<QAction*>(QObject::sender())->text().replace("&","");
577 setCurrentProfile( profile );
578 }
579
currentProfile()580 QString OptionsDetailed::currentProfile()
581 {
582 if( wPlugin )
583 return wPlugin->currentProfile();
584 else
585 return "";
586 }
587
setCurrentProfile(const QString & profile)588 bool OptionsDetailed::setCurrentProfile( const QString& profile )
589 {
590 if( config->data.profiles.keys().contains(profile) )
591 {
592 const ConversionOptions *conversionOptions = config->data.profiles.value( profile );
593 if( conversionOptions )
594 return setCurrentConversionOptions( conversionOptions );
595 }
596 else if( wPlugin )
597 {
598 return wPlugin->setCurrentProfile( profile );
599 }
600
601 return false;
602 }
603
currentFormat()604 QString OptionsDetailed::currentFormat()
605 {
606 return cFormat->currentText();
607 }
608
setCurrentFormat(const QString & format)609 void OptionsDetailed::setCurrentFormat( const QString& format )
610 {
611 if( !format.isEmpty() && format != cFormat->currentText() )
612 {
613 cFormat->setCurrentIndex( cFormat->findText(format) );
614 formatChanged( cFormat->currentText() );
615 }
616 }
617
618
619