From 8633b96646d1a3ae3dbeb440c62855f829db4604 Mon Sep 17 00:00:00 2001 From: cat Date: Sat, 26 Nov 2022 23:25:35 +0500 Subject: [PATCH] Add option to read tags from command's output. --- src/global_enums.cpp | 2 + src/global_enums.h | 16 +++ src/settings_dialog.cpp | 11 ++ src/tagger.cpp | 121 +++++++++++----------- src/tagger.h | 3 + src/window.cpp | 100 ++++++++++++++++-- src/window.h | 3 + ui/settings.ui | 198 +++++++++++++++++++++--------------- util/command_placeholders.h | 1 + 9 files changed, 308 insertions(+), 147 deletions(-) diff --git a/src/global_enums.cpp b/src/global_enums.cpp index 327c978..d00ce76 100644 --- a/src/global_enums.cpp +++ b/src/global_enums.cpp @@ -46,12 +46,14 @@ struct MetaTypeRegistrator REGISTER_METATYPE_STREAM_OPERATORS(GlobalEnums::ViewMode) REGISTER_METATYPE_STREAM_OPERATORS(GlobalEnums::SortQueueBy) REGISTER_METATYPE_STREAM_OPERATORS(GlobalEnums::EditMode) + REGISTER_METATYPE_STREAM_OPERATORS(GlobalEnums::CommandOutputMode) } }; static const MetaTypeRegistrator _; IMPLEMENT_ENUM_STREAM_OPERATORS(GlobalEnums::ViewMode) IMPLEMENT_ENUM_STREAM_OPERATORS(GlobalEnums::SortQueueBy) IMPLEMENT_ENUM_STREAM_OPERATORS(GlobalEnums::EditMode) +IMPLEMENT_ENUM_STREAM_OPERATORS(GlobalEnums::CommandOutputMode) GlobalEnums::EditMode GlobalEnums::next_edit_mode(EditMode current) diff --git a/src/global_enums.h b/src/global_enums.h index bf5e861..f5752e0 100644 --- a/src/global_enums.h +++ b/src/global_enums.h @@ -79,11 +79,24 @@ class GlobalEnums */ static EditMode next_edit_mode(EditMode current); + + /*! + * \brief Specifies command output mode. + */ + enum class CommandOutputMode { + Ignore, ///< Command output is ignored, and the process is detached. + Replace, ///< Command output replaces current tags + Append, ///< Command output appended to current tags + Prepend ///< Command output prepended to current tags + }; + Q_ENUM(CommandOutputMode); + GlobalEnums() = delete; }; ENUM_STREAM_OPERATORS(GlobalEnums::ViewMode) ENUM_STREAM_OPERATORS(GlobalEnums::SortQueueBy) ENUM_STREAM_OPERATORS(GlobalEnums::EditMode) +ENUM_STREAM_OPERATORS(GlobalEnums::CommandOutputMode) /// Alias for \ref GlobalEnums::ViewMode enumeration @@ -95,4 +108,7 @@ using SortQueueBy = GlobalEnums::SortQueueBy; /// Alias for \ref GlobalEnums::EditMode enumeration using EditMode = GlobalEnums::EditMode; +/// Alias for \ref GlobalEnums::CommandOutputMode enumeration +using CommandOutputMode = GlobalEnums::CommandOutputMode; + #endif // GLOBAL_ENUMS_H diff --git a/src/settings_dialog.cpp b/src/settings_dialog.cpp index 4260bca..34276fc 100644 --- a/src/settings_dialog.cpp +++ b/src/settings_dialog.cpp @@ -9,6 +9,7 @@ #include "ui_settings.h" #include "util/misc.h" #include "util/project_info.h" +#include "global_enums.h" #include "util/command_placeholders.h" #include #include @@ -307,6 +308,7 @@ void SettingsDialog::reset() m_dmpr->setModel(m_cmdl); m_dmpr->addMapping(ui->cmdExecutableEdit, 2); m_dmpr->addMapping(ui->cmdArgsEdit, 3); + m_dmpr->addMapping(ui->cmdOutputMode, 4, "currentIndex"); m_dmpr->toFirst(); auto fmd = new HideColumnsFilter(m_cmdl); @@ -332,6 +334,8 @@ void SettingsDialog::reset() } disable_widgets(disabled); }); + + ui->cmdView->selectRow(0); } void SettingsDialog::resetModel() @@ -371,6 +375,11 @@ void SettingsDialog::resetModel() path.removeFirst(); m_cmdl->setItem(i, 3, new QStandardItem(join_args(path))); } + + auto mode = st.value(SETT_COMMAND_MODE).value(); + auto item = new QStandardItem(); + item->setData(static_cast(mode), Qt::DisplayRole); + m_cmdl->setItem(i, 4, item); } st.endArray(); } @@ -442,10 +451,12 @@ void SettingsDialog::apply() auto exec_path = m_cmdl->data(m_cmdl->index(i,2)).toString(); auto args = parse_arguments(m_cmdl->data(m_cmdl->index(i,3)).toString()); args.prepend(exec_path); + auto mode = m_cmdl->data(m_cmdl->index(i, 4)).toInt(); settings.setValue(SETT_COMMAND_NAME, name); settings.setValue(SETT_COMMAND_HOTKEY, hotkey); settings.setValue(SETT_COMMAND_CMD, args); + settings.setValue(SETT_COMMAND_MODE, QVariant::fromValue(static_cast(mode))); } settings.endArray(); emit updated(); diff --git a/src/tagger.cpp b/src/tagger.cpp index beaf65c..1f60191 100644 --- a/src/tagger.cpp +++ b/src/tagger.cpp @@ -267,6 +267,69 @@ QString Tagger::text() const return m_input.text(); } +void Tagger::addTags(QString tags, int *tag_count) +{ + TagEditState state; + auto options = TagParser::FixOptions::from_settings(); + options.sort = true; + + // Autofix imageboard tags before comparing and assigning + util::replace_special(tags); + auto fixed_tags_list = m_input.tag_parser().fixTags(state, tags, options); + tags = util::join(fixed_tags_list); + if (tag_count) { + *tag_count = fixed_tags_list.size(); + } + + util::replace_special(tags); + auto current_tags = m_input.tags(); + + if (current_tags != tags) { + + if (!util::is_hex_string(current_tags)) { + + auto current_list = m_input.tags_list(); + // sort current tags in case they were being edited and unsorted. + // imageboard tags are already sorted. + current_list.sort(); + + QString added_tags_str, removed_tags_str; + getTagDifference(current_list, fixed_tags_list, added_tags_str, removed_tags_str, true); + + // Ask user what to do with tags + QMessageBox mbox(QMessageBox::Question, + tr("Fetched tags mismatch"), + tr("

