1 /*
2 networkselectionmodel.cpp
3
4 This file is part of GammaRay, the Qt application inspection and
5 manipulation tool.
6
7 Copyright (C) 2013-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 "networkselectionmodel.h"
30 #include "common/modelutils.h"
31 #include "endpoint.h"
32 #include "message.h"
33 #include "settempvalue.h"
34
35 #include <QAbstractProxyModel>
36 #include <QDebug>
37
38 using namespace GammaRay;
39
operator <<(QDataStream & out,QItemSelectionModel::SelectionFlags command)40 static QDataStream &operator<<(QDataStream &out, QItemSelectionModel::SelectionFlags command)
41 {
42 out << (quint32)command; // Qt4 and Qt5 use the same enum layout, so this is fine for now
43 return out;
44 }
45
operator >>(QDataStream & in,QItemSelectionModel::SelectionFlags & command)46 QDataStream &operator>>(QDataStream &in, QItemSelectionModel::SelectionFlags &command)
47 {
48 quint32 v;
49 in >> v;
50 command = static_cast<QItemSelectionModel::SelectionFlags>(v);
51 return in;
52 }
53
writeSelection(Message * msg,const QItemSelection & selection)54 static void writeSelection(Message *msg, const QItemSelection &selection)
55 {
56 *msg << qint32(selection.size());
57 for (const QItemSelectionRange &range : selection)
58 *msg << Protocol::fromQModelIndex(range.topLeft()) << Protocol::fromQModelIndex(
59 range.bottomRight());
60 }
61
62 // find a model having a defaultSelectedItem method
findSourceModel(QAbstractItemModel * model)63 static QAbstractItemModel *findSourceModel(QAbstractItemModel *model)
64 {
65 if (model) {
66 if (model->metaObject()->indexOfMethod(QMetaObject::normalizedSignature(
67 "defaultSelectedItem()")) != -1)
68 return model;
69 else if (auto proxy = qobject_cast<QAbstractProxyModel *>(model))
70 return findSourceModel(proxy->sourceModel());
71 }
72
73 return nullptr;
74 }
75
NetworkSelectionModel(const QString & objectName,QAbstractItemModel * model,QObject * parent)76 NetworkSelectionModel::NetworkSelectionModel(const QString &objectName, QAbstractItemModel *model,
77 QObject *parent)
78 : QItemSelectionModel(model, parent)
79 , m_objectName(objectName)
80 , m_myAddress(Protocol::InvalidObjectAddress)
81 , m_pendingCommand(NoUpdate)
82 , m_handlingRemoteMessage(false)
83 {
84 setObjectName(m_objectName + QLatin1String("Network"));
85 connect(this, &QItemSelectionModel::currentChanged, this,
86 &NetworkSelectionModel::slotCurrentChanged);
87 }
88
89 NetworkSelectionModel::~NetworkSelectionModel() = default;
90
requestSelection()91 void NetworkSelectionModel::requestSelection()
92 {
93 if (m_handlingRemoteMessage || !isConnected())
94 return;
95 Message msg(m_myAddress, Protocol::SelectionModelStateRequest);
96 Endpoint::send(msg);
97 }
98
sendSelection()99 void NetworkSelectionModel::sendSelection()
100 {
101 if (!isConnected())
102 return;
103
104 clearPendingSelection();
105
106 if (!hasSelection()) {
107 if (model()->rowCount() > 0) {
108 const QItemSelectionModel::SelectionFlags selectionFlags
109 = QItemSelectionModel::ClearAndSelect
110 |QItemSelectionModel::Rows
111 | QItemSelectionModel::Current;
112 const Qt::MatchFlags matchFlags = Qt::MatchExactly | Qt::MatchRecursive | Qt::MatchWrap;
113 QAbstractItemModel *sourceModel = findSourceModel(model());
114 QModelIndex index = model()->index(0, 0);
115
116 // Query the model to get its default selected index
117 if (sourceModel) {
118 QPair<int, QVariant> result;
119 QModelIndex defaultIndex;
120
121 QMetaObject::invokeMethod(sourceModel, "defaultSelectedItem", Qt::DirectConnection,
122 QReturnArgument<QPair<int, QVariant> >("QPair<int,QVariant>",
123 result));
124
125 if (result.second.userType() == qMetaTypeId<ModelUtils::MatchAcceptor>()) {
126 defaultIndex
127 = ModelUtils::match(index, result.first,
128 result.second.value<ModelUtils::MatchAcceptor>(),
129 1, matchFlags).value(0);
130 } else {
131 defaultIndex
132 = model()->match(index, result.first, result.second, 1,
133 matchFlags).value(0);
134 }
135
136 if (defaultIndex.isValid())
137 index = defaultIndex;
138 }
139
140 select(QItemSelection(index, index), selectionFlags);
141 }
142 } else {
143 Message msg(m_myAddress, Protocol::SelectionModelSelect);
144 writeSelection(&msg, selection());
145 msg << ClearAndSelect;
146 Endpoint::send(msg);
147 }
148 }
149
readSelection(const GammaRay::Message & msg)150 Protocol::ItemSelection GammaRay::NetworkSelectionModel::readSelection(const GammaRay::Message &msg)
151 {
152 Protocol::ItemSelection selection;
153 qint32 size = 0;
154 msg >> size;
155 selection.reserve(size);
156
157 for (int i = 0; i < size; ++i) {
158 Protocol::ItemSelectionRange range;
159 msg >> range.topLeft >> range.bottomRight;
160 selection.push_back(range);
161 }
162
163 return selection;
164 }
165
translateSelection(const Protocol::ItemSelection & selection,QItemSelection & qselection) const166 bool GammaRay::NetworkSelectionModel::translateSelection(const Protocol::ItemSelection &selection,
167 QItemSelection &qselection) const
168 {
169 qselection.clear();
170 for (const auto &range : selection) {
171 const QModelIndex qmiTopLeft = Protocol::toQModelIndex(model(), range.topLeft);
172 const QModelIndex qmiBottomRight = Protocol::toQModelIndex(model(), range.bottomRight);
173 if (!qmiTopLeft.isValid() && !qmiBottomRight.isValid())
174 return false;
175 qselection.push_back(QItemSelectionRange(qmiTopLeft, qmiBottomRight));
176 }
177 return true;
178 }
179
newMessage(const Message & msg)180 void NetworkSelectionModel::newMessage(const Message &msg)
181 {
182 Q_ASSERT(msg.address() == m_myAddress);
183 switch (msg.type()) {
184 case Protocol::SelectionModelSelect:
185 {
186 Util::SetTempValue<bool> guard(m_handlingRemoteMessage, true);
187 m_pendingSelection = readSelection(msg);
188 msg >> m_pendingCommand;
189 applyPendingSelection();
190 break;
191 }
192 case Protocol::SelectionModelCurrent:
193 {
194 SelectionFlags flags;
195 Protocol::ModelIndex index;
196 msg >> flags >> index;
197 const QModelIndex qmi = Protocol::toQModelIndex(model(), index);
198 if (!qmi.isValid())
199 break;
200 Util::SetTempValue<bool> guard(m_handlingRemoteMessage, true);
201 setCurrentIndex(qmi, flags);
202 break;
203 }
204 case Protocol::SelectionModelStateRequest:
205 sendSelection();
206 break;
207 default:
208 Q_ASSERT(false);
209 }
210 }
211
slotCurrentChanged(const QModelIndex & current,const QModelIndex & previous)212 void NetworkSelectionModel::slotCurrentChanged(const QModelIndex ¤t,
213 const QModelIndex &previous)
214 {
215 Q_UNUSED(previous);
216 if (m_handlingRemoteMessage || !isConnected())
217 return;
218 clearPendingSelection();
219
220 Message msg(m_myAddress, Protocol::SelectionModelCurrent);
221 msg << QItemSelectionModel::Current << Protocol::fromQModelIndex(current);
222 Endpoint::send(msg);
223 }
224
select(const QItemSelection & selection,QItemSelectionModel::SelectionFlags command)225 void NetworkSelectionModel::select(const QItemSelection &selection,
226 QItemSelectionModel::SelectionFlags command)
227 {
228 QItemSelectionModel::select(selection, command);
229
230 if (m_handlingRemoteMessage || !isConnected())
231 return;
232 clearPendingSelection();
233
234 Message msg(m_myAddress, Protocol::SelectionModelSelect);
235 writeSelection(&msg, selection);
236 msg << command;
237 Endpoint::send(msg);
238 }
239
applyPendingSelection()240 void GammaRay::NetworkSelectionModel::applyPendingSelection()
241 {
242 if (m_pendingSelection.isEmpty() && m_pendingCommand == NoUpdate)
243 return;
244
245 QItemSelection qmiSelection;
246 if (translateSelection(m_pendingSelection, qmiSelection)) {
247 if (!qmiSelection.isEmpty())
248 select(qmiSelection, m_pendingCommand);
249 clearPendingSelection();
250 }
251 }
252
clearPendingSelection()253 void GammaRay::NetworkSelectionModel::clearPendingSelection()
254 {
255 m_pendingSelection.clear();
256 m_pendingCommand = NoUpdate;
257 }
258
isConnected() const259 bool NetworkSelectionModel::isConnected() const
260 {
261 return Endpoint::isConnected() && m_myAddress != Protocol::InvalidObjectAddress;
262 }
263