/*
Copyright (C) 2010-2020 Arnaud Champenois arthelion@free.fr
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include "about.h"
#include <QFileDialog>
#include <QErrorMessage>
#include <QMessageBox>
#include <sstream>
#include <QDateTime>
#include <QDragEnterEvent>
#include <QMimeData>
#include <QDropEvent>
#include <QUrl>
#include <QThread>
#include <QtGlobal>
#include <version.h>
#include <io/sound_mp3.h>
#include <omp.h>
#include "parameters.h"
#include "settingsreader.h"
#include <QSettings>
#include <QInputDialog>
#include <windows.h>
#include <winbase.h>
#include <sound_algo.h>
#include <QDesktopServices>
#define FILE_COL 3
#define TIME_COL 2
#define ICON_COL 1
using namespace SoundTools;
//=================================
// Construction/destruction
//=================================
MainWindow::MainWindow(QWidget *parent,bool aDebugFlag) :
QMainWindow(parent),
ui(new Ui::MainWindow),
mChecker(QUrl("https://www.arthelion.com/images/arthelion/lastar/LASTAR_info.json"))
{
qRegisterMetaType<SoundTools::Status>();
ui->setupUi(this);
QString aString = "LASTAR Version ";
aString += LASTAR_VERSION_CODE;
LogInfo (aString);
aString = tr("Built of ");
aString += LASTAR_VERSION_DATE;
LogMessage (aString);
LogMessage(tr("Ctrl+O to open file or drop file(s) or directory(ies) here."));
QObject::connect(&mChecker, SIGNAL(versionReceived(VersionChecker::Version)),
this, SLOT(versionReceived(VersionChecker::Version)));
QObject::connect(&mChecker, SIGNAL(versionError()),
this, SLOT(versionError()));
mStopCalled = false; // Etat de la commande
mProcessing = false; // Etat du process
mStopDisplayed = false; // Etat de l'IHM
mIsBatch = false;
mRestartPlay = false;
// Première version
mLastVersionCheck = LASTAR_VERSION_NUMBER;
mProcessor = std::make_unique<LASTARProcessor>();
connectProcessor();
initProcessingUI();
ui->action_Statistics->setEnabled (false);
ui->actionLaunch_processing->setEnabled (false);
ui->StartSlider->setEnabled(false);
ui->mAllCheck->setVisible(false);
ui->IgnoreIfExistsToggle->setVisible(false);
ui->mLaunchButton->setIcon(QIcon(":/gnome/system-run.png"));
SoundAlgo::setOStream(new std::ostream(this));
ui->fileProgress->setVisible(false);
ui->mProgressBar->setRange (0,100);
// TODO remplacer par une constante
mDefaultTags.mComment = (tr("Processed by LASTAR ")+LASTAR_VERSION_CODE).toUtf8().data();
if(!SoundWriterMp3::IsInit())
{
LogWarning(tr("To be able to save mp3 install libmp3lame-0.dll or lame_enc.dll in the program directory."));
ui->mOutputBox->setCurrentIndex(1);
ui->mOutputBox->setEnabled(false);
setWindowTitle(tr("LASTAR (no mp3)"));
}
SoundAlgo::setDebug(aDebugFlag);
SoundAlgo::setVerbose(aDebugFlag);
if(aDebugFlag)
LogMessage("Debug mode");
ui->KeepInput->setChecked(true);
ui->DeleteUserSettings->setEnabled(false);
ReadSettings();
mChecker.check();
ui->FileTable->SetMainWindow(this);
ui->MainTab->setCurrentIndex(0);
QHeaderView *aHViewPt = ui->FileTable->header ();
aHViewPt->setSectionResizeMode(QHeaderView::ResizeToContents);
ui->FileTable->hideColumn(0);
setAcceptDrops(true);
on_FileTable_itemSelectionChanged();
connect(this,SIGNAL(setAmpLevel(double)),mProcessor.get(),SLOT(ampBias(double)),Qt::QueuedConnection);
connect(this,SIGNAL(logRequestDisplay()),this,SLOT(logDoDisplay()),Qt::QueuedConnection);
connect(this,SIGNAL(sendStatus(SoundTools::Status)),this,SLOT(processingHasFinished(SoundTools::Status)),Qt::QueuedConnection);
connect(this,SIGNAL(statsComputed()),SLOT(statsRecomputed()),Qt::QueuedConnection);
connect(this,SIGNAL(opened()),SLOT(setOpenedStatus()),Qt::QueuedConnection);
connect(this,SIGNAL(toggleStopButton()),SLOT(ToggleStopButton()),Qt::QueuedConnection);
setClosedStatus();
}
MainWindow::~MainWindow()
{
Close();
SaveSettings();
delete ui;
}
void MainWindow::getTitleFromFileName(QString fileName,std::string&titleName,std::string&trackNumber) {
QRegularExpression regExp("^(?<trackNumber>[0-9]+)(?:[\\.-_,;:\\s]+)(?:.*)");
QRegularExpressionMatch match = regExp.match(fileName);
if(match.hasMatch()) {
trackNumber = match.captured("trackNumber").toStdString();
titleName = match.captured("title").toStdString();
}
else titleName = fileName.toStdString();
}
void MainWindow::displayStatus(QString status)
{
ui->statusBar->showMessage (status);
}
void MainWindow::initProcessingUI()
{
ui->mMusicTypeCombo->clear();
std::vector<const char*> anArray = ProcessParameters::GetNameArray();
for(unsigned i=0;i<anArray.size();i++)
{
ProcessParameters *aParam = new ProcessParameters;
aParam->InitFactorySettings(anArray[i]);
ui->mMusicTypeCombo->addItem(tr(aParam->mName.toUtf8()),qVariantFromValue((void*)(aParam)));
if(i==0) { setCurrentParam(aParam); }
}
}
void MainWindow::connectProcessor()
{
connect(mProcessor.get(),SIGNAL(logError(QString)),SLOT(LogError(QString)),
Qt::QueuedConnection);
connect(mProcessor.get(),SIGNAL(logInfo(QString)),SLOT(LogInfo(QString)),
Qt::QueuedConnection);
connect(mProcessor.get(),SIGNAL(logMessage(QString)),SLOT(LogMessage(QString)),
Qt::QueuedConnection);
connect(mProcessor.get(),SIGNAL(logWarning(QString)),SLOT(LogWarning(QString)),
Qt::QueuedConnection);
connect(mProcessor.get(),SIGNAL(displayStatus(QString)),SLOT(displayStatus(QString)),
Qt::QueuedConnection);
connect(mProcessor.get(),SIGNAL(closed()),SLOT(setClosedStatus()),
Qt::QueuedConnection);
connect(mProcessor.get(),SIGNAL(playerHasStopped()),SLOT(playingHasStopped()),
Qt::QueuedConnection);
connect(mProcessor.get(),SIGNAL(progress(int)),SLOT(progress(int)),
Qt::QueuedConnection);
connect(mProcessor.get(),SIGNAL(processingBegins()),SLOT(processingBegins()),
Qt::QueuedConnection);
connect(this,SIGNAL(setAmpLevel(double)),mProcessor.get(),SLOT(ampBias(double)),
Qt::QueuedConnection);
}
//=================================
// on_mLaunchButton_clicked
//=================================
void MainWindow::on_mLaunchButton_clicked()
{
// Protects SoundReaderPt
if (mProcessor->isClosed()) return;
if (!mStopCalled && mProcessing)
{
mStopCalled = true;
mProcessor->stop();
return;
}
if(mProcessing) return; // On n'est pas encore sorti du coup d'avant
bool isFirstBatch = !mIsBatch;
if(isFirstBatch && HasFiles()>1 && ui->mAllCheck->isChecked())
{
// Mode batch si plusieurs fichiers à traiter et qu'on demande à tous les traiter
ui->fileProgress->setVisible(true);
ui->fileProgress->setRange(0,HasFiles());
ui->fileProgress->setValue(0);
mIsBatch = true;
}
SetOutputFileName();
if (mOutputFileName.isEmpty() )
{
LogError(tr("No output file"));
return;
}
if(GetParameters()) return;
// Sur une sortie de mp3, pour le premier fichier, on peut demander les tags par défaut
if(isFirstBatch)
{
SoundIDTag aTags;
if(!mProcessor->getTags(aTags))
{
Parameters aParam(this);
getTitleFromFileName(mInputFileName.getBaseFileName(),mCurrentParamPt->mTags.mTitle,mCurrentParamPt->mTags.mTrack);
if(mIsBatch) // En batch, le titre provient toujours du fichier
aParam.LockTitle();
aParam.GetParam(*mCurrentParamPt,true);
}
}
else if(mIsBatch)
{
// On est sur un batch (pas le premier) : on récupère le nom du fichier de toutes façons
getTitleFromFileName(mInputFileName.getBaseFileName(),mCurrentParamPt->mTags.mTitle,mCurrentParamPt->mTags.mTrack);
}
emit toggleStopButton();
if(!QFile::exists(mOutputFileName.getPath()) || !ui->IgnoreIfExistsToggle->isChecked())
{
std::thread ampThread([this]() {
mProcessing = true;
mStopCalled = false;
Status aStatus = mProcessor->amplify(mOutputFileName);
if(aStatus != STATUS_WAITING) // Should be in playing so never here...
emit sendStatus(aStatus);
});
ampThread.detach();
}
else {
emit sendStatus(SoundTools::STATUS_SUCCESS);
LogWarning(tr("Output file exists ! No processing."));
}
}
//=================================
// FileList management
//=================================
int MainWindow::HasFiles()
{ return ui->FileTable->topLevelItemCount(); }
void MainWindow::OpenIfClosed()
{
if(mProcessor->isClosed() && HasFiles())
OpenFile(GetFirstFileName());
}
void MainWindow::UpdateGuiFromList()
{
if(HasFiles())
{
ui->mAllCheck->setChecked(true);
bool multiFile = ((!mIsBatch && HasFiles()>1) ||
(mIsBatch && ui->IgnoreIfExistsToggle->isChecked()));
bool multiVisible = (HasFiles()>1 || mIsBatch);
ui->mAllCheck->setVisible(multiVisible);
ui->IgnoreIfExistsToggle->setVisible(multiVisible);
ui->IgnoreIfExistsToggle->setChecked(multiFile);
ui->MainTab->setCurrentIndex(1);
}
else
{
ui->mAllCheck->setVisible(false);
ui->IgnoreIfExistsToggle->setVisible(false);
ui->IgnoreIfExistsToggle->setChecked(false);
ui->MainTab->setCurrentIndex(0);
}
}
InputFileEntry *MainWindow::GetFirstEntry()
{
if(!HasFiles()) return nullptr;
return (InputFileEntry*)ui->FileTable->topLevelItem(0);
}
void MainWindow::DeleteTableSelection()
{
if(mProcessing) return;
QList<QTreeWidgetItem *> aSelRanges = ui->FileTable->selectedItems ();
QList<QTreeWidgetItem *>::iterator anIter;
for(anIter = aSelRanges.begin();
anIter != aSelRanges.end();
anIter++)
{
QTreeWidgetItem*anItemPt = (*anIter);
int anIndex = ui->FileTable->indexOfTopLevelItem(anItemPt);
#pragma omp critical
{
if(anIndex == 0 && !mProcessing) Close();
if(anIndex != 0 || !mProcessing)
delete ui->FileTable->takeTopLevelItem(anIndex);
}
}
UpdateGuiFromList();
OpenIfClosed();
}
void MainWindow::AddFileList(QStringList&aList, bool anErase, QString*directory, QDir*anInputDir, LoadingProgressWindow *win)
{
if(mProcessing) return;
if(anErase)
{
ClearFiles();
}
if(win && win->isStopped()) return;
QStringList::iterator anIter;
for(anIter = aList.begin();
anIter != aList.end();anIter++)
{
if(*anIter == "." || *anIter == "..") continue;
QString fullFileName;
if(anInputDir) fullFileName = anInputDir->absoluteFilePath(*anIter);
else fullFileName = *anIter;
QFileInfo anInfo(fullFileName);
if(win && win->isStopped()) return;
if(anInfo.isDir())
{
// Recursive processing of directory
bool toDelete = false;
if(!win) { toDelete = true; win = new LoadingProgressWindow(this); }
QDir aDir(fullFileName);
QStringList anEntryList = aDir.entryList();
win->setDir(fullFileName);
AddFileList(anEntryList,false,directory?directory:&fullFileName,&aDir,win);
if(toDelete)
{
LogMessage(tr("%1 audio files found.").arg(win->getFileCount()));
if(ui->KeepInput->isChecked())
{
// Pas de conservation de la destination sur du traitement de répertoire
// risque de mélanger les fichiers de façon peu sympathique
ui->KeepInput->setChecked(false);
aDir.cdUp();
ui->mOutputText->setText(aDir.path());
}
delete win;
win = nullptr;
}
}
else
{
std::string aLength = LASTARProcessor::getFileLength(fullFileName);
if(!aLength.empty())
{
InputFileEntry*anItem;
if(directory!=nullptr)
anItem = new InputFileEntry(fullFileName,*directory);
else
anItem = new InputFileEntry(fullFileName);
if(win) win->incFileCount();
anItem->setText(0,"");
anItem->setText(ICON_COL,"");
anItem->setText(FILE_COL,anItem->getLabel());
anItem->setText(TIME_COL,QString(aLength.c_str()));
ui->FileTable->addTopLevelItem(anItem);
}
}
}
// Only open on the root dir
if(!directory) {
UpdateGuiFromList();
OpenIfClosed();
}
}
void MainWindow::OpenFile(const QString &aString)
{
mInputFileName = aString.toUtf8().data();
if (mProcessor->open(mInputFileName) == 0)
{
emit opened();
}
else
{
Close();
}
SetOutputFileName();
}
void MainWindow::on_action_Open_triggered()
{
QStringList list = QFileDialog::getOpenFileNames(this,
tr("Open sound files"),
mInputFileName.getPath(),
tr("Audio files (*.mp3 *.wav *.flac *.ogg)"));
if(list.isEmpty())
return;
AddFileList(list,true);
}
void MainWindow::on_actionOpen_directory_tree_triggered()
{
QFileDialog dialog(this,tr("Open a directory to process"),mInputFileName.getDirectory());
dialog.setFileMode(QFileDialog::Directory);
#ifdef Q_OS_WIN
if(QSysInfo::WindowsVersion == QSysInfo::WV_XP || QSysInfo::WindowsVersion == QSysInfo::WV_2003)
dialog.setOptions(QFileDialog::ShowDirsOnly | QFileDialog::DontUseNativeDialog);
else
#endif
dialog.setOptions(QFileDialog::ShowDirsOnly);
if (dialog.exec())
{
QStringList list = dialog.selectedFiles();
AddFileList(list,true);
}
}
void MainWindow::on_action_Statistics_triggered()
{
if(GetNoiseParameter()) return;
emit toggleStopButton();
std::thread statsThread([this](){
mProcessing = true;
mStopCalled = false;
Status status = mProcessor->recomputeStats();
if(status == STATUS_SUCCESS)
emit statsComputed();
else
emit sendStatus(status);
});
statsThread.detach();
}
void MainWindow::statsRecomputed() {
ui->MainTab->setCurrentIndex(0);
emit toggleStopButton();
}
void MainWindow::on_action_Quit_triggered()
{
QApplication::instance()->exit(0);
}
void MainWindow::closeEvent(QCloseEvent *){
on_action_Quit_triggered();
}
void MainWindow::ClearFiles()
{
ui->FileTable->clear();
Close();
ui->MainTab->setCurrentIndex(0);
}
void MainWindow::on_mOutputButton_clicked()
{
QFileDialog aFileDial(this,tr("Choose output directory"),mOutputFileName.getFullDirectory());
aFileDial.setFileMode(QFileDialog::Directory);
#ifdef Q_OS_WIN
if(QSysInfo::WindowsVersion == QSysInfo::WV_XP || QSysInfo::WindowsVersion == QSysInfo::WV_2003)
aFileDial.setOptions(QFileDialog::ShowDirsOnly | QFileDialog::DontUseNativeDialog);
else
#endif
aFileDial.setOptions(QFileDialog::ShowDirsOnly);
if(!aFileDial.exec()) return;
QString fileName = aFileDial.selectedFiles().front();
ui->mOutputText->setText(fileName);
SetOutputFileName();
}
void MainWindow::on_mSplitCheck_toggled(bool /*checked*/)
{
SetOutputFileName();
}
//=================================
// Log functions
//=================================
void MainWindow::LogError(const QString&aMessage)
{
QErrorMessage aMessageBox;
aMessageBox.showMessage(aMessage);
aMessageBox.exec();
ui->mLogText->setTextColor(QColor(255,0,0));
LogString(aMessage);
}
void MainWindow::LogWarning(const QString&aMessage)
{
ui->mLogText->setTextColor(QColor(255,128,0));
LogString(aMessage);
}
void MainWindow::LogMessage(const QString&aMessage)
{
if(!mLogString.isEmpty()) logDoDisplay();
ui->mLogText->setTextColor(QColor(0,0,0));
LogString(aMessage);
}
void MainWindow::LogInfo(const QString&aMessage)
{
ui->mLogText->setTextColor(QColor(0,0,200));
LogString(aMessage);
}
void MainWindow::LogString(const QString &aString)
{
QDateTime aDateTime = QDateTime::currentDateTime();
QString aFullString = aDateTime.toString("dd/MM/yyyy hh:mm:ss ");
aFullString += aString;
ui->mLogText->append(aFullString);
ui->mLogText->ensureCursorVisible();
}
void MainWindow::logDoDisplay() {
if(!mLogString.isEmpty()) {
QString aNewString = mLogString;
mLogString.clear();
LogMessage(aNewString);
}
}
int MainWindow::sync ()
{
emit logRequestDisplay();
return 0;
}
int MainWindow::overflow (int ch)
{
#pragma omp critical(overflow)
{
if(ch != EOF && ch != '\n' && ch)
mLogString += char(ch);
else {
if(ch=='\n') mLogString += '\n';
sync();
}
}
return 0;
}
// Defining xsputn is an optional optimization.
// (streamsize was recently added to ANSI C++, not portable yet.)
std::streamsize MainWindow::xsputn (const char *text, std::streamsize n)
{
#pragma omp critical(xsputn)
for(std::streamsize t=n;t;t--,text++) overflow(*text);
return n;
}
//=================================
// Output file management
//=================================
void MainWindow::SetExtension()
{
mOutputFileName.setExtension(ui->mOutputBox->currentText().toUtf8().data());
}
void MainWindow::SetOutputFileName()
{
QString anOutputDir = ui->mOutputText->text();
if (!mInputFileName.isEmpty() && (ui->KeepInput->isChecked() || anOutputDir.isEmpty()))
{
// On garde le même chemin
mOutputFileName = mInputFileName;
SetExtension();
if(ui->mSplitCheck->isChecked()) // en split, on ajoute le répertoire de sortie
mOutputFileName.appendDirectory (mOutputFileName.getBaseFileName() );
// Sinon, si le fichier existe, en mode non batch si on ignore la sortie ou, dans tous les cas que l'entrée égale la sortie on rajoute un truc
else if(QFile::exists(mOutputFileName.getPath()) && ((!ui->IgnoreIfExistsToggle->isChecked() && !mIsBatch) || QString(mOutputFileName.getPath()) == QString(mInputFileName.getPath())))
{
mOutputFileName.appendBaseFileName("_LASTAR");
}
ui->mOutputText->setText(mOutputFileName.getFullDirectory());
}
else if (!mInputFileName.isEmpty() && !anOutputDir.isEmpty())
{
// The file is a dir entry
if(FirstIsDirEntry()) {
mOutputFileName = (anOutputDir+"/"+GetFirstRelativePath());
FileNameString fns(GetFirstRelativePath());
QDir(anOutputDir).mkpath(fns.getDirectory());
}
else {
mOutputFileName.setDirectory(anOutputDir);
mOutputFileName.setFileName(mInputFileName.getFileName());
}
SetExtension();
// Si ca existe et qu'on ne veut pas skipper et qu'on n'est pas en batch, on rajoute un suffixe
if(QFile::exists(mOutputFileName.getPath()) && (!ui->IgnoreIfExistsToggle->isChecked() && !mIsBatch))
mOutputFileName.appendBaseFileName("_LASTAR");
}
}
//=================================
// SetOutputFileName
//=================================
void MainWindow::ToggleStopButton()
{
QTreeWidgetItem * anItemPt = ui->FileTable->topLevelItem (0);
if (!mStopDisplayed)
{
ui->mLaunchButton->setText (tr("Stop") );
ui->mLaunchButton->setIcon(QIcon(":/gnome/process-stop.png"));
ui->mProgressBar->setTextVisible(true);
anItemPt->setIcon(ICON_COL,QIcon(":/gnome/system-run.png"));
ui->Play->setEnabled(false);
ui->actionAide->setEnabled(false);
ui->actionOpen_directory_tree->setEnabled(false);
ui->actionClear_Files->setEnabled(false);
ui->actionLaunch_processing->setEnabled(false);
ui->action_Statistics->setEnabled(false);
ui->action_Open->setEnabled(false);
ui->action_About->setEnabled(false);
ui->action_Quit->setEnabled(false);
ui->mAutoEqCheck->setEnabled(false);
ui->noiseLevelCheck->setEnabled(false);
ui->deleteFile->setEnabled(false);
ui->FileTable->setEnabled(false);
ui->Play->setEnabled(false);
ui->mSplitCheck->setEnabled(false);
ui->mAmplifyCheck->setEnabled(false);
ui->StartSlider->setEnabled(false);
ui->mCompressionSlider->setEnabled(false);
ui->mMusicTypeCombo->setEnabled(false);
ui->mAllCheck->setEnabled(false);
mStopDisplayed = true;
}
else
{
mStopDisplayed = false;
ui->mLaunchButton->setText (tr("Launch processing") );
ui->mLaunchButton->setIcon(QIcon(":/gnome/system-run.png"));
ui->mProgressBar->setTextVisible(false);
ui->mProgressBar->setValue(0);
if(mProcessor->isOpened()) {
anItemPt->setIcon(ICON_COL,QIcon(":/gnome/document-open.png"));
ui->Play->setEnabled(true);
ui->actionLaunch_processing->setEnabled(true);
}
ui->actionAide->setEnabled(true);
ui->actionOpen_directory_tree->setEnabled(true);
ui->actionClear_Files->setEnabled(true);
ui->action_Statistics->setEnabled(true);
ui->action_Open->setEnabled(true);
ui->action_About->setEnabled(true);
ui->action_Quit->setEnabled(true);
ui->mAutoEqCheck->setEnabled(true);
ui->noiseLevelCheck->setEnabled(true);
ui->FileTable->setEnabled(true);
ui->mSplitCheck->setEnabled(true);
ui->mAmplifyCheck->setEnabled(true);
ui->StartSlider->setEnabled(true);
ui->mCompressionSlider->setEnabled(true);
ui->mMusicTypeCombo->setEnabled(true);
ui->mAllCheck->setEnabled(true);
on_FileTable_itemSelectionChanged();
}
}
void MainWindow::on_action_About_triggered()
{
About anAbout(this);
anAbout.exec();
}
void MainWindow::on_mOutputBox_activated(QString /*aString*/)
{
SetExtension();
}
void MainWindow::dragEnterEvent(QDragEnterEvent *event)
{
const QMimeData *mimeData = event->mimeData();
if(!mProcessing && mimeData->hasUrls())
event->acceptProposedAction();
}
void MainWindow::dropEvent(QDropEvent *event)
{
if(mProcessing) return;
const QMimeData *mimeData = event->mimeData();
QList<QUrl> urlList = mimeData->urls();
QStringList aFileList;
bool anErase = (ui->MainTab->currentIndex() == 0);
if(urlList.size()>0)
{
for(QList<QUrl>::iterator anIter = urlList.begin();
anIter != urlList.end();anIter++)
aFileList.push_back((*anIter).toLocalFile());
}
AddFileList(aFileList,anErase);
}
void MainWindow::on_mNoiseSlider_valueChanged(int /*value*/)
{
if(mProcessor->hasStats())
{
bool aPlay = mProcessor->stopAndClosePlayer();
GetNoiseParameter();
mProcessor->reinitNoiseStats();
if(aPlay) on_Play_clicked();
}
}
void MainWindow::on_actionLaunch_processing_triggered()
{
on_mLaunchButton_clicked();
}
//=================================
// Parameters management
//=================================
void MainWindow::AddParameters(ProcessParameters *aParamPt)
{
int anIndex = 2;
QString aName = aParamPt->mName;
while(ui->mMusicTypeCombo->findText(tr(aName.toStdString().c_str()))>=0)
{
aName = aParamPt->mName;
aName += QString("_%1").arg(anIndex++);
}
aParamPt->mName = aName;
ui->mMusicTypeCombo->addItem(tr(aParamPt->mName.toUtf8()),qVariantFromValue((void*)aParamPt));
ui->mMusicTypeCombo->setCurrentIndex(ui->mMusicTypeCombo->count()-1);
UpdateWithCurrentParam();
}
void MainWindow::RemoveParameters(ProcessParameters *aParamPt)
{
int anIndex = ui->mMusicTypeCombo->findData(qVariantFromValue((void*)aParamPt));
if(!aParamPt->mIsFactory)
{
ui->mMusicTypeCombo->removeItem(anIndex);
on_mMusicTypeCombo_activated(ui->mMusicTypeCombo->currentIndex());
}
}
void MainWindow::on_actionEditParam_triggered()
{
if(GetParameters())
return;
Parameters aParam(this);
if(aParam.GetParam(*mCurrentParamPt))
UpdateWithCurrentParam();
}
void MainWindow::UpdateWithCurrentParam()
{
ui->mAmplifyCheck->setChecked(mCurrentParamPt->mAmplify);
ui->mOutputSlider->setValue(int(floor(2.0*(mCurrentParamPt->mLocAmplifyValue-mCurrentParamPt->mRMSValue))));
ui->mCompressionSlider->setValue(int(floor(mCurrentParamPt->mCompressValue)));
ui->mSplitCheck->setChecked(mCurrentParamPt->mSplit);
ui->mSplitText->setText(QString("%1").arg(mCurrentParamPt->mMinFileSize/60.0));
ui->mSplitMaxText->setText(QString("%1").arg(mCurrentParamPt->mMaxFileSize/60.0));
ui->DeleteUserSettings->setEnabled(!mCurrentParamPt->mIsFactory);
ui->mNoiseSlider->setValue(int(floor((mCurrentParamPt->mNoiseTime-2.0)*5.0)));
ui->mAutoEqCheck->setChecked(mCurrentParamPt->mAutoEq);
ui->mAutoEqCombo->setCurrentText(mCurrentParamPt->mAutoEqName);
ui->noiseLevelCheck->setChecked(mCurrentParamPt->mEstimNoise);
RestartPlayer();
}
void MainWindow::on_mMusicTypeCombo_activated(int anIndex)
{
QVariant aData = ui->mMusicTypeCombo->itemData(anIndex);
setCurrentParam(static_cast<ProcessParameters*>(aData.value<void*>()));
mProcessor->setCurrentParam(mCurrentParamPt);
UpdateWithCurrentParam();
}
bool MainWindow::GetNoiseParameter()
{
mCurrentParamPt->mNoiseThreshold = -40.0;
mCurrentParamPt->mNoiseTime = 2.0+0.2*double(ui->mNoiseSlider->value());
return false;
}
bool MainWindow::GetParameters()
{
mCurrentParamPt->mAmplify = ui->mAmplifyCheck->isChecked();
mCurrentParamPt->mLocAmplifyValue = mCurrentParamPt->mRMSValue+0.5*double(ui->mOutputSlider->value());
if(!mCurrentParamPt->mAmplify) mCurrentParamPt->mCompress = false;
else
{
mCurrentParamPt->mCompressValue = (double)ui->mCompressionSlider->value();
mCurrentParamPt->mCompress = (mCurrentParamPt->mCompressValue>1);
}
bool anOk;
mCurrentParamPt->mSplit = ui->mSplitCheck->isChecked();
mCurrentParamPt->mMinFileSize = ui->mSplitText->text().toDouble(&anOk);
if (mCurrentParamPt->mSplit && (!anOk || mCurrentParamPt->mMinFileSize < 0) ) // s
{
LogError(tr("Incorrect min file size (must be >=0)") );
return true;
}
mCurrentParamPt->mMinFileSize *= 60.0;
mCurrentParamPt->mMaxFileSize = ui->mSplitMaxText->text().toDouble(&anOk);
if (mCurrentParamPt->mSplit && (!anOk || mCurrentParamPt->mMaxFileSize < 0) ) // s
{
LogError(tr("Incorrect min file size (must be >=0)") );
return true;
}
mCurrentParamPt->mMaxFileSize *= 60.0;
mCurrentParamPt->mAutoEq = ui->mAutoEqCheck->isChecked();
mCurrentParamPt->mEstimNoise = ui->noiseLevelCheck->isChecked();
if(GetNoiseParameter()) return true;
return false;
}
void MainWindow::SaveSettings()
{
QSettings *aSet;
QDir aCurrentDir(QDir::currentPath());
if(aCurrentDir.exists("LASTAR.ini"))
aSet = new QSettings(aCurrentDir.absoluteFilePath("LASTAR.ini"),QSettings::IniFormat);
else
aSet = new QSettings(QSettings::IniFormat,QSettings::UserScope,"Arthelion","LASTAR");
SoundDebuggableClass::getOStream() << aSet->fileName().toUtf8().data() << std::endl;
GetParameters();
aSet->beginGroup("Main");
aSet->setValue("Version",15);
aSet->setValue("Preset",ui->mMusicTypeCombo->currentIndex());
aSet->setValue("KeepInputPath",ui->KeepInput->isChecked());
aSet->setValue("OutputDir",ui->mOutputText->text());
aSet->setValue("OutputBox",ui->mOutputBox->currentIndex());
aSet->setValue("LastVersionCheck",mLastVersionCheck);
aSet->endGroup();
aSet->beginWriteArray("Presets",ui->mMusicTypeCombo->count());
for(int i=0;i<ui->mMusicTypeCombo->count();i++)
{
aSet->setArrayIndex(i);
QVariant aData = ui->mMusicTypeCombo->itemData(i);
ProcessParameters*aParamPt = (ProcessParameters*)aData.value<void*>();
aSet->setValue("Name",aParamPt->mName);
aSet->setValue("MinFreq",aParamPt->mMinFreq);
aSet->setValue("MaxFreq",aParamPt->mMaxFreq);
aSet->setValue("RMSValue",aParamPt->mRMSValue);
aSet->setValue("Attack",aParamPt->mAttack);
aSet->setValue("Release",aParamPt->mRelease);
aSet->setValue("Bitrate",aParamPt->mBitrate);
aSet->setValue("Multiband",aParamPt->mMultiband);
aSet->setValue("PostAtten",aParamPt->mPostAtten);
aSet->setValue("Amplify",aParamPt->mAmplify);
aSet->setValue("LocAmplifyValue",aParamPt->mLocAmplifyValue);
aSet->setValue("Compress",aParamPt->mCompress);
aSet->setValue("CompressValue",aParamPt->mCompressValue);
aSet->setValue("Split",aParamPt->mSplit);
aSet->setValue("MinFileSize",aParamPt->mMinFileSize);
aSet->setValue("MaxFileSize",aParamPt->mMaxFileSize);
aSet->setValue("NoiseThreshold",aParamPt->mNoiseThreshold);
aSet->setValue("NoiseTime",aParamPt->mNoiseTime);
aSet->setValue("RMSSize",aParamPt->mRMSSize);
aSet->setValue("Factory",aParamPt->mIsFactory);
aSet->setValue("NoiseGate",aParamPt->mNoiseGate);
aSet->setValue("NoiseGateThreshold",aParamPt->mNoiseGateThreshold);
aSet->setValue("NoiseGateTime",aParamPt->mNoiseGateTime);
aSet->setValue("SNRMax",aParamPt->mSNRMax);
aSet->setValue("Trim",aParamPt->mTrim);
aSet->setValue("TrimMarginStart",aParamPt->mTrimMarginStart);
aSet->setValue("TrimMarginEnd",aParamPt->mTrimMarginEnd);
aSet->setValue("LowEq",aParamPt->mLowDB);
aSet->setValue("MidEq",aParamPt->mMidDB);
aSet->setValue("MidFreq",aParamPt->mMidFreq);
aSet->setValue("MidOct",aParamPt->mMidOct);
aSet->setValue("HighEq",aParamPt->mHighDB);
aSet->setValue("AutoEq",aParamPt->mAutoEq);
aSet->setValue("AutoEqMaxCorrection",aParamPt->mAutoEqMaxCorrection);
aSet->setValue("AutoEqName",aParamPt->mAutoEqName);
aSet->setValue("EstimNoise",aParamPt->mEstimNoise);
aSet->setValue("WeightingStat",aParamPt->mWeightingStat);
aSet->setValue("Artist",aParamPt->mTags.mArtist.c_str());
aSet->setValue("Album",aParamPt->mTags.mAlbum.c_str());
aSet->setValue("Title",aParamPt->mTags.mTitle.c_str());
aSet->setValue("Year",aParamPt->mTags.mYear.c_str());
aSet->setValue("Genre",aParamPt->mTags.mGenre.c_str());
if(!aParamPt->mTags.mCoverArt.isEmpty()) {
aSet->setValue("Cover",aParamPt->mTags.mCoverArt.toBase64());
aSet->setValue("MimeType",aParamPt->mTags.mMimeType.c_str());
}
else
{
aSet->remove("Cover");
aSet->remove("MimeType");
}
}
aSet->endArray();
delete aSet;
}
void MainWindow::setCurrentParam(ProcessParameters *param)
{
mCurrentParamPt = param;
mProcessor->setCurrentParam(param);
}
void MainWindow::ReadSettings()
{
SettingsReader reader;
reader.readAutoEqSettings(mProcessor.get());
QStringList eqList = mProcessor->getEQNameList();
eqList.insert(eqList.begin(),tr("-- Auto --"));
ui->mAutoEqCombo->addItems(eqList);
if(!reader.isOpened())
return;
int aVersion = reader.getVersion();
std::vector<ProcessParameters*> paramList = reader.readProcessParameters();
if(paramList.empty())
return; // Problème quelconque. Il y a au moins les usine.
int i=0;
for(ProcessParameters*aParamPt : paramList) {
int anIndex = ui->mMusicTypeCombo->findText(tr(aParamPt->mName.toUtf8()));
if(anIndex>=0)
{
QVariant aData = ui->mMusicTypeCombo->itemData(anIndex);
ProcessParameters*aParam2Pt = (ProcessParameters*)aData.value<void*>();
*aParam2Pt = *aParamPt;
delete aParamPt;
}
else
ui->mMusicTypeCombo->addItem(tr(aParamPt->mName.toUtf8()),qVariantFromValue((void*)aParamPt));
i++;
}
QSettings*aSet = reader.getSettings();
aSet->beginGroup("Main");
int aPreset = aSet->value("Preset").toInt();
ui->mMusicTypeCombo->setCurrentIndex(aPreset);
on_mMusicTypeCombo_activated(aPreset);
if(aVersion>=4)
ui->mOutputBox->setCurrentIndex(aSet->value("OutputBox").toInt());
if(aVersion>=2)
{
ui->KeepInput->setChecked(aSet->value("KeepInputPath").toBool());
if(!ui->KeepInput->isChecked())
ui->mOutputText->setText(aSet->value("OutputDir").toString());
}
if(aVersion<5)
{
ui->mAmplifyCheck->setChecked(aSet->value("Amplify").toBool());
ui->mOutputSlider->setValue(aSet->value("AmpValue").toInt());
ui->mCompressionSlider->setValue(aSet->value("Compress").toInt());
ui->mSplitCheck->setChecked(aSet->value("Split").toBool());
ui->mSplitText->setText(aSet->value("SplitMin").toString());
ui->mSplitMaxText->setText(aSet->value("SplitMax").toString());
ui->mNoiseSlider->setValue(aSet->value("Noise").toInt());
aSet->remove("Amplify");
aSet->remove("AmpValue");
aSet->remove("Compress");
aSet->remove("Split");
aSet->remove("SplitMin");
aSet->remove("SplitMax");
aSet->remove("Noise");
}
if(aVersion>=12) {
mLastVersionCheck = aSet->value("LastVersionCheck").toInt();
if(mLastVersionCheck<LASTAR_VERSION_NUMBER)
mLastVersionCheck = LASTAR_VERSION_NUMBER;
}
aSet->endGroup();
}
void MainWindow::on_KeepInput_toggled(bool /*checked*/)
{
SetOutputFileName();
}
void MainWindow::on_SaveUserSettings_clicked()
{
bool ok;
QString text = QInputDialog::getText(this, tr("Save settings"),
tr("Name of parameters set :"),
QLineEdit::Normal,
tr(mCurrentParamPt->mName.toUtf8()), &ok);
if (ok && !text.isEmpty())
{
bool aNewOne = (text != mCurrentParamPt->mName || mCurrentParamPt->mIsFactory);
if(aNewOne)
setCurrentParam(new ProcessParameters(*mCurrentParamPt));
GetParameters();
if(aNewOne)
{
mCurrentParamPt->mName = text;
mCurrentParamPt->mIsFactory = false;
AddParameters(mCurrentParamPt);
}
}
}
void MainWindow::on_DeleteUserSettings_clicked()
{
RemoveParameters(mCurrentParamPt);
}
void MainWindow::on_Play_clicked()
{
if(mProcessor->isClosed() || mProcessor->isPlaying()) return;
if(GetParameters()) return;
emit toggleStopButton();
startPlay();
}
void MainWindow::startPlay() {
mStopCalled = false;
mProcessing = true;
std::thread ampThread([this]() {
Status status = mProcessor->amplify(mOutputFileName,true,getStartValue());
if(status != STATUS_WAITING)
emit sendStatus(status);
});
ampThread.detach();
}
void MainWindow::playingHasStopped()
{
// Playing has stopped, but we wanted to restard, so we do...
if(mRestartPlay)
{
startPlay();
mRestartPlay = false;
}
else
{
mProcessor->stopAndClosePlayer();
mProcessing = false;
emit toggleStopButton();
}
}
void MainWindow::processingBegins()
{
ui->mProgressBar->setValue(0);
}
void MainWindow::progress(int percent)
{
ui->mProgressBar->setValue(percent);
}
void MainWindow::processingHasFinished(Status aStatus)
{
mProcessing = false;
mStopCalled = false;
emit toggleStopButton();
if(aStatus != SoundTools::STATUS_INTERRUPTED && mIsBatch)
{
// Mode batch : on ferme le ficher et on l'enlève de la liste
Close();
delete ui->FileTable->takeTopLevelItem(0);
ui->fileProgress->setValue(ui->fileProgress->value()+1);
if(HasFiles())
{
// On rouvre le suivant
OpenFile(GetFirstFileName());
UpdateGuiFromList();
on_mLaunchButton_clicked();
}
else
{
// on a fini le batch !
mIsBatch = false;
ui->fileProgress->setVisible(false);
UpdateGuiFromList();
}
}
else
{
// Interruption ou pas batch
mIsBatch = false;
ui->fileProgress->setVisible(false);
UpdateGuiFromList();
if(aStatus == SoundTools::STATUS_INTERRUPTED)
QFile(mOutputFileName.getPath()).remove(); // In case of interruption, remove the file
}
}
void MainWindow::Close() {
mProcessor->close();
mInputFileName = nullptr;
}
void MainWindow::RestartPlayer()
{
// On attend d'être notifié de la destruction pour relancer
mRestartPlay = mProcessor->stopAndClosePlayer();
}
void MainWindow::on_mCompressionSlider_valueChanged(int /*value*/)
{
RestartPlayer();
}
void MainWindow::on_StartSlider_valueChanged(int aValue)
{
if(mProcessor->isOpened())
{
double aStartPoint = mProcessor->getLength()*double(aValue)*0.001;
ui->StartLabel->setText(SoundTools::time_fmt (aStartPoint).c_str());
RestartPlayer();
}
}
void MainWindow::on_StartSlider_sliderMoved(int position)
{
double aStartPoint = mProcessor->getLength()*double(position)*0.001;
ui->StartLabel->setText(SoundTools::time_fmt (aStartPoint).c_str());
}
void MainWindow::on_actionAide_triggered()
{
QString anHelpFile = QDir::current().absolutePath();
anHelpFile += "/";
anHelpFile += "LASTAR_";
anHelpFile += QLocale::system().name().left(2);
anHelpFile += ".pdf";
if(!QFile(anHelpFile).exists())
{
anHelpFile = QDir::current().absolutePath();
anHelpFile += "/LASTAR.pdf";
}
ShellExecuteA(nullptr,"open",anHelpFile.toUtf8().data(),nullptr,nullptr,SW_NORMAL);
}
void MainWindow::on_actionClear_Files_triggered()
{
ClearFiles();
}
void MainWindow::on_mAutoEqCombo_activated(int index)
{
if(index == 0) {
mCurrentParamPt->mAutoEqName.clear();
} else {
mCurrentParamPt->mAutoEqName = ui->mAutoEqCombo->itemText(index);
}
mProcessor->printAutoEqStats();
RestartPlayer();
}
void MainWindow::on_mAutoEqCheck_clicked(bool checked)
{
mCurrentParamPt->mAutoEq = checked;
mProcessor->printAutoEqStats();
RestartPlayer();
}
void MainWindow::versionReceived(VersionChecker::Version version)
{
bool answer = false;
if(mLastVersionCheck == -1) {
answer = true;
mLastVersionCheck = LASTAR_VERSION_NUMBER;
}
if(version.mNumber>mLastVersionCheck) {
QMessageBox box(this);
box.setWindowTitle(tr("Version check"));
box.setText(tr("A new version %1 is available.\nCurrent version is %2").arg(version.mCode).arg(LASTAR_VERSION_CODE));
QPushButton *downloadIt = box.addButton(tr("Download"), QMessageBox::ActionRole);
QPushButton *seeNews = box.addButton(tr("See"), QMessageBox::ActionRole);
QPushButton *forget = box.addButton(tr("Forget"), QMessageBox::ActionRole);
box.addButton(QMessageBox::Close);
box.exec();
if(box.clickedButton()==downloadIt) {
QDesktopServices::openUrl(QUrl("https://sourceforge.net/projects/lastar/files/LASTAR_setup_win32.exe/download"));
QThread::sleep(2);
QApplication::exit();
} else
if(box.clickedButton()==seeNews) {
QDesktopServices::openUrl(QUrl(version.mURL));
}
else if(box.clickedButton()==forget) {
mLastVersionCheck = version.mNumber;
}
} else if(answer) {
QMessageBox::information(this,tr("Version check"),tr("Your version is up-to-date."));
}
}
void MainWindow::versionError()
{
if(mLastVersionCheck == -1) {
QMessageBox::information(this,tr("Version check"),tr("Unable to contact site."));
mLastVersionCheck = LASTAR_VERSION_NUMBER;
}
}
void MainWindow::on_actionCheck_for_new_version_triggered()
{
mLastVersionCheck = -1;
mChecker.check();
}
void MainWindow::on_deleteFile_clicked()
{
DeleteTableSelection();
}
void MainWindow::on_FileTable_itemSelectionChanged()
{
ui->deleteFile->setDisabled(ui->FileTable->selectedItems().empty());
}
void MainWindow::setOpenedStatus()
{
ui->mLaunchButton->setEnabled (true);
ui->action_Statistics->setEnabled (true);
ui->actionLaunch_processing->setEnabled (true);
QTreeWidgetItem *anItemPt = ui->FileTable->topLevelItem (0);
anItemPt->setIcon(ICON_COL,QIcon(":/gnome/document-open.png"));
ui->StartSlider->setValue(0);
ui->StartSlider->setEnabled(true);
ui->Play->setEnabled(true);
}
void MainWindow::setClosedStatus()
{
mInputFileName = nullptr;
ui->mLaunchButton->setEnabled (false);
ui->action_Statistics->setEnabled (false);
ui->actionLaunch_processing->setEnabled (false);
ui->StartSlider->setEnabled(false);
ui->Play->setEnabled(false);
ui->statusBar->showMessage (tr("No file loaded"));
}
double MainWindow::getStartValue() {
return double(ui->StartSlider->value())*0.001;
}
void MainWindow::SetAmpDebugFileName(const QString &value) {
mProcessor->setAmpDebugFileName(value);
}
void MainWindow::on_mOutputSlider_valueChanged(int value)
{
emit setAmpLevel(double(value)*0.5);
}