Imageboard tags do not match current tags:" + "

" + "%1

" + "%2%3" + "

Please choose what to do:

").arg(tags) + .arg(added_tags_str) + .arg(removed_tags_str), + QMessageBox::Save|QMessageBox::SaveAll); + + mbox.addButton(QMessageBox::Cancel); + mbox.setButtonText(QMessageBox::Save, tr("Use only imageboard tags")); + mbox.setButtonText(QMessageBox::SaveAll, tr("Merge tags")); + + int res = mbox.exec(); + + if (res == QMessageBox::Save) { + m_input.setTags(tags); + } + if (res == QMessageBox::SaveAll) { + current_tags.append(' '); + current_tags.append(tags); + m_input.setTags(current_tags); + } + } else { + // if there was only hash filename to begin with, + // just use the imageboard tags without asking + setText(tags); + } + } +} + void Tagger::resetText() { setText(QFileInfo(m_file_queue.current()).completeBaseName()); @@ -334,63 +397,7 @@ void Tagger::tagsFetched(QString file, QString tags) // Current file might have already changed since reply came if (currentFile() == file) { - TagEditState state; - auto options = TagParser::FixOptions::from_settings(); - options.sort = true; - - // Autofix imageboard tags before comparing and assigning - util::replace_special(tags); - auto fixed_tags_list = m_input.tag_parser().fixTags(state, tags, options); - tags = util::join(fixed_tags_list); - processed_tags_count = fixed_tags_list.size(); - - util::replace_special(tags); - auto current_tags = m_input.tags(); - - if (current_tags != tags) { - - if (!util::is_hex_string(current_tags)) { - - auto current_list = m_input.tags_list(); - // sort current tags in case they were being edited and unsorted. - // imageboard tags are already sorted. - current_list.sort(); - - QString added_tags_str, removed_tags_str; - getTagDifference(current_list, fixed_tags_list, added_tags_str, removed_tags_str, true); - - // Ask user what to do with tags - QMessageBox mbox(QMessageBox::Question, - tr("Fetched tags mismatch"), - tr("

Imageboard tags do not match current tags:" - "

" - "%1

" - "%2%3" - "

Please choose what to do:

").arg(tags) - .arg(added_tags_str) - .arg(removed_tags_str), - QMessageBox::Save|QMessageBox::SaveAll); - - mbox.addButton(QMessageBox::Cancel); - mbox.setButtonText(QMessageBox::Save, tr("Use only imageboard tags")); - mbox.setButtonText(QMessageBox::SaveAll, tr("Merge tags")); - - int res = mbox.exec(); - - if (res == QMessageBox::Save) { - m_input.setTags(tags); - } - if (res == QMessageBox::SaveAll) { - current_tags.append(' '); - current_tags.append(tags); - m_input.setTags(current_tags); - } - } else { - // if there was only hash filename to begin with, - // just use the imageboard tags without asking - setText(tags); - } - } + addTags(tags, &processed_tags_count); } TaggerStatistics::instance().tagsFetched(overall_tags_count, processed_tags_count); diff --git a/src/tagger.h b/src/tagger.h index cb16213..2eaf857 100644 --- a/src/tagger.h +++ b/src/tagger.h @@ -138,6 +138,9 @@ class Tagger : public QWidget /// Current tag input text. QString text() const; + /// Ask user to add tags (maybe replacing current tags?) + void addTags(QString tags, int* tag_count=nullptr); + /// Undo tag input changes and set original tags. void resetText(); diff --git a/src/window.cpp b/src/window.cpp index 7e68bb9..e01c175 100644 --- a/src/window.cpp +++ b/src/window.cpp @@ -465,6 +465,17 @@ void Window::showFileHashingProgress(QString file, int value) statusBar()->showMessage(tr("Calculating file hash... %1% complete").arg(value)); } +void Window::showCommandExecutionProgress(QString command_name) +{ +#ifdef Q_OS_WIN32 + auto progress = m_win_taskbar_button.progress(); + progress->setVisible(true); + progress->setMaximum(0); + progress->setValue(0); +#endif + statusBar()->showMessage(tr("Running %1...").arg(command_name)); +} + void Window::hideUploadProgress() { #ifdef Q_OS_WIN32 @@ -974,6 +985,7 @@ void Window::createCommands() auto name = settings.value(SETT_COMMAND_NAME).toString(); auto cmd = settings.value(SETT_COMMAND_CMD).toStringList(); auto hkey = settings.value(SETT_COMMAND_HOTKEY).toString(); + auto mode = settings.value(SETT_COMMAND_MODE).value(); if(name == CMD_SEPARATOR) { menu_commands.addSeparator(); @@ -998,7 +1010,7 @@ void Window::createCommands() action->setShortcut(hkey); } - connect(action, &QAction::triggered, this, [this,name,binary,cmd]() + connect(action, &QAction::triggered, this, [this,name,binary,cmd,mode]() { auto cmd_tmp = cmd; // NOTE: separate copy is needed instead of mutable lambda auto to_native = [](const auto& path) @@ -1026,13 +1038,85 @@ void Window::createCommands() to_native(remove_ext(m_tagger.currentFileName()))); } - auto success = QProcess::startDetached(binary, cmd_tmp, m_tagger.currentDir()); - pdbg << "QProcess::startDetached(" << binary << "," << cmd_tmp << ") =>" << success; - if(!success) { - QMessageBox::critical(this, - tr("Failed to start command"), - tr("

Failed to launch command %1:

Could not start %2.

") - .arg(name, binary)); + if (mode == CommandOutputMode::Ignore) { + bool success = QProcess::startDetached(binary, cmd_tmp, m_tagger.currentDir()); + pdbg << "QProcess::startDetached(" << binary << "," << cmd_tmp << ") =>" << success; + if(!success) { + QMessageBox::critical(this, + tr("Failed to start command"), + tr("

Failed to launch command %1:

Could not start %2.

") + .arg(name, binary)); + } + } else { + + auto proc = new QProcess{this}; + proc->setProgram(binary); + proc->setArguments(cmd_tmp); + proc->setWorkingDirectory(m_tagger.currentDir()); + + showCommandExecutionProgress(name); + + connect(&m_tagger, + &Tagger::fileOpened, + proc, + [this, proc](const QString&) + { + if (proc->state() != QProcess::NotRunning) { + disconnect(proc, nullptr, this, nullptr); + proc->terminate(); + } + }); + + connect(proc, + QOverload::of(&QProcess::finished), + this, + [this, name, binary, mode](int exit_code, QProcess::ExitStatus exit_status) + { + Q_UNUSED(exit_code); + + if (exit_status != QProcess::NormalExit) { + QMessageBox::critical(this, + tr("Failed to start command"), + tr("

Failed to launch command %1:

Could not start %2.

