1 /***************************************************************************
2 * Copyright (C) 2005-07 by The Quassel Team *
3 * devel@quassel-irc.org *
5 * This program is free software; you can redistribute it and/or modify *
6 * it under the terms of the GNU General Public License as published by *
7 * the Free Software Foundation; either version 2 of the License, or *
8 * (at your option) any later version. *
10 * This program is distributed in the hope that it will be useful, *
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
13 * GNU General Public License for more details. *
15 * You should have received a copy of the GNU General Public License *
16 * along with this program; if not, write to the *
17 * Free Software Foundation, Inc., *
18 * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
19 ***************************************************************************/
20 #include "synchronizer.h"
24 #include <QMetaProperty>
25 #include <QMetaMethod>
28 #include "signalproxy.h"
33 // ====================
35 // ====================
36 Synchronizer::Synchronizer(QObject *parent, SignalProxy *signalproxy)
39 _signalproxy(signalproxy)
42 if(!getMethodByName("objectNameSet()").isEmpty())
43 connect(parent,SIGNAL(objectNameSet()), this, SLOT(parentChangedName()));
46 bool Synchronizer::initialized() const {
50 SignalProxy *Synchronizer::proxy() const {
54 QVariantMap Synchronizer::initData() const {
55 QVariantMap properties;
57 // we collect data from properties
58 foreach(QMetaProperty property, parentProperties()) {
59 QString name = QString(property.name());
60 QVariant value = property.read(parent());
61 properties[name] = value;
62 // qDebug() << ">>> SYNC:" << name << value;
65 // ...as well as methods, which have names starting with "init"
66 foreach(QMetaMethod method, parentSlots()) {
67 QString methodname = QString(method.signature()).section("(", 0, 0);
68 if(methodname.startsWith("initSet") ||
69 !methodname.startsWith("init"))
72 QVariant value = QVariant(QVariant::nameToType(method.typeName()));
73 QGenericReturnArgument genericvalue = QGenericReturnArgument(method.typeName(), &value);
74 QMetaObject::invokeMethod(parent(), methodname.toAscii(), genericvalue);
76 properties[methodBaseName(method)] = value;
77 // qDebug() << ">>> SYNC:" << methodBaseName(method) << value;
80 // properties["Payload"] = QByteArray(10000000, 'a'); // for testing purposes
84 void Synchronizer::setInitData(const QVariantMap &properties) {
88 const QMetaObject *metaobject = parent()->metaObject();
89 QMapIterator<QString, QVariant> iterator(properties);
90 while(iterator.hasNext()) {
92 QString name = iterator.key();
93 int propertyIndex = metaobject->indexOfProperty(name.toAscii());
94 if(propertyIndex == -1) {
95 setInitValue(name, iterator.value());
97 if((metaobject->property(propertyIndex)).isWritable())
98 parent()->setProperty(name.toAscii(), iterator.value());
100 setInitValue(name, iterator.value());
102 // qDebug() << "<<< SYNC:" << name << iterator.value();
109 // ====================
111 // ====================
112 void Synchronizer::synchronizeClients() const {
113 emit sendInitData(initData());
116 void Synchronizer::recvInitData(QVariantMap properties) {
117 proxy()->detachObject(this);
118 setInitData(properties);
121 void Synchronizer::parentChangedName() {
122 proxy()->detachObject(parent());
123 proxy()->detachObject(this);
127 // ====================
129 // ====================
130 QString Synchronizer::signalPrefix() const {
131 return QString(parent()->metaObject()->className()) + "_" + QString(parent()->objectName()) + "_";
134 QString Synchronizer::initSignal() const {
135 return QString(metaObject()->className())
136 + "_" + QString(parent()->metaObject()->className())
137 + "_" + QString(parent()->objectName())
138 + "_" + QString(SIGNAL(sendInitData(QVariantMap)));
141 QString Synchronizer::requestSyncSignal() const {
142 return QString(metaObject()->className())
143 + "_" + QString(parent()->metaObject()->className())
144 + "_" + QString(parent()->objectName())
145 + "_" + QString(SIGNAL(requestSync()));
148 QString Synchronizer::methodBaseName(const QMetaMethod &method) const {
149 QString methodname = QString(method.signature()).section("(", 0, 0);
151 // determine where we have to chop:
152 if(method.methodType() == QMetaMethod::Slot) {
153 // we take evertyhing from the first uppercase char if it's slot
154 methodname = methodname.mid(methodname.indexOf(QRegExp("[A-Z]")));
156 // and if it's a signal we discard everything from the last uppercase char
157 methodname = methodname.left(methodname.lastIndexOf(QRegExp("[A-Z]")));
160 methodname[0] = methodname[0].toUpper();
165 bool Synchronizer::methodsMatch(const QMetaMethod &signal, const QMetaMethod &slot) const {
166 // if we don't even have the same basename it's a sure NO
167 if(methodBaseName(signal) != methodBaseName(slot))
170 const QMetaObject *metaobject = parent()->metaObject();
172 // are the signatures compatible?
173 if(! metaobject->checkConnectArgs(signal.signature(), slot.signature()))
176 // we take an educated guess if the signals and slots match
177 QString signalsuffix = QString(signal.signature()).section("(", 0, 0);
178 signalsuffix = signalsuffix.mid(signalsuffix.lastIndexOf(QRegExp("[A-Z]"))).toLower();
180 QString slotprefix = QString(slot.signature()).section("(", 0, 0);
181 slotprefix = slotprefix.left(slotprefix.indexOf(QRegExp("[A-Z]"))).toLower();
184 if(signalsuffix.size() < slotprefix.size())
185 sizediff = slotprefix.size() - signalsuffix.size();
187 sizediff = signalsuffix.size() - slotprefix.size();
189 int ratio = editingDistance(slotprefix, signalsuffix) - sizediff;
194 QList<QMetaProperty> Synchronizer::parentProperties() const {
195 QList<QMetaProperty> _properties;
197 const QMetaObject *metaobject = parent()->metaObject();
198 for(int i = metaobject->propertyOffset(); i < metaobject->propertyCount(); i++) {
199 _properties << metaobject->property(i);
205 QList<QMetaMethod> Synchronizer::parentSlots() const {
206 QList<QMetaMethod> _slots;
208 const QMetaObject *metaobject = parent()->metaObject();
209 for(int i = metaobject->methodOffset(); i < metaobject->methodCount(); i++) {
210 QMetaMethod method = metaobject->method(i);
211 if(method.methodType() == QMetaMethod::Slot)
219 QList<QMetaMethod> Synchronizer::parentSignals() const {
220 QList<QMetaMethod> _signals;
222 const QMetaObject *metaobject = parent()->metaObject();
223 for(int i = metaobject->methodOffset(); i < metaobject->methodCount(); i++) {
224 QMetaMethod method = metaobject->method(i);
225 if(method.methodType() == QMetaMethod::Signal)
232 QList<QMetaMethod> Synchronizer::getMethodByName(const QString &methodname) {
233 QList<QMetaMethod> _methods;
235 const QMetaObject* metaobject = parent()->metaObject();
236 for(int i = metaobject->methodOffset(); i < metaobject->methodCount(); i++) {
237 if(QString(metaobject->method(i).signature()).startsWith(methodname))
238 _methods << metaobject->method(i);
244 void Synchronizer::attach() {
245 if(proxy()->proxyMode() == SignalProxy::Server)
251 void Synchronizer::attachAsSlave() {
252 QList<QMetaMethod> signals_ = parentSignals();
254 foreach(QMetaMethod slot, parentSlots()) {
258 for(int i = 0; i < signals_.count(); i++) {
259 QMetaMethod signal = signals_[i];
260 if(!methodsMatch(signal, slot))
263 // we could simply put a "1" in front of the normalized signature
264 // but to guarantee future compatibility we construct a dummy signal
265 // and replace the known the fake signature by ours...
266 QString dummySlot = QString(SIGNAL(dummy()));
267 QString proxySignal = signalPrefix() + QString(signal.signature());
268 QString slotsignature = dummySlot.replace("dummy()", QString(slot.signature()));
270 // qDebug() << "attachSlot:" << proxySignal << slotsignature;
271 proxy()->attachSlot(proxySignal.toAscii(), parent(), slotsignature.toAscii());
272 signals_.removeAt(i);
277 if(!getMethodByName("setInitialized()").isEmpty())
278 connect(this, SIGNAL(initDone()), parent(), SLOT(setInitialized()));
281 // and then we connect ourself, so we can receive init data
282 // qDebug() << "attachSlot:" << initSignal() << "recvInitData(QVariantMap)";
283 // qDebug() << "attachSignal:" << "requestSync()" << requestSyncSignal();
284 proxy()->attachSlot(initSignal().toAscii(), this, SLOT(recvInitData(QVariantMap)));
285 proxy()->attachSignal(this, SIGNAL(requestSync()), requestSyncSignal().toAscii());
291 void Synchronizer::attachAsMaster() {
292 QList<QMetaMethod> slots_ = parentSlots();
294 foreach(QMetaMethod signal, parentSignals()) {
298 // we don't attach all signals, just the ones that have a maching counterpart
299 for(int i = 0; i < slots_.count(); i++) {
300 QMetaMethod slot = slots_[i];
301 if(!methodsMatch(signal, slot))
304 // we could simply put a "2" in front of the normalized signature
305 // but to guarantee future compatibility we construct a dummy signal
306 // and replace the known the fake signature by ours...
307 QString dummySignal = QString(SIGNAL(dummy()));
308 QString proxySignal = signalPrefix() + QString(signal.signature());
309 QString signalsignature = dummySignal.replace("dummy()", QString(signal.signature()));
311 // qDebug() << "attachSignal:" << signalsignature << proxySignal;
312 proxy()->attachSignal(parent(), signalsignature.toAscii(), proxySignal.toAscii());
318 // and then we connect ourself, so we can initialize slaves
319 // qDebug() << "attachSignal:" << "sendInitData(QVariantMap)" << initSignal();
320 // qDebug() << "attachSlot:" << "synchronizeClients()" << requestSyncSignal();
321 proxy()->attachSignal(this, SIGNAL(sendInitData(QVariantMap)), initSignal().toAscii());
322 proxy()->attachSlot(requestSyncSignal().toAscii(), this, SLOT(synchronizeClients()));
325 bool Synchronizer::setInitValue(const QString &property, const QVariant &value) {
326 QString handlername = QString("initSet") + property;
327 handlername[7] = handlername[7].toUpper();
329 //determine param type
330 QByteArray paramtype;
331 foreach(QMetaMethod method, getMethodByName(handlername)) {
332 if(method.parameterTypes().size() == 1) {
333 paramtype = method.parameterTypes()[0];
338 if(paramtype.isNull())
341 QGenericArgument param = QGenericArgument(paramtype, &value);
342 return QMetaObject::invokeMethod(parent(), handlername.toAscii(), param);