File indexing completed on 2024-04-28 04:51:35

0001 /*
0002     SPDX-FileCopyrightText: 2022 Jean-Baptiste Mardelle <jb@kdenlive.org>
0003 
0004 SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
0005 */
0006 
0007 #include "clipjobmanager.h"
0008 
0009 #include "core.h"
0010 #include "effects/effectsrepository.hpp"
0011 #include "kdenlivesettings.h"
0012 
0013 #include "kdenlive_debug.h"
0014 #include <KLocalizedString>
0015 #include <KMessageBox>
0016 #include <QButtonGroup>
0017 #include <QFontDatabase>
0018 #include <QUuid>
0019 
0020 ClipJobManager::ClipJobManager(AbstractTask::JOBTYPE type, QWidget *parent)
0021     : QDialog(parent)
0022 {
0023     setFont(QFontDatabase::systemFont(QFontDatabase::SmallestReadableFont));
0024     setupUi(this);
0025     setWindowTitle(i18n("Manage Bin Clip Jobs"));
0026     connect(job_list, &QListWidget::currentRowChanged, this, &ClipJobManager::displayJob);
0027     loadJobs();
0028     if (type != AbstractTask::NOJOBTYPE) {
0029         for (int i = 0; i < job_list->count(); i++) {
0030             QListWidgetItem *item = job_list->item(i);
0031             if (item && item->data(Qt::UserRole + 1).toInt() == (int)type) {
0032                 job_list->setCurrentRow(i);
0033                 break;
0034             }
0035         }
0036     } else {
0037         job_list->setCurrentRow(0);
0038     }
0039 
0040     job_list->setEditTriggers(QAbstractItemView::DoubleClicked | QAbstractItemView::EditKeyPressed);
0041     connect(button_add, &QToolButton::clicked, this, &ClipJobManager::addJob);
0042     connect(button_delete, &QToolButton::clicked, this, &ClipJobManager::deleteJob);
0043 
0044     job_params->setToolTip(i18n("Arguments for the command line script"));
0045     job_params->setWhatsThis(
0046         xi18nc("@info:whatsthis",
0047                "<b>{&#x25;1}</b> will be replaced by the first parameter, <b>{&#x25;2}</b> by the second, , <b>{&#x25;3}</b> by the output file path."));
0048     destination_pattern->setToolTip(i18n("File extension for the output file"));
0049     destination_pattern->setWhatsThis(
0050         xi18nc("@info:whatsthis",
0051                "The output file name will be the same as the source bin clip filename, with the modified extension. It will be appended at the end of the "
0052                "arguments or inserted in place of <b>{&#x25;3}</b> if found. If output filename already exists, a -0001 pattern will be appended."));
0053 
0054     param1_isfile->setToolTip(i18n("If selected, a file path will be requested on execution"));
0055     param1_islist->setToolTip(i18n("If selected, a dropdown list of values will be shown on execution"));
0056     param1_isframe->setToolTip(
0057         i18n("If selected, the selected clip's current frame will be extracted to a temporary file and the parameter value will be the image path"));
0058     param1_list->setToolTip(i18n("A newline separated list of possible values that will be offered on execution"));
0059     param1_list->setWhatsThis(
0060         xi18nc("@info:whatsthis",
0061                "When the parameter is set to <b>Request Option in List</b> the user will be asked to choose a value in this list when the job is started."));
0062 
0063     param2_isfile->setToolTip(i18n("If selected, a file path will be requested on execution"));
0064     param1_islist->setToolTip(i18n("If selected, a dropdown list of values will be shown on execution"));
0065     param2_list->setWhatsThis(
0066         xi18nc("@info:whatsthis",
0067                "When the parameter is set to <b>Request Option in List</b> the user will be asked to choose a value in this list when the job is started."));
0068 
0069     connect(job_list, &QListWidget::itemChanged, this, &ClipJobManager::updateName);
0070 
0071     QButtonGroup *bg = new QButtonGroup(this);
0072     bg->setExclusive(false);
0073     bg->addButton(enable_video);
0074     bg->addButton(enable_audio);
0075     bg->addButton(enable_image);
0076 
0077     QButtonGroup *param1Bg = new QButtonGroup(this);
0078     param1Bg->setExclusive(true);
0079     param1Bg->addButton(param1_isfile);
0080     param1Bg->addButton(param1_islist);
0081     param1Bg->addButton(param1_isframe);
0082 
0083     // Mark preset as dirty if anything changes
0084     connect(url_binary, &KUrlRequester::textChanged, this, [this]() {
0085         checkScript();
0086         setDirty();
0087     });
0088     connect(job_params, &QPlainTextEdit::textChanged, this, &ClipJobManager::setDirty);
0089     connect(destination_pattern, &QLineEdit::textChanged, this, &ClipJobManager::setDirty);
0090     connect(radio_replace, &QRadioButton::toggled, this, &ClipJobManager::setDirty);
0091     connect(combo_folder, &QComboBox::currentIndexChanged, this, &ClipJobManager::setDirty);
0092     connect(folder_name, &QLineEdit::textChanged, this, &ClipJobManager::setDirty);
0093     connect(param1_list, &QPlainTextEdit::textChanged, this, &ClipJobManager::setDirty);
0094     connect(param2_list, &QPlainTextEdit::textChanged, this, &ClipJobManager::setDirty);
0095     connect(taskDescription, &QPlainTextEdit::textChanged, this, &ClipJobManager::setDirty);
0096     connect(param1Bg, &QButtonGroup::buttonClicked, this, &ClipJobManager::setDirty);
0097     connect(param2_islist, &QAbstractButton::toggled, this, &ClipJobManager::setDirty);
0098     connect(param1_name, &QLineEdit::textChanged, this, &ClipJobManager::setDirty);
0099     connect(param2_name, &QLineEdit::textChanged, this, &ClipJobManager::setDirty);
0100     connect(bg, &QButtonGroup::idToggled, this, &ClipJobManager::setDirty);
0101     info_message->setVisible(false);
0102     checkScript();
0103     connect(buttonBox->button(QDialogButtonBox::Close), &QPushButton::clicked, this, &ClipJobManager::validate);
0104 }
0105 
0106 ClipJobManager::~ClipJobManager() {}
0107 
0108 void ClipJobManager::setDirty()
0109 {
0110     m_dirty = job_list->currentItem()->data(Qt::UserRole).toString();
0111 }
0112 
0113 void ClipJobManager::validate()
0114 {
0115     // ensure changes to the current preset get saved
0116     displayJob(-1);
0117     writePresetsToConfig();
0118     accept();
0119 }
0120 
0121 void ClipJobManager::loadJobs()
0122 {
0123     QSignalBlocker bk(job_list);
0124     job_list->clear();
0125     // Add jobs
0126     KConfig conf(QStringLiteral("clipjobsettings.rc"), KConfig::CascadeConfig, QStandardPaths::AppDataLocation);
0127     KConfigGroup group(&conf, "Ids");
0128     m_ids = group.entryMap();
0129     QListWidgetItem *item;
0130 
0131     // Add builtin jobs
0132     item = new QListWidgetItem(i18n("Stabilize"), job_list, QListWidgetItem::Type);
0133     item->setData(Qt::UserRole, QLatin1String("stabilize"));
0134     item->setData(Qt::UserRole + 1, AbstractTask::STABILIZEJOB);
0135     item = new QListWidgetItem(i18n("Duplicate Clip with Speed Change…"), job_list, QListWidgetItem::Type);
0136     item->setData(Qt::UserRole, QLatin1String("timewarp"));
0137     item->setData(Qt::UserRole + 1, AbstractTask::SPEEDJOB);
0138 
0139     QMapIterator<QString, QString> k(m_ids);
0140     while (k.hasNext()) {
0141         k.next();
0142         if (!k.value().isEmpty()) {
0143             item = new QListWidgetItem(k.value(), job_list, QListWidgetItem::UserType);
0144             item->setData(Qt::UserRole, k.key());
0145             item->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEditable | Qt::ItemIsEnabled);
0146         }
0147     }
0148     // Read all data
0149     KConfigGroup groupParams(&conf, "Parameters");
0150     m_params = groupParams.entryMap();
0151     KConfigGroup groupFolder(&conf, "FolderName");
0152     m_folderNames = groupFolder.entryMap();
0153     KConfigGroup groupUse(&conf, "FolderUse");
0154     m_folderUse = groupUse.entryMap();
0155     KConfigGroup groupOutput(&conf, "Output");
0156     m_output = groupOutput.entryMap();
0157     KConfigGroup groupBinary(&conf, "Binary");
0158     m_binaries = groupBinary.entryMap();
0159     KConfigGroup groupEnabled(&conf, "EnabledTypes");
0160     m_enableType = groupEnabled.entryMap();
0161     KConfigGroup param1Type(&conf, "Param1Type");
0162     m_param1Type = param1Type.entryMap();
0163     KConfigGroup param1List(&conf, "Param1List");
0164     m_param1List = param1List.entryMap();
0165     KConfigGroup param2Type(&conf, "Param2Type");
0166     m_param2Type = param2Type.entryMap();
0167     KConfigGroup param2List(&conf, "Param2List");
0168     m_param2List = param2List.entryMap();
0169     KConfigGroup param1Name(&conf, "Param1Name");
0170     m_param1Name = param1Name.entryMap();
0171     KConfigGroup param2Name(&conf, "Param2Name");
0172     m_param2Name = param2Name.entryMap();
0173     KConfigGroup descName(&conf, "Description");
0174     m_description = descName.entryMap();
0175 }
0176 
0177 void ClipJobManager::displayJob(int row)
0178 {
0179     if (!m_dirty.isEmpty()) {
0180         saveCurrentPreset();
0181     }
0182     if (row == -1) {
0183         param_box->setEnabled(false);
0184         return;
0185     }
0186     QListWidgetItem *item = job_list->item(row);
0187     QString jobId = item->data(Qt::UserRole).toString();
0188     bool customJob = item->type() == QListWidgetItem::UserType;
0189     param_box->setEnabled(customJob);
0190     button_delete->setEnabled(customJob);
0191     if (customJob && !m_ids.contains(jobId)) {
0192         // This is a new job, set some default values
0193         url_binary->setUrl(QUrl::fromLocalFile(KdenliveSettings::ffmpegpath()));
0194         job_params->setPlainText(QStringLiteral("-i {source} -codec:a copy -codec:v copy {output}"));
0195     } else {
0196         url_binary->setText(m_binaries.value(jobId));
0197         job_params->setPlainText(m_params.value(jobId));
0198     }
0199     destination_pattern->setText(m_output.value(jobId));
0200     folder_name->setText(m_folderNames.value(jobId));
0201     folder_name->setText(m_folderNames.value(jobId));
0202     if (m_folderUse.contains(jobId) || m_dirty == jobId) {
0203         folder_box->setEnabled(true);
0204         if (m_folderUse.value(jobId) == QLatin1String("replace")) {
0205             radio_replace->setChecked(true);
0206         } else if (m_folderUse.value(jobId) == QLatin1String("rootfolder")) {
0207             radio_folder->setChecked(true);
0208             combo_folder->setCurrentIndex(0);
0209         } else if (m_folderUse.value(jobId) == QLatin1String("subfolder")) {
0210             radio_folder->setChecked(true);
0211             combo_folder->setCurrentIndex(1);
0212         } else {
0213             folder_box->setEnabled(false);
0214         }
0215     } else {
0216         folder_box->setEnabled(false);
0217     }
0218     enable_video->setChecked(m_enableType.value(jobId).contains(QLatin1String("v")));
0219     enable_audio->setChecked(m_enableType.value(jobId).contains(QLatin1String("a")));
0220     enable_image->setChecked(m_enableType.value(jobId).contains(QLatin1String("i")));
0221 
0222     if (m_param1Type.value(jobId) == QLatin1String("list")) {
0223         param1_islist->setChecked(true);
0224     } else if (m_param1Type.value(jobId) == QLatin1String("frame")) {
0225         param1_isframe->setChecked(true);
0226     } else {
0227         param1_isfile->setChecked(true);
0228     }
0229     if (m_param2Type.value(jobId) == QLatin1String("list")) {
0230         param2_islist->setChecked(true);
0231     } else {
0232         param2_isfile->setChecked(true);
0233     }
0234     param1_name->setText(m_param1Name.value(jobId));
0235     param2_name->setText(m_param2Name.value(jobId));
0236     taskDescription->setPlainText(m_description.value(jobId));
0237 
0238     QStringList option1 = m_param1List.value(jobId).split(QLatin1String("  "));
0239     param1_list->setPlainText(option1.join(QLatin1Char('\n')));
0240     QStringList option2 = m_param2List.value(jobId).split(QLatin1String("  "));
0241     param2_list->setPlainText(option2.join(QLatin1Char('\n')));
0242 }
0243 
0244 void ClipJobManager::checkScript()
0245 {
0246     if (url_binary->text().isEmpty() || !url_binary->isEnabled()) {
0247         script_message->setVisible(false);
0248         return;
0249     }
0250     QFileInfo info(url_binary->text());
0251     if (!info.exists()) {
0252         script_message->setText(i18n("Missing executable"));
0253         script_message->setVisible(true);
0254     } else if (!info.isExecutable()) {
0255         script_message->setText(i18n("Your script or application %1 is not executable, change permissions", info.fileName()));
0256         script_message->setVisible(true);
0257     } else {
0258         script_message->setVisible(false);
0259     }
0260 }
0261 
0262 QMap<QString, QString> ClipJobManager::getClipJobNames()
0263 {
0264     KConfig conf(QStringLiteral("clipjobsettings.rc"), KConfig::CascadeConfig, QStandardPaths::AppDataLocation);
0265     KConfigGroup group(&conf, "Ids");
0266     QMap<QString, QString> ids = group.entryMap();
0267     KConfigGroup group2(&conf, "EnabledTypes");
0268     QMap<QString, QString> id2s = group2.entryMap();
0269     // Append supported type after job key
0270     QMapIterator<QString, QString> i(ids);
0271     QMap<QString, QString> newIds;
0272     while (i.hasNext()) {
0273         i.next();
0274         if (!i.value().isEmpty()) {
0275             newIds.insert(QString("%1;%2").arg(i.key(), id2s.value(i.key())), i.value());
0276         }
0277     }
0278     // Add the 3 internal jobs
0279     if (EffectsRepository::get()->exists(QLatin1String("vidstab"))) {
0280         newIds.insert(QStringLiteral("stabilize;v"), i18n("Stabilize"));
0281     }
0282     newIds.insert(QStringLiteral("scenesplit;v"), i18n("Automatic Scene Split…"));
0283     if (KdenliveSettings::producerslist().contains(QLatin1String("timewarp"))) {
0284         newIds.insert(QStringLiteral("timewarp;av"), i18n("Duplicate Clip with Speed Change…"));
0285     }
0286     return newIds;
0287 }
0288 
0289 std::pair<ClipJobManager::JobCompletionAction, QString> ClipJobManager::getJobAction(const QString &jobId)
0290 {
0291     KConfig conf(QStringLiteral("clipjobsettings.rc"), KConfig::CascadeConfig, QStandardPaths::AppDataLocation);
0292     KConfigGroup group(&conf, "FolderUse");
0293     KConfigGroup nameGroup(&conf, "FolderName");
0294     QString useValue = group.readEntry(jobId, QString());
0295     if (useValue == QLatin1String("replace")) {
0296         return {JobCompletionAction::ReplaceOriginal, QString()};
0297     }
0298     if (useValue == QLatin1String("rootfolder")) {
0299         return {JobCompletionAction::RootFolder, nameGroup.readEntry(jobId, QString())};
0300     }
0301     if (useValue == QLatin1String("subfolder")) {
0302         return {JobCompletionAction::SubFolder, nameGroup.readEntry(jobId, QString())};
0303     }
0304     return {JobCompletionAction::NoAction, nameGroup.readEntry(jobId, QString())};
0305 }
0306 
0307 QMap<QString, QString> ClipJobManager::getJobParameters(const QString &jobId)
0308 {
0309     QMap<QString, QString> result;
0310     KConfig conf(QStringLiteral("clipjobsettings.rc"), KConfig::CascadeConfig, QStandardPaths::AppDataLocation);
0311     KConfigGroup idGroup(&conf, "Ids");
0312     result.insert(QLatin1String("description"), idGroup.readEntry(jobId, i18n("Job description")));
0313     KConfigGroup group(&conf, "Binary");
0314     result.insert(QLatin1String("binary"), group.readEntry(jobId, QString()));
0315     KConfigGroup paramGroup(&conf, "Parameters");
0316     result.insert(QLatin1String("parameters"), paramGroup.readEntry(jobId, QString()));
0317     KConfigGroup outGroup(&conf, "Output");
0318     result.insert(QLatin1String("output"), outGroup.readEntry(jobId, QString()));
0319     KConfigGroup p1Group(&conf, "Param1Type");
0320     result.insert(QLatin1String("param1type"), p1Group.readEntry(jobId, QString()));
0321     KConfigGroup p2Group(&conf, "Param2Type");
0322     result.insert(QLatin1String("param2type"), p2Group.readEntry(jobId, QString()));
0323     KConfigGroup p1List(&conf, "Param1List");
0324     result.insert(QLatin1String("param1list"), p1List.readEntry(jobId, QString()));
0325     KConfigGroup p2List(&conf, "Param2List");
0326     result.insert(QLatin1String("param2list"), p2List.readEntry(jobId, QString()));
0327     KConfigGroup p1Name(&conf, "Param1Name");
0328     result.insert(QLatin1String("param1name"), p1Name.readEntry(jobId, QString()));
0329     KConfigGroup p2Name(&conf, "Param2Name");
0330     result.insert(QLatin1String("param2name"), p2Name.readEntry(jobId, QString()));
0331     KConfigGroup descName(&conf, "Description");
0332     result.insert(QLatin1String("details"), descName.readEntry(jobId, QString()));
0333     return result;
0334 }
0335 
0336 void ClipJobManager::saveCurrentPreset()
0337 {
0338     m_folderNames.insert(m_dirty, folder_name->text().simplified());
0339     if (radio_replace->isChecked()) {
0340         m_folderUse.insert(m_dirty, QStringLiteral("replace"));
0341     } else if (combo_folder->currentIndex() == 0) {
0342         m_folderUse.insert(m_dirty, QStringLiteral("rootfolder"));
0343     } else {
0344         m_folderUse.insert(m_dirty, QStringLiteral("subfolder"));
0345     }
0346     m_binaries.insert(m_dirty, url_binary->text().simplified());
0347     m_params.insert(m_dirty, job_params->toPlainText().simplified());
0348     m_output.insert(m_dirty, destination_pattern->text().simplified());
0349     m_description.insert(m_dirty, taskDescription->toPlainText().simplified());
0350     QString enabledTypes;
0351     if (enable_video->isChecked()) {
0352         enabledTypes.append(QLatin1String("v"));
0353     }
0354     if (enable_audio->isChecked()) {
0355         enabledTypes.append(QLatin1String("a"));
0356     }
0357     if (enable_image->isChecked()) {
0358         enabledTypes.append(QLatin1String("i"));
0359     }
0360     m_enableType.insert(m_dirty, enabledTypes);
0361     m_param1Type.insert(m_dirty,
0362                         param1_islist->isChecked() ? QLatin1String("list") : (param1_isframe->isChecked() ? QLatin1String("frame") : QLatin1String("file")));
0363     m_param2Type.insert(m_dirty, param2_islist->isChecked() ? QLatin1String("list") : QLatin1String("file"));
0364     QStringList option1 = param1_list->toPlainText().split(QLatin1Char('\n'));
0365     m_param1List.insert(m_dirty, option1.join(QLatin1String("  ")));
0366     QStringList option2 = param2_list->toPlainText().split(QLatin1Char('\n'));
0367     m_param2List.insert(m_dirty, option2.join(QLatin1String("  ")));
0368     m_param1Name.insert(m_dirty, param1_name->text());
0369     m_param2Name.insert(m_dirty, param2_name->text());
0370     m_description.insert(m_dirty, taskDescription->toPlainText());
0371     m_dirty.clear();
0372 }
0373 
0374 void ClipJobManager::writeGroup(KConfig &conf, const QString &groupName, QMap<QString, QString> values)
0375 {
0376     KConfigGroup idGroup(&conf, groupName);
0377     QMapIterator<QString, QString> i(values);
0378     while (i.hasNext()) {
0379         i.next();
0380         idGroup.writeEntry(i.key(), i.value());
0381     }
0382 }
0383 
0384 void ClipJobManager::writePresetsToConfig()
0385 {
0386     KConfig conf(QStringLiteral("clipjobsettings.rc"), KConfig::CascadeConfig, QStandardPaths::AppDataLocation);
0387     writeGroup(conf, QStringLiteral("Ids"), m_ids);
0388     writeGroup(conf, QStringLiteral("Binary"), m_binaries);
0389     writeGroup(conf, QStringLiteral("Parameters"), m_params);
0390     writeGroup(conf, QStringLiteral("Output"), m_output);
0391     writeGroup(conf, QStringLiteral("FolderName"), m_folderNames);
0392     writeGroup(conf, QStringLiteral("FolderUse"), m_folderUse);
0393     writeGroup(conf, QStringLiteral("EnabledTypes"), m_enableType);
0394     writeGroup(conf, QStringLiteral("Param1Type"), m_param1Type);
0395     writeGroup(conf, QStringLiteral("Param2Type"), m_param2Type);
0396     writeGroup(conf, QStringLiteral("Param1List"), m_param1List);
0397     writeGroup(conf, QStringLiteral("Param2List"), m_param2List);
0398     writeGroup(conf, QStringLiteral("Param1Name"), m_param1Name);
0399     writeGroup(conf, QStringLiteral("Param2Name"), m_param2Name);
0400     writeGroup(conf, QStringLiteral("Description"), m_description);
0401 }
0402 
0403 void ClipJobManager::addJob()
0404 {
0405     const QString uuid = QUuid::createUuid().toString();
0406     QString jobName = i18n("My Clip Job");
0407     bool newName = false;
0408     int j = 1;
0409     while (newName == false) {
0410         int i = 0;
0411         for (; i < job_list->count(); i++) {
0412             QListWidgetItem *it = job_list->item(i);
0413             if (it->text() == jobName) {
0414                 jobName = i18n("My Clip Job %1", j);
0415                 j++;
0416                 break;
0417             }
0418         }
0419         if (i == job_list->count()) {
0420             // All the list was parsed so this is a unique name
0421             newName = true;
0422         }
0423     }
0424     QListWidgetItem *item = new QListWidgetItem(jobName, job_list, QListWidgetItem::UserType);
0425     item->setData(Qt::UserRole, uuid);
0426     m_folderUse.insert(uuid, QStringLiteral("replace"));
0427     m_enableType.insert(uuid, QStringLiteral("v"));
0428     item->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEditable | Qt::ItemIsEnabled);
0429     job_list->setCurrentItem(item);
0430     KConfig conf(QStringLiteral("clipjobsettings.rc"), KConfig::CascadeConfig, QStandardPaths::AppDataLocation);
0431     KConfigGroup idGroup(&conf, "Ids");
0432     idGroup.writeEntry(uuid, item->text());
0433     m_dirty = uuid;
0434 }
0435 
0436 void ClipJobManager::deleteJob()
0437 {
0438     QListWidgetItem *item = job_list->currentItem();
0439     if (item && item->type() == QListWidgetItem::UserType) {
0440         if (KMessageBox::warningContinueCancel(this, i18n("Permanently delete this Clip Job:\n%1 ?", item->text())) == KMessageBox::Continue) {
0441             const QString jobId = item->data(Qt::UserRole).toString();
0442             KConfig conf(QStringLiteral("clipjobsettings.rc"), KConfig::CascadeConfig, QStandardPaths::AppDataLocation);
0443             QStringList groupIds = {QStringLiteral("Ids"),    QStringLiteral("Binary"),     QStringLiteral("Parameters"),
0444                                     QStringLiteral("Output"), QStringLiteral("FolderName"), QStringLiteral("FolderUse")};
0445             for (auto &gName : groupIds) {
0446                 KConfigGroup group(&conf, gName);
0447                 group.deleteEntry(jobId);
0448             }
0449             delete item;
0450             m_ids.remove(jobId);
0451             m_binaries.remove(jobId);
0452             m_params.remove(jobId);
0453             m_output.remove(jobId);
0454             m_folderNames.remove(jobId);
0455             m_folderUse.remove(jobId);
0456             m_enableType.remove(jobId);
0457             m_param1Type.remove(jobId);
0458             m_param1List.remove(jobId);
0459             m_param2Type.remove(jobId);
0460             m_param2List.remove(jobId);
0461             m_param1Name.remove(jobId);
0462             m_param2Name.remove(jobId);
0463             m_description.remove(jobId);
0464             m_dirty.clear();
0465             job_list->setCurrentRow(0);
0466         }
0467     }
0468 }
0469 
0470 void ClipJobManager::updateName(QListWidgetItem *item)
0471 {
0472     setDirty();
0473     m_ids.insert(m_dirty, item->text());
0474 }