1 /*
2 propertysyncer.cpp
3
4 This file is part of GammaRay, the Qt application inspection and
5 manipulation tool.
6
7 Copyright (C) 2015-2021 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com
8 Author: Volker Krause <volker.krause@kdab.com>
9
10 Licensees holding valid commercial KDAB GammaRay licenses may use this file in
11 accordance with GammaRay Commercial License Agreement provided with the Software.
12
13 Contact info@kdab.com if any conditions of this licensing are not clear to you.
14
15 This program is free software; you can redistribute it and/or modify
16 it under the terms of the GNU General Public License as published by
17 the Free Software Foundation, either version 2 of the License, or
18 (at your option) any later version.
19
20 This program is distributed in the hope that it will be useful,
21 but WITHOUT ANY WARRANTY; without even the implied warranty of
22 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23 GNU General Public License for more details.
24
25 You should have received a copy of the GNU General Public License
26 along with this program. If not, see <http://www.gnu.org/licenses/>.
27 */
28
29 #include "propertysyncer.h"
30 #include "message.h"
31
32 #include <compat/qasconst.h>
33
34 #include <QDebug>
35 #include <QMetaProperty>
36
37 #include <algorithm>
38
39 using namespace GammaRay;
40
qobjectPropertyOffset()41 static int qobjectPropertyOffset()
42 {
43 return QObject::staticMetaObject.propertyCount();
44 }
45
PropertySyncer(QObject * parent)46 PropertySyncer::PropertySyncer(QObject *parent)
47 : QObject(parent)
48 , m_address(Protocol::InvalidObjectAddress)
49 , m_initialSync(false)
50 {
51 }
52
53 PropertySyncer::~PropertySyncer() = default;
54
setRequestInitialSync(bool initialSync)55 void PropertySyncer::setRequestInitialSync(bool initialSync)
56 {
57 m_initialSync = initialSync;
58 }
59
addObject(Protocol::ObjectAddress addr,QObject * obj)60 void PropertySyncer::addObject(Protocol::ObjectAddress addr, QObject *obj)
61 {
62 Q_ASSERT(addr != Protocol::InvalidObjectAddress);
63 Q_ASSERT(obj);
64 if (qobjectPropertyOffset() == obj->metaObject()->propertyCount())
65 return; // no properties we could sync
66
67 for (int i = qobjectPropertyOffset(); i < obj->metaObject()->propertyCount(); ++i) {
68 const auto prop = obj->metaObject()->property(i);
69 if (!prop.hasNotifySignal())
70 continue;
71 connect(obj, QByteArray("2") + prop.notifySignal().methodSignature(), this, SLOT(propertyChanged()));
72 }
73
74 connect(obj, &QObject::destroyed, this, &PropertySyncer::objectDestroyed);
75
76 ObjectInfo info;
77 info.addr = addr;
78 info.obj = obj;
79 info.recursionLock = false;
80 info.enabled = false;
81 m_objects.push_back(info);
82 }
83
setObjectEnabled(Protocol::ObjectAddress addr,bool enabled)84 void PropertySyncer::setObjectEnabled(Protocol::ObjectAddress addr, bool enabled)
85 {
86 const auto it = std::find_if(m_objects.begin(), m_objects.end(), [addr](
87 const ObjectInfo &info) {
88 return info.addr == addr;
89 });
90 if (it == m_objects.end() || (*it).enabled == enabled)
91 return;
92
93 (*it).enabled = enabled;
94 if (enabled && m_initialSync) {
95 Message msg(m_address, Protocol::PropertySyncRequest);
96 msg << addr;
97 emit message(msg);
98 }
99 }
100
address() const101 Protocol::ObjectAddress PropertySyncer::address() const
102 {
103 return m_address;
104 }
105
setAddress(Protocol::ObjectAddress addr)106 void PropertySyncer::setAddress(Protocol::ObjectAddress addr)
107 {
108 m_address = addr;
109 }
110
handleMessage(const GammaRay::Message & msg)111 void PropertySyncer::handleMessage(const GammaRay::Message &msg)
112 {
113 Q_ASSERT(msg.address() == m_address);
114 switch (msg.type()) {
115 case Protocol::PropertySyncRequest:
116 {
117 Protocol::ObjectAddress addr;
118 msg >> addr;
119 Q_ASSERT(addr != Protocol::InvalidObjectAddress);
120
121 const auto it
122 = std::find_if(m_objects.constBegin(), m_objects.constEnd(),
123 [addr](const ObjectInfo &info) {
124 return info.addr == addr;
125 });
126 if (it == m_objects.constEnd())
127 break;
128
129 QVector<QPair<QByteArray, QVariant> > values;
130 const auto propCount = (*it).obj->metaObject()->propertyCount();
131 values.reserve(propCount);
132 for (int i = qobjectPropertyOffset(); i < propCount; ++i) {
133 const auto prop = (*it).obj->metaObject()->property(i);
134 values.push_back(qMakePair(QByteArray(prop.name()), prop.read((*it).obj)));
135 }
136 Q_ASSERT(!values.isEmpty());
137
138 Message msg(m_address, Protocol::PropertyValuesChanged);
139 msg << addr << (quint32)values.size();
140 for (const auto &value : qAsConst(values))
141 msg << value.first << value.second;
142 emit message(msg);
143 break;
144 }
145 case Protocol::PropertyValuesChanged:
146 {
147 Protocol::ObjectAddress addr;
148 quint32 changeSize;
149 msg >> addr >> changeSize;
150 Q_ASSERT(addr != Protocol::InvalidObjectAddress);
151 Q_ASSERT(changeSize > 0);
152
153 auto it = std::find_if(m_objects.begin(), m_objects.end(), [addr](const ObjectInfo &info) {
154 return info.addr == addr;
155 });
156 if (it == m_objects.end())
157 break;
158
159 for (quint32 i = 0; i < changeSize; ++i) {
160 QByteArray propName;
161 QVariant propValue;
162 msg >> propName >> propValue;
163 (*it).recursionLock = true;
164 (*it).obj->setProperty(propName, propValue);
165
166 // it can be invalid if as a result of the above call new objects have been registered for example
167 it = std::find_if(m_objects.begin(), m_objects.end(), [addr](const ObjectInfo &info) {
168 return info.addr == addr;
169 });
170 Q_ASSERT(it != m_objects.end());
171 (*it).recursionLock = false;
172 }
173 break;
174 }
175 default:
176 Q_ASSERT_X(false, "PropertySyncer::handleMessage",
177 "Unexpected Gammaray::Message type encountered");
178 }
179 }
180
propertyChanged()181 void PropertySyncer::propertyChanged()
182 {
183 const auto *obj = sender();
184 Q_ASSERT(obj);
185 const auto it
186 = std::find_if(m_objects.constBegin(), m_objects.constEnd(), [obj](const ObjectInfo &info) {
187 return info.obj == obj;
188 });
189 Q_ASSERT(it != m_objects.constEnd());
190
191 if ((*it).recursionLock || !(*it).enabled)
192 return;
193
194 const auto sigIndex = senderSignalIndex();
195 QVector<QPair<QByteArray, QVariant> > changes;
196 for (int i = qobjectPropertyOffset(); i < obj->metaObject()->propertyCount(); ++i) {
197 const auto prop = obj->metaObject()->property(i);
198 if (prop.notifySignalIndex() != sigIndex)
199 continue;
200 changes.push_back(qMakePair(QByteArray(prop.name()), prop.read(obj)));
201 }
202 Q_ASSERT(!changes.isEmpty());
203
204 Message msg(m_address, Protocol::PropertyValuesChanged);
205 msg << (*it).addr << (quint32)changes.size();
206 for (const auto &change : qAsConst(changes))
207 msg << change.first << change.second;
208 emit message(msg);
209 }
210
objectDestroyed(QObject * obj)211 void PropertySyncer::objectDestroyed(QObject *obj)
212 {
213 const auto it = std::find_if(m_objects.begin(), m_objects.end(), [obj](const ObjectInfo &info) {
214 return info.obj == obj;
215 });
216 Q_ASSERT(it != m_objects.end());
217 m_objects.erase(it);
218 }
219