OSDN Git Service

Merge branch 'master' of github.com:lordmulder/LameXP
[lamexp/LameXP.git] / src / Dialog_Processing.cpp
1 ///////////////////////////////////////////////////////////////////////////////
2 // LameXP - Audio Encoder Front-End
3 // Copyright (C) 2004-2012 LoRd_MuldeR <MuldeR2@GMX.de>
4 //
5 // This program is free software; you can redistribute it and/or modify
6 // it under the terms of the GNU General Public License as published by
7 // the Free Software Foundation; either version 2 of the License, or
8 // (at your option) any later version.
9 //
10 // This program is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 // GNU General Public License for more details.
14 //
15 // You should have received a copy of the GNU General Public License along
16 // with this program; if not, write to the Free Software Foundation, Inc.,
17 // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 //
19 // http://www.gnu.org/licenses/gpl-2.0.txt
20 ///////////////////////////////////////////////////////////////////////////////
21
22 #include "Dialog_Processing.h"
23
24 #include "Global.h"
25 #include "Resource.h"
26 #include "Model_FileList.h"
27 #include "Model_Progress.h"
28 #include "Model_Settings.h"
29 #include "Thread_Process.h"
30 #include "Thread_CPUObserver.h"
31 #include "Thread_RAMObserver.h"
32 #include "Thread_DiskObserver.h"
33 #include "Dialog_LogView.h"
34 #include "Encoder_AAC.h"
35 #include "Encoder_AAC_FHG.h"
36 #include "Encoder_AAC_QAAC.h"
37 #include "Encoder_AC3.h"
38 #include "Encoder_DCA.h"
39 #include "Encoder_FLAC.h"
40 #include "Encoder_MP3.h"
41 #include "Encoder_Vorbis.h"
42 #include "Encoder_Opus.h"
43 #include "Encoder_Wave.h"
44 #include "Filter_Downmix.h"
45 #include "Filter_Normalize.h"
46 #include "Filter_Resample.h"
47 #include "Filter_ToneAdjust.h"
48 #include "WinSevenTaskbar.h"
49
50 #include <QApplication>
51 #include <QRect>
52 #include <QDesktopWidget>
53 #include <QMovie>
54 #include <QMessageBox>
55 #include <QTimer>
56 #include <QCloseEvent>
57 #include <QDesktopServices>
58 #include <QUrl>
59 #include <QUuid>
60 #include <QFileInfo>
61 #include <QDir>
62 #include <QMenu>
63 #include <QSystemTrayIcon>
64 #include <QProcess>
65 #include <QProgressDialog>
66 #include <QResizeEvent>
67 #include <QTime>
68
69 #include <MMSystem.h>
70 #include <math.h>
71 #include <float.h>
72
73 ////////////////////////////////////////////////////////////
74
75 //Maximum number of parallel instances
76 #define MAX_INSTANCES 16U
77
78 //Function to calculate the number of instances
79 static int cores2instances(int cores);
80
81 ////////////////////////////////////////////////////////////
82
83 #define CHANGE_BACKGROUND_COLOR(WIDGET, COLOR) do \
84 { \
85         QPalette palette = WIDGET->palette(); \
86         palette.setColor(QPalette::Background, COLOR); \
87         WIDGET->setPalette(palette); \
88 } \
89 while(0)
90
91 #define SET_PROGRESS_TEXT(TXT) do \
92 { \
93         label_progress->setText(TXT); \
94         m_systemTray->setToolTip(QString().sprintf("LameXP v%d.%02d\n%ls", lamexp_version_major(), lamexp_version_minor(), QString(TXT).utf16())); \
95 } \
96 while(0)
97
98 #define SET_FONT_BOLD(WIDGET,BOLD) do \
99 { \
100         QFont _font = WIDGET->font(); \
101         _font.setBold(BOLD); WIDGET->setFont(_font); \
102 } \
103 while(0)
104
105 #define SET_TEXT_COLOR(WIDGET, COLOR) do \
106 { \
107         QPalette _palette = WIDGET->palette(); \
108         _palette.setColor(QPalette::WindowText, (COLOR)); \
109         _palette.setColor(QPalette::Text, (COLOR)); \
110         WIDGET->setPalette(_palette); \
111 } \
112 while(0)
113
114 #define UPDATE_MIN_WIDTH(WIDGET) do \
115 { \
116         if(WIDGET->width() > WIDGET->minimumWidth()) WIDGET->setMinimumWidth(WIDGET->width()); \
117 } \
118 while(0)
119
120 ////////////////////////////////////////////////////////////
121 // Constructor
122 ////////////////////////////////////////////////////////////
123
124 ProcessingDialog::ProcessingDialog(FileListModel *fileListModel, AudioFileModel *metaInfo, SettingsModel *settings, QWidget *parent)
125 :
126         QDialog(parent),
127         m_systemTray(new QSystemTrayIcon(QIcon(":/icons/cd_go.png"), this)),
128         m_settings(settings),
129         m_metaInfo(metaInfo),
130         m_shutdownFlag(shutdownFlag_None),
131         m_diskObserver(NULL),
132         m_cpuObserver(NULL),
133         m_ramObserver(NULL),
134         m_progressViewFilter(-1),
135         m_firstShow(true)
136 {
137         //Init the dialog, from the .ui file
138         setupUi(this);
139         setWindowFlags(windowFlags() ^ Qt::WindowContextHelpButtonHint);
140         
141         //Update header icon
142         label_headerIcon->setPixmap(lamexp_app_icon().pixmap(label_headerIcon->size()));
143         
144         //Setup version info
145         label_versionInfo->setText(QString().sprintf("v%d.%02d %s (Build %d)", lamexp_version_major(), lamexp_version_minor(), lamexp_version_release(), lamexp_version_build()));
146         label_versionInfo->installEventFilter(this);
147
148         //Register meta type
149         qRegisterMetaType<QUuid>("QUuid");
150
151         //Center window in screen
152         QRect desktopRect = QApplication::desktop()->screenGeometry();
153         QRect thisRect = this->geometry();
154         move((desktopRect.width() - thisRect.width()) / 2, (desktopRect.height() - thisRect.height()) / 2);
155         setMinimumSize(thisRect.width(), thisRect.height());
156
157         //Enable buttons
158         connect(button_AbortProcess, SIGNAL(clicked()), this, SLOT(abortEncoding()));
159         
160         //Init progress indicator
161         m_progressIndicator = new QMovie(":/images/Working.gif");
162         m_progressIndicator->setCacheMode(QMovie::CacheAll);
163         m_progressIndicator->setSpeed(50);
164         label_headerWorking->setMovie(m_progressIndicator);
165         progressBar->setValue(0);
166
167         //Init progress model
168         m_progressModel = new ProgressModel();
169         view_log->setModel(m_progressModel);
170         view_log->verticalHeader()->setResizeMode(QHeaderView::ResizeToContents);
171         view_log->verticalHeader()->hide();
172         view_log->horizontalHeader()->setResizeMode(QHeaderView::ResizeToContents);
173         view_log->horizontalHeader()->setResizeMode(0, QHeaderView::Stretch);
174         view_log->viewport()->installEventFilter(this);
175         connect(m_progressModel, SIGNAL(rowsInserted(QModelIndex,int,int)), this, SLOT(progressModelChanged()));
176         connect(m_progressModel, SIGNAL(rowsMoved(QModelIndex,int,int,QModelIndex,int)), this, SLOT(progressModelChanged()));
177         connect(m_progressModel, SIGNAL(rowsRemoved(QModelIndex,int,int)), this, SLOT(progressModelChanged()));
178         connect(m_progressModel, SIGNAL(modelReset()), this, SLOT(progressModelChanged()));
179         connect(view_log, SIGNAL(activated(QModelIndex)), this, SLOT(logViewDoubleClicked(QModelIndex)));
180         connect(view_log->horizontalHeader(), SIGNAL(sectionResized(int,int,int)), this, SLOT(logViewSectionSizeChanged(int,int,int)));
181
182         //Create context menu
183         m_contextMenu = new QMenu();
184         QAction *contextMenuDetailsAction = m_contextMenu->addAction(QIcon(":/icons/zoom.png"), tr("Show details for selected job"));
185         QAction *contextMenuShowFileAction = m_contextMenu->addAction(QIcon(":/icons/folder_go.png"), tr("Browse Output File Location"));
186         m_contextMenu->addSeparator();
187
188         //Create "filter" context menu
189         m_progressViewFilterGroup = new QActionGroup(this);
190         QAction *contextMenuFilterAction[5] = {NULL, NULL, NULL, NULL, NULL};
191         if(QMenu *filterMenu = m_contextMenu->addMenu(QIcon(":/icons/filter.png"), tr("Filter Log Items")))
192         {
193                 contextMenuFilterAction[0] = filterMenu->addAction(QIcon(":/icons/media_play.png"), tr("Show Running Only"));
194                 contextMenuFilterAction[1] = filterMenu->addAction(QIcon(":/icons/tick.png"), tr("Show Succeeded Only"));
195                 contextMenuFilterAction[2] = filterMenu->addAction(QIcon(":/icons/exclamation.png"), tr("Show Failed Only"));
196                 contextMenuFilterAction[3] = filterMenu->addAction(QIcon(":/icons/step_over.png"), tr("Show Skipped Only"));
197                 contextMenuFilterAction[4] = filterMenu->addAction(QIcon(":/icons/report.png"), tr("Show All Items"));
198                 if(QAction *act = contextMenuFilterAction[0]) { m_progressViewFilterGroup->addAction(act); act->setCheckable(true); act->setData(ProgressModel::JobRunning); }
199                 if(QAction *act = contextMenuFilterAction[1]) { m_progressViewFilterGroup->addAction(act); act->setCheckable(true); act->setData(ProgressModel::JobComplete); }
200                 if(QAction *act = contextMenuFilterAction[2]) { m_progressViewFilterGroup->addAction(act); act->setCheckable(true); act->setData(ProgressModel::JobFailed); }
201                 if(QAction *act = contextMenuFilterAction[3]) { m_progressViewFilterGroup->addAction(act); act->setCheckable(true); act->setData(ProgressModel::JobSkipped); }
202                 if(QAction *act = contextMenuFilterAction[4]) { m_progressViewFilterGroup->addAction(act); act->setCheckable(true); act->setData(-1); act->setChecked(true); }
203         }
204
205         //Create info label
206         if(m_filterInfoLabel = new QLabel(view_log))
207         {
208                 m_filterInfoLabel->setFrameShape(QFrame::NoFrame);
209                 m_filterInfoLabel->setAlignment(Qt::AlignHCenter | Qt::AlignTop);
210                 m_filterInfoLabel->setUserData(0, reinterpret_cast<QObjectUserData*>(-1));
211                 SET_FONT_BOLD(m_filterInfoLabel, true);
212                 SET_TEXT_COLOR(m_filterInfoLabel, Qt::darkGray);
213                 m_filterInfoLabel->setContextMenuPolicy(Qt::CustomContextMenu);
214                 connect(m_filterInfoLabel, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(contextMenuTriggered(QPoint)));
215                 m_filterInfoLabel->hide();
216         }
217
218         //Connect context menu
219         view_log->setContextMenuPolicy(Qt::CustomContextMenu);
220         connect(view_log, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(contextMenuTriggered(QPoint)));
221         connect(contextMenuDetailsAction, SIGNAL(triggered(bool)), this, SLOT(contextMenuDetailsActionTriggered()));
222         connect(contextMenuShowFileAction, SIGNAL(triggered(bool)), this, SLOT(contextMenuShowFileActionTriggered()));
223         for(size_t i = 0; i < 5; i++)
224         {
225                 if(contextMenuFilterAction[i]) connect(contextMenuFilterAction[i], SIGNAL(triggered(bool)), this, SLOT(contextMenuFilterActionTriggered()));
226         }
227         SET_FONT_BOLD(contextMenuDetailsAction, true);
228
229         //Enque jobs
230         if(fileListModel)
231         {
232                 for(int i = 0; i < fileListModel->rowCount(); i++)
233                 {
234                         m_pendingJobs.append(fileListModel->getFile(fileListModel->index(i,0)));
235                 }
236         }
237
238         //Translate
239         label_headerStatus->setText(QString("<b>%1</b><br>%2").arg(tr("Encoding Files"), tr("Your files are being encoded, please be patient...")));
240         
241         //Enable system tray icon
242         connect(m_systemTray, SIGNAL(activated(QSystemTrayIcon::ActivationReason)), this, SLOT(systemTrayActivated(QSystemTrayIcon::ActivationReason)));
243
244         //Init other vars
245         m_runningThreads = 0;
246         m_currentFile = 0;
247         m_allJobs.clear();
248         m_succeededJobs.clear();
249         m_failedJobs.clear();
250         m_skippedJobs.clear();
251         m_userAborted = false;
252         m_forcedAbort = false;
253         m_timerStart = 0I64;
254 }
255
256 ////////////////////////////////////////////////////////////
257 // Destructor
258 ////////////////////////////////////////////////////////////
259
260 ProcessingDialog::~ProcessingDialog(void)
261 {
262         view_log->setModel(NULL);
263
264         if(m_progressIndicator)
265         {
266                 m_progressIndicator->stop();
267         }
268
269         if(m_diskObserver)
270         {
271                 m_diskObserver->stop();
272                 if(!m_diskObserver->wait(15000))
273                 {
274                         m_diskObserver->terminate();
275                         m_diskObserver->wait();
276                 }
277         }
278         if(m_cpuObserver)
279         {
280                 m_cpuObserver->stop();
281                 if(!m_cpuObserver->wait(15000))
282                 {
283                         m_cpuObserver->terminate();
284                         m_cpuObserver->wait();
285                 }
286         }
287         if(m_ramObserver)
288         {
289                 m_ramObserver->stop();
290                 if(!m_ramObserver->wait(15000))
291                 {
292                         m_ramObserver->terminate();
293                         m_ramObserver->wait();
294                 }
295         }
296
297         LAMEXP_DELETE(m_progressIndicator);
298         LAMEXP_DELETE(m_progressModel);
299         LAMEXP_DELETE(m_contextMenu);
300         LAMEXP_DELETE(m_systemTray);
301         LAMEXP_DELETE(m_diskObserver);
302         LAMEXP_DELETE(m_cpuObserver);
303         LAMEXP_DELETE(m_ramObserver);
304         LAMEXP_DELETE(m_progressViewFilterGroup);
305
306         WinSevenTaskbar::setOverlayIcon(this, NULL);
307         WinSevenTaskbar::setTaskbarState(this, WinSevenTaskbar::WinSevenTaskbarNoState);
308
309         while(!m_threadList.isEmpty())
310         {
311                 ProcessThread *thread = m_threadList.takeFirst();
312                 thread->terminate();
313                 thread->wait(15000);
314                 delete thread;
315         }
316
317 }
318
319 ////////////////////////////////////////////////////////////
320 // EVENTS
321 ////////////////////////////////////////////////////////////
322
323 void ProcessingDialog::showEvent(QShowEvent *event)
324 {
325         QDialog::showEvent(event);
326
327         if(m_firstShow)
328         {
329                 static const char *NA = " N/A";
330         
331                 setCloseButtonEnabled(false);
332                 button_closeDialog->setEnabled(false);
333                 button_AbortProcess->setEnabled(false);
334                 m_systemTray->setVisible(true);
335         
336                 if(!SetPriorityClass(GetCurrentProcess(), ABOVE_NORMAL_PRIORITY_CLASS))
337                 {
338                         SetPriorityClass(GetCurrentProcess(), HIGH_PRIORITY_CLASS);
339                 }
340
341                 label_cpu->setText(NA);
342                 label_disk->setText(NA);
343                 label_ram->setText(NA);
344
345                 QTimer::singleShot(1000, this, SLOT(initEncoding()));
346                 m_firstShow = false;
347         }
348
349         //Force update geometry
350         resizeEvent(NULL);
351 }
352
353 void ProcessingDialog::closeEvent(QCloseEvent *event)
354 {
355         if(!button_closeDialog->isEnabled())
356         {
357                 event->ignore();
358         }
359         else
360         {
361                 m_systemTray->setVisible(false);
362         }
363 }
364
365 bool ProcessingDialog::eventFilter(QObject *obj, QEvent *event)
366 {
367         static QColor defaultColor = QColor();
368
369         if(obj == label_versionInfo)
370         {
371                 if(event->type() == QEvent::Enter)
372                 {
373                         QPalette palette = label_versionInfo->palette();
374                         defaultColor = palette.color(QPalette::Normal, QPalette::WindowText);
375                         palette.setColor(QPalette::Normal, QPalette::WindowText, Qt::red);
376                         label_versionInfo->setPalette(palette);
377                 }
378                 else if(event->type() == QEvent::Leave)
379                 {
380                         QPalette palette = label_versionInfo->palette();
381                         palette.setColor(QPalette::Normal, QPalette::WindowText, defaultColor);
382                         label_versionInfo->setPalette(palette);
383                 }
384                 else if(event->type() == QEvent::MouseButtonPress)
385                 {
386                         QUrl url(lamexp_website_url());
387                         QDesktopServices::openUrl(url);
388                 }
389         }
390
391         return false;
392 }
393
394 bool ProcessingDialog::event(QEvent *e)
395 {
396         switch(e->type())
397         {
398         case lamexp_event_queryendsession:
399                 qWarning("System is shutting down, preparing to abort...");
400                 if(!m_userAborted) abortEncoding(true);
401                 return true;
402         case lamexp_event_endsession:
403                 qWarning("System is shutting down, encoding will be aborted now...");
404                 if(isVisible())
405                 {
406                         while(!close())
407                         {
408                                 if(!m_userAborted) abortEncoding(true);
409                                 QApplication::processEvents(QEventLoop::WaitForMoreEvents & QEventLoop::ExcludeUserInputEvents);
410                         }
411                 }
412                 m_pendingJobs.clear();
413                 return true;
414         default:
415                 return QDialog::event(e);
416         }
417 }
418
419 /*
420  * Window was resized
421  */
422 void ProcessingDialog::resizeEvent(QResizeEvent *event)
423 {
424         if(event) QDialog::resizeEvent(event);
425
426         if(QWidget *port = view_log->viewport())
427         {
428                 QRect geom = port->geometry();
429                 m_filterInfoLabel->setGeometry(geom.left() + 16, geom.top() + 16, geom.width() - 32, geom.height() - 32);
430         }
431 }
432
433 bool ProcessingDialog::winEvent(MSG *message, long *result)
434 {
435         return WinSevenTaskbar::handleWinEvent(message, result);
436 }
437
438 ////////////////////////////////////////////////////////////
439 // SLOTS
440 ////////////////////////////////////////////////////////////
441
442 void ProcessingDialog::initEncoding(void)
443 {
444         qDebug("Initializing encoding process...");
445         
446         m_runningThreads = 0;
447         m_currentFile = 0;
448         m_allJobs.clear();
449         m_succeededJobs.clear();
450         m_failedJobs.clear();
451         m_skippedJobs.clear();
452         m_userAborted = false;
453         m_forcedAbort = false;
454         m_playList.clear();
455         
456         CHANGE_BACKGROUND_COLOR(frame_header, QColor(Qt::white));
457         SET_PROGRESS_TEXT(tr("Encoding files, please wait..."));
458         m_progressIndicator->start();
459         
460         button_closeDialog->setEnabled(false);
461         button_AbortProcess->setEnabled(true);
462         progressBar->setRange(0, m_pendingJobs.count());
463         checkBox_shutdownComputer->setEnabled(true);
464         checkBox_shutdownComputer->setChecked(false);
465
466         WinSevenTaskbar::setTaskbarState(this, WinSevenTaskbar::WinSevenTaskbarNormalState);
467         WinSevenTaskbar::setTaskbarProgress(this, 0, m_pendingJobs.count());
468         WinSevenTaskbar::setOverlayIcon(this, &QIcon(":/icons/control_play_blue.png"));
469
470         if(!m_diskObserver)
471         {
472                 m_diskObserver = new DiskObserverThread(m_settings->customTempPathEnabled() ? m_settings->customTempPath() : lamexp_temp_folder2());
473                 connect(m_diskObserver, SIGNAL(messageLogged(QString,int)), m_progressModel, SLOT(addSystemMessage(QString,int)), Qt::QueuedConnection);
474                 connect(m_diskObserver, SIGNAL(freeSpaceChanged(quint64)), this, SLOT(diskUsageHasChanged(quint64)), Qt::QueuedConnection);
475                 m_diskObserver->start();
476         }
477         if(!m_cpuObserver)
478         {
479                 m_cpuObserver = new CPUObserverThread();
480                 connect(m_cpuObserver, SIGNAL(currentUsageChanged(double)), this, SLOT(cpuUsageHasChanged(double)), Qt::QueuedConnection);
481                 m_cpuObserver->start();
482         }
483         if(!m_ramObserver)
484         {
485                 m_ramObserver = new RAMObserverThread();
486                 connect(m_ramObserver, SIGNAL(currentUsageChanged(double)), this, SLOT(ramUsageHasChanged(double)), Qt::QueuedConnection);
487                 m_ramObserver->start();
488         }
489         
490         unsigned int maximumInstances = qBound(0U, m_settings->maximumInstances(), MAX_INSTANCES);
491         if(maximumInstances < 1)
492         {
493                 lamexp_cpu_t cpuFeatures = lamexp_detect_cpu_features(lamexp_arguments());
494                 maximumInstances = cores2instances(qBound(1, cpuFeatures.count, 64));
495         }
496
497         maximumInstances = qBound(1U, maximumInstances, static_cast<unsigned int>(m_pendingJobs.count()));
498         if(maximumInstances > 1)
499         {
500                 m_progressModel->addSystemMessage(tr("Multi-threading enabled: Running %1 instances in parallel!").arg(QString::number(maximumInstances)));
501         }
502
503         for(unsigned int i = 0; i < maximumInstances; i++)
504         {
505                 startNextJob();
506                 qApp->processEvents();
507         }
508
509         LARGE_INTEGER counter;
510         if(QueryPerformanceCounter(&counter))
511         {
512                 m_timerStart = counter.QuadPart;
513         }
514 }
515
516 void ProcessingDialog::abortEncoding(bool force)
517 {
518         m_userAborted = true;
519         if(force) m_forcedAbort = true;
520         button_AbortProcess->setEnabled(false);
521         SET_PROGRESS_TEXT(tr("Aborted! Waiting for running jobs to terminate..."));
522
523         for(int i = 0; i < m_threadList.count(); i++)
524         {
525                 m_threadList.at(i)->abort();
526         }
527 }
528
529 void ProcessingDialog::doneEncoding(void)
530 {
531         m_runningThreads--;
532         progressBar->setValue(progressBar->value() + 1);
533         
534         if(!m_userAborted)
535         {
536                 SET_PROGRESS_TEXT(tr("Encoding: %1 files of %2 completed so far, please wait...").arg(QString::number(progressBar->value()), QString::number(progressBar->maximum())));
537                 WinSevenTaskbar::setTaskbarProgress(this, progressBar->value(), progressBar->maximum());
538         }
539         
540         int index = m_threadList.indexOf(dynamic_cast<ProcessThread*>(QWidget::sender()));
541         if(index >= 0)
542         {
543                 m_threadList.takeAt(index)->deleteLater();
544         }
545
546         if(!m_pendingJobs.isEmpty() && !m_userAborted)
547         {
548                 startNextJob();
549                 qDebug("Running jobs: %u", m_runningThreads);
550                 return;
551         }
552         
553         if(m_runningThreads > 0)
554         {
555                 qDebug("Running jobs: %u", m_runningThreads);
556                 return;
557         }
558
559         QApplication::setOverrideCursor(Qt::WaitCursor);
560         qDebug("Running jobs: %u", m_runningThreads);
561
562         if(!m_userAborted && m_settings->createPlaylist() && !m_settings->outputToSourceDir())
563         {
564                 SET_PROGRESS_TEXT(tr("Creating the playlist file, please wait..."));
565                 QApplication::processEvents();
566                 writePlayList();
567         }
568         
569         if(m_userAborted)
570         {
571                 CHANGE_BACKGROUND_COLOR(frame_header, QColor("#FFF3BA"));
572                 WinSevenTaskbar::setTaskbarState(this, WinSevenTaskbar::WinSevenTaskbarErrorState);
573                 WinSevenTaskbar::setOverlayIcon(this, &QIcon(":/icons/error.png"));
574                 SET_PROGRESS_TEXT((m_succeededJobs.count() > 0) ? tr("Process was aborted by the user after %1 file(s)!").arg(QString::number(m_succeededJobs.count())) : tr("Process was aborted prematurely by the user!"));
575                 m_systemTray->showMessage(tr("LameXP - Aborted"), tr("Process was aborted by the user."), QSystemTrayIcon::Warning);
576                 m_systemTray->setIcon(QIcon(":/icons/cd_delete.png"));
577                 QApplication::processEvents();
578                 if(m_settings->soundsEnabled() && !m_forcedAbort)
579                 {
580                         PlaySound(MAKEINTRESOURCE(IDR_WAVE_ABORTED), GetModuleHandle(NULL), SND_RESOURCE | SND_SYNC);
581                 }
582         }
583         else
584         {
585                 LARGE_INTEGER counter, frequency;
586                 if(QueryPerformanceCounter(&counter) && QueryPerformanceFrequency(&frequency))
587                 {
588                         if((m_timerStart > 0I64) && (frequency.QuadPart > 0I64) && (m_timerStart < counter.QuadPart))
589                         {
590                                 double timeElapsed = static_cast<double>(counter.QuadPart - m_timerStart) / static_cast<double>(frequency.QuadPart);
591                                 m_progressModel->addSystemMessage(tr("Process finished after %1.").arg(time2text(timeElapsed)), ProgressModel::SysMsg_Performance);
592                         }
593                 }
594
595                 if(m_failedJobs.count() > 0)
596                 {
597                         CHANGE_BACKGROUND_COLOR(frame_header, QColor("#FFBABA"));
598                         WinSevenTaskbar::setTaskbarState(this, WinSevenTaskbar::WinSevenTaskbarErrorState);
599                         WinSevenTaskbar::setOverlayIcon(this, &QIcon(":/icons/exclamation.png"));
600                         if(m_skippedJobs.count() > 0)
601                         {
602                                 SET_PROGRESS_TEXT(tr("Error: %1 of %2 files failed (%3 files skipped). Double-click failed items for detailed information!").arg(QString::number(m_failedJobs.count()), QString::number(m_failedJobs.count() + m_succeededJobs.count() + m_skippedJobs.count()), QString::number(m_skippedJobs.count())));
603                         }
604                         else
605                         {
606                                 SET_PROGRESS_TEXT(tr("Error: %1 of %2 files failed. Double-click failed items for detailed information!").arg(QString::number(m_failedJobs.count()), QString::number(m_failedJobs.count() + m_succeededJobs.count())));
607                         }
608                         m_systemTray->showMessage(tr("LameXP - Error"), tr("At least one file has failed!"), QSystemTrayIcon::Critical);
609                         m_systemTray->setIcon(QIcon(":/icons/cd_delete.png"));
610                         QApplication::processEvents();
611                         if(m_settings->soundsEnabled()) PlaySound(MAKEINTRESOURCE(IDR_WAVE_ERROR), GetModuleHandle(NULL), SND_RESOURCE | SND_SYNC);
612                 }
613                 else
614                 {
615                         CHANGE_BACKGROUND_COLOR(frame_header, QColor("#E0FFE2"));
616                         WinSevenTaskbar::setTaskbarState(this, WinSevenTaskbar::WinSevenTaskbarNormalState);
617                         WinSevenTaskbar::setOverlayIcon(this, &QIcon(":/icons/accept.png"));
618                         if(m_skippedJobs.count() > 0)
619                         {
620                                 SET_PROGRESS_TEXT(tr("All files completed successfully. Skipped %1 files.").arg(QString::number(m_skippedJobs.count())));
621                         }
622                         else
623                         {
624                                 SET_PROGRESS_TEXT(tr("All files completed successfully."));
625                         }
626                         m_systemTray->showMessage(tr("LameXP - Done"), tr("All files completed successfully."), QSystemTrayIcon::Information);
627                         m_systemTray->setIcon(QIcon(":/icons/cd_add.png"));
628                         QApplication::processEvents();
629                         if(m_settings->soundsEnabled()) PlaySound(MAKEINTRESOURCE(IDR_WAVE_SUCCESS), GetModuleHandle(NULL), SND_RESOURCE | SND_SYNC);
630                 }
631         }
632         
633         setCloseButtonEnabled(true);
634         button_closeDialog->setEnabled(true);
635         button_AbortProcess->setEnabled(false);
636         checkBox_shutdownComputer->setEnabled(false);
637
638         m_progressModel->restoreHiddenItems();
639         view_log->scrollToBottom();
640         m_progressIndicator->stop();
641         progressBar->setValue(progressBar->maximum());
642         WinSevenTaskbar::setTaskbarProgress(this, progressBar->value(), progressBar->maximum());
643
644         QApplication::restoreOverrideCursor();
645
646         if(!m_userAborted && checkBox_shutdownComputer->isChecked())
647         {
648                 if(shutdownComputer())
649                 {
650                         m_shutdownFlag = m_settings->hibernateComputer() ? shutdownFlag_Hibernate : shutdownFlag_TurnPowerOff;
651                         accept();
652                 }
653         }
654 }
655
656 void ProcessingDialog::processFinished(const QUuid &jobId, const QString &outFileName, int success)
657 {
658         if(success > 0)
659         {
660                 m_playList.insert(jobId, outFileName);
661                 m_succeededJobs.append(jobId);
662         }
663         else if(success < 0)
664         {
665                 m_playList.insert(jobId, outFileName);
666                 m_skippedJobs.append(jobId);
667         }
668         else
669         {
670                 m_failedJobs.append(jobId);
671         }
672
673         //Update filter as soon as a job finished!
674         if(m_progressViewFilter >= 0)
675         {
676                 QTimer::singleShot(0, this, SLOT(progressViewFilterChanged()));
677         }
678 }
679
680 void ProcessingDialog::progressModelChanged(void)
681 {
682         //Update filter as soon as the model changes!
683         if(m_progressViewFilter >= 0)
684         {
685                 QTimer::singleShot(0, this, SLOT(progressViewFilterChanged()));
686         }
687
688         QTimer::singleShot(0, view_log, SLOT(scrollToBottom()));
689 }
690
691 void ProcessingDialog::logViewDoubleClicked(const QModelIndex &index)
692 {
693         if(m_runningThreads == 0)
694         {
695                 const QStringList &logFile = m_progressModel->getLogFile(index);
696                 
697                 if(!logFile.isEmpty())
698                 {
699                         LogViewDialog *logView = new LogViewDialog(this);
700                         logView->setWindowTitle(QString("LameXP - [%1]").arg(m_progressModel->data(index, Qt::DisplayRole).toString()));
701                         logView->exec(logFile);
702                         LAMEXP_DELETE(logView);
703                 }
704                 else
705                 {
706                         QMessageBox::information(this, windowTitle(), m_progressModel->data(m_progressModel->index(index.row(), 0)).toString());
707                 }
708         }
709         else
710         {
711                 MessageBeep(MB_ICONWARNING);
712         }
713 }
714
715 void ProcessingDialog::logViewSectionSizeChanged(int logicalIndex, int oldSize, int newSize)
716 {
717         if(logicalIndex == 1)
718         {
719                 if(QHeaderView *hdr = view_log->horizontalHeader())
720                 {
721                         hdr->setMinimumSectionSize(qMax(hdr->minimumSectionSize(), hdr->sectionSize(1)));
722                 }
723         }
724 }
725
726 void ProcessingDialog::contextMenuTriggered(const QPoint &pos)
727 {
728         QAbstractScrollArea *scrollArea = dynamic_cast<QAbstractScrollArea*>(QObject::sender());
729         QWidget *sender = scrollArea ? scrollArea->viewport() : dynamic_cast<QWidget*>(QObject::sender());      
730
731         if(pos.x() <= sender->width() && pos.y() <= sender->height() && pos.x() >= 0 && pos.y() >= 0)
732         {
733                 m_contextMenu->popup(sender->mapToGlobal(pos));
734         }
735 }
736
737 void ProcessingDialog::contextMenuDetailsActionTriggered(void)
738 {
739         QModelIndex index = view_log->indexAt(view_log->viewport()->mapFromGlobal(m_contextMenu->pos()));
740         logViewDoubleClicked(index.isValid() ? index : view_log->currentIndex());
741 }
742
743 void ProcessingDialog::contextMenuShowFileActionTriggered(void)
744 {
745         QModelIndex index = view_log->indexAt(view_log->viewport()->mapFromGlobal(m_contextMenu->pos()));
746         const QUuid &jobId = m_progressModel->getJobId(index.isValid() ? index : view_log->currentIndex());
747         QString filePath = m_playList.value(jobId, QString());
748
749         if(filePath.isEmpty())
750         {
751                 MessageBeep(MB_ICONWARNING);
752                 return;
753         }
754
755         if(QFileInfo(filePath).exists())
756         {
757                 QString systemRootPath;
758
759                 QDir systemRoot(lamexp_known_folder(lamexp_folder_systemfolder));
760                 if(systemRoot.exists() && systemRoot.cdUp())
761                 {
762                         systemRootPath = systemRoot.canonicalPath();
763                 }
764
765                 if(!systemRootPath.isEmpty())
766                 {
767                         QFileInfo explorer(QString("%1/explorer.exe").arg(systemRootPath));
768                         if(explorer.exists() && explorer.isFile())
769                         {
770                                 QProcess::execute(explorer.canonicalFilePath(), QStringList() << "/select," << QDir::toNativeSeparators(QFileInfo(filePath).canonicalFilePath()));
771                                 return;
772                         }
773                 }
774                 else
775                 {
776                         qWarning("SystemRoot directory could not be detected!");
777                 }
778         }
779         else
780         {
781                 qWarning("File not found: %s", filePath.toLatin1().constData());
782                 MessageBeep(MB_ICONERROR);
783         }
784 }
785
786 void ProcessingDialog::contextMenuFilterActionTriggered(void)
787 {
788         if(QAction *action = dynamic_cast<QAction*>(QObject::sender()))
789         {
790                 if(action->data().type() == QVariant::Int)
791                 {
792                         m_progressViewFilter = action->data().toInt();
793                         progressViewFilterChanged();
794                         QTimer::singleShot(0, this, SLOT(progressViewFilterChanged()));
795                         QTimer::singleShot(0, view_log, SLOT(scrollToBottom()));
796                         action->setChecked(true);
797                 }
798         }
799 }
800
801 /*
802  * Filter progress items
803  */
804 void ProcessingDialog::progressViewFilterChanged(void)
805 {
806         unsigned int counter = 0;
807
808         for(int i = 0; i < view_log->model()->rowCount(); i++)
809         {
810                 QModelIndex index = (m_progressViewFilter >= 0) ? m_progressModel->index(i, 0) : QModelIndex();
811                 const bool bHide = index.isValid() ? (m_progressModel->getJobState(index) != m_progressViewFilter) : false;
812                 view_log->setRowHidden(i, bHide);
813                 if(!bHide) counter++;
814         }
815
816         if((m_progressViewFilter >= 0) && (counter == 0))
817         {
818                 if(m_filterInfoLabel->isHidden() || (m_filterInfoLabel->userData(0) != reinterpret_cast<QObjectUserData*>(m_progressViewFilter)))
819                 {
820                         QString iconPath;
821                         switch(m_progressViewFilter)
822                         {
823                                 case ProgressModel::JobRunning: iconPath = ":/icons/media_play.png"; break;
824                                 case ProgressModel::JobComplete: iconPath = ":/icons/tick.png"; break;
825                                 case ProgressModel::JobFailed: iconPath = ":/icons/exclamation.png"; break;
826                                 case ProgressModel::JobSkipped: iconPath = ":/icons/step_over.png"; break;
827                                 default: iconPath = ":/icons/report.png"; break;
828                         }
829                         m_filterInfoLabel->show();
830                         m_filterInfoLabel->setText(QString("&raquo; %1 &laquo;<br><br><img src=\"%2\">").arg(tr("None of the items matches the current filtering rules"), iconPath));
831                         m_filterInfoLabel->setUserData(0, reinterpret_cast<QObjectUserData*>(m_progressViewFilter));
832                         resizeEvent(NULL);
833                 }
834         }
835         else if(!m_filterInfoLabel->isHidden())
836         {
837                 m_filterInfoLabel->hide();
838         }
839 }
840
841 ////////////////////////////////////////////////////////////
842 // Private Functions
843 ////////////////////////////////////////////////////////////
844
845 void ProcessingDialog::startNextJob(void)
846 {
847         if(m_pendingJobs.isEmpty())
848         {
849                 return;
850         }
851         
852         m_currentFile++;
853         AudioFileModel currentFile = updateMetaInfo(m_pendingJobs.takeFirst());
854         bool nativeResampling = false;
855
856         //Create encoder instance
857         AbstractEncoder *encoder = makeEncoder(&nativeResampling);
858
859         //Create processing thread
860         ProcessThread *thread = new ProcessThread
861         (
862                 currentFile,
863                 (m_settings->outputToSourceDir() ? QFileInfo(currentFile.filePath()).absolutePath() : m_settings->outputDir()),
864                 (m_settings->customTempPathEnabled() ? m_settings->customTempPath() : lamexp_temp_folder2()),
865                 encoder,
866                 m_settings->prependRelativeSourcePath() && (!m_settings->outputToSourceDir())
867         );
868
869         //Add audio filters
870         if(m_settings->forceStereoDownmix())
871         {
872                 thread->addFilter(new DownmixFilter());
873         }
874         if((m_settings->samplingRate() > 0) && !nativeResampling)
875         {
876                 if(SettingsModel::samplingRates[m_settings->samplingRate()] != currentFile.formatAudioSamplerate() || currentFile.formatAudioSamplerate() == 0)
877                 {
878                         thread->addFilter(new ResampleFilter(SettingsModel::samplingRates[m_settings->samplingRate()]));
879                 }
880         }
881         if((m_settings->toneAdjustBass() != 0) || (m_settings->toneAdjustTreble() != 0))
882         {
883                 thread->addFilter(new ToneAdjustFilter(m_settings->toneAdjustBass(), m_settings->toneAdjustTreble()));
884         }
885         if(m_settings->normalizationFilterEnabled())
886         {
887                 thread->addFilter(new NormalizeFilter(m_settings->normalizationFilterMaxVolume(), m_settings->normalizationFilterEqualizationMode()));
888         }
889         if(m_settings->renameOutputFilesEnabled() && (!m_settings->renameOutputFilesPattern().simplified().isEmpty()))
890         {
891                 thread->setRenamePattern(m_settings->renameOutputFilesPattern());
892         }
893         if(m_settings->overwriteMode() != SettingsModel::Overwrite_KeepBoth)
894         {
895                 thread->setOverwriteMode((m_settings->overwriteMode() == SettingsModel::Overwrite_SkipFile), (m_settings->overwriteMode() == SettingsModel::Overwrite_Replaces));
896         }
897
898         m_threadList.append(thread);
899         m_allJobs.append(thread->getId());
900         
901         //Connect thread signals
902         connect(thread, SIGNAL(finished()), this, SLOT(doneEncoding()), Qt::QueuedConnection);
903         connect(thread, SIGNAL(processStateInitialized(QUuid,QString,QString,int)), m_progressModel, SLOT(addJob(QUuid,QString,QString,int)), Qt::QueuedConnection);
904         connect(thread, SIGNAL(processStateChanged(QUuid,QString,int)), m_progressModel, SLOT(updateJob(QUuid,QString,int)), Qt::QueuedConnection);
905         connect(thread, SIGNAL(processStateFinished(QUuid,QString,int)), this, SLOT(processFinished(QUuid,QString,int)), Qt::QueuedConnection);
906         connect(thread, SIGNAL(processMessageLogged(QUuid,QString)), m_progressModel, SLOT(appendToLog(QUuid,QString)), Qt::QueuedConnection);
907         
908         //Give it a go!
909         m_runningThreads++;
910         thread->start();
911
912         //Give thread some advance
913         for(unsigned int i = 0; i < MAX_INSTANCES; i++)
914         {
915                 QThread::yieldCurrentThread();
916         }
917 }
918
919 AbstractEncoder *ProcessingDialog::makeEncoder(bool *nativeResampling)
920 {
921         AbstractEncoder *encoder =  NULL;
922         *nativeResampling = false;
923         
924         switch(m_settings->compressionEncoder())
925         {
926         case SettingsModel::MP3Encoder:
927                 {
928                         MP3Encoder *mp3Encoder = new MP3Encoder();
929                         mp3Encoder->setBitrate(m_settings->compressionBitrate());
930                         mp3Encoder->setRCMode(m_settings->compressionRCMode());
931                         mp3Encoder->setAlgoQuality(m_settings->lameAlgoQuality());
932                         if(m_settings->bitrateManagementEnabled())
933                         {
934                                 mp3Encoder->setBitrateLimits(m_settings->bitrateManagementMinRate(), m_settings->bitrateManagementMaxRate());
935                         }
936                         if(m_settings->samplingRate() > 0)
937                         {
938                                 mp3Encoder->setSamplingRate(SettingsModel::samplingRates[m_settings->samplingRate()]);
939                                 *nativeResampling = true;
940                         }
941                         mp3Encoder->setChannelMode(m_settings->lameChannelMode());
942                         mp3Encoder->setCustomParams(m_settings->customParametersLAME());
943                         encoder = mp3Encoder;
944                 }
945                 break;
946         case SettingsModel::VorbisEncoder:
947                 {
948                         VorbisEncoder *vorbisEncoder = new VorbisEncoder();
949                         vorbisEncoder->setBitrate(m_settings->compressionBitrate());
950                         vorbisEncoder->setRCMode(m_settings->compressionRCMode());
951                         if(m_settings->bitrateManagementEnabled())
952                         {
953                                 vorbisEncoder->setBitrateLimits(m_settings->bitrateManagementMinRate(), m_settings->bitrateManagementMaxRate());
954                         }
955                         if(m_settings->samplingRate() > 0)
956                         {
957                                 vorbisEncoder->setSamplingRate(SettingsModel::samplingRates[m_settings->samplingRate()]);
958                                 *nativeResampling = true;
959                         }
960                         vorbisEncoder->setCustomParams(m_settings->customParametersOggEnc());
961                         encoder = vorbisEncoder;
962                 }
963                 break;
964         case SettingsModel::AACEncoder:
965                 {
966                         if(lamexp_check_tool("qaac.exe") && lamexp_check_tool("libsoxrate.dll"))
967                         {
968                                 QAACEncoder *aacEncoder = new QAACEncoder();
969                                 aacEncoder->setBitrate(m_settings->compressionBitrate());
970                                 aacEncoder->setRCMode(m_settings->compressionRCMode());
971                                 aacEncoder->setProfile(m_settings->aacEncProfile());
972                                 aacEncoder->setCustomParams(m_settings->customParametersAacEnc());
973                                 encoder = aacEncoder;
974                         }
975                         else if(lamexp_check_tool("fhgaacenc.exe") && lamexp_check_tool("enc_fhgaac.dll"))
976                         {
977                                 FHGAACEncoder *aacEncoder = new FHGAACEncoder();
978                                 aacEncoder->setBitrate(m_settings->compressionBitrate());
979                                 aacEncoder->setRCMode(m_settings->compressionRCMode());
980                                 aacEncoder->setProfile(m_settings->aacEncProfile());
981                                 aacEncoder->setCustomParams(m_settings->customParametersAacEnc());
982                                 encoder = aacEncoder;
983                         }
984                         else
985                         {
986                                 AACEncoder *aacEncoder = new AACEncoder();
987                                 aacEncoder->setBitrate(m_settings->compressionBitrate());
988                                 aacEncoder->setRCMode(m_settings->compressionRCMode());
989                                 aacEncoder->setEnable2Pass(m_settings->neroAACEnable2Pass());
990                                 aacEncoder->setProfile(m_settings->aacEncProfile());
991                                 aacEncoder->setCustomParams(m_settings->customParametersAacEnc());
992                                 encoder = aacEncoder;
993                         }
994                 }
995                 break;
996         case SettingsModel::AC3Encoder:
997                 {
998                         AC3Encoder *ac3Encoder = new AC3Encoder();
999                         ac3Encoder->setBitrate(m_settings->compressionBitrate());
1000                         ac3Encoder->setRCMode(m_settings->compressionRCMode());
1001                         ac3Encoder->setCustomParams(m_settings->customParametersAften());
1002                         ac3Encoder->setAudioCodingMode(m_settings->aftenAudioCodingMode());
1003                         ac3Encoder->setDynamicRangeCompression(m_settings->aftenDynamicRangeCompression());
1004                         ac3Encoder->setExponentSearchSize(m_settings->aftenExponentSearchSize());
1005                         ac3Encoder->setFastBitAllocation(m_settings->aftenFastBitAllocation());
1006                         encoder = ac3Encoder;
1007                 }
1008                 break;
1009         case SettingsModel::FLACEncoder:
1010                 {
1011                         FLACEncoder *flacEncoder = new FLACEncoder();
1012                         flacEncoder->setBitrate(m_settings->compressionBitrate());
1013                         flacEncoder->setRCMode(m_settings->compressionRCMode());
1014                         flacEncoder->setCustomParams(m_settings->customParametersFLAC());
1015                         encoder = flacEncoder;
1016                 }
1017                 break;
1018         case SettingsModel::OpusEncoder:
1019                 {
1020                         OpusEncoder *opusEncoder = new OpusEncoder();
1021                         opusEncoder->setBitrate(m_settings->compressionBitrate());
1022                         opusEncoder->setRCMode(m_settings->compressionRCMode());
1023                         opusEncoder->setOptimizeFor(m_settings->opusOptimizeFor());
1024                         opusEncoder->setEncodeComplexity(m_settings->opusComplexity());
1025                         opusEncoder->setFrameSize(m_settings->opusFramesize());
1026                         opusEncoder->setExpAnalysisOn(m_settings->opusExpAnalysis());
1027                         opusEncoder->setCustomParams(m_settings->customParametersOpus());
1028                         encoder = opusEncoder;
1029                 }
1030                 break;  case SettingsModel::DCAEncoder:
1031                 {
1032                         DCAEncoder *dcaEncoder = new DCAEncoder();
1033                         dcaEncoder->setBitrate(m_settings->compressionBitrate());
1034                         dcaEncoder->setRCMode(m_settings->compressionRCMode());
1035                         encoder = dcaEncoder;
1036                 }
1037                 break;
1038         case SettingsModel::PCMEncoder:
1039                 {
1040                         WaveEncoder *waveEncoder = new WaveEncoder();
1041                         waveEncoder->setBitrate(m_settings->compressionBitrate());
1042                         waveEncoder->setRCMode(m_settings->compressionRCMode());
1043                         encoder = waveEncoder;
1044                 }
1045                 break;
1046         default:
1047                 throw "Unsupported encoder!";
1048         }
1049
1050         return encoder;
1051 }
1052
1053 void ProcessingDialog::writePlayList(void)
1054 {
1055         if(m_succeededJobs.count() <= 0 || m_allJobs.count() <= 0)
1056         {
1057                 qWarning("WritePlayList: Nothing to do!");
1058                 return;
1059         }
1060         
1061         //Init local variables
1062         QStringList list;
1063         QRegExp regExp1("\\[\\d\\d\\][^/\\\\]+$", Qt::CaseInsensitive);
1064         QRegExp regExp2("\\(\\d\\d\\)[^/\\\\]+$", Qt::CaseInsensitive);
1065         QRegExp regExp3("\\d\\d[^/\\\\]+$", Qt::CaseInsensitive);
1066         bool usePrefix[3] = {true, true, true};
1067         bool useUtf8 = false;
1068         int counter = 1;
1069
1070         //Generate playlist name
1071         QString playListName = (m_metaInfo->fileAlbum().isEmpty() ? "Playlist" : m_metaInfo->fileAlbum());
1072         if(!m_metaInfo->fileArtist().isEmpty())
1073         {
1074                 playListName = QString("%1 - %2").arg(m_metaInfo->fileArtist(), playListName);
1075         }
1076
1077         //Clean playlist name
1078         playListName = lamexp_clean_filename(playListName);
1079
1080         //Create list of audio files
1081         for(int i = 0; i < m_allJobs.count(); i++)
1082         {
1083                 if(!m_succeededJobs.contains(m_allJobs.at(i))) continue;
1084                 list << QDir::toNativeSeparators(QDir(m_settings->outputDir()).relativeFilePath(m_playList.value(m_allJobs.at(i), "N/A")));
1085         }
1086
1087         //Use prefix?
1088         for(int i = 0; i < list.count(); i++)
1089         {
1090                 if(regExp1.indexIn(list.at(i)) < 0) usePrefix[0] = false;
1091                 if(regExp2.indexIn(list.at(i)) < 0) usePrefix[1] = false;
1092                 if(regExp3.indexIn(list.at(i)) < 0) usePrefix[2] = false;
1093         }
1094         if(usePrefix[0] || usePrefix[1] || usePrefix[2])
1095         {
1096                 playListName.prepend(usePrefix[0] ? "[00] " : (usePrefix[1] ? "(00) " : "00 "));
1097         }
1098
1099         //Do we need an UTF-8 playlist?
1100         for(int i = 0; i < list.count(); i++)
1101         {
1102                 if(wcscmp(QWCHAR(QString::fromLatin1(list.at(i).toLatin1().constData())), QWCHAR(list.at(i))))
1103                 {
1104                         useUtf8 = true;
1105                         break;
1106                 }
1107         }
1108
1109         //Generate playlist output file
1110         QString playListFile = QString("%1/%2.%3").arg(m_settings->outputDir(), playListName, (useUtf8 ? "m3u8" : "m3u"));
1111         while(QFileInfo(playListFile).exists())
1112         {
1113                 playListFile = QString("%1/%2 (%3).%4").arg(m_settings->outputDir(), playListName, QString::number(++counter), (useUtf8 ? "m3u8" : "m3u"));
1114         }
1115
1116         //Now write playlist to output file
1117         QFile playList(playListFile);
1118         if(playList.open(QIODevice::WriteOnly))
1119         {
1120                 if(useUtf8)
1121                 {
1122                         playList.write("\xef\xbb\xbf");
1123                 }
1124                 playList.write("#EXTM3U\r\n");
1125                 while(!list.isEmpty())
1126                 {
1127                         playList.write(useUtf8 ? list.takeFirst().toUtf8().constData() : list.takeFirst().toLatin1().constData());
1128                         playList.write("\r\n");
1129                 }
1130                 playList.close();
1131         }
1132         else
1133         {
1134                 QMessageBox::warning(this, tr("Playlist creation failed"), QString("%1<br><nobr>%2</nobr>").arg(tr("The playlist file could not be created:"), playListFile));
1135         }
1136 }
1137
1138 AudioFileModel ProcessingDialog::updateMetaInfo(const AudioFileModel &audioFile)
1139 {
1140         if(!m_settings->writeMetaTags())
1141         {
1142                 return AudioFileModel(audioFile, false);
1143         }
1144         
1145         AudioFileModel result = audioFile;
1146         result.updateMetaInfo(*m_metaInfo);
1147         
1148         if(m_metaInfo->filePosition() == UINT_MAX)
1149         {
1150                 result.setFilePosition(m_currentFile);
1151         }
1152
1153         return result;
1154 }
1155
1156 void ProcessingDialog::setCloseButtonEnabled(bool enabled)
1157 {
1158         HMENU hMenu = GetSystemMenu((HWND) winId(), FALSE);
1159         EnableMenuItem(hMenu, SC_CLOSE, MF_BYCOMMAND | (enabled ? MF_ENABLED : MF_GRAYED));
1160 }
1161
1162 void ProcessingDialog::systemTrayActivated(QSystemTrayIcon::ActivationReason reason)
1163 {
1164         if(reason == QSystemTrayIcon::DoubleClick)
1165         {
1166                 SetForegroundWindow(reinterpret_cast<HWND>(this->winId()));
1167         }
1168 }
1169
1170 void ProcessingDialog::cpuUsageHasChanged(const double val)
1171 {
1172         
1173         this->label_cpu->setText(QString().sprintf(" %d%%", qRound(val * 100.0)));
1174         UPDATE_MIN_WIDTH(label_cpu);
1175 }
1176
1177 void ProcessingDialog::ramUsageHasChanged(const double val)
1178 {
1179         
1180         this->label_ram->setText(QString().sprintf(" %d%%", qRound(val * 100.0)));
1181         UPDATE_MIN_WIDTH(label_ram);
1182 }
1183
1184 void ProcessingDialog::diskUsageHasChanged(const quint64 val)
1185 {
1186         int postfix = 0;
1187         const char *postfixStr[6] = {"B", "KB", "MB", "GB", "TB", "PB"};
1188         double space = static_cast<double>(val);
1189
1190         while((space >= 1000.0) && (postfix < 5))
1191         {
1192                 space = space / 1024.0;
1193                 postfix++;
1194         }
1195
1196         this->label_disk->setText(QString().sprintf(" %3.1f %s", space, postfixStr[postfix]));
1197         UPDATE_MIN_WIDTH(label_disk);
1198 }
1199
1200 bool ProcessingDialog::shutdownComputer(void)
1201 {
1202         const int iTimeout = m_settings->hibernateComputer() ? 10 : 30;
1203         const Qt::WindowFlags flags = Qt::WindowStaysOnTopHint | Qt::CustomizeWindowHint | Qt::WindowTitleHint | Qt::MSWindowsFixedSizeDialogHint | Qt::WindowSystemMenuHint;
1204         const QString text = QString("%1%2%1").arg(QString().fill(' ', 18), tr("Warning: Computer will shutdown in %1 seconds..."));
1205         
1206         qWarning("Initiating shutdown sequence!");
1207         
1208         QProgressDialog progressDialog(text.arg(iTimeout), tr("Cancel Shutdown"), 0, iTimeout + 1, this, flags);
1209         QPushButton *cancelButton = new QPushButton(tr("Cancel Shutdown"), &progressDialog);
1210         cancelButton->setIcon(QIcon(":/icons/power_on.png"));
1211         progressDialog.setModal(true);
1212         progressDialog.setAutoClose(false);
1213         progressDialog.setAutoReset(false);
1214         progressDialog.setWindowIcon(QIcon(":/icons/power_off.png"));
1215         progressDialog.setCancelButton(cancelButton);
1216         progressDialog.show();
1217         
1218         QApplication::processEvents();
1219
1220         if(m_settings->soundsEnabled())
1221         {
1222                 QApplication::setOverrideCursor(Qt::WaitCursor);
1223                 PlaySound(MAKEINTRESOURCE(IDR_WAVE_SHUTDOWN), GetModuleHandle(NULL), SND_RESOURCE | SND_SYNC);
1224                 QApplication::restoreOverrideCursor();
1225         }
1226
1227         QTimer timer;
1228         timer.setInterval(1000);
1229         timer.start();
1230
1231         QEventLoop eventLoop(this);
1232         connect(&timer, SIGNAL(timeout()), &eventLoop, SLOT(quit()));
1233         connect(&progressDialog, SIGNAL(canceled()), &eventLoop, SLOT(quit()));
1234
1235         for(int i = 1; i <= iTimeout; i++)
1236         {
1237                 eventLoop.exec();
1238                 if(progressDialog.wasCanceled())
1239                 {
1240                         progressDialog.close();
1241                         return false;
1242                 }
1243                 progressDialog.setValue(i+1);
1244                 progressDialog.setLabelText(text.arg(iTimeout-i));
1245                 if(iTimeout-i == 3) progressDialog.setCancelButton(NULL);
1246                 QApplication::processEvents();
1247                 PlaySound(MAKEINTRESOURCE((i < iTimeout) ? IDR_WAVE_BEEP : IDR_WAVE_BEEP_LONG), GetModuleHandle(NULL), SND_RESOURCE | SND_SYNC);
1248         }
1249         
1250         progressDialog.close();
1251         return true;
1252 }
1253
1254 QString ProcessingDialog::time2text(const double timeVal) const
1255 {
1256         double intPart = 0;
1257         double frcPart = modf(timeVal, &intPart);
1258         int x = 0, y = 0; QString a, b;
1259
1260         QTime time = QTime().addSecs(qRound(intPart)).addMSecs(qRound(frcPart * 1000.0));
1261
1262         if(time.hour() > 0)
1263         {
1264                 x = time.hour();   a = tr("hour(s)");
1265                 y = time.minute(); b = tr("minute(s)");
1266         }
1267         else if(time.minute() > 0)
1268         {
1269                 x = time.minute(); a = tr("minute(s)");
1270                 y = time.second(); b = tr("second(s)");
1271         }
1272         else
1273         {
1274                 x = time.second(); a = tr("second(s)");
1275                 y = time.msec();   b = tr("millisecond(s)");
1276         }
1277
1278         return QString("%1 %2, %3 %4").arg(QString::number(x), a, QString::number(y), b);
1279 }
1280
1281 ////////////////////////////////////////////////////////////
1282 // HELPER FUNCTIONS
1283 ////////////////////////////////////////////////////////////
1284
1285 static int cores2instances(int cores)
1286 {
1287         //This function is a "cubic spline" with sampling points at:
1288         //(1,1); (2,2); (4,4); (8,6); (16,8); (32,11); (64,16)
1289         static const double LUT[8][5] =
1290         {
1291                 { 1.0,  0.014353554, -0.043060662, 1.028707108,  0.000000000},
1292                 { 2.0, -0.028707108,  0.215303309, 0.511979167,  0.344485294},
1293                 { 4.0,  0.010016468, -0.249379596, 2.370710784, -2.133823529},
1294                 { 8.0,  0.000282437, -0.015762868, 0.501776961,  2.850000000},
1295                 {16.0,  0.000033270, -0.003802849, 0.310416667,  3.870588235},
1296                 {32.0,  0.000006343, -0.001217831, 0.227696078,  4.752941176},
1297                 {64.0,  0.000000000,  0.000000000, 0.000000000, 16.000000000},
1298                 {DBL_MAX, 0.0, 0.0, 0.0, 0.0}
1299         };
1300
1301         double x = abs(static_cast<double>(cores)), y = 1.0;
1302         
1303         for(size_t i = 0; i < 7; i++)
1304         {
1305                 if((x >= LUT[i][0]) && (x < LUT[i+1][0]))
1306                 {
1307                         y = (((((LUT[i][1] * x) + LUT[i][2]) * x) + LUT[i][3]) * x) + LUT[i][4];
1308                         break;
1309                 }
1310         }
1311
1312         return qRound(y);
1313 }