"quick": rename "Lisp.fun()" to "Lisp.call()"; allow optionally passing JS "this" (a QQuickItem) as first argument;

This commit is contained in:
polos 2017-01-26 20:37:44 +01:00
parent 9e09c38766
commit 2aee9968c6
14 changed files with 117 additions and 44 deletions

View file

@ -35,11 +35,14 @@ Try the following:
Since the logic of the buttons is defined in the QML file, changes to Since the logic of the buttons is defined in the QML file, changes to
properties will be reflected immediately by the UI. properties will be reflected immediately by the UI.
Please see also "../qml-lisp/qml/example.lisp" for documentation on calling
Lisp functions from QML.
TIP TIP
=== ===
In order to have uniform access to QML objects from both JS and Lisp In order to have uniform access to QQuickItems from both QML and Lisp
functions, it is convenient to set both 'id:' and 'objectName:' to the functions, it is convenient to set both 'id:' and 'objectName:' to the
same name. same name.

View file

@ -13,9 +13,10 @@ QObject* ini() {
qmlRegisterSingletonType<Lisp>("EQL5", 1, 0, "EQL5", lisp_provider); } qmlRegisterSingletonType<Lisp>("EQL5", 1, 0, "EQL5", lisp_provider); }
return lisp; } return lisp; }
QVariant Lisp::apply(const QString& function, const QVariantList& arguments) { QVariant Lisp::apply(QObject* caller, const QString& function, const QVariantList& arguments) {
QVariant ret = QVariant ret =
eql_fun("qml:qml-apply", QVariant::String, eql_fun("qml:qml-apply", QVariant::String,
Q_ARG(QObject*, caller),
Q_ARG(QString, function), Q_ARG(QString, function),
Q_ARG(QVariantList, arguments)); Q_ARG(QVariantList, arguments));
return ret; } return ret; }

View file

@ -17,7 +17,7 @@ class Lisp : public QObject {
Q_OBJECT Q_OBJECT
public: public:
Q_INVOKABLE QVariant apply(const QString&, const QVariantList& = QVariantList()); Q_INVOKABLE QVariant apply(QObject*, const QString&, const QVariantList& = QVariantList());
}; };
QT_END_NAMESPACE QT_END_NAMESPACE

View file

