2 * Copyright 2007-2008 Richard J. Moore <rich@kde.org>
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU Library General Public License version 2 as
6 * published by the Free Software Foundation
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details
13 * You should have received a copy of the GNU Library General Public
14 * License along with this program; if not, write to the
15 * Free Software Foundation, Inc.,
16 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19 #include "simplejavascriptapplet.h"
21 #include <QScriptEngine>
24 #include <QGraphicsLayout>
29 #include <KStandardDirs>
30 #include <KConfigGroup>
32 #include <Plasma/Applet>
34 #include <Plasma/FrameSvg>
35 #include <Plasma/Package>
37 #include "appletinterface.h"
39 using namespace Plasma
;
41 #include "bind_dataengine.h"
43 Q_DECLARE_METATYPE(QPainter
*)
44 Q_DECLARE_METATYPE(QStyleOptionGraphicsItem
*)
45 Q_DECLARE_METATYPE(SimpleJavaScriptApplet
*)
46 Q_DECLARE_METATYPE(AppletInterface
*)
47 Q_DECLARE_METATYPE(Applet
*)
48 Q_DECLARE_METATYPE(QGraphicsWidget
*)
49 Q_DECLARE_METATYPE(QGraphicsLayout
*)
50 Q_DECLARE_METATYPE(KConfigGroup
)
52 Q_SCRIPT_DECLARE_QMETAOBJECT(AppletInterface
, SimpleJavaScriptApplet
*)
54 QScriptValue
constructPainterClass(QScriptEngine
*engine
);
55 QScriptValue
constructGraphicsItemClass(QScriptEngine
*engine
);
56 QScriptValue
constructLinearLayoutClass(QScriptEngine
*engine
);
57 QScriptValue
constructTimerClass(QScriptEngine
*engine
);
58 QScriptValue
constructFontClass(QScriptEngine
*engine
);
59 QScriptValue
constructQRectFClass(QScriptEngine
*engine
);
60 QScriptValue
constructQPointClass(QScriptEngine
*engine
);
61 QScriptValue
constructQSizeFClass(QScriptEngine
*engine
);
63 class DummyService
: public Service
66 ServiceJob
*createJob(const QString
&operation
, QMap
<QString
, QVariant
> ¶meters
)
75 * Workaround the fact that QtScripts handling of variants seems a bit broken.
77 QScriptValue
variant2ScriptValue(QScriptEngine
*engine
, QVariant var
)
80 return engine
->nullValue();
85 case QVariant::Invalid
:
86 return engine
->nullValue();
88 return QScriptValue(engine
, var
.toBool());
90 return engine
->newDate(var
.toDateTime());
91 case QVariant::DateTime
:
92 return engine
->newDate(var
.toDateTime());
93 case QVariant::Double
:
94 return QScriptValue(engine
, var
.toDouble());
96 case QVariant::LongLong
:
97 return QScriptValue(engine
, var
.toInt());
98 case QVariant::String
:
99 return QScriptValue(engine
, var
.toString());
100 case QVariant::Time
: {
101 QDateTime
t(QDate::currentDate(), var
.toTime());
102 return engine
->newDate(t
);
105 return QScriptValue(engine
, var
.toUInt());
110 return qScriptValueFromValue(engine
, var
);
113 QScriptValue
qScriptValueFromData(QScriptEngine
*engine
, const DataEngine::Data
&data
)
115 DataEngine::Data::const_iterator begin
= data
.begin();
116 DataEngine::Data::const_iterator end
= data
.end();
117 DataEngine::Data::const_iterator it
;
119 QScriptValue obj
= engine
->newObject();
121 for (it
= begin
; it
!= end
; ++it
) {
122 //kDebug() << "setting" << it.key() << "to" << it.value();
123 QString prop
= it
.key();
124 prop
.replace(' ', '_');
125 obj
.setProperty(prop
, variant2ScriptValue(engine
, it
.value()));
131 QScriptValue
qScriptValueFromKConfigGroup(QScriptEngine
*engine
, const KConfigGroup
&config
)
133 QScriptValue obj
= engine
->newObject();
135 if (!config
.isValid()) {
139 QMap
<QString
, QString
> entryMap
= config
.entryMap();
140 QMap
<QString
, QString
>::const_iterator it
= entryMap
.constBegin();
141 QMap
<QString
, QString
>::const_iterator begin
= it
;
142 QMap
<QString
, QString
>::const_iterator end
= entryMap
.constEnd();
144 //setting the group name
145 obj
.setProperty("__name", QScriptValue(engine
, config
.name()));
147 //setting the key/value pairs
148 for (it
= begin
; it
!= end
; ++it
) {
149 //kDebug() << "setting" << it.key() << "to" << it.value();
150 QString prop
= it
.key();
151 prop
.replace(' ', '_');
152 obj
.setProperty(prop
, variant2ScriptValue(engine
, it
.value()));
158 void kConfigGroupFromScriptValue(const QScriptValue
& obj
, KConfigGroup
&config
)
160 KConfigSkeleton
*skel
= new KConfigSkeleton();
161 config
= KConfigGroup(skel
->config(), obj
.property("__name").toString());
163 QScriptValueIterator
it(obj
);
165 while (it
.hasNext()) {
167 //kDebug() << it.name() << "is" << it.value().toString();
168 if (it
.name() != "__name") {
169 config
.writeEntry(it
.name(), it
.value().toString());
174 KSharedPtr
<UiLoader
> SimpleJavaScriptApplet::s_widgetLoader
;
176 SimpleJavaScriptApplet::SimpleJavaScriptApplet(QObject
*parent
, const QVariantList
&args
)
177 : Plasma::AppletScript(parent
)
179 //kDebug() << "Script applet launched, args" << args;
181 m_engine
= new QScriptEngine(this);
185 SimpleJavaScriptApplet::~SimpleJavaScriptApplet()
187 if (s_widgetLoader
.count() == 1) {
188 s_widgetLoader
.clear();
192 void SimpleJavaScriptApplet::reportError()
194 kDebug() << "Error: " << m_engine
->uncaughtException().toString()
195 << " at line " << m_engine
->uncaughtExceptionLineNumber() << endl
;
196 kDebug() << m_engine
->uncaughtExceptionBacktrace();
199 void SimpleJavaScriptApplet::showConfigurationInterface()
201 kDebug() << "Script: showConfigurationInterface";
203 // Here we'll load a ui file...
204 QScriptValue global
= m_engine
->globalObject();
206 QScriptValue fun
= m_self
.property("showConfigurationInterface");
207 if (!fun
.isFunction()) {
208 kDebug() << "Script: ShowConfiguratioInterface is not a function, " << fun
.toString();
212 QScriptContext
*ctx
= m_engine
->pushContext();
213 ctx
->setActivationObject(m_self
);
215 m_engine
->popContext();
217 if (m_engine
->hasUncaughtException()) {
222 void SimpleJavaScriptApplet::configAccepted()
224 QScriptValue fun
= m_self
.property("configAccepted");
225 if (!fun
.isFunction()) {
226 kDebug() << "Script: configAccepted is not a function, " << fun
.toString();
230 QScriptContext
*ctx
= m_engine
->pushContext();
231 ctx
->setActivationObject(m_self
);
233 m_engine
->popContext();
235 if (m_engine
->hasUncaughtException()) {
240 void SimpleJavaScriptApplet::dataUpdated(const QString
&name
, const DataEngine::Data
&data
)
242 QScriptValue fun
= m_self
.property("dataUpdate");
243 if (!fun
.isFunction()) {
244 kDebug() << "Script: dataUpdate is not a function, " << fun
.toString();
248 QScriptValueList args
;
249 args
<< m_engine
->toScriptValue(name
) << m_engine
->toScriptValue(data
);
251 QScriptContext
*ctx
= m_engine
->pushContext();
252 ctx
->setActivationObject(m_self
);
253 fun
.call(m_self
, args
);
254 m_engine
->popContext();
256 if (m_engine
->hasUncaughtException()) {
261 void SimpleJavaScriptApplet::executeAction(const QString
&name
)
263 callFunction("action_" + name
);
265 QScriptValue fun = m_self.property("action_" + name);
266 if (fun.isFunction()) {
267 QScriptContext *ctx = m_engine->pushContext();
268 ctx->setActivationObject(m_self);
270 m_engine->popContext();
272 if (m_engine->hasUncaughtException()) {
278 void SimpleJavaScriptApplet::paintInterface(QPainter
*p
, const QStyleOptionGraphicsItem
*option
, const QRect
&contentsRect
)
281 Q_UNUSED(contentsRect
)
283 //kDebug() << "paintInterface() (c++)";
284 QScriptValue fun
= m_self
.property("paintInterface");
285 if (!fun
.isFunction()) {
286 //kDebug() << "Script: paintInterface is not a function, " << fun.toString();
287 AppletScript::paintInterface(p
, option
, contentsRect
);
291 QScriptValueList args
;
292 args
<< m_engine
->toScriptValue(p
);
293 args
<< m_engine
->toScriptValue(const_cast<QStyleOptionGraphicsItem
*>(option
));
294 args
<< m_engine
->toScriptValue(contentsRect
);
296 QScriptContext
*ctx
= m_engine
->pushContext();
297 ctx
->setActivationObject(m_self
);
298 fun
.call(m_self
, args
);
299 m_engine
->popContext();
301 if (m_engine
->hasUncaughtException()) {
306 QList
<QAction
*> SimpleJavaScriptApplet::contextualActions()
308 return m_interface
->contextualActions();
311 void SimpleJavaScriptApplet::callFunction(const QString
&functionName
, const QScriptValueList
&args
)
313 QScriptValue fun
= m_self
.property(functionName
);
314 if (fun
.isFunction()) {
315 QScriptContext
*ctx
= m_engine
->pushContext();
316 ctx
->setActivationObject(m_self
);
317 fun
.call(m_self
, args
);
318 m_engine
->popContext();
320 if (m_engine
->hasUncaughtException()) {
326 void SimpleJavaScriptApplet::constraintsEvent(Plasma::Constraints constraints
)
328 QString functionName
;
330 if (constraints
& Plasma::FormFactorConstraint
) {
331 callFunction("formFactorChanged");
334 if (constraints
& Plasma::LocationConstraint
) {
335 callFunction("locationChanged");
338 if (constraints
& Plasma::ContextConstraint
) {
339 callFunction("contextChanged");
343 bool SimpleJavaScriptApplet::init()
347 kDebug() << "ScriptName:" << applet()->name();
348 kDebug() << "ScriptCategory:" << applet()->category();
350 QFile
file(mainScript());
351 if (!file
.open(QIODevice::ReadOnly
| QIODevice::Text
)) {
352 kWarning() << "Unable to load script file";
356 QString script
= file
.readAll();
357 //kDebug() << "Script says" << script;
359 m_engine
->evaluate(script
);
360 if (m_engine
->hasUncaughtException()) {
368 void SimpleJavaScriptApplet::importExtensions()
370 return; // no extension, so do bother wasting cycles
373 QStringList extensions;
374 //extensions << "qt.core" << "qt.gui" << "qt.svg" << "qt.xml" << "qt.plasma";
375 //extensions << "qt.core" << "qt.gui" << "qt.xml";
376 foreach (const QString &ext, extensions) {
377 kDebug() << "importing " << ext << "...";
378 QScriptValue ret = m_engine->importExtension(ext);
380 kDebug() << "failed to import extension" << ext << ":" << ret.toString();
383 kDebug() << "done importing extensions.";
387 void SimpleJavaScriptApplet::setupObjects()
389 QScriptValue global
= m_engine
->globalObject();
391 // Bindings for data engine
392 m_engine
->setDefaultPrototype(qMetaTypeId
<DataEngine
*>(), m_engine
->newQObject(new DataEngine()));
393 m_engine
->setDefaultPrototype(qMetaTypeId
<Service
*>(), m_engine
->newQObject(new DummyService()));
394 m_engine
->setDefaultPrototype(qMetaTypeId
<ServiceJob
*>(), m_engine
->newQObject(new ServiceJob(QString(), QString(), QMap
<QString
, QVariant
>())));
396 global
.setProperty("dataEngine", m_engine
->newFunction(SimpleJavaScriptApplet::dataEngine
));
397 global
.setProperty("service", m_engine
->newFunction(SimpleJavaScriptApplet::service
));
398 qScriptRegisterMetaType
<DataEngine::Data
>(m_engine
, qScriptValueFromData
, 0, QScriptValue());
399 qScriptRegisterMetaType
<KConfigGroup
>(m_engine
, qScriptValueFromKConfigGroup
, kConfigGroupFromScriptValue
, QScriptValue());
401 // Expose applet interface
402 m_interface
= new AppletInterface(this);
403 m_self
= m_engine
->newQObject(m_interface
);
404 m_self
.setScope(global
);
405 global
.setProperty("plasmoid", m_self
);
407 //manually create enum values. ugh
408 QMetaObject meta
= AppletInterface::staticMetaObject
;
409 for (int i
=0; i
< meta
.enumeratorCount(); ++i
) {
410 QMetaEnum e
= meta
.enumerator(i
);
411 //kDebug() << e.name();
412 for (int i
=0; i
< e
.keyCount(); ++i
) {
413 //kDebug() << e.key(i) << e.value(i);
414 global
.setProperty(e
.key(i
), QScriptValue(m_engine
, e
.value(i
)));
418 // Add a global loadui method for ui files
419 QScriptValue fun
= m_engine
->newFunction(SimpleJavaScriptApplet::loadui
);
420 global
.setProperty("loadui", fun
);
422 fun
= m_engine
->newFunction(SimpleJavaScriptApplet::print
);
423 global
.setProperty("print", fun
);
426 // Work around bug in 4.3.0
427 qMetaTypeId
<QVariant
>();
430 global
.setProperty("PlasmaSvg", m_engine
->newFunction(SimpleJavaScriptApplet::newPlasmaSvg
));
431 global
.setProperty("PlasmaFrameSvg", m_engine
->newFunction(SimpleJavaScriptApplet::newPlasmaFrameSvg
));
433 // Add stuff from 4.4
434 global
.setProperty("QPainter", constructPainterClass(m_engine
));
435 global
.setProperty("QGraphicsItem", constructGraphicsItemClass(m_engine
));
436 global
.setProperty("QTimer", constructTimerClass(m_engine
));
437 global
.setProperty("QFont", constructFontClass(m_engine
));
438 global
.setProperty("QRectF", constructQRectFClass(m_engine
));
439 global
.setProperty("QSizeF", constructQSizeFClass(m_engine
));
440 global
.setProperty("QPoint", constructQPointClass(m_engine
));
441 global
.setProperty("LinearLayout", constructLinearLayoutClass(m_engine
));
443 installWidgets(m_engine
);
446 QString
SimpleJavaScriptApplet::findDataResource(const QString
&filename
)
448 QString
path("plasma-script/%1");
449 return KGlobal::dirs()->findResource("data", path
.arg(filename
));
452 void SimpleJavaScriptApplet::debug(const QString
&msg
)
458 QScriptValue
SimpleJavaScriptApplet::dataEngine(QScriptContext
*context
, QScriptEngine
*engine
)
460 if (context
->argumentCount() != 1)
461 return context
->throwError("dataEngine takes one argument");
463 QString dataEngine
= context
->argument(0).toString();
465 Script
*self
= engine
->fromScriptValue
<Script
*>(context
->thisObject());
467 DataEngine
*data
= self
->dataEngine(dataEngine
);
468 return engine
->newQObject(data
);
472 QScriptValue
SimpleJavaScriptApplet::dataEngine(QScriptContext
*context
, QScriptEngine
*engine
)
474 if (context
->argumentCount() != 1) {
475 return context
->throwError(i18n("DataEngine takes one argument"));
478 QString dataEngine
= context
->argument(0).toString();
480 QScriptValue appletValue
= engine
->globalObject().property("plasmoid");
481 //kDebug() << "appletValue is " << appletValue.toString();
483 QObject
*appletObject
= appletValue
.toQObject();
485 return context
->throwError(i18n("Could not extract the AppletObject"));
488 AppletInterface
*interface
= qobject_cast
<AppletInterface
*>(appletObject
);
490 return context
->throwError(i18n("Could not extract the Applet"));
493 DataEngine
*data
= interface
->dataEngine(dataEngine
);
494 return engine
->newQObject(data
);
497 QScriptValue
SimpleJavaScriptApplet::service(QScriptContext
*context
, QScriptEngine
*engine
)
499 if (context
->argumentCount() != 2) {
500 return context
->throwError(i18n("Service takes two arguments"));
503 QString dataEngine
= context
->argument(0).toString();
505 QScriptValue appletValue
= engine
->globalObject().property("plasmoid");
506 //kDebug() << "appletValue is " << appletValue.toString();
508 QObject
*appletObject
= appletValue
.toQObject();
510 return context
->throwError(i18n("Could not extract the AppletObject"));
513 AppletInterface
*interface
= qobject_cast
<AppletInterface
*>(appletObject
);
515 return context
->throwError(i18n("Could not extract the Applet"));
518 DataEngine
*data
= interface
->dataEngine(dataEngine
);
519 QString source
= context
->argument(1).toString();
520 Service
*service
= data
->serviceForSource(source
);
521 //kDebug( )<< "lets try to get" << source << "from" << dataEngine;
522 return engine
->newQObject(service
);
525 QScriptValue
SimpleJavaScriptApplet::loadui(QScriptContext
*context
, QScriptEngine
*engine
)
527 if (context
->argumentCount() != 1) {
528 return context
->throwError(i18n("loadUI takes one argument"));
531 QString filename
= context
->argument(0).toString();
533 if (!f
.open(QIODevice::ReadOnly
)) {
534 return context
->throwError(i18n("Unable to open '%1'",filename
));
538 QWidget
*w
= loader
.load(&f
);
541 return engine
->newQObject(w
);
544 QString
SimpleJavaScriptApplet::findSvg(QScriptEngine
*engine
, const QString
&file
)
546 QScriptValue appletValue
= engine
->globalObject().property("plasmoid");
547 //kDebug() << "appletValue is " << appletValue.toString();
549 QObject
*appletObject
= appletValue
.toQObject();
554 AppletInterface
*interface
= qobject_cast
<AppletInterface
*>(appletObject
);
559 QString path
= interface
->package()->filePath("images", file
+ ".svg");
560 if (path
.isEmpty()) {
561 path
= interface
->package()->filePath("images", file
+ ".svgz");
563 if (path
.isEmpty()) {
571 QScriptValue
SimpleJavaScriptApplet::newPlasmaSvg(QScriptContext
*context
, QScriptEngine
*engine
)
573 if (context
->argumentCount() == 0) {
574 return context
->throwError(i18n("Constructor takes at least 1 argument"));
577 QString filename
= context
->argument(0).toString();
580 if (context
->argumentCount() == 2) {
581 parent
= qscriptvalue_cast
<QObject
*>(context
->argument(1));
584 bool parentedToApplet
= false;
586 QScriptValue appletValue
= engine
->globalObject().property("plasmoid");
587 //kDebug() << "appletValue is " << appletValue.toString();
589 QObject
*appletObject
= appletValue
.toQObject();
591 AppletInterface
*interface
= qobject_cast
<AppletInterface
*>(appletObject
);
593 parentedToApplet
= true;
594 parent
= interface
->applet();
599 Svg
*svg
= new Svg(parent
);
600 svg
->setImagePath(parentedToApplet
? filename
: findSvg(engine
, filename
));
601 return engine
->newQObject(svg
);
604 QScriptValue
SimpleJavaScriptApplet::newPlasmaFrameSvg(QScriptContext
*context
, QScriptEngine
*engine
)
606 if (context
->argumentCount() == 0) {
607 return context
->throwError(i18n("Constructor takes at least 1 argument"));
610 QString filename
= context
->argument(0).toString();
613 if (context
->argumentCount() == 2) {
614 parent
= qscriptvalue_cast
<QObject
*>(context
->argument(1));
617 bool parentedToApplet
= false;
619 QScriptValue appletValue
= engine
->globalObject().property("plasmoid");
620 //kDebug() << "appletValue is " << appletValue.toString();
622 QObject
*appletObject
= appletValue
.toQObject();
624 AppletInterface
*interface
= qobject_cast
<AppletInterface
*>(appletObject
);
626 parentedToApplet
= true;
627 parent
= interface
->applet();
632 FrameSvg
*frameSvg
= new FrameSvg(parent
);
633 frameSvg
->setImagePath(parentedToApplet
? filename
: findSvg(engine
, filename
));
634 return engine
->newQObject(frameSvg
);
637 void SimpleJavaScriptApplet::installWidgets(QScriptEngine
*engine
)
639 QScriptValue globalObject
= engine
->globalObject();
640 if (!s_widgetLoader
) {
641 s_widgetLoader
= new UiLoader
;
644 foreach (const QString
&widget
, s_widgetLoader
->availableWidgets()) {
645 QScriptValue fun
= engine
->newFunction(createWidget
);
646 QScriptValue name
= engine
->toScriptValue(widget
);
647 fun
.setProperty(QString("functionName"), name
,
648 QScriptValue::ReadOnly
| QScriptValue::Undeletable
| QScriptValue::SkipInEnumeration
);
649 fun
.setProperty(QString("prototype"), createPrototype(engine
, name
.toString()));
651 globalObject
.setProperty(widget
, fun
);
655 QScriptValue
SimpleJavaScriptApplet::createWidget(QScriptContext
*context
, QScriptEngine
*engine
)
657 if (context
->argumentCount() > 1) {
658 return context
->throwError(i18n("CreateWidget takes one argument"));
661 QGraphicsWidget
*parent
= 0;
662 if (context
->argumentCount()) {
663 parent
= qscriptvalue_cast
<QGraphicsWidget
*>(context
->argument(0));
666 return context
->throwError(i18n("The parent must be a QGraphicsWidget"));
670 QString self
= context
->callee().property("functionName").toString();
671 if (!s_widgetLoader
) {
672 s_widgetLoader
= new UiLoader
;
675 QGraphicsWidget
*w
= s_widgetLoader
->createWidget(self
, parent
);
678 return QScriptValue();
681 QScriptValue fun
= engine
->newQObject(w
);
682 fun
.setPrototype(context
->callee().property("prototype"));
687 QScriptValue
SimpleJavaScriptApplet::print(QScriptContext
*context
, QScriptEngine
*engine
)
689 if (context
->argumentCount() != 1) {
690 return context
->throwError(i18n("print takes one argument"));
693 kDebug() << context
->argument(0).toString();
694 return engine
->undefinedValue();
697 QScriptValue
SimpleJavaScriptApplet::createPrototype(QScriptEngine
*engine
, const QString
&name
)
700 QScriptValue proto
= engine
->newObject();
702 // Hook for adding extra properties/methods
706 K_EXPORT_PLASMA_APPLETSCRIPTENGINE(qscriptapplet
, SimpleJavaScriptApplet
)
708 #include "simplejavascriptapplet.moc"