/* * SPL - The SPL Programming Language * Copyright (C) 2004, 2005 Clifford Wolf * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * * mod_qt.c: SPL bindings for Qt, using the SMOKE library * * WARNING: I am a C (not C++) coder. So this is a pretty wild mix between C * and C++. Expect the worst from both worlds when reading this source code! * * TODOs: * - Pass arguments and return values of overloaded virtual functions * - Optimize lookups in qt_obj_hnd_list using trees */ /** * The SPL/Qt Module * * This module provides a thin wrapper for the Qt toolkit. * It is using the SMOKE library from the kdebindings package. */ /** * The SPL/Qt Module * * This module provides a thin wrapper for the Qt toolkit. * It is using the SMOKE library from the kdebindings package. * * All Qt classes are available via the [[qt]] namespace and can be instanciated * using the "new" keyword, like normal SPL objects: * * load "qt"; * * var a = new qt.QApplication(); * var l = new qt.QLabel(undef); * l.setText("Hello World!"); * a.setMainWidget(l); * l.show(); * a.exec(); * * Qt objects can be used like normal SPL objects. However: Only the qt methods * are available from SPL. The object properties can't be accessed directly. * * The qt.QApplication class has a non-standard constructor which does not take * any arguments. It is recommended to use this constructor for creating a * qt.QApplication instance. * * Some data types can't be converted by this module. So some Qt methods might * be unaccesable thru this module. * * This is a thin wrapper for the Qt C++ libraries. It is possible to produce * segmentation faults or other errors by using this module incorrectly. * * This module does not allow you to straight-forward derive your own classes * from qt classes. However, this functionality might be added later. * * All Qt Objects have a pseudo member-variable ".class" which contains the * name of the Qt class for this object. * * The pseudo member-variable ".ptr" contains a text representation of the * memory address of Qt Object. This can be used e.g. for checking if two SPL * Qt Object handlers point to the same Qt Object. * * Static methods can be called directly from the qt namespace without * instanciating the class. Enums behave like static methods without arguments * which do return the numeric value for the enum. * * The complete Qt Reference Documentation can be found at: * http://doc.trolltech.com/ * * An SPL Virtual Machine which loaded the "qt" module is unable to dump its * state. So the SPL dump/restore feature doesn't work for SPL/Qt programs. */ //manual SPL_Qt; #include #include #include #ifndef MAKEDEPS # include # include # include # include // this is needed for SplSignalHandler # include # include #endif #include "spl.h" #if defined __GNUC__ && (__GNUC__ >= 4 || (__GNUC__ == 3 && __GNUC_MINOR__ >= 4)) # define HIDDEN_CLASS __attribute__((visibility("hidden"))) # define UNUSED(x) x __attribute__((unused)) #elif defined(__LCLINT__) # define UNUSED(x) /*@unused@*/ x #else # define HIDDEN_CLASS # define UNUSED(x) x # define qt_obj_hnd_guard spl_mod_qt__qt_obj_hnd_guard # define SplSmokeBinding spl_mod_qt__SplSmokeBinding # define SplEventHandler spl_mod_qt__SplEventHandler # define SplSignalHandler spl_mod_qt__SplSignalHandler #endif #ifdef QT_MOC_CPP # undef HIDDEN_CLASS # define HIDDEN_CLASS #endif /* ----------------------- Some global variables ----------------------- */ extern "C" void SPL_ABI(spl_mod_qt_init)(struct spl_vm *vm, struct spl_module *mod, int restore); extern "C" void SPL_ABI(spl_mod_qt_done)(struct spl_vm *vm, struct spl_module *mod); static int callback_task_id = 0; static int enable_debug_output = 0; static int use_kde_libs = 0; #define debugf(...) do { if (enable_debug_output) fprintf(stderr, __VA_ARGS__); } while (0) static Smoke *qts; static Smoke::Index classId_QObject; static Smoke::Index classId_QWidget; static Smoke::Index classId_QApplication; static struct qt_obj_hnd *qt_obj_hnd_list = 0; static struct qt_obj_hnd *qt_obj_hnd_late_destroy_list = 0; static struct qt_obj_virtual *qt_obj_virtual_list = 0; /* ---------------------- Per-VM Module Data ---------------------- */ struct qt_moddata { struct spl_node *cblist; }; static struct qt_moddata *getmoddata(struct spl_vm *vm) { struct spl_module *m = vm->module_list; while (m) { if (!strcmp(m->name, "qt")) return (struct qt_moddata *)m->data; m = m->next; } return 0; } /* ---------------------- Virtual Methods Interface ---------------------- */ struct qt_obj_virtual { struct qt_obj_virtual *left, *right; Smoke::Index classId, methodId; struct spl_node *callback; int callback_cbid; struct spl_vm *vm; void *obj; }; static void delete_qt_obj_virtual(struct qt_obj_virtual *it) { if (it->left) it->left->right = it->right; else qt_obj_virtual_list = it->right; if (it->right) it->right->left = it->left; if (it->vm && it->callback) { struct spl_node *cblist = getmoddata(it->vm)->cblist; char key[32]; snprintf(key, 32, "%d", it->callback_cbid); spl_delete(0, cblist, key); spl_put(it->vm, it->callback); } free(it); } static struct qt_obj_virtual *find_qt_obj_virtual(struct qt_obj_virtual *it, void *ptr, Smoke::Index classId, Smoke::Index methodId) { if (!ptr || !classId) return 0; while (it) { if (it->obj && (!methodId || it->methodId == methodId)) { // qts->cast() returns the original point if casting is not possible // this is ok in this case but may cause troubles in others if (qts->cast(ptr, classId, it->classId) == it->obj) return it; if (qts->cast(it->obj, it->classId, classId) == ptr) return it; } it = it->right; } return 0; } /* ----------------------- Qt Object HND Interface ----------------------- */ struct qt_obj_hnd { Smoke::Index classId; struct qt_obj_hnd *left, *right; class qt_obj_hnd_guard *guard; int late_destroy, autodelete; void *obj; }; struct qt_smoke_hnd { Smoke::Index typeId; Smoke::StackItem smoke_value; struct freelist *fl; }; static int qts_is_castable(Smoke::Index from, Smoke::Index to) { if (from == to) return 1; for(int p = qts->classes[from].parents; qts->inheritanceList[p]; p++) { if (qts_is_castable(qts->inheritanceList[p], to)) return 1; } return 0; } class HIDDEN_CLASS qt_obj_hnd_guard : public QObject { Q_OBJECT public: qt_obj_hnd_guard(struct qt_obj_hnd *hnd) { guarded_hnd = hnd; QObject *qo = (QObject*)qts->cast(hnd->obj, hnd->classId, classId_QObject); connect(qo, SIGNAL(destroyed()), this, SLOT(objectDestroyed())); } private: struct qt_obj_hnd *guarded_hnd; private slots: void objectDestroyed() { debugf("*** Got destroyed() signal: %p (%s)\n", guarded_hnd->obj, qts->classes[guarded_hnd->classId].className); guarded_hnd->obj = 0; } }; static void qt_obj_hnd_setguard(struct qt_obj_hnd *hnd) { if (hnd->guard) { delete hnd->guard; hnd->guard = 0; } if (hnd->obj && qts_is_castable(hnd->classId, classId_QObject)) { hnd->guard = hnd->obj ? new qt_obj_hnd_guard(hnd) : 0; debugf("*** New hnd->guard at %p for %p (%s)\n", hnd->guard, hnd->obj, qts->classes[hnd->classId].className); } } static struct qt_obj_hnd *new_qt_obj_hnd() { struct qt_obj_hnd *hnd = (struct qt_obj_hnd *)calloc(1, sizeof(struct qt_obj_hnd)); if (!qt_obj_hnd_list) { qt_obj_hnd_list = hnd; } else { hnd->right = qt_obj_hnd_list; qt_obj_hnd_list->left = hnd; qt_obj_hnd_list = hnd; } return hnd; } static struct spl_node *new_qt_obj(Smoke::Index classId, void *obj, const char *reflection_prefix) { struct qt_obj_hnd *hnd = new_qt_obj_hnd(); char *reflection_string; hnd->classId = classId; hnd->obj = obj; qt_obj_hnd_setguard(hnd); asprintf(&reflection_string, "Qt Object (%s)%s", qts->classes[classId].className, reflection_prefix); struct spl_node *value = SPL_NEW_STRING(reflection_string); value->hnode_name = strdup("qt_obj"); value->hnode_data = hnd; return value; } static struct qt_obj_hnd *find_qt_obj_hnd(struct qt_obj_hnd *it, void *ptr, Smoke::Index classId) { if (!ptr || !classId) return 0; while (it) { if (it->obj) { // qts->cast() returns the original point if casting is not possible // this is ok in this case but may cause troubles in others if (qts->cast(ptr, classId, it->classId) == it->obj) return it; if (qts->cast(it->obj, it->classId, classId) == ptr) return it; } it = it->right; } return 0; } static int qt_obj_destroy(struct qt_obj_hnd *hnd) { const char *classname = qts->classes[hnd->classId].className; char destructor[strlen(classname)+2]; sprintf(destructor, "~%s", classname); Smoke::StackItem smoke_args[1]; Smoke::Index method = qts->findMethod(classname, destructor); if (method <= 0) return 1; Smoke::Method *m = qts->methods + qts->methodMaps[method].method; Smoke::ClassFn fn = qts->classes[m->classId].classFn; fn(m->method, qts->cast(hnd->obj, hnd->classId, m->classId), smoke_args); struct qt_obj_hnd *it = find_qt_obj_hnd(qt_obj_hnd_list, hnd->obj, hnd->classId); while (it) { it->obj = 0; it = find_qt_obj_hnd(it->right, hnd->obj, hnd->classId); } return 0; } static void delete_qt_obj_hnd(struct qt_obj_hnd *hnd) { int run_late_destroy_loop = 0; // unlink this hnd if (!hnd->late_destroy) { if (!hnd->left) { qt_obj_hnd_list = hnd->right; if (qt_obj_hnd_list) qt_obj_hnd_list->left = 0; } else { hnd->left->right = hnd->right; if (hnd->right) hnd->right->left = hnd->left; } if (!qt_obj_hnd_list) run_late_destroy_loop = 1; } // destroy object if we are the last user or autodelete is set if (hnd->obj && (hnd->autodelete || !find_qt_obj_hnd(qt_obj_hnd_list, hnd->obj, hnd->classId))) { if (!hnd->late_destroy && qts_is_castable(hnd->classId, classId_QApplication)) { // destroy this obect later hnd->right = qt_obj_hnd_late_destroy_list; if (qt_obj_hnd_late_destroy_list) qt_obj_hnd_late_destroy_list->left = hnd; qt_obj_hnd_late_destroy_list = hnd; hnd->late_destroy = 1; hnd->left = 0; goto skip_free_hnd; } else if (qts_is_castable(hnd->classId, classId_QObject)) { // destroy if there is no parent class QObject *qo = (QObject*)qts->cast(hnd->obj, hnd->classId, classId_QObject); if (hnd->autodelete || qo->parent() == 0) { debugf("Destroying object %p (QObject) / %p (%s).\n", qo, hnd->obj, qts->classes[hnd->classId].className); delete qo; } } else if (hnd->autodelete) { debugf("Destroying object %p (%s).\n", hnd->obj, qts->classes[hnd->classId].className); qt_obj_destroy(hnd); } else debugf("Non-QObjects are not destoyed automatically: %p (%s).\n", hnd->obj, qts->classes[hnd->classId].className); } if (hnd->guard) delete hnd->guard; free(hnd); skip_free_hnd: if (run_late_destroy_loop && !qt_obj_hnd_list) { while (qt_obj_hnd_late_destroy_list) { hnd = qt_obj_hnd_late_destroy_list; qt_obj_hnd_late_destroy_list = hnd->right; delete_qt_obj_hnd(hnd); } } } /* ----------------------- FreeList Interface ----------------------- */ struct freelist_entry { QString *data_QString; struct freelist_entry *next; }; struct freelist { struct freelist_entry *entries; }; static struct freelist_entry *freelist_add(struct freelist *fl) { struct freelist_entry *e = (struct freelist_entry *)calloc(1, sizeof(struct freelist_entry)); e->next = fl->entries; fl->entries = e; return e; } static void freelist_add_qstring(struct freelist *fl, QString *data) { struct freelist_entry *e = freelist_add(fl); e->data_QString = data; } static struct freelist *freelist_create() { struct freelist *fl = (struct freelist *)calloc(1, sizeof(struct freelist)); return fl; } static void freelist_delete(struct freelist *fl) { struct freelist_entry *flentry = fl->entries; while (flentry) { struct freelist_entry *e = flentry; flentry = flentry->next; if (e->data_QString) delete e->data_QString; free(e); } free(fl); } /* ----------------------- Smoke Interface ----------------------- */ class HIDDEN_CLASS SplSmokeBinding : public SmokeBinding { public: SplSmokeBinding(Smoke *s) : SmokeBinding(s) {} virtual ~SplSmokeBinding() {} virtual void deleted(Smoke::Index classId, void *obj) { struct qt_obj_virtual *it = find_qt_obj_virtual(qt_obj_virtual_list, obj, classId, 0); while (it) { struct qt_obj_virtual *next = it->right; delete_qt_obj_virtual(it); it = find_qt_obj_virtual(next, obj, classId, 0); } } virtual bool callMethod(Smoke::Index method, void *obj, Smoke::Stack UNUSED(args), bool UNUSED(isAbstract) = false) { Smoke::Index classId = qts->methods[method].classId; Smoke::Index methodId = method; struct qt_obj_virtual *it = find_qt_obj_virtual(qt_obj_virtual_list, obj, classId, methodId); if (it) { struct spl_task *cb_task = spl_clib_callback_task(it->vm); struct spl_asm *as = spl_asm_create(); spl_asm_add(as, SPL_OP_PUSHC, "retval"); spl_asm_add(as, SPL_OP_ZERO, 0); // FIXME: Pass arguments spl_create(cb_task, cb_task->ctx, "callback", spl_get(it->callback), SPL_CREATE_LOCAL); spl_asm_add(as, SPL_OP_DCALL, "callback"); spl_asm_add(as, SPL_OP_POPL, 0); spl_asm_add(as, SPL_OP_HALT, 0); spl_task_setcode(cb_task, spl_asm_dump(as)); spl_asm_destroy(as); spl_clib_callback_run(cb_task); // FIXME: Pass return value back // struct spl_node *retval = spl_get(spl_lookup(cb_task, cb_task->ctx, "retval", 0)); spl_task_destroy(cb_task->vm, cb_task); return true; } return false; } virtual char *className(Smoke::Index classId) { // return a new[] copy of the name of this Smoke class const char *className = smoke->className(classId); char *buf = new char[strlen(className) + 1]; strcpy(buf, className); return buf; } }; // call init function, and return the master Smoke* object static Smoke *init_smoke() { typedef void (*smoke_init_func)(); void *lib, *qt_smoke; smoke_init_func init; lib = dlopen(use_kde_libs ? "libsmokekde.so.1" : "libsmokeqt.so.1", RTLD_NOW); if(!lib) { fprintf(stderr, "SPL/Qt: Unable to load Smoke library (%s).\n", use_kde_libs ? "KDE" : "Qt"); exit(-1); } // new (extern "C") and old (Linux C++ ABI) symbol name init = (smoke_init_func)dlsym(lib, "init_libsmokeqt"); if(!init) init = (smoke_init_func)dlsym(lib, "_Z13init_qt_Smokev"); if(!init) { fprintf(stderr, "SPL/Qt: Unable to initialize Smoke (%s).\n", use_kde_libs ? "KDE" : "Qt"); exit(-1); } init(); qt_smoke = dlsym(lib, "qt_Smoke"); if(!qt_smoke) { fprintf(stderr, "SPL/Qt: Initializing Smoke failed (%s).\n", use_kde_libs ? "KDE" : "Qt"); exit(-1); } return *(Smoke**)qt_smoke; } // return true if the SPL node seams to be an 'undef' node static int is_undef_node(struct spl_node *n) { if (!n || (!n->hnode_name && !n->value && !n->subs_counter && !n->code && !n->ctx && !n->cls)) return 1; return 0; } // test if (and how well) the SPL node can be converted to a Smoke StackItem // return value of zero: does not match // non-zero values: higher value is better match static int test_spl_to_smoke(struct spl_task UNUSED(*task), Smoke::Index typei, struct spl_node *spl_val) { Smoke::Type *type = qts->types + typei; debugf("Test_SPL_to_Smoke %s (%02x, %d):", type->name, type->flags, type->classId); // Maybe we have got a hint? if (spl_val->hnode_name && !strcmp(spl_val->hnode_name, "qt_smoke") && spl_val->hnode_data) { struct qt_smoke_hnd *hnd = (struct qt_smoke_hnd *)spl_val->hnode_data; return hnd->typeId == typei ? 500 : 0; } // Only qt objects and undef nodes may be used as objects if (type->classId > 0 && ((type->flags & Smoke::tf_elem) == Smoke::t_class)) { if (spl_val->hnode_name && !strcmp(spl_val->hnode_name, "qt_obj")) { struct qt_obj_hnd *hnd = (struct qt_obj_hnd *)spl_val->hnode_data; if (hnd->classId == type->classId) return 100; return 50; } if (is_undef_node(spl_val)) return 40; return 0; } return 1; } // convert an SPL node to a Smoke StackItem static Smoke::StackItem spl_to_smoke(struct spl_task UNUSED(*task), Smoke::Index typei, struct spl_node *spl_val, struct freelist *fl) { Smoke::Type *type = qts->types + typei; debugf("SPL_to_Smoke: %s (%02x, %d)\n", type->name, type->flags, type->classId); if (spl_val->hnode_name && !strcmp(spl_val->hnode_name, "qt_smoke") && spl_val->hnode_data) { struct qt_smoke_hnd *hnd = (struct qt_smoke_hnd *)spl_val->hnode_data; return hnd->smoke_value; } Smoke::StackItem value; if ((type->flags & Smoke::tf_elem) == Smoke::t_class && type->classId >= 0 && spl_val->hnode_name && !strcmp(spl_val->hnode_name, "qt_obj")) { struct qt_obj_hnd *hnd = (struct qt_obj_hnd *)spl_val->hnode_data; debugf("Casting %s (%d) at %p to %s (%d): ", qts->classes[hnd->classId].className, hnd->classId, hnd->obj, qts->classes[type->classId].className, type->classId); value.s_class = qts->cast(hnd->obj, hnd->classId, type->classId); debugf("%p\n", value.s_class); return value; } // FIXME: dummy argv if (!strcmp(type->name, "char**")) { static char *dummy_argv[1]; dummy_argv[0] = (char*)" "; value.s_voidp = dummy_argv; return value; } if (!strcmp(type->name, "const char*")) { if (is_undef_node(spl_val)) value.s_voidp = 0; else value.s_voidp = (void*)spl_get_string(spl_val); return value; } if (!strcmp(type->name, "const QString&") || !strcmp(type->name, "const QString*") || !strcmp(type->name, "QString*") || !strcmp(type->name, "QString&") || !strcmp(type->name, "QString")) { QString *s = new QString(spl_get_string(spl_val)); freelist_add_qstring(fl, s); value.s_voidp = (void*)s; return value; } switch (type->flags & Smoke::tf_elem) { case Smoke::t_bool: value.s_bool = spl_get_int(spl_val); break; case Smoke::t_char: value.s_char = spl_get_int(spl_val); break; case Smoke::t_uchar: value.s_uchar = spl_get_int(spl_val); break; case Smoke::t_short: value.s_short = spl_get_int(spl_val); break; case Smoke::t_ushort: value.s_ushort = spl_get_int(spl_val); break; case Smoke::t_int: value.s_int = spl_get_int(spl_val); break; case Smoke::t_uint: value.s_uint = spl_get_int(spl_val); break; case Smoke::t_long: value.s_long = spl_get_int(spl_val); break; case Smoke::t_ulong: value.s_ulong = spl_get_int(spl_val); break; case Smoke::t_float: value.s_float = spl_get_float(spl_val); break; case Smoke::t_double: value.s_double = spl_get_float(spl_val); break; case Smoke::t_enum: value.s_enum = spl_get_int(spl_val); break; case Smoke::t_voidp: if (!is_undef_node(spl_val)) debugf("WARNING: Can't create general void pointer from SPL node!\n"); value.s_voidp = 0; break; case Smoke::t_class: if (!is_undef_node(spl_val)) debugf("WARNING: Can't create general class pointer from SPL node!\n"); value.s_class = 0; break; } return value; } // convert a Smoke StackItem to an SPL node static struct spl_node *smoke_to_spl(struct spl_task UNUSED(*task), Smoke::Index typei, Smoke::StackItem smoke_val) { Smoke::Type *type = qts->types + typei; debugf("Smoke_to_SPL: %s (%02x, %d)\n", type->name, type->flags, type->classId); switch (type->flags & Smoke::tf_elem) { case Smoke::t_voidp: case Smoke::t_class: if (!smoke_val.s_voidp) return spl_get(0); if (type->classId > 0) return new_qt_obj(type->classId, smoke_val.s_class, ""); if (!strcmp(type->name, "const char*")) return SPL_NEW_STRING_DUP((const char*)smoke_val.s_voidp); if (!strcmp(type->name, "QString")) { QString *qs = (QString*)smoke_val.s_voidp; return SPL_NEW_STRING_DUP(qs->utf8()); } break; case Smoke::t_bool: return SPL_NEW_INT(smoke_val.s_bool); break; case Smoke::t_char: return SPL_NEW_INT(smoke_val.s_char); break; case Smoke::t_uchar: return SPL_NEW_INT(smoke_val.s_uchar); break; case Smoke::t_short: return SPL_NEW_INT(smoke_val.s_short); break; case Smoke::t_ushort: return SPL_NEW_INT(smoke_val.s_ushort); break; case Smoke::t_int: return SPL_NEW_INT(smoke_val.s_int); break; case Smoke::t_uint: return SPL_NEW_INT(smoke_val.s_uint); break; case Smoke::t_long: return SPL_NEW_INT(smoke_val.s_long); break; case Smoke::t_ulong: return SPL_NEW_INT(smoke_val.s_ulong); break; case Smoke::t_float: return SPL_NEW_FLOAT(smoke_val.s_float); break; case Smoke::t_double: return SPL_NEW_FLOAT(smoke_val.s_double); break; case Smoke::t_enum: return SPL_NEW_INT(smoke_val.s_enum); break; } debugf("WARNING: Can't convert smoke data. Returning undef.\n"); return spl_get(0); } // check if the arguments match to the method // return value of zero: does not match // non-zero values: higher value is better match static int checkMethod(struct spl_task *task, Smoke::Index method, struct spl_node *args) { int result = 1; Smoke::Method *m = qts->methods + method; debugf("Found %s::%s with %d args (%d).\n", qts->classes[m->classId].className, qts->methodNames[m->name], m->numArgs, method); if (m->numArgs != args->subs_counter) return 0; Smoke::Index smoke_arg_idx = m->args; struct spl_node_sub *spl_arg_idx = args->subs_begin; while (spl_arg_idx) { int this_result = test_spl_to_smoke(task, qts->argumentList[smoke_arg_idx], spl_arg_idx->node); debugf("%d\n", this_result); if (!this_result) { debugf("Method does not match arguments.\n"); return 0; } result += this_result; spl_arg_idx = spl_arg_idx->next; smoke_arg_idx++; } debugf("Method matches arguments with a score of %d.\n", result); return result; } // given class-name, function-name and argument list, return an unambiguous method ID static Smoke::Index getMethod(struct spl_task *task, const char* c, const char* m, struct spl_node *args) { Smoke::Index best_method = -1; int best_method_score = 0; Smoke::Index idc = qts->idClass(c); Smoke::Index idm = qts->idMethodName(m); int mlen = strlen(m); debugf("--\n"); debugf("Looking for %s::%s with %d args.\n", c, m, args->subs_counter); while ( idm <= qts->numMethodNames && !strncmp(qts->methodNames[idm], m, mlen) && (qts->methodNames[idm][mlen] == '$' || qts->methodNames[idm][mlen] == '#' || qts->methodNames[idm][mlen] == '?' || qts->methodNames[idm][mlen] == 0) ) { Smoke::Index method = qts->findMethod(idc, idm); Smoke::Index i = qts->methodMaps[method].method; if (i > 0) { int score = checkMethod(task, i, args); if (score > best_method_score) { best_method_score = score; best_method = i; } } else if(i < 0) { for (i = -i; qts->ambiguousMethodList[i]; i++) { int score = checkMethod(task, qts->ambiguousMethodList[i], args); if (score > best_method_score) { best_method_score = score; best_method = qts->ambiguousMethodList[i]; } } } idm++; } return best_method; } // call obj->method(args) static struct spl_node *callMethod(struct spl_task *task, Smoke::Index classId, void *obj, Smoke::Index method, struct spl_node *spl_args) { struct freelist *fl = freelist_create(); Smoke::Method *m = qts->methods + method; Smoke::StackItem args[spl_args->subs_counter+1]; { struct spl_node_sub *spl_args_idx = spl_args->subs_begin; Smoke::Method *m = qts->methods + method; Smoke::Index smoke_arg_idx = m->args; int args_idx = 1; while (spl_args_idx) { args[args_idx++] = spl_to_smoke(task, qts->argumentList[smoke_arg_idx++], spl_args_idx->node, fl); spl_args_idx = spl_args_idx->next; } } if (obj) { debugf("Casting %s (%d) to %s (%d).\n", qts->classes[classId].className, classId, qts->classes[m->classId].className, m->classId); obj = qts->cast(obj, classId, m->classId); } Smoke::ClassFn fn = qts->classes[m->classId].classFn; fn(m->method, obj, args); freelist_delete(fl); if (m->ret) return smoke_to_spl(task, m->ret, args[0]); return 0; } /* ----------------------- Creating Qt Objects ----------------------- */ static void handler_qt_namespace(struct spl_task *task, struct spl_vm UNUSED(*vm), struct spl_node UNUSED(*node), struct spl_hnode_args *args, void UNUSED(*data)) { if (args->action == SPL_HNODE_ACTION_LOOKUP) { int instanciate = 0; char *key_dup = strdup(args->key); char *strtokptr; char *classname = strtok_r(key_dup, ".", &strtokptr); char *methodname = strtok_r(0, ".", &strtokptr); classname = spl_hash_decode(classname); if (methodname) { methodname = spl_hash_decode(methodname); } else { methodname = strdup(classname); instanciate = 1; } free(key_dup); if (instanciate) { args->value = SPL_NEW_STRING_DUP("Qt Class Wrapper"); args->value->cls = spl_get(spl_lookup(task, task->vm->root, "__qt_instanciate_wrapper", 0)); spl_create(task, args->value, "qt_classname", SPL_NEW_STRING(classname), SPL_CREATE_LOCAL); spl_create(task, args->value, "qt_methodname", SPL_NEW_STRING(methodname), SPL_CREATE_LOCAL); args->value->flags |= SPL_NODE_FLAG_CLASS; args->value->ctx_type = SPL_CTX_OBJECT; } else { struct spl_node *wrapper_obj; wrapper_obj = SPL_NEW_STRING_DUP("Qt Method Wrapper"); wrapper_obj->cls = spl_get(spl_lookup(task, task->vm->root, "__qt_callstatic_wrapper", 0)); spl_create(task, wrapper_obj, "qt_classname", SPL_NEW_STRING(classname), SPL_CREATE_LOCAL); spl_create(task, wrapper_obj, "qt_methodname", SPL_NEW_STRING(methodname), SPL_CREATE_LOCAL); wrapper_obj->ctx_type = SPL_CTX_OBJECT; args->value = spl_get(spl_lookup(task, wrapper_obj, "call", 0)); spl_put(task->vm, wrapper_obj); } } } static struct spl_node *handler_qt_callstatic(struct spl_task *task, void UNUSED(*data)) { char *classname = spl_clib_get_string(task); char *methodname = spl_clib_get_string(task); struct spl_node *args = spl_cleanup(task, spl_clib_get_node(task)); /* Special handler for QApplication */ if (!strcmp(classname, "QApplication") && !strcmp(methodname, "QApplication")) { static int my_dummy_argc = 0; Smoke::Index method = qts->methodMaps[qts->findMethod("QApplication", "QApplication$?")].method; Smoke::StackItem smoke_args[3]; smoke_args[1].s_voidp = &my_dummy_argc; smoke_args[2].s_voidp = 0; Smoke::Method *m = qts->methods + method; Smoke::ClassFn fn = qts->classes[m->classId].classFn; debugf("Calling constructor QApplication::QApplication.\n"); fn(m->method, 0, smoke_args); return smoke_to_spl(task, m->ret, smoke_args[0]); } Smoke::Index method = getMethod(task, classname, methodname, args); if (method < 0) { spl_clib_exception(task, "QtEx", "description", SPL_NEW_SPL_STRING(spl_string_printf(0, 0, 0, "Unable to resolve %s::%s for the given arguments.", classname, methodname)), NULL); return 0; } #ifdef SMOKE_WITHOUT_MF_CTOR if ((qts->methods[method].flags & Smoke::mf_static) == 0 && strcmp(classname, methodname)) { #else if ((qts->methods[method].flags & (Smoke::mf_static|Smoke::mf_ctor)) == 0) { #endif spl_clib_exception(task, "QtEx", "description", SPL_NEW_SPL_STRING(spl_string_printf(0, 0, 0, "Used %s::%s as static method but it is not (%x).", classname, methodname, qts->methods[method].flags)), NULL); return 0; } debugf("Calling static method %s::%s (%d).\n", classname, methodname, method); return callMethod(task, -1, NULL, method, args); } /* ----------------------- Calling Qt Methods ----------------------- */ static void handler_qt_obj(struct spl_task *task, struct spl_vm UNUSED(*vm), struct spl_node *node, struct spl_hnode_args *args, void UNUSED(*data)) { if (args->action == SPL_HNODE_ACTION_PUT) { delete_qt_obj_hnd((struct qt_obj_hnd *)node->hnode_data); node->hnode_data = 0; return; } if (args->action == SPL_HNODE_ACTION_LOOKUP) { char *member = spl_hash_decode(args->key); if (!strcmp(member, "class")) { struct qt_obj_hnd *hnd = (struct qt_obj_hnd *)node->hnode_data; args->value = SPL_NEW_STRING_DUP(qts->classes[hnd->classId].className); free(member); return; } if (!strcmp(member, "ptr")) { struct qt_obj_hnd *hnd = (struct qt_obj_hnd *)node->hnode_data; char buffer[64]; snprintf(buffer, 64, "%p", hnd->obj); args->value = SPL_NEW_STRING_DUP(buffer); free(member); return; } struct spl_node *wrapper_obj; wrapper_obj = SPL_NEW_STRING_DUP("Qt Method Wrapper"); wrapper_obj->cls = spl_get(spl_lookup(task, task->vm->root, "__qt_callmethod_wrapper", 0)); spl_create(task, wrapper_obj, "qt_methodname", SPL_NEW_STRING(member), SPL_CREATE_LOCAL); spl_create(task, wrapper_obj, "qt_object", spl_get(node), SPL_CREATE_LOCAL); wrapper_obj->ctx_type = SPL_CTX_OBJECT; args->value = spl_get(spl_lookup(task, wrapper_obj, "call", 0)); spl_put(task->vm, wrapper_obj); } } static struct spl_node *handler_qt_callmethod(struct spl_task *task, void UNUSED(*data)) { struct spl_node *obj = spl_cleanup(task, spl_clib_get_node(task)); char *methodname = spl_clib_get_string(task); struct spl_node *args = spl_cleanup(task, spl_clib_get_node(task)); if (!obj->hnode_name || strcmp(obj->hnode_name, "qt_obj")) { spl_clib_exception(task, "QtEx", "description", SPL_NEW_SPL_STRING(spl_string_printf(0, 0, 0, "Not a Qt Object.")), NULL); return 0; } struct qt_obj_hnd *hnd = (struct qt_obj_hnd *)obj->hnode_data; const char *classname = qts->classes[hnd->classId].className; if (!hnd->obj) { spl_clib_exception(task, "QtEx", "description", SPL_NEW_SPL_STRING(spl_string_printf(0, 0, 0, "Qt Object (%s) is a NULL pointer or has " "been destroyed.", classname)), NULL); return 0; } Smoke::Index method = getMethod(task, classname, methodname, args); if (method < 0) { spl_clib_exception(task, "QtEx", "description", SPL_NEW_SPL_STRING(spl_string_printf(0, 0, 0, "Unable to resolve %s::%s for the given arguments.", classname, methodname)), NULL); return 0; } debugf("Calling method %s::%s (%d).\n", classname, methodname, method); return callMethod(task, hnd->classId, hnd->obj, method, args); } /* ----------------------- simple builtin functions ----------------------- */ /** * Enable/disable debug output. * * Call this function with an argument of '1' to enable the debug output and * with an argument of '0' to disable it. * * The debug output is disabled per default. */ // builtin qt_debug(mode) static struct spl_node *handler_qt_debug(struct spl_task *task, void UNUSED(*data)) { enable_debug_output = spl_clib_get_int(task); return 0; } /** * Check for kde libraries. * * This function returns '1' if the kde APIs are available and '0' if only the * core Qt APIs can be used. */ // builtin qt_kde() static struct spl_node *handler_qt_kde(struct spl_task UNUSED(*task), void UNUSED(*data)) { return SPL_NEW_INT(use_kde_libs); } /** * Instanciates the widget described in the specified .ui file. * The new widget is returned. */ // builtin qt_ui(ui_file) static struct spl_node *handler_qt_ui(struct spl_task *task, void UNUSED(*data)) { char *ui_file = spl_clib_get_string(task); QWidget *w = QWidgetFactory::create(QString(ui_file)); if (!w) { spl_clib_exception(task, "QtEx", "description", SPL_NEW_SPL_STRING(spl_string_printf(0, 0, 0, "Failed to do qt_ui() on '%s'.", ui_file)), NULL); return 0; } return new_qt_obj(classId_QWidget, w, " from qt_ui()"); } /** * This is a frontend to the Qt QObject::child() function. The difference is * that this function automatically casts the return value to the specified * class. Example given: * * var d = qt_ui("dialog.ui"); * var b = qt_child(d, "pushButton1", "QPushButton", 1); * b.setText("Click Me!"); */ // builtin qt_child(parent, name, class, recursive) static struct spl_node *handler_qt_child(struct spl_task *task, void UNUSED(*data)) { struct spl_node *parent_node = spl_cleanup(task, spl_clib_get_node(task)); struct qt_obj_hnd *parent_hnd = (struct qt_obj_hnd *)parent_node->hnode_data; if (!parent_node->hnode_name || strcmp(parent_node->hnode_name, "qt_obj") || !parent_hnd || !parent_hnd->obj) { spl_clib_exception(task, "QtEx", "description", SPL_NEW_SPL_STRING(spl_string_printf(0, 0, 0, "Argument 1 to qt_child() is not a qt object.")), NULL); return 0; } if (!qts_is_castable(parent_hnd->classId, classId_QObject)) { spl_clib_exception(task, "QtEx", "description", SPL_NEW_SPL_STRING(spl_string_printf(0, 0, 0, "Argument 1 to qt_child() is not derived from QObject.")), NULL); return 0; } QObject *parent = (QObject *)qts->cast(parent_hnd->obj, parent_hnd->classId, classId_QObject); const char *objectname = spl_clib_get_string(task); const char *classname = spl_clib_get_string(task); int recursive = spl_clib_get_int(task); QObject *child = parent->child(objectname, classname, recursive); if (!child) { spl_clib_exception(task, "QtEx", "description", SPL_NEW_SPL_STRING(spl_string_printf(0, 0, 0, "Lookup in qt_child() '%s' (%s) failed.", objectname, classname)), NULL); return 0; } Smoke::Index classId = qts->idClass(classname); void *result = child->qt_cast(classname); if (classId < 0 || !result) { spl_clib_exception(task, "QtEx", "description", SPL_NEW_SPL_STRING(spl_string_printf(0, 0, 0, "Cast in qt_child() '%s' (%s) failed.", objectname, classname)), NULL); return 0; } return new_qt_obj(classId, result, " from qt_child()"); } /** * Cast a Qt Object. The new object handler is returned. * For QObjects this is a frontend to QObject::qt_cast(). */ // builtin qt_cast(object, type) static struct spl_node *handler_qt_cast(struct spl_task *task, void UNUSED(*data)) { struct spl_node *object_node = spl_cleanup(task, spl_clib_get_node(task)); struct qt_obj_hnd *object_hnd = (struct qt_obj_hnd *)object_node->hnode_data; if (!object_node->hnode_name || strcmp(object_node->hnode_name, "qt_obj") || !object_hnd || !object_hnd->obj) { spl_clib_exception(task, "QtEx", "description", SPL_NEW_SPL_STRING(spl_string_printf(0, 0, 0, "Argument 1 to qt_cast() is not a qt object.")), NULL); return 0; } const char *target_classname = spl_clib_get_string(task); Smoke::Index target_classId = qts->idClass(target_classname); if (target_classId <= 0) { spl_clib_exception(task, "QtEx", "description", SPL_NEW_SPL_STRING(spl_string_printf(0, 0, 0, "In qt_cast(): Class '%s' is unkown.", target_classname)), NULL); return 0; } if (qts_is_castable(object_hnd->classId, classId_QObject)) { QObject *qo = (QObject*)qts->cast(object_hnd->obj, object_hnd->classId, classId_QObject); void *newobj = qo->qt_cast(target_classname); if (newobj) return new_qt_obj(target_classId, newobj, " from qt_cast()"); } if (qts_is_castable(object_hnd->classId, target_classId)) { return new_qt_obj(target_classId, qts->cast(object_hnd->obj, object_hnd->classId, target_classId), " from qt_cast()"); } spl_clib_exception(task, "QtEx", "description", SPL_NEW_SPL_STRING(spl_string_printf(0, 0, 0, "Can't cast this object to '%s'.", target_classname)), NULL); return 0; } /** * Destroy a QObject. * * Usually QObjects are destroyed automatically when the last SPL variable * refering to it is destroyed and the QObjects has no parent objects. * * (Destroying QApplication Objects is automatically delayed until all other * Qt Objects are destroyed.) * * But sometimes it is neccessary to explicitely destroy a QObject, e.g. when * one want's to remove a Widget from a dialog. */ // builtin qt_destroy(object) static struct spl_node *handler_qt_destroy(struct spl_task *task, void UNUSED(*data)) { struct spl_node *object_node = spl_cleanup(task, spl_clib_get_node(task)); struct qt_obj_hnd *object_hnd = (struct qt_obj_hnd *)object_node->hnode_data; if (!object_node->hnode_name || strcmp(object_node->hnode_name, "qt_obj") || !object_hnd || !object_hnd->obj) { spl_clib_exception(task, "QtEx", "description", SPL_NEW_SPL_STRING(spl_string_printf(0, 0, 0, "Argument 1 to qt_destroy() is not a qt object.")), NULL); return 0; } if (!qts_is_castable(object_hnd->classId, classId_QObject)) { spl_clib_exception(task, "QtEx", "description", SPL_NEW_SPL_STRING(spl_string_printf(0, 0, 0, "Argument 1 to qt_destroy() is not derived from QObject.")), NULL); return 0; } if (object_hnd->obj) { QObject *qo = (QObject*)qts->cast(object_hnd->obj, object_hnd->classId, classId_QObject); // object_hnd->obj is automatically set to null by hnd->guard delete qo; } return 0; } /** * Delete a Qt Object. * * This function can be used to delete any Qt Object. It is recommended to * use [[qt_destroy()]] for QObject. This function should only be used for * deleting Qt Objects which are not QObjects. */ // builtin qt_delete(object) static struct spl_node *handler_qt_delete(struct spl_task *task, void UNUSED(*data)) { struct spl_node *object_node = spl_cleanup(task, spl_clib_get_node(task)); struct qt_obj_hnd *object_hnd = (struct qt_obj_hnd *)object_node->hnode_data; if (!object_node->hnode_name || strcmp(object_node->hnode_name, "qt_obj") || !object_hnd || !object_hnd->obj) { spl_clib_exception(task, "QtEx", "description", SPL_NEW_SPL_STRING(spl_string_printf(0, 0, 0, "Argument 1 to qt_delete() is not a qt object.")), NULL); return 0; } if (qt_obj_destroy(object_hnd)) spl_clib_exception(task, "QtEx", "description", SPL_NEW_SPL_STRING(spl_string_printf(0, 0, 0, "Failed to delete object in qt_delete().")), NULL); return 0; } /** * Mark a Qt Object for autodeletion. * * This maks an object for autodeletion. That means that the object will be * deleted automatically when the SPL handler is removed by the garbage * collector. * * The object is returned by the function. So this function can be used like * a filter when instanciating a new object. Example Given: * * var background = qt_autodelete(new qt.QColor(200, 100, 100)); */ // builtin qt_autodelete(object) static struct spl_node *handler_qt_autodelete(struct spl_task *task, void UNUSED(*data)) { struct spl_node *object_node = spl_clib_get_node(task); struct qt_obj_hnd *object_hnd = (struct qt_obj_hnd *)object_node->hnode_data; if (!object_node->hnode_name || strcmp(object_node->hnode_name, "qt_obj") || !object_hnd || !object_hnd->obj) { spl_clib_exception(task, "QtEx", "description", SPL_NEW_SPL_STRING(spl_string_printf(0, 0, 0, "Argument 1 to qt_autodelete() is not a qt object.")), NULL); return 0; } object_hnd->autodelete = 1; return object_node; } /** * Create a hint for the SPL/Qt type converter * * The SPL/Qt module does its best to guess which version of a method should be * used. But sometimes it needs a hint to make the right decision. * * E.g. there are two implementations of QMessageBox::question(): * * int QMessageBox::question(QWidget *parent, * const QString &caption, const QString &text, * int button0, int button1 = 0, int button2 = 0) * and * * int QMessageBox::question (QWidget *parent, * const QString &caption, const QString &text, * const QString &button0Text = QString::null, * const QString &button1Text = QString::null, * const QString &button2Text = QString::null, * int defaultButtonNumber = 0, * int escapeButtonNumber = -1) * * To choose the first version one needs to use qt_as() for the integer * parameters so they not converted to strings: * * qt.QMessageBox.question(win, "Hey!", "Are you sure?", * qt_as("int", 3), qt_as("int", 4)); */ // builtin qt_as(type, value) static struct spl_node *handler_qt_as(struct spl_task *task, void UNUSED(*data)) { char *type = spl_clib_get_string(task); struct spl_node *input_value = spl_clib_get_node(task); Smoke::Index typeId = qts->idType(type); if (typeId <= 0) { spl_clib_exception(task, "QtEx", "description", SPL_NEW_SPL_STRING(spl_string_printf(0, 0, 0, "Argument 1 to qt_as() is not a valid type.")), NULL); spl_put(task->vm, input_value); return 0; } struct spl_node *value = SPL_NEW_STRING_DUP("Qt/SMOKE Value"); value->ctx = input_value; struct qt_smoke_hnd *hnd = (struct qt_smoke_hnd *)calloc(1, sizeof(struct qt_smoke_hnd)); hnd->typeId = typeId; hnd->fl = freelist_create(); hnd->smoke_value = spl_to_smoke(task, typeId, input_value, hnd->fl); value->hnode_name = strdup("qt_smoke"); value->hnode_data = hnd; return value; } static void handler_qt_smoke(struct spl_task UNUSED(*task), struct spl_vm UNUSED(*vm), struct spl_node *node, struct spl_hnode_args *args, void UNUSED(*data)) { if (args->action == SPL_HNODE_ACTION_PUT && node->hnode_data) { struct qt_smoke_hnd *hnd = (struct qt_smoke_hnd *)node->hnode_data; freelist_delete(hnd->fl); free(hnd); node->hnode_data = 0; } } /** * Connect a signal to a slot. This is a frontend to the QObject::connect * method. The SIGNAL() and SLOT() macros are not available in SPL. This * function simply expects the signal or slot as string. E.g.: * * qt_connect(mybutton, "clicked()", myapp, "quit()"); */ // builtin qt_connect(sender, signal, receiver, slot) /** * Like [[qt_connect()]], but for disconnecting a signal and a slot. */ // builtin qt_disconnect(sender, signal, receiver, slot) static struct spl_node *handler_qt_connect(struct spl_task *task, void *data) { int connect_mode = !strcmp((char*)data, "connect"); const char *mode = connect_mode ? "connect" : "disconnect"; struct spl_node *sender_node = spl_cleanup(task, spl_clib_get_node(task)); struct qt_obj_hnd *sender_hnd = (struct qt_obj_hnd *)sender_node->hnode_data; const char *signal = spl_clib_get_string(task); struct spl_node *receiver_node = spl_cleanup(task, spl_clib_get_node(task)); struct qt_obj_hnd *receiver_hnd = (struct qt_obj_hnd *)receiver_node->hnode_data; const char *slot = spl_clib_get_string(task); if (!sender_node->hnode_name || strcmp(sender_node->hnode_name, "qt_obj") || !sender_hnd || !sender_hnd->obj) { spl_clib_exception(task, "QtEx", "description", SPL_NEW_SPL_STRING(spl_string_printf(0, 0, 0, "Argument 1 to qt_%s() is not a qt object.", mode)), NULL); return 0; } if (!qts_is_castable(sender_hnd->classId, classId_QObject)) { spl_clib_exception(task, "QtEx", "description", SPL_NEW_SPL_STRING(spl_string_printf(0, 0, 0, "Argument 1 to qt_%s() is not derived from QObject.", mode)), NULL); return 0; } QObject *sender = (QObject *)qts->cast(sender_hnd->obj, sender_hnd->classId, classId_QObject); if (!receiver_node->hnode_name || strcmp(receiver_node->hnode_name, "qt_obj") || !receiver_hnd || !receiver_hnd->obj) { spl_clib_exception(task, "QtEx", "description", SPL_NEW_SPL_STRING(spl_string_printf(0, 0, 0, "Argument 3 to qt_%s() is not a qt object.", mode)), NULL); return 0; } if (!qts_is_castable(receiver_hnd->classId, classId_QObject)) { spl_clib_exception(task, "QtEx", "description", SPL_NEW_SPL_STRING(spl_string_printf(0, 0, 0, "Argument 3 to qt_%s() is not derived from QObject.", mode)), NULL); return 0; } QObject *receiver = (QObject *)qts->cast(receiver_hnd->obj, receiver_hnd->classId, classId_QObject); char *signal_enc, *slot_enc; asprintf(&signal_enc, "%d%s", QSIGNAL_CODE, signal); asprintf(&slot_enc, "%d%s", QSLOT_CODE, slot); bool ret; if (connect_mode) ret = QObject::connect(sender, signal_enc, receiver, slot_enc); else ret = QObject::disconnect(sender, signal_enc, receiver, slot_enc); free(signal_enc); free(slot_enc); if (!ret) { spl_clib_exception(task, "QtEx", "description", SPL_NEW_SPL_STRING(spl_string_printf(0, 0, 0, "qt_%s() returned an error.", mode)), NULL); } return 0; } /* ----------------------- event callbacks ----------------------- */ class HIDDEN_CLASS SplEventHandler : public QObject { Q_OBJECT public: struct spl_vm *this_spl_vm; struct spl_node *this_spl_callback; int this_spl_callback_cbid; int event_list_size; QEvent::Type *event_list; SplEventHandler(struct spl_vm *vm, struct spl_node *callback) { this_spl_vm = vm; this_spl_callback = callback; event_list_size = 0; event_list = 0; struct spl_node *cblist = getmoddata(vm)->cblist; this_spl_callback_cbid = cblist->subs_next_idx; spl_create(0, cblist, 0, spl_get(callback), SPL_CREATE_LOCAL); } ~SplEventHandler() { struct spl_node *cblist = getmoddata(this_spl_vm)->cblist; char key[32]; snprintf(key, 32, "%d", this_spl_callback_cbid); spl_delete(0, cblist, key); spl_put(this_spl_vm, this_spl_callback); if (event_list_size) delete[] event_list; } bool eventFilter(QObject *o, QEvent *e); public slots: void objectDestroyed() { // self destruct.. ;-) delete this; } }; bool SplEventHandler::eventFilter(QObject UNUSED(*o), QEvent *e) { if (this_spl_vm->destroy_in_progress) return false; if (event_list_size) { for (int i=0; itype() == event_list[i]) goto found_this_event_in_list; return false; } found_this_event_in_list:; const char *event_type = "QEvent"; switch (e->type()) { case QEvent::Timer: event_type = "QTimerEvent"; break; case QEvent::MouseButtonPress: case QEvent::MouseButtonRelease: case QEvent::MouseButtonDblClick: case QEvent::MouseMove: event_type = "QMouseEvent"; break; default: /* ignore the others */ break; } struct spl_node *event_node = new_qt_obj(qts->idClass(event_type), e, " is an Event Object"); struct spl_asm *as = spl_asm_create(); spl_asm_add(as, SPL_OP_PUSHC, "r"); spl_asm_add(as, SPL_OP_ZERO, 0); spl_asm_add(as, SPL_OP_PUSHA, "e"); spl_asm_add(as, SPL_OP_DCALL, "c"); spl_asm_add(as, SPL_OP_POPL, 0); spl_asm_add(as, SPL_OP_HALT, 0); char task_name[64]; snprintf(task_name, 64, "__qt_callback_task_%d", callback_task_id++); struct spl_task *task = spl_task_create(this_spl_vm, task_name); spl_task_setcode(task, spl_asm_dump(as)); spl_asm_destroy(as); struct spl_node *ctx = spl_get(0); ctx->ctx = task->ctx; task->ctx = ctx; spl_create(task, ctx, "e", event_node, SPL_CREATE_LOCAL); spl_create(task, ctx, "c", spl_get(this_spl_callback), SPL_CREATE_LOCAL); bool ret = false; debugf("+++ Entering SPL event callback (%s) ...\n", event_type); int runloop_ret = this_spl_vm->runloop(this_spl_vm, task); if (runloop_ret == 0) { struct spl_node *rn = spl_lookup(task, ctx, "r", SPL_LOOKUP_TEST); if (rn) ret = spl_get_int(rn) ? true : false; } spl_task_destroy(task->vm, task); debugf("+++ Return value from SPL callback: %s\n", ret ? "true" : "false"); return ret; } /** * Register an event callback in a Qt object. * * The callback function will be passed the QEvent object as 1st parameter. The * callback must return true (non-zero) if the event has been comsumed and false * (zero) if the event should be passed to the other event handlers. * * A list of event types may be passed as additional parameters. If no types * are specified, all events are passed thru the callback function. * * Example given: * * function click_callback(e) { * debug "Click: ${e.x()} / ${e.y()}"; * return 1; * } * * qt_event_callback(widget_object, click_callback, * qt.QEvent.MouseButtonPress(), qt.QEvent.MouseButtonDblClick()); * * Once a callback function is registered it stays active until the widget gets * destroyed. There is no function for unregistering an event callback. * * This function can be used to connect an SPL closoure to a Qt widget. The Qt * C++ API does not have such a function because C++ has not closoures.. * * In some cases it is dangerous to copy to variables passed to an event * callback to a different context and use it after the callback has been * returned. So it is recommended to not do this. */ // builtin qt_event_callback(object, callback, @eventtypes) static struct spl_node *handler_qt_event_callback(struct spl_task *task, void UNUSED(*data)) { struct spl_node *node = spl_cleanup(task, spl_clib_get_node(task)); struct qt_obj_hnd *hnd = (struct qt_obj_hnd *)node->hnode_data; if (!node->hnode_name || strcmp(node->hnode_name, "qt_obj") || !hnd || !hnd->obj) { spl_clib_exception(task, "QtEx", "description", SPL_NEW_SPL_STRING(spl_string_printf(0, 0, 0, "Argument 1 to qt_event_callback() is not a qt object.")), NULL); return 0; } if (!qts_is_castable(hnd->classId, classId_QObject)) { spl_clib_exception(task, "QtEx", "description", SPL_NEW_SPL_STRING(spl_string_printf(0, 0, 0, "Argument 1 to qt_event_callback() is not derived from QObject.")), NULL); return 0; } QObject *object = (QObject *)qts->cast(hnd->obj, hnd->classId, classId_QObject); SplEventHandler *handler = new SplEventHandler(task->vm, spl_clib_get_node(task)); QObject::connect(object, SIGNAL(destroyed()), handler, SLOT(objectDestroyed())); object->installEventFilter(handler); int elist_size = spl_clib_get_argc(task); if (elist_size) { handler->event_list_size = elist_size; handler->event_list = new QEvent::Type[elist_size]; for (int i=0; ievent_list[i] = (QEvent::Type)spl_clib_get_int(task); } return 0; } /* ----------------------- signal callbacks ----------------------- */ class HIDDEN_CLASS SplSignalHandler : public QObject { Q_OBJECT public: struct spl_vm *this_spl_vm; struct spl_node *this_spl_callback; int this_spl_callback_cbid; char *dynamic_slot_args; SplSignalHandler(struct spl_vm *vm, struct spl_node *callback, char *slot) { this_spl_vm = vm; this_spl_callback = callback; dynamic_slot_args = slot; struct spl_node *cblist = getmoddata(vm)->cblist; this_spl_callback_cbid = cblist->subs_next_idx; spl_create(0, cblist, 0, spl_get(callback), SPL_CREATE_LOCAL); } ~SplSignalHandler() { free(dynamic_slot_args); struct spl_node *cblist = getmoddata(this_spl_vm)->cblist; char key[32]; snprintf(key, 32, "%d", this_spl_callback_cbid); spl_delete(0, cblist, key); spl_put(this_spl_vm, this_spl_callback); } public slots: void objectDestroyed() { // self destruct.. ;-) delete this; } void dynamicSlot() { } private: void dynamicSlotTrap(QUObject *o); }; void SplSignalHandler::dynamicSlotTrap(QUObject *o) { if (this_spl_vm->destroy_in_progress) return; struct spl_asm *as = spl_asm_create(); spl_asm_add(as, SPL_OP_ZERO, 0); spl_asm_add(as, SPL_OP_PUSH, "a"); spl_asm_add(as, SPL_OP_APUSHA, 0); spl_asm_add(as, SPL_OP_DCALL, "c"); spl_asm_add(as, SPL_OP_DROP, 0); spl_asm_add(as, SPL_OP_HALT, 0); char task_name[64]; snprintf(task_name, 64, "__qt_callback_task_%d", callback_task_id++); struct spl_task *task = spl_task_create(this_spl_vm, task_name); spl_task_setcode(task, spl_asm_dump(as)); spl_asm_destroy(as); struct spl_node *ctx = spl_get(0); ctx->ctx = task->ctx; task->ctx = ctx; struct spl_node *args_node = spl_get(0); char *args_string = strdup(dynamic_slot_args); char *args_string_first = args_string; char *args_string_ptr, *arg; int argc = 0; while ((arg = strtok_r(args_string_first, ",", &args_string_ptr)) != 0) { struct spl_node *node = 0; args_string_first = 0; argc++; Smoke::Index typeId = qts->idType(arg); if (typeId <= 0) { debugf("WARING: Can't convert signal arg '%s' to SPL!\n", arg); node = spl_get(0); } else { Smoke::Type *type = qts->types + typeId; Smoke::StackItem smoke_val; switch (type->flags & Smoke::tf_elem) { case Smoke::t_voidp: case Smoke::t_class: smoke_val.s_voidp = static_QUType_ptr.get(o+argc); break; case Smoke::t_bool: smoke_val.s_bool = static_QUType_bool.get(o+argc); break; case Smoke::t_char: smoke_val.s_char = static_QUType_int.get(o+argc); break; case Smoke::t_uchar: smoke_val.s_uchar = static_QUType_int.get(o+argc); break; case Smoke::t_short: smoke_val.s_short = static_QUType_int.get(o+argc); break; case Smoke::t_ushort: smoke_val.s_ushort = static_QUType_int.get(o+argc); break; case Smoke::t_int: smoke_val.s_int = static_QUType_int.get(o+argc); break; case Smoke::t_uint: smoke_val.s_uint = static_QUType_int.get(o+argc); break; case Smoke::t_long: smoke_val.s_long = static_QUType_int.get(o+argc); break; case Smoke::t_ulong: smoke_val.s_ulong = static_QUType_int.get(o+argc); break; case Smoke::t_float: smoke_val.s_float = static_QUType_double.get(o+argc); break; case Smoke::t_double: smoke_val.s_double = static_QUType_double.get(o+argc); break; case Smoke::t_enum: smoke_val.s_enum = static_QUType_enum.get(o+argc); break; } /***** Unused QUType handlers ***** QUType_Null static_QUType_Null; QUType_iface static_QUType_iface; QUType_idisp static_QUType_idisp; QUType_charstar static_QUType_charstar; QUType_QString static_QUType_QString; ***********************************/ node = smoke_to_spl(task, typeId, smoke_val); } spl_create(task, args_node, 0, node, SPL_CREATE_LOCAL); } free(args_string); spl_create(task, ctx, "a", args_node, SPL_CREATE_LOCAL); spl_create(task, ctx, "c", spl_get(this_spl_callback), SPL_CREATE_LOCAL); debugf("+++ Entering SPL signal callback (%s) ...\n", dynamic_slot_args); this_spl_vm->runloop(this_spl_vm, task); spl_task_destroy(task->vm, task); debugf("+++ Returning from SPL callback.\n"); } /** * Register a signal callback in a Qt object. * Example given: * * function printpos(x,y) { * debug "New content position: $x / $y"; * } * * qt_signal_callback(my_scrollview, "contentsMoving(int,int)", printpos); * * Once a callback function is registered it stays active until the widget gets * destroyed. There is no function for unregistering a signal callback. * * This function can be used to connect an SPL closoure to a Qt widget. The Qt * C++ API does not have such a function because C++ has not closoures.. * * In some cases it is dangerous to copy to variables passed to a signal * callback to a different context and use it after the callback has been * returned. So it is recommended to not do this. */ // builtin qt_signal_callback(object, signalspec, callback) static struct spl_node *handler_qt_signal_callback(struct spl_task *task, void UNUSED(*data)) { struct spl_node *node = spl_cleanup(task, spl_clib_get_node(task)); struct qt_obj_hnd *hnd = (struct qt_obj_hnd *)node->hnode_data; if (!node->hnode_name || strcmp(node->hnode_name, "qt_obj") || !hnd || !hnd->obj) { spl_clib_exception(task, "QtEx", "description", SPL_NEW_SPL_STRING(spl_string_printf(0, 0, 0, "Argument 1 to qt_signal_callback() is not a qt object.")), NULL); return 0; } if (!qts_is_castable(hnd->classId, classId_QObject)) { spl_clib_exception(task, "QtEx", "description", SPL_NEW_SPL_STRING(spl_string_printf(0, 0, 0, "Argument 1 to qt_signal_callback() is not derived from QObject.")), NULL); return 0; } QObject *object = (QObject *)qts->cast(hnd->obj, hnd->classId, classId_QObject); const char *signal = spl_clib_get_string(task); const char *signal_args = strchr(signal, '('); if (!signal_args) signal_args = "()"; char *signal_enc; asprintf(&signal_enc, "%d%s", QSIGNAL_CODE, signal); char *slot_args = strdup(signal_args+1); char *slot_args_end = strchr(slot_args, ')'); if (slot_args_end) *slot_args_end = 0; SplSignalHandler *handler = new SplSignalHandler(task->vm, spl_clib_get_node(task), slot_args); QObject::connect(object, SIGNAL(destroyed()), handler, SLOT(objectDestroyed())); QObject::connect(object, signal_enc, handler, SLOT(dynamicSlot())); free(signal_enc); return 0; } /** * Overload a virtual method in a Qt object. The method name is specified in * the SMOKE syntax (method names, optionally postfixed with '$', '#' and '?' * for scalar, QObject or other arguments). */ // builtin qt_virtual_callback(object, method, callback) static struct spl_node *handler_qt_virtual_callback(struct spl_task *task, void UNUSED(*data)) { struct spl_node *obj = spl_clib_get_node(task); char *methodname = spl_clib_get_string(task); struct spl_node *callback = spl_clib_get_node(task); struct qt_obj_virtual *it = (struct qt_obj_virtual *)calloc(1, sizeof(struct qt_obj_virtual)); it->right = qt_obj_virtual_list; if (qt_obj_virtual_list) qt_obj_virtual_list->left = it; qt_obj_virtual_list = it; if (obj->hnode_name && !strcmp(obj->hnode_name, "qt_obj")) { struct qt_obj_hnd *hnd = (struct qt_obj_hnd *)obj->hnode_data; it->methodId = qts->methodMaps[qts->idMethod(hnd->classId, qts->idMethodName(methodname))].method; it->classId = hnd->classId; it->obj = hnd->obj; } it->vm = task->vm; it->callback = callback; struct spl_node *cblist = getmoddata(task->vm)->cblist; it->callback_cbid = cblist->subs_next_idx; spl_create(task, cblist, 0, spl_get(callback), SPL_CREATE_LOCAL); spl_put(task->vm, obj); return 0; } /* ----------------------- debug function ----------------------- */ /** * Return information about classes and methods. * * This function returns a data structure describing all classes and methods * available thru this interface. */ // builtin qt_info() static struct spl_node *handler_qt_info(struct spl_task *task, void UNUSED(*data)) { struct spl_node *info = spl_get(0); for (int i=1; i<=qts->numClasses; i++) { struct spl_node *cn = SPL_NEW_STRING_DUP(qts->classes[i].className); spl_create(task, info, qts->classes[i].className, cn, SPL_CREATE_LOCAL); } // FIXME return info; } /* ----------------------- more documentation ----------------------- */ /** * This function is a simple frontend to KCmdLineArgs::init(). It must be * executed before instanciating a KApplication object. */ //function qt_kdeinit(progname, desc, version); /** * All Qt classes are available via this namespace. */ //namespace qt; /** * An instance of this object is thrown on internal errors of the Qt wrapper. */ //object QtEx /** * A description text describing the error. */ // var description; /* ----------------------- SPL Module Constructor ----------------------- */ void SPL_ABI(spl_mod_qt_init)(struct spl_vm *vm, struct spl_module *mod, int restore) { static int first_load = 1; struct spl_module *m = vm->module_list; while (m) { if (!strcmp("kde", m->name)) use_kde_libs = 1; m = m->next; } if (first_load) { // do not unload this *.so file, qt has atexit() callbacks.. // but only do it for the first module load so we do not leak // dlopen() handlers. mod->dlhandle = 0; first_load = 0; qts = init_smoke(); qts->binding = new SplSmokeBinding(qts); classId_QObject = qts->idClass("QObject"); classId_QWidget = qts->idClass("QWidget"); classId_QApplication = qts->idClass("QApplication"); } spl_undumpable_inc(vm, "Qt Module loaded"); spl_hnode_reg(vm, "qt_namespace", handler_qt_namespace, 0); spl_clib_reg(vm, "__qt_callstatic", handler_qt_callstatic, 0); spl_hnode_reg(vm, "qt_obj", handler_qt_obj, 0); spl_clib_reg(vm, "__qt_callmethod", handler_qt_callmethod, 0); if ( !restore ) { spl_hnode(vm, vm->root, "qt", "qt_namespace", mod); spl_eval(vm, 0, strdup(mod->name), " " " object QtEx { } " " " " object __qt_instanciate_wrapper { " " var qt_classname, qt_methodname; " " method init(@args) { " " return __qt_callstatic(qt_classname, " " qt_methodname, args); " " } " " } " " " " object __qt_callstatic_wrapper { " " var qt_classname, qt_methodname; " " method call(@args) { " " return __qt_callstatic(qt_classname, " " qt_methodname, args); " " } " " } " " " " object __qt_callmethod_wrapper { " " var qt_object, qt_methodname; " " method call(@args) { " " return __qt_callmethod(qt_object, " " qt_methodname, args); " " } " " } " " " " function qt_kdeinit(progname, desc, version) { " " qt.KCmdLineArgs.init(1, undef, \" \", " " progname, desc, version); " " } " " " " " " var __qt_callbacks; "); } spl_clib_reg(vm, "qt_debug", handler_qt_debug, 0); spl_clib_reg(vm, "qt_kde", handler_qt_kde, 0); spl_clib_reg(vm, "qt_ui", handler_qt_ui, 0); spl_clib_reg(vm, "qt_child", handler_qt_child, 0); spl_clib_reg(vm, "qt_cast", handler_qt_cast, 0); spl_clib_reg(vm, "qt_destroy", handler_qt_destroy, 0); spl_clib_reg(vm, "qt_delete", handler_qt_delete, 0); spl_clib_reg(vm, "qt_autodelete", handler_qt_autodelete, 0); spl_hnode_reg(vm, "qt_smoke", handler_qt_smoke, 0); spl_clib_reg(vm, "qt_as", handler_qt_as, 0); spl_clib_reg(vm, "qt_connect", handler_qt_connect, (void*)"connect"); spl_clib_reg(vm, "qt_disconnect", handler_qt_connect, (void*)"disconnect"); spl_clib_reg(vm, "qt_event_callback", handler_qt_event_callback, 0); spl_clib_reg(vm, "qt_signal_callback", handler_qt_signal_callback, 0); spl_clib_reg(vm, "qt_virtual_callback", handler_qt_virtual_callback, 0); spl_clib_reg(vm, "qt_info", handler_qt_info, 0); mod->data = calloc(1, sizeof(struct qt_moddata)); ((struct qt_moddata*)mod->data)->cblist = spl_get(spl_lookup(0, vm->root, "__qt_callbacks", 0)); } void SPL_ABI(spl_mod_qt_done)(struct spl_vm *vm, struct spl_module *mod) { spl_put(vm, ((struct qt_moddata*)mod->data)->cblist); return; } // simply include the MOC output file here #define dynamicSlot() dynamicSlotTrap(_o) #include "spl_modules/moc_mod_qt.c"