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