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