#include "ecl_ext.h" #include "marshal.h" #include "lqml.h" #include "single_shot.h" #include #include #include #include #include #include #include #include #include QT_BEGIN_NAMESPACE void iniCLFunctions() { cl_object qml(STRING("QML")); if (cl_find_package(qml) == ECL_NIL) { cl_make_package(1, qml); } si_select_package(qml); DEFUN ("%js", js2, 2) DEFUN ("pixel-ratio", pixel_ratio, 0) DEFUN ("%qapropos", qapropos2, 3) DEFUN ("qchildren", qchildren, 1) DEFUN ("qescape", qescape, 1) DEFUN ("%qexec", qexec2, 1) DEFUN ("qexit", qexit, 0) DEFUN ("qfind-child", qfind_child, 2) DEFUN ("%qfind-children", qfind_children2, 3) DEFUN ("qfrom-utf8", qfrom_utf8, 1) DEFUN ("%qinvoke-method", qinvoke_method2, 3) DEFUN ("%qload-c++", qload_cpp, 2) DEFUN ("%qlog", qlog2, 1) DEFUN ("%qml-get", qml_get2, 2) DEFUN ("%qml-set", qml_set2, 3) DEFUN ("qobject-name", qobject_name, 1) DEFUN ("qprocess-events", qprocess_events, 0) DEFUN ("%qquit", qquit2, 1) DEFUN ("%qrun-on-ui-thread", qrun_on_ui_thread2, 2) DEFUN ("%qget", qget2, 2) DEFUN ("%qset", qset2, 2) DEFUN ("%qsingle-shot", qsingle_shot2, 2) DEFUN ("qtranslate", qtranslate, 3) DEFUN ("qversion", qversion, 0) DEFUN ("qt-object-info", qt_object_info, 1) DEFUN ("%reload", reload2, 0) DEFUN ("root-item", root_item, 0) DEFUN ("%set-shutdown-p", set_shutdown_p, 1) } // *** utils *** void error_msg(const char* fun, cl_object l_args) { // for error messages in ECL functions defined in C++ STATIC_SYMBOL_PKG (s_break_on_errors, "*BREAK-ON-ERRORS*", "QML") if (cl_symbol_value(s_break_on_errors) != ECL_NIL) { STATIC_SYMBOL_PKG (s_break, "%BREAK", "QML") // see "ini.lisp" cl_funcall(4, s_break, STRING("~%[LQML:error] ~A ~{~S~^ ~}~%"), STRING(fun), l_args); } else { STATIC_SYMBOL (s_error_output, "*ERROR-OUTPUT*") cl_format(4, cl_symbol_value(s_error_output), STRING("~%[LQML:error] ~A ~{~S~^ ~}~%"), STRING(fun), l_args); } } // *** main functions *** cl_object set_shutdown_p(cl_object l_obj) { // for internal use LQML::cl_shutdown_p = (l_obj != ECL_NIL); ecl_return1(ecl_process_env(), l_obj); } cl_object qget2(cl_object l_obj, cl_object l_name) { /// args: (qt-object name) /// Gets a Qt property. Enumerator values are returned as integer values. /// Returns T as second return value for successful calls. /// (qget *quick-view* |width|) QObject* qobject = toQObjectPointer(l_obj); if (ECL_STRINGP(l_name) && (qobject != nullptr)) { const QMetaObject* mo = qobject->metaObject(); int n = mo->indexOfProperty(toCString(l_name)); if (n != -1) { QMetaProperty mp(mo->property(n)); QVariant var(mp.read(qobject)); cl_object l_ret1 = from_qvariant(var); ecl_return2(ecl_process_env(), l_ret1, ECL_T); } } ecl_process_env()->nvalues = 1; error_msg("QGET", LIST2(l_obj, l_name)); ecl_return1(ecl_process_env(), ECL_NIL); } cl_object qset2(cl_object l_obj, cl_object l_args) { /// args: (qt-object name1 value1 &optional name2 value2...) /// Sets a Qt property. Enumerators have to be passed as integer values. /// Returns T as second return value for successful calls. /// (qset *quick-view* |x| 100 |y| 100) QObject* qobject = toQObjectPointer(l_obj); if (qobject != nullptr) { const QMetaObject* mo = qobject->metaObject(); for (cl_object l_do = l_args; l_do != ECL_NIL; l_do = cl_cddr(l_do)) { cl_object l_name = cl_first(l_do); cl_object l_val = cl_second(l_do); int n = mo->indexOfProperty(toCString(l_name)); if (n == -1) { goto fail; } QMetaProperty mp(mo->property(n)); QVariant var; if (mp.isEnumType()) { var = toInt(l_val); } else { #if QT_VERSION < 0x060000 var = toQVariant(l_val, mp.type()); #else var = toQVariant(l_val, mp.typeId()); #endif } if (!mp.write(qobject, var)) { goto fail; } } ecl_return2(ecl_process_env(), l_args, ECL_T); } fail: error_msg("QSET", LIST2(l_obj, l_args)); ecl_return1(ecl_process_env(), ECL_NIL); } cl_object qfind_child(cl_object l_obj, cl_object l_name) { /// args: (qt-object name) /// Calls QObject::findChild(). ecl_process_env()->nvalues = 1; QString name(toQString(l_name)); if (!name.isEmpty()) { QObject* qobject = toQObjectPointer(l_obj); if (qobject != nullptr) { QObject* obj = qobject->findChild(name); if (obj != nullptr) { cl_object l_ret = from_qobject_pointer(obj); return l_ret; } } } error_msg("QFIND-CHILD", LIST2(l_obj, l_name)); return ECL_NIL; } cl_object qfind_children2(cl_object l_obj, cl_object l_name, cl_object l_class) { // for internal use ecl_process_env()->nvalues = 1; QString objectName(toQString(l_name)); QByteArray className(toCString(l_class)); QObject* qobject = toQObjectPointer(l_obj); if (qobject != nullptr) { QObjectList children = qobject->findChildren(objectName); cl_object l_children = ECL_NIL; Q_FOREACH(QObject* child, children) { QByteArray className2(child->metaObject()->className()); if (className.isEmpty() || (className == className2)) { l_children = CONS(from_qobject_pointer(child), l_children); } } l_children = cl_nreverse(l_children); return l_children; } error_msg("QFIND-CHILDREN", LIST3(l_obj, l_name, l_class)); return ECL_NIL; } cl_object qchildren(cl_object l_item) { /// args: (item/name) /// Like QML function children(). ecl_process_env()->nvalues = 1; QObject* qobject = toQObjectPointer(l_item); QQuickItem* item = qobject_cast(qobject); // type check if (item != nullptr) { QList children = item->childItems(); cl_object l_children = ECL_NIL; Q_FOREACH(QQuickItem* child, children) { l_children = CONS(from_qobject_pointer(child), l_children); } l_children = cl_nreverse(l_children); return l_children; } error_msg("QCHILDREN", LIST1(l_item)); return ECL_NIL; } cl_object qload_cpp(cl_object l_lib_name, cl_object l_unload) { /// qload-c++ /// args: (library-name &optional unload) /// Loads a custom Qt/C++ plugin (see 'cpp-lib' in sources). The LIBRARY-NAME /// has to be passed as path to the plugin, without file ending. This offers /// a simple way to extend your application with your own Qt/C++ functions. /// The plugin will be reloaded (if supported by the OS) every time you call /// this function. If the UNLOAD argument is not NIL, the plugin will be /// unloaded (if supported by the OS). /// N.B: This works only for Qt functions with the following signature: /// "QVariant foo(QVariant, ...)" ; max 10 QVariant arguments /// Since a QVariant can also be of type QVariantList, this is a perfect fit /// for (nested) Lisp lists. /// (defparameter *c++* (qload-c++ "my-lib")) /// (qapropos nil *c++*) ; documentation /// (define-qt-wrappers *c++*) ; Lisp wrapper functions static QHash libraries; QString libName = toQString(l_lib_name); bool unload = (l_unload != ECL_NIL); if (!libName.isEmpty()) { if (!libName.contains('/')) { libName.prepend("./"); } QLibrary* lib = libraries.value(libName, 0); if (lib) { if (lib->isLoaded()) { lib->unload(); // for both unload/reload if (!unload) { cl_sleep(ecl_make_singlefloat(0.5)); } } } if (unload) { ecl_process_env()->nvalues = 1; if (lib) { delete lib; libraries.remove(libName); return l_lib_name; } return ECL_NIL; } if (!lib) { lib = new QLibrary(libName); libraries[libName] = lib; } typedef QObject* (*Ini)(); Ini ini = (Ini)lib->resolve("ini"); if (ini) { QObject* main = ini(); if (main) { cl_object l_ret = from_qobject_pointer(main); ecl_return1(ecl_process_env(), l_ret); } } } error_msg("QLOAD-C++", LIST2(l_lib_name, l_unload)); ecl_return1(ecl_process_env(), ECL_NIL); } // *** convenience functions *** cl_object qtranslate(cl_object l_con, cl_object l_src, cl_object l_n) { // called by QML:TR QByteArray context(toQString(l_con).toUtf8()); QByteArray source(toQString(l_src).toUtf8()); int n = toInt(l_n); cl_object l_ret; if (n == -1) { l_ret = from_qstring(QCoreApplication::translate(context, source)); } else { l_ret = from_qstring(QCoreApplication::translate(context, source, 0, n)); } ecl_return1(ecl_process_env(), l_ret); } cl_object qfrom_utf8(cl_object l_ba) { /// args: (byte-array) /// Returns the BYTE-ARRAY (vector of octets) converted using /// QString::fromUtf8(). cl_object l_ret = from_qstring(QString::fromUtf8(toQByteArray(l_ba))); ecl_return1(ecl_process_env(), l_ret); } cl_object qescape(cl_object l_str) { /// args: (string) /// Calls QString::toHtmlEscaped(). cl_object l_ret = from_qstring(toQString(l_str).toHtmlEscaped()); ecl_return1(ecl_process_env(), l_ret); } cl_object qprocess_events() { /// args: () /// Calls QCoreApplication::processEvents(). QCoreApplication::processEvents(); ecl_return1(ecl_process_env(), ECL_T); } cl_object qexec2(cl_object l_milliseconds) { /// args: (&optional milliseconds) /// Calls QCoreApplication::exec(). Optionally pass the time in milliseconds /// after which QEventLoop::exit() will be called. See also QSLEEP. ecl_process_env()->nvalues = 1; if (l_milliseconds != ECL_NIL) { static QTimer* timer = 0; if (!timer) { timer = new QTimer; LQML::eventLoop = new QEventLoop; timer->setSingleShot(true); QObject::connect(timer, &QTimer::timeout, LQML::me, &LQML::exitEventLoop); } timer->start(toInt(l_milliseconds)); LQML::eventLoop->exec(); return l_milliseconds; } QCoreApplication::exit(); // prevent "the event loop is already running" QCoreApplication::exec(); return ECL_T; } cl_object qexit() { /// args: () /// Calls QEventLoop::exit(), in order to exit event processing after a call /// QEXEC with a timeout. Returns T if the event loop has effectively been /// exited. ecl_process_env()->nvalues = 1; if (LQML::eventLoop) { if (LQML::eventLoop->isRunning()) { LQML::eventLoop->exit(); return ECL_T; } } return ECL_NIL; } cl_object qsingle_shot2(cl_object l_msec, cl_object l_fun) { /// args: (milliseconds function) /// A single shot timer similar to QTimer::singleShot(). /// (qsingle-shot 1000 'one-second-later) ecl_process_env()->nvalues = 1; if (l_fun != ECL_NIL) { new SingleShot(toInt(l_msec), l_fun); // see 'deleteLater()' in sources return l_msec; } error_msg("QSINGLE-SHOT", LIST2(l_msec, l_fun)); return ECL_NIL; } cl_object qversion() { /// args: () /// Returns the LQML version number as 'year.month.counter'. The second /// return value is the Qt version as returned by QLibraryInfo::version(). cl_object l_ret1 = from_cstring(LQML::version); cl_object l_ret2 = from_qstring(QLibraryInfo::version().toString()); ecl_return2(ecl_process_env(), l_ret1, l_ret2); } cl_object qrun_on_ui_thread2(cl_object l_function_or_closure, cl_object l_blocking) { // for internal use, you should never need to call it explicitely ecl_process_env()->nvalues = 1; if (l_function_or_closure != ECL_NIL) { QObject o; if (o.thread() == qGuiApp->thread()) { // direct call LQML::me->runOnUiThread(l_function_or_closure); return ECL_T; } else { // queued call in main event loop (GUI thread) QMetaObject::invokeMethod(LQML::me, "runOnUiThread", (l_blocking != ECL_NIL) ? Qt::BlockingQueuedConnection : Qt::QueuedConnection, Q_ARG(void*, l_function_or_closure)); return ECL_T; } } error_msg("QRUN-ON-UI-THREAD", LIST1(l_function_or_closure)); return ECL_NIL; } cl_object qlog2(cl_object l_msg) { // called by QML:QLOG // for android logging only; see also 'lqml.cpp::logMessageHandler' qDebug() << toQString(l_msg); ecl_return1(ecl_process_env(), ECL_NIL); } cl_object qinvoke_method2(cl_object l_obj, cl_object l_name, cl_object l_args) { // for internal use: this is used to call user defined JS functions, and to // call user defined Qt/C++ plugin functions. // Max. 10 arguments of type T, NIL, INTEGER, FLOAT, STRING, VECTOR of // octets, (nested) LIST of mentioned arguments. On Qt side, only QVariant // arguments are allowed. // N.B. does not support default arguments, if used to call JS functions ecl_process_env()->nvalues = 1; const int MAX = 10; QVariant arg[MAX]; QGenericArgument genA[MAX]; const char* v = "QVariant"; int i = 0; for (cl_object l_do_list = l_args; l_do_list != ECL_NIL; l_do_list = cl_cdr(l_do_list), i++) { cl_object l_el = cl_car(l_do_list); arg[i] = toQVariant(l_el); genA[i] = QGenericArgument(v, &arg[i]); } QGenericArgument null; for (; i < MAX; i++) { genA[i] = null; } QObject* qobject = toQObjectPointer(l_obj); QByteArray name(toCString(l_name)); if ((qobject != nullptr) && !name.isEmpty()) { QVariant ret; QGenericReturnArgument genR(v, &ret); QMetaObject::invokeMethod(qobject, name, genR, genA[0], genA[1], genA[2], genA[3], genA[4], genA[5], genA[6], genA[7], genA[8], genA[9]); cl_object l_ret = from_qvariant(ret); return l_ret; } error_msg("QJS", LIST3(l_obj, l_name, l_args)); return ECL_NIL; } cl_object js2(cl_object l_item, cl_object l_str) { // called by function QML:JS ecl_process_env()->nvalues = 1; QObject* qobject = toQObjectPointer(l_item); if (qobject != nullptr) { QQmlExpression exp(LQML::quickView->rootContext(), qobject, toQString(l_str)); cl_object l_ret = from_qvariant(exp.evaluate()); return l_ret; } error_msg("JS", LIST2(l_item, l_str)); return ECL_NIL; } cl_object qml_get2(cl_object l_item, cl_object l_name) { // called by QML:QML-GET QObject* qobject = toQObjectPointer(l_item); QByteArray name = toCString(l_name); if ((qobject != nullptr) && !name.isEmpty()) { QQmlProperty property(qobject, name); if (property.isValid()) { cl_object l_val = from_qvariant(property.read()); ecl_return2(ecl_process_env(), l_val, ECL_T); } } error_msg("QML-GET", LIST2(l_item, l_name)); ecl_return1(ecl_process_env(), ECL_NIL); } cl_object qml_set2(cl_object l_item, cl_object l_name, cl_object l_value) { // called by QML:QML-SET ecl_process_env()->nvalues = 1; QObject* qobject = toQObjectPointer(l_item); QByteArray name = toCString(l_name); if ((qobject != nullptr) && !name.isEmpty()) { QQmlProperty property(qobject, name); if (property.isValid()) { cl_object l_ret = property.write(toQVariant(l_value, property.propertyType())) ? ECL_T : ECL_NIL; return l_ret; } } error_msg("QML-SET", LIST3(l_item, l_name, l_value)); return ECL_NIL; } cl_object qobject_name(cl_object l_obj) { /// args: (qt-object) /// Returns the QObject::objectName() of passed QT-OBJECT. ecl_process_env()->nvalues = 1; QObject* qobject = toQObjectPointer(l_obj); if (qobject != nullptr) { cl_object l_ret = from_qstring(qobject->objectName()); return l_ret; } error_msg("QOBJECT-NAME", LIST1(l_obj)); return ECL_NIL; } cl_object qt_object_info(cl_object l_obj) { // for internal use QString className("?"); QString objectName(""); quintptr address = 0; QObject* qobject = toQObjectPointer(l_obj); if (qobject != nullptr) { className = qobject->metaObject()->className(); int i = -1; if ((i = className.indexOf('_')) != -1) { className.truncate(i); } objectName = qobject->objectName(); address = reinterpret_cast(qobject); } cl_object l_class = from_qstring(className); cl_object l_name = from_qstring(objectName); cl_object l_addr = ecl_make_unsigned_integer(address); ecl_return3(ecl_process_env(), l_class, l_name, l_addr); } cl_object root_item() { /// args: () /// Returns the root item of the QQuickView. ecl_process_env()->nvalues = 1; cl_object l_ret = from_qobject_pointer(LQML::quickView->rootObject()); ecl_return1(ecl_process_env(), l_ret); } cl_object qquit2(cl_object l_status) { // called by QML:QQUIT int s = toInt(l_status); qGuiApp->quit(); cl_shutdown(); LQML::cl_shutdown_p = true; if (s < 0) { abort(); } else { exit(s); } return ECL_NIL; } cl_object pixel_ratio() { /// args: () /// Returns the effective device pixel ratio. cl_object l_ret = ecl_make_doublefloat(LQML::quickView->effectiveDevicePixelRatio()); ecl_return1(ecl_process_env(), l_ret); } cl_object reload2() { // called by QML:RELOAD LQML::quickView->engine()->clearComponentCache(); QUrl source(LQML::quickView->source()); LQML::quickView->setSource(source); cl_object l_ret = from_qstring(source.toString()); ecl_return1(ecl_process_env(), l_ret); } // *** meta info *** static QByteArrayList metaInfo(const QByteArray& type, const QByteArray& qclass, const QByteArray& search, const QMetaObject* mo, bool no_offset = false) { QByteArrayList info; if ("methods" == type) { for (int i = mo->methodOffset(); i < mo->methodCount(); i++) { QMetaMethod mm(mo->method(i)); if (mm.methodType() == QMetaMethod::Method) { QString sig(mm.methodSignature()); QString ret(mm.typeName()); if (ret.isEmpty()) { ret = "void"; } ret.append(" "); if (!sig.startsWith("_q_")) { QString name(ret + sig); QByteArray rm('(' + qclass + '*'); if (mm.parameterNames().size() > 1) { rm.append(','); } name.replace(rm, "("); if (name.contains(search, Qt::CaseInsensitive)) { info << name.toLatin1(); } } } } } else if ("properties" == type) { // 'no_offset' is for properties only (QML) for (int i = (no_offset ? 0 : mo->propertyOffset()); i < mo->propertyCount(); i++) { QMetaProperty mp(mo->property(i)); QString name = QString("%1 %2%3").arg(mp.typeName()) .arg(mp.name()) .arg(mp.isWritable() ? "" : " const"); if (name.contains(search, Qt::CaseInsensitive)) { info << name.toLatin1(); } } } else { int _type = ("signals" == type) ? QMetaMethod::Signal : QMetaMethod::Slot; for (int i = mo->methodOffset(); i < mo->methodCount(); i++) { QMetaMethod mm(mo->method(i)); if (mm.methodType() == _type) { QString ret(mm.typeName()); if (ret.isEmpty()) { ret = "void"; } QString sig(mm.methodSignature()); if (!sig.startsWith("_q_")) { QString name(QString("%1 %2").arg(ret).arg(sig)); if (name.contains(search, Qt::CaseInsensitive)) { info << name.toLatin1(); } } } } } return info; } static bool metaInfoLessThan(const QByteArray& s1, const QByteArray& s2) { if (s1.contains('(')) { return s1.mid(1 + s1.lastIndexOf(' ', s1.indexOf('('))) < s2.mid(1 + s2.lastIndexOf(' ', s2.indexOf('('))); } return s1.mid(1 + s1.indexOf(' ')) < s2.mid(1 + s2.indexOf(' ')); } static cl_object collectInfo(const QByteArray& type, const QByteArray& qclass, const QByteArray& qsearch, bool* found, const QMetaObject* mo, bool no_offset = false) { cl_object l_info = ECL_NIL; QByteArrayList info = metaInfo(type, qclass, qsearch, mo, no_offset); std::sort(info.begin(), info.end(), metaInfoLessThan); if (info.size()) { *found = true; Q_FOREACH(QByteArray i, info) { l_info = CONS(STRING_COPY(i.constData()), l_info); } } l_info = cl_nreverse(l_info); return l_info; } cl_object qapropos2(cl_object l_search, cl_object l_obj, cl_object l_no_offset) { // called by QML:QAPROPOS ecl_process_env()->nvalues = 1; QByteArray search; if (ECL_STRINGP(l_search)) { search = toCString(l_search); } bool no_offset = (l_no_offset != ECL_NIL); // for QML (all instance properties) const QMetaObject* mo = 0; QObject* obj = toQObjectPointer(l_obj); if (obj != nullptr) { mo = obj->metaObject(); cl_object l_docs = ECL_NIL; do { bool found = false; const QMetaObject* super = mo->superClass(); QString superName; if (super != nullptr) { superName = QString(" : %1").arg(super->className()); } QByteArray _class = (QString(mo->className()) + superName).toLatin1(); cl_object l_doc_pro = ECL_NIL; cl_object l_doc_slo = ECL_NIL; cl_object l_doc_sig = ECL_NIL; l_doc_pro = collectInfo("properties", _class, search, &found, mo, no_offset); cl_object l_doc_met = collectInfo("methods", _class, search, &found, mo); l_doc_slo = collectInfo("slots", _class, search, &found, mo); l_doc_sig = collectInfo("signals", _class, search, &found, mo); if (found) { cl_object l_doc = ECL_NIL; if (l_doc_pro != ECL_NIL) { l_doc = CONS(CONS(STRING("Properties:"), l_doc_pro), l_doc); } if (l_doc_met != ECL_NIL) { l_doc = CONS(CONS(STRING("Methods:"), l_doc_met), l_doc); } if (l_doc_slo != ECL_NIL) { l_doc = CONS(CONS(STRING("Slots:"), l_doc_slo), l_doc); } if (l_doc_sig != ECL_NIL) { l_doc = CONS(CONS(STRING("Signals:"), l_doc_sig), l_doc); } l_doc = cl_nreverse(l_doc); if (l_doc != ECL_NIL) { l_docs = CONS(CONS(STRING_COPY(_class.data()), l_doc), l_docs); } } } while ((mo = mo->superClass())); cl_object l_ret = cl_nreverse(l_docs); return l_ret; } error_msg("QAPROPOS", LIST3(l_search, l_obj, l_no_offset)); return ECL_NIL; } QT_END_NAMESPACE