OSDN Git Service

Added support for saving and deleting custom templates.
[x264-launcher/x264-launcher.git] / src / win_addJob.cpp
1 ///////////////////////////////////////////////////////////////////////////////
2 // Simple x264 Launcher
3 // Copyright (C) 2004-2012 LoRd_MuldeR <MuldeR2@GMX.de>
4 //
5 // This program is free software; you can redistribute it and/or modify
6 // it under the terms of the GNU General Public License as published by
7 // the Free Software Foundation; either version 2 of the License, or
8 // (at your option) any later version.
9 //
10 // This program is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 // GNU General Public License for more details.
14 //
15 // You should have received a copy of the GNU General Public License along
16 // with this program; if not, write to the Free Software Foundation, Inc.,
17 // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 //
19 // http://www.gnu.org/licenses/gpl-2.0.txt
20 ///////////////////////////////////////////////////////////////////////////////
21
22 #include "win_addJob.h"
23
24 #include "global.h"
25 #include "model_options.h"
26
27 #include <QDate>
28 #include <QTimer>
29 #include <QCloseEvent>
30 #include <QMessageBox>
31 #include <QFileDialog>
32 #include <QDesktopServices>
33 #include <QValidator>
34 #include <QDir>
35 #include <QInputDialog>
36
37 static const struct
38 {
39         const char *name;
40         const char *fext;
41 }
42 g_filters[] =
43 {
44         {"Avisynth Scripts", "avs"},
45         {"Matroska Files", "mkv"},
46         {"MPEG-4 Part 14 Container", "mp4"},
47         {"Audio Video Interleaved", "avi"},
48         {"Flash Video", "flv"},
49         {NULL, NULL}
50 };
51
52 ///////////////////////////////////////////////////////////////////////////////
53 // Validator
54 ///////////////////////////////////////////////////////////////////////////////
55
56 class StringValidator : public QValidator
57 {
58 public:
59         StringValidator(QLabel *notifier) : m_notifier(notifier) { m_notifier->hide(); }
60         
61         virtual State validate(QString &input, int &pos) const
62         {
63                 bool invalid = false;
64                 
65                 invalid = invalid || (input.contains(" -B") || input.startsWith("-B"));
66                 invalid = invalid || (input.contains(" -o") || input.startsWith("-o"));
67                 invalid = invalid || (input.contains(" -h") || input.startsWith("-h"));
68                 invalid = invalid || (input.contains(" -p") || input.startsWith("-p"));
69
70                 invalid = invalid || input.contains("--fps", Qt::CaseInsensitive);
71                 invalid = invalid || input.contains("--frames", Qt::CaseInsensitive);
72                 invalid = invalid || input.contains("--preset", Qt::CaseInsensitive);
73                 invalid = invalid || input.contains("--tune", Qt::CaseInsensitive);
74                 invalid = invalid || input.contains("--profile", Qt::CaseInsensitive);
75                 invalid = invalid || input.contains("--stdin", Qt::CaseInsensitive);
76                 invalid = invalid || input.contains("--crf", Qt::CaseInsensitive);
77                 invalid = invalid || input.contains("--bitrate", Qt::CaseInsensitive);
78                 invalid = invalid || input.contains("--qp", Qt::CaseInsensitive);
79                 invalid = invalid || input.contains("--pass", Qt::CaseInsensitive);
80                 invalid = invalid || input.contains("--stats", Qt::CaseInsensitive);
81                 invalid = invalid || input.contains("--output", Qt::CaseInsensitive);
82                 invalid = invalid || input.contains("--help", Qt::CaseInsensitive);
83
84                 if(invalid)
85                 {
86                         MessageBeep(MB_ICONWARNING);
87                         if(m_notifier->isHidden())
88                         {
89                                 m_notifier->show();
90                                 QTimer::singleShot(1000, m_notifier, SLOT(hide()));
91                         }
92                 }
93
94                 return invalid ? QValidator::Invalid : QValidator::Acceptable;
95         }
96
97         virtual void fixup(QString &input) const
98         {
99                 input = input.simplified();
100         }
101
102 protected:
103         QLabel *const m_notifier;
104 };
105
106 ///////////////////////////////////////////////////////////////////////////////
107 // Constructor & Destructor
108 ///////////////////////////////////////////////////////////////////////////////
109
110 AddJobDialog::AddJobDialog(QWidget *parent, OptionsModel *options)
111 :
112         QDialog(parent),
113         m_defaults(new OptionsModel()),
114         m_options(options)
115 {
116         //Init the dialog, from the .ui file
117         setupUi(this);
118         setWindowFlags(windowFlags() & (~Qt::WindowContextHelpButtonHint));
119         
120         //Fix dialog size
121         buttonSaveTemplate->setMaximumHeight(20);
122         buttonDeleteTemplate->setMaximumHeight(20);
123         resize(width(), minimumHeight());
124         setMinimumSize(size());
125         setMaximumHeight(height());
126
127         //Monitor RC mode combobox
128         connect(cbxRateControlMode, SIGNAL(currentIndexChanged(int)), this, SLOT(modeIndexChanged(int)));
129
130         //Activate buttons
131         connect(buttonBrowseSource, SIGNAL(clicked()), this, SLOT(browseButtonClicked()));
132         connect(buttonBrowseOutput, SIGNAL(clicked()), this, SLOT(browseButtonClicked()));
133         connect(buttonSaveTemplate, SIGNAL(clicked()), this, SLOT(saveTemplateButtonClicked()));
134         connect(buttonDeleteTemplate, SIGNAL(clicked()), this, SLOT(deleteTemplateButtonClicked()));
135
136         //Setup validator
137         editCustomParams->installEventFilter(this);
138         editCustomParams->setValidator(new StringValidator(labelNotification));
139         editCustomParams->clear();
140
141         //Install event filter
142         labelHelpScreen->installEventFilter(this);
143
144         //Monitor for options changes
145         connect(cbxRateControlMode, SIGNAL(currentIndexChanged(int)), this, SLOT(configurationChanged()));
146         connect(spinQuantizer, SIGNAL(valueChanged(int)), this, SLOT(configurationChanged()));
147         connect(spinBitrate, SIGNAL(valueChanged(int)), this, SLOT(configurationChanged()));
148         connect(cbxPreset, SIGNAL(currentIndexChanged(int)), this, SLOT(configurationChanged()));
149         connect(cbxTuning, SIGNAL(currentIndexChanged(int)), this, SLOT(configurationChanged()));
150         connect(cbxProfile, SIGNAL(currentIndexChanged(int)), this, SLOT(configurationChanged()));
151         connect(editCustomParams, SIGNAL(textChanged(QString)), this, SLOT(configurationChanged()));
152
153         //Setup template selector
154         loadTemplateList();
155         connect(cbxTemplate, SIGNAL(currentIndexChanged(int)), this, SLOT(templateSelected()));
156 }
157
158 AddJobDialog::~AddJobDialog(void)
159 {
160         for(int i = 0; i < cbxTemplate->model()->rowCount(); i++)
161         {
162                 if(cbxTemplate->itemText(i).startsWith("<") || cbxTemplate->itemText(i).endsWith(">"))
163                 {
164                         continue;
165                 }
166                 OptionsModel *item = reinterpret_cast<OptionsModel*>(cbxTemplate->itemData(i).value<void*>());
167                 cbxTemplate->setItemData(i, QVariant::fromValue<void*>(NULL));
168                 X264_DELETE(item);
169         }
170         
171         X264_DELETE(m_defaults);
172 }
173
174 ///////////////////////////////////////////////////////////////////////////////
175 // Events
176 ///////////////////////////////////////////////////////////////////////////////
177
178 void AddJobDialog::showEvent(QShowEvent *event)
179 {
180         QDialog::showEvent(event);
181         templateSelected();
182 }
183
184 bool AddJobDialog::eventFilter(QObject *o, QEvent *e)
185 {
186         if((o == labelHelpScreen) && (e->type() == QEvent::MouseButtonPress))
187         {
188                 QMessageBox::information(this, tr("Not yet"), tr("Not implemented yet. Please use the '?' menu for now!"));
189         }
190         else if((o == editCustomParams) && (e->type() == QEvent::FocusOut))
191         {
192                 editCustomParams->setText(editCustomParams->text().simplified());
193         }
194         return false;
195 }
196
197 ///////////////////////////////////////////////////////////////////////////////
198 // Slots
199 ///////////////////////////////////////////////////////////////////////////////
200
201 void AddJobDialog::modeIndexChanged(int index)
202 {
203         spinQuantizer->setEnabled(index == 0 || index == 1);
204         spinBitrate->setEnabled(index == 2 || index == 3);
205 }
206
207 void AddJobDialog::accept(void)
208 {
209         if(editSource->text().trimmed().isEmpty())
210         {
211                 QMessageBox::warning(this, tr("Not Found!"), tr("Please select a valid source file first!"));
212                 return;
213         }
214         
215         QFileInfo sourceFile = QFileInfo(editSource->text());
216         if(!(sourceFile.exists() && sourceFile.isFile()))
217         {
218                 QMessageBox::warning(this, tr("Not Found!"), tr("The selected source file could not be found!"));
219                 return;
220         }
221
222         QFileInfo outputDir = QFileInfo(QFileInfo(editOutput->text()).path());
223         if(!(outputDir.exists() && outputDir.isDir() && outputDir.isWritable()))
224         {
225                 QMessageBox::warning(this, tr("Not Writable!"), tr("Output directory does not exist or is not writable!"));
226                 return;
227         }
228
229         QFileInfo outputFile = QFileInfo(editOutput->text());
230         if(outputFile.exists() && outputFile.isFile())
231         {
232                 if(QMessageBox::question(this, tr("Already Exists!"), tr("Output file already exists! Overwrite?"), QMessageBox::Yes | QMessageBox::No, QMessageBox::No) != QMessageBox::Yes)
233                 {
234                         return;
235                 }
236         }
237         if(outputFile.exists() && (!outputFile.isFile()))
238         {
239                 QMessageBox::warning(this, tr("Not a File!"), tr("Selected output files does not appear to be a file!"));
240                 return;
241         }
242
243         saveOptions(m_options);
244         QDialog::accept();
245 }
246
247 void AddJobDialog::browseButtonClicked(void)
248 {
249         QString initialDir = QDesktopServices::storageLocation(QDesktopServices::MoviesLocation);
250
251         if(QObject::sender() == buttonBrowseSource)
252         {
253                 QString filePath = QFileDialog::getOpenFileName(this, tr("Open Source File"), initialDir, makeFileFilter());
254                 
255                 if(!(filePath.isNull() || filePath.isEmpty()))
256                 {
257                         editSource->setText(QDir::toNativeSeparators(filePath));
258
259                         QString path = QFileInfo(filePath).path();
260                         QString name = QFileInfo(filePath).completeBaseName();
261                         
262                         QString outPath = QString("%1/%2.mkv").arg(path, name);
263
264                         if(QFileInfo(outPath).exists())
265                         {
266                                 int i = 2;
267                                 while(QFileInfo(outPath).exists())
268                                 {
269                                         outPath = QString("%1/%2 (%3).mkv").arg(path, name, QString::number(i++));
270                                 }
271                         }
272
273                         editOutput->setText(QDir::toNativeSeparators(outPath));
274                 }
275         }
276         else if(QObject::sender() == buttonBrowseOutput)
277         {
278                 QString filters;
279                 filters += tr("Matroska Files (*.mkv)").append(";;");
280                 filters += tr("MPEG-4 Part 14 Container (*.mp4)").append(";;");
281                 filters += tr("H.264 Elementary Stream (*.264)");
282
283                 QString filePath = QFileDialog::getSaveFileName(this, tr("Choose Output File"), initialDir, filters);
284
285                 if(!(filePath.isNull() || filePath.isEmpty()))
286                 {
287                         editOutput->setText(QDir::toNativeSeparators(filePath));
288                 }
289         }
290 }
291
292 void AddJobDialog::configurationChanged(void)
293 {
294         OptionsModel* options = reinterpret_cast<OptionsModel*>(cbxTemplate->itemData(cbxTemplate->currentIndex()).value<void*>());
295         if(options)
296         {
297                 cbxTemplate->blockSignals(true);
298                 cbxTemplate->insertItem(0, tr("<Unsaved Configuration>"), QVariant::fromValue<void*>(NULL));
299                 cbxTemplate->setCurrentIndex(0);
300                 cbxTemplate->blockSignals(false);
301         }
302 }
303
304 void AddJobDialog::templateSelected(void)
305 {
306         OptionsModel* options = reinterpret_cast<OptionsModel*>(cbxTemplate->itemData(cbxTemplate->currentIndex()).value<void*>());
307         if(options)
308         {
309                 qDebug("Loading options!");
310                 for(int i = 0; i < cbxTemplate->model()->rowCount(); i++)
311                 {
312                         OptionsModel* temp = reinterpret_cast<OptionsModel*>(cbxTemplate->itemData(i).value<void*>());
313                         if(temp == NULL)
314                         {
315                                 cbxTemplate->blockSignals(true);
316                                 cbxTemplate->removeItem(i);
317                                 cbxTemplate->blockSignals(false);
318                                 break;
319                         }
320                 }
321                 restoreOptions(options);
322         }
323
324         modeIndexChanged(cbxRateControlMode->currentIndex());
325 }
326
327 void AddJobDialog::saveTemplateButtonClicked(void)
328 {
329         qDebug("Saving template");
330         QString name = tr("New Template");
331
332         forever
333         {
334                 bool ok = false;
335                 name = QInputDialog::getText(this, tr("Save Template"), tr("Please enter the name of the template:"), QLineEdit::Normal, name, &ok).simplified();
336                 if(!ok) return;
337                 if(name.startsWith("<") || name.endsWith(">"))
338                 {
339                         QMessageBox::warning (this, tr("Invalid Name"), tr("Sorry, the name you have entered is invalid!"));
340                         while(name.startsWith("<")) name = name.mid(1).trimmed();
341                         while(name.endsWith(">")) name = name.left(name.size() - 1).trimmed();
342                         continue;
343                 }
344                 if(OptionsModel::templateExists(name))
345                 {
346                         QMessageBox::warning (this, tr("Already Exists"), tr("Sorry, a template of that name already exists!"));
347                         continue;
348                 }
349                 break;
350         }
351
352         OptionsModel *options = new OptionsModel();
353         saveOptions(options);
354         
355         if(options->equals(m_defaults))
356         {
357                 QMessageBox::warning (this, tr("Default"), tr("It makes no sense to save the defaults!"));
358                 X264_DELETE(options);
359                 return;
360         }
361         if(!OptionsModel::saveTemplate(options, name))
362         {
363                 QMessageBox::warning (this, tr("Save Failed"), tr("Sorry, the template could not be used!"));
364                 X264_DELETE(options);
365                 return;
366         }
367         
368         int index = cbxTemplate->model()->rowCount();
369         cbxTemplate->blockSignals(true);
370         cbxTemplate->insertItem(index, name, QVariant::fromValue<void*>(options));
371         cbxTemplate->setCurrentIndex(index);
372         for(int i = 0; i < cbxTemplate->model()->rowCount(); i++)
373         {
374                 OptionsModel* temp = reinterpret_cast<OptionsModel*>(cbxTemplate->itemData(i).value<void*>());
375                 if(temp == NULL)
376                 {
377                         cbxTemplate->removeItem(i);
378                         break;
379                 }
380         }
381         cbxTemplate->blockSignals(false);
382 }
383
384 void AddJobDialog::deleteTemplateButtonClicked(void)
385 {
386         const int index = cbxTemplate->currentIndex();
387         QString name = cbxTemplate->itemText(index);
388
389         if(name.startsWith("<") || name.endsWith(">"))
390         {
391                 QMessageBox::warning (this, tr("Invalid Item"), tr("Sorry, the selected item cannot be deleted!"));
392                 return;
393         }
394
395         OptionsModel::deleteTemplate(name);
396         OptionsModel *item = reinterpret_cast<OptionsModel*>(cbxTemplate->itemData(index).value<void*>());
397         cbxTemplate->removeItem(index);
398         X264_DELETE(item);
399 }
400
401 ///////////////////////////////////////////////////////////////////////////////
402 // Public functions
403 ///////////////////////////////////////////////////////////////////////////////
404
405 QString AddJobDialog::sourceFile(void)
406 {
407         return QDir::fromNativeSeparators(editSource->text());
408 }
409
410 QString AddJobDialog::outputFile(void)
411 {
412         return QDir::fromNativeSeparators(editOutput->text());
413 }
414
415 ///////////////////////////////////////////////////////////////////////////////
416 // Private functions
417 ///////////////////////////////////////////////////////////////////////////////
418
419 void AddJobDialog::loadTemplateList(void)
420 {
421         cbxTemplate->addItem(tr("<Default>"), QVariant::fromValue<void*>(m_defaults));
422         cbxTemplate->setCurrentIndex(0);
423
424         QMap<QString, OptionsModel*> templates = OptionsModel::loadAllTemplates();
425         QStringList templateNames = templates.keys();
426         templateNames.sort();
427
428         while(!templateNames.isEmpty())
429         {
430                 QString current = templateNames.takeFirst();
431                 cbxTemplate->addItem(current, QVariant::fromValue<void*>(templates.value(current)));
432
433                 if(templates.value(current)->equals(m_options))
434                 {
435                         cbxTemplate->setCurrentIndex(cbxTemplate->count() - 1);
436                 }
437         }
438
439         if((cbxTemplate->currentIndex() == 0) && (!m_options->equals(m_defaults)))
440         {
441                 cbxTemplate->insertItem(1, tr("<Recently Used>"), QVariant::fromValue<void*>(m_options));
442                 cbxTemplate->setCurrentIndex(1);
443         }
444 }
445
446 void AddJobDialog::updateComboBox(QComboBox *cbox, const QString &text)
447 {
448         for(int i = 0; i < cbox->model()->rowCount(); i++)
449         {
450                 if(cbox->model()->data(cbox->model()->index(i, 0, QModelIndex())).toString().compare(text, Qt::CaseInsensitive) == 0)
451                 {
452                         cbox->setCurrentIndex(i);
453                         break;
454                 }
455         }
456 }
457
458 void AddJobDialog::restoreOptions(OptionsModel *options)
459 {
460         cbxRateControlMode->blockSignals(true);
461         spinQuantizer->blockSignals(true);
462         spinBitrate->blockSignals(true);
463         cbxPreset->blockSignals(true);
464         cbxTuning->blockSignals(true);
465         cbxProfile->blockSignals(true);
466         editCustomParams->blockSignals(true);
467
468         cbxRateControlMode->setCurrentIndex(options->rcMode());
469         spinQuantizer->setValue(options->quantizer());
470         spinBitrate->setValue(options->bitrate());
471         updateComboBox(cbxPreset, options->preset());
472         updateComboBox(cbxTuning, options->tune());
473         updateComboBox(cbxProfile, options->profile());
474         editCustomParams->setText(options->custom());
475
476         cbxRateControlMode->blockSignals(false);
477         spinQuantizer->blockSignals(false);
478         spinBitrate->blockSignals(false);
479         cbxPreset->blockSignals(false);
480         cbxTuning->blockSignals(false);
481         cbxProfile->blockSignals(false);
482         editCustomParams->blockSignals(false);
483 }
484
485 void AddJobDialog::saveOptions(OptionsModel *options)
486 {
487         options->setRCMode(static_cast<OptionsModel::RCMode>(cbxRateControlMode->currentIndex()));
488         options->setQuantizer(spinQuantizer->value());
489         options->setBitrate(spinBitrate->value());
490         options->setPreset(cbxPreset->model()->data(cbxPreset->model()->index(cbxPreset->currentIndex(), 0)).toString());
491         options->setTune(cbxTuning->model()->data(cbxTuning->model()->index(cbxTuning->currentIndex(), 0)).toString());
492         options->setProfile(cbxProfile->model()->data(cbxProfile->model()->index(cbxProfile->currentIndex(), 0)).toString());
493         options->setCustom(editCustomParams->text().simplified());
494 }
495
496 QString AddJobDialog::makeFileFilter(void)
497 {
498         QString filters("All supported files (");
499
500         for(size_t index = 0; g_filters[index].name && g_filters[index].fext; index++)
501         {
502                 filters += QString((index > 0) ? " *.%1" : "*.%1").arg(QString::fromLatin1(g_filters[index].fext));
503         }
504
505         filters += QString(");;");
506
507         for(size_t index = 0; g_filters[index].name && g_filters[index].fext; index++)
508         {
509                 filters += QString("%1 (*.%2);;").arg(QString::fromLatin1(g_filters[index].name), QString::fromLatin1(g_filters[index].fext));
510         }
511                 
512         filters += QString("All files (*.*)");
513         return filters;
514 }