From 462585075231daec427bf08de4de75339aa9f386 Mon Sep 17 00:00:00 2001 From: "pls.153" Date: Sat, 22 Jan 2022 15:07:55 +0100 Subject: [PATCH] add help generated from doc-strings; some revisions --- doc/help.htm | 210 ++++++++++++++++++++++++++++++++++++++++++ src/cpp/ecl_ext.cpp | 105 +++++++++++++++------ src/cpp/ecl_ext.h | 4 +- src/cpp/lqml.cpp | 3 +- src/cpp/main.cpp | 27 ++++-- src/cpp/marshal.cpp | 4 +- src/cpp/qml.cpp | 2 +- src/cpp/qt_ecl.cpp | 2 - src/cpp/single_shot.h | 2 - src/lisp/ini.lisp | 59 +++++++++--- src/lisp/package.lisp | 13 +-- src/lisp/qml.lisp | 84 +++++++++++------ 12 files changed, 420 insertions(+), 95 deletions(-) create mode 100644 doc/help.htm diff --git a/doc/help.htm b/doc/help.htm new file mode 100644 index 0000000..f42fd32 --- /dev/null +++ b/doc/help.htm @@ -0,0 +1,210 @@ + + + +Function List + + + +
+qload-c++ (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 Qt6 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
+
+
+find-quick-item (object-name)
+
+  Finds the first QQuickItem matching OBJECT-NAME. Locally set *ROOT-ITEM* if
+  you want to find items inside a specific item, like in a QML Repeater. See
+  also note in sources.
+
+
+pixel-ratio ()
+
+  Returns the effective device pixel ratio.
+
+
+q! (method-name item/name &rest arguments)
+
+  For calling methods of QML items.
+
+    (q! |requestPaint| *canvas*)
+
+
+q< (property-name item/name)
+
+  Convenience macro for QML-GET. Use symbol instead of string name.
+
+    (q< |text| *label*)
+    (q< |font.pixelSize| *label*)
+
+
+q> (property-name item/name value)
+
+  Convenience macro for QML-SET. Use symbol instead of string name.
+
+    (q> |text| *label* "greetings!")
+
+
+q>* (property-name item/name value)
+
+  Convenience macro for QML-SET-ALL. Use symbol instead of string name. Sets
+  given property of all items sharing the same 'objectName'.
+
+
+qapropos (name &optional qobject/name)
+
+  Searches properties, methods, signals, slots for NAME in QObject
+  (e.g. QQuickItem) passed as second argument. QQuickItems can also be passed
+  by their 'objectName'.
+
+    (qapropos nil *canvas*)
+    (qapropos "color")
+
+
+qapropos* (name &optional qobject/name)
+
+  Similar to QAPROPOS, returning the results as nested list.
+
+
+qchildren (item/name)
+
+  Like QML function children().
+
+
+qescape (string)
+
+  Calls QString::toHtmlEscaped().
+
+
+qexec (&optional milliseconds)
+
+  Calls QCoreApplication::exec(). Optionally pass the time in milliseconds
+  after which QEventLoop::exit() will be called. See also QSLEEP.
+
+
+qexit ()
+
+  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.
+
+
+qfind-child (qobject name)
+
+  Calls QObject::findChild().
+
+
+qfrom-utf8 (byte-array)
+
+  Returns the BYTE-ARRAY (vector of octets) converted using
+  QString::fromUtf8().
+
+
+qget (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|)
+
+
+qjs (method-name item/name &rest arguments
+
+  Fast and convenient way to call JS functions defined in QML. You may pass
+  up to 10 arguments of the following types:
+  T, NIL, INTEGER, FLOAT, STRING, and (nested) lists of mentioned arguments.
+  N.B: Does not work with JS default arguments.
+
+
+qlater (function)
+
+  Calls FUNCTION as soon as the Qt event loop is idle.
+
+
+qlog (arg1 &optional arg2 arg3...)
+
+  For log messages on android.
+
+    (qlog 12)
+    (qlog "width" 10 "height" 20)
+    (qlog "x ~A y ~A" x y)
+
+
+qobject-name (qobject)
+
+  Returns the QObject::objectName() of passed QOBJECT (FFI pointer).
+
+
+qobject-p (x)
+
+  Tests if argument is of type QObject.
+
+
+qprocess-events ()
+
+  Calls QCoreApplication::processEvents().
+
+
+qquit (&optional (exit-status 0) (kill-all-threads t))
+qq
+
+  Terminates LQML. Use this function instead of ECL (ext:quit) to quit
+  gracefully. Negative values for EXIT-STATUS will call C abort() instead of
+  normal program exit.
+
+
+qset (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)
+
+
+qsingle-shot (milliseconds function)
+
+  A single shot timer similar to QTimer::singleShot().
+
+    (qsingle-shot 1000 'one-second-later)
+
+
+qsleep (seconds)
+
+  Similar to SLEEP, but continuing to process Qt events.
+
+
+qversion ()
+
+  Returns the LQML version number as 'year.month.counter'. The second
+  return value is the Qt version as returned by QLibraryInfo::version().
+
+
+root-item ()
+
+  Returns the root item of the QQuickView.
+
+
+tr (source &optional context plural-number)
+
+  Macro expanding to QTRANSLATE, which calls QCoreApplication::translate().
+  Both SOURCE and CONTEXT can be Lisp forms evaluating to constant strings
+  (at compile time). The CONTEXT argument defaults to the Lisp file name.
+  For the PLURAL-NUMBER, see Qt Assistant.
+
+
+
+ + diff --git a/src/cpp/ecl_ext.cpp b/src/cpp/ecl_ext.cpp index a1af116..70661df 100644 --- a/src/cpp/ecl_ext.cpp +++ b/src/cpp/ecl_ext.cpp @@ -4,6 +4,7 @@ #include "single_shot.h" #include #include +#include #include #include #include @@ -22,7 +23,7 @@ void iniCLFunctions() { DEFUN ("%js", js2, 2) DEFUN ("pixel-ratio", pixel_ratio, 0) DEFUN ("%qapropos", qapropos2, 3) - DEFUN ("qchild-items", qchild_items, 1) + DEFUN ("qchildren", qchildren, 1) DEFUN ("qescape", qescape, 1) DEFUN ("%qexec", qexec2, 1) DEFUN ("qexit", qexit, 0) @@ -31,7 +32,6 @@ void iniCLFunctions() { DEFUN ("qfrom-utf8", qfrom_utf8, 1) DEFUN ("%qinvoke-method", qinvoke_method2, 3) DEFUN ("%qload-c++", qload_cpp, 2) - DEFUN ("qlocal8bit", qlocal8bit, 1) DEFUN ("%qlog", qlog2, 1) DEFUN ("%qml-get", qml_get2, 2) DEFUN ("%qml-set", qml_set2, 3) @@ -43,7 +43,6 @@ void iniCLFunctions() { DEFUN ("%qset", qset2, 2) DEFUN ("%qsingle-shot", qsingle_shot2, 2) DEFUN ("qtranslate", qtranslate, 3) - DEFUN ("qutf8", qutf8, 1) DEFUN ("qversion", qversion, 0) DEFUN ("%reload", reload2, 0) DEFUN ("root-item", root_item, 0) @@ -55,6 +54,7 @@ void iniCLFunctions() { // *** 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" @@ -79,11 +79,16 @@ void error_msg(const char* fun, cl_object 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: (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(); @@ -101,6 +106,10 @@ cl_object qget2(cl_object l_obj, cl_object l_name) { } cl_object qset2(cl_object l_obj, cl_object l_args) { + /// args: (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(); @@ -131,6 +140,8 @@ fail: } cl_object qfind_child(cl_object l_obj, cl_object l_name) { + /// args: (qobject name) + /// Calls QObject::findChild(). ecl_process_env()->nvalues = 1; QString name(toQString(l_name)); if (!name.isEmpty()) { @@ -148,6 +159,7 @@ cl_object qfind_child(cl_object l_obj, cl_object l_name) { } 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)); @@ -169,7 +181,9 @@ cl_object qfind_children2(cl_object l_obj, cl_object l_name, cl_object l_class) return ECL_NIL; } -cl_object qchild_items(cl_object l_item) { +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 @@ -183,11 +197,25 @@ cl_object qchild_items(cl_object l_item) { l_children = cl_nreverse(l_children); return l_children; } - error_msg("QCHILD-ITEMS", LIST1(l_item)); + 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 Qt6 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); @@ -235,6 +263,7 @@ cl_object qload_cpp(cl_object l_lib_name, cl_object l_unload) { /// qload-c++ // *** 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); @@ -248,34 +277,32 @@ cl_object qtranslate(cl_object l_con, cl_object l_src, cl_object l_n) { ecl_return1(ecl_process_env(), l_ret); } -cl_object qlocal8bit(cl_object l_str) { - // returns 'ecl_simple_base_string', not Unicode - cl_object l_ret = from_cstring(toQString(l_str).toLocal8Bit()); - ecl_return1(ecl_process_env(), l_ret); -} - -cl_object qutf8(cl_object l_str) { - // returns 'ecl_simple_base_string', not Unicode - cl_object l_ret = from_cstring(toQString(l_str).toUtf8()); - 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() { - QGuiApplication::processEvents(); + /// 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; @@ -290,11 +317,15 @@ cl_object qexec2(cl_object l_milliseconds) { return l_milliseconds; } QCoreApplication::exit(); // prevent "the event loop is already running" - QGuiApplication::exec(); + 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()) { @@ -306,6 +337,9 @@ cl_object qexit() { } 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 "delete this;" in "single_shot.h" @@ -316,12 +350,16 @@ cl_object qsingle_shot2(cl_object l_msec, cl_object l_fun) { } 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_cstring(qVersion()); + 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; @@ -344,17 +382,18 @@ cl_object qrun_on_ui_thread2(cl_object l_function_or_closure, cl_object l_blocki } cl_object qlog2(cl_object l_msg) { - // for android logging only; see 'ini.lisp::qlog' and 'lqml.cpp::logMessageHandler' + // 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) { - // max. 10 arguments - // supported argument types: T, NIL, INTEGER, FLOAT, STRING, - // (nested) LIST of mentioned arguments - // - // N.B. does not support default arguments if used to call JS functions + // 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, (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]; @@ -385,6 +424,7 @@ cl_object qinvoke_method2(cl_object l_obj, cl_object l_name, cl_object l_args) { } 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) { @@ -397,6 +437,7 @@ cl_object js2(cl_object l_item, cl_object l_str) { } 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()) { @@ -411,6 +452,7 @@ cl_object qml_get2(cl_object l_item, cl_object l_name) { } 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); @@ -427,6 +469,8 @@ cl_object qml_set2(cl_object l_item, cl_object l_name, cl_object l_value) { } cl_object qobject_name(cl_object l_obj) { + /// args: (qobject) + /// Returns the QObject::objectName() of passed QOBJECT (FFI pointer). ecl_process_env()->nvalues = 1; QObject* qobject = toQObjectPointer(l_obj); if (qobject != nullptr) { @@ -438,16 +482,19 @@ cl_object qobject_name(cl_object l_obj) { } 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; - int s = toInt(l_status); if (s < 0) { abort(); } else { @@ -457,11 +504,14 @@ cl_object qquit2(cl_object l_status) { } 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); @@ -564,6 +614,7 @@ static cl_object collectInfo(const QByteArray& type, } 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)) { diff --git a/src/cpp/ecl_ext.h b/src/cpp/ecl_ext.h index 593344c..a30a2e6 100644 --- a/src/cpp/ecl_ext.h +++ b/src/cpp/ecl_ext.h @@ -52,7 +52,7 @@ QT_BEGIN_NAMESPACE cl_object js2 (cl_object, cl_object); cl_object pixel_ratio (); cl_object qapropos2 (cl_object, cl_object, cl_object); -cl_object qchild_items (cl_object); +cl_object qchildren (cl_object); cl_object qescape (cl_object); cl_object qexec2 (cl_object); cl_object qexit (); @@ -61,7 +61,6 @@ cl_object qfind_children2 (cl_object, cl_object, cl_object); cl_object qfrom_utf8 (cl_object); cl_object qinvoke_method2 (cl_object, cl_object, cl_object); cl_object qload_cpp (cl_object, cl_object); -cl_object qlocal8bit (cl_object); cl_object qlog2 (cl_object); cl_object qml_get2 (cl_object, cl_object); cl_object qml_set2 (cl_object, cl_object, cl_object); @@ -73,7 +72,6 @@ cl_object qget2 (cl_object, cl_object); cl_object qset2 (cl_object, cl_object); cl_object qsingle_shot2 (cl_object, cl_object); cl_object qtranslate (cl_object, cl_object, cl_object); -cl_object qutf8 (cl_object); cl_object qversion (); cl_object reload2 (); cl_object root_item (); diff --git a/src/cpp/lqml.cpp b/src/cpp/lqml.cpp index ff6abc0..cfe0825 100644 --- a/src/cpp/lqml.cpp +++ b/src/cpp/lqml.cpp @@ -48,7 +48,8 @@ LQML::LQML(int argc, char* argv[], QQuickView* view) : QObject() { iniCLFunctions(); ecl_init_module(NULL, ini_LQML); eval("(in-package :qml-user)"); - eval(QString("(setf *quick-view* (make-qobject %1))").arg((quintptr)view)); + eval(QString("(setf *quick-view* (make-qobject %1))") + .arg(reinterpret_cast(view))); } LQML::~LQML() { diff --git a/src/cpp/main.cpp b/src/cpp/main.cpp index 22292a9..686b31c 100644 --- a/src/cpp/main.cpp +++ b/src/cpp/main.cpp @@ -28,6 +28,7 @@ int main(int argc, char* argv[]) { //app.setOrganizationName("MyProject"); //app.setOrganizationDomain("my.org"); app.setApplicationName(QFileInfo(app.applicationFilePath()).baseName()); + QStringList arguments(QCoreApplication::arguments()); QQuickView view; ADD_MACOS_BUNDLE_IMPORT_PATH @@ -41,7 +42,6 @@ int main(int argc, char* argv[]) { view.connect(view.engine(), &QQmlEngine::quit, &app, &QCoreApplication::quit); LQML lqml(argc, argv, &view); - QStringList arguments(QCoreApplication::arguments()); if (arguments.contains("-v") || arguments.contains("--version")) { lqml.printVersion(); std::cout << std::endl; @@ -49,16 +49,25 @@ int main(int argc, char* argv[]) { } new QQmlFileSelector(view.engine(), &view); - QUrl url("qrc:///qml/main.qml"); - if (!QFile::exists(url.fileName())) { - url = "qml/main.qml"; + QString qml("qml/main.qml"); + QUrl url("qrc:///" + qml); + bool set = false; + if (QFile::exists(url.fileName())) { + set = true; + } else { + url = QUrl::fromLocalFile(qml); + if (QFile::exists(QDir::currentPath() + "/" + qml)) { + set = true; + } } - view.setSource(url); - if (view.status() == QQuickView::Error) { - return -1; + if (set) { + view.setSource(url); + if (view.status() == QQuickView::Error) { + return -1; + } + view.setResizeMode(QQuickView::SizeRootObjectToView); + QTimer::singleShot(0, &view, &QQuickView::show); } - view.setResizeMode(QQuickView::SizeRootObjectToView); - QTimer::singleShot(0, &view, &QQuickView::show); // load .eclrc if (arguments.contains("-norc")) { diff --git a/src/cpp/marshal.cpp b/src/cpp/marshal.cpp index 5909b46..e862ab7 100644 --- a/src/cpp/marshal.cpp +++ b/src/cpp/marshal.cpp @@ -246,7 +246,9 @@ cl_object from_qvariant(const QVariant& var) { cl_object from_qobject_pointer(QObject* qobject) { STATIC_SYMBOL_PKG (s_make_qobject, "MAKE-QOBJECT", "QML") // see 'ini.lisp' - return cl_funcall(2, s_make_qobject, ecl_make_unsigned_integer((quintptr)qobject)); + return cl_funcall(2, + s_make_qobject, + ecl_make_unsigned_integer(reinterpret_cast(qobject))); } QT_END_NAMESPACE diff --git a/src/cpp/qml.cpp b/src/cpp/qml.cpp index 66784c5..d4b01b6 100644 --- a/src/cpp/qml.cpp +++ b/src/cpp/qml.cpp @@ -20,7 +20,7 @@ QObject* iniQml() { static QVariant qmlApply(QObject* caller, const QString& function, const QVariantList& arguments) { QVariant var = ecl_fun("qml:qml-apply", - QVariant((quintptr)caller), + QVariant(reinterpret_cast(caller)), QVariant(function), QVariant(arguments)); QString str(var.toString()); diff --git a/src/cpp/qt_ecl.cpp b/src/cpp/qt_ecl.cpp index c65808f..6c7d13c 100644 --- a/src/cpp/qt_ecl.cpp +++ b/src/cpp/qt_ecl.cpp @@ -1,5 +1,3 @@ -#undef SLOT - #include "qt_ecl.h" #include "marshal.h" #include "ecl_ext.h" diff --git a/src/cpp/single_shot.h b/src/cpp/single_shot.h index 27eca58..833244a 100644 --- a/src/cpp/single_shot.h +++ b/src/cpp/single_shot.h @@ -1,8 +1,6 @@ #ifndef SINGLE_SHOT_H #define SINGLE_SHOT_H -#undef SLOT - #include #include diff --git a/src/lisp/ini.lisp b/src/lisp/ini.lisp index f30810d..7ea41a0 100644 --- a/src/lisp/ini.lisp +++ b/src/lisp/ini.lisp @@ -1,21 +1,31 @@ -#+ecl -(si::trap-fpe t nil) ; ignore floating point exceptions (they happen on Qt side) +;;; doc-string note: documentation is added where a function is defined; +;;; sometimes this is in file 'ecl_ext.cpp' + +(si::trap-fpe t nil) ; ignore floating point exceptions (caused on Qt side) (in-package :qml) -(defvar *break-on-errors* t) +(defvar *break-on-errors* t + "If T, call (BREAK) on errors inside of LQML functions defined in C++.") (defun make-qobject (address) + ;; for internal use (ffi:make-pointer address :pointer-void)) (defun qobject-p (x) + "args: (x) + Tests if argument is of type QObject." (eql 'si:foreign-data (type-of x))) (defmacro alias (s1 s2) `(setf (symbol-function ',s1) (function ,s2))) -(defmacro ! (fun &rest args) - `(qfun ,(cadar args) ,fun ,@(rest args))) +(defmacro ! (fun qobject &rest args) + ;; legacy, should not be needed, use DEFINE-QT-WRAPPERS instead + ;; usage: + ;; (! "myFunction" *cpp* 1 2 3) + ;; (! |myFunction| *cpp* 1 2 3) + `(qfun ,qobject ,(if (stringp fun) fun (symbol-name fun)) ,@(rest args))) (defun %reference-name () (format nil "%~A%" (gensym))) @@ -24,6 +34,8 @@ (%qexec ms)) (defun qsleep (seconds) + "args: (seconds) + Similar to SLEEP, but continuing to process Qt events." (%qexec (floor (* 1000 seconds))) nil) @@ -35,9 +47,11 @@ (%qsingle-shot ,milliseconds (setf (symbol-function (intern ,(%reference-name))) ; lambda ,function)))) `(qrun (lambda () - (%qsingle-shot ,milliseconds ,function))))) ; 'foo + (%qsingle-shot ,milliseconds ,function))))) ; 'foo (defmacro qlater (function) + "args: (function) + Calls FUNCTION as soon as the Qt event loop is idle." `(qsingle-shot 0 ,function)) (defun %ensure-persistent-function (fun) @@ -50,12 +64,16 @@ fun)))) (defun %make-vector () + ;; for internal use (called from 'ecl_ext.cpp') (make-array 0 :adjustable t :fill-pointer t)) (defun %break (&rest args) + ;; for internal use (called from 'ecl_ext.cpp') (apply 'break args)) (defun ignore-io-streams () + "Needed on Windows to prevent crash on print output (for apps without + a console window)." (setf *standard-output* (make-broadcast-stream) *trace-output* *standard-output* *error-output* *standard-output* @@ -63,6 +81,11 @@ *standard-output*))) (defmacro tr (source &optional context (plural-number -1)) + "args: (source &optional context plural-number) + Macro expanding to QTRANSLATE, which calls QCoreApplication::translate(). + Both SOURCE and CONTEXT can be Lisp forms evaluating to constant strings + (at compile time). The CONTEXT argument defaults to the Lisp file name. + For the PLURAL-NUMBER, see Qt Assistant." ;; see compiler-macro in "tr.lisp" (let ((source* (ignore-errors (eval source))) (context* (ignore-errors (eval context)))) @@ -87,8 +110,12 @@ (%qload-c++ library-name unload)) (defun define-qt-wrappers (qt-library &rest what) - ;; N.B. This works only for Qt6 functions with the following signature: - ;; "QVariant foo(QVariant, ...)" ; max 10 QVariant arguments + "args: (qt-library &rest what) + Defines Lisp methods for all Qt methods/signals/slots of given library, + previously loaded with QLOAD-C++. + (define-qt-wrappers *c++*) ; generate wrappers + (define-qt-wrappers *c++* :methods) ; Qt methods only (no slots/signals) + (my-qt-function *c++* x y) ; call from Lisp" (let ((all-functions (qapropos* nil (ensure-qt-object qt-library))) (lispify (not (find :do-not-lispify what)))) (setf what (remove-if (lambda (x) (find x '(:do-not-lispify t))) @@ -119,6 +146,7 @@ (%qinvoke-method object ,qt-name arguments))))))))) (defun qinvoke-method (object function-name &rest arguments) + ;; for internal use (%qinvoke-method object function-name arguments)) (defmacro qget (object name) @@ -137,11 +165,13 @@ arguments)))) (defun qrun-on-ui-thread (function &optional (blocking t)) + ;; for internal use (%qrun-on-ui-thread function blocking)) (defvar *gui-thread* mp:*current-process*) (defmacro qrun-on-ui-thread* (&body body) + ;; for internal use (let ((values (gensym))) `(if (eql *gui-thread* mp:*current-process*) ,(if (second body) @@ -158,6 +188,11 @@ `(qrun-on-ui-thread* ,@body)) (defun qquit (&optional (exit-status 0) (kill-all-threads t)) + "args: (&optional (exit-status 0) (kill-all-threads t)) + alias: qq + Terminates LQML. Use this function instead of ECL (ext:quit) to quit + gracefully. Negative values for EXIT-STATUS will call C abort() instead of + normal program exit." (declare (ignore kill-all-threads)) ; only here to be equivalent to EXT:QUIT (assert (typep exit-status 'fixnum)) (%qquit exit-status)) @@ -169,9 +204,11 @@ ;;; for android logging (defun qlog (arg1 &rest args) - ;; (qlog 12) - ;; (qlog 1 "plus" 2 "gives" 3) - ;; (qlog "x ~A y ~A" x y) + "args: (arg1 &optional arg2 arg3...) + For log messages on android. + (qlog 12) + (qlog \"width\" 10 \"height\" 20) + (qlog \"x ~A y ~A\" x y)" (%qlog (if (and (stringp arg1) (find #\~ arg1)) (apply 'format nil arg1 args) diff --git a/src/lisp/package.lisp b/src/lisp/package.lisp index 4a2fd93..eb2f176 100644 --- a/src/lisp/package.lisp +++ b/src/lisp/package.lisp @@ -13,6 +13,7 @@ #:pixel-ratio #:qapropos #:qapropos* + #:qfind-child #:qml-call #:qml-get #:qml-set @@ -22,20 +23,16 @@ #:q> #:q>* #:qjs - #+linux - #:qchild-items + #:qchildren #:qescape #:qexec #:qexit - #:qfind-child - #:qfind-children #:qfrom-utf8 #:qfun #:qget #:qset #:qlater #:qload-c++ - #:qlocal8bit #:qlog #:qobject-p #:qprocess-events @@ -48,13 +45,11 @@ #:qsingle-shot #:qsleep #:qtranslate - #:qutf8 #:qversion - #:qvariant-from-value - #:qvariant-value #:root-item #:reload - #:tr)) + #:tr + #:!)) (defpackage :qml-user (:use :common-lisp :qml)) diff --git a/src/lisp/qml.lisp b/src/lisp/qml.lisp index 7dd8bff..8b66976 100644 --- a/src/lisp/qml.lisp +++ b/src/lisp/qml.lisp @@ -49,9 +49,9 @@ (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()'." + ;; 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 (zerop caller) ; don't change LET* *caller* (make-qobject caller))) @@ -64,7 +64,8 @@ ;;; utils (defun find-quick-item (object-name) - "Finds the first QQuickItem matching OBJECT-NAME. Locally set *ROOT-ITEM* if + "args: (object-name) + Finds the first QQuickItem matching OBJECT-NAME. Locally set *ROOT-ITEM* if you want to find items inside a specific item, like in a QML Repeater. See also note in sources." ;; @@ -94,6 +95,7 @@ (qfind-child parent object-name))))) (defun quick-item (item/name) + ;; for internal use (cond ((stringp item/name) (find-quick-item item/name)) ((qobject-p item/name) @@ -102,52 +104,55 @@ (root-item)))) (defun children (item/name) - "Like QML function 'children'." + "args: (item/name) + Like QML function 'children'." (qrun* (qchild-items (quick-item item/name)))) (defun reload () - "Reloads all QML files, clearing the cache." + "args: () + Reloads all QML files, clearing the cache." (qrun* (%reload))) ;;; get/set QML properties, call QML methods (through JS) (defun qml-get (item/name property-name) - "Gets QQmlProperty of either ITEM or first object matching NAME." + ;; see Q< (qrun* (%qml-get (quick-item item/name) property-name))) (defun qml-set (item/name property-name value) - "Sets QQmlProperty of either ITEM, or first object matching NAME. - Returns T on success." + ;; see Q> (qrun* (%qml-set (quick-item item/name) property-name value))) (defun qml-set-all (name property-name value) - "Sets QQmlProperty of all objects matching NAME." + ;; see Q>* (assert (stringp name)) (qrun* (dolist (item (qfind-children (root-item) name)) (qml-set item property-name value)))) -(defmacro q! (method-name item/name &rest arguments) - "Convenience macro for QML-CALL. Use symbol instead of string name." - `(js ,item/name ,(symbol-name method-name) ,@arguments)) - -(defmacro q> (property-name item/name value) - "Convenience macro for QML-SET. Use symbol instead of string name." - `(qml-set ,item/name ,(symbol-name property-name) ,value)) +;;; convenience macros: re-arrange arguments to put the name first, and use +;;; a symbol instead of the string name, so we can use auto completion (defmacro q< (property-name item/name) - "Convenience macro for QML-GET. Use symbol instead of string name." + "args: (property-name item/name) + Convenience macro for QML-GET. Use symbol instead of string name. + (q< |text| *label*) + (q< |font.pixelSize| *label*)" `(qml-get ,item/name ,(symbol-name property-name))) +(defmacro q> (property-name item/name value) + "args: (property-name item/name value) + Convenience macro for QML-SET. Use symbol instead of string name. + (q> |text| *label* \"greetings!\")" + `(qml-set ,item/name ,(symbol-name property-name) ,value)) + (defmacro q>* (property-name item/name value) - "Convenience macro for QML-SET-ALL. Use symbol instead of string name." + "args: (property-name item/name value) + Convenience macro for QML-SET-ALL. Use symbol instead of string name. Sets + given property of all items sharing the same 'objectName'." `(qml-set-all ,item/name ,(symbol-name property-name) ,value)) -;;; JS - (defun js (item/name fun &rest arguments) - "Evaluates a JS string, with 'this' bound to either ITEM, or first object - matching NAME. Use this function instead of the (faster) QJS if you need to - evaluate generic JS code, or for JS functions with default arguments." + ;; for internal use, see macro Q! (qrun* (%js (quick-item item/name) (apply 'format nil (format nil "~A(~A)" @@ -156,13 +161,26 @@ (mapcar 'js-arg arguments))))) (defun js-arg (object) - "Used for arguments in function JS." + ;; for arguments in function JS (if (stringp object) object (with-output-to-string (*standard-output*) (print-js-readably object)))) +(defmacro q! (method-name item/name &rest arguments) + "args: (method-name item/name &rest arguments) + For calling methods of QML items. + (q! |requestPaint| *canvas*)" + `(js ,item/name ,(symbol-name method-name) ,@arguments)) + +;;; JS calls + (defmacro qjs (method-name item/name &rest arguments) + "args: (method-name item/name &rest arguments + Fast and convenient way to call JS functions defined in QML. You may pass + up to 10 arguments of the following types: + T, NIL, INTEGER, FLOAT, STRING, and (nested) lists of mentioned arguments. + N.B: Does not work with JS default arguments." `(qrun* (qfun (quick-item ,item/name) ,(if (symbolp method-name) (symbol-name method-name) @@ -176,8 +194,14 @@ x (quick-item x))) -(defun qapropos (name &optional class offset) - (dolist (sub1 (%qapropos (%string-or-nil name) (%to-qobject class) offset)) +(defun qapropos (name &optional qobject/name offset) + "args: (name &optional qobject/name) + Searches properties, methods, signals, slots for NAME in QObject + (e.g. QQuickItem) passed as second argument. QQuickItems can also be passed + by their 'objectName'. + (qapropos nil *canvas*) + (qapropos \"color\")" + (dolist (sub1 (%qapropos (%string-or-nil name) (%to-qobject qobject/name) offset)) (format t "~%~%~A~%" (first sub1)) (dolist (sub2 (rest sub1)) (format t "~% ~A~%~%" (first sub2)) @@ -190,6 +214,8 @@ (terpri) nil) -(defun qapropos* (name &optional class offset) - (%qapropos (%string-or-nil name) (%to-qobject class) offset)) +(defun qapropos* (name &optional qobject/name offset) + "args: (name &optional qobject/name) + Similar to QAPROPOS, returning the results as nested list." + (%qapropos (%string-or-nil name) (%to-qobject qobject/name) offset))