1 /***************************************************************************
2 scriptplugin.cpp - description
3 -------------------
4 begin : Fri Nov 9 2007
5 copyright : (C) 2007 by Dominik Seichter
6 email : domseichter@web.de
7 copyright : (C) 2020 by Harald Sitter <sitter@kde.org>
8 ***************************************************************************/
9
10 /***************************************************************************
11 * *
12 * This program is free software; you can redistribute it and/or modify *
13 * it under the terms of the GNU General Public License as published by *
14 * the Free Software Foundation; either version 2 of the License, or *
15 * (at your option) any later version. *
16 * *
17 ***************************************************************************/
18
19 #include "scriptplugin.h"
20
21 #include <kconfiggroup.h>
22 #include <kiconloader.h>
23 #include <kmessagebox.h>
24 #include <KIO/StoredTransferJob>
25 #include <KIO/StatJob>
26 #include <KJobWidgets>
27 #include <QTemporaryFile>
28
29 #include <QFile>
30 #include <QMenu>
31 #include <QTextStream>
32 #include <QVariant>
33 #include <QFileDialog>
34 #include <QDebug>
35
36 #include "ui_scriptplugindialog.h"
37 #include "ui_scriptpluginwidget.h"
38 #include "batchrenamer.h"
39 #include "krenamefile.h"
40
41 const char *ScriptPlugin::s_pszFileDialogLocation = "kfiledialog://krenamejscript";
42 const char *ScriptPlugin::s_pszVarNameIndex = "krename_index";
43 const char *ScriptPlugin::s_pszVarNameUrl = "krename_url";
44 const char *ScriptPlugin::s_pszVarNameFilename = "krename_filename";
45 const char *ScriptPlugin::s_pszVarNameExtension = "krename_extension";
46 const char *ScriptPlugin::s_pszVarNameDirectory = "krename_directory";
47
48 enum EVarType {
49 eVarType_String = 0,
50 eVarType_Int,
51 eVarType_Double,
52 eVarType_Bool
53 };
54
ScriptPlugin(PluginLoader * loader)55 ScriptPlugin::ScriptPlugin(PluginLoader *loader)
56 : QObject(),
57 Plugin(loader), m_parent(nullptr)
58 {
59 m_name = i18n("JavaScript Plugin");
60 m_icon = "applications-development";
61 m_menu = new QMenu();
62 m_widget = new Ui::ScriptPluginWidget();
63
64 this->addSupportedToken("js;.*");
65
66 m_help.append("[js;4+5];;" + i18n("Insert a snippet of JavaScript code (4+5 in this case)"));
67
68 m_menu->addAction(i18n("Index of the current file"), this, &ScriptPlugin::slotInsertIndex);
69 m_menu->addAction(i18n("URL of the current file"), this, &ScriptPlugin::slotInsertUrl);
70 m_menu->addAction(i18n("Filename of the current file"), this, &ScriptPlugin::slotInsertFilename);
71 m_menu->addAction(i18n("Extension of the current file"), this, &ScriptPlugin::slotInsertExtension);
72 m_menu->addAction(i18n("Directory of the current file"), this, &ScriptPlugin::slotInsertDirectory);
73 }
74
~ScriptPlugin()75 ScriptPlugin::~ScriptPlugin()
76 {
77 delete m_widget;
78 delete m_menu;
79 }
80
processFile(BatchRenamer * b,int index,const QString & filenameOrToken,EPluginType)81 QString ScriptPlugin::processFile(BatchRenamer *b, int index,
82 const QString &filenameOrToken, EPluginType)
83 {
84 QString token(filenameOrToken);
85 QString script;
86 QString definitions = m_widget->textCode->toPlainText();
87
88 if (token.contains(";")) {
89 script = token.section(';', 1); // all sections from 1 to the last
90 token = token.section(';', 0, 0).toLower();
91 } else {
92 token = token.toLower();
93 }
94
95 if (token == "js") {
96 // Setup interpreter
97 const KRenameFile &file = b->files()->at(index);
98 initKRenameVars(file, index);
99
100 // Make sure definitions are executed first
101 script = definitions + '\n' + script;
102
103 const QJSValue result = m_engine.evaluate(script);
104 if (result.isError()) {
105 qDebug() << "JavaScript Error:" << result.toString();
106 return QString();
107 }
108
109 return result.toString();
110 }
111
112 return QString();
113 }
114
icon() const115 const QPixmap ScriptPlugin::icon() const
116 {
117 return KIconLoader::global()->loadIcon(m_icon, KIconLoader::NoGroup, KIconLoader::SizeSmall);
118 }
119
createUI(QWidget * parent) const120 void ScriptPlugin::createUI(QWidget *parent) const
121 {
122 QStringList labels;
123 labels << i18n("Variable Name");
124 labels << i18n("Initial Value");
125
126 const_cast<ScriptPlugin *>(this)->m_parent = parent;
127 m_widget->setupUi(parent);
128 m_widget->listVariables->setColumnCount(2);
129 m_widget->listVariables->setHeaderLabels(labels);
130
131 connect(m_widget->listVariables, &QTreeWidget::itemSelectionChanged,
132 this, &ScriptPlugin::slotEnableControls);
133 connect(m_widget->buttonAdd, &QPushButton::clicked,
134 this, &ScriptPlugin::slotAdd);
135 connect(m_widget->buttonRemove, &QPushButton::clicked,
136 this, &ScriptPlugin::slotRemove);
137 connect(m_widget->buttonLoad, &QPushButton::clicked,
138 this, &ScriptPlugin::slotLoad);
139 connect(m_widget->buttonSave, &QPushButton::clicked,
140 this, &ScriptPlugin::slotSave);
141 connect(m_widget->textCode, &QTextEdit::textChanged,
142 this, &ScriptPlugin::slotEnableControls);
143
144 const_cast<ScriptPlugin *>(this)->slotEnableControls();
145
146 QPixmap openIcon = KIconLoader::global()->loadIcon("document-open", KIconLoader::NoGroup, KIconLoader::SizeSmall);
147 QPixmap saveIcon = KIconLoader::global()->loadIcon("document-save-as", KIconLoader::NoGroup, KIconLoader::SizeSmall);
148 QPixmap removeIcon = KIconLoader::global()->loadIcon("list-remove", KIconLoader::NoGroup, KIconLoader::SizeSmall);
149 QPixmap addIcon = KIconLoader::global()->loadIcon("list-add", KIconLoader::NoGroup, KIconLoader::SizeSmall);
150
151 m_widget->buttonLoad->setIcon(openIcon);
152 m_widget->buttonSave->setIcon(saveIcon);
153 m_widget->buttonAdd->setIcon(addIcon);
154 m_widget->buttonRemove->setIcon(removeIcon);
155
156 m_widget->buttonInsert->setMenu(m_menu);
157 }
158
initKRenameVars(const KRenameFile & file,int index)159 void ScriptPlugin::initKRenameVars(const KRenameFile &file, int index)
160 {
161 // KRename definitions
162 m_engine.globalObject().setProperty(ScriptPlugin::s_pszVarNameIndex, index);
163 m_engine.globalObject().setProperty(ScriptPlugin::s_pszVarNameUrl, file.srcUrl().url());
164 m_engine.globalObject().setProperty(ScriptPlugin::s_pszVarNameFilename, file.srcFilename());
165 m_engine.globalObject().setProperty(ScriptPlugin::s_pszVarNameExtension, file.srcExtension());
166 m_engine.globalObject().setProperty(ScriptPlugin::s_pszVarNameDirectory, file.srcDirectory());
167
168 // User definitions, set them only on first file
169 if (index != 0) {
170 return;
171 }
172
173 for (int i = 0; i < m_widget->listVariables->topLevelItemCount(); i++) {
174 // TODO, we have to know the type of the variable!
175 QTreeWidgetItem *item = m_widget->listVariables->topLevelItem(i);
176 if (!item) {
177 continue;
178 }
179
180 EVarType eVarType = static_cast<EVarType>(item->data(1, Qt::UserRole).toInt());
181 const QString &name = item->text(0);
182 const QString &value = item->text(1);
183 switch (eVarType) {
184 default:
185 case eVarType_String:
186 m_engine.globalObject().setProperty(name, value);
187 break;
188 case eVarType_Int:
189 m_engine.globalObject().setProperty(name, value.toInt());
190 break;
191 case eVarType_Double:
192 m_engine.globalObject().setProperty(name, value.toDouble());
193 break;
194 case eVarType_Bool:
195 m_engine.globalObject().setProperty(name, (value.toLower() == "true" ? true : false));
196 break;
197 }
198 }
199 }
200
insertVariable(const char * name)201 void ScriptPlugin::insertVariable(const char *name)
202 {
203 m_widget->textCode->insertPlainText(QString(name));
204 }
205
slotEnableControls()206 void ScriptPlugin::slotEnableControls()
207 {
208 bool bEnable = (m_widget->listVariables->selectedItems().count() != 0);
209 m_widget->buttonRemove->setEnabled(bEnable);
210
211 bEnable = !m_widget->textCode->toPlainText().isEmpty();
212 m_widget->buttonSave->setEnabled(bEnable);
213 }
214
slotAdd()215 void ScriptPlugin::slotAdd()
216 {
217 QDialog dialog;
218 Ui::ScriptPluginDialog dlg;
219
220 dlg.setupUi(&dialog);
221 dlg.comboType->addItem(i18n("String"), eVarType_String);
222 dlg.comboType->addItem(i18n("Int"), eVarType_Int);
223 dlg.comboType->addItem(i18n("Double"), eVarType_Double);
224 dlg.comboType->addItem(i18n("Boolean"), eVarType_Bool);
225
226 if (dialog.exec() != QDialog::Accepted) {
227 return;
228 }
229
230 QString name = dlg.lineName->text();
231 QString value = dlg.lineValue->text();
232
233 // Build a Java script statement
234 QString script = name + " = " + value + ';';
235
236 const QJSValue result = m_engine.evaluate(script);
237 if (result.isError()) {
238 KMessageBox::error(m_parent, i18n("A JavaScript error has occurred: ") + result.toString(), this->name());
239 } else {
240 QTreeWidgetItem *item = new QTreeWidgetItem();
241 item->setText(0, name);
242 item->setText(1, value);
243 item->setData(1, Qt::UserRole, dlg.comboType->currentData());
244
245 m_widget->listVariables->addTopLevelItem(item);
246 }
247 }
248
slotRemove()249 void ScriptPlugin::slotRemove()
250 {
251 QTreeWidgetItem *item = m_widget->listVariables->currentItem();
252 if (item) {
253 m_widget->listVariables->invisibleRootItem()->removeChild(item);
254 delete item;
255 }
256 }
257
slotLoad()258 void ScriptPlugin::slotLoad()
259 {
260 if (!m_widget->textCode->toPlainText().isEmpty() &&
261 KMessageBox::questionYesNo(m_parent,
262 i18n("All currently entered definitions will be lost. Do you want to continue?"))
263 == KMessageBox::No) {
264 return;
265 }
266
267 QUrl url = QFileDialog::getOpenFileUrl(m_parent, i18n("Select file"),
268 QUrl(ScriptPlugin::s_pszFileDialogLocation));
269
270 if (!url.isEmpty()) {
271 // Also support remote files
272 KIO::StoredTransferJob *job = KIO::storedGet(url);
273 KJobWidgets::setWindow(job, m_parent);
274 if (job->exec()) {
275 m_widget->textCode->setPlainText(QString::fromLocal8Bit(job->data()));
276 } else {
277 KMessageBox::error(m_parent, job->errorString());
278 }
279 }
280
281 slotEnableControls();
282 }
283
slotSave()284 void ScriptPlugin::slotSave()
285 {
286 QUrl url = QFileDialog::getSaveFileUrl(m_parent, i18n("Select file"),
287 QUrl(ScriptPlugin::s_pszFileDialogLocation));
288
289 if (!url.isEmpty()) {
290 KIO::StatJob *statJob = KIO::stat(url, KIO::StatJob::DestinationSide, 0);
291 statJob->exec();
292 if (statJob->error() != KIO::ERR_DOES_NOT_EXIST) {
293 int m = KMessageBox::warningYesNo(m_parent, i18n("The file %1 already exists. "
294 "Do you want to overwrite it?", url.toDisplayString(QUrl::PreferLocalFile)));
295
296 if (m == KMessageBox::No) {
297 return;
298 }
299 }
300
301 if (url.isLocalFile()) {
302 QFile file(url.path());
303 if (file.open(QIODevice::WriteOnly | QIODevice::Text)) {
304 QTextStream out(&file);
305 out << m_widget->textCode->toPlainText();
306 out.flush();
307 file.close();
308 } else {
309 KMessageBox::error(m_parent, i18n("Unable to open %1 for writing.", url.path()));
310 }
311 } else {
312 KIO::StoredTransferJob *job = KIO::storedPut(m_widget->textCode->toPlainText().toLocal8Bit(), url, -1);
313 KJobWidgets::setWindow(job, m_parent);
314 job->exec();
315 if (job->error()) {
316 KMessageBox::error(m_parent, job->errorString());
317 }
318 }
319 }
320
321 slotEnableControls();
322 }
323
slotTest()324 void ScriptPlugin::slotTest()
325 {
326 }
327
slotInsertIndex()328 void ScriptPlugin::slotInsertIndex()
329 {
330 this->insertVariable(ScriptPlugin::s_pszVarNameIndex);
331 }
332
slotInsertUrl()333 void ScriptPlugin::slotInsertUrl()
334 {
335 this->insertVariable(ScriptPlugin::s_pszVarNameUrl);
336 }
337
slotInsertFilename()338 void ScriptPlugin::slotInsertFilename()
339 {
340 this->insertVariable(ScriptPlugin::s_pszVarNameFilename);
341 }
342
slotInsertExtension()343 void ScriptPlugin::slotInsertExtension()
344 {
345 this->insertVariable(ScriptPlugin::s_pszVarNameExtension);
346 }
347
slotInsertDirectory()348 void ScriptPlugin::slotInsertDirectory()
349 {
350 this->insertVariable(ScriptPlugin::s_pszVarNameDirectory);
351 }
352
loadConfig(KConfigGroup & group)353 void ScriptPlugin::loadConfig(KConfigGroup &group)
354 {
355 QStringList variableNames;
356 QStringList variableValues;
357 QVariantList variableTypes;
358
359 variableNames = group.readEntry("JavaScriptVariableNames", variableNames);
360 variableValues = group.readEntry("JavaScriptVariableValues", variableValues);
361 variableTypes = group.readEntry("JavaScriptVariableTypes", variableTypes);
362
363 int min = qMin(variableNames.count(), variableValues.count());
364 min = qMin(min, variableTypes.count());
365
366 for (int i = 0; i < min; i++) {
367 QTreeWidgetItem *item = new QTreeWidgetItem();
368 item->setText(0, variableNames[i]);
369 item->setText(1, variableValues[i]);
370 item->setData(1, Qt::UserRole, variableTypes[i]);
371
372 m_widget->listVariables->addTopLevelItem(item);
373 }
374
375 m_widget->textCode->setPlainText(group.readEntry("JavaScriptDefinitions", QString()));
376 }
377
saveConfig(KConfigGroup & group) const378 void ScriptPlugin::saveConfig(KConfigGroup &group) const
379 {
380 QStringList variableNames;
381 QStringList variableValues;
382 QVariantList variableTypes;
383
384 for (int i = 0; i < m_widget->listVariables->topLevelItemCount(); i++) {
385 QTreeWidgetItem *item = m_widget->listVariables->topLevelItem(i);
386 if (item) {
387 variableNames << item->text(0);
388 variableValues << item->text(1);
389 variableTypes << item->data(1, Qt::UserRole);
390 }
391 }
392
393 group.writeEntry("JavaScriptVariableNames", variableNames);
394 group.writeEntry("JavaScriptVariableValues", variableValues);
395 group.writeEntry("JavaScriptVariableTypes", variableTypes);
396 group.writeEntry("JavaScriptDefinitions", m_widget->textCode->toPlainText());
397 }
398
399