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"