OSDN Git Service

Implemented VaporSynth input support.
[x264-launcher/x264-launcher.git] / src / win_addJob.cpp
1 ///////////////////////////////////////////////////////////////////////////////
2 // Simple x264 Launcher
3 // Copyright (C) 2004-2013 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 #include "model_recently.h"
27 #include "win_help.h"
28 #include "win_editor.h"
29
30 #include <QDate>
31 #include <QTimer>
32 #include <QCloseEvent>
33 #include <QMessageBox>
34 #include <QFileDialog>
35 #include <QDesktopServices>
36 #include <QValidator>
37 #include <QDir>
38 #include <QInputDialog>
39 #include <QSettings>
40 #include <QUrl>
41 #include <QAction>
42 #include <QClipboard>
43 #include <QToolTip>
44
45 #define ARRAY_SIZE(ARRAY) (sizeof((ARRAY))/sizeof((ARRAY[0])))
46 #define VALID_DIR(PATH) ((!(PATH).isEmpty()) && QFileInfo(PATH).exists() && QFileInfo(PATH).isDir())
47
48 #define REMOVE_USAFED_ITEM \
49 { \
50         for(int i = 0; i < cbxTemplate->count(); i++) \
51         { \
52                 OptionsModel* temp = reinterpret_cast<OptionsModel*>(cbxTemplate->itemData(i).value<void*>()); \
53                 if(temp == NULL) \
54                 { \
55                         cbxTemplate->blockSignals(true); \
56                         cbxTemplate->removeItem(i); \
57                         cbxTemplate->blockSignals(false); \
58                         break; \
59                 } \
60         } \
61 }
62
63 #define ADD_CONTEXTMENU_ACTION(WIDGET, ICON, TEXT, SLOTNAME) \
64 { \
65         QAction *_action = new QAction((ICON), (TEXT), this); \
66         _action->setData(QVariant::fromValue<void*>(WIDGET)); \
67         WIDGET->addAction(_action); \
68         connect(_action, SIGNAL(triggered(bool)), this, SLOT(SLOTNAME())); \
69 }
70
71 #define ADD_CONTEXTMENU_SEPARATOR(WIDGET) \
72 { \
73         QAction *_action = new QAction(this); \
74         _action->setSeparator(true); \
75         WIDGET->addAction(_action); \
76
77
78 ///////////////////////////////////////////////////////////////////////////////
79 // Validator
80 ///////////////////////////////////////////////////////////////////////////////
81
82 class StringValidator : public QValidator
83 {
84 public:
85         StringValidator(QLabel *notifier, QLabel *icon)
86         :
87                 m_notifier(notifier), m_icon(icon)
88         {
89                 m_notifier->hide();
90                 m_icon->hide();
91         }
92         
93         virtual State validate(QString &input, int &pos) const = 0;
94
95         virtual void fixup(QString &input) const
96         {
97                 input = input.simplified();
98         }
99
100 protected:
101         QLabel *const m_notifier, *const m_icon;
102
103         bool checkParam(const QString &input, const QString &param, const bool doubleMinus) const
104         {
105                 static const char c[20] = {' ', '*', '?', '<', '>', '/', '\\', '"', '\'', '!', '+', '#', '&', '%', '=', ',', ';', '.', 'ยด', '`'};
106                 const QString prefix = doubleMinus ? QLatin1String("--") : QLatin1String("-");
107                 
108                 bool flag = false;
109                 if(param.length() > 1)
110                 {
111                         flag = flag || input.endsWith(QString("%1%2").arg(prefix, param), Qt::CaseInsensitive);
112                         for(size_t i = 0; i < sizeof(c); i++)
113                         {
114                                 flag = flag || input.contains(QString("%1%2%3").arg(prefix, param, QChar::fromLatin1(c[i])), Qt::CaseInsensitive);
115                         }
116                 }
117                 else
118                 {
119                         flag = flag || input.startsWith(QString("-%1").arg(param));
120                         for(size_t i = 0; i < sizeof(c); i++)
121                         {
122                                 flag = flag || input.contains(QString("%1-%2").arg(QChar::fromLatin1(c[i]), param), Qt::CaseSensitive);
123                         }
124                 }
125                 if((flag) && (m_notifier))
126                 {
127                         m_notifier->setText(tr("Invalid parameter: %1").arg((param.length() > 1) ? QString("%1%2").arg(prefix, param) : QString("-%1").arg(param)));
128                 }
129                 return flag;
130         }
131
132         const bool &setStatus(const bool &flag, const QString &toolName) const
133         {
134                 if(flag)
135                 {
136                         if(m_notifier)
137                         {
138                                 if(m_notifier->isHidden()) m_notifier->show();
139                                 if(m_icon) { if(m_icon->isHidden()) m_icon->show(); }
140                                 if(QWidget *w = m_notifier->topLevelWidget()->focusWidget())
141                                 {
142                                         QToolTip::showText(static_cast<QWidget*>(w->parent())->mapToGlobal(w->pos()), QString("<nobr>%1</nobr>").arg(tr("<b>Warning:</b> You entered a parameter that is incomaptible with using %1 from a GUI.<br>Please note that the GUI will automatically set <i>this</i> parameter for you (if required).").arg(toolName)), m_notifier, QRect());
143                                 }
144                         }
145                 }
146                 else
147                 {
148                         if(m_notifier)
149                         {
150                                 if(m_notifier->isVisible()) m_notifier->hide();
151                                 if(m_icon) { if(m_icon->isVisible()) m_icon->hide(); }
152                                 QToolTip::hideText();
153                         }
154                 }
155                 return flag;
156         }
157 };
158
159 class StringValidatorX264 : public StringValidator
160 {
161 public:
162         StringValidatorX264(QLabel *notifier, QLabel *icon) : StringValidator(notifier, icon) {}
163
164         virtual State validate(QString &input, int &pos) const
165         {
166                 static const char* p[] = {"B", "o", "h", "p", "q", /*"fps", "frames",*/ "preset", "tune", "profile",
167                         "stdin", "crf", "bitrate", "qp", "pass", "stats", "output", "help","quiet", NULL};
168
169                 bool invalid = false;
170
171                 for(size_t i = 0; p[i] && (!invalid); i++)
172                 {
173                         invalid = invalid || checkParam(input, QString::fromLatin1(p[i]), true);
174                 }
175
176                 return setStatus(invalid, "x264") ? QValidator::Intermediate : QValidator::Acceptable;
177         }
178 };
179
180 class StringValidatorAvs2YUV : public StringValidator
181 {
182 public:
183         StringValidatorAvs2YUV(QLabel *notifier, QLabel *icon) : StringValidator(notifier, icon) {}
184
185         virtual State validate(QString &input, int &pos) const
186         {
187                 static const char* p[] = {"o", "frames", "seek", "raw", "hfyu", "slave", NULL};
188
189                 bool invalid = false;
190
191                 for(size_t i = 0; p[i] && (!invalid); i++)
192                 {
193                         invalid = invalid || checkParam(input, QString::fromLatin1(p[i]), false);
194                 }
195                 
196                 return setStatus(invalid, "Avs2YUV") ? QValidator::Intermediate : QValidator::Acceptable;
197         }
198 };
199
200 ///////////////////////////////////////////////////////////////////////////////
201 // Constructor & Destructor
202 ///////////////////////////////////////////////////////////////////////////////
203
204 AddJobDialog::AddJobDialog(QWidget *parent, OptionsModel *options, RecentlyUsed *recentlyUsed, bool x64supported, bool use10BitEncoding, bool saveToSourceFolder)
205 :
206         QDialog(parent),
207         m_defaults(new OptionsModel()),
208         m_options(options),
209         m_x64supported(x64supported),
210         m_use10BitEncoding(use10BitEncoding),
211         m_saveToSourceFolder(saveToSourceFolder),
212         m_recentlyUsed(recentlyUsed)
213 {
214         //Init the dialog, from the .ui file
215         setupUi(this);
216         setWindowFlags(windowFlags() & (~Qt::WindowContextHelpButtonHint));
217         
218         //Fix dialog size
219         buttonSaveTemplate->setMaximumHeight(20);
220         buttonDeleteTemplate->setMaximumHeight(20);
221         resize(width(), minimumHeight());
222         setMinimumSize(size());
223         setMaximumHeight(height());
224
225         //Hide optional controls
226         checkBoxApplyToAll->setVisible(false);
227
228         //Monitor RC mode combobox
229         connect(cbxRateControlMode, SIGNAL(currentIndexChanged(int)), this, SLOT(modeIndexChanged(int)));
230
231         //Activate buttons
232         connect(buttonBrowseSource, SIGNAL(clicked()), this, SLOT(browseButtonClicked()));
233         connect(buttonBrowseOutput, SIGNAL(clicked()), this, SLOT(browseButtonClicked()));
234         connect(buttonSaveTemplate, SIGNAL(clicked()), this, SLOT(saveTemplateButtonClicked()));
235         connect(buttonDeleteTemplate, SIGNAL(clicked()), this, SLOT(deleteTemplateButtonClicked()));
236
237         //Setup validator
238         editCustomX264Params->installEventFilter(this);
239         editCustomX264Params->setValidator(new StringValidatorX264(labelNotificationX264, iconNotificationX264));
240         editCustomX264Params->clear();
241         editCustomAvs2YUVParams->installEventFilter(this);
242         editCustomAvs2YUVParams->setValidator(new StringValidatorAvs2YUV(labelNotificationAvs2YUV, iconNotificationAvs2YUV));
243         editCustomAvs2YUVParams->clear();
244
245         //Install event filter
246         labelHelpScreenX264->installEventFilter(this);
247         labelHelpScreenAvs2YUV->installEventFilter(this);
248
249         //Monitor for options changes
250         connect(cbxRateControlMode, SIGNAL(currentIndexChanged(int)), this, SLOT(configurationChanged()));
251         connect(spinQuantizer, SIGNAL(valueChanged(double)), this, SLOT(configurationChanged()));
252         connect(spinBitrate, SIGNAL(valueChanged(int)), this, SLOT(configurationChanged()));
253         connect(cbxPreset, SIGNAL(currentIndexChanged(int)), this, SLOT(configurationChanged()));
254         connect(cbxTuning, SIGNAL(currentIndexChanged(int)), this, SLOT(configurationChanged()));
255         connect(cbxProfile, SIGNAL(currentIndexChanged(int)), this, SLOT(configurationChanged()));
256         connect(editCustomX264Params, SIGNAL(textChanged(QString)), this, SLOT(configurationChanged()));
257         connect(editCustomAvs2YUVParams, SIGNAL(textChanged(QString)), this, SLOT(configurationChanged()));
258
259         //Create context menus
260         ADD_CONTEXTMENU_ACTION(editCustomX264Params, QIcon(":/buttons/page_edit.png"), tr("Open the Text-Editor"), editorActionTriggered);
261         ADD_CONTEXTMENU_ACTION(editCustomAvs2YUVParams, QIcon(":/buttons/page_edit.png"), tr("Open the Text-Editor"), editorActionTriggered);
262         ADD_CONTEXTMENU_SEPARATOR(editCustomX264Params);
263         ADD_CONTEXTMENU_SEPARATOR(editCustomAvs2YUVParams);
264         ADD_CONTEXTMENU_ACTION(editCustomX264Params, QIcon(":/buttons/page_copy.png"), tr("Copy to Clipboard"), copyActionTriggered);
265         ADD_CONTEXTMENU_ACTION(editCustomAvs2YUVParams, QIcon(":/buttons/page_copy.png"), tr("Copy to Clipboard"), copyActionTriggered);
266         ADD_CONTEXTMENU_ACTION(editCustomX264Params, QIcon(":/buttons/page_paste.png"), tr("Paste from Clipboard"), pasteActionTriggered);
267         ADD_CONTEXTMENU_ACTION(editCustomAvs2YUVParams, QIcon(":/buttons/page_paste.png"), tr("Paste from Clipboard"), pasteActionTriggered);
268
269         //Setup template selector
270         loadTemplateList();
271         connect(cbxTemplate, SIGNAL(currentIndexChanged(int)), this, SLOT(templateSelected()));
272 }
273
274 AddJobDialog::~AddJobDialog(void)
275 {
276         //Free templates
277         for(int i = 0; i < cbxTemplate->model()->rowCount(); i++)
278         {
279                 if(cbxTemplate->itemText(i).startsWith("<") || cbxTemplate->itemText(i).endsWith(">"))
280                 {
281                         continue;
282                 }
283                 OptionsModel *item = reinterpret_cast<OptionsModel*>(cbxTemplate->itemData(i).value<void*>());
284                 cbxTemplate->setItemData(i, QVariant::fromValue<void*>(NULL));
285                 X264_DELETE(item);
286         }
287
288         //Free validators
289         if(const QValidator *tmp = editCustomX264Params->validator())
290         {
291                 editCustomX264Params->setValidator(NULL);
292                 X264_DELETE(tmp);
293         }
294         if(const QValidator *tmp = editCustomAvs2YUVParams->validator())
295         {
296                 editCustomAvs2YUVParams->setValidator(NULL);
297                 X264_DELETE(tmp);
298         }
299
300         X264_DELETE(m_defaults);
301 }
302
303 ///////////////////////////////////////////////////////////////////////////////
304 // Events
305 ///////////////////////////////////////////////////////////////////////////////
306
307 void AddJobDialog::showEvent(QShowEvent *event)
308 {
309         QDialog::showEvent(event);
310         templateSelected();
311
312         if((!editSource->text().isEmpty()) && editOutput->text().isEmpty())
313         {
314                 QString outPath = generateOutputFileName(QDir::fromNativeSeparators(editSource->text()), m_recentlyUsed->outputDirectory(), m_recentlyUsed->filterIndex(), m_saveToSourceFolder);
315                 editOutput->setText(QDir::toNativeSeparators(outPath));
316                 buttonAccept->setFocus();
317         }
318
319         labelNotificationX264->hide();
320         iconNotificationX264->hide();
321         labelNotificationAvs2YUV->hide();
322         iconNotificationAvs2YUV->hide();
323
324         //Enable drag&drop support for this window, required for Qt v4.8.4+
325         setAcceptDrops(true);
326 }
327
328 bool AddJobDialog::eventFilter(QObject *o, QEvent *e)
329 {
330         if((o == labelHelpScreenX264) && (e->type() == QEvent::MouseButtonPress))
331         {
332                 HelpDialog *helpScreen = new HelpDialog(this, false, m_x64supported, m_use10BitEncoding);
333                 helpScreen->exec();
334                 X264_DELETE(helpScreen);
335         }
336         else if((o == labelHelpScreenAvs2YUV) && (e->type() == QEvent::MouseButtonPress))
337         {
338                 HelpDialog *helpScreen = new HelpDialog(this, true, m_x64supported, m_use10BitEncoding);
339                 helpScreen->exec();
340                 X264_DELETE(helpScreen);
341         }
342         else if((o == editCustomX264Params) && (e->type() == QEvent::FocusOut))
343         {
344                 editCustomX264Params->setText(editCustomX264Params->text().simplified());
345         }
346         else if((o == editCustomAvs2YUVParams) && (e->type() == QEvent::FocusOut))
347         {
348                 editCustomAvs2YUVParams->setText(editCustomAvs2YUVParams->text().simplified());
349         }
350         return false;
351 }
352
353 void AddJobDialog::dragEnterEvent(QDragEnterEvent *event)
354 {
355         bool accept[2] = {false, false};
356
357         foreach(const QString &fmt, event->mimeData()->formats())
358         {
359                 accept[0] = accept[0] || fmt.contains("text/uri-list", Qt::CaseInsensitive);
360                 accept[1] = accept[1] || fmt.contains("FileNameW", Qt::CaseInsensitive);
361         }
362
363         if(accept[0] && accept[1])
364         {
365                 event->acceptProposedAction();
366         }
367 }
368
369 void AddJobDialog::dropEvent(QDropEvent *event)
370 {
371         QString droppedFile;
372         QList<QUrl> urls = event->mimeData()->urls();
373
374         if(urls.count() > 1)
375         {
376                 QDragEnterEvent dragEvent(event->pos(), event->proposedAction(), event->mimeData(), Qt::NoButton, Qt::NoModifier);
377                 if(qApp->notify(parent(), &dragEvent))
378                 {
379                         qApp->notify(parent(), event);
380                         reject(); return;
381                 }
382         }
383
384         while((!urls.isEmpty()) && droppedFile.isEmpty())
385         {
386                 QUrl currentUrl = urls.takeFirst();
387                 QFileInfo file(currentUrl.toLocalFile());
388                 if(file.exists() && file.isFile())
389                 {
390                         qDebug("AddJobDialog::dropEvent: %s", file.canonicalFilePath().toUtf8().constData());
391                         droppedFile = file.canonicalFilePath();
392                 }
393         }
394         
395         if(!droppedFile.isEmpty())
396         {
397                 const QString outFileName = generateOutputFileName(droppedFile, currentOutputPath(), currentOutputIndx(), m_saveToSourceFolder);
398                 editSource->setText(QDir::toNativeSeparators(droppedFile));
399                 editOutput->setText(QDir::toNativeSeparators(outFileName));
400         }
401 }
402
403 ///////////////////////////////////////////////////////////////////////////////
404 // Slots
405 ///////////////////////////////////////////////////////////////////////////////
406
407 void AddJobDialog::modeIndexChanged(int index)
408 {
409         spinQuantizer->setEnabled(index == 0 || index == 1);
410         spinBitrate->setEnabled(index == 2 || index == 3);
411 }
412
413 void AddJobDialog::accept(void)
414 {
415         //Selection complete?
416         if(editSource->text().trimmed().isEmpty())
417         {
418                 QMessageBox::warning(this, tr("Not Found!"), tr("Please select a valid source file first!"));
419                 return;
420         }
421         if(editOutput->text().trimmed().isEmpty())
422         {
423                 QMessageBox::warning(this, tr("Not Selected!"), tr("<nobr>Please select a valid output file first!</nobr>"));
424                 return;
425         }
426
427         //Does source exist?
428         QFileInfo sourceFile = QFileInfo(this->sourceFile());
429         if(!(sourceFile.exists() && sourceFile.isFile()))
430         {
431                 QMessageBox::warning(this, tr("Not Found!"), tr("<nobr>The selected source file could not be found!</nobr>"));
432                 return;
433         }
434
435         //Does output file already exist?
436         QFileInfo outputFile = QFileInfo(this->outputFile());
437         if(outputFile.exists() && outputFile.isFile())
438         {
439                 int ret = QMessageBox::question(this, tr("Already Exists!"), tr("<nobr>Output file already exists! Overwrite?</nobr>"), QMessageBox::Yes | QMessageBox::No, QMessageBox::No);
440                 if(ret != QMessageBox::Yes) return;
441         }
442         if(outputFile.exists() && (!outputFile.isFile()))
443         {
444                 QMessageBox::warning(this, tr("Not a File!"), tr("<nobr>Selected output file does not appear to be a valid file!</nobr>"));
445                 return;
446         }
447
448         //Is destination dir writable?
449         QFileInfo outputDir = QFileInfo(outputFile.absolutePath());
450         if(!(outputDir.exists() && outputDir.isDir() && outputDir.isWritable()))
451         {
452                 QMessageBox::warning(this, tr("Not Writable!"), tr("<nobr>Output directory does not exist or is not writable!</nobr>"));
453                 return;
454         }
455
456         //Custom parameters okay?
457         if(!editCustomX264Params->hasAcceptableInput())
458         {
459                 int ret = QMessageBox::warning(this, tr("Invalid Params"), tr("<nobr>Your custom parameters are invalid and will be discarded!</nobr>"), QMessageBox::Ignore | QMessageBox::Cancel, QMessageBox::Cancel);
460                 if(ret != QMessageBox::Ignore) return;
461         }
462
463         //Update recently used
464         m_recentlyUsed->setFilterIndex(currentOutputIndx());
465         m_recentlyUsed->setSourceDirectory(currentSourcePath());
466         m_recentlyUsed->setOutputDirectory(currentOutputPath());
467         RecentlyUsed::saveRecentlyUsed(m_recentlyUsed);
468
469         //Save options
470         saveOptions(m_options);
471         QDialog::accept();
472 }
473
474 void AddJobDialog::browseButtonClicked(void)
475 {
476         if(QObject::sender() == buttonBrowseSource)
477         {
478                 QString filePath = QFileDialog::getOpenFileName(this, tr("Open Source File"), currentSourcePath(true), getInputFilterLst(), NULL, QFileDialog::DontUseNativeDialog);
479                 if(!(filePath.isNull() || filePath.isEmpty()))
480                 {
481                         QString destFile = generateOutputFileName(filePath, currentOutputPath(), currentOutputIndx(), m_saveToSourceFolder);
482                         editSource->setText(QDir::toNativeSeparators(filePath));
483                         editOutput->setText(QDir::toNativeSeparators(destFile));
484                 }
485         }
486         else if(QObject::sender() == buttonBrowseOutput)
487         {
488                 QString selectedType = getFilterStr(currentOutputIndx());
489                 QString filePath = QFileDialog::getSaveFileName(this, tr("Choose Output File"), currentOutputPath(true), getFilterLst(), &selectedType, QFileDialog::DontUseNativeDialog | QFileDialog::DontConfirmOverwrite);
490
491                 if(!(filePath.isNull() || filePath.isEmpty()))
492                 {
493                         if(getFilterIdx(QFileInfo(filePath).suffix()) < 0)
494                         {
495                                 int tempIndex = -1;
496                                 QRegExp regExp("\\(\\*\\.(\\w+)\\)");
497                                 if(regExp.lastIndexIn(selectedType) >= 0)
498                                 {
499                                         tempIndex = getFilterIdx(regExp.cap(1));
500                                 }
501                                 if(tempIndex < 0)
502                                 {
503                                         tempIndex = m_recentlyUsed->filterIndex();
504                                 }
505                                 filePath = QString("%1.%2").arg(filePath, getFilterExt(tempIndex));
506                         }
507                         editOutput->setText(QDir::toNativeSeparators(filePath));
508                 }
509         }
510 }
511
512 void AddJobDialog::configurationChanged(void)
513 {
514         OptionsModel* options = reinterpret_cast<OptionsModel*>(cbxTemplate->itemData(cbxTemplate->currentIndex()).value<void*>());
515         if(options)
516         {
517                 cbxTemplate->blockSignals(true);
518                 cbxTemplate->insertItem(0, tr("<Unsaved Configuration>"), QVariant::fromValue<void*>(NULL));
519                 cbxTemplate->setCurrentIndex(0);
520                 cbxTemplate->blockSignals(false);
521         }
522 }
523
524 void AddJobDialog::templateSelected(void)
525 {
526         OptionsModel* options = reinterpret_cast<OptionsModel*>(cbxTemplate->itemData(cbxTemplate->currentIndex()).value<void*>());
527         if(options)
528         {
529                 qDebug("Loading options!");
530                 REMOVE_USAFED_ITEM;
531                 restoreOptions(options);
532         }
533
534         modeIndexChanged(cbxRateControlMode->currentIndex());
535 }
536
537 void AddJobDialog::saveTemplateButtonClicked(void)
538 {
539         qDebug("Saving template");
540         QString name = tr("New Template");
541         int n = 2;
542
543         while(OptionsModel::templateExists(name))
544         {
545                 name = tr("New Template (%1)").arg(QString::number(n++));
546         }
547
548         OptionsModel *options = new OptionsModel();
549         saveOptions(options);
550
551         if(options->equals(m_defaults))
552         {
553                 QMessageBox::warning (this, tr("Oups"), tr("<nobr>It makes no sense to save the default settings!</nobr>"));
554                 cbxTemplate->blockSignals(true);
555                 cbxTemplate->setCurrentIndex(0);
556                 cbxTemplate->blockSignals(false);
557                 REMOVE_USAFED_ITEM;
558                 X264_DELETE(options);
559                 return;
560         }
561
562         for(int i = 0; i < cbxTemplate->count(); i++)
563         {
564                 const QString tempName = cbxTemplate->itemText(i);
565                 if(tempName.contains('<') || tempName.contains('>'))
566                 {
567                         continue;
568                 }
569                 OptionsModel* test = reinterpret_cast<OptionsModel*>(cbxTemplate->itemData(i).value<void*>());
570                 if(test != NULL)
571                 {
572                         if(options->equals(test))
573                         {
574                                 QMessageBox::warning (this, tr("Oups"), tr("<nobr>There already is a template for the current settings!</nobr>"));
575                                 cbxTemplate->blockSignals(true);
576                                 cbxTemplate->setCurrentIndex(i);
577                                 cbxTemplate->blockSignals(false);
578                                 REMOVE_USAFED_ITEM;
579                                 X264_DELETE(options);
580                                 return;
581                         }
582                 }
583         }
584
585         forever
586         {
587                 bool ok = false;
588                 name = QInputDialog::getText(this, tr("Save Template"), tr("Please enter the name of the template:").leftJustified(144, ' '), QLineEdit::Normal, name, &ok).simplified();
589                 if(!ok)
590                 {
591                         X264_DELETE(options);
592                         return;
593                 }
594                 if(name.contains('<') || name.contains('>') || name.contains('\\') || name.contains('/') || name.contains('"'))
595                 {
596                         QMessageBox::warning (this, tr("Invalid Name"), tr("<nobr>Sorry, the name you have entered is invalid!</nobr>"));
597                         while(name.contains('<')) name.remove('<');
598                         while(name.contains('>')) name.remove('>');
599                         while(name.contains('\\')) name.remove('\\');
600                         while(name.contains('/')) name.remove('/');
601                         while(name.contains('"')) name.remove('"');
602                         name = name.simplified();
603                         continue;
604                 }
605                 if(OptionsModel::templateExists(name))
606                 {
607                         int ret = QMessageBox::warning (this, tr("Already Exists"), tr("<nobr>A template of that name already exists! Overwrite?</nobr>"), QMessageBox::Yes | QMessageBox::No, QMessageBox::No);
608                         if(ret != QMessageBox::Yes)
609                         {
610                                 continue;
611                         }
612                 }
613                 break;
614         }
615         
616         if(!OptionsModel::saveTemplate(options, name))
617         {
618                 QMessageBox::critical(this, tr("Save Failed"), tr("Sorry, the template could not be saved!"));
619                 X264_DELETE(options);
620                 return;
621         }
622         
623         int index = cbxTemplate->model()->rowCount();
624         cbxTemplate->blockSignals(true);
625         for(int i = 0; i < cbxTemplate->count(); i++)
626         {
627                 if(cbxTemplate->itemText(i).compare(name, Qt::CaseInsensitive) == 0)
628                 {
629                         index = -1; //Do not append new template
630                         OptionsModel *oldItem = reinterpret_cast<OptionsModel*>(cbxTemplate->itemData(i).value<void*>());
631                         cbxTemplate->setItemData(i, QVariant::fromValue<void*>(options));
632                         cbxTemplate->setCurrentIndex(i);
633                         X264_DELETE(oldItem);
634                 }
635         }
636         if(index >= 0)
637         {
638                 cbxTemplate->insertItem(index, name, QVariant::fromValue<void*>(options));
639                 cbxTemplate->setCurrentIndex(index);
640         }
641         cbxTemplate->blockSignals(false);
642
643         REMOVE_USAFED_ITEM;
644 }
645
646 void AddJobDialog::deleteTemplateButtonClicked(void)
647 {
648         const int index = cbxTemplate->currentIndex();
649         QString name = cbxTemplate->itemText(index);
650
651         if(name.contains('<') || name.contains('>') || name.contains('\\') || name.contains('/'))
652         {
653                 QMessageBox::warning (this, tr("Invalid Item"), tr("Sorry, the selected item cannot be deleted!"));
654                 return;
655         }
656
657         int ret = QMessageBox::question (this, tr("Delete Template"), tr("<nobr>Do you really want to delete the selected template?</nobr>"), QMessageBox::Yes | QMessageBox::No, QMessageBox::No);
658         if(ret != QMessageBox::Yes)
659         {
660                 return;
661         }
662
663
664         OptionsModel::deleteTemplate(name);
665         OptionsModel *item = reinterpret_cast<OptionsModel*>(cbxTemplate->itemData(index).value<void*>());
666         cbxTemplate->removeItem(index);
667         X264_DELETE(item);
668 }
669
670 void AddJobDialog::editorActionTriggered(void)
671 {
672
673         if(QAction *action = dynamic_cast<QAction*>(QObject::sender()))
674         {
675                 QLineEdit *lineEdit = reinterpret_cast<QLineEdit*>(action->data().value<void*>());
676                 
677                 EditorDialog *editor = new EditorDialog(this);
678                 editor->setEditText(lineEdit->text());
679
680                 if(editor->exec() == QDialog::Accepted)
681                 {
682                         lineEdit->setText(editor->getEditText());
683                 }
684
685                 X264_DELETE(editor);
686         }
687 }
688
689 void AddJobDialog::copyActionTriggered(void)
690 {
691         if(QAction *action = dynamic_cast<QAction*>(QObject::sender()))
692         {
693                 QClipboard *clipboard = QApplication::clipboard();
694                 QLineEdit *lineEdit = reinterpret_cast<QLineEdit*>(action->data().value<void*>());
695                 QString text = lineEdit->hasSelectedText() ? lineEdit->selectedText() : lineEdit->text();
696                 clipboard->setText(text);
697         }
698 }
699
700 void AddJobDialog::pasteActionTriggered(void)
701 {
702         if(QAction *action = dynamic_cast<QAction*>(QObject::sender()))
703         {
704                 QClipboard *clipboard = QApplication::clipboard();
705                 QLineEdit *lineEdit = reinterpret_cast<QLineEdit*>(action->data().value<void*>());
706                 QString text = clipboard->text();
707                 if(!text.isEmpty()) lineEdit->setText(text);
708         }
709 }
710
711 ///////////////////////////////////////////////////////////////////////////////
712 // Public functions
713 ///////////////////////////////////////////////////////////////////////////////
714
715 QString AddJobDialog::sourceFile(void)
716 {
717         return QDir::fromNativeSeparators(editSource->text());
718 }
719
720 QString AddJobDialog::outputFile(void)
721 {
722         return QDir::fromNativeSeparators(editOutput->text());
723 }
724
725 ///////////////////////////////////////////////////////////////////////////////
726 // Private functions
727 ///////////////////////////////////////////////////////////////////////////////
728
729 void AddJobDialog::loadTemplateList(void)
730 {
731         cbxTemplate->addItem(tr("<Default>"), QVariant::fromValue<void*>(m_defaults));
732         cbxTemplate->setCurrentIndex(0);
733
734         QMap<QString, OptionsModel*> templates = OptionsModel::loadAllTemplates();
735         QStringList templateNames = templates.keys();
736         templateNames.sort();
737
738         while(!templateNames.isEmpty())
739         {
740                 QString current = templateNames.takeFirst();
741                 cbxTemplate->addItem(current, QVariant::fromValue<void*>(templates.value(current)));
742
743                 if(templates.value(current)->equals(m_options))
744                 {
745                         cbxTemplate->setCurrentIndex(cbxTemplate->count() - 1);
746                 }
747         }
748
749         if((cbxTemplate->currentIndex() == 0) && (!m_options->equals(m_defaults)))
750         {
751                 cbxTemplate->insertItem(1, tr("<Recently Used>"), QVariant::fromValue<void*>(m_options));
752                 cbxTemplate->setCurrentIndex(1);
753         }
754 }
755
756 void AddJobDialog::updateComboBox(QComboBox *cbox, const QString &text)
757 {
758         for(int i = 0; i < cbox->model()->rowCount(); i++)
759         {
760                 if(cbox->model()->data(cbox->model()->index(i, 0, QModelIndex())).toString().compare(text, Qt::CaseInsensitive) == 0)
761                 {
762                         cbox->setCurrentIndex(i);
763                         break;
764                 }
765         }
766 }
767
768 void AddJobDialog::restoreOptions(OptionsModel *options)
769 {
770         cbxRateControlMode->blockSignals(true);
771         spinQuantizer->blockSignals(true);
772         spinBitrate->blockSignals(true);
773         cbxPreset->blockSignals(true);
774         cbxTuning->blockSignals(true);
775         cbxProfile->blockSignals(true);
776         editCustomX264Params->blockSignals(true);
777         editCustomAvs2YUVParams->blockSignals(true);
778
779         cbxRateControlMode->setCurrentIndex(options->rcMode());
780         spinQuantizer->setValue(options->quantizer());
781         spinBitrate->setValue(options->bitrate());
782         updateComboBox(cbxPreset, options->preset());
783         updateComboBox(cbxTuning, options->tune());
784         updateComboBox(cbxProfile, options->profile());
785         editCustomX264Params->setText(options->customX264());
786         editCustomAvs2YUVParams->setText(options->customAvs2YUV());
787
788         cbxRateControlMode->blockSignals(false);
789         spinQuantizer->blockSignals(false);
790         spinBitrate->blockSignals(false);
791         cbxPreset->blockSignals(false);
792         cbxTuning->blockSignals(false);
793         cbxProfile->blockSignals(false);
794         editCustomX264Params->blockSignals(false);
795         editCustomAvs2YUVParams->blockSignals(false);
796 }
797
798 void AddJobDialog::saveOptions(OptionsModel *options)
799 {
800         options->setRCMode(static_cast<OptionsModel::RCMode>(cbxRateControlMode->currentIndex()));
801         options->setQuantizer(spinQuantizer->value());
802         options->setBitrate(spinBitrate->value());
803         options->setPreset(cbxPreset->model()->data(cbxPreset->model()->index(cbxPreset->currentIndex(), 0)).toString());
804         options->setTune(cbxTuning->model()->data(cbxTuning->model()->index(cbxTuning->currentIndex(), 0)).toString());
805         options->setProfile(cbxProfile->model()->data(cbxProfile->model()->index(cbxProfile->currentIndex(), 0)).toString());
806         options->setCustomX264(editCustomX264Params->hasAcceptableInput() ? editCustomX264Params->text().simplified() : QString());
807         options->setCustomAvs2YUV(editCustomAvs2YUVParams->hasAcceptableInput() ? editCustomAvs2YUVParams->text().simplified() : QString());
808 }
809
810 QString AddJobDialog::currentSourcePath(const bool bWithName)
811 {
812         QString path = m_recentlyUsed->sourceDirectory();
813         QString currentSourceFile = this->sourceFile();
814         
815         if(!currentSourceFile.isEmpty())
816         {
817                 QString currentSourceDir = QFileInfo(currentSourceFile).absolutePath();
818                 if(VALID_DIR(currentSourceDir))
819                 {
820                         path = currentSourceDir;
821                 }
822                 if(bWithName)
823                 {
824                         path.append("/").append(QFileInfo(currentSourceFile).fileName());
825                 }
826         }
827
828         return path;
829 }
830
831 QString AddJobDialog::currentOutputPath(const bool bWithName)
832 {
833         QString path = m_recentlyUsed->outputDirectory();
834         QString currentOutputFile = this->outputFile();
835         
836         if(!currentOutputFile.isEmpty())
837         {
838                 QString currentOutputDir = QFileInfo(currentOutputFile).absolutePath();
839                 if(VALID_DIR(currentOutputDir))
840                 {
841                         path = currentOutputDir;
842                 }
843                 if(bWithName)
844                 {
845                         path.append("/").append(QFileInfo(currentOutputFile).fileName());
846                 }
847         }
848
849         return path;
850 }
851
852 int AddJobDialog::currentOutputIndx(void)
853 {
854         int index = m_recentlyUsed->filterIndex();
855         QString currentOutputFile = this->outputFile();
856         
857         if(!currentOutputFile.isEmpty())
858         {
859                 const QString currentOutputExtn = QFileInfo(currentOutputFile).suffix();
860                 const int tempIndex = getFilterIdx(currentOutputExtn);
861                 if(tempIndex >= 0)
862                 {
863                         index = tempIndex;
864                 }
865         }
866
867         return index;
868 }
869
870 ///////////////////////////////////////////////////////////////////////////////
871 // Static functions
872 ///////////////////////////////////////////////////////////////////////////////
873
874 QString AddJobDialog::generateOutputFileName(const QString &sourceFilePath, const QString &destinationDirectory, const int filterIndex, const bool saveToSourceDir)
875 {
876         QString name = QFileInfo(sourceFilePath).completeBaseName();
877         QString path = saveToSourceDir ? QFileInfo(sourceFilePath).canonicalPath() : destinationDirectory;
878         QString fext = getFilterExt(filterIndex);
879         
880         if(!VALID_DIR(path))
881         {
882                 RecentlyUsed defaults;
883                 path = defaults.outputDirectory();
884         }
885
886         QString outPath = QString("%1/%2.%3").arg(path, name, fext);
887
888         int n = 2;
889         while(QFileInfo(outPath).exists())
890         {
891                 outPath = QString("%1/%2 (%3).%4").arg(path, name, QString::number(n++), fext);
892         }
893
894         return outPath;
895 }
896
897 /* ------------------------------------------------------------------------- */
898
899 QString AddJobDialog::getFilterExt(const int filterIndex)
900 {
901         const int count = ARRAY_SIZE(X264_FILE_TYPE_FILTERS);
902
903         if((filterIndex >= 0) && (filterIndex < count))
904         {
905                 return QString::fromLatin1(X264_FILE_TYPE_FILTERS[filterIndex].pcExt);
906         }
907
908         return QString::fromLatin1(X264_FILE_TYPE_FILTERS[0].pcExt);
909 }
910
911 int AddJobDialog::getFilterIdx(const QString &fileExt)
912 {
913         const int count = ARRAY_SIZE(X264_FILE_TYPE_FILTERS);
914
915         for(int i = 0; i < count; i++)
916         {
917                 if(fileExt.compare(QString::fromLatin1(X264_FILE_TYPE_FILTERS[i].pcExt), Qt::CaseInsensitive) == 0)
918                 {
919                         return i;
920                 }
921         }
922
923         return -1;
924 }
925
926 QString AddJobDialog::getFilterStr(const int filterIndex)
927 {
928         const int count = ARRAY_SIZE(X264_FILE_TYPE_FILTERS);
929
930         if((filterIndex >= 0) && (filterIndex < count))
931         {
932                 return QString("%1 (*.%2)").arg(QString::fromLatin1(X264_FILE_TYPE_FILTERS[filterIndex].pcStr), QString::fromLatin1(X264_FILE_TYPE_FILTERS[filterIndex].pcExt));
933         }
934
935         return QString("%1 (*.%2)").arg(QString::fromLatin1(X264_FILE_TYPE_FILTERS[0].pcStr), QString::fromLatin1(X264_FILE_TYPE_FILTERS[0].pcExt));
936 }
937
938 QString AddJobDialog::getFilterLst(void)
939 {
940         QStringList filters;
941         const int count = ARRAY_SIZE(X264_FILE_TYPE_FILTERS);
942         
943         for(int i = 0; i < count; i++)
944         {
945                 filters << QString("%1 (*.%2)").arg(QString::fromLatin1(X264_FILE_TYPE_FILTERS[i].pcStr), QString::fromLatin1(X264_FILE_TYPE_FILTERS[i].pcExt));
946         }
947
948         return filters.join(";;");
949 }
950
951 QString AddJobDialog::getInputFilterLst(void)
952 {
953         static const struct
954         {
955                 const char *name;
956                 const char *fext;
957         }
958         s_filters[] =
959         {
960                 {"Avisynth Scripts", "avs"},
961                 {"VapourSynth Scripts", "vpy"},
962                 {"Matroska Files", "mkv"},
963                 {"MPEG-4 Part 14 Container", "mp4"},
964                 {"Audio Video Interleaved", "avi"},
965                 {"Flash Video", "flv"},
966                 {"YUV4MPEG2 Stream", "y4m"},
967                 {"Uncompresses YUV Data", "yuv"},
968         };
969
970         const int count = ARRAY_SIZE(s_filters);
971
972         QString allTypes;
973         for(size_t index = 0; index < count; index++)
974         {
975
976                 allTypes += QString((index > 0) ? " *.%1" : "*.%1").arg(QString::fromLatin1(s_filters[index].fext));
977         }
978         
979         QStringList filters;
980         filters << QString("All supported files (%1)").arg(allTypes);
981
982         for(size_t index = 0; index < count; index++)
983         {
984                 filters << QString("%1 (*.%2)").arg(QString::fromLatin1(s_filters[index].name), QString::fromLatin1(s_filters[index].fext));
985         }
986                 
987         filters << QString("All files (*.*)");
988         return filters.join(";;");
989 }