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