Compare commits

...
Sign in to create a new pull request.

36 commits
sfos ... master

Author SHA1 Message Date
Renaud Casenave-Péré
92ca801038 Bump version number to 24.2.1 2025-06-29 17:59:56 +02:00
P. Ruetz
ff1020f87e Merge branch 'private-libs' into 'master'
Install private libraries in a subdirectory

Closes #29

See merge request eql/EQL5!7
2024-02-01 19:10:22 +00:00
Benson Muite
df5f6e07b6 Install private libraries in a subdirectory
Move them out of LD_LIBRARY_PATH
2024-01-23 10:15:41 +03:00
pls.153
440ac762a8 fix '(qexec)' to work with QML Preview (Qt Designer) when used inside a fallback restart in Slime mode 2022-02-28 10:50:04 +01:00
pls.153
da1285cace fix function 'x:path' for unix 2022-02-19 08:29:22 +01:00
pls.153
0bf4fbaf2e change 'linux' to 'unix' in *.pro files, small revisions 2022-02-17 11:39:25 +01:00
polos
82c209a469 fix paren bug 2021-11-05 13:42:08 +01:00
polos
9738cdad85 'Qt_EQL': in 'EQL::addObject' / 'define-qt-wrappers': also add methods of all super classes; make lispifying C names optional; 2021-06-14 20:13:15 +02:00
polos
1758101cf2 revert beta hacks (sorry); fix comment in 'qml.lisp' 2021-06-10 22:28:45 +02:00
polos
c3d4050dc6 fix comment in 'qml.lisp' 2021-06-10 22:20:08 +02:00
polos
1695e9fc36 fix 'qml:find-quick-item' when used together with '*root-item*' (note comment in sources) 2021-05-21 13:35:35 +02:00
polos
8494a71fae better integration with QML Repeater and 'objectName' of respective children in there 2021-05-18 20:25:25 +02:00
polos
44689ec14e update comment 2021-05-12 16:28:10 +02:00
polos
5cf7e87bd4 small revision 2021-05-11 09:54:26 +02:00
polos
4f75551971 add third argument to 'EQL::addObject' to call 'define-qt-wrappers' 2021-05-11 09:48:00 +02:00
polos
73844d81ab revision 2021-05-10 10:01:16 +02:00
polos
2156ea501f add convenience function EQL::addObject() for adding Qt class instances as Lisp variables 2021-05-09 17:31:43 +02:00
polos
53c6a9e246 tiny revision 2021-05-07 19:16:34 +02:00
polos
28d5d133fb add tutorial in 'Qt_EQL/' for accessing C++ apps from Lisp 2021-05-07 19:06:19 +02:00
polos
490c878dee add MetaArg 'cl_object' to enable passing complex Lisp data to C++ 2021-05-04 16:03:41 +02:00
polos
44056e0853 add 'cl_shutdown()' in destructor of class EQL 2021-04-30 16:09:01 +02:00
polos
7b692f4e12 update comment in 'lib/thread-safe.lisp' 2021-04-29 10:09:33 +02:00
polos
b20eed0306 fix for running EQL5 on a separate thread (not the UI one); N.B. requires 'thread-safe.lisp' to be loaded on startup; 2021-04-28 17:22:33 +02:00
polos
dab9b53283 fix 'qml-ui-vars', see '(qml)' 2021-04-05 11:54:05 +02:00
polos
8c6a45d9e4 adapt '(qml)' for Sailfish 2021-04-01 11:28:05 +02:00
P. Ruetz
ecbef474db Merge branch 'master' into 'master'
Fix compile errors due to conflicting libraries

See merge request eql/EQL5!6
2021-03-29 08:53:09 +00:00
Renaud Casenave-Péré
6d0e46bcc7 Fix compile errors due to conflicting libraries 2021-03-28 09:41:28 +02:00
polos
7b19fd33dd adapt 'my_app' to new install path; small revisions; 2021-03-27 09:44:04 +01:00
polos
8fd473b5a2 revert 'wrong brackets', misunderstanding! (sorry) 2021-03-25 15:39:17 +01:00
polos
9d48cf502a fix wrong brackets in *.pro 2021-03-25 11:13:19 +01:00
polos
4969ea2b90 oversight 2021-03-25 11:01:43 +01:00
polos
95bb218660 update README 2021-03-25 10:59:37 +01:00
polos
0871e751fa revision of Sailfish ini 2021-03-25 10:38:50 +01:00
P. Ruetz
afacf3f74d Merge branch 'sfos' into 'master'
Patches for sailfish package

See merge request eql/EQL5!5
2021-03-25 08:28:51 +00:00
P. Ruetz
440089d90d Merge branch 'bugfixes' into 'master'
Pass argc/argv to Qt before passing it to ecl and fixes in webkit module

See merge request eql/EQL5!4
2021-03-25 08:28:13 +00:00
Renaud Casenave-Péré
ec089749b8 Pass argc/argv to Qt before passing it to ecl
QApplication will remove arguments it recognizes but ecl will not detect this
and might reference a no more existent argument
2021-03-23 22:26:19 +01:00
37 changed files with 466 additions and 145 deletions

23
Qt_EQL/tutorial/README.md Normal file
View file

@ -0,0 +1,23 @@
## Info
This is a basic demo showing how to access a C++ app from Lisp, including the
creation of instances of a C++ class (the implementation of which is not really
elegant, but it works).
It also starts a simple REPL for playing around interactively.
## Build
```
qmake
make
```
## Run
```
./test
```

