1 /*
2 bindingmodel.cpp
3
4 This file is part of GammaRay, the Qt application inspection and
5 manipulation tool.
6
7 Copyright (C) 2017-2021 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com
8 Authors: Volker Krause <volker.krause@kdab.com>
9 Anton Kreuzkamp <anton.kreuzkamp@kdab.com>
10
11 Licensees holding valid commercial KDAB GammaRay licenses may use this file in
12 accordance with GammaRay Commercial License Agreement provided with the Software.
13
14 Contact info@kdab.com if any conditions of this licensing are not clear to you.
15
16 This program is free software; you can redistribute it and/or modify
17 it under the terms of the GNU General Public License as published by
18 the Free Software Foundation, either version 2 of the License, or
19 (at your option) any later version.
20
21 This program is distributed in the hope that it will be useful,
22 but WITHOUT ANY WARRANTY; without even the implied warranty of
23 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
24 GNU General Public License for more details.
25
26 You should have received a copy of the GNU General Public License
27 along with this program. If not, see <http://www.gnu.org/licenses/>.
28 */
29
30 // Own
31 #include "bindingmodel.h"
32
33 #include <common/objectmodel.h>
34 #include <common/problem.h>
35 #include <core/problemcollector.h>
36 #include <core/objectdataprovider.h>
37 #include <core/abstractbindingprovider.h>
38 #include <core/bindingnode.h>
39 #include <core/util.h>
40
41 // Qt
42 #include <QDebug>
43 #include <QMetaProperty>
44
45 using namespace GammaRay;
46
BindingModel(QObject * parent)47 BindingModel::BindingModel(QObject* parent)
48 : QAbstractItemModel(parent)
49 , m_obj(nullptr)
50 , m_bindings(nullptr)
51 {
52 }
53
54 BindingModel::~BindingModel() = default;
55
aboutToClear()56 void BindingModel::aboutToClear()
57 {
58 beginResetModel();
59 }
60
cleared()61 void BindingModel::cleared()
62 {
63 m_obj = nullptr;
64 endResetModel();
65 }
66
setObject(QObject * obj,std::vector<std::unique_ptr<BindingNode>> & bindings)67 void BindingModel::setObject(QObject* obj, std::vector<std::unique_ptr<BindingNode>> &bindings)
68 {
69 if (m_obj == obj)
70 return;
71
72 // TODO use removerows/insertrows instead of reset here
73 beginResetModel();
74 m_bindings = &bindings;
75 m_obj = obj;
76 endResetModel();
77 }
78
refresh(int row,std::vector<std::unique_ptr<BindingNode>> && newDependencies)79 void GammaRay::BindingModel::refresh(int row, std::vector<std::unique_ptr<BindingNode>> &&newDependencies)
80 {
81 Q_ASSERT(m_bindings);
82 refresh((*m_bindings)[row].get(), std::move(newDependencies), createIndex(row, 0, (*m_bindings)[row].get()));
83 }
84
lessThan(const std::unique_ptr<BindingNode> & a,const std::unique_ptr<BindingNode> & b)85 bool BindingModel::lessThan(const std::unique_ptr<BindingNode> &a, const std::unique_ptr<BindingNode> &b) {
86 return a->object() < b->object()
87 || (a->object() == b->object() && a->propertyIndex() < b->propertyIndex());
88 }
89
refresh(BindingNode * oldBindingNode,std::vector<std::unique_ptr<BindingNode>> && newDependencies,const QModelIndex & index)90 void BindingModel::refresh(BindingNode *oldBindingNode, std::vector<std::unique_ptr<BindingNode>> &&newDependencies, const QModelIndex &index)
91 {
92 if (oldBindingNode->cachedValue() != oldBindingNode->readValue()) {
93 oldBindingNode->refreshValue();
94 emit dataChanged(createIndex(index.row(), ValueColumn, oldBindingNode), createIndex(index.row(), ValueColumn, oldBindingNode));
95 }
96 uint oldDepth = oldBindingNode->depth();
97
98 // Refresh dependencies
99 auto &oldDependencies = oldBindingNode->dependencies();
100 std::sort(newDependencies.begin(), newDependencies.end(), &BindingModel::lessThan);
101 oldDependencies.reserve(newDependencies.size());
102 auto oldIt = oldDependencies.begin();
103 auto newIt = newDependencies.begin();
104
105 while (oldIt != oldDependencies.end() && newIt != newDependencies.end()) {
106 const auto idx = std::distance(oldDependencies.begin(), oldIt);
107 if (lessThan(*oldIt, *newIt)) { // handle deleted node
108 const auto firstToRemove = oldIt;
109 while (oldIt != oldDependencies.end() && lessThan(*oldIt, *newIt)) { ++oldIt; } // if more than one was removed, find all
110 const auto count = std::distance(firstToRemove, oldIt);
111 beginRemoveRows(index, idx, idx + count - 1);
112 oldIt = oldDependencies.erase(firstToRemove, oldIt);
113 endRemoveRows();
114 } else if (lessThan(*newIt, *oldIt)) { // handle added node
115 int count = 0;
116 for (auto addIt = newIt; addIt != newDependencies.end() && lessThan(*addIt, *oldIt); ++addIt) {
117 ++count; // count, how many additions we have
118 }
119 beginInsertRows(index, idx, idx + count - 1);
120 for (int i = 0; i < count; ++i) {
121 (*newIt)->setParent(oldBindingNode);
122 oldIt = oldDependencies.insert(oldIt, std::move(*newIt));
123 ++oldIt;
124 ++newIt;
125 }
126 endInsertRows();
127 } else { // already known node, no change
128 refresh(oldIt->get(), std::move(newIt->get()->dependencies()), createIndex(idx, 0, oldIt->get()));
129 ++oldIt;
130 ++newIt;
131 }
132 }
133 if (oldIt == oldDependencies.end() && newIt != newDependencies.end()) {
134 // Add remaining new items to list and inform the client
135 const auto idx = std::distance(oldDependencies.begin(), oldIt);
136 const auto count = std::distance(newIt, newDependencies.end());
137
138 beginInsertRows(index, idx, idx + count - 1);
139 while (newIt != newDependencies.end()) {
140 (*newIt)->setParent(oldBindingNode);
141 oldDependencies.push_back(std::move(*newIt));
142 ++newIt;
143 }
144 endInsertRows();
145 } else if (oldIt != oldDependencies.end()) { // Inform the client about the removed rows
146 const auto idx = std::distance(oldDependencies.begin(), oldIt);
147 const auto count = std::distance(oldIt, oldDependencies.end());
148
149 beginRemoveRows(index, idx, idx + count - 1);
150 oldIt = oldDependencies.erase(oldIt, oldDependencies.end());
151 endRemoveRows();
152 }
153
154 if (oldBindingNode->depth() != oldDepth) {
155 emit dataChanged(createIndex(index.row(), DepthColumn, oldBindingNode), createIndex(index.row(), DepthColumn, oldBindingNode));
156 }
157 }
158
columnCount(const QModelIndex & parent) const159 int BindingModel::columnCount(const QModelIndex& parent) const
160 {
161 Q_UNUSED(parent);
162 return 4;
163 }
164
rowCount(const QModelIndex & parent) const165 int BindingModel::rowCount(const QModelIndex& parent) const
166 {
167 if (!m_bindings)
168 return 0;
169 if (!parent.isValid())
170 return m_bindings->size();
171 if (parent.column() != 0)
172 return 0;
173 return static_cast<BindingNode *>(parent.internalPointer())->dependencies().size();
174 }
175
data(const QModelIndex & index,int role) const176 QVariant BindingModel::data(const QModelIndex& index, int role) const
177 {
178 if (!index.isValid())
179 return QVariant();
180
181 BindingNode *binding = static_cast<BindingNode*>(index.internalPointer());
182 if (!binding)
183 return QVariant();
184
185 if (role == Qt::DisplayRole) {
186 switch (index.column()) {
187 case NameColumn: {
188 return binding->canonicalName();
189 }
190 case ValueColumn: return binding->cachedValue();
191 case LocationColumn: return binding->sourceLocation().displayString();
192 case DepthColumn: {
193 uint depth = binding->depth();
194 return depth == std::numeric_limits<uint>::max() ? QStringLiteral("\u221E") : QString::number(depth); // Unicode infinity sign
195 }
196 }
197 } else if (role == ObjectModel::DeclarationLocationRole) {
198 return QVariant::fromValue(binding->sourceLocation());
199 }
200
201 return QVariant();
202 }
203
itemData(const QModelIndex & index) const204 QMap<int, QVariant> BindingModel::itemData(const QModelIndex &index) const
205 {
206 QMap<int, QVariant> d = QAbstractItemModel::itemData(index);
207 d.insert(ObjectModel::DeclarationLocationRole, data(index, ObjectModel::DeclarationLocationRole));
208 return d;
209 }
210
headerData(int section,Qt::Orientation orientation,int role) const211 QVariant BindingModel::headerData(int section, Qt::Orientation orientation, int role) const
212 {
213 if (orientation == Qt::Horizontal && role == Qt::DisplayRole) {
214 switch (section) {
215 case NameColumn: return tr("Property");
216 case ValueColumn: return tr("Value");
217 case LocationColumn: return tr("Source");
218 case DepthColumn: return tr("Depth");
219 }
220 }
221 return QAbstractItemModel::headerData(section, orientation, role);
222 }
223
index(int row,int column,const QModelIndex & parent) const224 QModelIndex GammaRay::BindingModel::index(int row, int column, const QModelIndex& parent) const
225 {
226 if (!m_bindings || !hasIndex(row, column, parent)) {
227 return {};
228 }
229 QModelIndex index;
230 if (parent.isValid()) {
231 index = createIndex(row, column, static_cast<BindingNode *>(parent.internalPointer())->dependencies()[row].get());
232 } else {
233 index = createIndex(row, column, (*m_bindings)[row].get());
234 }
235 return index;
236 }
237
findEquivalent(const std::vector<std::unique_ptr<BindingNode>> & container,BindingNode * bindingNode) const238 QModelIndex BindingModel::findEquivalent(const std::vector<std::unique_ptr<BindingNode>> &container, BindingNode *bindingNode) const
239 {
240 for (size_t i = 0; i < container.size(); i++) {
241 if (bindingNode->object() == container[i]->object() && bindingNode->propertyIndex() == container[i]->propertyIndex()) {
242 return createIndex(i, 0, container[i].get());
243 }
244 }
245 return {};
246 }
247
parent(const QModelIndex & child) const248 QModelIndex GammaRay::BindingModel::parent(const QModelIndex& child) const
249 {
250 if (!m_bindings || !child.isValid())
251 return {};
252
253 BindingNode *parent = static_cast<BindingNode *>(child.internalPointer())->parent();
254 if (!parent)
255 return QModelIndex();
256
257 BindingNode *grandparent = parent->parent();
258
259 if (!grandparent)
260 return findEquivalent(*m_bindings, parent);
261
262 return findEquivalent(grandparent->dependencies(), parent);
263 }
264