merge changes for integrating 'qml-lisp.lisp' into eql5 library

This commit is contained in:
polos 2021-03-13 23:21:41 +01:00
parent a4583e0cce
commit 3d48ea0211
7 changed files with 554 additions and 308 deletions

File diff suppressed because it is too large Load diff

View file

@ -15,8 +15,6 @@
QT_BEGIN_NAMESPACE
typedef cl_object (*cl_objectfn_fixed)();
#define QSLOT(x) "1"#x
#define QSIGNAL(x) "2"#x
@ -24,28 +22,28 @@ typedef cl_object (*cl_objectfn_fixed)();
static const int constant = qRegisterMetaType< type >(#type);
#define DEFUN(name, c_name, num_args) \
ecl_def_c_function(c_string_to_object((char*)name), (cl_objectfn_fixed)c_name, num_args);
ecl_def_c_function(ecl_read_from_cstring((char*)name), (cl_objectfn_fixed)c_name, num_args);
#define STRING(s) make_constant_base_string((char*)s)
#define STRING(s) ecl_make_constant_base_string((char*)s, -1)
#define STRING_COPY(s) (s ? make_base_string_copy((char*)s) : Cnil)
#define STRING_COPY(s) (s ? ecl_make_simple_base_string((char*)s, -1) : ECL_NIL)
#define PRINT(x) cl_print(1, x)
#define TERPRI() cl_terpri(0)
#define STATIC_SYMBOL(var, name) \
static cl_object var = cl_intern(1, make_constant_base_string((char*)name));
static cl_object var = cl_intern(1, ecl_make_constant_base_string((char*)name, -1));
#define STATIC_SYMBOL_PKG(var, name, pkg) \
static cl_object var = cl_intern(2, \
make_constant_base_string((char*)name), \
cl_find_package(make_constant_base_string((char*)pkg)));
ecl_make_constant_base_string((char*)name, -1), \
cl_find_package(ecl_make_constant_base_string((char*)pkg, -1)));
#define LEN(x) fixint(cl_length(x))
#define LIST1(a1) \
CONS(a1, Cnil)
CONS(a1, ECL_NIL)
#define LIST2(a1, a2) \
CONS(a1, LIST1(a2))
#define LIST3(a1, a2, a3) \
@ -99,7 +97,7 @@ static cap_name* to##cap_name##Pointer(cl_object x) { \
p = (cap_name*)o.pointer; } \
return p; } \
static cl_object from_##name(const cap_name& x) { \
cl_object l_ret = Cnil; \
cl_object l_ret = ECL_NIL; \
if(EQL::return_value_p) { \
l_ret = qt_object_from_name(#cap_name, new cap_name(x), 0, true); } \
else { \
@ -108,7 +106,7 @@ static cl_object from_##name(const cap_name& x) { \
#define FROM_QT_TYPE_ONLY(cap_name, name) \
static cl_object from_##name(const cap_name& x) { \
cl_object l_ret = Cnil; \
cl_object l_ret = ECL_NIL; \
if(EQL::return_value_p) { \
l_ret = qt_object_from_name(#cap_name, new cap_name(x), 0, true); } \
else { \
@ -125,7 +123,7 @@ static cap_name to##cap_name(cl_object l_x) { \
#define TO_CL_TYPE(cap_name, name, x1, x2) \
static cl_object from_##name(const cap_name& q) { \
cl_object l_ret = LIST2(MAKE_FIXNUM(q.x1()), MAKE_FIXNUM(q.x2())); \
cl_object l_ret = LIST2(ecl_make_fixnum(q.x1()), ecl_make_fixnum(q.x2())); \
return l_ret; }
#define TO_CL_TYPEF(cap_name, name, x1, x2) \
@ -136,7 +134,7 @@ static cap_name to##cap_name(cl_object l_x) { \
#define TO_CL_TYPE2(cap_name, name, x1, x2, x3, x4) \
static cl_object from_##name(const cap_name& q) { \
cl_object l_ret = LIST4(MAKE_FIXNUM(q.x1()), MAKE_FIXNUM(q.x2()), MAKE_FIXNUM(q.x3()), MAKE_FIXNUM(q.x4())); \
cl_object l_ret = LIST4(ecl_make_fixnum(q.x1()), ecl_make_fixnum(q.x2()), ecl_make_fixnum(q.x3()), ecl_make_fixnum(q.x4())); \
return l_ret; }
#define TO_CL_TYPEF2(cap_name, name, x1, x2, x3, x4) \
@ -147,7 +145,7 @@ static cap_name to##cap_name(cl_object l_x) { \
#define TO_CL_LIST_PTR(cap_type, type) \
static cl_object from_##type##list(const QList<cap_type*>& l) { \
cl_object l_list = Cnil; \
cl_object l_list = ECL_NIL; \
Q_FOREACH(cap_type* x, l) { \
l_list = CONS(qt_object_from_name(#cap_type, x), l_list); } \
l_list = cl_nreverse(l_list); \
@ -155,7 +153,7 @@ static cap_name to##cap_name(cl_object l_x) { \
#define TO_CL_LIST_VAL(cap_type, type) \
static cl_object from_##type##list(const QList<cap_type>& l) { \
cl_object l_list = Cnil; \
cl_object l_list = ECL_NIL; \
Q_FOREACH(cap_type x, l) { \
l_list = CONS(from_##type(x), l_list); } \
l_list = cl_nreverse(l_list); \
@ -163,7 +161,7 @@ static cap_name to##cap_name(cl_object l_x) { \
#define TO_CL_LIST_VAL2(cap_type, fun) \
static cl_object from_##type##list(const QList<cap_type*>& l) { \
cl_object l_list = Cnil; \
cl_object l_list = ECL_NIL; \
Q_FOREACH(cap_type* x, l) { \
l_list = CONS(fun(*x), l_list); } \
l_list = cl_nreverse(l_list); \
@ -174,7 +172,7 @@ static cap_name to##cap_name(cl_object l_x) { \
QList<type*> l; \
if(LISTP(l_list)) { \
cl_object l_el = l_list; \
while(l_el != Cnil) { \
while(l_el != ECL_NIL) { \
l << (type*)toQtObject(cl_car(l_el)).pointer; \
l_el = cl_cdr(l_el); }} \
return l; }
@ -184,7 +182,7 @@ static cap_name to##cap_name(cl_object l_x) { \
QList<type> l; \
if(LISTP(l_list)) { \
cl_object l_el = l_list; \
while(l_el != Cnil) { \
while(l_el != ECL_NIL) { \
l << to##type(cl_car(l_el)); \
l_el = cl_cdr(l_el); }} \
return l; }
@ -194,7 +192,7 @@ static cap_name to##cap_name(cl_object l_x) { \
QList<type> l; \
if(LISTP(l_list)) { \
cl_object l_el = l_list; \
while(l_el != Cnil) { \
while(l_el != ECL_NIL) { \
l << to##fun(cl_car(l_el)); \
l_el = cl_cdr(l_el); }} \
return l; }
@ -202,17 +200,17 @@ static cap_name to##cap_name(cl_object l_x) { \
#define TO_QT_VECTOR_VAL(type) \
static QVector<type> to##type##Vector(cl_object l_v) { \
QVector<type> v; \
if(cl_simple_vector_p(l_v) == Ct) { \
if(cl_simple_vector_p(l_v) == ECL_T) { \
for(int i = 0; i < LEN(l_v); ++i) { \
v.append(to##type(cl_svref(l_v, MAKE_FIXNUM(i)))); }} \
v.append(to##type(cl_svref(l_v, ecl_make_fixnum(i)))); }} \
return v; }
#define TO_QT_VECTOR_VAL2(type, fun) \
static QVector<type> to##type##Vector(cl_object l_v) { \
QVector<type> v; \
if(cl_simple_vector_p(l_v) == Ct) { \
if(cl_simple_vector_p(l_v) == ECL_T) { \
for(int i = 0; i < LEN(l_v); ++i) { \
v.append(to##fun(cl_svref(l_v, MAKE_FIXNUM(i)))); }} \
v.append(to##fun(cl_svref(l_v, ecl_make_fixnum(i)))); }} \
return v; }
#define TO_CL_VECTOR_VAL(cap_type, type) \
@ -267,6 +265,8 @@ cl_object qload_ui (cl_object);
cl_object qlocal8bit (cl_object);
cl_object qlog2 (cl_object);
cl_object qmeta_enums ();
cl_object qml_get2 (cl_object, cl_object);
cl_object qml_set2 (cl_object, cl_object, cl_object);
cl_object qnew_instance2 (cl_object, cl_object);
cl_object qobject_names2 (cl_object);
cl_object qok ();
@ -340,7 +340,7 @@ cl_object to_lisp_arg(const MetaArg&);
EQL_EXPORT QVariant callOverrideFun(void*, int, const void**, quint64);
EQL_EXPORT cl_object qt_object_from_name(const QByteArray&, void*, uint = 0, bool = false);
EQL_EXPORT QtObject toQtObject(cl_object, cl_object = Cnil, bool* = 0, bool = false);
EQL_EXPORT QtObject toQtObject(cl_object, cl_object = ECL_NIL, bool* = 0, bool = false);
QT_END_NAMESPACE

View file

@ -9,7 +9,7 @@
#include <QStringList>
#include <QDebug>
const char EQL::version[] = "21.3.1"; // March 2021
const char EQL::version[] = "21.3.2"; // March 2021
extern "C" void ini_EQL(cl_object);
@ -45,7 +45,7 @@ EQL::EQL() : QObject() {
cl_boot(1, (char**)_argv_); }
iniCLFunctions();
LObjects::ini(this);
read_VV(OBJNULL, ini_EQL); } // see "src/make.lisp"
ecl_init_module(NULL, ini_EQL); } // see "src/make.lisp"
void EQL::ini(char** argv) {
cl_booted = true;
@ -93,7 +93,7 @@ void EQL::ignoreIOStreams() {
eval("(eql::ignore-io-streams)"); }
void EQL::exec(const QStringList& args) {
cl_object s_qtpl = cl_intern(1, make_constant_base_string("*QTPL*"));
cl_object s_qtpl = cl_intern(1, ecl_make_constant_base_string("*QTPL*", -1));
bool exec_with_simple_restart = false;
QStringList arguments(args);
eval("(in-package :eql-user)");
@ -126,10 +126,10 @@ void EQL::exec(const QStringList& args) {
arguments << swankFile; }
exec_with_simple_restart = true; }
// -qtpl
else if(arguments.contains("-qtpl") || (cl_symbol_value(s_qtpl) == Ct)) {
else if(arguments.contains("-qtpl") || (cl_symbol_value(s_qtpl) == ECL_T)) {
arguments.removeAll("-qtpl");
evalMode = DebugOnError;
ecl_setq(ecl_process_env(), s_qtpl, Ct);
ecl_setq(ecl_process_env(), s_qtpl, ECL_T);
QApplication::setQuitOnLastWindowClosed(false);
forms << "(when (directory (in-home \"lib/ecl-readline.fas*\"))"
" (load (x:check-recompile (in-home \"lib/ecl-readline\"))))"
@ -184,7 +184,7 @@ void EQL::exec(const QStringList& args) {
void EQL::exec(lisp_ini ini, const QByteArray& expression, const QByteArray& package) {
// see my_app example
read_VV(OBJNULL, ini);
ecl_init_module(NULL, ini);
eval(QString("(in-package :%1)").arg(QString(package)).toLatin1().constData());
eval(expression.constData()); }

View file

@ -17,6 +17,10 @@
(compile-file (format nil "lisp/~A.lisp" file)
:system-p t))
(progn
(compile-file "lisp/qml" :system-p t)
(setf *lisp-files* (append *lisp-files* (list "qml"))))
(let ((lib-name "ios-libs/ini_eql5"))
(ensure-directories-exist lib-name)
(c:build-static-library lib-name

View file

@ -58,6 +58,7 @@
#:qlocal8bit
#:qlog
#:qmessage-box
#:qml
#:qmsg
#:qnew
#:qnew-instance
@ -115,5 +116,33 @@
(defpackage :eql-user
(:use :common-lisp :eql))
(defpackage :qml-lisp
(:use :common-lisp :eql)
(:nicknames :qml)
(:export
#:*quick-view*
#:*root*
#:*caller*
#:children
#:file-to-url
#:find-quick-item
#:ini-quick-view
#:js
#:js-arg
#:qml-call
#:qml-get
#:qml-set
#:qml-set-all
#:q!
#:q<
#:q>
#:q>*
#:qjs
#:paint
#:scale
#:reload
#:root-context
#:root-item))
(pushnew :eql *features*)
(pushnew :eql5 *features*)

211
src/lisp/qml.lisp Normal file
View file

@ -0,0 +1,211 @@
;;;
;;; * enables QML to call Lisp functions
;;; * allows to get/set any QML property from Lisp (needs 'objectName' to be set)
;;; * allows to call QML methods from Lisp (needs 'objectName' to be set)
;;; * allows to evaluate JS code from Lisp (needs 'objectName' to be set)
;;;
(in-package :qml-lisp)
(defvar *quick-view* nil)
(defvar *caller* nil)
(defvar *root* nil)
(defun string-to-symbol (name)
(let ((upper (string-upcase name))
(p (position #\: name)))
(if p
(find-symbol (subseq upper (1+ (position #\: name :from-end t)))
(subseq upper 0 p))
(find-symbol upper))))
;;; function calls from QML
(defun print-js-readably (object)
"Prints (nested) lists, vectors, T, NIL, floats in JS notation, which will be passed to JS 'eval()'."
(if (and (not (stringp object))
(vectorp object))
(print-js-readably (coerce object 'list))
(typecase object
(cons
(write-char #\[)
(do ((list object (rest list)))
((null list) (write-char #\]))
(print-js-readably (first list))
(when (rest list)
(write-char #\,))))
(float
;; JS can't read 'd0' 'l0'
(let ((str (princ-to-string object)))
(x:when-it (position-if (lambda (ch) (find ch "dl")) str)
(setf (char str x:it) #\e))
(princ str)))
(t
(cond ((eql 't object)
(princ "true"))
((eql 'nil object)
(princ "false"))
(t
(prin1 object)))))))
(defun print-to-js-string (object)
(with-output-to-string (*standard-output*)
(princ "#<>") ; mark for passing to JS "eval()"
(print-js-readably object)))
(defun qml-apply (caller function arguments)
"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* ((*caller* (if (qnull caller) *caller* (qt-object-? caller)))
(object (apply (string-to-symbol function)
arguments)))
(if (stringp object)
object
(print-to-js-string object))))
;;; utils
(defun root-item ()
(when *quick-view*
(qrun* (if (= (qt-object-id *quick-view*) (qid "QQmlApplicationEngine"))
(let ((object (first (|rootObjects| *quick-view*))))
(setf (qt-object-id object) (qid "QObject")) ; unknown to EQL, so resort to QObject
object)
(qt-object-? (|rootObject| *quick-view*))))))
(defun root-context ()
(when *quick-view*
(|rootContext| *quick-view*)))
(defun find-quick-item (object-name)
"Finds the first QQuickItem matching OBJECT-NAME."
(let ((root (root-item)))
(unless (qnull root)
(if (string= (|objectName| root) object-name)
(root-item)
(qt-object-? (qfind-child root object-name))))))
(defun quick-item (item/name)
(cond ((stringp item/name)
(find-quick-item item/name))
((qt-object-p item/name)
item/name)
((not item/name)
(root-item))))
(defun children (item/name)
"Like QML function 'children'."
(qrun* (mapcar 'qt-object-? (|childItems| (quick-item item/name)))))
(defun scale ()
"Returns the scale factor used on high dpi scaled devices (e.g. phones)."
(|effectiveDevicePixelRatio| *quick-view*))
(defun reload ()
"Force reloading of QML file after changes made to it."
(|clearComponentCache| (|engine| *quick-view*))
(|setSource| *quick-view* (|source| *quick-view*)))
(defun file-to-url (file)
"Convert FILE to a QUrl, distinguishing between development and release version."
(if (probe-file file)
(|fromLocalFile.QUrl| file)
(qnew "QUrl(QString)"
(x:cc (or *root* "qrc:/") file)))) ; see "Qt Resource System"
;;; call QML methods
(defun qml-call (item/name method-name &rest arguments)
;; QFUN+ comes in handy here
(qrun* (apply 'qfun+ (quick-item item/name) method-name arguments)))
;;; get/set QQmlProperty
(defun qml-get (item/name property-name)
"Gets QQmlProperty of either ITEM or first object matching NAME."
(qrun* (eql::%qml-get (quick-item item/name) property-name)))
(defun qml-set (item/name property-name value &optional update)
"Sets QQmlProperty of either ITEM, or first object matching NAME. Returns T on success. If UPDATE is not NIL and ITEM is a QQuickPaintedItem, |update| will be called on it."
(qrun* (prog1
(eql::%qml-set (quick-item item/name) property-name value)
(when (and update (= (qt-object-id item) (qid "QQuickPaintedItem")))
(|update| item)))))
(defun qml-set-all (name property-name value &optional update)
"Sets QQmlProperty of all objects matching NAME."
(assert (stringp name))
(qrun* (dolist (item (qfind-children (root-item) name))
(qml-set item property-name value update))))
(defmacro q! (method-name item/name &rest arguments)
"Convenience macro for QML-CALL. Use symbol instead of string name."
`(qml-call ,item/name ,(symbol-name method-name) ,@arguments))
(defmacro q> (property-name item/name value &optional update)
"Convenience macro for QML-SET. Use symbol instead of string name."
`(qml-set ,item/name ,(symbol-name property-name) ,value ,update))
(defmacro q< (property-name item/name)
"Convenience macro for QML-GET. Use symbol instead of string name."
`(qml-get ,item/name ,(symbol-name property-name)))
(defmacro q>* (property-name item/name value &optional update)
"Convenience macro for QML-SET-ALL. Use symbol instead of string name."
`(qml-set-all ,item/name ,(symbol-name property-name) ,value ,update))
;;; JS
(defun js (item/name js-format-string &rest arguments)
"Evaluates a JS string, with 'this' bound to either ITEM, or first object matching NAME. Arguments are passed through FORMAT. Use this function instead of the (faster) QJS if you need to evaluate generic JS code."
(qrun* (qlet ((qml-exp "QQmlExpression(QQmlContext*,QObject*,QString)"
(root-context)
(quick-item item/name)
(apply 'format nil js-format-string (mapcar 'js-arg arguments)))
(variant (|evaluate| qml-exp)))
(qvariant-value variant))))
(defun js-arg (object)
"Used for arguments in function JS."
(if (stringp object)
object
(with-output-to-string (*standard-output*)
(print-js-readably object))))
(defun %qjs (item/name function-name &rest arguments)
;; QJS-CALL is defined in EQL5, function 'ecl_fun.cpp'
(qrun* (eql::qjs-call (quick-item item/name) function-name arguments)))
(defmacro qjs (function-name item/name &rest arguments)
"Fast and direct JS calls; max 10 arguments of type: T, NIL, INTEGER, FLOAT, STRING, (nested) LIST of mentioned types.
Examples:
(qjs |drawLine| *canvas* 0 0 100.0 100.0)
(qjs |addItems| *model* (list (list \"Frank\" 42) (list \"Susan\" 40)))"
`(%qjs ,item/name ,(symbol-name function-name) ,@arguments))
;;; ini
(defun ini-quick-view (file &optional widget)
(setf *quick-view* (qnew (if widget
"QQuickWidget" ; needed for 'PaintedItem'
"QQuickView")))
;; special settings for mobile, taken from Qt example
(let ((env (ext:getenv "QT_QUICK_CORE_PROFILE")))
(when (and (stringp env)
(not (zerop (parse-integer env :junk-allowed t))))
(let ((f (|format| *quick-view*)))
(|setProfile| f |QSurfaceFormat.CoreProfile|)
(|setVersion| f 4 4)
(|setFormat| *quick-view* f))))
(qconnect (|engine| *quick-view*) "quit()" 'qquit)
(qnew "QQmlFileSelector(QQmlEngine*,QObject*)" (|engine| *quick-view*) *quick-view*)
(|setSource| *quick-view* (file-to-url file))
(when (= |QQuickView.Error| (|status| *quick-view*))
;; display eventual QML errors
(qmsg (x:join (mapcar '|toString| (|errors| *quick-view*))
#.(make-string 2 :initial-element #\Newline))))
(|setResizeMode| *quick-view* |QQuickView.SizeRootObjectToView|)
(let ((platform (|platformName.QGuiApplication|)))
(if (find platform '("qnx" "eglfs") :test 'string=)
(|showFullScreen| *quick-view*)
(|show| *quick-view*))))

View file

@ -39,6 +39,10 @@
(dolist (file *all-wrappers*)
(compile-file (format nil "lisp/~A.lisp" file) :system-p t))
(progn
(compile-file "lisp/qml" :system-p t)
(setf *lisp-files* (append *lisp-files* (list "qml"))))
;; lib
(c:build-static-library "ini_eql5"