35
Qt_EQL/tutorial/main.cpp Normal file
View file

@ -0,0 +1,35 @@
#include "test.h"
#include <eql5/eql.h>
#include <QApplication>
#include <QLabel>
int main(int argc, char* argv[]) {
QApplication app(argc, argv);
QLabel* main = new QLabel("<h2>Main Window</h2>");
main->setAlignment(Qt::AlignCenter);
main->resize(600, 400);
main->show();
EQL eql;
EQL::eval("(in-package :eql-user)");
// add desired Qt class instances as Lisp variables (uses 'defvar');
// you may provide a package name (which needs to exist); if not provided,
// the current package will be used (see above 'in-package');
// pass 'true' as last argument to also call 'define-qt-wrappers'
EQL::addObject(main, "eql-user:*main-widget*"); // add main
// note argument 3 (true): 'define-qt-wrappers'
EQL::addObject(new Test(main), "*test*", true); // add 'Test'
// note argument 4 (false): do not lispify C function names
EQL::addObject(new Test2(main), "*test-2*", true, false); // add 'Test2'
EQL::eval("(load \"test.lisp\")"); // will start a REPL
app.processEvents(); // needed for 'qlater' in 'test.lisp'
return 0; // no 'app.exec()' because of REPL
}

55
Qt_EQL/tutorial/test.cpp Normal file
View file

@ -0,0 +1,55 @@
#include "test.h"
#include <QtDebug>
#include <eql5/eql_fun.h>
// class Test
Test::Test(QObject* parent, const QString& name) : QObject(parent) {
setObjectName(name);
}
QObject* Test::newInstance(QObject* parent, const QString& name) {
return new Test(parent, name);
}
QString Test::concat(const QStringList& list) {
return list.join(", ");
}
void Test::processData(cl_object data) {
// meant for passing complex Lisp data to be processed in C++
if(cl_listp(data) == ECL_T) {
cl_object l_dolist = data;
while(l_dolist != ECL_NIL) {
cl_print(1, cl_car(l_dolist));
l_dolist = cl_cdr(l_dolist);
}
cl_terpri(0);
}
}
void Test::printMe() {
// you may pass up to 10 arguments of any type found in
// '~/eql5/src/ecl_fun.cpp::toMetaArg()', wrapped in macro Q_ARG;
// C++ class instances are passed as pointers of a vanilla Qt class
// known to EQL5, here: 'QObject*'
eql_fun("eql-user:print-qt-object", Q_ARG(QObject*, this));
}
// class Test2, which inherits Test
Test2::Test2(QObject* parent, const QString& name) : Test(parent) {
setObjectName(name);
}
QObject* Test2::newInstance(QObject* parent, const QString& name) {
return new Test2(parent, name);
}
void Test2::printAllMemberFunctions() {
// see comment above
eql_fun("eql:qapropos", Q_ARG(bool, false),
Q_ARG(QObject*, this));
}

42
Qt_EQL/tutorial/test.h Normal file
View file

@ -0,0 +1,42 @@
#ifndef APP_H
#define APP_H
#include <QObject>
#include <ecl/ecl.h>
class Test : public QObject {
Q_OBJECT
public:
Test(QObject* = nullptr, const QString& = QString());
// define function acting as constructor (callable from Lisp)
// N.B. return a vanilla Qt class (here: QObject*) known to EQL5
Q_INVOKABLE QObject* newInstance(QObject* = nullptr, const QString& = QString());
public Q_SLOTS:
// you may pass any type found in '~/eql5/src/ecl_fun.cpp::toMetaArg()';
// it's common practice in Qt to always pass const references (except for
// pointers, of course), as you'll find in any Qt example code
QString concat(const QStringList&);
// pass Lisp data ('cl_object' is just a pointer)
void processData(cl_object);
// call back to Lisp
void printMe();
};
class Test2 : public Test { // inherits class above
Q_OBJECT
public:
Test2(QObject* = nullptr, const QString& = QString());
// (see comment above)
Q_INVOKABLE QObject* newInstance(QObject* = nullptr, const QString& = QString());
public Q_SLOTS:
// (see comment above)
void printAllMemberFunctions();
};
#endif

45
Qt_EQL/tutorial/test.lisp Normal file
View file

