PDA

View Full Version : add signal/slots at runtime?



oc2k1
25th June 2008, 04:24
In Lumina http://lumina.sourceforge.net I have a QObject based tree that represents the (virtual) world.
The main design problem is that subobject require always simple generator function/slots like addChildFoo(....) that are used by the contextmenu and scripting.

It would be much better if the child object could be registerd without these additional functions. That won't be a problem with some abused c++ code like that:



class A{
static bool v;
public:
A(){
std::cout << "Constructor" << v << std::endl;
}
static bool init(){
std::cout << "init class A" << std::endl;
return true;
}
};

bool A::v = A::init();

int main(int argc, char **argv){
return 0; // do nothing, not really
}


A::init() is called before any regular code will be executed. It won't be a problem to use that mechanism, to register that class on a parent class (really class because no object exist at that time).

Now it would be possible that the parent class has a registered QAction (for a context menu entry) But there is a small issue left:
Either the QAction will be connected with a childclasses create function: Any information about the activating objects get lost.
Or the QAction have to be connected to the activating objects slot: Unfortunately it's also impossible to determine the right QAction, but with QAction derivated class (which knows the activating and child object) it should be solvable.

Without scripting the dynamic glueing at startup is possible, but now theres the scripting problem: It isn't possible to call the non existend slots anymore....
on e solution could be a generic create()....) slot that takes a class name as first argument, but that won't be ideal.
Another problem are objects extended by scripts, there isn't a interfact to a scripted member function....

The ideal solution would be a proxy slot / signal system, where a proxy could be created by with a signature like "slotname(argumenttype,.....)" and always it's activated a matching signal with the signature "slotname_sig(QObject*, argumenttype,.....)" will be emit.

In that case the setup would look like:

* child class registers its QAction contextmenu entry at parent class
* child class registers also a proxy slot+signal at parent class
* main() will be executed
* a new parent class will be created:
* a own instance of the registered QAction will be created (copy) and connected to that proxy slot.

if the context menu is triggered it will call the proxy slot that add the objects pointer to the arguments, so that the createChild slot can be called without problems.
A script function would also be able to call that proxy slot. that acts like a true memberfunction. Although it may handled by something unknown :P

The only problem is the implementation, because the moc has to modified :crying: to add that functionality.....

patrik08
25th June 2008, 08:25
To store QAction as static .. to use alround on code i use this piece of code....
but you must register on Q_OBJECT class to tell moc





typedef enum {
ID_NONE = 0,
WEB_PAGE_ACTUAL = 1000,
ID_ABOUT_QT = 1403
} CommandID;


struct Command {
Command() {
id = ID_NONE;
}
Command(CommandID Id, QString Name, QIcon Icon, QKeySequence Seq, QObject* Reciever, const QString& Slot) {
id = Id;
name = Name;
icon = Icon;
shortcut = Seq;
reciever = Reciever;
slot = Slot;
}

CommandID id;
QString name;
QIcon icon;
QKeySequence shortcut;
QObject* reciever;
QString slot;
};

class CommandStorage {
public:
static CommandStorage* instance();

void registerCommand(const Command&);
QAction* action(CommandID);
inline void clear() { cmds_.clear(); }

private:
CommandStorage() { }
static CommandStorage* st_;
QMap<CommandID, QAction*> cmds_;
};




#include <QtGui/QAction>

CommandStorage* CommandStorage::instance() {
if (st_ == 0)
st_ = new CommandStorage();

return st_;
}

void CommandStorage::registerCommand(const Command& cmd) {
CommandID id = cmd.id;

if (cmds_.contains(id))
delete cmds_[id];

QString keya = QString(); /* not Shortcut */
if (!cmd.shortcut.isEmpty()) {
keya = QString(" ") + cmd.shortcut.toString(QKeySequence::NativeText);
}
QAction* action = new QAction(cmd.icon, cmd.name + keya, 0);
action->setShortcut(cmd.shortcut);
action->setData(id);
action->setIcon(cmd.icon);
QObject::connect(action, SIGNAL(triggered()), cmd.reciever, qPrintable(cmd.slot));
cmds_[id] = action;
}

QAction* CommandStorage::action(CommandID id) {
return cmds_[id];
}

CommandStorage* CommandStorage::st_ = 0;




register:



void PageEdit::createCommands()
{
CommandStorage* st = CommandStorage::instance(); /* new or exist? */
st->clear(); /* clear old command !!! or replace reciver....
CommandID id is on data from qaction ... to distinct its
*/
Command cmds[] = {
Command(NEW_LAYER_AUTO,tr("New flow text Layer"),QIcon(":/img/view_remove.png"),QKeySequence("F9"),ViewPanel, SLOT(NewLayer())),
Command(NEW_LAYER_ABS,tr("New absolute Layer"),QIcon(":/img/view_sidetree.png"),QKeySequence("F10"),ViewPanel, SLOT(NewLayer())),
Command()
};
for (unsigned i = 0; cmds[i].id != 0; i++) {
Command& cmd = cmds[i];
if (st->action(cmd.id) == 0) {
st->registerCommand(cmds[i]);
}
else {
st->action(cmd.id)->setIcon(cmd.icon);
}
}
}



append on a toolbar after toolbar-clear() or compose qmenu at live time....



CommandID viewMenu[] = { NEW_LAYER_AUTO , NEW_LAYER_ABS, NEW_LAYER_OOO , PRINT_CURRENT , SAVE_PAGE, OPEN_PAGE , CLEAR_PAGE , PASTE_LAYER , ID_NONE };


for (int j = 0; viewMenu[j] != ID_NONE; j++) {
CommandID id = viewMenu[j];
QAction* a_1 = CommandStorage::instance()->action(id);
if (a_1) {
toolBar_save->addAction ( a_1 );
}
}

wysota
25th June 2008, 14:38
I haven't read your whole post (sorry, I currently can't focus myself to read a larger chunk of text), but based on what I understand I'd like to suggest using one of two solutions:

1. a dirty hack to monitor widgets that currently have focus (you do that through the application object) and remember which of the wanted widgets had focus last time and assume the action came from the last widget

2. subclass QAction and equip it with a custom signal emitting a pointer or identifier with the triggered signal. Then allow registration of objects within it and write a modified handler for the context menu so that it sets an "active" object with those actions and when the action is chosen, it emits the triggered signal with the proper pointer/identifier. A mockup could be:

void ...::customContextMenuHandler(){
QList<QAction*> actList = actions();
QMenu menu;
foreach(QAction *act, actList){
MyAction *myact = qobject_cast<MyAction*>(act);
if(myact==0) continue;
myact->setActiveObject("xyz"/this/7/whatever);
menu.addAction(myact);
}
if(menu.actions().count()>0) menu.exec(QCursor::pos());
// reset the current object
foreach(QAction *act, menu.actions()){
MyAction *myact = qobject_cast<MyAction*>(act);
if(myact==0) continue;
myact->setActiveObject(0/whatever);
}
}

The action class should emit the custom signal upon its own triggered() signal.

oc2k1
26th June 2008, 01:19
There are several QAction based versions possible, but it won't fix the non existent slots, that are required for the scripting. And using a generic call function that takes the function name as first and the arguments as second argument is ugly. I'm using something like this for calling functions in other scripts, but there are many problems with the return values....

wysota
26th June 2008, 18:00
Creating slots and signals on the fly is possible, but they won't allow scripting, so I don't think it would fix your problem.

oc2k1
26th June 2008, 21:22
Why not? Where can i find information about creating signal slots on the fly (at runtime) ?

wysota
26th June 2008, 21:44
http://doc.trolltech.com/qq/qq16-dynamicqobject.html

oc2k1
27th June 2008, 13:59
That looks interresting. But it think the optimal solution would be a templateclass, that creates a dynamic qobject from a qobject.

oc2k1
29th June 2008, 19:00
It looks like my template based class is working :D

It uses a dynamic meta object class that is derived from QMetaObject and has some functions to manipulate the internal data, so that the new slots are visible for QtScript :D

With some more code it should be possible to patch/extend a class with scripts. Unfortunately it's no possible to clone a class to use it with different extensions.... but maybe the static metaobject could be replaced by a own for each object.

oc2k1
30th July 2008, 03:39
The DQObject system is working :D In the next days I 'll release a new version of Lumina that will used that technique as core feature.

The current implementation is able to create dynamic slots combined with a callback function to handle the slot call. (Using the Qt Signal Slot system wouldn't work with static slots, because it uses a virtual function for processing the meta calls)

For scripting the new dynamic slots are like the usual ones, the can use any metatypes as argument and return value.

Maybee I'll will release the framework as a own project too.... ( If anyone is interrested :D)

jacmoe
15th June 2009, 19:52
I'd be very interested in seeing any code! :)