File indexing completed on 2024-05-26 04:59:40
0001 /* 0002 SPDX-FileCopyrightText: 2007-2009 Sergio Pistone <sergio_pistone@yahoo.com.ar> 0003 SPDX-FileCopyrightText: 2010-2022 Mladen Milinkovic <max@smoothware.net> 0004 0005 SPDX-License-Identifier: GPL-2.0-or-later 0006 */ 0007 0008 #include "scriptsmanager.h" 0009 0010 #include "appglobal.h" 0011 #include "application.h" 0012 #include "actions/useraction.h" 0013 #include "actions/useractionnames.h" 0014 #include "dialogs/textinputdialog.h" 0015 #include "helpers/common.h" 0016 #include "helpers/fileloadhelper.h" 0017 #include "helpers/filetrasher.h" 0018 #include "scripting/scripting_rangesmodule.h" 0019 #include "scripting/scripting_stringsmodule.h" 0020 #include "scripting/scripting_subtitlemodule.h" 0021 #include "scripting/scripting_subtitlelinemodule.h" 0022 0023 #include <QAbstractItemModel> 0024 #include <QStandardPaths> 0025 #include <QDialog> 0026 #include <QFileDialog> 0027 #include <QJSEngine> 0028 #include <QMenuBar> 0029 #include <QMenu> 0030 #include <QDesktopServices> 0031 #include <QKeyEvent> 0032 #include <QStringBuilder> 0033 0034 #include <kio_version.h> 0035 #include <KMessageBox> 0036 #if KIO_VERSION < QT_VERSION_CHECK(5, 71, 0) 0037 #include <KRun> 0038 #endif 0039 #include <KActionCollection> 0040 #include <KLocalizedString> 0041 #if KIO_VERSION >= QT_VERSION_CHECK(5, 71, 0) 0042 #include <KIO/OpenUrlJob> 0043 #include <KIO/JobUiDelegate> 0044 #endif 0045 #if KIO_VERSION >= QT_VERSION_CHECK(5, 98, 0) 0046 #include <KIO/JobUiDelegateFactory> 0047 #endif 0048 #include <kwidgetsaddons_version.h> 0049 0050 inline static const QDir & 0051 userScriptDir() 0052 { 0053 static const QDir *dir = nullptr; 0054 if(dir == nullptr) { 0055 const QString userScriptDirName = $("scripts"); 0056 static QDir d(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation)); 0057 if(!d.exists(userScriptDirName)) 0058 d.mkpath(userScriptDirName); 0059 d.cd(userScriptDirName); 0060 dir = &d; 0061 } 0062 return *dir; 0063 } 0064 0065 namespace SubtitleComposer { 0066 class SCScript 0067 { 0068 friend class InstalledScriptsModel; 0069 0070 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) 0071 template<class> friend struct QtPrivate::QGenericArrayOps; 0072 template<typename iterator, typename N> 0073 friend void QtPrivate::q_relocate_overlap_n_left_move(iterator first, N n, iterator d_first); 0074 template<typename> friend class QList; 0075 #endif 0076 0077 SCScript(const QString &path, const QString &name) 0078 : m_name(name), 0079 m_path(path), 0080 m_isScript(name.right(3) == $(".js")), 0081 m_title(name) 0082 { 0083 if(m_isScript) 0084 initScript(); 0085 else 0086 m_category = i18n("Non-Script Files"); 0087 } 0088 0089 void initScript() 0090 { 0091 const QString script = content(); 0092 0093 QRegularExpressionMatch m; 0094 staticRE$(reCat, "@category\\s+(.+)\\s*$", REm); 0095 if((m = reCat.match(script)).hasMatch()) 0096 m_category = m.captured(1); 0097 staticRE$(reName, "@name\\s+(.+)\\s*$", REm); 0098 if((m = reName.match(script)).hasMatch()) 0099 m_title = m.captured(1); 0100 staticRE$(reVer, "@version\\s+(.+)\\s*$", REm); 0101 if((m = reVer.match(script)).hasMatch()) 0102 m_version = m.captured(1); 0103 staticRE$(reSummary, "@summary\\s+(.+)\\s*$", REm); 0104 if((m = reSummary.match(script)).hasMatch()) 0105 m_description = m.captured(1); 0106 staticRE$(reAuthor, "@author\\s+(.+)\\s*$", REm); 0107 if((m = reAuthor.match(script)).hasMatch()) 0108 m_author = m.captured(1); 0109 } 0110 0111 friend QVector<SCScript>; 0112 SCScript() noexcept {} 0113 SCScript(const SCScript &) noexcept = default; 0114 SCScript & operator=(const SCScript &) noexcept = default; 0115 SCScript(SCScript &&) noexcept = default; 0116 SCScript & operator=(SCScript &&) noexcept = default; 0117 0118 public: 0119 QString content() const 0120 { 0121 QFile jsf(m_path); 0122 if(!jsf.open(QFile::ReadOnly | QFile::Text)) 0123 return QString(); 0124 return QTextStream(&jsf).readAll(); 0125 } 0126 0127 inline bool isScript() const { return m_isScript; } 0128 0129 inline const QString & name() const { return m_name; } 0130 inline const QString & path() const { return m_path; } 0131 0132 inline const QString & title() const { return m_title; } 0133 inline const QString & version() const { return m_version; } 0134 inline const QString & category() const { return m_category; } 0135 inline const QString & description() const { return m_description; } 0136 inline const QString & author() const { return m_author; } 0137 0138 inline int compare(const SCScript &other, Qt::CaseSensitivity cs = Qt::CaseSensitive) const 0139 { return m_title.compare(other.m_title, cs); } 0140 0141 private: 0142 QString m_name; 0143 QString m_path; 0144 0145 bool m_isScript; 0146 QString m_title; 0147 QString m_version; 0148 QString m_category; 0149 QString m_description; 0150 QString m_author; 0151 }; 0152 0153 class InstalledScriptsModel : public QAbstractItemModel 0154 { 0155 Q_OBJECT 0156 0157 enum { Title, Version, Author, Path, ColumnCount }; 0158 0159 public: 0160 InstalledScriptsModel(QObject *parent = 0) : QAbstractItemModel(parent) {} 0161 ~InstalledScriptsModel() {} 0162 0163 QVariant headerData(int section, Qt::Orientation orientation, int role) const override 0164 { 0165 if(role != Qt::DisplayRole || orientation == Qt::Vertical) 0166 return QVariant(); 0167 switch(section) { 0168 case Title: return i18n("Title"); 0169 case Version: return i18n("Version"); 0170 case Author: return i18n("Author"); 0171 case Path: return i18n("Path"); 0172 default: return QString(); 0173 } 0174 } 0175 0176 Qt::ItemFlags flags(const QModelIndex &index) const override 0177 { 0178 if(index.internalId() > 0) 0179 return Qt::ItemNeverHasChildren | Qt::ItemIsSelectable | Qt::ItemIsEnabled; 0180 return Qt::ItemIsEnabled; 0181 } 0182 0183 int childIndex(const QString &categoryName, int row) const 0184 { 0185 for(int i = 0, si = 0; i < m_scripts.size(); i++) { 0186 const SCScript &s = m_scripts.at(i); 0187 if(s.category() != categoryName) 0188 continue; 0189 if(si == row) 0190 return i; 0191 si++; 0192 } 0193 Q_ASSERT(false); 0194 return -1; 0195 } 0196 0197 QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override 0198 { 0199 if(!hasIndex(row, column, parent)) 0200 return QModelIndex(); 0201 0202 if(!parent.isValid()) { 0203 // category 0204 if(row < m_categories.size()) 0205 return createIndex(row, column, quintptr(0ULL)); 0206 // script (root) 0207 return createIndex(row, column, childIndex(QString(), row - m_categories.size()) + 1); 0208 } else { 0209 // script (child) 0210 Q_ASSERT(!parent.parent().isValid()); 0211 Q_ASSERT(parent.row() < m_categories.size()); 0212 const QString catName = m_categories.at(parent.row()); 0213 return createIndex(row, column, childIndex(catName, row) + 1); 0214 } 0215 } 0216 0217 QModelIndex parent(const QModelIndex &child) const override 0218 { 0219 if(!child.isValid() || child.internalId() == 0) 0220 return QModelIndex(); 0221 0222 // script 0223 Q_ASSERT(int(child.internalId()) <= m_scripts.size()); 0224 const SCScript &s = m_scripts.at(child.internalId() - 1); 0225 const int i = m_categories.indexOf(s.category()); 0226 if(i >= 0) // script (child) 0227 return createIndex(i, 0, quintptr(0ULL)); 0228 return QModelIndex(); // script (root) 0229 } 0230 0231 int scriptCount(const QString &categoryName) const 0232 { 0233 int n = 0; 0234 for(int i = 0; i < m_scripts.size(); i++) { 0235 if(m_scripts.at(i).category() == categoryName) 0236 n++; 0237 } 0238 return n; 0239 } 0240 0241 int rowCount(const QModelIndex &parent = QModelIndex()) const override 0242 { 0243 if(!parent.isValid()) 0244 return m_categories.size() + scriptCount(QString()); 0245 if(parent.row() >= m_categories.size()) 0246 return 0; 0247 return scriptCount(m_categories.at(parent.row())); 0248 } 0249 0250 int columnCount(const QModelIndex &parent = QModelIndex()) const override 0251 { 0252 Q_UNUSED(parent); 0253 return ColumnCount; 0254 } 0255 0256 QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override 0257 { 0258 if(index.internalId() > 0) { 0259 if(role == Qt::DisplayRole) { 0260 switch(index.column()) { 0261 case Title: return m_scripts.at(index.internalId() - 1).title(); 0262 case Version: return m_scripts.at(index.internalId() - 1).version(); 0263 case Author: return m_scripts.at(index.internalId() - 1).author(); 0264 case Path: return m_scripts.at(index.internalId() - 1).path(); 0265 } 0266 } else if(role == Qt::ToolTipRole) { 0267 if(index.column() == Path) 0268 return m_scripts.at(index.internalId() - 1).path(); 0269 return m_scripts.at(index.internalId() - 1).description(); 0270 } 0271 } else { 0272 if(role == Qt::DisplayRole && index.column() == Title) { 0273 const QString title = m_categories.at(index.row()); 0274 return title.isEmpty() ? $("Misc") : title; 0275 } 0276 } 0277 return QVariant(); 0278 0279 } 0280 0281 const SCScript * findByFilename(const QString &name) const 0282 { 0283 if(name.isEmpty()) 0284 return nullptr; 0285 for(auto it = m_scripts.begin(); it != m_scripts.end(); ++it) { 0286 if(it->name() == name) 0287 return &(*it); 0288 } 0289 return nullptr; 0290 } 0291 0292 const SCScript * findByTitle(const QString &title) const 0293 { 0294 if(title.isEmpty()) 0295 return nullptr; 0296 for(auto it = m_scripts.begin(); it != m_scripts.end(); ++it) { 0297 if(it->title() == title) 0298 return &(*it); 0299 } 0300 return nullptr; 0301 } 0302 0303 void removeAll() 0304 { 0305 beginRemoveRows(QModelIndex(), 0, m_categories.size()); 0306 m_categories.clear(); 0307 m_scripts.clear(); 0308 endRemoveRows(); 0309 } 0310 0311 inline const SCScript * at(int index) const { return &m_scripts.at(index); } 0312 0313 template<class T> 0314 const T * insertSorted(QVector<T> *vec, T &&el) 0315 { 0316 for(int i = 0; i < vec->size(); i++) { 0317 if(vec->at(i).compare(el, Qt::CaseInsensitive) >= 0) { 0318 vec->insert(i, std::move(el)); 0319 return &vec->at(i); 0320 } 0321 } 0322 vec->push_back(std::move(el)); 0323 return &vec->last(); 0324 } 0325 0326 const SCScript * add(const QString &path, int prefixLen) 0327 { 0328 const QString name = path.mid(prefixLen); 0329 if(findByFilename(name)) 0330 return nullptr; 0331 0332 const SCScript *script = insertSorted(&m_scripts, SCScript(path, name)); 0333 const QString catTitle = script->category(); 0334 0335 int catIndex = m_categories.indexOf(catTitle); 0336 if(catIndex < 0 && !catTitle.isEmpty()) { 0337 catIndex = m_categories.size(); 0338 beginInsertRows(QModelIndex(), catIndex, catIndex + 1); 0339 insertSorted(&m_categories, QString(catTitle)); 0340 endInsertRows(); 0341 } 0342 0343 const int n = m_scripts.size(); 0344 beginInsertRows(catTitle.isEmpty() ? QModelIndex() : createIndex(catIndex, 0, quintptr(0ULL)), n - 1, n + 1); 0345 endInsertRows(); 0346 return script; 0347 } 0348 0349 private: 0350 QVector<SCScript> m_scripts; 0351 QVector<QString> m_categories; 0352 }; 0353 0354 class Debug : public QObject 0355 { 0356 Q_OBJECT 0357 0358 public: 0359 Debug() {} 0360 ~Debug() {} 0361 0362 public slots: 0363 void information(const QString &message) 0364 { 0365 KMessageBox::information(app()->mainWindow(), message, i18n("Information")); 0366 qDebug() << message; 0367 } 0368 0369 void warning(const QString &message) 0370 { 0371 KMessageBox::error(app()->mainWindow(), message, i18n("Warning")); 0372 qWarning() << message; 0373 } 0374 0375 void error(const QString &message) 0376 { 0377 KMessageBox::error(app()->mainWindow(), message, i18n("Error")); 0378 qWarning() << message; 0379 } 0380 }; 0381 } 0382 0383 using namespace SubtitleComposer; 0384 0385 ScriptsManager::ScriptsManager(QObject *parent) 0386 : QObject(parent) 0387 { 0388 m_dialog = new QDialog(app()->mainWindow()); 0389 setupUi(m_dialog); 0390 0391 scriptsView->header()->setSectionResizeMode(QHeaderView::ResizeToContents); 0392 scriptsView->installEventFilter(this); 0393 if(QItemSelectionModel *m = scriptsView->selectionModel()) 0394 m->deleteLater(); 0395 scriptsView->setModel(new InstalledScriptsModel(scriptsView)); 0396 scriptsView->setSortingEnabled(false); 0397 scriptsView->expandAll(); 0398 connect(scriptsView, &QTreeView::doubleClicked, this, [&](){ editScript(); }); 0399 connect(scriptsView->selectionModel(), &QItemSelectionModel::currentRowChanged, this, [&](){ 0400 const SCScript *s = currentScript(); 0401 const bool isScriptSelected = s && s->isScript(); 0402 const bool isFileWritable = s && QFileInfo(s->path()).isWritable(); 0403 btnRemove->setEnabled(isFileWritable); 0404 if(isFileWritable) { 0405 btnEdit->setText(i18n("Edit")); 0406 btnEdit->setIcon(QIcon::fromTheme($("document-edit"))); 0407 } else { 0408 btnEdit->setText(i18n("View")); 0409 btnEdit->setIcon(QIcon::fromTheme($("document-open"))); 0410 } 0411 btnEdit->setEnabled(isScriptSelected); 0412 btnRun->setEnabled(isScriptSelected); 0413 }); 0414 0415 connect(btnCreate, &QPushButton::clicked, this, [&](){ createScript(); }); 0416 connect(btnAdd, &QPushButton::clicked, this, [&](){ addScript(); }); 0417 connect(btnRemove, &QPushButton::clicked, this, [&](){ removeScript(); }); 0418 connect(btnEdit, &QPushButton::clicked, this, [&](){ editScript(); }); 0419 connect(btnRun, &QPushButton::clicked, this, [&](){ runScript(); }); 0420 connect(btnRefresh, &QAbstractButton::clicked, this, [&](){ reloadScripts(); }); 0421 } 0422 0423 ScriptsManager::~ScriptsManager() 0424 { 0425 0426 } 0427 0428 void 0429 ScriptsManager::setSubtitle(Subtitle *subtitle) 0430 { 0431 btnRun->setEnabled(subtitle != 0); 0432 } 0433 0434 void 0435 ScriptsManager::showDialog() 0436 { 0437 m_dialog->show(); 0438 } 0439 0440 const SCScript * 0441 ScriptsManager::findScript(const QString filename) const 0442 { 0443 const SCScript *s = filename.isEmpty() ? nullptr : static_cast<InstalledScriptsModel *>(scriptsView->model())->findByFilename(filename); 0444 return s ? s : currentScript(); 0445 } 0446 0447 const SCScript * 0448 ScriptsManager::currentScript() const 0449 { 0450 const QModelIndex ci = scriptsView->currentIndex(); 0451 if(ci.isValid() && ci.internalId() > 0) 0452 return static_cast<InstalledScriptsModel *>(scriptsView->model())->at(ci.internalId() - 1); 0453 return nullptr; 0454 } 0455 0456 #if KWIDGETSADDONS_VERSION < QT_VERSION_CHECK(5, 100, 0) 0457 #define questionTwoActions questionYesNo 0458 #define PrimaryAction Yes 0459 #endif 0460 0461 void 0462 ScriptsManager::createScript(const QString &sN) 0463 { 0464 QString scriptName = sN; 0465 0466 InstalledScriptsModel *model = static_cast<InstalledScriptsModel *>(scriptsView->model()); 0467 0468 const SCScript *script = nullptr; 0469 while(scriptName.isEmpty() || (script = model->findByTitle(scriptName))) { 0470 if(script) { 0471 QFileInfo fp(script->path()); 0472 if(!fp.canonicalFilePath().startsWith(userScriptDir().canonicalPath())) { 0473 // a system script can be overridden by user 0474 scriptName = script->name(); 0475 break; 0476 } 0477 if(KMessageBox::questionTwoActions(app()->mainWindow(), 0478 i18n("A script already exists with this name.\nWould you like to enter another name?"), 0479 i18n("Duplicate filename"), 0480 KGuiItem(i18n("Yes")), KStandardGuiItem::cancel()) != KMessageBox::PrimaryAction) 0481 return; 0482 } 0483 0484 TextInputDialog nameDlg(i18n("Create New Script"), i18n("Script name:")); 0485 if(nameDlg.exec() != QDialog::Accepted) 0486 return; 0487 scriptName = nameDlg.value(); 0488 } 0489 0490 if(!scriptName.endsWith($(".js"))) 0491 scriptName.append($(".js")); 0492 0493 QFile scriptFile(userScriptDir().absoluteFilePath(scriptName)); 0494 if(!scriptFile.open(QIODevice::WriteOnly | QIODevice::Text)) { 0495 KMessageBox::error(app()->mainWindow(), i18n("There was an error creating the file <b>%1</b>.", userScriptDir().absoluteFilePath(scriptName))); 0496 return; 0497 } 0498 0499 QTextStream outputStream(&scriptFile); 0500 outputStream << "/*\n" 0501 "\t@name " << scriptName.chopped(3) << " Title\n" 0502 "\t@version 1.0\n" 0503 "\t@summary " << scriptName << " summary/short desription.\n" 0504 "\t@author Author's Name\n" 0505 "*/\n"; 0506 0507 scriptFile.close(); 0508 0509 reloadScripts(); 0510 editScript(scriptName); 0511 } 0512 0513 const QStringList & 0514 ScriptsManager::mimeTypes() 0515 { 0516 static QStringList mimeTypes; 0517 0518 if(mimeTypes.isEmpty()) 0519 mimeTypes.append("application/javascript"); 0520 0521 return mimeTypes; 0522 } 0523 0524 void 0525 ScriptsManager::addScript(const QUrl &sSU) 0526 { 0527 QUrl srcScriptUrl = sSU; 0528 0529 if(srcScriptUrl.isEmpty()) { 0530 QFileDialog fileDialog(m_dialog, i18n("Select Existing Script"), QString(), QString()); 0531 fileDialog.setAcceptMode(QFileDialog::AcceptOpen); 0532 fileDialog.setFileMode(QFileDialog::ExistingFile); 0533 fileDialog.setMimeTypeFilters(mimeTypes()); 0534 fileDialog.setModal(true); 0535 0536 if(fileDialog.exec() != QDialog::Accepted) 0537 return; 0538 0539 srcScriptUrl = fileDialog.selectedUrls().constFirst(); 0540 } 0541 0542 QString scriptName = QFileInfo(srcScriptUrl.fileName()).fileName(); 0543 0544 InstalledScriptsModel *model = static_cast<InstalledScriptsModel *>(scriptsView->model()); 0545 while(model->findByFilename(scriptName)) { 0546 if(KMessageBox::questionTwoActions(app()->mainWindow(), 0547 i18n("You must enter an unused name to continue.\nWould you like to enter another name?"), 0548 i18n("Name Already Used"), 0549 KStandardGuiItem::cont(), KStandardGuiItem::cancel()) != KMessageBox::PrimaryAction) 0550 return; 0551 0552 TextInputDialog nameDlg(i18n("Rename Script"), i18n("Script name:")); 0553 if(nameDlg.exec() != QDialog::Accepted) 0554 return; 0555 scriptName = nameDlg.value(); 0556 } 0557 0558 FileLoadHelper fileLoadHelper(srcScriptUrl); 0559 0560 if(!fileLoadHelper.open()) { 0561 KMessageBox::error(app()->mainWindow(), i18n("There was an error opening the file <b>%1</b>.", srcScriptUrl.toString(QUrl::PreferLocalFile))); 0562 return; 0563 } 0564 0565 QFile dest(userScriptDir().absoluteFilePath(scriptName)); 0566 if(!dest.open(QIODevice::WriteOnly | QIODevice::Truncate) 0567 || dest.write(fileLoadHelper.file()->readAll()) == -1 0568 || !dest.flush()) { 0569 KMessageBox::error(app()->mainWindow(), i18n("There was an error copying the file to <b>%1</b>.", dest.fileName())); 0570 return; 0571 } 0572 0573 reloadScripts(); 0574 } 0575 0576 void 0577 ScriptsManager::removeScript(const QString &sN) 0578 { 0579 const SCScript *script = findScript(sN); 0580 if(!script) 0581 return; 0582 0583 if(KMessageBox::warningContinueCancel(app()->mainWindow(), 0584 i18n("Do you really want to send file <b>%1</b> to the trash?", script->path()), i18n("Move to Trash")) != KMessageBox::Continue) 0585 return; 0586 0587 if(!FileTrasher(script->path()).exec()) { 0588 KMessageBox::error(app()->mainWindow(), i18n("There was an error removing the file <b>%1</b>.", script->path())); 0589 return; 0590 } 0591 0592 reloadScripts(); 0593 } 0594 0595 void 0596 ScriptsManager::editScript(const QString &sN) 0597 { 0598 const SCScript *script = findScript(sN); 0599 if(!script) { 0600 qWarning() << "unknown script specified"; 0601 return; 0602 } 0603 0604 const QUrl scriptUrl = QUrl::fromLocalFile(script->path()); 0605 #ifdef SC_APPIMAGE 0606 { 0607 #elif KIO_VERSION >= QT_VERSION_CHECK(5, 98, 0) 0608 KIO::OpenUrlJob *job = new KIO::OpenUrlJob(scriptUrl); 0609 job->setUiDelegate(KIO::createDefaultJobUiDelegate(KJobUiDelegate::AutoHandlingEnabled, app()->mainWindow())); 0610 if(!job->exec()) { 0611 #elif KIO_VERSION >= QT_VERSION_CHECK(5, 71, 0) 0612 KIO::OpenUrlJob *job = new KIO::OpenUrlJob(scriptUrl); 0613 job->setUiDelegate(new KIO::JobUiDelegate(KJobUiDelegate::AutoHandlingEnabled, app()->mainWindow())); 0614 if(!job->exec()) { 0615 #elif KIO_VERSION >= QT_VERSION_CHECK(5, 31, 0) 0616 if(!KRun::runUrl(scriptUrl, "text/plain", app()->mainWindow(), KRun::RunFlags())) { 0617 #else 0618 if(!KRun::runUrl(scriptUrl, "text/plain", app()->mainWindow(), false, false)) { 0619 #endif 0620 if(!QDesktopServices::openUrl(scriptUrl)) 0621 KMessageBox::error(app()->mainWindow(), i18n("Could not launch external editor.\n")); 0622 } 0623 } 0624 0625 void 0626 ScriptsManager::runScript(const QString &sN) 0627 { 0628 const SCScript *script = findScript(sN); 0629 if(!script || !script->isScript()) 0630 return; 0631 0632 QJSEngine jse; 0633 jse.installExtensions(QJSEngine::ConsoleExtension); 0634 jse.globalObject().setProperty("ranges", jse.newQObject(new Scripting::RangesModule)); 0635 jse.globalObject().setProperty("strings", jse.newQObject(new Scripting::StringsModule)); 0636 jse.globalObject().setProperty("subtitle", jse.newQObject(new Scripting::SubtitleModule)); 0637 jse.globalObject().setProperty("subtitleline", jse.newQObject(new Scripting::SubtitleLineModule)); 0638 jse.globalObject().setProperty("debug", jse.newQObject(new Debug())); 0639 0640 QString scriptData = script->content(); 0641 if(scriptData.isNull()) { 0642 KMessageBox::error(app()->mainWindow(), i18n("Error opening script %1.", script->path()), i18n("Error Running Script")); 0643 return; 0644 } 0645 0646 QJSValue res; 0647 { 0648 // everything done by the script will be undoable in a single step 0649 SubtitleCompositeActionExecutor executor(appSubtitle(), script->title()); 0650 res = jse.evaluate(scriptData, script->name()); 0651 } 0652 0653 if(!res.isUndefined()) { 0654 if(res.isError()) { 0655 const QString details = i18n("Path: %1", script->path()) % "\n" 0656 % res.property($("stack")).toString(); 0657 KMessageBox::detailedError(app()->mainWindow(), res.toString(), details, i18n("Error Running Script")); 0658 } else { 0659 KMessageBox::error(app()->mainWindow(), res.toString(), i18n("Error Running Script")); 0660 } 0661 } 0662 } 0663 0664 QMenu * 0665 ScriptsManager::toolsMenu() 0666 { 0667 static QMenu *toolsMenu = 0; 0668 0669 if(!toolsMenu) { 0670 toolsMenu = app()->mainWindow()->findChild<QMenu *>("tools"); 0671 if(!toolsMenu) { 0672 toolsMenu = app()->mainWindow()->menuBar()->addMenu(i18n("Tools")); 0673 toolsMenu->setObjectName("tools"); 0674 } 0675 connect(toolsMenu, &QMenu::triggered, this, &ScriptsManager::onToolsMenuActionTriggered); 0676 } 0677 0678 return toolsMenu; 0679 } 0680 0681 void 0682 ScriptsManager::findAllFiles(QString directory, QStringList &fileList) 0683 { 0684 QDir path(directory); 0685 const QFileInfoList files = path.entryInfoList(); 0686 for(const QFileInfo &file: files) { 0687 if(file.isDir()) { 0688 if(file.fileName().at(0) != '.') 0689 findAllFiles(file.absoluteFilePath(), fileList); 0690 } else { 0691 fileList.append(file.absoluteFilePath()); 0692 } 0693 } 0694 } 0695 0696 void 0697 ScriptsManager::reloadScripts() 0698 { 0699 QMenu *toolsMenu = ScriptsManager::toolsMenu(); 0700 KActionCollection *actionCollection = app()->mainWindow()->actionCollection(); 0701 UserActionManager *actionManager = UserActionManager::instance(); 0702 0703 InstalledScriptsModel *model = static_cast<InstalledScriptsModel *>(scriptsView->model()); 0704 0705 toolsMenu->clear(); 0706 toolsMenu->addAction(app()->action(ACT_SCRIPTS_MANAGER)); 0707 toolsMenu->addSeparator(); 0708 0709 QMap<QString, QMenu *> categoryMenus; 0710 0711 model->removeAll(); 0712 0713 // make sure userScriptDir is first on the list so it will override system scripts 0714 const QString userDir = userScriptDir().absolutePath(); 0715 QStringList scriptDirs = QStandardPaths::locateAll(QStandardPaths::AppDataLocation, userScriptDir().dirName(), QStandardPaths::LocateDirectory); 0716 int pos = scriptDirs.indexOf(userDir); 0717 if(pos == -1) 0718 scriptDirs.prepend(userDir); 0719 else if(pos > 0) 0720 scriptDirs.move(pos, 0); 0721 0722 foreach(const QString &path, scriptDirs) { 0723 const int pathLen = QDir(path).absolutePath().length() + 1; 0724 QStringList scriptPaths; 0725 findAllFiles(path, scriptPaths); 0726 foreach(const QString &path, scriptPaths) { 0727 const SCScript *script = model->add(path, pathLen); 0728 if(!script) 0729 continue; 0730 0731 if(script->isScript()) { 0732 QMenu *parentMenu = toolsMenu; 0733 if(!script->category().isEmpty()) { 0734 auto it = categoryMenus.constFind(script->category()); 0735 if(it != categoryMenus.cend()) { 0736 parentMenu = it.value(); 0737 } else { 0738 parentMenu = new QMenu(script->category(), toolsMenu); 0739 categoryMenus[script->category()] = parentMenu; 0740 } 0741 } 0742 0743 QAction *scriptAction = parentMenu->addAction(script->title()); 0744 scriptAction->setObjectName(script->name()); 0745 if(!script->description().isEmpty()) 0746 scriptAction->setStatusTip(script->description()); 0747 actionCollection->addAction(script->name(), scriptAction); 0748 actionManager->addAction(scriptAction, UserAction::SubOpened | UserAction::FullScreenOff); 0749 } 0750 } 0751 } 0752 0753 for(auto it = categoryMenus.cbegin(); it != categoryMenus.cend(); ++it) 0754 toolsMenu->addMenu(it.value()); 0755 } 0756 0757 void 0758 ScriptsManager::onToolsMenuActionTriggered(QAction *triggeredAction) 0759 { 0760 if(triggeredAction == app()->action(ACT_SCRIPTS_MANAGER)) 0761 return; 0762 0763 runScript(triggeredAction->objectName()); 0764 } 0765 0766 bool 0767 ScriptsManager::eventFilter(QObject *object, QEvent *event) 0768 { 0769 if(object == scriptsView && event->type() == QEvent::KeyPress && static_cast<QKeyEvent *>(event)->matches(QKeySequence::Delete)) { 0770 removeScript(); 0771 return true; // eat event 0772 } 0773 0774 return QObject::eventFilter(object, event); 0775 } 0776 0777 #include "scriptsmanager.moc"