OSDN Git Service

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