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"