File indexing completed on 2024-04-14 14:27:12

0001 /* This file is part of the KDE project
0002    Copyright (C) 2008 Paulo Moura Guedes <moura@kdewebdev.org>
0003 
0004    This library is free software; you can redistribute it and/or
0005    modify it under the terms of the GNU Library General Public
0006    License as published by the Free Software Foundation; either
0007    version 2 of the License, or (at your option) any later version.
0008 
0009    This library is distributed in the hope that it will be useful,
0010    but WITHOUT ANY WARRANTY; without even the implied warranty of
0011    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
0012    Library General Public License for more details.
0013 
0014    You should have received a copy of the GNU Library General Public License
0015    along with this library; see the file COPYING.LIB.  If not, write to
0016    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
0017    Boston, MA 02110-1301, USA.
0018 */
0019 
0020 #include "scriptingplugin.h"
0021 
0022 #include <krun.h>
0023 #include <kxmlguifactory.h>
0024 #include <kactioncollection.h>
0025 #include <kross/core/manager.h>
0026 #include <kross/core/actioncollection.h>
0027 
0028 #include <QAction>
0029 #include <QPointer>
0030 #include <QTextStream>
0031 #include <QDirIterator>
0032 #include <QUrl>
0033 #include <QStandardPaths>
0034 
0035 using namespace Kross;
0036 
0037 struct Object {
0038     QPointer<QObject> object;
0039     ChildrenInterface::Options options;
0040     Object(QObject *obj, ChildrenInterface::Options opt): object(obj), options(opt) {}
0041 };
0042 
0043 /// \internal d-pointer class
0044 class ScriptingPlugin::ScriptingPluginPrivate
0045 {
0046 public:
0047     QString collectionName;
0048     QString userActionsFile;
0049     QString referenceActionsDir;
0050     QHash<QString, Object> objects;
0051 
0052     QDomElement menuFromName(QString const &name, const QDomDocument &document)
0053     {
0054         QDomElement menuBar = document.documentElement().firstChildElement("MenuBar");
0055         QDomElement menu = menuBar.firstChildElement("Menu");
0056         for (; !menu.isNull(); menu = menu.nextSiblingElement("Menu")) {
0057             if (menu.attribute("name") == name) {
0058                 return menu;
0059             }
0060         }
0061         return QDomElement();
0062     }
0063 };
0064 
0065 ScriptingPlugin::ScriptingPlugin(QObject *parent)
0066     : KParts::Plugin(parent)
0067     , d(new ScriptingPluginPrivate())
0068 {
0069     d->userActionsFile = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + QLatin1Char('/') + "scripts/scriptactions.rc";
0070     d->collectionName = "scripting-plugin";
0071 }
0072 
0073 ScriptingPlugin::ScriptingPlugin(const QString &collectionName, const QString &userActionsFile, const QString &referenceActionsDir, QObject *parent)
0074     : KParts::Plugin(parent)
0075     , d(new ScriptingPluginPrivate())
0076 {
0077     d->collectionName = collectionName;
0078     d->userActionsFile = userActionsFile;
0079     d->referenceActionsDir = referenceActionsDir;
0080 }
0081 
0082 ScriptingPlugin::~ScriptingPlugin()
0083 {
0084     if (QFile::exists(d->userActionsFile)) {
0085         save();
0086     }
0087 
0088     Kross::ActionCollection *collection = Kross::Manager::self().actionCollection()->collection(d->collectionName);
0089     if (collection) {
0090         collection->setParentCollection(nullptr);
0091         collection->deleteLater();
0092     }
0093 
0094     delete d;
0095 }
0096 
0097 void ScriptingPlugin::setDOMDocument(const QDomDocument &document, bool merge)
0098 {
0099     QDomDocument doc = buildDomDocument(document);
0100     KXMLGUIClient::setDOMDocument(doc, merge);
0101 }
0102 
0103 void ScriptingPlugin::addObject(QObject *object, const QString &name)
0104 {
0105     QString n = name.isNull() ? object->objectName() : name;
0106     d->objects.insert(n, Object(object, ChildrenInterface::NoOption));
0107 }
0108 
0109 void ScriptingPlugin::addObject(QObject *object, const QString &name, ChildrenInterface::Options options)
0110 {
0111     QString n = name.isNull() ? object->objectName() : name;
0112     d->objects.insert(n, Object(object, options));
0113 }
0114 
0115 QDomDocument ScriptingPlugin::buildDomDocument(const QDomDocument &document)
0116 {
0117     Kross::ActionCollection *collection = Kross::Manager::self().actionCollection()->collection(d->collectionName);
0118     if (!collection) {
0119         collection = new Kross::ActionCollection(d->collectionName, Kross::Manager::self().actionCollection());
0120     }
0121 
0122     QStringList allActionFiles;
0123     const QStringList scriptDirs = QStandardPaths::locateAll(QStandardPaths::AppDataLocation, QStringLiteral("scripts/") + d->referenceActionsDir, QStandardPaths::LocateDirectory);
0124     Q_FOREACH (const QString &scriptDir, scriptDirs) {
0125         QDirIterator it(scriptDir, QStringList() << QStringLiteral("*.rc"));
0126         while (it.hasNext()) {
0127             allActionFiles.append(it.next());
0128         }
0129     }
0130 
0131     //move userActionsFile to the end so that it updates existing actions and adds new ones.
0132     int pos = allActionFiles.indexOf(d->userActionsFile);
0133     if (pos != -1) {
0134         allActionFiles.append(allActionFiles.takeAt(pos));
0135     } else if (QFile::exists(d->userActionsFile)) { //in case d->userActionsFile isn't in the standard local dir
0136         allActionFiles.append(d->userActionsFile);
0137     }
0138 
0139     QStringList searchPath = QStandardPaths::locateAll(QStandardPaths::AppDataLocation, QStringLiteral("scripts/") + d->referenceActionsDir, QStandardPaths::LocateDirectory);
0140     foreach (const QString &file, allActionFiles) {
0141         QFile f(file);
0142         if (!f.open(QIODevice::ReadOnly)) {
0143             continue;
0144         }
0145 
0146         collection->readXml(&f, searchPath + QStringList(QFileInfo(f).absolutePath()));
0147         f.close();
0148 
0149     }
0150 
0151     QDomDocument doc(document);
0152     buildDomDocument(doc, collection);
0153 
0154     return doc;
0155 }
0156 
0157 void ScriptingPlugin::buildDomDocument(QDomDocument &document,
0158                                        Kross::ActionCollection *collection)
0159 {
0160     QDomElement menuElement = d->menuFromName(collection->name(), document);
0161 
0162     foreach (Kross::Action *action, collection->actions()) {
0163         QHashIterator<QString, Object> i(d->objects);
0164         while (i.hasNext()) {
0165             i.next();
0166             action->addObject(i.value().object, i.key(), i.value().options);
0167         }
0168 
0169         // Create and append new Menu element if doesn't exist
0170         if (menuElement.isNull()) {
0171             menuElement = document.createElement("Menu");
0172             menuElement.setAttribute("name", collection->name());
0173             menuElement.setAttribute("noMerge", "0");
0174 
0175             QDomElement textElement = document.createElement("text");
0176             textElement.appendChild(document.createTextNode(collection->text()));
0177             menuElement.appendChild(textElement);
0178 
0179             Kross::ActionCollection *parentCollection = collection->parentCollection();
0180             QDomElement root;
0181             if (parentCollection) {
0182                 QDomElement parentMenuElement = d->menuFromName(parentCollection->name(), document);
0183                 if (!parentMenuElement.isNull()) {
0184                     root = parentMenuElement;
0185                 }
0186             }
0187             if (root.isNull()) {
0188                 root = document.documentElement().firstChildElement("MenuBar");
0189             }
0190             root.appendChild(menuElement);
0191         }
0192 
0193         // Create and append new Action element
0194         QDomElement newActionElement = document.createElement("Action");
0195         newActionElement.setAttribute("name", action->name());
0196 
0197         menuElement.appendChild(newActionElement);
0198 
0199         QAction *adaptor = new QAction(action->text(), action);
0200         connect(adaptor, SIGNAL(triggered()), action, SLOT(trigger()));
0201         adaptor->setEnabled(action->isEnabled());
0202         adaptor->setIcon(action->icon());
0203         actionCollection()->addAction(action->name(), adaptor);
0204     }
0205 
0206     foreach (const QString &collectionname, collection->collections()) {
0207         Kross::ActionCollection *c = collection->collection(collectionname);
0208         if (c->isEnabled()) {
0209             buildDomDocument(document, c);
0210         }
0211     }
0212 }
0213 
0214 void ScriptingPlugin::save()
0215 {
0216     QFile f(d->userActionsFile);
0217     if (!f.open(QIODevice::WriteOnly)) {
0218         return;
0219     }
0220 
0221     Kross::ActionCollection *collection = Kross::Manager::self().actionCollection()->collection(d->collectionName);
0222     bool collectionEmpty = !collection || (collection->actions().empty() && collection->collections().empty());
0223 
0224     if (!collectionEmpty) {
0225         QStringList searchPath = QStandardPaths::locateAll(QStandardPaths::AppDataLocation, QStringLiteral("scripts/") + d->referenceActionsDir, QStandardPaths::LocateDirectory);
0226         searchPath.append(QFileInfo(d->userActionsFile).absolutePath());
0227         if (collection->writeXml(&f, 2, searchPath)) {
0228             //qDebug() << "Successfully saved file: " << d->userActionsFile;
0229         }
0230     } else {
0231         QTextStream out(&f);
0232         QString xml =
0233             "<!-- "
0234             "\n"
0235             "Collection name attribute represents the name of the menu, e.g., to use menu \"File\" use \"file\" or \"Help\" use \"help\". You can add new menus."
0236             "\n\n\n"
0237             "If you type a relative script file beware that this script is located in $XDG_DATA_HOME/applicationname/"
0238             "\n\n"
0239             "The following example adds an action with the text \"Export...\" into the \"File\" menu"
0240             "\n\n"
0241             "<KrossScripting>"
0242             "\n"
0243             "<collection name=\"file\" text=\"File\" comment=\"File menu\">"
0244             "\n"
0245             "<script name=\"export\" text=\"Export...\" comment=\"Export content\" file=\"export.py\" />"
0246             "\n"
0247             "</collection>"
0248             "\n"
0249             "</KrossScripting>"
0250             "\n"
0251             "-->";
0252 
0253         out << xml;
0254     }
0255     f.close();
0256 }
0257 
0258 void ScriptingPlugin::slotEditScriptActions()
0259 {
0260     if (!QFile::exists(d->userActionsFile)) {
0261         QString dir = QFileInfo(d->userActionsFile).absolutePath();
0262         QDir().mkpath(dir);
0263 
0264         save();
0265     }
0266 
0267     //TODO very funny! this should use ui/view.h instead --Nick
0268     KRun::runUrl(QUrl::fromLocalFile(d->userActionsFile), QString("text/plain"), nullptr, false);
0269 }
0270 
0271 void ScriptingPlugin::slotResetScriptActions()
0272 {
0273     QFile::remove(d->userActionsFile);
0274 }
0275 
0276 #include "moc_scriptingplugin.cpp"