File indexing completed on 2024-04-21 15:02:57

0001 /***************************************************************************
0002  * action.cpp
0003  * This file is part of the KDE project
0004  * copyright (C)2004-2006 by Sebastian Sauer (mail@dipe.org)
0005  *
0006  * This program is free software; you can redistribute it and/or
0007  * modify it under the terms of the GNU Library General Public
0008  * License as published by the Free Software Foundation; either
0009  * version 2 of the License, or (at your option) any later version.
0010  * This program is distributed in the hope that it will be useful,
0011  * but WITHOUT ANY WARRANTY; without even the implied warranty of
0012  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
0013  * Library General Public License for more details.
0014  * You should have received a copy of the GNU Library General Public License
0015  * along with this program; see the file COPYING.  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 "action.h"
0021 #include "actioncollection.h"
0022 #include "interpreter.h"
0023 #include "script.h"
0024 #include "manager.h"
0025 #include "wrapperinterface.h"
0026 #include "kross_debug.h"
0027 
0028 #include <QFile>
0029 #include <QFileInfo>
0030 
0031 #include <klocalizedstring.h>
0032 #include <qmimedatabase.h>
0033 
0034 using namespace Kross;
0035 
0036 namespace Kross
0037 {
0038 
0039 /// \internal d-pointer class.
0040 class Action::Private
0041 {
0042 public:
0043 
0044     /**
0045     * The \a Script instance the \a Action uses if initialized. It will
0046     * be NULL as long as we didn't initialized it what will be done on
0047     * demand.
0048     */
0049     Script *script;
0050 
0051     /**
0052     * The version number this \a Action has. Those version number got
0053     * used internally to deal with different releases of scripts.
0054     */
0055     int version;
0056 
0057     /**
0058     * The optional description to provide some more details about the
0059     * Action to the user.
0060     */
0061     QString description;
0062 
0063     /**
0064     * The name of the icon.
0065     */
0066     QString iconname;
0067 
0068     /**
0069     * The scripting code.
0070     */
0071     QByteArray code;
0072 
0073     /**
0074     * The name of the interpreter. This could be something
0075     * like for example "python" for the python binding.
0076     */
0077     QString interpretername;
0078 
0079     /**
0080     * The name of the scriptfile that should be executed. Those
0081     * scriptfile will be read and the content will be used to
0082     * set the scripting code and, if not defined, the used
0083     * interpreter.
0084     */
0085     QString scriptfile;
0086 
0087     /**
0088     * The path list where the \a Script may be located.
0089     * \todo after BIC break: don't keep it all the time,
0090     * as it is now passed to [to|from]DomElement
0091     */
0092     QStringList searchpath;
0093 
0094     /**
0095     * Map of options that overwritte the \a InterpreterInfo::Option::Map
0096     * standard options.
0097     */
0098     QMap< QString, QVariant > options;
0099 
0100     Private() : script(nullptr), version(0) {}
0101 };
0102 
0103 }
0104 
0105 enum InitOptions {Enable = 1};
0106 void static init(Action *th, const QString &name, int options = 0)
0107 {
0108     th->setEnabled(options & Enable);
0109     th->setObjectName(name);
0110 #ifdef KROSS_ACTION_DEBUG
0111     qCDebug(KROSS_LOG) << "Action::Action(QObject*,QString,QDir) Ctor name=" << th->objectName();
0112 #endif
0113     QObject::connect(th, SIGNAL(triggered(bool)), th, SLOT(slotTriggered()));
0114 }
0115 
0116 Action::Action(QObject *parent, const QString &name, const QDir &packagepath)
0117     : QAction(parent)
0118     , QScriptable()
0119     , ChildrenInterface()
0120     , ErrorInterface()
0121     , d(new Private())
0122 {
0123     init(this, name);
0124     d->searchpath = QStringList(packagepath.absolutePath());
0125 }
0126 
0127 Action::Action(QObject *parent, const QUrl &url)
0128     : QAction(parent)
0129     , ChildrenInterface()
0130     , ErrorInterface()
0131     , d(new Private())
0132 {
0133     init(this, url.path(), Enable);
0134     QFileInfo fi(url.toLocalFile());
0135     setText(fi.fileName());
0136     QMimeDatabase db;
0137     setIconName(db.mimeTypeForUrl(url).iconName());
0138     setFile(url.toLocalFile());
0139 }
0140 
0141 Action::~Action()
0142 {
0143 #ifdef KROSS_ACTION_DEBUG
0144     qCDebug(KROSS_LOG) << QStringLiteral("Action::~Action() Dtor name='%1'").arg(objectName());
0145 #endif
0146     finalize();
0147     ActionCollection *coll = qobject_cast<ActionCollection *>(parent());
0148     if (coll) {
0149         coll->removeAction(this);
0150     }
0151     delete d;
0152 }
0153 
0154 void Action::fromDomElement(const QDomElement &element)
0155 {
0156     fromDomElement(element, d->searchpath);
0157 }
0158 
0159 void Action::fromDomElement(const QDomElement &element, const QStringList &searchPath)
0160 {
0161     if (element.isNull()) {
0162         return;
0163     }
0164 
0165     QString file = element.attribute("file");
0166     if (! file.isEmpty()) {
0167         if (QFileInfo(file).exists()) {
0168             setFile(file);
0169         } else {
0170             foreach (const QString &packagepath, searchPath) {
0171                 QFileInfo fi(QDir(packagepath), file);
0172                 if (fi.exists()) {
0173                     setFile(fi.absoluteFilePath());
0174                     break;
0175                 }
0176             }
0177         }
0178     }
0179 
0180     d->version = QVariant(element.attribute("version", QString(d->version))).toInt();
0181 
0182     setText(i18nd(KLocalizedString::applicationDomain().constData(), element.attribute("text").toUtf8().constData()));
0183     const QString comment = element.attribute("comment");
0184     if (!comment.isEmpty()) {
0185         setDescription(i18nd(KLocalizedString::applicationDomain().constData(), comment.toUtf8().constData()));
0186     }
0187     setEnabled(true);
0188     setInterpreter(element.attribute("interpreter"));
0189     setEnabled(QVariant(element.attribute("enabled", "true")).toBool() && isEnabled());
0190 
0191     QString icon = element.attribute("icon");
0192     if (icon.isEmpty() && ! d->scriptfile.isNull()) {
0193         QMimeDatabase db;
0194         icon = db.mimeTypeForUrl(QUrl::fromLocalFile(d->scriptfile)).iconName();
0195     }
0196     setIconName(icon);
0197 
0198     const QString code = element.attribute("code");
0199     if (! code.isNull()) {
0200         setCode(code.toUtf8());
0201     }
0202 
0203     for (QDomNode node = element.firstChild(); ! node.isNull(); node = node.nextSibling()) {
0204         QDomElement e = node.toElement();
0205         if (! e.isNull()) {
0206             if (e.tagName() == "property") {
0207                 const QString n = e.attribute("name", QString());
0208                 if (! n.isNull()) {
0209 #ifdef KROSS_ACTION_DEBUG
0210                     qCDebug(KROSS_LOG) << "Action::readDomElement: Setting property name=" <<
0211                         n << " value=" << e.text();
0212 #endif
0213                     setProperty(n.toLatin1().constData(), QVariant(e.text()));
0214                 }
0215             }
0216         }
0217     }
0218 }
0219 
0220 QDomElement Action::toDomElement() const
0221 {
0222     return toDomElement(QStringList());
0223 }
0224 
0225 QDomElement Action::toDomElement(const QStringList &searchPath) const
0226 {
0227     QDomDocument doc;
0228     QDomElement e = doc.createElement("script");
0229     e.setAttribute("name", objectName());
0230     if (d->version > 0) {
0231         e.setAttribute("version", QString(d->version));
0232     }
0233     if (! text().isNull()) {
0234         e.setAttribute("text", text());
0235     }
0236     if (! description().isNull()) {
0237         e.setAttribute("comment", description());
0238     }
0239     if (! iconName().isNull()) {
0240         e.setAttribute("icon", iconName());
0241     }
0242     if (! isEnabled()) {
0243         e.setAttribute("enabled", "false");
0244     }
0245     if (! interpreter().isNull()) {
0246         e.setAttribute("interpreter", interpreter());
0247     }
0248 
0249     QString fileName = file();
0250     if (!searchPath.isEmpty()) {
0251         //fileName=QDir(searchPath.first()).relativeFilePath(fileName); //prefer absname if it is short?
0252         foreach (const QString &packagepath, searchPath) {
0253             QString nfn = QDir(packagepath).relativeFilePath(file());
0254             if (nfn.length() < fileName.length()) {
0255                 fileName = nfn;
0256             }
0257         }
0258     }
0259 
0260     if (! fileName.isNull()) {
0261         e.setAttribute("file", fileName);
0262     }
0263 
0264     QList<QByteArray> props = dynamicPropertyNames();
0265     foreach (const QByteArray &prop, props) {
0266         QDomElement p = doc.createElement("property");
0267         p.setAttribute("name", QString::fromLatin1(prop));
0268         p.appendChild(doc.createTextNode(property(prop.constData()).toString()));
0269         e.appendChild(p);
0270     }
0271     /*
0272     else if( ! code().isNull() ) {
0273         e.setAttribute("code", code());
0274     }
0275     */
0276 
0277     return e;
0278 }
0279 
0280 Kross::Script *Action::script() const
0281 {
0282     return d->script;
0283 }
0284 
0285 QString Action::name() const
0286 {
0287     return objectName();
0288 }
0289 
0290 int Action::version() const
0291 {
0292     return d->version;
0293 }
0294 
0295 QString Action::description() const
0296 {
0297     return d->description;
0298 }
0299 
0300 void Action::setDescription(const QString &description)
0301 {
0302     d->description = description;
0303     emit dataChanged(this);
0304     emit updated();
0305 }
0306 
0307 QString Action::iconName() const
0308 {
0309     return d->iconname;
0310 }
0311 
0312 void Action::setIconName(const QString &iconname)
0313 {
0314     setIcon(QIcon::fromTheme(iconname));
0315     d->iconname = iconname;
0316     emit dataChanged(this);
0317     emit updated();
0318 }
0319 
0320 bool Action::isEnabled() const
0321 {
0322     return QAction::isEnabled();
0323 }
0324 
0325 void Action::setEnabled(bool enabled)
0326 {
0327     QAction::setEnabled(enabled);
0328     emit dataChanged(this);
0329     emit updated();
0330 }
0331 
0332 QByteArray Action::code() const
0333 {
0334     return d->code;
0335 }
0336 
0337 void Action::setCode(const QByteArray &code)
0338 {
0339     if (d->code != code) {
0340         finalize();
0341         d->code = code;
0342         emit dataChanged(this);
0343         emit updated();
0344     }
0345 }
0346 
0347 QString Action::interpreter() const
0348 {
0349     return d->interpretername;
0350 }
0351 
0352 void Action::setInterpreter(const QString &interpretername)
0353 {
0354     if (d->interpretername != interpretername) {
0355         finalize();
0356         d->interpretername = interpretername;
0357         setEnabled(Manager::self().interpreters().contains(interpretername));
0358         if (!isEnabled()) {
0359             qCWarning(KROSS_LOG) << "Action::setInterpreter: interpreter not found: " << interpretername;
0360         }
0361         emit dataChanged(this);
0362         emit updated();
0363     }
0364 }
0365 
0366 QString Action::file() const
0367 {
0368     return d->scriptfile;
0369 }
0370 
0371 bool Action::setFile(const QString &scriptfile)
0372 {
0373     if (d->scriptfile != scriptfile) {
0374         finalize();
0375         if (scriptfile.isNull()) {
0376             if (! d->scriptfile.isNull()) {
0377                 d->interpretername.clear();
0378             }
0379             d->scriptfile.clear();
0380             d->searchpath.clear();
0381         } else {
0382             d->scriptfile = scriptfile;
0383             d->interpretername = Manager::self().interpreternameForFile(scriptfile);
0384             if (d->interpretername.isNull()) {
0385                 return false;
0386             }
0387         }
0388     }
0389     return true;
0390 }
0391 
0392 QString Action::currentPath() const
0393 {
0394     return file().isEmpty() ? QString() : QFileInfo(file()).absolutePath(); //obey Qt docs and don't cheat
0395 }
0396 
0397 QVariantMap Action::options() const
0398 {
0399     return d->options;
0400 }
0401 
0402 void Action::addQObject(QObject *obj, const QString &name)
0403 {
0404     this->addObject(obj, name);
0405 }
0406 
0407 QObject *Action::qobject(const QString &name) const
0408 {
0409     return this->object(name);
0410 }
0411 
0412 QStringList Action::qobjectNames() const
0413 {
0414     return this->objects().keys();
0415 }
0416 
0417 QVariant Action::option(const QString &name, const QVariant &defaultvalue)
0418 {
0419     if (d->options.contains(name)) {
0420         return d->options[name];
0421     }
0422     InterpreterInfo *info = Manager::self().interpreterInfo(d->interpretername);
0423     return info ? info->optionValue(name, defaultvalue) : defaultvalue;
0424 }
0425 
0426 bool Action::setOption(const QString &name, const QVariant &value)
0427 {
0428     InterpreterInfo *info = Manager::self().interpreterInfo(d->interpretername);
0429     if (info) {
0430         if (info->hasOption(name)) {
0431             d->options.insert(name, value);
0432             return true;
0433         } else {
0434             qCWarning(KROSS_LOG) << QStringLiteral("Kross::Action::setOption(%1, %2): No such option")
0435                 .arg(name).arg(value.toString());
0436         }
0437     } else {
0438         qCWarning(KROSS_LOG) << QStringLiteral("Kross::Action::setOption(%1, %2): No such interpreterinfo")
0439             .arg(name).arg(value.toString());
0440     }
0441     return false;
0442 }
0443 
0444 QStringList Action::functionNames()
0445 {
0446     if (! d->script) {
0447         if (! initialize()) {
0448             return QStringList();
0449         }
0450     }
0451     return d->script->functionNames();
0452 }
0453 
0454 QVariant Action::callFunction(const QString &name, const QVariantList &args)
0455 {
0456     if (! d->script) {
0457         if (! initialize()) {
0458             return QVariant();
0459         }
0460     }
0461     return d->script->callFunction(name, args);
0462 }
0463 
0464 QVariant Action::evaluate(const QByteArray &code)
0465 {
0466     if (! d->script) {
0467         if (! initialize()) {
0468             return QVariant();
0469         }
0470     }
0471     return d->script->evaluate(code);
0472 }
0473 
0474 bool Action::initialize()
0475 {
0476     finalize();
0477 
0478     if (! d->scriptfile.isNull()) {
0479         QFile f(d->scriptfile);
0480         if (! f.exists()) {
0481             setError(i18n("Scriptfile \"%1\" does not exist.", d->scriptfile));
0482             return false;
0483         }
0484         if (d->interpretername.isNull()) {
0485             setError(i18n("Failed to determine interpreter for scriptfile \"%1\"", d->scriptfile));
0486             return false;
0487         }
0488         if (! f.open(QIODevice::ReadOnly)) {
0489             setError(i18n("Failed to open scriptfile \"%1\"", d->scriptfile));
0490             return false;
0491         }
0492         d->code = f.readAll();
0493         f.close();
0494     }
0495 
0496     Interpreter *interpreter = Manager::self().interpreter(d->interpretername);
0497     if (! interpreter) {
0498         InterpreterInfo *info = Manager::self().interpreterInfo(d->interpretername);
0499         if (info) {
0500             setError(i18n("Failed to load interpreter \"%1\"", d->interpretername));
0501         } else {
0502             setError(i18n("No such interpreter \"%1\"", d->interpretername));
0503         }
0504         return false;
0505     }
0506 
0507     d->script = interpreter->createScript(this);
0508     if (! d->script) {
0509         setError(i18n("Failed to create script for interpreter \"%1\"", d->interpretername));
0510         return false;
0511     }
0512 
0513     if (d->script->hadError()) {
0514         setError(d->script);
0515         finalize();
0516         return false;
0517     }
0518 
0519     clearError(); // clear old exception
0520     return true;
0521 }
0522 
0523 void Action::finalize()
0524 {
0525     if (d->script) {
0526         emit finalized(this);
0527     }
0528     delete d->script;
0529     d->script = nullptr;
0530 }
0531 
0532 bool Action::isFinalized() const
0533 {
0534     return d->script == nullptr;
0535 }
0536 
0537 void Action::slotTriggered()
0538 {
0539 #ifdef KROSS_ACTION_DEBUG
0540     qCDebug(KROSS_LOG) << "Action::slotTriggered() name=" << objectName();
0541 #endif
0542 
0543     emit started(this);
0544 
0545     if (! d->script) {
0546         if (! initialize()) {
0547             Q_ASSERT(hadError());
0548         }
0549     }
0550 
0551     if (hadError()) {
0552 #ifdef KROSS_ACTION_DEBUG
0553         qCDebug(KROSS_LOG) << "Action::slotTriggered() on init, errorMessage=" << errorMessage();
0554 #endif
0555     } else {
0556         d->script->execute();
0557         if (d->script->hadError()) {
0558 #ifdef KROSS_ACTION_DEBUG
0559             qCDebug(KROSS_LOG) << "Action::slotTriggered() after exec, errorMessage=" << errorMessage();
0560 #endif
0561             setError(d->script);
0562             //emit finished(this);
0563             finalize();
0564             //return;
0565         }
0566     }
0567 
0568     emit finished(this);
0569 }
0570 
0571 // --------
0572 
0573 // interface files
0574 WrapperInterface::~WrapperInterface()
0575 {
0576 }
0577 
0578 #include "moc_action.cpp"