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 &current,
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