#include "batchprocessing.h" #include "ui_batchprocessing.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "scriptengine.h" #include "chartgraph.h" #ifdef Q_OS_LINUX #include #include #endif QList> scanDirectories(const QStringList &paths) { QList> files; QStringList scannedDirs; std::function scanDirectory = [&](const QString &root, const QString &path) { QFileInfo info(path); if(info.isDir() && !scannedDirs.contains(info.canonicalFilePath())) { scannedDirs.append(info.canonicalFilePath()); QDir dir(path); QStringList entries = dir.entryList(QDir::Dirs | QDir::Files | QDir::NoDotAndDotDot); for(QString &entry : entries) scanDirectory(root, dir.absoluteFilePath(entry)); } else if(info.isFile()) { if(path == root) files.append({path, info.absolutePath()}); else files.append({path, root}); } }; for(const QString &path : paths) scanDirectory(path, path); return files; } void BatchProcessing::scanScriptDir() { QString current; if(_ui->scriptsList->currentItem()) current = _ui->scriptsList->currentItem()->text(); _ui->scriptsList->clear(); QDir dir(_scriptBasePath); QDir embededDir(":/scripts"); QStringList scripts = dir.entryList(QDir::Files | QDir::Readable); scripts.append(embededDir.entryList(QDir::Files)); scripts.removeDuplicates(); _ui->scriptsList->addItems(scripts); int idx = scripts.indexOf(current); if(idx>=0)_ui->scriptsList->setCurrentRow(idx); } BatchProcessing::BatchProcessing(Database *database, QWidget *parent) : QDialog(parent) , _database(database) { _ui = new Ui::BatchProcessing; _ui->setupUi(this); QStringList scriptsPath = QStandardPaths::standardLocations(QStandardPaths::AppDataLocation); if(scriptsPath.size()) { QDir dir(scriptsPath.first()); if(!dir.exists("scripts")) { if(!dir.mkpath("scripts")) qWarning() << "Failed to create scripts directory"; } dir.cd("scripts"); _scriptBasePath = dir.absolutePath() + "/"; scanScriptDir(); _fileWatcher.addPath(_scriptBasePath); connect(&_fileWatcher, &QFileSystemWatcher::directoryChanged, this, &BatchProcessing::scanScriptDir); } else { qWarning() << "Failed to get app data location"; } connect(_ui->addFilesButton, &QPushButton::released, this, static_cast(&BatchProcessing::addFiles)); connect(_ui->addDirButton, &QPushButton::released, this, static_cast(&BatchProcessing::addDir)); connect(_ui->addMarkedButton, &QPushButton::released, this, &BatchProcessing::addMarked); connect(_ui->removeButton, &QPushButton::released, this, &BatchProcessing::removePath); connect(_ui->removeAllButton, &QPushButton::released, this, &BatchProcessing::removeAllPaths); connect(_ui->startButton, &QPushButton::released, this, static_cast(&BatchProcessing::runScript)); connect(_ui->stopButton, &QPushButton::released, this, &BatchProcessing::stopScript); connect(_ui->browseButton, &QPushButton::released, this, &BatchProcessing::browse); connect(_ui->openScriptsButton, &QPushButton::released, this, &BatchProcessing::openScriptDir); _textColor = _ui->log->palette().text().color(); _engine = new Script::ScriptEngine(_database, this); connect(_engine, &Script::ScriptEngine::newMessage, this, &BatchProcessing::newMessage); _completerModel = new QStringListModel(this); _completer = new QCompleter(_completerModel, this); _ui->consoleLineEdit->setCompleter(_completer); connect(_ui->executeButton, &QPushButton::clicked, _ui->consoleLineEdit, &QLineEdit::returnPressed); connect(_ui->consoleLineEdit, &QLineEdit::returnPressed, [this](){ if(!_completer->popup()->isVisible()) { QString program = _ui->consoleLineEdit->text(); QJSValue val = _engine->eval(program); _ui->consoleLineEdit->addLine(); //qDebug() << val.toString(); } }); connect(_ui->consoleLineEdit, &QLineEdit::textEdited, [this](const QString &text){ QStringList comp = _engine->complete(text); //qDebug() << comp; _completerModel->setStringList(comp); }); _ui->addFilesButton->setAutoDefault(false); QSettings settings; _ui->outputPath->setText(settings.value("batchprocessing/outputpath", QStandardPaths::standardLocations(QStandardPaths::PicturesLocation).first()).toString()); } BatchProcessing::~BatchProcessing() { delete _engineThread; QSettings settings; settings.setValue("batchprocessing/outputpath", _ui->outputPath->text()); delete _ui; } void BatchProcessing::setOutputDir(const QString &output) { _ui->outputPath->setText(output); } void BatchProcessing::setPaths(const QStringList &paths) { _ui->pathsList->addItems(paths); refreshPaths(); } void BatchProcessing::closeEvent(QCloseEvent *event) { if(_engineThread) { QMessageBox::StandardButton ret = QMessageBox::question(this, tr("Interrupt running script?"), tr("Interrupt running script?")); if(ret == QMessageBox::StandardButton::Yes) { _engineThread->interrupt(); event->accept(); } else { event->ignore(); } } else { event->accept(); } } void BatchProcessing::refreshPaths() { QStringList paths; for(int i=0; i<_ui->pathsList->count(); i++) paths.append(_ui->pathsList->item(i)->text()); _paths = scanDirectories(paths); _engine->setParams("", _paths, _ui->outputPath->text(), QString()); } void BatchProcessing::addFiles() { QSettings settings; QStringList files = QFileDialog::getOpenFileNames(this, tr("Select files"), settings.value("batchprocessing/inputpath", QDir::homePath()).toString()); if(!files.isEmpty()) { _ui->pathsList->addItems(files); settings.setValue("batchprocessing/inputpath", QFileInfo(files.first()).absolutePath()); } refreshPaths(); } void BatchProcessing::addDir() { QSettings settings; QString dir = QFileDialog::getExistingDirectory(this, tr("Select directory"), settings.value("batchprocessing/inputpath", QDir::homePath()).toString()); if(!dir.isEmpty()) { _ui->pathsList->addItem(dir); settings.setValue("batchprocessing/inputpath", dir); } refreshPaths(); } void BatchProcessing::addMarked() { QStringList files = _database->getMarkedFiles(); for(const QString &file : files) { QFileInfo info(file); if(info.exists() && info.isReadable()) _ui->pathsList->addItem(file); }; refreshPaths(); } void BatchProcessing::removePath() { for(auto &item : _ui->pathsList->selectedItems()) delete item; refreshPaths(); } void BatchProcessing::removeAllPaths() { _ui->pathsList->clear(); refreshPaths(); } void BatchProcessing::browse() { QString output = QFileDialog::getExistingDirectory(this, tr("Select output directory"), _ui->outputPath->text()); if(!output.isEmpty()) _ui->outputPath->setText(output); } void BatchProcessing::openScriptDir() { openDir(_scriptBasePath); } void BatchProcessing::runScript() { _ui->log->clear(); auto selectedItems = _ui->scriptsList->selectedItems(); if(selectedItems.size()) { _engineThread = new Script::ScriptEngineThread(_database, this); connect(_engineThread, &Script::ScriptEngineThread::newMessage, this, &BatchProcessing::newMessage); connect(_engineThread, &Script::ScriptEngineThread::finished, this, &BatchProcessing::scriptFinished); QFileInfo outDir(_ui->outputPath->text()); if(outDir.exists() && outDir.isWritable()) { QString script = selectedItems.first()->text(); if(QDir(_scriptBasePath).exists(script)) script = _scriptBasePath + script; else script = ":/scripts/" + script; _engineThread->setParams(script, _paths, _ui->outputPath->text(), QString()); _engineThread->start(); _ui->startButton->setEnabled(false); _ui->stopButton->setEnabled(true); } else { QMessageBox::warning(this, tr("Invalid output directory"), tr("Output directory path doesn't exist or is not writable")); delete _engineThread; _engineThread = nullptr; } } } void BatchProcessing::runScript(const QString &script, const QString &arg, bool exit) { _ui->log->clear(); { _engineThread = new Script::ScriptEngineThread(_database, this); connect(_engineThread, &Script::ScriptEngineThread::newMessage, this, &BatchProcessing::newMessage); connect(_engineThread, &Script::ScriptEngineThread::newMessage, this, &BatchProcessing::newMessageCli); connect(_engineThread, &Script::ScriptEngineThread::finished, this, &BatchProcessing::scriptFinished); if(exit)connect(_engineThread, &Script::ScriptEngineThread::finished, this, &BatchProcessing::accept); QFileInfo outDir(_ui->outputPath->text()); if(outDir.exists() && outDir.isWritable()) { _engineThread->setParams(script, _paths, _ui->outputPath->text(), arg); _engineThread->start(); _ui->startButton->setEnabled(false); _ui->stopButton->setEnabled(true); } else { QMessageBox::warning(this, tr("Invalid output directory"), tr("Output directory path doesn't exist or is not writable")); delete _engineThread; _engineThread = nullptr; } } } void BatchProcessing::stopScript() { qDebug() << "Stop script"; if(_engineThread) _engineThread->interrupt(); } void BatchProcessing::scriptFinished() { _ui->startButton->setEnabled(true); _ui->stopButton->setEnabled(false); qDebug() << "script finished"; _engineThread->deleteLater(); _engineThread = nullptr; } void BatchProcessing::newMessage(const QString &message, bool error) { if(error)_ui->log->setTextColor(Qt::red); else _ui->log->setTextColor(_textColor); _ui->log->append(message); } void BatchProcessing::newMessageCli(const QString &message, bool error) { if(error) qWarning() << message; else qDebug() << message; } QJSValue BatchProcessing::getString(const QString &label, const QString &text) { bool ok = false; QString ret = QInputDialog::getText(this, tr("Enter text"), label, QLineEdit::Normal, text, &ok); return ok ? ret : QJSValue(); } QJSValue BatchProcessing::getInt(const QString &label, int value) { bool ok = false; int ret = QInputDialog::getInt(this, tr("Enter integer number"), label, value, INT_MIN, INT_MAX, 1, &ok); return ok ? ret : QJSValue(); } QJSValue BatchProcessing::getFloat(const QString &label, double value, int decimals) { bool ok = false; double ret = QInputDialog::getDouble(this, tr("Enter float number"), label, value, -INFINITY, INFINITY, decimals, &ok); return ok ? ret : QJSValue(); } QJSValue BatchProcessing::getItem(const QStringList &items, const QString &label, int current) { bool ok = false; QString ret = QInputDialog::getItem(this, tr("Select item"), label, items, current, false, &ok); return ok ? ret : QJSValue(); } QJSValue BatchProcessing::question(const QString &question, const QStringList &buttons, const QString &title) { QMessageBox::StandardButtons standardButtons = QMessageBox::NoButton; if(buttons.contains("ok"))standardButtons |= QMessageBox::Ok; if(buttons.contains("yes"))standardButtons |= QMessageBox::Yes; if(buttons.contains("no"))standardButtons |= QMessageBox::No; if(buttons.contains("yesall"))standardButtons |= QMessageBox::YesToAll; if(buttons.contains("noall"))standardButtons |= QMessageBox::NoToAll; if(buttons.contains("abort"))standardButtons |= QMessageBox::Abort; if(buttons.contains("retry"))standardButtons |= QMessageBox::Retry; if(buttons.contains("ignore"))standardButtons |= QMessageBox::Ignore; if(buttons.contains("cancel"))standardButtons |= QMessageBox::Cancel; if(buttons.contains("discard"))standardButtons |= QMessageBox::Discard; if(buttons.contains("apply"))standardButtons |= QMessageBox::Apply; if(buttons.contains("reset"))standardButtons |= QMessageBox::Reset; if(standardButtons == QMessageBox::NoButton)standardButtons = QMessageBox::Ok; QMessageBox::StandardButton button = QMessageBox::question(this, title, question, standardButtons); QJSValue ret; switch(button) { default: case QMessageBox::Ok: ret = "ok"; break; case QMessageBox::Yes: ret = "yes"; break; case QMessageBox::No: ret = "no"; break; case QMessageBox::YesToAll: ret = "yesall"; break; case QMessageBox::NoToAll: ret = "noall"; break; case QMessageBox::Abort: ret = "abort"; break; case QMessageBox::Retry: ret = "retry"; break; case QMessageBox::Ignore: ret = "ignore"; break; case QMessageBox::Cancel: ret = "cancel"; break; case QMessageBox::Discard: ret = "discard"; break; case QMessageBox::Apply: ret = "apply"; break; case QMessageBox::Reset: ret = "reset"; break; } return ret; } void BatchProcessing::plot(const QVariant &graph) { ChartGraph *chart = new ChartGraph(this); chart->plot(graph); } ConsoleLine::ConsoleLine(QWidget *parent) : QLineEdit(parent) { } void ConsoleLine::addLine() { QString line = text(); clear(); if(_history.size() && _history.last() == line)return; _history.append(line); if(_history.size() > 100)_history.removeFirst(); _currentLine = _history.size(); } void ConsoleLine::keyReleaseEvent(QKeyEvent *event) { if(event->key() == Qt::Key_Up) { _currentLine--; if(_currentLine < 0) { _currentLine = -1; clear(); return; } setText(_history.at(_currentLine)); } else if(event->key() == Qt::Key_Down) { _currentLine++; if(_currentLine >= _history.size()) { _currentLine = _history.size(); clear(); return; } setText(_history.at(_currentLine)); } else QLineEdit::keyReleaseEvent(event); } void openDir(const QString &path) { #ifdef Q_OS_LINUX QDBusConnection con = QDBusConnection::sessionBus(); QDBusMessage message = QDBusMessage::createMethodCall("org.freedesktop.FileManager1", "/org/freedesktop/FileManager1", "org.freedesktop.FileManager1", "ShowFolders"); QList args = {QStringList(QUrl::fromLocalFile(path).toString()), QString()}; message.setArguments(args); con.call(message); #endif #ifdef Q_OS_WINDOWS QProcess::startDetached("explorer.exe", {QDir::toNativeSeparators(path)}); #endif #ifdef Q_OS_MACOS QDesktopServices::openUrl(QUrl::fromLocalFile(path)); #endif }