sigproxy: Actually rename SyncableObjects when requested
[quassel.git] / src / common / syncableobject.cpp
1 /***************************************************************************
2  *   Copyright (C) 2005-2018 by the Quassel Project                        *
3  *   devel@quassel-irc.org                                                 *
4  *                                                                         *
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) version 3.                                           *
9  *                                                                         *
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.                          *
14  *                                                                         *
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  *   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.         *
19  ***************************************************************************/
20
21 #include "syncableobject.h"
22
23 #include <QDebug>
24 #include <QMetaProperty>
25
26 #include "signalproxy.h"
27 #include "util.h"
28
29 SyncableObject::SyncableObject(QObject* parent)
30     : SyncableObject(QString{}, parent)
31 {}
32
33 SyncableObject::SyncableObject(const QString& objectName, QObject* parent)
34     : QObject(parent)
35 {
36     _objectName = objectName;
37     setObjectName(objectName);
38
39     connect(this, &QObject::objectNameChanged, this, [this](auto&& newName) {
40         for (auto&& proxy : _signalProxies) {
41             proxy->renameObject(this, newName, _objectName);
42         }
43         _objectName = newName;
44     });
45 }
46
47 SyncableObject::SyncableObject(const SyncableObject& other, QObject* parent)
48     : SyncableObject(QString{}, parent)
49 {
50     _initialized = other._initialized;
51     _allowClientUpdates = other._allowClientUpdates;
52 }
53
54 SyncableObject::~SyncableObject()
55 {
56     QList<SignalProxy*>::iterator proxyIter = _signalProxies.begin();
57     while (proxyIter != _signalProxies.end()) {
58         SignalProxy* proxy = (*proxyIter);
59         proxyIter = _signalProxies.erase(proxyIter);
60         proxy->stopSynchronize(this);
61     }
62 }
63
64 SyncableObject& SyncableObject::operator=(const SyncableObject& other)
65 {
66     if (this == &other)
67         return *this;
68
69     _initialized = other._initialized;
70     _allowClientUpdates = other._allowClientUpdates;
71     return *this;
72 }
73
74 bool SyncableObject::isInitialized() const
75 {
76     return _initialized;
77 }
78
79 void SyncableObject::setInitialized()
80 {
81     _initialized = true;
82     emit initDone();
83 }
84
85 QVariantMap SyncableObject::toVariantMap()
86 {
87     QVariantMap properties;
88
89     const QMetaObject* meta = metaObject();
90
91     // we collect data from properties
92     QMetaProperty prop;
93     QString propName;
94     for (int i = 0; i < meta->propertyCount(); i++) {
95         prop = meta->property(i);
96         propName = QString(prop.name());
97         if (propName == "objectName")
98             continue;
99         properties[propName] = prop.read(this);
100     }
101
102     // ...as well as methods, which have names starting with "init"
103     for (int i = 0; i < meta->methodCount(); i++) {
104         QMetaMethod method = meta->method(i);
105         QString methodname(SignalProxy::ExtendedMetaObject::methodName(method));
106         if (!methodname.startsWith("init") || methodname.startsWith("initSet") || methodname.startsWith("initDone"))
107             continue;
108
109         QVariant::Type variantType = QVariant::nameToType(method.typeName());
110         if (variantType == QVariant::Invalid && !QByteArray(method.typeName()).isEmpty()) {
111             qWarning() << "SyncableObject::toVariantMap(): cannot fetch init data for:" << this << method.methodSignature()
112                        << "- Returntype is unknown to Qt's MetaSystem:" << QByteArray(method.typeName());
113             continue;
114         }
115
116         QVariant value(variantType, (const void*)nullptr);
117         QGenericReturnArgument genericvalue = QGenericReturnArgument(method.typeName(), value.data());
118         QMetaObject::invokeMethod(this, methodname.toLatin1(), genericvalue);
119
120         properties[SignalProxy::ExtendedMetaObject::methodBaseName(method)] = value;
121     }
122     return properties;
123 }
124
125 void SyncableObject::fromVariantMap(const QVariantMap& properties)
126 {
127     const QMetaObject* meta = metaObject();
128
129     QVariantMap::const_iterator iterator = properties.constBegin();
130     QString propName;
131     while (iterator != properties.constEnd()) {
132         propName = iterator.key();
133         if (propName == "objectName") {
134             ++iterator;
135             continue;
136         }
137
138         int propertyIndex = meta->indexOfProperty(propName.toLatin1());
139
140         if (propertyIndex == -1 || !meta->property(propertyIndex).isWritable())
141             setInitValue(propName, iterator.value());
142         else
143             setProperty(propName.toLatin1(), iterator.value());
144         // qDebug() << "<<< SYNC:" << name << iterator.value();
145         ++iterator;
146     }
147 }
148
149 bool SyncableObject::setInitValue(const QString& property, const QVariant& value)
150 {
151     QString handlername = QString("initSet") + property;
152     handlername[7] = handlername[7].toUpper();
153
154     QString methodSignature = QString("%1(%2)").arg(handlername).arg(value.typeName());
155     int methodIdx = metaObject()->indexOfMethod(methodSignature.toLatin1().constData());
156     if (methodIdx < 0) {
157         QByteArray normedMethodName = QMetaObject::normalizedSignature(methodSignature.toLatin1().constData());
158         methodIdx = metaObject()->indexOfMethod(normedMethodName.constData());
159     }
160     if (methodIdx < 0) {
161         return false;
162     }
163
164     QGenericArgument param(value.typeName(), value.constData());
165     return QMetaObject::invokeMethod(this, handlername.toLatin1(), param);
166 }
167
168 void SyncableObject::update(const QVariantMap& properties)
169 {
170     fromVariantMap(properties);
171     SYNC(ARG(properties))
172     emit updated();
173 }
174
175 void SyncableObject::requestUpdate(const QVariantMap& properties)
176 {
177     if (allowClientUpdates()) {
178         update(properties);
179     }
180     REQUEST(ARG(properties))
181 }
182
183 void SyncableObject::sync_call__(SignalProxy::ProxyMode modeType, const char* funcname, ...) const
184 {
185     // qDebug() << Q_FUNC_INFO << modeType << funcname;
186     foreach (SignalProxy* proxy, _signalProxies) {
187         va_list ap;
188         va_start(ap, funcname);
189         proxy->sync_call__(this, modeType, funcname, ap);
190         va_end(ap);
191     }
192 }
193
194 void SyncableObject::synchronize(SignalProxy* proxy)
195 {
196     if (_signalProxies.contains(proxy))
197         return;
198     _signalProxies << proxy;
199 }
200
201 void SyncableObject::stopSynchronize(SignalProxy* proxy)
202 {
203     for (int i = 0; i < _signalProxies.count(); i++) {
204         if (_signalProxies[i] == proxy) {
205             _signalProxies.removeAt(i);
206             break;
207         }
208     }
209 }