@ -10,11 +10,13 @@
(:nicknames :qml) (:nicknames :qml)
(:export (:export
#:*quick-view* #:*quick-view*
#:*caller*
#:children #:children
#:find-quick-item #:find-quick-item
#:js #:js
#:qml-get #:qml-get
#:qml-set #:qml-set
#:properties
#:root-item)) #:root-item))
(provide :qml-lisp) (provide :qml-lisp)
@ -22,10 +24,11 @@
(in-package :qml-lisp) (in-package :qml-lisp)
(defvar *qml-lisp* (qload-c++ "lib/qml_lisp")) (defvar *qml-lisp* (qload-c++ "lib/qml_lisp"))
(defvar *caller* nil)
(defvar *quick-view* nil) (defvar *quick-view* nil)
(defun string-to-symbol (name) (defun string-to-symbol (name)
(let* ((upper (string-upcase name)) (let ((upper (string-upcase name))
(p (position #\: name))) (p (position #\: name)))
(if p (if p
(intern (subseq upper (1+ (position #\: name :from-end t))) (intern (subseq upper (1+ (position #\: name :from-end t)))
@ -63,9 +66,10 @@
(princ "#<>") ; mark for passing to JS "eval()" (princ "#<>") ; mark for passing to JS "eval()"
(print-js-readably object))) (print-js-readably object)))
(defun qml-apply (function arguments) (defun qml-apply (caller function arguments)
"Every 'Lisp.fun()' or 'Lisp.apply()' function call in QML will call this function." "Every 'Lisp.call()' or 'Lisp.apply()' function call in QML will call this function. The variable *CALLER* will be bound to the calling QQuickItem, if passed with 'this' as first argument to 'Lisp.call' / 'Lisp.apply()'."
(let ((object (apply (string-to-symbol function) (let* ((*caller* (if (qnull caller) nil (qt-object-? caller)))
(object (apply (string-to-symbol function)
arguments))) arguments)))
(if (stringp object) (if (stringp object)
object object
@ -74,13 +78,14 @@
;;; utils ;;; utils
(defun root-item () (defun root-item ()
(|rootObject| *quick-view*)) (when *quick-view*
(|rootObject| *quick-view*)))
(defun find-quick-item (object-name) (defun find-quick-item (object-name)
"Finds the first QQuickItem matching OBJECT-NAME." "Finds the first QQuickItem matching OBJECT-NAME."
(if (string= (|objectName| (root-item)) object-name) (if (string= (|objectName| (root-item)) object-name)
(root-item) (root-item)
(qfind-child (root-item) object-name))) (qt-object-? (qfind-child (root-item) object-name))))
(defun quick-item (item/name) (defun quick-item (item/name)
(if (stringp item/name) (if (stringp item/name)
@ -116,7 +121,7 @@
;;; JS ;;; JS
(defun js (item/name js-format-string &rest arguments) (defun js (item/name js-format-string &rest arguments)
"Evaluates a JS string, with 'this' bound to either ITEM, or first object matching NAME." "Evaluates a JS string, with 'this' bound to either ITEM, or first object matching NAME. Arguments are passed through FORMAT."
(qlet ((qml-exp "QQmlExpression(QQmlContext*,QObject*,QString)" (qlet ((qml-exp "QQmlExpression(QQmlContext*,QObject*,QString)"
(|rootContext| *quick-view*) (|rootContext| *quick-view*)
(quick-item item/name) (quick-item item/name)

View file

@ -38,7 +38,7 @@ Rectangle {
width: board.width / 3 width: board.width / 3
height: board.height / 3 height: board.height / 3
onClicked: { Lisp.fun("tic-tac-clicked", index) } onClicked: { Lisp.call("tic-tac-clicked", index) }
} }
} }
} }
@ -76,7 +76,7 @@ Rectangle {
Timer { Timer {
running: messageDisplay.visible running: messageDisplay.visible
onTriggered: { Lisp.fun("restart-game") } onTriggered: { Lisp.call("restart-game") }
} }
} }
} }

View file

@ -46,7 +46,7 @@ Please see also the documentation in "qml/example.qml".
TIP TIP
=== ===
In order to have uniform access to QML objects from both JS and Lisp In order to have uniform access to QQuickItems from both QML and Lisp
functions, it is convenient to set both 'id:' and 'objectName:' to the functions, it is convenient to set both 'id:' and 'objectName:' to the
same name. same name.

View file

@ -7,12 +7,21 @@
(require :qml-lisp "qml-lisp") (require :qml-lisp "qml-lisp")
(use-package :qml)
;; for example (5) in "qml/example.qml"
(defun show-properties-dialog ()
(unless (find-package :properties)
(load (in-home "gui/properties")))
(funcall (find-symbol "SHOW" :properties) qml:*caller*))
(defun run () (defun run ()
;; *quick-view* can be either a QQuickView or a QQuickWidget ;; *quick-view* can be either a QQuickView or a QQuickWidget
(setf qml:*quick-view* (qnew "QQuickView(QUrl)" (setf qml:*quick-view* (qnew "QQuickView(QUrl)"
(|fromLocalFile.QUrl| "qml/example.qml"))) (|fromLocalFile.QUrl| "qml/example.qml")))
(|setResizeMode| qml:*quick-view* |QQuickView.SizeRootObjectToView|) (|setResizeMode| qml:*quick-view* |QQuickView.SizeRootObjectToView|)
(|resize| qml:*quick-view* '(300 200)) (|resize| qml:*quick-view* '(350 350))
(|show| qml:*quick-view*)) (|show| qml:*quick-view*))
(run) (run)

View file

@ -13,9 +13,10 @@ QObject* ini() {
qmlRegisterSingletonType<Lisp>("EQL5", 1, 0, "EQL5", lisp_provider); } qmlRegisterSingletonType<Lisp>("EQL5", 1, 0, "EQL5", lisp_provider); }
return lisp; } return lisp; }
QVariant Lisp::apply(const QString& function, const QVariantList& arguments) { QVariant Lisp::apply(QObject* caller, const QString& function, const QVariantList& arguments) {
QVariant ret = QVariant ret =
eql_fun("qml:qml-apply", QVariant::String, eql_fun("qml:qml-apply", QVariant::String,
Q_ARG(QObject*, caller),
Q_ARG(QString, function), Q_ARG(QString, function),
Q_ARG(QVariantList, arguments)); Q_ARG(QVariantList, arguments));
return ret; } return ret; }

View file

@ -17,7 +17,7 @@ class Lisp : public QObject {
Q_OBJECT Q_OBJECT
public: public:
Q_INVOKABLE QVariant apply(const QString&, const QVariantList& = QVariantList()); Q_INVOKABLE QVariant apply(QObject*, const QString&, const QVariantList& = QVariantList());
}; };
QT_END_NAMESPACE QT_END_NAMESPACE

View file

@ -10,11 +10,13 @@
(:nicknames :qml) (:nicknames :qml)
(:export (:export
#:*quick-view* #:*quick-view*
#:*caller*
#:children #:children
#:find-quick-item #:find-quick-item
#:js #:js
#:qml-get #:qml-get
#:qml-set #:qml-set
#:properties
#:root-item)) #:root-item))
(provide :qml-lisp) (provide :qml-lisp)
@ -22,10 +24,11 @@
(in-package :qml-lisp) (in-package :qml-lisp)
(defvar *qml-lisp* (qload-c++ "lib/qml_lisp")) (defvar *qml-lisp* (qload-c++ "lib/qml_lisp"))
(defvar *caller* nil)
(defvar *quick-view* nil) (defvar *quick-view* nil)
(defun string-to-symbol (name) (defun string-to-symbol (name)
(let* ((upper (string-upcase name)) (let ((upper (string-upcase name))
(p (position #\: name))) (p (position #\: name)))
(if p (if p
(intern (subseq upper (1+ (position #\: name :from-end t))) (intern (subseq upper (1+ (position #\: name :from-end t)))
@ -63,9 +66,10 @@
(princ "#<>") ; mark for passing to JS "eval()" (princ "#<>") ; mark for passing to JS "eval()"
(print-js-readably object))) (print-js-readably object)))
(defun qml-apply (function arguments) (defun qml-apply (caller function arguments)
"Every 'Lisp.fun()' or 'Lisp.apply()' function call in QML will call this function." "Every 'Lisp.call()' or 'Lisp.apply()' function call in QML will call this function. The variable *CALLER* will be bound to the calling QQuickItem, if passed with 'this' as first argument to 'Lisp.call' / 'Lisp.apply()'."
(let ((object (apply (string-to-symbol function) (let* ((*caller* (if (qnull caller) nil (qt-object-? caller)))
(object (apply (string-to-symbol function)
arguments))) arguments)))
(if (stringp object) (if (stringp object)
object object
@ -74,13 +78,14 @@
;;; utils ;;; utils
(defun root-item () (defun root-item ()
(|rootObject| *quick-view*)) (when *quick-view*
(|rootObject| *quick-view*)))
(defun find-quick-item (object-name) (defun find-quick-item (object-name)
"Finds the first QQuickItem matching OBJECT-NAME." "Finds the first QQuickItem matching OBJECT-NAME."
(if (string= (|objectName| (root-item)) object-name) (if (string= (|objectName| (root-item)) object-name)
(root-item) (root-item)
(qfind-child (root-item) object-name))) (qt-object-? (qfind-child (root-item) object-name))))
(defun quick-item (item/name) (defun quick-item (item/name)
(if (stringp item/name) (if (stringp item/name)
@ -116,7 +121,7 @@
;;; JS ;;; JS
(defun js (item/name js-format-string &rest arguments) (defun js (item/name js-format-string &rest arguments)
"Evaluates a JS string, with 'this' bound to either ITEM, or first object matching NAME." "Evaluates a JS string, with 'this' bound to either ITEM, or first object matching NAME. Arguments are passed through FORMAT."
(qlet ((qml-exp "QQmlExpression(QQmlContext*,QObject*,QString)" (qlet ((qml-exp "QQmlExpression(QQmlContext*,QObject*,QString)"
(|rootContext| *quick-view*) (|rootContext| *quick-view*)
(quick-item item/name) (quick-item item/name)

View file

@ -11,32 +11,48 @@ Item {
// Please note: // Please note:
// //
// * to call lisp functions, use either Lisp.fun() or Lisp.apply(); // * to call lisp functions, use either Lisp.call() or Lisp.apply();
// use JS arrays for lists (can be nested); // use JS arrays for lists (can be nested);
// //
// * return values can be nested Lisp lists or vectors, which will be converted to // * return values can be nested Lisp lists or vectors, which will be converted to
// nested JS arrays: they will be prepared in Lisp and passed to JS eval; // nested JS arrays: they will be prepared in Lisp and passed to JS eval;
//
// * optionally pass 'this' (or any other item) as first argument to either
// Lisp.call() or Lisp.apply(); it can then be accessed in Lisp via qml:*caller*;
// (1) call CL function // (1) call CL function
console.log(Lisp.fun("format", false, "~R", 123)) console.log(Lisp.call("format", false, "~R", 123))
// (2) call EQL function // (2) call EQL function
Lisp.fun("qmsg", "hello from QML") Lisp.call("qmsg", "hello from QML")
// (3) pass list argument // (3) pass list argument
console.log(Lisp.fun("x:join", ["11", "55"], ":")) console.log(Lisp.call("x:join", ["11", "55"], ":"))
// (4) nested list arguments // (4) nested list arguments
// N.B: don't get fooled by the printed representation of the return value: // N.B: don't get fooled by the printed representation of the return value:
// it's a nested JS array internally // it's a nested JS array internally
console.log(Lisp.fun("list", [[1, 2, 3], ["a", "b", "c"], 4, 5], 6, [[7, 8], 9])) console.log(Lisp.call("list", [[1, 2, 3], ["a", "b", "c"], 4, 5], 6, [[7, 8], 9]))
// (5) pass 'this' as first argument (can be accessed in Lisp via qml:*caller*)
Lisp.call(this, "eql-user:show-properties-dialog")
} }
Text { Text {
id: label id: label
objectName: "label" objectName: "label"
anchors.centerIn: parent anchors.centerIn: parent
color: "blue" color: "blue"
text: "Lisp enabled QML" text: "Lisp enabled QML"
font.bold: true
font.pixelSize: 32
NumberAnimation on rotation {
from: 0; to: 360;
easing.type: Easing.InOutElastic;
duration: 3000;
loops: Animation.Infinite;
}
} }
} }

View file

@ -7,7 +7,7 @@
#include <QTimer> #include <QTimer>
#include <QStringList> #include <QStringList>
const char EQL::version[] = "17.1.8"; // Jan 2017 const char EQL::version[] = "17.1.9"; // Jan 2017
extern "C" void ini_EQL(cl_object); extern "C" void ini_EQL(cl_object);

View file

@ -1,4 +1,19 @@
// helper functions for convenient QML/EQL5 integration //
// Helper functions for convenient QML/EQL5 integration.
//
// Both 'Lisp.call()' and 'Lisp.apply()' can be called passing optionally
// 'this' as first argument.
//
// The 'this' argument (a QQuickItem) can be accessed in Lisp via qml:*caller*.
//
// Examples:
//
// Lisp.call("cl:foo", x, y)
// Lisp.call(this, "cl:foo", x, y)
//
// Lisp.apply("cl:foo", [x, y])
// Lisp.apply(this, "cl:foo", [x, y])
//
function checkEval(arg) { function checkEval(arg) {
// prepared in Lisp for JS evaluation // prepared in Lisp for JS evaluation
@ -6,13 +21,26 @@ function checkEval(arg) {
return eval(arg.substr(3)); } return eval(arg.substr(3)); }
return arg; } return arg; }
function fun() { function call() {
var name = arguments[0]; var arg1 = arguments[0];
var len = arguments.length - 1; if(arguments.length > 1) {
var arg2 = arguments[1];
var len = arguments.length - 2;
var args = new Array(len); var args = new Array(len);
for(var i = 0; i < len; i++) { for(var i = 0; i < len; i++) {
args[i] = arguments[i + 1]; } args[i] = arguments[i + 2]; }
return checkEval(EQL5.apply(name, args)); } return apply(arg1, arg2, args); }
return apply(arg1); }
function apply(name, args) { function apply(arg1, arg2, args) {
return checkEval(EQL5.apply(name, args)); } var caller, name;
if(typeof(arg1) == "object") { // 'this'
caller = arg1;
name = arg2; }
else {
name = arg1;
if(args === undefined) {
args = arg2; }
else {
args.unshift(arg2); }}
return checkEval(EQL5.apply(caller, name, args)); }

View file

@ -120,7 +120,12 @@ public:
static QByteArray vanillaQtSuperClassName(const QMetaObject* mo) { static QByteArray vanillaQtSuperClassName(const QMetaObject* mo) {
QByteArray className(mo->className()); QByteArray className(mo->className());
while(!q_names.contains(className)) { Q_FOREVER {
int p = className.indexOf('_');
if(p != -1) {
className.truncate(p); }
if(q_names.contains(className)) {
break; }
mo = mo->superClass(); mo = mo->superClass();
if(!mo) { if(!mo) {
break; } break; }