diff --git a/Qt_EQL/tutorial/README.md b/Qt_EQL/tutorial/README.md new file mode 100644 index 0000000..24dacd6 --- /dev/null +++ b/Qt_EQL/tutorial/README.md @@ -0,0 +1,19 @@ +## 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 + diff --git a/Qt_EQL/tutorial/main.cpp b/Qt_EQL/tutorial/main.cpp new file mode 100644 index 0000000..5b07e6c --- /dev/null +++ b/Qt_EQL/tutorial/main.cpp @@ -0,0 +1,27 @@ +#include "test.h" +#include +#include +#include + +int main(int argc, char* argv[]) { + QApplication app(argc, argv); + + QLabel* main = new QLabel("

Main Window

"); + main->setAlignment(Qt::AlignCenter); + main->resize(600, 400); + main->show(); + + // we need an instance for 'define-qt-wrappers' and 'new-instance' to work; + // we pass the main widget as parent and a unique 'objectName', so we can + // find it from Lisp + + Test test(main, "test"); + + EQL eql; + EQL::eval("(in-package :eql-user)"); + 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 +} + diff --git a/Qt_EQL/tutorial/test.cpp b/Qt_EQL/tutorial/test.cpp new file mode 100644 index 0000000..6ebaf9b --- /dev/null +++ b/Qt_EQL/tutorial/test.cpp @@ -0,0 +1,37 @@ +#include "test.h" +#include +#include + +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) { + for(cl_object l_dolist = data; cl_car(l_dolist) != ECL_NIL; l_dolist = cl_cdr(l_dolist)) { + cl_object l_el = cl_car(l_dolist); + cl_print(1, l_el); + } + 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)); +} + diff --git a/Qt_EQL/tutorial/test.h b/Qt_EQL/tutorial/test.h new file mode 100644 index 0000000..6db8fb5 --- /dev/null +++ b/Qt_EQL/tutorial/test.h @@ -0,0 +1,27 @@ +#ifndef APP_H +#define APP_H + +#include +#include + +class Test : public QObject { + Q_OBJECT +public: + Test(QObject*, const 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*, const QString&); + +public Q_SLOTS: + // you may pass any type found in '~/eql5/src/ecl_fun.cpp::toMetaArg()' + QString concat(const QStringList&); + + // pass Lisp data + void processData(cl_object); + + // call back to Lisp + void printMe(); +}; + +#endif diff --git a/Qt_EQL/tutorial/test.lisp b/Qt_EQL/tutorial/test.lisp new file mode 100644 index 0000000..6939289 --- /dev/null +++ b/Qt_EQL/tutorial/test.lisp @@ -0,0 +1,45 @@ +(in-package :eql-user) + +;; find main widget of app +(defvar *main-widget* (first (|topLevelWidgets.QApplication|))) + +;; find 'Test' instance by its 'objectName' +(defvar *test* (qfind-child *main-widget* "test")) + +;; the following defines generic functions for all Qt signals, +;; Qt slots and functions declared Q_INVOKABLE + +(define-qt-wrappers *test*) + +;; now we can call those functions + +(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 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) + (qlater 'repl)) ; QLATER: don't block call from 'main.cpp' + diff --git a/Qt_EQL/tutorial/test.pro b/Qt_EQL/tutorial/test.pro new file mode 100644 index 0000000..9e9939c --- /dev/null +++ b/Qt_EQL/tutorial/test.pro @@ -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 +