") + .arg(name, binary)); + + + hideUploadProgress(); + return; + } + + auto proc = qobject_cast(sender()); + Q_ASSERT(proc); + + auto tags_data = proc->readAllStandardOutput(); + + QTextStream in{tags_data}; + in.setCodec("UTF-8"); + + auto tags = in.readAll().replace('\n', ' ').trimmed(); + if (!tags.isEmpty()) { + auto current_tags = m_tagger.text(); + + switch(mode) { + case CommandOutputMode::Replace: + m_tagger.addTags(tags); + break; + case CommandOutputMode::Append: + m_tagger.setText(current_tags + " " + tags); + break; + case CommandOutputMode::Prepend: + m_tagger.setText(tags + " " + current_tags); + break; + default: + break; + } + } + hideUploadProgress(); + proc->deleteLater(); + }, Qt::QueuedConnection); + + proc->start(QProcess::ReadOnly); + } }); menu_commands.setEnabled(true); diff --git a/src/window.h b/src/window.h index a61fd86..77bee01 100644 --- a/src/window.h +++ b/src/window.h @@ -49,6 +49,9 @@ public slots: /// Display file hashing progress. void showFileHashingProgress(QString file, int value); + /// Display command execution progress. + void showCommandExecutionProgress(QString command_name); + /// Hide current upload progress void hideUploadProgress(); diff --git a/ui/settings.ui b/ui/settings.ui index b895787..661af3c 100644 --- a/ui/settings.ui +++ b/ui/settings.ui @@ -587,6 +587,122 @@ In Main View + + + + + + true + + + + + + + &Browse... + + + + + + + Arguments are used to pass information to launched program, such as files to open. + + + + + + + Arguments are used to pass information to launched program, such as files to open. + + + Arguments are used to pass information to launched program, such as files to open. + + + &Arguments + + + cmdArgsEdit + + + + + + + E&xecutable + + + cmdExecutableEdit + + + + + + + + 0 + 0 + + + + + 0 + 22 + + + + <html><head/><body><p>Available substitutions: </p><ul style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;"><li style=" margin-top:12px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">{path} - Path of current file</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">{dir} - Directory of current file</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">{fullname} - Name of current file</li><li style=" margin-top:0px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">{basename} - Name of current file without extension</li></ul></body></html> + + + <html><head/><body><p>Available substitutions: </p><ul style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;"><li style=" margin-top:12px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">{path} - Path of current file</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">{dir} - Directory of current file</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">{fullname} - Name of current file</li><li style=" margin-top:0px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">{basename} - Name of current file without extension</li></ul></body></html> + + + Placeholders + + + QToolButton::InstantPopup + + + Qt::ToolButtonTextOnly + + + + + + + + Ignore + + + + + Replace tags + + + + + Append tags + + + + + Prepend tags + + + + + + + + &Output + + + cmdOutputMode + + + + + @@ -690,88 +806,6 @@ In Main View - - - - - - true - - - - - - - &Browse... - - - - - - - E&xecutable - - - cmdExecutableEdit - - - - - - - Arguments are used to pass information to launched program, such as files to open. - - - Arguments are used to pass information to launched program, such as files to open. - - - &Arguments - - - cmdArgsEdit - - - - - - - Arguments are used to pass information to launched program, such as files to open. - - - - - - - - 0 - 0 - - - - - 0 - 22 - - - - <html><head/><body><p>Available substitutions: </p><ul style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;"><li style=" margin-top:12px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">{path} - Path of current file</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">{dir} - Directory of current file</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">{fullname} - Name of current file</li><li style=" margin-top:0px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">{basename} - Name of current file without extension</li></ul></body></html> - - - <html><head/><body><p>Available substitutions: </p><ul style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;"><li style=" margin-top:12px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">{path} - Path of current file</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">{dir} - Directory of current file</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">{fullname} - Name of current file</li><li style=" margin-top:0px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">{basename} - Name of current file without extension</li></ul></body></html> - - - Placeholders - - - QToolButton::InstantPopup - - - Qt::ToolButtonTextOnly - - - - - diff --git a/util/command_placeholders.h b/util/command_placeholders.h index 7bc13c8..727f1ac 100644 --- a/util/command_placeholders.h +++ b/util/command_placeholders.h @@ -12,6 +12,7 @@ #define SETT_COMMAND_NAME QStringLiteral("display_name") #define SETT_COMMAND_CMD QStringLiteral("command") #define SETT_COMMAND_HOTKEY QStringLiteral("hotkey") +#define SETT_COMMAND_MODE QStringLiteral("mode") #define CMD_SEPARATOR QStringLiteral("__separator__")