@ -0,0 +1,45 @@
;;; below function calls only work because 'define-qt-wrappers' has already
;;; been called implicitely on '*test*', see 'EQL::addObject()' in 'main.cpp'
;;; (note third argument 'true')
;;;
;;; Qt function names are translated to Lisp names like this:
;;; Qt: Lisp:
;;; newInstance() (new-instance)
;;; doIOStuff() (do-i-o-stuff)
(defun test ()
;; make new instance
(format t "~%Creating instance of 'Test': ~A~%"
(new-instance *test* *main-widget* "test-1"))
;; call Qt slot
(format t "~%Calling 'Test::concat()': ~S~%"
(concat *test* (list "one" "two" "three")))
;; pass Lisp data
(format t "~%Processing complex Lisp data in C++:")
(process-data *test* (list 1 "virus" #\Esc 'the #(l a b)))
;; call C++ which will call back to Lisp
(print-me *test*)
(terpri))
(defun test-2 ()
;; call method from *test-2*, which function names are not lispified
;; (see 'main.cpp')
(|printAllMemberFunctions| *test-2*))
(defun print-qt-object (object)
(format t "~%This is an instance of 'Test': ~S~%" object))
(defun repl ()
;; for playing around interactively (taken from '~/eql5/src/eql.cpp')
(setf *qtpl* t
*break-on-errors* t)
(when (directory (in-home "lib/ecl-readline.fas*"))
(load (x:check-recompile (in-home "lib/ecl-readline"))))
(qsingle-shot 500 'eql::start-read-thread)
(eql::exec-with-simple-restart)) ; start event loop
(progn
(test)
(test-2)
(qlater 'repl)) ; QLATER: don't block call from 'main.cpp'

19
Qt_EQL/tutorial/test.pro Normal file
View file

@ -0,0 +1,19 @@
QT += widgets printsupport uitools
TEMPLATE = app
CONFIG += no_keywords release
INCLUDEPATH += /usr/local/include
LIBS += -L/usr/local/lib -lecl -L$$[QT_INSTALL_LIBS] -leql5
TARGET = test
DESTDIR = ./
OBJECTS_DIR = ./tmp/
MOC_DIR = ./tmp/
win32 {
include(../../src/windows.pri)
}
HEADERS += test.h
SOURCES += test.cpp \
main.cpp

View file

@ -19,10 +19,13 @@ In `src/` do:
```
$ ecl -shell make # will take a while
$ qmake eql5.pro # comment out all modules you don't need
$ qmake eql5.pro
$ make
$ sudo make install # Unix only
$ sudo make install # Unix
```
*Please note that the eql5 libs are now installed in `$$[QT_INSTALL_LIBS]`, so
you'll need to manually remove any eventually present old version from e.g.
`/usr/lib/`.*
Now you should be able to run `eql5`.
@ -113,9 +116,9 @@ variable to switch between the both on Unix (which requires the different Qt
$ export QT_SELECT=5
```
Please note that you need to build EQL5 in a path where you will keep it (you
can't move around the whole file tree later, since the build path needs to be
hard-coded in the executable).
Please note that you should build EQL5 in a path where you will keep it (you
can't move around the whole file tree later, since the path to find some
example files is hard-coded in the executable.)

View file

@ -52,10 +52,10 @@ Item {
font.pixelSize: 32
NumberAnimation on rotation {
from: 0; to: 360;
easing.type: Easing.InOutElastic;
duration: 3000;
loops: Animation.Infinite;
from: 0; to: 360
easing.type: Easing.InOutElastic
duration: 3000
loops: Animation.Infinite
}
}
}

View file

@ -44,9 +44,9 @@
(flet ((str (x)
(format nil "~:D" x)))
(x:when-it (funcall-protect (lambda (x) (float x *precision*)) n)
(|setText| *float* (princ-to-string x:it)
(|setText| *float* (princ-to-string x:it))
#+darwin
(|repaint| *float*)))
(|repaint| *float*))
(let* ((num (str (numerator n)))
(den (str (denominator n)))
(dif (- (length den) (length num))))

View file

@ -11,13 +11,15 @@
;;; - call '(eql:qml)'
;;; - generated file is 'lisp/ui-vars.lisp' (replaced without warning)
;;(pushnew :sailfish *features*) ; for testing
(defpackage qml-ui-variables
(:use :cl :eql))
(in-package :qml-ui-variables)
(defparameter *qml-items* (qfind-children #+sailfish (qml:root-item)
#-sailfish qml:*quick-view*))
(defparameter *qml-items* #+sailfish (cons (qml:root-item) (qfind-children (qml:root-item)))
#-sailfish (qfind-children qml:*quick-view*))
(defun class-name* (item)
(let ((name (|className| (|metaObject| item))))
@ -43,15 +45,15 @@
:test 'string=)))
list))
(defun one-space (string)
(x:join (remove-if 'x:empty-string (x:split string))))
(defun grep (name)
(flet ((one-space (s)
(x:join (remove-if 'x:empty-string (x:split s)))))
(let ((s (ext:run-program "grep" (list "-rn"
#-sailfish "--include" #-sailfish "*.qml"
(format nil "objectName:[[:space:]]*~S" name)
"qml/"))))
(loop :for line = (read-line s nil nil)
:while line :collect (one-space line)))))
(let ((s (ext:run-program "sh" (list "-c"
(format nil "find qml/ -name *.qml -print0 | xargs -0 grep -rn \"objectName:[[:space:]]*\\\"~A\\\"\""
name)))))
(loop :for line = (read-line s nil nil)
:while line :collect (one-space line))))
(defun collect ()
(setf *qml-items*
@ -62,6 +64,7 @@
(max-class 0)
collected not-unique grepped)
(dolist (item *qml-items*)
(print item)
(let* ((name (|objectName| item))
(class* (class-name* item))
(grep (grep name))
@ -72,6 +75,10 @@
(not (find (first grep) grepped :test 'string=))))
(when (and (find name not-unique :test 'string=)
(rest occur))
#+sailfish
(princ (format nil "~%~%### QML: not unique:~%~%~A~%~%"
(x:join grep #\Newline)))
#-sailfish
(qmsg (format nil "<font color=red>QML: not unique:</font><br><pre>~A</pre>"
(x:join grep #\Newline)))
(return-from collect :error))

View file

@ -2,18 +2,26 @@
;;; SIMPLE AND SAFE SLIME MODE
;;; ==========================
;;;
;;; Loading this file before loading EQL code guarantees running EQL functions in the GUI thread.
;;; Loading this file before calling any EQL function (involving the UI)
;;; guarantees running EQL functions on the UI thread.
;;;
;;; This means that we don't need a Slime REPL-hook, making it safe to evaluate any EQL code in
;;; Slime, both on the REPL and using 'eval-region'.
;;; This means that we don't need a Slime REPL-hook, making it safe to evaluate
;;; any EQL code in Slime, both on the REPL and using 'eval-region'.
;;;
;;; The only drawback is a little more consing for every EQL function call, but allowing to safely
;;; call GUI functions from any ECL thread.
;;; The only drawback is a little more consing for every EQL function call, but
;;; allowing to safely call UI functions from any ECL thread.
;;;
;;; Note also that wrapping functions in QRUN* is basically the same as a direct call, if called
;;; from the ECL main thread (GUI thread), so it will add almost no overhead.
;;; Since most EQL function calls are driven by the Qt event loop anyway, you won't even notice
;;; the presence of macro QRUN* (performance wise).
;;; Note also that wrapping functions in QRUN* (like done here, see below) is
;;; basically the same as a direct call, if called from the ECL main thread (UI
;;; thread), so it will add almost no overhead. Since most EQL function calls
;;; are driven by the Qt event loop anyway, you won't even notice the presence
;;; of macro QRUN* (performance wise).
;;;
;;; N.B: If you want to start/run EQL in a thread (other than the UI one), you
;;; just need to add this file to your project. Note that any EQL function call
;;; will now do a thread switch internally, so you may experience (much) slower
;;; execution. See also comment in 'examples/X-extras/primes-thread.lisp' about
;;; manually wrapping repeated calls in macro QRUN*.
;;;
(in-package :eql)

View file

@ -19,7 +19,7 @@ int catch_all_qexec() {
int main(int argc, char** argv) {
EQL::ini(argv); // best initialized here
//EQL::ini(argc, argv); // obsolete, was needed on some OS years ago
QCoreApplication::setAttribute(Qt::AA_ShareOpenGLContexts); // for Qt WebEngine
QApplication qapp(argc, argv);

View file

@ -1,5 +1,5 @@
(defsystem :my-app
:serial t
:depends-on (:alexandria)
;;:depends-on (:alexandria)
:components ((:file "package")
(:file "lisp/my")))

View file

@ -2,7 +2,7 @@ QT += widgets printsupport uitools
TEMPLATE = app
CONFIG += no_keywords release
INCLUDEPATH += /usr/local/include
LIBS += -lecl -L. -lmy_app_lib -L/usr/local/lib -leql5
LIBS += -L/usr/local/lib -lecl -L$$[QT_INSTALL_LIBS] -leql5 -L. -lmy_app_lib
TARGET = my_app
DESTDIR = ./
OBJECTS_DIR = ./tmp/

View file

@ -13,7 +13,9 @@ int DynObject::event_filter_handle = 0;
QObject* DynObject::currentSender = 0;
DynObject::DynObject(QObject* par) : QObject(par), event_filters(false) {
qApp->installEventFilter(this); }
// note: QADD-EVENT-FILTER will not work if EQL5 is not running on the UI thread
if(thread() == QApplication::instance()->thread()) {
qApp->installEventFilter(this); }}
int DynObject::qt_metacall(QMetaObject::Call c, int id, void** args) {
if(QMetaObject::InvokeMetaMethod == c) {

View file

@ -177,7 +177,8 @@ void iniCLFunctions() {
DEFUN ("%qvariant-equal", qvariant_equal2, 2)
DEFUN ("qvariant-from-value", qvariant_from_value, 2)
DEFUN ("qvariant-value", qvariant_value, 1)
DEFUN ("qversion", qversion, 0) }
DEFUN ("qversion", qversion, 0)
DEFUN ("%set-shutdown-p", set_shutdown_p, 1) }
// QtObject methods
@ -1266,6 +1267,10 @@ static MetaArg toMetaArg(const QByteArray& sType, cl_object l_arg) {
_cstring_buffer_ << ba;
const char** s = new const char*(ba.constData());
p = s; }}
else if("cl_object" == sType) {
quintptr l = (quintptr)l_arg;
void** v = new void*((void*)l);
p = v; }
else if(T_QFileInfo == n) p = new QFileInfo(toQFileInfo(l_arg));
else if(T_QFileInfoList == n) p = new QFileInfoList(toQFileInfoList(l_arg));
else if(T_QGradient == n) p = new QGradient(toQGradient(l_arg));
@ -1715,41 +1720,47 @@ cl_object qapropos2(cl_object l_search, cl_object l_class, cl_object l_type, cl_
if(obj.pointer) {
qt_eql = true;
mo = ((QObject*)obj.pointer)->metaObject();
classes << QString("%1 : %2")
.arg(mo->className())
.arg(QString(obj.className()))
.toLatin1(); }}}
classes << QByteArray(); }}} // dummy
cl_object l_docs = ECL_NIL;
Q_FOREACH(QByteArray cl, classes) {
bool found = false;
bool non = LObjects::n_names.contains(cl);
if(non || qt_eql || LObjects::q_names.contains(cl)) {
cl_object l_doc_pro = ECL_NIL;
cl_object l_doc_slo = ECL_NIL;
cl_object l_doc_sig = ECL_NIL;
cl_object l_doc_ovr = ECL_NIL;
if(!non) {
l_doc_pro = collect_info("properties", cl, search, non, &found, mo, no_offset); }
cl_object l_doc_met = collect_info("methods", cl, search, non, &found, mo);
if(!non) {
l_doc_slo = collect_info("slots", cl, search, non, &found, mo);
l_doc_sig = collect_info("signals", cl, search, non, &found, mo); }
l_doc_ovr = collect_info("override", cl, search, non, &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); }
if((l_doc_ovr != ECL_NIL) && !qt_eql) {
l_doc = CONS(CONS(STRING("Override:"), l_doc_ovr), l_doc); }
l_doc = cl_nreverse(l_doc);
if(l_doc != ECL_NIL) {
l_docs = CONS(CONS(STRING_COPY(cl.data()), l_doc), l_docs); }}}}
do {
if(qt_eql && LObjects::q_names.contains(mo->className())) {
break; }
Q_FOREACH(QByteArray cl, classes) {
bool found = false;
bool non = LObjects::n_names.contains(cl);
if(qt_eql) {
cl = QString("%1 : %2")
.arg(mo->className())
.arg(mo->superClass()->className())
.toLatin1(); }
if(non || qt_eql || LObjects::q_names.contains(cl)) {
cl_object l_doc_pro = ECL_NIL;
cl_object l_doc_slo = ECL_NIL;
cl_object l_doc_sig = ECL_NIL;
cl_object l_doc_ovr = ECL_NIL;
if(!non) {
l_doc_pro = collect_info("properties", cl, search, non, &found, mo, no_offset); }
cl_object l_doc_met = collect_info("methods", cl, search, non, &found, mo);
if(!non) {
l_doc_slo = collect_info("slots", cl, search, non, &found, mo);
l_doc_sig = collect_info("signals", cl, search, non, &found, mo); }
l_doc_ovr = collect_info("override", cl, search, non, &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); }
if((l_doc_ovr != ECL_NIL) && !qt_eql) {
l_doc = CONS(CONS(STRING("Override:"), l_doc_ovr), l_doc); }
l_doc = cl_nreverse(l_doc);
if(l_doc != ECL_NIL) {
l_docs = CONS(CONS(STRING_COPY(cl.data()), l_doc), l_docs); }}}}
} while(qt_eql && (mo = mo->superClass()));
cl_object l_ret = cl_nreverse(l_docs);
return l_ret; }
@ -1854,6 +1865,10 @@ cl_object qset_gc(cl_object l_obj) {
_garbage_collection_ = (l_obj != ECL_NIL);
ecl_return1(ecl_process_env(), l_obj); }
cl_object set_shutdown_p(cl_object l_obj) {
EQL::cl_shutdown_p = (l_obj != ECL_NIL);
ecl_return1(ecl_process_env(), l_obj); }
enum { GarbageCollection = 1 };
cl_object qdelete2(cl_object l_obj, cl_object l_later) {
@ -2726,7 +2741,6 @@ cl_object qexec2(cl_object l_milliseconds) {
timer->start(toInt(l_milliseconds));
EQL::eventLoop->exec();
return l_milliseconds; }
QCoreApplication::exit(); // prevent "The event loop is already running"
QApplication::exec();
return ECL_T; }
@ -3022,16 +3036,17 @@ QVariantList lispToQVariantList(cl_object l_list) {
// converts (nested) Lisp lists to (nested) QVariant lists
QVariantList l;
if(ECL_LISTP(l_list)) {
cl_object l_do_list = l_list;
for(cl_object l_do_list = l_list; l_do_list != ECL_NIL; l_do_list = cl_cdr(l_do_list)) {
cl_object l_el = cl_car(l_do_list);
l << lispToQVariant(l_el); }}
return l; }
cl_object qjs_call(cl_object l_item, cl_object l_name, cl_object l_args) {
// direct, fast JS calls, see 'qml-lisp' in QML examples
// direct, fast JS calls, see 'lisp/qml.lisp'
// max. 10 arguments
// supported argument types: T, NIL, INTEGER, FLOAT, STRING, (nested) LIST of mentioned arguments
//
// N.B. does not support default arguments in JS
ecl_process_env()->nvalues = 1;
const int MAX = 10;
QVariant arg[MAX];

View file

@ -295,6 +295,7 @@ cl_object qvariant_equal2 (cl_object, cl_object);
cl_object qvariant_from_value (cl_object, cl_object);
cl_object qvariant_value (cl_object);
cl_object qversion ();
cl_object set_shutdown_p (cl_object);
struct EQL_EXPORT QtObject {
void* pointer;

View file

@ -9,7 +9,7 @@
#include <QStringList>
#include <QDebug>
const char EQL::version[] = "21.3.3"; // March 2021
const char EQL::version[] = "24.2.1"; // February 2024
extern "C" void ini_EQL(cl_object);
@ -41,18 +41,22 @@ EQL::EQL() : QObject() {
#ifdef COMPILE_ANDROID
qInstallMessageHandler(logMessageHandler); // see above
#endif
if(!cl_booted) {
if(!cl_booted_p) {
cl_boot(1, (char**)_argv_); }
iniCLFunctions();
LObjects::ini(this);
ecl_init_module(NULL, ini_EQL); } // see "src/make.lisp"
EQL::~EQL() {
if(!EQL::cl_shutdown_p) {
cl_shutdown(); }}
void EQL::ini(char** argv) {
cl_booted = true;
cl_booted_p = true;
cl_boot(1, argv); }
void EQL::ini(int argc, char** argv) {
cl_booted = true;
cl_booted_p = true;
cl_boot(argc, argv); }
static void safe_eval_debug(const char* lisp_code) {
@ -211,6 +215,37 @@ void EQL::exec(QWidget* widget, const QString& lispFile, const QString& slimeHoo
if(exec_with_simple_restart) {
eval("(eql::exec-with-simple-restart)"); }}
void EQL::addObject(QObject* object, const QByteArray& varName, bool defineWrappers, bool lispifyNames) {
cl_object l_symbol = ECL_NIL;
int p = varName.indexOf(':');
if(p == -1) {
// use current package
l_symbol = cl_intern(1,
STRING_COPY(varName.toUpper().constData())); }
else {
// use provided package
QByteArray pkg = varName.left(p);
QByteArray var = varName.mid(varName.lastIndexOf(':') + 1);
l_symbol = cl_intern(2,
STRING_COPY(var.toUpper().constData()),
cl_find_package(STRING_COPY(pkg.toUpper().constData()))); }
cl_object l_object = qt_object_from_name(LObjects::vanillaQtSuperClassName(object->metaObject()), object);
// 'defvar'
ecl_defvar(l_symbol, l_object);
if(defineWrappers) {
// 'define-qt-wrappers'
STATIC_SYMBOL_PKG (s_define_qt_wrappers, "DEFINE-QT-WRAPPERS", "EQL")
STATIC_SYMBOL_PKG (s_do_not_lispify, "DO-NOT-LISPIFY", "KEYWORD")
if(lispifyNames) {
cl_funcall(2,
s_define_qt_wrappers,
l_object); }
else {
cl_funcall(3,
s_define_qt_wrappers,
l_object,
s_do_not_lispify); }}}
void EQL::runOnUiThread(void* function_or_closure) {
const cl_env_ptr l_env = ecl_process_env();
CL_CATCH_ALL_BEGIN(l_env) {
@ -222,7 +257,8 @@ void EQL::runOnUiThread(void* function_or_closure) {
CL_CATCH_ALL_END; }
EQL::EvalMode EQL::evalMode = DieOnError;
bool EQL::cl_booted = false;
bool EQL::cl_booted_p = false;
bool EQL::cl_shutdown_p = false;
bool EQL::return_value_p = false;
bool EQL::qexec = true;
QEventLoop* EQL::eventLoop = 0;

View file

@ -10,7 +10,7 @@ SUBDIRS = help \
sql \
svg \
webengine \
# webkit
#webkit
# -----------------------------------------------------
sailfish {

View file

@ -25,6 +25,7 @@ class EQL_EXPORT EQL : public QObject {
Q_OBJECT
public:
EQL();
~EQL();
enum EvalMode {
DebugOnError,
@ -32,7 +33,8 @@ public:
DieOnError,
};
static bool cl_booted;
static bool cl_booted_p;
static bool cl_shutdown_p;
static bool return_value_p;
static bool qexec;
static const char version[];
@ -40,6 +42,7 @@ public:
static void ini(int, char**);
static void ini(char**);
static void eval(const char*, const EvalMode = evalMode);
static void addObject(QObject*, const QByteArray&, bool = false, bool = true);
static EvalMode evalMode;
void exec(const QStringList&);

View file

@ -10,7 +10,7 @@ MOC_DIR = ./tmp/
QMAKE_RPATHDIR = /usr/lib
linux {
unix {
target.path = /usr/bin
}
@ -18,7 +18,7 @@ osx {
target.path = /usr/local/bin
}
INSTALLS = target
INSTALLS = target
win32 {
include(windows.pri)

View file

@ -19,7 +19,7 @@ VERSION = $$(EQL_VERSION)
include.files = eql5/*
linux {
unix {
include.path = /usr/include/eql5
target.path = $$[QT_INSTALL_LIBS]
}
@ -57,15 +57,14 @@ static {
CONFIG += create_prl
DEFINES += STATIC_MODULES
QT += qml multimedia network quick sql
DESTDIR = ./
LIBS -= -lecl -L. -lini_eql5 -L/usr/local/lib
OBJECTS_DIR = ./tmp/static/
MOC_DIR = ./tmp/static/
combinedlib.files = libeql5.a libeql5.prl
combinedlib.extra = ecl -shell make-static.lisp
combinedlib.path = $$[QT_INSTALL_LIBS]
INSTALLS = combinedlib
INSTALLS = combinedlib target
}
HEADERS += gen/_lobjects.h \

View file

@ -8,7 +8,7 @@
(defparameter *all-wrappers* (append (loop :for i :from 1 :to 12 :collect (format nil "all-wrappers-~D" i))
(loop :for i :from 1 :to 2 :collect (format nil "all-wrappers-webengine-~D" i))))
(defparameter *lisp-files* (append '("x" "package" "ini"
(defparameter *lisp-files* (append '("x" "package" "ini" "qml"
"enums1" "enums2" "enums3" "enums4" "enums5"
"special-extensions")
*all-wrappers*))

View file

@ -648,29 +648,38 @@
(defun define-qt-wrappers (qt-library &rest what)
"args: (qt-library &rest what)
Defines Lisp methods for all Qt methods/signals/slots of given library.<br>(See example <code>Qt_EQL/trafficlight/</code>).
Defines Lisp methods for all Qt methods/signals/slots of given library.<br>(See example <code>Qt_EQL/trafficlight/</code>). Pass :do-not-lispify if you don't want C++ names to be lispified automatically, like in: 'doThis()' -> '(do-this)' / 'do_that()' -> '(do-that)'.
(define-qt-wrappers *c++*) ; generate wrappers (see \"Qt_EQL/\")
(define-qt-wrappers *c++* :slots) ; Qt slots only (any of :methods :slots :signals)
(my-qt-function *c++* x y) ; instead of: (! \"myQtFunction\" (:qt *c++*) x y)"
(let ((all-functions (cdar (qapropos* nil (ensure-qt-object qt-library)))))
(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)))
what))
(unless what
(setf what '(:methods :slots :signals)))
(dolist (functions (loop :for el :in what :collect
(concatenate 'string (string-capitalize el) ":")))
(dolist (fun (rest (find functions all-functions
:key 'first :test 'string=)))
(let* ((p (position #\( fun))
(qt-name (subseq fun (1+ (position #\Space fun :from-end t :end p)) p))
(lisp-name (intern (with-output-to-string (s)
(x:do-string (ch qt-name)
(if (upper-case-p ch)
(format s "-~C" ch)
(write-char (char-upcase ch) s)))))))
;; no way to avoid EVAL here (excluding non-portable hacks)
(eval `(defgeneric ,lisp-name (object &rest arguments)))
(eval `(defmethod ,lisp-name ((object qt-object) &rest arguments)
(%qinvoke-method object :qt ,qt-name arguments))))))))
(dolist (class-functions all-functions)
(dolist (fun (rest (find functions (cdr class-functions)
:key 'first :test 'string=)))
(let* ((p (position #\( fun))
(qt-name (subseq fun (1+ (position #\Space fun :from-end t :end p)) p))
(lisp-name (intern (if lispify
(with-output-to-string (s)
(x:do-string (ch qt-name)
(cond ((upper-case-p ch)
(format s "-~C" ch))
((char= #\_ ch)
(write-char #\- s))
(t
(write-char (char-upcase ch) s)))))
qt-name))))
;; no way to avoid EVAL here (excluding non-portable hacks)
(eval `(defgeneric ,lisp-name (object &rest arguments)))
(eval `(defmethod ,lisp-name ((object qt-object) &rest arguments)
(%qinvoke-method object :qt ,qt-name arguments)))))))))
#+linux
(defun %ini-auto-reload (library-name watcher on-file-change)
@ -751,6 +760,7 @@
(qfun (qapp) "aboutToQuit")
(qfun (qapp) "quit")
(ffi:c-inline nil nil :void "cl_shutdown();" :one-liner t :side-effects t)
(%set-shutdown-p t) ; prevent multiple calls of 'cl_shutdown()', see 'eql.cpp:~EQL()'
(if (minusp exit-status)
(ffi:c-inline nil nil :void "abort();" :one-liner t :side-effects t)
(ffi:c-inline (exit-status) (:int) :void "exit(#0);" :one-liner t :side-effects t)))

View file

@ -122,6 +122,7 @@
(:export
#:*quick-view*
#:*root*
#:*root-item*
#:*caller*
#:children
#:file-to-url

View file

@ -10,6 +10,7 @@
(defvar *quick-view* nil)
(defvar *caller* nil)
(defvar *root* nil)
(defvar *root-item* nil) ; see note in 'find-quick-item'
(defun string-to-symbol (name)
(let ((upper (string-upcase name))
@ -77,12 +78,33 @@
(|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))))))
"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."
;;
;; when to use *ROOT-ITEM*
;;
;; say you have a Repeater QML item with multiple instances of the same
;; QQuickItem. The children of those QQuickItems all have the same object
;; names, respectively. In order to access those child items, we need to
;; search in the specific item of the Repeater.
;;
;; So, we locally bind *ROOT-ITEM* in order to find a specific child item
;; inside the Repeater (note QRUN* which is needed because of the special
;; variable and threads):
;;
;; (setf qml:*root-item* (q! |itemAt| ui:*repeater* 0)) ; (1) set
;; ;; everything we do here will only affect children of the first
;; ;; item in ui:*repeater* (see index 0 above)
;; (q< |text| ui:*edit*)
;; (setf qml:*root-item* nil) ; (2) reset
;;
;; N.B. we need SETF (instead of LET) because of the global var and threads
;; (QRUN* is used internally here)
;;
(let ((parent (or *root-item* (root-item))))
(unless (qnull parent)
(if (string= (|objectName| parent) object-name)
parent
(qt-object-? (qfind-child parent object-name))))))
(defun quick-item (item/name)
(cond ((stringp item/name)
@ -172,7 +194,7 @@
(print-js-readably object))))
(defun %qjs (item/name function-name &rest arguments)
;; QJS-CALL is defined in EQL5, function 'ecl_fun.cpp'
;; see 'ecl_fun.cpp::qjs_call()'
(qrun* (eql::qjs-call (quick-item item/name) function-name arguments)))
(defmacro qjs (function-name item/name &rest arguments)
@ -211,13 +233,13 @@
;;; for SailfishOS
(defun ini-sailfish (file/url &optional root quick-view)
(defun ini-sailfish (file/url &optional root quick-view (start-event-loop t))
"Pass either just the main QML file (during development), or all of: main QML url, root url, Sailfish QQuickView (in final app)."
(when (and root
(qt-object-p root)
(= (qid "QUrl") (qt-object-id root)))
(setf *root* (x:cc (|toString| root) "/")))
(setf *quick-view* (or quick-view (qnew "QQuickView")))
(qconnect (|engine| *quick-view*) "quit()" 'qquit)
(qnew "QQmlFileSelector(QQmlEngine*,QObject*)" (|engine| *quick-view*) *quick-view*)
(|setSource| *quick-view* (if (stringp file/url)
(file-to-url file/url)
@ -228,12 +250,6 @@
#.(make-string 2 :initial-element #\Newline))))
(|setResizeMode| *quick-view* |QQuickView.SizeRootObjectToView|)
(|show| *quick-view*)
(when quick-view
;; for compiled app;
;; we add a fallback restart for eventual connections from Slime;
;; this is needed if an exception occurs during event driven
;; function calls (which would otherwise block the Slime REPL)
(loop
(with-simple-restart (restart-qt-events "Restart Qt event processing.")
(qexec)))))
(when (and quick-view start-event-loop)
(qexec)))

View file

@ -151,7 +151,7 @@
(defun path (name)
"Needed because ECL uses base strings (not Unicode) for pathnames internally."
#+(or darwin linux)
#+unix
(funcall (intern "QUTF8" :eql) name)
#+win32
(if (< (funcall (intern "%WINDOWS-VERSION" :eql)) #xa0)

View file

@ -55,10 +55,11 @@ int catch_all_qexec() {
int main(int argc, char** argv) {
EQL::ini(argc, argv); // best initialized here
QCoreApplication::setAttribute(Qt::AA_ShareOpenGLContexts); // for Qt WebEngine
QApplication qapp(argc, argv);
EQL::ini(argc, argv); // best initialized here
QStringList args(QCoreApplication::arguments());
if(args.contains("-h") || (args.contains("--help"))) {
std::cout << "Usage: eql5 [file] [-qtpl] [-qgui] [-quic file.ui [:ui-package] [:maximized]] [-slime] [-norc] [-debug-on-error]" << std::endl;

View file

@ -1,3 +1,3 @@
(let* ((static-libs (mapcar #'file-namestring (directory "libeql5_*.a")))
(mri (format nil "create libeql5static.a~%addlib libini_eql5.a~%addlib libeql5.a~%~{addlib ~a~%~}save~%end~%" static-libs)))
(ext:system (format nil "sh -c 'echo ~s | ar -M && mv libeql5static.a libeql5.a'" mri)))
(mri (format nil "create libeql5static.a~%addlib libini_eql5.a~%addlib ../libeql5.a~%~{addlib ~a~%~}save~%end~%" static-libs)))
(ext:system (format nil "sh -c 'echo ~s | ar -M && mv libeql5static.a ../libeql5.a'" mri)))

View file

@ -10,12 +10,12 @@ OBJECTS_DIR = ./tmp/help/
MOC_DIR = ./tmp/help/
VERSION = $$(EQL_VERSION)
linux {
target.path = $$[QT_INSTALL_LIBS]
unix {
target.path = $$[QT_INSTALL_LIBS]/eql5
}
osx {
target.path = /usr/local/lib
target.path = /usr/local/lib/eql5
}
INSTALLS = target

View file

@ -10,12 +10,12 @@ OBJECTS_DIR = ./tmp/multimedia/
MOC_DIR = ./tmp/multimedia/
VERSION = $$(EQL_VERSION)
linux {
target.path = $$[QT_INSTALL_LIBS]
unix {
target.path = $$[QT_INSTALL_LIBS]/eql5
}
osx {
target.path = /usr/local/lib
target.path = /usr/local/lib/eql5
}
INSTALLS = target

View file

@ -10,12 +10,12 @@ OBJECTS_DIR = ./tmp/network/
MOC_DIR = ./tmp/network/
VERSION = $$(EQL_VERSION)
linux {
target.path = $$[QT_INSTALL_LIBS]
unix {
target.path = $$[QT_INSTALL_LIBS]/eql5
}
osx {
target.path = /usr/local/lib
target.path = /usr/local/lib/eql5
}
INSTALLS = target

View file

@ -10,12 +10,12 @@ OBJECTS_DIR = ./tmp/quick/
MOC_DIR = ./tmp/quick/
VERSION = $$(EQL_VERSION)
linux {
target.path = $$[QT_INSTALL_LIBS]
unix {
target.path = $$[QT_INSTALL_LIBS]/eql5
}
osx {
target.path = /usr/local/lib
target.path = /usr/local/lib/eql5
}
INSTALLS = target

View file

@ -10,12 +10,12 @@ OBJECTS_DIR = ./tmp/sql/
MOC_DIR = ./tmp/sql/
VERSION = $$(EQL_VERSION)
linux {
target.path = $$[QT_INSTALL_LIBS]
unix {
target.path = $$[QT_INSTALL_LIBS]/eql5
}
osx {
target.path = /usr/local/lib
target.path = /usr/local/lib/eql5
}
INSTALLS = target

View file

@ -10,7 +10,7 @@ OBJECTS_DIR = ./tmp/svg/
MOC_DIR = ./tmp/svg/
VERSION = $$(EQL_VERSION)
linux {
unix {
target.path = $$[QT_INSTALL_LIBS]
}

View file

@ -10,12 +10,12 @@ OBJECTS_DIR = ./tmp/webengine/
MOC_DIR = ./tmp/webengine/
VERSION = $$(EQL_VERSION)
linux {
target.path = $$[QT_INSTALL_LIBS]
unix {
target.path = $$[QT_INSTALL_LIBS]/eql5
}
osx {
target.path = /usr/local/lib
target.path = /usr/local/lib/eql5
}
INSTALLS = target

View file

@ -11,12 +11,12 @@ MOC_DIR = ./tmp/webkit/
VERSION = $$(EQL_VERSION)
macx:QT += network
linux {
target.path = $$[QT_INSTALL_LIBS]
unix {
target.path = $$[QT_INSTALL_LIBS]/eql5
}
osx {
target.path = /usr/local/lib
target.path = /usr/local/lib/eql5
}
INSTALLS = target