History

changes per CR

humbletim committed 6 years ago

Commit 5c2a8bd1 parent dedf6a61 file · +202 −201Browse files

Showing the changes to plugins/JSAPIExample/src/JSAPIExample.cpp (1 of 1 file). Show all

Unified · Split

plugins/JSAPIExample/src/JSAPIExample.cpp+202−201
@@ -28,222 +28,223 @@ · 27 unmodified lines
2828 // NOTE: replace this with your own namespace when starting a new plugin (to avoid .so/.dll symbol clashes)
2929 namespace REPLACE_ME_WITH_UNIQUE_NAME {
3030
31−static constexpr auto JSAPI_SEMANTIC_VERSION = "0.0.1";
32−static constexpr auto JSAPI_EXPORT_NAME = "JSAPIExample";
33−
34−QLoggingCategory logger{ "jsapiexample" };
35−
36−inline QVariant raiseScriptingError(QScriptContext* context, const QString& message, const QVariant& returnValue = QVariant()) {
37− if (context) {
38− // when a QScriptContext is available throw an actual JS Exception (which can be caught using try/catch on JS side)
39− context->throwError(message);
40− } else {
41− // otherwise just log the error
42− qCWarning(logger) << "error:" << message;
43− }
44− return returnValue;
45−}
46−
47−QObject* createScopedSettings(const QString& scope, QObject* parent, QString& error);
48−
49−class JSAPIExample : public QObject, public QScriptable {
50− Q_OBJECT
51− Q_PLUGIN_METADATA(IID "JSAPIExample" FILE "plugin.json")
52− Q_PROPERTY(QString version MEMBER _version CONSTANT)
53−public:
54− JSAPIExample() {
55− setObjectName(JSAPI_EXPORT_NAME);
56− auto scriptInit = DependencyManager::get<ScriptInitializers>();
57− if (!scriptInit) {
58− qCWarning(logger) << "COULD NOT INITIALIZE (ScriptInitializers unavailable)" << qApp << this;
59− return;
31+ static constexpr auto JSAPI_SEMANTIC_VERSION = "0.0.1";
32+ static constexpr auto JSAPI_EXPORT_NAME = "JSAPIExample";
33+
34+ QLoggingCategory logger { "jsapiexample" };
35+
36+ inline QVariant raiseScriptingError(QScriptContext* context, const QString& message, const QVariant& returnValue = QVariant()) {
37+ if (context) {
38+ // when a QScriptContext is available throw an actual JS Exception (which can be caught using try/catch on JS side)
39+ context->throwError(message);
40+ } else {
41+ // otherwise just log the error
42+ qCWarning(logger) << "error:" << message;
6043 }
61− qCWarning(logger) << "registering w/ScriptInitializerMixin..." << scriptInit.data();
62− scriptInit->registerScriptInitializer([this](QScriptEngine* engine) {
63− auto value = engine->newQObject(this, QScriptEngine::QtOwnership, QScriptEngine::ExcludeDeleteLater);
64− engine->globalObject().setProperty(objectName(), value);
65− // qCDebug(logger) << "setGlobalInstance" << objectName() << engine->property("fileName");
66− });
67− // qCInfo(logger) << "plugin loaded" << qApp << toString() << QThread::currentThread();
44+ return returnValue;
6845 }
6946
70− // NOTES: everything within the "public slots:" section below will be available from JS via overall plugin QObject
71− // also, to demonstrate future-proofing JS API code, QVariant's are used throughout most of these examples --
72− // which still makes them very Qt-specific, but avoids depending directly on deprecated QtScript/QScriptValue APIs.
73− // (as such this plugin class and its methods remain forward-compatible with other engines like QML's QJSEngine)
74−
75−public slots:
76− // pretty-printed representation for logging eg: print(JSAPIExample)
77− // (note: Qt script engines automatically look for a ".toString" method on native classes when coercing values to strings)
78− QString toString() const { return QString("[%1 version=%2]").arg(objectName()).arg(_version); }
79−
80− /**jsdoc
81− * Returns current microseconds (usecs) since Epoch. note: 1000usecs == 1ms
82− * @example <caption>Measure current setTimeout accuracy.</caption>
83− * var expected = 1000;
84− * var start = JSAPIExample.now();
85− * Script.setTimeout(function () {
86− * var elapsed = (JSAPIExample.now() - start)/1000;
87− * print("expected (ms):", expected, "actual (ms):", elapsed);
88− * }, expected);
89− */
90− QVariant now() const { return usecTimestampNow(); }
91−
92− /**jsdoc
93− * Example of returning a JS Object key-value map
94− * @example <caption>"zip" a list of keys and corresponding values to form key-value map</caption>
95− * print(JSON.stringify(JSAPIExample.zip(["a","b"], [1,2])); // { "a": 1, "b": 2 }
96− */
97− QVariant zip(const QStringList& keys, const QVariantList& values) const {
98− QVariantMap out;
99− for (int i = 0; i < keys.size(); i++) {
100− out[keys[i]] = i < values.size() ? values[i] : QVariant();
47+ QObject* createScopedSettings(const QString& scope, QObject* parent, QString& error);
48+
49+ class JSAPIExample : public QObject, public QScriptable {
50+ Q_OBJECT
51+ Q_PLUGIN_METADATA(IID "JSAPIExample" FILE "plugin.json")
52+ Q_PROPERTY(QString version MEMBER _version CONSTANT)
53+ public:
54+ JSAPIExample() {
55+ setObjectName(JSAPI_EXPORT_NAME);
56+ auto scriptInit = DependencyManager::get<ScriptInitializers>();
57+ if (!scriptInit) {
58+ qCWarning(logger) << "COULD NOT INITIALIZE (ScriptInitializers unavailable)" << qApp << this;
59+ return;
60+ }
61+ qCWarning(logger) << "registering w/ScriptInitializerMixin..." << scriptInit.data();
62+ scriptInit->registerScriptInitializer([this](QScriptEngine* engine) {
63+ auto value = engine->newQObject(this, QScriptEngine::QtOwnership, QScriptEngine::ExcludeDeleteLater);
64+ engine->globalObject().setProperty(objectName(), value);
65+ // qCDebug(logger) << "setGlobalInstance" << objectName() << engine->property("fileName");
66+ });
67+ // qCInfo(logger) << "plugin loaded" << qApp << toString() << QThread::currentThread();
10168 }
102− return out;
103− }
10469
105− /**jsdoc
106− * Example of returning a JS Array result
107− * @example <caption>emulate Object.values(keyValues)</caption>
108− * print(JSON.stringify(JSAPIExample.values({ "a": 1, "b": 2 }))); // [1,2]
109− */
110− QVariant values(const QVariantMap& keyValues) const {
111− QVariantList values = keyValues.values();
112− return values;
113− }
70+ // NOTES: everything within the "public slots:" section below will be available from JS via overall plugin QObject
71+ // also, to demonstrate future-proofing JS API code, QVariant's are used throughout most of these examples --
72+ // which still makes them very Qt-specific, but avoids depending directly on deprecated QtScript/QScriptValue APIs.
73+ // (as such this plugin class and its methods remain forward-compatible with other engines like QML's QJSEngine)
74+
75+ public slots:
76+ // pretty-printed representation for logging eg: print(JSAPIExample)
77+ // (note: Qt script engines automatically look for a ".toString" method on native classes when coercing values to strings)
78+ QString toString() const { return QString("[%1 version=%2]").arg(objectName()).arg(_version); }
79+
80+ /**jsdoc
81+ * Returns current microseconds (usecs) since Epoch. note: 1000usecs == 1ms
82+ * @example <caption>Measure current setTimeout accuracy.</caption>
83+ * var expected = 1000;
84+ * var start = JSAPIExample.now();
85+ * Script.setTimeout(function () {
86+ * var elapsed = (JSAPIExample.now() - start)/1000;
87+ * print("expected (ms):", expected, "actual (ms):", elapsed);
88+ * }, expected);
89+ */
90+ QVariant now() const { return usecTimestampNow(); }
91+
92+ /**jsdoc
93+ * Example of returning a JS Object key-value map
94+ * @example <caption>"zip" a list of keys and corresponding values to form key-value map</caption>
95+ * print(JSON.stringify(JSAPIExample.zip(["a","b"], [1,2])); // { "a": 1, "b": 2 }
96+ */
97+ QVariant zip(const QStringList& keys, const QVariantList& values) const {
98+ QVariantMap out;
99+ for (int i = 0; i < keys.size(); i++) {
100+ out[keys[i]] = i < values.size() ? values[i] : QVariant();
101+ }
102+ return out;
103+ }
114104
115− /**jsdoc
116− * Another example of returning JS Array data
117− * @example <caption>generate an integer sequence (inclusive of [from, to])</caption>
118− * print(JSON.stringify(JSAPIExample.seq(1,5)));// [1,2,3,4,5]
119− */
120− QVariant seq(int from, int to) const {
121− QVariantList out;
122− for (int i = from; i <= to; i++) {
123− out.append(i);
105+ /**jsdoc
106+ * Example of returning a JS Array result
107+ * @example <caption>emulate Object.values(keyValues)</caption>
108+ * print(JSON.stringify(JSAPIExample.values({ "a": 1, "b": 2 }))); // [1,2]
109+ */
110+ QVariant values(const QVariantMap& keyValues) const {
111+ QVariantList values = keyValues.values();
112+ return values;
124113 }
125− return out;
126− }
127114
128− /**jsdoc
129− * Example of returning arbitrary binary data from C++ (resulting in a JS ArrayBuffer)
130− * see also: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer#Examples
131− * @example <caption>return compressed/decompressed versions of the input data</caption>
132− * var data = "testing 1 2 3";
133− * var z = JSAPIExample.qCompressString(data); // z will be an ArrayBuffer
134− * var u = JSAPIExample.qUncompressString(z); // u will be a String value
135− * print(JSON.stringify({ input: data, compressed: z.byteLength, output: u, uncompressed: u.length }));
136− */
137− QVariant qCompressString(const QString& jsString, int compress_level = -1) const {
138− QByteArray arrayBuffer = qCompress(jsString.toUtf8(), compress_level);
139− return arrayBuffer;
140− }
141− QVariant qUncompressString(const QByteArray& arrayBuffer) const {
142− QString jsString = QString::fromUtf8(qUncompress(arrayBuffer));
143− return jsString;
144− }
115+ /**jsdoc
116+ * Another example of returning JS Array data
117+ * @example <caption>generate an integer sequence (inclusive of [from, to])</caption>
118+ * print(JSON.stringify(JSAPIExample.seq(1,5)));// [1,2,3,4,5]
119+ */
120+ QVariant seq(int from, int to) const {
121+ QVariantList out;
122+ for (int i = from; i <= to; i++) {
123+ out.append(i);
124+ }
125+ return out;
126+ }
127+
128+ /**jsdoc
129+ * Example of returning arbitrary binary data from C++ (resulting in a JS ArrayBuffer)
130+ * see also: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer#Examples
131+ * @example <caption>return compressed/decompressed versions of the input data</caption>
132+ * var data = "testing 1 2 3";
133+ * var z = JSAPIExample.qCompressString(data); // z will be an ArrayBuffer
134+ * var u = JSAPIExample.qUncompressString(z); // u will be a String value
135+ * print(JSON.stringify({ input: data, compressed: z.byteLength, output: u, uncompressed: u.length }));
136+ */
137+ QVariant qCompressString(const QString& jsString, int compress_level = -1) const {
138+ QByteArray arrayBuffer = qCompress(jsString.toUtf8(), compress_level);
139+ return arrayBuffer;
140+ }
141+ QVariant qUncompressString(const QByteArray& arrayBuffer) const {
142+ QString jsString = QString::fromUtf8(qUncompress(arrayBuffer));
143+ return jsString;
144+ }
145+
146+ /**
147+ * Example of exposing a custom "managed" C++ QObject to JS
148+ * The lifecycle of the created QObject* instance becomes managed by the invoking QScriptEngine --
149+ * it will be automatically cleaned up once no longer reachable from any JS variables/closures.
150+ * @example <caption>access persistent settings stored in separate .json files</caption>
151+ * var settings = JSAPIExample.getScopedSettings("example");
152+ * print("example settings stored in:", settings.fileName());
153+ * print("(before) example::timestamp", settings.getValue("timestamp"));
154+ * settings.setValue("timestamp", Date.now());
155+ * print("(after) example::timestamp", settings.getValue("timestamp"));
156+ * print("all example::* keys", settings.allKeys());
157+ * settings = null; // optional best pratice; allows the object to be reclaimed ASAP by the JS garbage collector
158+ */
159+ QScriptValue getScopedSettings(const QString& scope) {
160+ auto engine = QScriptable::engine();
161+ if (!engine) {
162+ return QScriptValue::NullValue;
163+ }
164+ QString error;
165+ auto cppValue = createScopedSettings(scope, engine, error);
166+ if (!cppValue) {
167+ raiseScriptingError(context(), "error creating scoped settings instance: " + error);
168+ return QScriptValue::NullValue;
169+ }
170+ return engine->newQObject(cppValue, QScriptEngine::ScriptOwnership, QScriptEngine::ExcludeDeleteLater);
171+ }
145172
146− /**
147− * Example of exposing a custom "managed" C++ QObject to JS
148− * The lifecycle of the created QObject* instance becomes managed by the invoking QScriptEngine --
149− * it will be automatically cleaned up once no longer reachable from any JS variables/closures.
150− * @example <caption>access persistent settings stored in separate .json files</caption>
151− * var settings = JSAPIExample.getScopedSettings("example");
152− * print("example settings stored in:", settings.fileName());
153− * print("(before) example::timestamp", settings.getValue("timestamp"));
154− * settings.setValue("timestamp", Date.now());
155− * print("(after) example::timestamp", settings.getValue("timestamp"));
156− * print("all example::* keys", settings.allKeys());
157− * settings = null; // optional best pratice; allows the object to be reclaimed ASAP by the JS garbage collector
158− */
159− QScriptValue getScopedSettings(const QString& scope) {
160− auto engine = QScriptable::engine();
161− if (!engine)
162− return QScriptValue::NullValue;
163− QString error;
164− auto cppValue = createScopedSettings(scope, engine, error);
165− if (!cppValue) {
166− raiseScriptingError(context(), "error creating scoped settings instance: " + error);
167− return QScriptValue::NullValue;
173+ private:
174+ const QString _version { JSAPI_SEMANTIC_VERSION };
175+ };
176+
177+ // JSSettingsHelper wraps a scoped (prefixed/separate) QSettings and exposes a subset of QSetting APIs as slots
178+ class JSSettingsHelper : public QObject {
179+ Q_OBJECT
180+ public:
181+ JSSettingsHelper(const QString& scope, QObject* parent = nullptr);
182+ ~JSSettingsHelper();
183+ operator bool() const;
184+ public slots:
185+ QString fileName() const;
186+ QString toString() const;
187+ QVariant getValue(const QString& key, const QVariant& defaultValue = QVariant());
188+ bool setValue(const QString& key, const QVariant& value);
189+ QStringList allKeys() const;
190+ protected:
191+ QString _scope;
192+ QString _fileName;
193+ QSharedPointer<QSettings> _settings;
194+ QString getLocalSettingsPath(const QString& scope) const;
195+ };
196+
197+ // verifies the requested scope is sensible and creates/returns a scoped JSSettingsHelper instance
198+ QObject* createScopedSettings(const QString& scope, QObject* parent, QString& error) {
199+ const QRegExp VALID_SETTINGS_SCOPE { "[-_A-Za-z0-9]{1,64}" };
200+ if (!VALID_SETTINGS_SCOPE.exactMatch(scope)) {
201+ error = QString("invalid scope (expected alphanumeric <= 64 chars not '%1')").arg(scope);
202+ return nullptr;
168203 }
169− return engine->newQObject(cppValue, QScriptEngine::ScriptOwnership, QScriptEngine::ExcludeDeleteLater);
204+ return new JSSettingsHelper(scope, parent);
170205 }
171206
172−private:
173− const QString _version{ JSAPI_SEMANTIC_VERSION };
174−};
175−
176−// JSSettingsHelper wraps a scoped (prefixed/separate) QSettings and exposes a subset of QSetting APIs as slots
177−class JSSettingsHelper : public QObject {
178− Q_OBJECT
179−public:
180− JSSettingsHelper(const QString& scope, QObject* parent = nullptr);
181− ~JSSettingsHelper();
182− operator bool() const;
183−public slots:
184− QString fileName() const;
185− QString toString() const;
186− QVariant getValue(const QString& key, const QVariant& defaultValue = QVariant());
187− bool setValue(const QString& key, const QVariant& value);
188− QStringList allKeys() const;
189−protected:
190− QString _scope;
191− QString _fileName;
192− QSharedPointer<QSettings> _settings;
193− QString getLocalSettingsPath(const QString& scope) const;
194−};
195−
196−// verifies the requested scope is sensible and creates/returns a scoped JSSettingsHelper instance
197−QObject* createScopedSettings(const QString& scope, QObject* parent, QString& error) {
198− const QRegExp VALID_SETTINGS_SCOPE{ "[-_A-Za-z0-9]{1,64}" };
199− if (!VALID_SETTINGS_SCOPE.exactMatch(scope)) {
200− error = QString("invalid scope (expected alphanumeric <= 64 chars not '%1')").arg(scope);
201− return nullptr;
207+ // --------------------------------------------------
208+ // ----- inline JSSettingsHelper implementation -----
209+ JSSettingsHelper::operator bool() const {
210+ return (bool)_settings;
202211 }
203− return new JSSettingsHelper(scope, parent);
204−}
205−
206−// --------------------------------------------------
207−// ----- inline JSSettingsHelper implementation -----
208−JSSettingsHelper::operator bool() const {
209− return (bool)_settings;
210−}
211−JSSettingsHelper::JSSettingsHelper(const QString& scope, QObject* parent) :
212− QObject(parent), _scope(scope), _fileName(getLocalSettingsPath(scope)),
213− _settings(_fileName.isEmpty() ? nullptr : new QSettings(_fileName, JSON_FORMAT)) {
214−}
215−JSSettingsHelper::~JSSettingsHelper() {
216− qCDebug(logger) << "~JSSettingsHelper" << _scope << _fileName << this;
217−}
218−QString JSSettingsHelper::fileName() const {
219− return _settings ? _settings->fileName() : "";
220−}
221−QString JSSettingsHelper::toString() const {
222− return QString("[JSSettingsHelper scope=%1 valid=%2]").arg(_scope).arg((bool)_settings);
223−}
224−QVariant JSSettingsHelper::getValue(const QString& key, const QVariant& defaultValue) {
225− return _settings ? _settings->value(key, defaultValue) : defaultValue;
226−}
227−bool JSSettingsHelper::setValue(const QString& key, const QVariant& value) {
228− if (_settings) {
229− if (value.isValid()) {
230− _settings->setValue(key, value);
231− } else {
232− _settings->remove(key);
212+ JSSettingsHelper::JSSettingsHelper(const QString& scope, QObject* parent) :
213+ QObject(parent), _scope(scope), _fileName(getLocalSettingsPath(scope)),
214+ _settings(_fileName.isEmpty() ? nullptr : new QSettings(_fileName, JSON_FORMAT)) {
215+ }
216+ JSSettingsHelper::~JSSettingsHelper() {
217+ qCDebug(logger) << "~JSSettingsHelper" << _scope << _fileName << this;
218+ }
219+ QString JSSettingsHelper::fileName() const {
220+ return _settings ? _settings->fileName() : "";
221+ }
222+ QString JSSettingsHelper::toString() const {
223+ return QString("[JSSettingsHelper scope=%1 valid=%2]").arg(_scope).arg((bool)_settings);
224+ }
225+ QVariant JSSettingsHelper::getValue(const QString& key, const QVariant& defaultValue) {
226+ return _settings ? _settings->value(key, defaultValue) : defaultValue;
227+ }
228+ bool JSSettingsHelper::setValue(const QString& key, const QVariant& value) {
229+ if (_settings) {
230+ if (value.isValid()) {
231+ _settings->setValue(key, value);
232+ } else {
233+ _settings->remove(key);
234+ }
235+ return true;
233236 }
234− return true;
237+ return false;
238+ }
239+ QStringList JSSettingsHelper::allKeys() const {
240+ return _settings ? _settings->allKeys() : QStringList{};
241+ }
242+ QString JSSettingsHelper::getLocalSettingsPath(const QString& scope) const {
243+ // generate a prefixed filename (relative to the main application's Interface.json file)
244+ const QString fileName = QString("jsapi_%1.json").arg(scope);
245+ return QFileInfo(::settingsFilename()).dir().filePath(fileName);
235246 }
236− return false;
237−}
238−QStringList JSSettingsHelper::allKeys() const {
239− return _settings ? _settings->allKeys() : QStringList{};
240−}
241−QString JSSettingsHelper::getLocalSettingsPath(const QString& scope) const {
242− // generate a prefixed filename (relative to the main application's Interface.json file)
243− const QString fileName = QString("jsapi_%1.json").arg(scope);
244− return QFileInfo(::settingsFilename()).dir().filePath(fileName);
245−}
246−// ----- /inline JSSettingsHelper implementation -----
247+ // ----- /inline JSSettingsHelper implementation -----
247248
248249 } // namespace REPLACE_ME_WITH_UNIQUE_NAME
249250