OSDN Git Service

Actually create the EncodeThread instance.
[x264-launcher/x264-launcher.git] / src / win_main.cpp
1 ///////////////////////////////////////////////////////////////////////////////
2 // Simple x264 Launcher
3 // Copyright (C) 2004-2014 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_main.h"
23 #include "uic_win_main.h"
24
25 #include "global.h"
26 #include "cli.h"
27 #include "ipc.h"
28 #include "model_status.h"
29 #include "model_sysinfo.h"
30 #include "model_jobList.h"
31 #include "model_options.h"
32 #include "model_preferences.h"
33 #include "model_recently.h"
34 #include "thread_avisynth.h"
35 #include "thread_vapoursynth.h"
36 #include "thread_encode.h"
37 #include "taskbar7.h"
38 #include "win_addJob.h"
39 #include "win_preferences.h"
40 #include "win_updater.h"
41 #include "resource.h"
42
43 #include <QDate>
44 #include <QTimer>
45 #include <QCloseEvent>
46 #include <QMessageBox>
47 #include <QDesktopServices>
48 #include <QUrl>
49 #include <QDir>
50 #include <QLibrary>
51 #include <QProcess>
52 #include <QProgressDialog>
53 #include <QScrollBar>
54 #include <QTextStream>
55 #include <QSettings>
56 #include <QFileDialog>
57
58 #include <ctime>
59
60 const char *home_url = "http://muldersoft.com/";
61 const char *update_url = "https://github.com/lordmulder/Simple-x264-Launcher/releases/latest";
62 const char *tpl_last = "<LAST_USED>";
63
64 #define SET_FONT_BOLD(WIDGET,BOLD) do { QFont _font = WIDGET->font(); _font.setBold(BOLD); WIDGET->setFont(_font); } while(0)
65 #define SET_TEXT_COLOR(WIDGET,COLOR) do { QPalette _palette = WIDGET->palette(); _palette.setColor(QPalette::WindowText, (COLOR)); _palette.setColor(QPalette::Text, (COLOR)); WIDGET->setPalette(_palette); } while(0)
66 #define LINK(URL) "<a href=\"" URL "\">" URL "</a>"
67 #define INIT_ERROR_EXIT() do { m_status = STATUS_EXITTING; close(); qApp->exit(-1); return; } while(0)
68 #define ENSURE_APP_IS_IDLE() do { if(m_status != STATUS_IDLE) { x264_beep(x264_beep_warning); qWarning("Cannot perfrom this action at this time!"); return; } } while(0)
69
70 ///////////////////////////////////////////////////////////////////////////////
71 // Constructor & Destructor
72 ///////////////////////////////////////////////////////////////////////////////
73
74 /*
75  * Constructor
76  */
77 MainWindow::MainWindow(const x264_cpu_t *const cpuFeatures, IPC *ipc)
78 :
79         m_ipc(ipc),
80         m_sysinfo(NULL),
81         m_options(NULL),
82         m_jobList(NULL),
83         m_pendingFiles(new QStringList()),
84         m_preferences(NULL),
85         m_recentlyUsed(NULL),
86         m_status(STATUS_PRE_INIT),
87         ui(new Ui::MainWindow())
88 {
89         //Init the dialog, from the .ui file
90         ui->setupUi(this);
91         setWindowFlags(windowFlags() & (~Qt::WindowMaximizeButtonHint));
92
93         //Register meta types
94         qRegisterMetaType<QUuid>("QUuid");
95         qRegisterMetaType<QUuid>("DWORD");
96         qRegisterMetaType<JobStatus>("JobStatus");
97
98         //Create and initialize the sysinfo object
99         m_sysinfo = new SysinfoModel();
100         m_sysinfo->setAppPath(QApplication::applicationDirPath());
101         m_sysinfo->setMMXSupport(cpuFeatures->mmx && cpuFeatures->mmx2);
102         m_sysinfo->setSSESupport(cpuFeatures->sse);
103         m_sysinfo->setX64Support(cpuFeatures->x64);
104
105         //Load preferences
106         m_preferences = new PreferencesModel();
107         PreferencesModel::loadPreferences(m_preferences);
108
109         //Load recently used
110         m_recentlyUsed = new RecentlyUsed();
111         RecentlyUsed::loadRecentlyUsed(m_recentlyUsed);
112
113         //Create options object
114         m_options = new OptionsModel();
115         OptionsModel::loadTemplate(m_options, QString::fromLatin1(tpl_last));
116
117         //Freeze minimum size
118         setMinimumSize(size());
119         ui->splitter->setSizes(QList<int>() << 16 << 196);
120
121         //Update title
122         ui->labelBuildDate->setText(tr("Built on %1 at %2").arg(x264_version_date().toString(Qt::ISODate), QString::fromLatin1(x264_version_time())));
123         ui->labelBuildDate->installEventFilter(this);
124         setWindowTitle(QString("%1 (%2 Mode)").arg(windowTitle(), m_sysinfo->hasX64Support() ? "64-Bit" : "32-Bit"));
125         if(X264_DEBUG)
126         {
127                 setWindowTitle(QString("%1 | !!! DEBUG VERSION !!!").arg(windowTitle()));
128                 setStyleSheet("QMenuBar, QMainWindow { background-color: yellow }");
129         }
130         else if(x264_is_prerelease())
131         {
132                 setWindowTitle(QString("%1 | PRE-RELEASE VERSION").arg(windowTitle()));
133         }
134         
135         //Create model
136         m_jobList = new JobListModel(m_preferences);
137         connect(m_jobList, SIGNAL(dataChanged(QModelIndex, QModelIndex)), this, SLOT(jobChangedData(QModelIndex, QModelIndex)));
138         ui->jobsView->setModel(m_jobList);
139         
140         //Setup view
141         ui->jobsView->horizontalHeader()->setSectionHidden(3, true);
142         ui->jobsView->horizontalHeader()->setResizeMode(0, QHeaderView::Stretch);
143         ui->jobsView->horizontalHeader()->setResizeMode(1, QHeaderView::Fixed);
144         ui->jobsView->horizontalHeader()->setResizeMode(2, QHeaderView::Fixed);
145         ui->jobsView->horizontalHeader()->resizeSection(1, 150);
146         ui->jobsView->horizontalHeader()->resizeSection(2, 90);
147         ui->jobsView->verticalHeader()->setResizeMode(QHeaderView::ResizeToContents);
148         connect(ui->jobsView->selectionModel(), SIGNAL(currentChanged(QModelIndex, QModelIndex)), this, SLOT(jobSelected(QModelIndex, QModelIndex)));
149
150         //Create context menu
151         QAction *actionClipboard = new QAction(QIcon(":/buttons/page_paste.png"), tr("Copy to Clipboard"), ui->logView);
152         actionClipboard->setEnabled(false);
153         ui->logView->addAction(actionClipboard);
154         connect(actionClipboard, SIGNAL(triggered(bool)), this, SLOT(copyLogToClipboard(bool)));
155         ui->jobsView->addActions(ui->menuJob->actions());
156
157         //Enable buttons
158         connect(ui->buttonAddJob, SIGNAL(clicked()), this, SLOT(addButtonPressed()));
159         connect(ui->buttonStartJob, SIGNAL(clicked()), this, SLOT(startButtonPressed()));
160         connect(ui->buttonAbortJob, SIGNAL(clicked()), this, SLOT(abortButtonPressed()));
161         connect(ui->buttonPauseJob, SIGNAL(toggled(bool)), this, SLOT(pauseButtonPressed(bool)));
162         connect(ui->actionJob_Delete, SIGNAL(triggered()), this, SLOT(deleteButtonPressed()));
163         connect(ui->actionJob_Restart, SIGNAL(triggered()), this, SLOT(restartButtonPressed()));
164         connect(ui->actionJob_Browse, SIGNAL(triggered()), this, SLOT(browseButtonPressed()));
165
166         //Enable menu
167         connect(ui->actionOpen, SIGNAL(triggered()), this, SLOT(openActionTriggered()));
168         connect(ui->actionAbout, SIGNAL(triggered()), this, SLOT(showAbout()));
169         connect(ui->actionWebMulder, SIGNAL(triggered()), this, SLOT(showWebLink()));
170         connect(ui->actionWebX264, SIGNAL(triggered()), this, SLOT(showWebLink()));
171         connect(ui->actionWebKomisar, SIGNAL(triggered()), this, SLOT(showWebLink()));
172         connect(ui->actionWebVideoLAN, SIGNAL(triggered()), this, SLOT(showWebLink()));
173         connect(ui->actionWebJEEB, SIGNAL(triggered()), this, SLOT(showWebLink()));
174         connect(ui->actionWebAvisynth32, SIGNAL(triggered()), this, SLOT(showWebLink()));
175         connect(ui->actionWebAvisynth64, SIGNAL(triggered()), this, SLOT(showWebLink()));
176         connect(ui->actionWebAvisynthPlus, SIGNAL(triggered()), this, SLOT(showWebLink()));
177         connect(ui->actionWebVapourSynth, SIGNAL(triggered()), this, SLOT(showWebLink()));
178         connect(ui->actionWebVapourSynthDocs, SIGNAL(triggered()), this, SLOT(showWebLink()));
179         connect(ui->actionWebWiki, SIGNAL(triggered()), this, SLOT(showWebLink()));
180         connect(ui->actionWebBluRay, SIGNAL(triggered()), this, SLOT(showWebLink()));
181         connect(ui->actionWebAvsWiki, SIGNAL(triggered()), this, SLOT(showWebLink()));
182         connect(ui->actionWebSecret, SIGNAL(triggered()), this, SLOT(showWebLink()));
183         connect(ui->actionWebSupport, SIGNAL(triggered()), this, SLOT(showWebLink()));
184         connect(ui->actionPreferences, SIGNAL(triggered()), this, SLOT(showPreferences()));
185         connect(ui->actionCheckForUpdates, SIGNAL(triggered()), this, SLOT(checkUpdates()));
186
187         //Create floating label
188         m_label = new QLabel(ui->jobsView->viewport());
189         m_label->setText(tr("No job created yet. Please click the 'Add New Job' button!"));
190         m_label->setAlignment(Qt::AlignHCenter | Qt::AlignVCenter);
191         SET_TEXT_COLOR(m_label, Qt::darkGray);
192         SET_FONT_BOLD(m_label, true);
193         m_label->setVisible(true);
194         m_label->setContextMenuPolicy(Qt::ActionsContextMenu);
195         m_label->addActions(ui->jobsView->actions());
196         connect(ui->splitter, SIGNAL(splitterMoved(int, int)), this, SLOT(updateLabelPos()));
197         updateLabelPos();
198 }
199
200 /*
201  * Destructor
202  */
203 MainWindow::~MainWindow(void)
204 {
205         m_status = STATUS_EXITTING;
206         OptionsModel::saveTemplate(m_options, QString::fromLatin1(tpl_last));
207         
208         X264_DELETE(m_jobList);
209         X264_DELETE(m_options);
210         X264_DELETE(m_pendingFiles);
211         X264_DELETE(m_label);
212
213         while(!m_toolsList.isEmpty())
214         {
215                 QFile *temp = m_toolsList.takeFirst();
216                 X264_DELETE(temp);
217         }
218
219         if(m_ipc->isListening())
220         {
221                 m_ipc->stopListening();
222         }
223
224         X264_DELETE(m_preferences);
225         X264_DELETE(m_recentlyUsed);
226         X264_DELETE(m_sysinfo);
227
228         VapourSynthCheckThread::unload();
229         AvisynthCheckThread::unload();
230
231         delete ui;
232 }
233
234 ///////////////////////////////////////////////////////////////////////////////
235 // Slots
236 ///////////////////////////////////////////////////////////////////////////////
237
238 /*
239  * The "add" button was clicked
240  */
241 void MainWindow::addButtonPressed()
242 {
243         ENSURE_APP_IS_IDLE();
244         m_status = STATUS_BLOCKED;
245
246         qDebug("MainWindow::addButtonPressed");
247         bool runImmediately = (countRunningJobs() < (m_preferences->getAutoRunNextJob() ? m_preferences->getMaxRunningJobCount() : 1));
248         QString sourceFileName, outputFileName;
249
250         if(createJob(sourceFileName, outputFileName, m_options, runImmediately))
251         {
252                 appendJob(sourceFileName, outputFileName, m_options, runImmediately);
253         }
254
255         m_status = STATUS_IDLE;
256 }
257
258 /*
259  * The "open" action was triggered
260  */
261 void MainWindow::openActionTriggered()
262 {
263         ENSURE_APP_IS_IDLE();
264         m_status = STATUS_BLOCKED;
265
266         QStringList fileList = QFileDialog::getOpenFileNames(this, tr("Open Source File(s)"), m_recentlyUsed->sourceDirectory(), AddJobDialog::getInputFilterLst(), NULL, QFileDialog::DontUseNativeDialog);
267         if(!fileList.empty())
268         {
269                 m_recentlyUsed->setSourceDirectory(QFileInfo(fileList.last()).absolutePath());
270                 if(fileList.count() > 1)
271                 {
272                         createJobMultiple(fileList);
273                 }
274                 else
275                 {
276                         bool runImmediately = (countRunningJobs() < (m_preferences->getAutoRunNextJob() ? m_preferences->getMaxRunningJobCount() : 1));
277                         QString sourceFileName(fileList.first()), outputFileName;
278                         if(createJob(sourceFileName, outputFileName, m_options, runImmediately))
279                         {
280                                 appendJob(sourceFileName, outputFileName, m_options, runImmediately);
281                         }
282                 }
283         }
284
285         m_status = STATUS_IDLE;
286 }
287
288 /*
289  * The "start" button was clicked
290  */
291 void MainWindow::startButtonPressed(void)
292 {
293         ENSURE_APP_IS_IDLE();
294         m_jobList->startJob(ui->jobsView->currentIndex());
295 }
296
297 /*
298  * The "abort" button was clicked
299  */
300 void MainWindow::abortButtonPressed(void)
301 {
302         ENSURE_APP_IS_IDLE();
303         m_jobList->abortJob(ui->jobsView->currentIndex());
304 }
305
306 /*
307  * The "delete" button was clicked
308  */
309 void MainWindow::deleteButtonPressed(void)
310 {
311         ENSURE_APP_IS_IDLE();
312         m_jobList->deleteJob(ui->jobsView->currentIndex());
313         m_label->setVisible(m_jobList->rowCount(QModelIndex()) == 0);
314 }
315
316 /*
317  * The "browse" button was clicked
318  */
319 void MainWindow::browseButtonPressed(void)
320 {
321         ENSURE_APP_IS_IDLE();
322         m_status = STATUS_BLOCKED;
323
324         QString outputFile = m_jobList->getJobOutputFile(ui->jobsView->currentIndex());
325         if((!outputFile.isEmpty()) && QFileInfo(outputFile).exists() && QFileInfo(outputFile).isFile())
326         {
327                 QProcess::startDetached(QString::fromLatin1("explorer.exe"), QStringList() << QString::fromLatin1("/select,") << QDir::toNativeSeparators(outputFile), QFileInfo(outputFile).path());
328         }
329         else
330         {
331                 QMessageBox::warning(this, tr("Not Found"), tr("Sorry, the output file could not be found!"));
332         }
333
334         m_status = STATUS_IDLE;
335 }
336
337 /*
338  * The "pause" button was clicked
339  */
340 void MainWindow::pauseButtonPressed(bool checked)
341 {
342         ENSURE_APP_IS_IDLE();
343
344         if(checked)
345         {
346                 m_jobList->pauseJob(ui->jobsView->currentIndex());
347         }
348         else
349         {
350                 m_jobList->resumeJob(ui->jobsView->currentIndex());
351         }
352 }
353
354 /*
355  * The "restart" button was clicked
356  */
357 void MainWindow::restartButtonPressed(void)
358 {
359         ENSURE_APP_IS_IDLE();
360
361         const QModelIndex index = ui->jobsView->currentIndex();
362         const OptionsModel *options = m_jobList->getJobOptions(index);
363         QString sourceFileName = m_jobList->getJobSourceFile(index);
364         QString outputFileName = m_jobList->getJobOutputFile(index);
365
366         if((options) && (!sourceFileName.isEmpty()) && (!outputFileName.isEmpty()))
367         {
368                 bool runImmediately = (countRunningJobs() < (m_preferences->getAutoRunNextJob() ? m_preferences->getMaxRunningJobCount() : 1));
369                 OptionsModel *tempOptions = new OptionsModel(*options);
370                 if(createJob(sourceFileName, outputFileName, tempOptions, runImmediately, true))
371                 {
372                         appendJob(sourceFileName, outputFileName, tempOptions, runImmediately);
373                 }
374                 X264_DELETE(tempOptions);
375         }
376 }
377
378 /*
379  * Job item selected by user
380  */
381 void MainWindow::jobSelected(const QModelIndex & current, const QModelIndex & previous)
382 {
383         qDebug("Job selected: %d", current.row());
384         
385         if(ui->logView->model())
386         {
387                 disconnect(ui->logView->model(), SIGNAL(rowsInserted(QModelIndex, int, int)), this, SLOT(jobLogExtended(QModelIndex, int, int)));
388         }
389         
390         if(current.isValid())
391         {
392                 ui->logView->setModel(m_jobList->getLogFile(current));
393                 connect(ui->logView->model(), SIGNAL(rowsInserted(QModelIndex, int, int)), this, SLOT(jobLogExtended(QModelIndex, int, int)));
394                 ui->logView->actions().first()->setEnabled(true);
395                 QTimer::singleShot(0, ui->logView, SLOT(scrollToBottom()));
396
397                 ui->progressBar->setValue(m_jobList->getJobProgress(current));
398                 ui->editDetails->setText(m_jobList->data(m_jobList->index(current.row(), 3, QModelIndex()), Qt::DisplayRole).toString());
399                 updateButtons(m_jobList->getJobStatus(current));
400                 updateTaskbar(m_jobList->getJobStatus(current), m_jobList->data(m_jobList->index(current.row(), 0, QModelIndex()), Qt::DecorationRole).value<QIcon>());
401         }
402         else
403         {
404                 ui->logView->setModel(NULL);
405                 ui->logView->actions().first()->setEnabled(false);
406                 ui->progressBar->setValue(0);
407                 ui->editDetails->clear();
408                 updateButtons(JobStatus_Undefined);
409                 updateTaskbar(JobStatus_Undefined, QIcon());
410         }
411
412         ui->progressBar->repaint();
413 }
414
415 /*
416  * Handle update of job info (status, progress, details, etc)
417  */
418 void MainWindow::jobChangedData(const QModelIndex &topLeft, const  QModelIndex &bottomRight)
419 {
420         int selected = ui->jobsView->currentIndex().row();
421         
422         if(topLeft.column() <= 1 && bottomRight.column() >= 1) /*STATUS*/
423         {
424                 for(int i = topLeft.row(); i <= bottomRight.row(); i++)
425                 {
426                         JobStatus status = m_jobList->getJobStatus(m_jobList->index(i, 0, QModelIndex()));
427                         if(i == selected)
428                         {
429                                 qDebug("Current job changed status!");
430                                 updateButtons(status);
431                                 updateTaskbar(status, m_jobList->data(m_jobList->index(i, 0, QModelIndex()), Qt::DecorationRole).value<QIcon>());
432                         }
433                         if((status == JobStatus_Completed) || (status == JobStatus_Failed))
434                         {
435                                 if(m_preferences->getAutoRunNextJob()) QTimer::singleShot(0, this, SLOT(launchNextJob()));
436                                 if(m_preferences->getShutdownComputer()) QTimer::singleShot(0, this, SLOT(shutdownComputer()));
437                                 if(m_preferences->getSaveLogFiles()) saveLogFile(m_jobList->index(i, 1, QModelIndex()));
438                         }
439                 }
440         }
441         if(topLeft.column() <= 2 && bottomRight.column() >= 2) /*PROGRESS*/
442         {
443                 for(int i = topLeft.row(); i <= bottomRight.row(); i++)
444                 {
445                         if(i == selected)
446                         {
447                                 ui->progressBar->setValue(m_jobList->getJobProgress(m_jobList->index(i, 0, QModelIndex())));
448                                 WinSevenTaskbar::setTaskbarProgress(this, ui->progressBar->value(), ui->progressBar->maximum());
449                                 break;
450                         }
451                 }
452         }
453         if(topLeft.column() <= 3 && bottomRight.column() >= 3) /*DETAILS*/
454         {
455                 for(int i = topLeft.row(); i <= bottomRight.row(); i++)
456                 {
457                         if(i == selected)
458                         {
459                                 ui->editDetails->setText(m_jobList->data(m_jobList->index(i, 3, QModelIndex()), Qt::DisplayRole).toString());
460                                 break;
461                         }
462                 }
463         }
464 }
465
466 /*
467  * Handle new log file content
468  */
469 void MainWindow::jobLogExtended(const QModelIndex & parent, int start, int end)
470 {
471         QTimer::singleShot(0, ui->logView, SLOT(scrollToBottom()));
472 }
473
474 /*
475  * About screen
476  */
477 void MainWindow::showAbout(void)
478 {
479         ENSURE_APP_IS_IDLE();
480         m_status = STATUS_BLOCKED;
481         
482         QString text;
483         text += QString().sprintf("<nobr><tt>Simple x264 Launcher v%u.%02u.%u - use 64-Bit x264 with 32-Bit Avisynth<br>", x264_version_major(), x264_version_minor(), x264_version_build());
484         text += QString().sprintf("Copyright (c) 2004-%04d LoRd_MuldeR &lt;mulder2@gmx.de&gt;. Some rights reserved.<br>", qMax(x264_version_date().year(),QDate::currentDate().year()));
485         text += QString().sprintf("Built on %s at %s with %s for Win-%s.<br><br>", x264_version_date().toString(Qt::ISODate).toLatin1().constData(), x264_version_time(), x264_version_compiler(), x264_version_arch());
486         text += QString().sprintf("This program is free software: you can redistribute it and/or modify<br>");
487         text += QString().sprintf("it under the terms of the GNU General Public License &lt;http://www.gnu.org/&gt;.<br>");
488         text += QString().sprintf("Note that this program is distributed with ABSOLUTELY NO WARRANTY.<br><br>");
489         text += QString().sprintf("Please check the web-site at <a href=\"%s\">%s</a> for updates !!!<br></tt></nobr>", home_url, home_url);
490
491         QMessageBox aboutBox(this);
492         aboutBox.setIconPixmap(QIcon(":/images/movie.png").pixmap(64,64));
493         aboutBox.setWindowTitle(tr("About..."));
494         aboutBox.setText(text.replace("-", "&minus;"));
495         aboutBox.addButton(tr("About x264"), QMessageBox::NoRole);
496         aboutBox.addButton(tr("About AVS"), QMessageBox::NoRole);
497         aboutBox.addButton(tr("About VPY"), QMessageBox::NoRole);
498         aboutBox.addButton(tr("About Qt"), QMessageBox::NoRole);
499         aboutBox.setEscapeButton(aboutBox.addButton(tr("Close"), QMessageBox::NoRole));
500                 
501         forever
502         {
503                 x264_beep(x264_beep_info);
504                 switch(aboutBox.exec())
505                 {
506                 case 0:
507                         {
508                                 QString text2;
509                                 text2 += tr("<nobr><tt>x264 - the best H.264/AVC encoder. Copyright (c) 2003-2013 x264 project.<br>");
510                                 text2 += tr("Free software library for encoding video streams into the H.264/MPEG-4 AVC format.<br>");
511                                 text2 += tr("Released under the terms of the GNU General Public License.<br><br>");
512                                 text2 += tr("Please visit <a href=\"%1\">%1</a> for obtaining a commercial x264 license.<br>").arg("http://x264licensing.com/");
513                                 text2 += tr("Read the <a href=\"%1\">user's manual</a> to get started and use the <a href=\"%2\">support forum</a> for help!<br></tt></nobr>").arg("http://mewiki.project357.com/wiki/X264_Settings", "http://forum.doom9.org/forumdisplay.php?f=77");
514
515                                 QMessageBox x264Box(this);
516                                 x264Box.setIconPixmap(QIcon(":/images/x264.png").pixmap(48,48));
517                                 x264Box.setWindowTitle(tr("About x264"));
518                                 x264Box.setText(text2.replace("-", "&minus;"));
519                                 x264Box.setEscapeButton(x264Box.addButton(tr("Close"), QMessageBox::NoRole));
520                                 x264_beep(x264_beep_info);
521                                 x264Box.exec();
522                         }
523                         break;
524                 case 1:
525                         {
526                                 QString text2;
527                                 text2 += tr("<nobr><tt>Avisynth - powerful video processing scripting language.<br>");
528                                 text2 += tr("Copyright (c) 2000 Ben Rudiak-Gould and all subsequent developers.<br>");
529                                 text2 += tr("Released under the terms of the GNU General Public License.<br><br>");
530                                 text2 += tr("Please visit the web-site <a href=\"%1\">%1</a> for more information.<br>").arg("http://avisynth.org/");
531                                 text2 += tr("Read the <a href=\"%1\">guide</a> to get started and use the <a href=\"%2\">support forum</a> for help!<br></tt></nobr>").arg("http://avisynth.nl/index.php/First_script", "http://forum.doom9.org/forumdisplay.php?f=33");
532
533                                 QMessageBox x264Box(this);
534                                 x264Box.setIconPixmap(QIcon(":/images/avisynth.png").pixmap(48,67));
535                                 x264Box.setWindowTitle(tr("About Avisynth"));
536                                 x264Box.setText(text2.replace("-", "&minus;"));
537                                 x264Box.setEscapeButton(x264Box.addButton(tr("Close"), QMessageBox::NoRole));
538                                 x264_beep(x264_beep_info);
539                                 x264Box.exec();
540                         }
541                         break;
542                 case 2:
543                         {
544                                 QString text2;
545                                 text2 += tr("<nobr><tt>VapourSynth - application for video manipulation based on Python.<br>");
546                                 text2 += tr("Copyright (c) 2012 Fredrik Mellbin.<br>");
547                                 text2 += tr("Released under the terms of the GNU Lesser General Public.<br><br>");
548                                 text2 += tr("Please visit the web-site <a href=\"%1\">%1</a> for more information.<br>").arg("http://www.vapoursynth.com/");
549                                 text2 += tr("Read the <a href=\"%1\">documentation</a> to get started and use the <a href=\"%2\">support forum</a> for help!<br></tt></nobr>").arg("http://www.vapoursynth.com/doc/", "http://forum.doom9.org/showthread.php?t=165771");
550
551                                 QMessageBox x264Box(this);
552                                 x264Box.setIconPixmap(QIcon(":/images/python.png").pixmap(48,48));
553                                 x264Box.setWindowTitle(tr("About VapourSynth"));
554                                 x264Box.setText(text2.replace("-", "&minus;"));
555                                 x264Box.setEscapeButton(x264Box.addButton(tr("Close"), QMessageBox::NoRole));
556                                 x264_beep(x264_beep_info);
557                                 x264Box.exec();
558                         }
559                         break;
560                 case 3:
561                         QMessageBox::aboutQt(this);
562                         break;
563                 default:
564                         m_status = STATUS_IDLE;
565                         return;
566                 }
567         }
568 }
569
570 /*
571  * Open web-link
572  */
573 void MainWindow::showWebLink(void)
574 {
575         ENSURE_APP_IS_IDLE();
576
577         if(QObject::sender() == ui->actionWebMulder)          QDesktopServices::openUrl(QUrl(home_url));
578         if(QObject::sender() == ui->actionWebX264)            QDesktopServices::openUrl(QUrl("http://www.x264.com/"));
579         if(QObject::sender() == ui->actionWebKomisar)         QDesktopServices::openUrl(QUrl("http://komisar.gin.by/"));
580         if(QObject::sender() == ui->actionWebVideoLAN)        QDesktopServices::openUrl(QUrl("http://download.videolan.org/pub/x264/binaries/"));
581         if(QObject::sender() == ui->actionWebJEEB)            QDesktopServices::openUrl(QUrl("http://x264.fushizen.eu/"));
582         if(QObject::sender() == ui->actionWebAvisynth32)      QDesktopServices::openUrl(QUrl("http://sourceforge.net/projects/avisynth2/files/AviSynth%202.5/"));
583         if(QObject::sender() == ui->actionWebAvisynth64)      QDesktopServices::openUrl(QUrl("http://code.google.com/p/avisynth64/downloads/list"));
584         if(QObject::sender() == ui->actionWebAvisynthPlus)    QDesktopServices::openUrl(QUrl("http://www.avs-plus.net/"));
585         if(QObject::sender() == ui->actionWebVapourSynth)     QDesktopServices::openUrl(QUrl("http://www.vapoursynth.com/"));
586         if(QObject::sender() == ui->actionWebVapourSynthDocs) QDesktopServices::openUrl(QUrl("http://www.vapoursynth.com/doc/"));
587         if(QObject::sender() == ui->actionWebWiki)            QDesktopServices::openUrl(QUrl("http://mewiki.project357.com/wiki/X264_Settings"));
588         if(QObject::sender() == ui->actionWebBluRay)          QDesktopServices::openUrl(QUrl("http://www.x264bluray.com/"));
589         if(QObject::sender() == ui->actionWebAvsWiki)         QDesktopServices::openUrl(QUrl("http://avisynth.nl/index.php/Main_Page#Usage"));
590         if(QObject::sender() == ui->actionWebSupport)         QDesktopServices::openUrl(QUrl("http://forum.doom9.org/showthread.php?t=144140"));
591         if(QObject::sender() == ui->actionWebSecret)          QDesktopServices::openUrl(QUrl("http://www.youtube.com/watch_popup?v=AXIeHY-OYNI"));
592 }
593
594 /*
595  * Pereferences dialog
596  */
597 void MainWindow::showPreferences(void)
598 {
599         ENSURE_APP_IS_IDLE();
600         m_status = STATUS_BLOCKED;
601
602         PreferencesDialog *preferences = new PreferencesDialog(this, m_preferences, m_sysinfo);
603         preferences->exec();
604
605         X264_DELETE(preferences);
606         m_status = STATUS_IDLE;
607 }
608
609 /*
610  * Launch next job, after running job has finished
611  */
612 void MainWindow::launchNextJob(void)
613 {
614         qDebug("launchNextJob(void)");
615         
616         const int rows = m_jobList->rowCount(QModelIndex());
617
618         if(countRunningJobs() >= m_preferences->getMaxRunningJobCount())
619         {
620                 qDebug("Still have too many jobs running, won't launch next one yet!");
621                 return;
622         }
623
624         int startIdx= ui->jobsView->currentIndex().isValid() ? qBound(0, ui->jobsView->currentIndex().row(), rows-1) : 0;
625
626         for(int i = 0; i < rows; i++)
627         {
628                 int currentIdx = (i + startIdx) % rows;
629                 JobStatus status = m_jobList->getJobStatus(m_jobList->index(currentIdx, 0, QModelIndex()));
630                 if(status == JobStatus_Enqueued)
631                 {
632                         if(m_jobList->startJob(m_jobList->index(currentIdx, 0, QModelIndex())))
633                         {
634                                 ui->jobsView->selectRow(currentIdx);
635                                 return;
636                         }
637                 }
638         }
639                 
640         qWarning("No enqueued jobs left!");
641 }
642
643 /*
644  * Save log to text file
645  */
646 void MainWindow::saveLogFile(const QModelIndex &index)
647 {
648         if(index.isValid())
649         {
650                 if(LogFileModel *log = m_jobList->getLogFile(index))
651                 {
652                         QDir(QString("%1/logs").arg(x264_data_path())).mkpath(".");
653                         QString logFilePath = QString("%1/logs/LOG.%2.%3.txt").arg(x264_data_path(), QDate::currentDate().toString(Qt::ISODate), QTime::currentTime().toString(Qt::ISODate).replace(':', "-"));
654                         QFile outFile(logFilePath);
655                         if(outFile.open(QIODevice::WriteOnly))
656                         {
657                                 QTextStream outStream(&outFile);
658                                 outStream.setCodec("UTF-8");
659                                 outStream.setGenerateByteOrderMark(true);
660                                 
661                                 const int rows = log->rowCount(QModelIndex());
662                                 for(int i = 0; i < rows; i++)
663                                 {
664                                         outStream << log->data(log->index(i, 0, QModelIndex()), Qt::DisplayRole).toString() << QLatin1String("\r\n");
665                                 }
666                                 outFile.close();
667                         }
668                         else
669                         {
670                                 qWarning("Failed to open log file for writing:\n%s", logFilePath.toUtf8().constData());
671                         }
672                 }
673         }
674 }
675
676 /*
677  * Shut down the computer (with countdown)
678  */
679 void MainWindow::shutdownComputer(void)
680 {
681         qDebug("shutdownComputer(void)");
682         
683         if((m_status != STATUS_IDLE) && (m_status != STATUS_EXITTING))
684         {
685                 qWarning("Cannot shutdown computer at this time!");
686                 return;
687         }
688
689         if(countPendingJobs() > 0)
690         {
691                 qDebug("Still have pending jobs, won't shutdown yet!");
692                 return;
693         }
694         
695         const x264_status_t previousStatus = m_status;
696         m_status = STATUS_BLOCKED;
697
698         const int iTimeout = 30;
699         const Qt::WindowFlags flags = Qt::WindowStaysOnTopHint | Qt::CustomizeWindowHint | Qt::WindowTitleHint | Qt::MSWindowsFixedSizeDialogHint | Qt::WindowSystemMenuHint;
700         const QString text = QString("%1%2%1").arg(QString().fill(' ', 18), tr("Warning: Computer will shutdown in %1 seconds..."));
701         
702         qWarning("Initiating shutdown sequence!");
703         
704         QProgressDialog progressDialog(text.arg(iTimeout), tr("Cancel Shutdown"), 0, iTimeout + 1, this, flags);
705         QPushButton *cancelButton = new QPushButton(tr("Cancel Shutdown"), &progressDialog);
706         cancelButton->setIcon(QIcon(":/buttons/power_on.png"));
707         progressDialog.setModal(true);
708         progressDialog.setAutoClose(false);
709         progressDialog.setAutoReset(false);
710         progressDialog.setWindowIcon(QIcon(":/buttons/power_off.png"));
711         progressDialog.setWindowTitle(windowTitle());
712         progressDialog.setCancelButton(cancelButton);
713         progressDialog.show();
714         
715         QApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
716         QApplication::setOverrideCursor(Qt::WaitCursor);
717         x264_play_sound(IDR_WAVE1, false);
718         QApplication::restoreOverrideCursor();
719         
720         QTimer timer;
721         timer.setInterval(1000);
722         timer.start();
723
724         QEventLoop eventLoop(this);
725         connect(&timer, SIGNAL(timeout()), &eventLoop, SLOT(quit()));
726         connect(&progressDialog, SIGNAL(canceled()), &eventLoop, SLOT(quit()));
727
728         for(int i = 1; i <= iTimeout; i++)
729         {
730                 eventLoop.exec();
731                 if(progressDialog.wasCanceled())
732                 {
733                         progressDialog.close();
734                         m_status = previousStatus;
735                         return;
736                 }
737                 progressDialog.setValue(i+1);
738                 progressDialog.setLabelText(text.arg(iTimeout-i));
739                 if(iTimeout-i == 3) progressDialog.setCancelButton(NULL);
740                 QApplication::processEvents();
741                 x264_play_sound(((i < iTimeout) ? IDR_WAVE2 : IDR_WAVE3), false);
742         }
743         
744         qWarning("Shutting down !!!");
745         m_status = previousStatus;
746
747         if(x264_shutdown_computer("Simple x264 Launcher: All jobs completed, shutting down!", 10, true))
748         {
749                 qApp->closeAllWindows();
750         }
751
752 }
753
754 /*
755  * Main initialization function (called only once!)
756  */
757 void MainWindow::init(void)
758 {
759         if(m_status != STATUS_PRE_INIT)
760         {
761                 qWarning("Already initialized -> skipping!");
762                 return;
763         }
764
765         const QStringList arguments = x264_arguments();
766         static const char *binFiles = "x86/x264_8bit_x86.exe:x64/x264_8bit_x64.exe:x86/x264_10bit_x86.exe:x64/x264_10bit_x64.exe:x86/avs2yuv_x86.exe:x64/avs2yuv_x64.exe";
767         QStringList binaries = QString::fromLatin1(binFiles).split(":", QString::SkipEmptyParts);
768
769         updateLabelPos();
770
771         //Create the IPC listener thread
772         if(m_ipc->isInitialized())
773         {
774                 connect(m_ipc, SIGNAL(receivedCommand(int,QStringList,quint32)), this, SLOT(handleCommand(int,QStringList,quint32)), Qt::QueuedConnection);
775                 m_ipc->startListening();
776         }
777
778         //Check all binaries
779         while(!binaries.isEmpty())
780         {
781                 qApp->processEvents(QEventLoop::ExcludeUserInputEvents);
782                 QString current = binaries.takeFirst();
783                 QFile *file = new QFile(QString("%1/toolset/%2").arg(m_sysinfo->getAppPath(), current));
784                 if(file->open(QIODevice::ReadOnly))
785                 {
786                         if(!x264_is_executable(file->fileName()))
787                         {
788                                 QMessageBox::critical(this, tr("Invalid File!"), tr("<nobr>At least on required tool is not a valid Win32 or Win64 binary:<br><tt style=\"whitespace:nowrap\">%1</tt><br><br>Please re-install the program in order to fix the problem!</nobr>").arg(QDir::toNativeSeparators(QString("%1/toolset/%2").arg(m_sysinfo->getAppPath(), current))).replace("-", "&minus;"));
789                                 qFatal(QString("Binary is invalid: %1/toolset/%2").arg(m_sysinfo->getAppPath(), current).toLatin1().constData());
790                                 INIT_ERROR_EXIT();
791                         }
792                         m_toolsList << file;
793                 }
794                 else
795                 {
796                         X264_DELETE(file);
797                         QMessageBox::critical(this, tr("File Not Found!"), tr("<nobr>At least on required tool could not be found:<br><tt style=\"whitespace:nowrap\">%1</tt><br><br>Please re-install the program in order to fix the problem!</nobr>").arg(QDir::toNativeSeparators(QString("%1/toolset/%2").arg(m_sysinfo->getAppPath(), current))).replace("-", "&minus;"));
798                         qFatal(QString("Binary not found: %1/toolset/%2").arg(m_sysinfo->getAppPath(), current).toLatin1().constData());
799                         INIT_ERROR_EXIT();
800                 }
801         }
802
803         //Check for portable mode
804         if(x264_portable())
805         {
806                 bool ok = false;
807                 static const char *data = "Lorem ipsum dolor sit amet, consectetur adipiscing elit.";
808                 QFile writeTest(QString("%1/%2").arg(x264_data_path(), QUuid::createUuid().toString()));
809                 if(writeTest.open(QIODevice::WriteOnly))
810                 {
811                         ok = (writeTest.write(data) == strlen(data));
812                         writeTest.remove();
813                 }
814                 if(!ok)
815                 {
816                         int val = QMessageBox::warning(this, tr("Write Test Failed"), tr("<nobr>The application was launched in portable mode, but the program path is <b>not</b> writable!</nobr>"), tr("Quit"), tr("Ignore"));
817                         if(val != 1) INIT_ERROR_EXIT();
818                 }
819         }
820
821         //Pre-release popup
822         if(x264_is_prerelease())
823         {
824                 qsrand(time(NULL)); int rnd = qrand() % 3;
825                 int val = QMessageBox::information(this, tr("Pre-Release Version"), tr("Note: This is a pre-release version. Please do NOT use for production!<br>Click the button #%1 in order to continue...<br><br>(There will be no such message box in the final version of this application)").arg(QString::number(rnd + 1)), tr("(1)"), tr("(2)"), tr("(3)"), qrand() % 3);
826                 if(rnd != val) INIT_ERROR_EXIT();
827         }
828
829         //Make sure this CPU can run x264 (requires MMX + MMXEXT/iSSE to run x264 with ASM enabled, additionally requires SSE1 for most x264 builds)
830         if(!m_sysinfo->hasMMXSupport())
831         {
832                 QMessageBox::critical(this, tr("Unsupported CPU"), tr("<nobr>Sorry, but this machine is <b>not</b> physically capable of running x264 (with assembly).<br>Please get a CPU that supports at least the MMX and MMXEXT instruction sets!</nobr>"), tr("Quit"));
833                 qFatal("System does not support MMX and MMXEXT, x264 will not work !!!");
834                 INIT_ERROR_EXIT();
835         }
836         else if(!m_sysinfo->hasSSESupport())
837         {
838                 qWarning("WARNING: System does not support SSE1, most x264 builds will not work !!!\n");
839                 int val = QMessageBox::warning(this, tr("Unsupported CPU"), tr("<nobr>It appears that this machine does <b>not</b> support the SSE1 instruction set.<br>Thus most builds of x264 will <b>not</b> run on this computer at all.<br><br>Please get a CPU that supports the MMX and SSE1 instruction sets!</nobr>"), tr("Quit"), tr("Ignore"));
840                 if(val != 1) INIT_ERROR_EXIT();
841         }
842
843         //Skip version check (not recommended!)
844         if(CLIParser::checkFlag(CLI_PARAM_SKIP_X264_CHECK, arguments))
845         {
846                 qWarning("x264 version check disabled, you have been warned!\n");
847                 m_preferences->setSkipVersionTest(true);
848         }
849         
850         //Don't abort encoding process on timeout (not recommended!)
851         if(CLIParser::checkFlag(CLI_PARAM_NO_DEADLOCK, arguments))
852         {
853                 qWarning("Deadlock detection disabled, you have been warned!\n");
854                 m_preferences->setAbortOnTimeout(false);
855         }
856
857         //Check for Avisynth support
858         if(!CLIParser::checkFlag(CLI_PARAM_SKIP_AVS_CHECK, arguments))
859         {
860                 qDebug("[Check for Avisynth support]");
861                 volatile double avisynthVersion = 0.0;
862                 const int result = AvisynthCheckThread::detect(&avisynthVersion);
863                 if(result < 0)
864                 {
865                         QString text = tr("A critical error was encountered while checking your Avisynth version.").append("<br>");
866                         text += tr("This is most likely caused by an erroneous Avisynth Plugin, please try to clean your Plugins folder!").append("<br>");
867                         text += tr("We suggest to move all .dll and .avsi files out of your Avisynth Plugins folder and try again.");
868                         int val = QMessageBox::critical(this, tr("Avisynth Error"), QString("<nobr>%1</nobr>").arg(text).replace("-", "&minus;"), tr("Quit"), tr("Ignore"));
869                         if(val != 1) INIT_ERROR_EXIT();
870                 }
871                 if(result && (avisynthVersion >= 2.5))
872                 {
873                         qDebug("Avisynth support is officially enabled now!");
874                         m_sysinfo->setAVSSupport(true);
875                 }
876                 else
877                 {
878                         if(!m_preferences->getDisableWarnings())
879                         {
880                                 QString text = tr("It appears that Avisynth is <b>not</b> currently installed on your computer.<br>Therefore Avisynth (.avs) input will <b>not</b> be working at all!").append("<br><br>");
881                                 text += tr("Please download and install Avisynth:").append("<br>").append(LINK("http://sourceforge.net/projects/avisynth2/files/AviSynth%202.5/"));
882                                 int val = QMessageBox::warning(this, tr("Avisynth Missing"), QString("<nobr>%1</nobr>").arg(text).replace("-", "&minus;"), tr("Quit"), tr("Ignore"));
883                                 if(val != 1) INIT_ERROR_EXIT();
884                         }
885                 }
886                 qDebug(" ");
887         }
888
889         //Check for VapourSynth support
890         if(!CLIParser::checkFlag(CLI_PARAM_SKIP_VPS_CHECK, arguments))
891         {
892                 qDebug("[Check for VapourSynth support]");
893                 QString vapoursynthPath;
894                 const int result = VapourSynthCheckThread::detect(vapoursynthPath);
895                 if(result < 0)
896                 {
897                         QString text = tr("A critical error was encountered while checking your VapourSynth installation.").append("<br>");
898                         text += tr("This is most likely caused by an erroneous VapourSynth Plugin, please try to clean your Filters folder!").append("<br>");
899                         text += tr("We suggest to move all .dll files out of your VapourSynth Filters folder and try again.");
900                         int val = QMessageBox::critical(this, tr("VapourSynth Error"), QString("<nobr>%1</nobr>").arg(text).replace("-", "&minus;"), tr("Quit"), tr("Ignore"));
901                         if(val != 1) INIT_ERROR_EXIT();
902                 }
903                 if(result && (!vapoursynthPath.isEmpty()))
904                 {
905                         qDebug("VapourSynth support is officially enabled now!");
906                         m_sysinfo->setVPSSupport(true);
907                         m_sysinfo->setVPSPath(vapoursynthPath);
908                 }
909                 else
910                 {
911                         if(!m_preferences->getDisableWarnings())
912                         {
913                                 QString text = tr("It appears that VapourSynth is <b>not</b> currently installed on your computer.<br>Therefore VapourSynth (.vpy) input will <b>not</b> be working at all!").append("<br><br>");
914                                 text += tr("Please download and install VapourSynth for Windows (R19 or later):").append("<br>").append(LINK("http://www.vapoursynth.com/")).append("<br><br>");
915                                 text += tr("Note that Python 3.3 (x86) is a prerequisite for installing VapourSynth:").append("<br>").append(LINK("http://www.python.org/getit/")).append("<br>");
916                                 int val = QMessageBox::warning(this, tr("VapourSynth Missing"), QString("<nobr>%1</nobr>").arg(text).replace("-", "&minus;"), tr("Quit"), tr("Ignore"));
917                                 if(val != 1) INIT_ERROR_EXIT();
918                         }
919                 }
920                 qDebug(" ");
921         }
922
923         //Enable drag&drop support for this window, required for Qt v4.8.4+
924         setAcceptDrops(true);
925
926         //Check for expiration
927         if(x264_version_date().addMonths(6) < x264_current_date_safe())
928         {
929                 QString text;
930                 text += QString("<nobr><tt>%1</tt></nobr><br><br>").arg(tr("Your version of Simple x264 Launcher is more than 6 months old!").replace("-", "&minus;"));
931                 text += QString("<nobr><tt>%1<br><a href=\"%2\">%2</a><br><br>").arg(tr("You can download the most recent version from the official web-site now:").replace("-", "&minus;"), QString::fromLatin1(update_url));
932                 text += QString("<nobr><tt>%1</tt></nobr><br>").arg(tr("Alternatively, click 'Check for Updates' to run the auto-update utility.").replace("-", "&minus;"));
933                 QMessageBox msgBox(this);
934                 msgBox.setIconPixmap(QIcon(":/images/update.png").pixmap(56,56));
935                 msgBox.setWindowTitle(tr("Update Notification"));
936                 msgBox.setWindowFlags(Qt::Window | Qt::WindowTitleHint | Qt::CustomizeWindowHint);
937                 msgBox.setText(text);
938                 QPushButton *btn1 = msgBox.addButton(tr("Check for Updates"), QMessageBox::AcceptRole);
939                 QPushButton *btn2 = msgBox.addButton(tr("Discard"), QMessageBox::NoRole);
940                 QPushButton *btn3 = msgBox.addButton(btn2->text(), QMessageBox::RejectRole);
941                 btn2->setEnabled(false);
942                 btn3->setVisible(false);
943                 QTimer::singleShot(7500, btn2, SLOT(hide()));
944                 QTimer::singleShot(7500, btn3, SLOT(show()));
945                 if(msgBox.exec() == 0)
946                 {
947                         m_status = STATUS_IDLE;
948                         QTimer::singleShot(0, this, SLOT(checkUpdates()));
949                         return;
950                 }
951         }
952
953         //Update app staus
954         m_status = STATUS_IDLE;
955
956         //Try adding files from command-line
957         if(!parseCommandLineArgs())
958         {
959                 //Update reminder
960                 if((!m_preferences->getNoUpdateReminder()) && (m_recentlyUsed->lastUpdateCheck() + 14 < x264_current_date_safe().toJulianDay()))
961                 {
962                         if(QMessageBox::warning(this, tr("Update Notification"), QString("<nobr>%1</nobr>").arg(tr("Your last update check was more than 14 days ago. Check for updates now?")), tr("Check for Updates"), tr("Discard")) == 0)
963                         {
964                                 QTimer::singleShot(0, this, SLOT(checkUpdates()));
965                                 return;
966                         }
967                 }
968         }
969 }
970
971 /*
972  * Update the label position
973  */
974 void MainWindow::updateLabelPos(void)
975 {
976         const QWidget *const viewPort = ui->jobsView->viewport();
977         m_label->setGeometry(0, 0, viewPort->width(), viewPort->height());
978 }
979
980 /*
981  * Copy the complete log to the clipboard
982  */
983 void MainWindow::copyLogToClipboard(bool checked)
984 {
985         qDebug("copyLogToClipboard");
986         
987         if(LogFileModel *log = dynamic_cast<LogFileModel*>(ui->logView->model()))
988         {
989                 log->copyToClipboard();
990                 x264_beep(x264_beep_info);
991         }
992 }
993
994 /*
995  * Process the dropped files
996  */
997 void MainWindow::handlePendingFiles(void)
998 {
999         if((m_status == STATUS_IDLE) || (m_status == STATUS_AWAITING))
1000         {
1001                 qDebug("MainWindow::handlePendingFiles");
1002                 if(!m_pendingFiles->isEmpty())
1003                 {
1004                         QStringList pendingFiles(*m_pendingFiles);
1005                         m_pendingFiles->clear();
1006                         createJobMultiple(pendingFiles);
1007                 }
1008                 qDebug("Leave from MainWindow::handlePendingFiles!");
1009                 m_status = STATUS_IDLE;
1010         }
1011 }
1012
1013 void MainWindow::handleCommand(const int &command, const QStringList &args, const quint32 &flags)
1014 {
1015         if((m_status != STATUS_IDLE) && (m_status != STATUS_AWAITING))
1016         {
1017                 qWarning("Cannot accapt commands at this time -> discarding!");
1018                 return;
1019         }
1020         
1021         x264_bring_to_front(this);
1022         
1023 #ifdef IPC_LOGGING
1024         qDebug("\n---------- IPC ----------");
1025         qDebug("CommandId: %d", command);
1026         for(QStringList::ConstIterator iter = args.constBegin(); iter != args.constEnd(); iter++)
1027         {
1028                 qDebug("Arguments: %s", iter->toUtf8().constData());
1029         }
1030         qDebug("The Flags: 0x%08X", flags);
1031         qDebug("---------- IPC ----------\n");
1032 #endif //IPC_LOGGING
1033
1034         switch(command)
1035         {
1036         case IPC_OPCODE_PING:
1037                 qDebug("Received a PING request from another instance!");
1038                 x264_blink_window(this, 5, 125);
1039                 break;
1040         case IPC_OPCODE_ADD_FILE:
1041                 if(!args.isEmpty())
1042                 {
1043                         if(QFileInfo(args[0]).exists() && QFileInfo(args[0]).isFile())
1044                         {
1045                                 *m_pendingFiles << QFileInfo(args[0]).canonicalFilePath();
1046                                 if(m_status != STATUS_AWAITING)
1047                                 {
1048                                         m_status = STATUS_AWAITING;
1049                                         QTimer::singleShot(5000, this, SLOT(handlePendingFiles()));
1050                                 }
1051                         }
1052                         else
1053                         {
1054                                 qWarning("File '%s' not found!", args[0].toUtf8().constData());
1055                         }
1056                 }
1057                 break;
1058         case IPC_OPCODE_ADD_JOB:
1059                 if(args.size() >= 3)
1060                 {
1061                         if(QFileInfo(args[0]).exists() && QFileInfo(args[0]).isFile())
1062                         {
1063                                 OptionsModel options;
1064                                 bool runImmediately = (countRunningJobs() < (m_preferences->getAutoRunNextJob() ? m_preferences->getMaxRunningJobCount() : 1));
1065                                 if(!(args[2].isEmpty() || X264_STRCMP(args[2], "-")))
1066                                 {
1067                                         if(!OptionsModel::loadTemplate(&options, args[2].trimmed()))
1068                                         {
1069                                                 qWarning("Template '%s' could not be found -> using defaults!", args[2].trimmed().toUtf8().constData());
1070                                         }
1071                                 }
1072                                 if((flags & IPC_FLAG_FORCE_START) && (!(flags & IPC_FLAG_FORCE_ENQUEUE))) runImmediately = true;
1073                                 if((flags & IPC_FLAG_FORCE_ENQUEUE) && (!(flags & IPC_FLAG_FORCE_START))) runImmediately = false;
1074                                 appendJob(args[0], args[1], &options, runImmediately);
1075                         }
1076                         else
1077                         {
1078                                 qWarning("Source file '%s' not found!", args[0].toUtf8().constData());
1079                         }
1080                 }
1081                 break;
1082         default:
1083                 throw std::exception("Unknown command received!");
1084         }
1085 }
1086
1087 void MainWindow::checkUpdates(void)
1088 {
1089         ENSURE_APP_IS_IDLE();
1090         m_status = STATUS_BLOCKED;
1091
1092         if(countRunningJobs() > 0)
1093         {
1094                 QMessageBox::warning(this, tr("Jobs Are Running"), tr("Sorry, can not update while there still are running jobs!"));
1095                 m_status = STATUS_IDLE;
1096                 return;
1097         }
1098
1099         UpdaterDialog *updater = new UpdaterDialog(this, m_sysinfo);
1100         const int ret = updater->exec();
1101
1102         if(updater->getSuccess())
1103         {
1104                 m_recentlyUsed->setLastUpdateCheck(x264_current_date_safe().toJulianDay());
1105                 RecentlyUsed::saveRecentlyUsed(m_recentlyUsed);
1106         }
1107
1108         if(ret == UpdaterDialog::READY_TO_INSTALL_UPDATE)
1109         {
1110                 m_status = STATUS_EXITTING;
1111                 qWarning("Exitting program to install update...");
1112                 close();
1113                 QApplication::quit();
1114         }
1115
1116         X264_DELETE(updater);
1117
1118         if(m_status != STATUS_EXITTING)
1119         {
1120                 m_status = STATUS_IDLE;
1121         }
1122 }
1123
1124 ///////////////////////////////////////////////////////////////////////////////
1125 // Event functions
1126 ///////////////////////////////////////////////////////////////////////////////
1127
1128 /*
1129  * Window shown event
1130  */
1131 void MainWindow::showEvent(QShowEvent *e)
1132 {
1133         QMainWindow::showEvent(e);
1134
1135         if(m_status == STATUS_PRE_INIT)
1136         {
1137                 QTimer::singleShot(0, this, SLOT(init()));
1138         }
1139 }
1140
1141 /*
1142  * Window close event
1143  */
1144 void MainWindow::closeEvent(QCloseEvent *e)
1145 {
1146         if((m_status != STATUS_IDLE) && (m_status != STATUS_EXITTING))
1147         {
1148                 e->ignore();
1149                 qWarning("Cannot close window at this time!");
1150                 return;
1151         }
1152
1153         if(m_status != STATUS_EXITTING)
1154         {
1155                 if(countRunningJobs() > 0)
1156                 {
1157                         e->ignore();
1158                         m_status = STATUS_BLOCKED;
1159                         QMessageBox::warning(this, tr("Jobs Are Running"), tr("Sorry, can not exit while there still are running jobs!"));
1160                         m_status = STATUS_IDLE;
1161                         return;
1162                 }
1163         
1164                 if(countPendingJobs() > 0)
1165                 {
1166                         m_status = STATUS_BLOCKED;
1167                         int ret = QMessageBox::question(this, tr("Jobs Are Pending"), tr("Do you really want to quit and discard the pending jobs?"), QMessageBox::Yes | QMessageBox::No, QMessageBox::No);
1168                         if(ret != QMessageBox::Yes)
1169                         {
1170                                 e->ignore();
1171                                 m_status = STATUS_IDLE;
1172                                 return;
1173                         }
1174                 }
1175         }
1176
1177         while(m_jobList->rowCount(QModelIndex()) > 0)
1178         {
1179                 qApp->processEvents(QEventLoop::ExcludeUserInputEvents);
1180                 if(!m_jobList->deleteJob(m_jobList->index(0, 0, QModelIndex())))
1181                 {
1182                         e->ignore();
1183                         m_status = STATUS_BLOCKED;
1184                         QMessageBox::warning(this, tr("Failed To Exit"), tr("Sorry, at least one job could not be deleted!"));
1185                         m_status = STATUS_IDLE;
1186                         return;
1187                 }
1188         }
1189         
1190         m_status = STATUS_EXITTING;
1191         qApp->processEvents(QEventLoop::ExcludeUserInputEvents);
1192         QMainWindow::closeEvent(e);
1193 }
1194
1195 /*
1196  * Window resize event
1197  */
1198 void MainWindow::resizeEvent(QResizeEvent *e)
1199 {
1200         QMainWindow::resizeEvent(e);
1201         updateLabelPos();
1202 }
1203
1204 /*
1205  * Event filter
1206  */
1207 bool MainWindow::eventFilter(QObject *o, QEvent *e)
1208 {
1209         if((o == ui->labelBuildDate) && (e->type() == QEvent::MouseButtonPress))
1210         {
1211                 QTimer::singleShot(0, this, SLOT(showAbout()));
1212                 return true;
1213         }
1214         return false;
1215 }
1216
1217 /*
1218  * Win32 message filter
1219  */
1220 bool MainWindow::winEvent(MSG *message, long *result)
1221 {
1222         return WinSevenTaskbar::handleWinEvent(message, result);
1223 }
1224
1225 /*
1226  * File dragged over window
1227  */
1228 void MainWindow::dragEnterEvent(QDragEnterEvent *event)
1229 {
1230         bool accept[2] = {false, false};
1231
1232         foreach(const QString &fmt, event->mimeData()->formats())
1233         {
1234                 accept[0] = accept[0] || fmt.contains("text/uri-list", Qt::CaseInsensitive);
1235                 accept[1] = accept[1] || fmt.contains("FileNameW", Qt::CaseInsensitive);
1236         }
1237
1238         if(accept[0] && accept[1])
1239         {
1240                 event->acceptProposedAction();
1241         }
1242 }
1243
1244 /*
1245  * File dropped onto window
1246  */
1247 void MainWindow::dropEvent(QDropEvent *event)
1248 {
1249         if((m_status != STATUS_IDLE) && (m_status != STATUS_AWAITING))
1250         {
1251                 qWarning("Cannot accept drooped files at this time -> discarding!");
1252                 return;
1253         }
1254
1255         QStringList droppedFiles;
1256         QList<QUrl> urls = event->mimeData()->urls();
1257
1258         while(!urls.isEmpty())
1259         {
1260                 QUrl currentUrl = urls.takeFirst();
1261                 QFileInfo file(currentUrl.toLocalFile());
1262                 if(file.exists() && file.isFile())
1263                 {
1264                         qDebug("MainWindow::dropEvent: %s", file.canonicalFilePath().toUtf8().constData());
1265                         droppedFiles << file.canonicalFilePath();
1266                 }
1267         }
1268         
1269         if(droppedFiles.count() > 0)
1270         {
1271                 m_pendingFiles->append(droppedFiles);
1272                 m_pendingFiles->sort();
1273                 if(m_status != STATUS_AWAITING)
1274                 {
1275                         m_status = STATUS_AWAITING;
1276                         QTimer::singleShot(0, this, SLOT(handlePendingFiles()));
1277                 }
1278         }
1279 }
1280
1281 ///////////////////////////////////////////////////////////////////////////////
1282 // Private functions
1283 ///////////////////////////////////////////////////////////////////////////////
1284
1285 /*
1286  * Creates a new job
1287  */
1288 bool MainWindow::createJob(QString &sourceFileName, QString &outputFileName, OptionsModel *options, bool &runImmediately, const bool restart, int fileNo, int fileTotal, bool *applyToAll)
1289 {
1290         bool okay = false;
1291         AddJobDialog *addDialog = new AddJobDialog(this, options, m_recentlyUsed, m_sysinfo, m_preferences);
1292
1293         addDialog->setRunImmediately(runImmediately);
1294         if(!sourceFileName.isEmpty()) addDialog->setSourceFile(sourceFileName);
1295         if(!outputFileName.isEmpty()) addDialog->setOutputFile(outputFileName);
1296         if(restart) addDialog->setWindowTitle(tr("Restart Job"));
1297
1298         const bool multiFile = (fileNo >= 0) && (fileTotal > 1);
1299         if(multiFile)
1300         {
1301                 addDialog->setSourceEditable(false);
1302                 addDialog->setWindowTitle(addDialog->windowTitle().append(tr(" (File %1 of %2)").arg(QString::number(fileNo+1), QString::number(fileTotal))));
1303                 addDialog->setApplyToAllVisible(applyToAll);
1304         }
1305
1306         if(addDialog->exec() == QDialog::Accepted)
1307         {
1308                 sourceFileName = addDialog->sourceFile();
1309                 outputFileName = addDialog->outputFile();
1310                 runImmediately = addDialog->runImmediately();
1311                 if(applyToAll)
1312                 {
1313                         *applyToAll = addDialog->applyToAll();
1314                 }
1315                 okay = true;
1316         }
1317
1318         X264_DELETE(addDialog);
1319         return okay;
1320 }
1321
1322 /*
1323  * Creates a new job from *multiple* files
1324  */
1325 bool MainWindow::createJobMultiple(const QStringList &filePathIn)
1326 {
1327         QStringList::ConstIterator iter;
1328         bool applyToAll = false, runImmediately = false;
1329         int counter = 0;
1330
1331         //Add files individually
1332         for(iter = filePathIn.constBegin(); (iter != filePathIn.constEnd()) && (!applyToAll); iter++)
1333         {
1334                 runImmediately = (countRunningJobs() < (m_preferences->getAutoRunNextJob() ? m_preferences->getMaxRunningJobCount() : 1));
1335                 QString sourceFileName(*iter), outputFileName;
1336                 if(createJob(sourceFileName, outputFileName, m_options, runImmediately, false, counter++, filePathIn.count(), &applyToAll))
1337                 {
1338                         if(appendJob(sourceFileName, outputFileName, m_options, runImmediately))
1339                         {
1340                                 continue;
1341                         }
1342                 }
1343                 return false;
1344         }
1345
1346         //Add remaining files
1347         while(applyToAll && (iter != filePathIn.constEnd()))
1348         {
1349                 const bool runImmediatelyTmp = runImmediately && (countRunningJobs() < (m_preferences->getAutoRunNextJob() ? m_preferences->getMaxRunningJobCount() : 1));
1350                 const QString sourceFileName = *iter;
1351                 const QString outputFileName = AddJobDialog::generateOutputFileName(sourceFileName, m_recentlyUsed->outputDirectory(), m_recentlyUsed->filterIndex(), m_preferences->getSaveToSourcePath());
1352                 if(!appendJob(sourceFileName, outputFileName, m_options, runImmediatelyTmp))
1353                 {
1354                         return false;
1355                 }
1356                 iter++;
1357         }
1358
1359         return true;
1360 }
1361
1362 /*
1363  * Append a new job
1364  */
1365 bool MainWindow::appendJob(const QString &sourceFileName, const QString &outputFileName, OptionsModel *options, const bool runImmediately)
1366 {
1367         bool okay = false;
1368         EncodeThread *thrd = new EncodeThread(sourceFileName, outputFileName, options, m_sysinfo, m_preferences);
1369         QModelIndex newIndex = m_jobList->insertJob(thrd);
1370
1371         if(newIndex.isValid())
1372         {
1373                 if(runImmediately)
1374                 {
1375                         ui->jobsView->selectRow(newIndex.row());
1376                         QApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
1377                         m_jobList->startJob(newIndex);
1378                 }
1379
1380                 okay = true;
1381         }
1382
1383         m_label->setVisible(m_jobList->rowCount(QModelIndex()) == 0);
1384         return okay;
1385 }
1386
1387 /*
1388  * Jobs that are not completed (or failed, or aborted) yet
1389  */
1390 unsigned int MainWindow::countPendingJobs(void)
1391 {
1392         unsigned int count = 0;
1393         const int rows = m_jobList->rowCount(QModelIndex());
1394
1395         for(int i = 0; i < rows; i++)
1396         {
1397                 JobStatus status = m_jobList->getJobStatus(m_jobList->index(i, 0, QModelIndex()));
1398                 if(status != JobStatus_Completed && status != JobStatus_Aborted && status != JobStatus_Failed)
1399                 {
1400                         count++;
1401                 }
1402         }
1403
1404         return count;
1405 }
1406
1407 /*
1408  * Jobs that are still active, i.e. not terminated or enqueued
1409  */
1410 unsigned int MainWindow::countRunningJobs(void)
1411 {
1412         unsigned int count = 0;
1413         const int rows = m_jobList->rowCount(QModelIndex());
1414
1415         for(int i = 0; i < rows; i++)
1416         {
1417                 JobStatus status = m_jobList->getJobStatus(m_jobList->index(i, 0, QModelIndex()));
1418                 if(status != JobStatus_Completed && status != JobStatus_Aborted && status != JobStatus_Failed && status != JobStatus_Enqueued)
1419                 {
1420                         count++;
1421                 }
1422         }
1423
1424         return count;
1425 }
1426
1427 /*
1428  * Update all buttons with respect to current job status
1429  */
1430 void MainWindow::updateButtons(JobStatus status)
1431 {
1432         qDebug("MainWindow::updateButtons(void)");
1433
1434         ui->buttonStartJob->setEnabled(status == JobStatus_Enqueued);
1435         ui->buttonAbortJob->setEnabled(status == JobStatus_Indexing || status == JobStatus_Running || status == JobStatus_Running_Pass1 || status == JobStatus_Running_Pass2 || status == JobStatus_Paused);
1436         ui->buttonPauseJob->setEnabled(status == JobStatus_Indexing || status == JobStatus_Running || status == JobStatus_Paused || status == JobStatus_Running_Pass1 || status == JobStatus_Running_Pass2);
1437         ui->buttonPauseJob->setChecked(status == JobStatus_Paused || status == JobStatus_Pausing);
1438
1439         ui->actionJob_Delete->setEnabled(status == JobStatus_Completed || status == JobStatus_Aborted || status == JobStatus_Failed || status == JobStatus_Enqueued);
1440         ui->actionJob_Restart->setEnabled(status == JobStatus_Completed || status == JobStatus_Aborted || status == JobStatus_Failed || status == JobStatus_Enqueued);
1441         ui->actionJob_Browse->setEnabled(status == JobStatus_Completed);
1442
1443         ui->actionJob_Start->setEnabled(ui->buttonStartJob->isEnabled());
1444         ui->actionJob_Abort->setEnabled(ui->buttonAbortJob->isEnabled());
1445         ui->actionJob_Pause->setEnabled(ui->buttonPauseJob->isEnabled());
1446         ui->actionJob_Pause->setChecked(ui->buttonPauseJob->isChecked());
1447
1448         ui->editDetails->setEnabled(status != JobStatus_Paused);
1449 }
1450
1451 /*
1452  * Update the taskbar with current job status
1453  */
1454 void MainWindow::updateTaskbar(JobStatus status, const QIcon &icon)
1455 {
1456         qDebug("MainWindow::updateTaskbar(void)");
1457
1458         switch(status)
1459         {
1460         case JobStatus_Undefined:
1461                 WinSevenTaskbar::setTaskbarState(this, WinSevenTaskbar::WinSevenTaskbarNoState);
1462                 break;
1463         case JobStatus_Aborting:
1464         case JobStatus_Starting:
1465         case JobStatus_Pausing:
1466         case JobStatus_Resuming:
1467                 WinSevenTaskbar::setTaskbarState(this, WinSevenTaskbar::WinSevenTaskbarIndeterminateState);
1468                 break;
1469         case JobStatus_Aborted:
1470         case JobStatus_Failed:
1471                 WinSevenTaskbar::setTaskbarState(this, WinSevenTaskbar::WinSevenTaskbarErrorState);
1472                 break;
1473         case JobStatus_Paused:
1474                 WinSevenTaskbar::setTaskbarState(this, WinSevenTaskbar::WinSevenTaskbarPausedState);
1475                 break;
1476         default:
1477                 WinSevenTaskbar::setTaskbarState(this, WinSevenTaskbar::WinSevenTaskbarNormalState);
1478                 break;
1479         }
1480
1481         switch(status)
1482         {
1483         case JobStatus_Aborting:
1484         case JobStatus_Starting:
1485         case JobStatus_Pausing:
1486         case JobStatus_Resuming:
1487                 break;
1488         default:
1489                 WinSevenTaskbar::setTaskbarProgress(this, ui->progressBar->value(), ui->progressBar->maximum());
1490                 break;
1491         }
1492
1493         WinSevenTaskbar::setOverlayIcon(this, icon.isNull() ? NULL : &icon);
1494 }
1495
1496 /*
1497  * Parse command-line arguments
1498  */
1499 bool MainWindow::parseCommandLineArgs(void)
1500 {
1501         bool bCommandAccepted = false;
1502         unsigned int flags = 0;
1503
1504         //Initialize command-line parser
1505         CLIParser parser(x264_arguments());
1506         int identifier;
1507         QStringList options;
1508
1509         //Process all command-line arguments
1510         while(parser.nextOption(identifier, &options))
1511         {
1512                 switch(identifier)
1513                 {
1514                 case CLI_PARAM_ADD_FILE:
1515                         handleCommand(IPC_OPCODE_ADD_FILE, options, flags);
1516                         bCommandAccepted = true;
1517                         break;
1518                 case CLI_PARAM_ADD_JOB:
1519                         handleCommand(IPC_OPCODE_ADD_JOB, options, flags);
1520                         bCommandAccepted = true;
1521                         break;
1522                 case CLI_PARAM_FORCE_START:
1523                         flags = ((flags | IPC_FLAG_FORCE_START) & (~IPC_FLAG_FORCE_ENQUEUE));
1524                         break;
1525                 case CLI_PARAM_NO_FORCE_START:
1526                         flags = (flags & (~IPC_FLAG_FORCE_START));
1527                         break;
1528                 case CLI_PARAM_FORCE_ENQUEUE:
1529                         flags = ((flags | IPC_FLAG_FORCE_ENQUEUE) & (~IPC_FLAG_FORCE_START));
1530                         break;
1531                 case CLI_PARAM_NO_FORCE_ENQUEUE:
1532                         flags = (flags & (~IPC_FLAG_FORCE_ENQUEUE));
1533                         break;
1534                 }
1535         }
1536
1537         return bCommandAccepted;
1538 }