1/****************************************************************************
2**
3** Copyright (C) 2018 Luca Beldi <v.ronin@yahoo.it>
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the QtCore module of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:LGPL$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU Lesser General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU Lesser
19** General Public License version 3 as published by the Free Software
20** Foundation and appearing in the file LICENSE.LGPL3 included in the
21** packaging of this file. Please review the following information to
22** ensure the GNU Lesser General Public License version 3 requirements
23** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24**
25** GNU General Public License Usage
26** Alternatively, this file may be used under the terms of the GNU
27** General Public License version 2.0 or (at your option) the GNU General
28** Public license version 3 or any later version approved by the KDE Free
29** Qt Foundation. The licenses are as published by the Free Software
30** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31** included in the packaging of this file. Please review the following
32** information to ensure the GNU General Public License requirements will
33** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34** https://www.gnu.org/licenses/gpl-3.0.html.
35**
36** $QT_END_LICENSE$
37**
38****************************************************************************/
39
40#include "qtransposeproxymodel.h"
41#include <private/qtransposeproxymodel_p.h>
42#include <QtCore/qlist.h>
43#include <QtCore/qmetaobject.h>
44#include <QtCore/qsize.h>
45
46QT_BEGIN_NAMESPACE
47
48QModelIndex QTransposeProxyModelPrivate::uncheckedMapToSource(const QModelIndex &proxyIndex) const
49{
50 if (!model || !proxyIndex.isValid())
51 return QModelIndex();
52 if (proxyIndex.internalPointer())
53 return model->createIndex(proxyIndex.column(), proxyIndex.row(), proxyIndex.internalPointer());
54 return model->index(proxyIndex.column(), proxyIndex.row());
55}
56
57QModelIndex QTransposeProxyModelPrivate::uncheckedMapFromSource(const QModelIndex &sourceIndex) const
58{
59 if (!model || !sourceIndex.isValid())
60 return QModelIndex();
61 Q_Q(const QTransposeProxyModel);
62 return q->createIndex(sourceIndex.column(), sourceIndex.row(), sourceIndex.internalPointer());
63}
64
65void QTransposeProxyModelPrivate::onLayoutChanged(const QList<QPersistentModelIndex> &parents, QAbstractItemModel::LayoutChangeHint hint)
66{
67 Q_Q(QTransposeProxyModel);
68 QModelIndexList toList;
69 toList.reserve(layoutChangePersistentIndexes.size());
70 for (const QPersistentModelIndex &persistIdx : qAsConst(layoutChangePersistentIndexes))
71 toList << q->mapFromSource(persistIdx);
72 q->changePersistentIndexList(layoutChangeProxyIndexes, toList);
73 layoutChangeProxyIndexes.clear();
74 layoutChangePersistentIndexes.clear();
75 QList<QPersistentModelIndex> proxyParents;
76 proxyParents.reserve(parents.size());
77 for (const QPersistentModelIndex &srcParent : parents)
78 proxyParents << q->mapFromSource(srcParent);
79 QAbstractItemModel::LayoutChangeHint proxyHint = QAbstractItemModel::NoLayoutChangeHint;
80 if (hint == QAbstractItemModel::VerticalSortHint)
81 proxyHint = QAbstractItemModel::HorizontalSortHint;
82 else if (hint == QAbstractItemModel::HorizontalSortHint)
83 proxyHint = QAbstractItemModel::VerticalSortHint;
84 emit q->layoutChanged(proxyParents, proxyHint);
85}
86
87void QTransposeProxyModelPrivate::onLayoutAboutToBeChanged(const QList<QPersistentModelIndex> &parents, QAbstractItemModel::LayoutChangeHint hint)
88{
89 Q_Q(QTransposeProxyModel);
90 const QModelIndexList proxyPersistentIndexes = q->persistentIndexList();
91 layoutChangeProxyIndexes.clear();
92 layoutChangePersistentIndexes.clear();
93 layoutChangeProxyIndexes.reserve(proxyPersistentIndexes.size());
94 layoutChangePersistentIndexes.reserve(proxyPersistentIndexes.size());
95 for (const QModelIndex &proxyPersistentIndex : proxyPersistentIndexes) {
96 layoutChangeProxyIndexes << proxyPersistentIndex;
97 Q_ASSERT(proxyPersistentIndex.isValid());
98 const QPersistentModelIndex srcPersistentIndex = q->mapToSource(proxyPersistentIndex);
99 Q_ASSERT(srcPersistentIndex.isValid());
100 layoutChangePersistentIndexes << srcPersistentIndex;
101 }
102 QList<QPersistentModelIndex> proxyParents;
103 proxyParents.reserve(parents.size());
104 for (auto& srcParent : parents)
105 proxyParents << q->mapFromSource(srcParent);
106 QAbstractItemModel::LayoutChangeHint proxyHint = QAbstractItemModel::NoLayoutChangeHint;
107 if (hint == QAbstractItemModel::VerticalSortHint)
108 proxyHint = QAbstractItemModel::HorizontalSortHint;
109 else if (hint == QAbstractItemModel::HorizontalSortHint)
110 proxyHint = QAbstractItemModel::VerticalSortHint;
111 emit q->layoutAboutToBeChanged(proxyParents, proxyHint);
112}
113
114void QTransposeProxyModelPrivate::onDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight,
115 const QList<int> &roles)
116{
117 Q_Q(QTransposeProxyModel);
118 emit q->dataChanged(q->mapFromSource(topLeft), q->mapFromSource(bottomRight), roles);
119}
120
121void QTransposeProxyModelPrivate::onHeaderDataChanged(Qt::Orientation orientation, int first, int last)
122{
123 Q_Q(QTransposeProxyModel);
124 emit q->headerDataChanged(orientation == Qt::Horizontal ? Qt::Vertical : Qt::Horizontal, first, last);
125}
126
127void QTransposeProxyModelPrivate::onColumnsAboutToBeInserted(const QModelIndex &parent, int first, int last)
128{
129 Q_Q(QTransposeProxyModel);
130 q->beginInsertRows(q->mapFromSource(parent), first, last);
131}
132
133void QTransposeProxyModelPrivate::onColumnsAboutToBeRemoved(const QModelIndex &parent, int first, int last)
134{
135 Q_Q(QTransposeProxyModel);
136 q->beginRemoveRows(q->mapFromSource(parent), first, last);
137}
138
139void QTransposeProxyModelPrivate::onColumnsAboutToBeMoved(const QModelIndex &sourceParent, int sourceStart, int sourceEnd, const QModelIndex &destinationParent, int destinationColumn)
140{
141 Q_Q(QTransposeProxyModel);
142 q->beginMoveRows(q->mapFromSource(sourceParent), sourceStart, sourceEnd, q->mapFromSource(destinationParent), destinationColumn);
143}
144
145void QTransposeProxyModelPrivate::onRowsAboutToBeInserted(const QModelIndex &parent, int first, int last)
146{
147 Q_Q(QTransposeProxyModel);
148 q->beginInsertColumns(q->mapFromSource(parent), first, last);
149}
150
151void QTransposeProxyModelPrivate::onRowsAboutToBeRemoved(const QModelIndex &parent, int first, int last)
152{
153 Q_Q(QTransposeProxyModel);
154 q->beginRemoveColumns(q->mapFromSource(parent), first, last);
155}
156
157void QTransposeProxyModelPrivate::onRowsAboutToBeMoved(const QModelIndex &sourceParent, int sourceStart, int sourceEnd, const QModelIndex &destinationParent, int destinationRow)
158{
159 Q_Q(QTransposeProxyModel);
160 q->beginMoveColumns(q->mapFromSource(sourceParent), sourceStart, sourceEnd, q->mapFromSource(destinationParent), destinationRow);
161}
162
163/*!
164 \since 5.13
165 \class QTransposeProxyModel
166 \brief This proxy transposes the source model.
167
168 This model will make the rows of the source model become columns of the proxy model and vice-versa.
169
170 If the model is a tree, the parents will be transposed as well. For example, if an index in the source model had parent `index(2,0)`, it will have parent `index(0,2)` in the proxy.
171*/
172
173/*!
174 Constructs a new proxy model with the given \a parent.
175*/
176QTransposeProxyModel::QTransposeProxyModel(QObject* parent)
177 : QAbstractProxyModel(*new QTransposeProxyModelPrivate, parent)
178{}
179
180/*!
181 Destructs the proxy model.
182*/
183QTransposeProxyModel::~QTransposeProxyModel() = default;
184
185/*!
186 \internal
187*/
188QTransposeProxyModel::QTransposeProxyModel(QTransposeProxyModelPrivate &dd, QObject *parent)
189 : QAbstractProxyModel(dd, parent)
190{}
191
192/*!
193 \reimp
194*/
195void QTransposeProxyModel::setSourceModel(QAbstractItemModel* newSourceModel)
196{
197 Q_D(QTransposeProxyModel);
198 if (newSourceModel == d->model)
199 return;
200 beginResetModel();
201 if (d->model) {
202 for (const QMetaObject::Connection& discIter : qAsConst(d->sourceConnections))
203 disconnect(discIter);
204 }
205 d->sourceConnections.clear();
206 QAbstractProxyModel::setSourceModel(newSourceModel);
207 if (d->model) {
208 using namespace std::placeholders;
209 d->sourceConnections = QList<QMetaObject::Connection>{
210 connect(d->model, &QAbstractItemModel::modelAboutToBeReset, this, &QTransposeProxyModel::beginResetModel),
211 connect(d->model, &QAbstractItemModel::modelReset, this, &QTransposeProxyModel::endResetModel),
212 connect(d->model, &QAbstractItemModel::dataChanged, this, std::bind(&QTransposeProxyModelPrivate::onDataChanged, d, _1, _2, _3)),
213 connect(d->model, &QAbstractItemModel::headerDataChanged, this, std::bind(&QTransposeProxyModelPrivate::onHeaderDataChanged, d, _1, _2, _3)),
214 connect(d->model, &QAbstractItemModel::columnsAboutToBeInserted, this, std::bind(&QTransposeProxyModelPrivate::onColumnsAboutToBeInserted, d, _1, _2, _3)),
215 connect(d->model, &QAbstractItemModel::columnsAboutToBeMoved, this, std::bind(&QTransposeProxyModelPrivate::onColumnsAboutToBeMoved, d, _1, _2, _3, _4, _5)),
216 connect(d->model, &QAbstractItemModel::columnsAboutToBeRemoved, this, std::bind(&QTransposeProxyModelPrivate::onColumnsAboutToBeRemoved, d, _1, _2, _3)),
217 connect(d->model, &QAbstractItemModel::columnsInserted, this, &QTransposeProxyModel::endInsertRows),
218 connect(d->model, &QAbstractItemModel::columnsRemoved, this, &QTransposeProxyModel::endRemoveRows),
219 connect(d->model, &QAbstractItemModel::columnsMoved, this, &QTransposeProxyModel::endMoveRows),
220 connect(d->model, &QAbstractItemModel::rowsAboutToBeInserted, this, std::bind(&QTransposeProxyModelPrivate::onRowsAboutToBeInserted, d, _1, _2, _3)),
221 connect(d->model, &QAbstractItemModel::rowsAboutToBeMoved, this, std::bind(&QTransposeProxyModelPrivate::onRowsAboutToBeMoved, d, _1, _2, _3, _4, _5)),
222 connect(d->model, &QAbstractItemModel::rowsAboutToBeRemoved, this, std::bind(&QTransposeProxyModelPrivate::onRowsAboutToBeRemoved, d, _1, _2, _3)),
223 connect(d->model, &QAbstractItemModel::rowsInserted, this, &QTransposeProxyModel::endInsertColumns),
224 connect(d->model, &QAbstractItemModel::rowsRemoved, this, &QTransposeProxyModel::endRemoveColumns),
225 connect(d->model, &QAbstractItemModel::rowsMoved, this, &QTransposeProxyModel::endMoveColumns),
226 connect(d->model, &QAbstractItemModel::layoutAboutToBeChanged, this, std::bind(&QTransposeProxyModelPrivate::onLayoutAboutToBeChanged, d, _1, _2)),
227 connect(d->model, &QAbstractItemModel::layoutChanged, this, std::bind(&QTransposeProxyModelPrivate::onLayoutChanged, d, _1, _2))
228 };
229 }
230 endResetModel();
231}
232
233/*!
234 \reimp
235*/
236int QTransposeProxyModel::rowCount(const QModelIndex &parent) const
237{
238 Q_D(const QTransposeProxyModel);
239 if (!d->model)
240 return 0;
241 Q_ASSERT(checkIndex(parent));
242 return d->model->columnCount(mapToSource(parent));
243}
244
245/*!
246 \reimp
247*/
248int QTransposeProxyModel::columnCount(const QModelIndex &parent) const
249{
250 Q_D(const QTransposeProxyModel);
251 if (!d->model)
252 return 0;
253 Q_ASSERT(checkIndex(parent));
254 return d->model->rowCount(mapToSource(parent));
255}
256
257/*!
258 \reimp
259*/
260QVariant QTransposeProxyModel::headerData(int section, Qt::Orientation orientation, int role) const
261{
262 Q_D(const QTransposeProxyModel);
263 if (!d->model)
264 return QVariant();
265 return d->model->headerData(section, orientation == Qt::Horizontal ? Qt::Vertical : Qt::Horizontal, role);
266}
267
268/*!
269 \reimp
270*/
271bool QTransposeProxyModel::setHeaderData(int section, Qt::Orientation orientation, const QVariant &value, int role)
272{
273 Q_D(QTransposeProxyModel);
274 if (!d->model)
275 return false;
276 return d->model->setHeaderData(section, orientation == Qt::Horizontal ? Qt::Vertical : Qt::Horizontal, value, role);
277}
278
279/*!
280 \reimp
281*/
282bool QTransposeProxyModel::setItemData(const QModelIndex &index, const QMap<int, QVariant> &roles)
283{
284 Q_D(QTransposeProxyModel);
285 Q_ASSERT(checkIndex(index));
286 if (!d->model || !index.isValid())
287 return false;
288 return d->model->setItemData(mapToSource(index), roles);
289}
290
291/*!
292 \reimp
293*/
294QSize QTransposeProxyModel::span(const QModelIndex &index) const
295{
296 Q_D(const QTransposeProxyModel);
297 Q_ASSERT(checkIndex(index));
298 if (!d->model || !index.isValid())
299 return QSize();
300 return d->model->span(mapToSource(index)).transposed();
301}
302
303/*!
304 \reimp
305*/
306QMap<int, QVariant> QTransposeProxyModel::itemData(const QModelIndex &index) const
307{
308 Q_D(const QTransposeProxyModel);
309 if (!d->model)
310 return QMap<int, QVariant>();
311 Q_ASSERT(checkIndex(index));
312 return d->model->itemData(mapToSource(index));
313}
314
315/*!
316 \reimp
317*/
318QModelIndex QTransposeProxyModel::mapFromSource(const QModelIndex &sourceIndex) const
319{
320 Q_D(const QTransposeProxyModel);
321 if (!d->model || !sourceIndex.isValid())
322 return QModelIndex();
323 Q_ASSERT(d->model->checkIndex(sourceIndex));
324 return d->uncheckedMapFromSource(sourceIndex);
325}
326
327/*!
328 \reimp
329*/
330QModelIndex QTransposeProxyModel::mapToSource(const QModelIndex &proxyIndex) const
331{
332 Q_D(const QTransposeProxyModel);
333 Q_ASSERT(checkIndex(proxyIndex));
334 if (!d->model || !proxyIndex.isValid())
335 return QModelIndex();
336 return d->uncheckedMapToSource(proxyIndex);
337}
338
339/*!
340 \reimp
341*/
342QModelIndex QTransposeProxyModel::parent(const QModelIndex &index) const
343{
344 Q_D(const QTransposeProxyModel);
345 Q_ASSERT(checkIndex(index, CheckIndexOption::DoNotUseParent));
346 if (!d->model || !index.isValid())
347 return QModelIndex();
348 return d->uncheckedMapFromSource(d->uncheckedMapToSource(index).parent());
349}
350
351/*!
352 \reimp
353*/
354QModelIndex QTransposeProxyModel::index(int row, int column, const QModelIndex &parent) const
355{
356 Q_D(const QTransposeProxyModel);
357 Q_ASSERT(checkIndex(parent));
358 if (!d->model)
359 return QModelIndex();
360 return mapFromSource(d->model->index(column, row, mapToSource(parent)));
361}
362
363/*!
364 \reimp
365*/
366bool QTransposeProxyModel::insertRows(int row, int count, const QModelIndex &parent)
367{
368 Q_D(QTransposeProxyModel);
369 Q_ASSERT(checkIndex(parent));
370 if (!d->model)
371 return false;
372 return d->model->insertColumns(row, count, mapToSource(parent));
373}
374
375/*!
376 \reimp
377*/
378bool QTransposeProxyModel::removeRows(int row, int count, const QModelIndex &parent)
379{
380 Q_D(QTransposeProxyModel);
381 Q_ASSERT(checkIndex(parent));
382 if (!d->model)
383 return false;
384 return d->model->removeColumns(row, count, mapToSource(parent));
385}
386
387/*!
388 \reimp
389*/
390bool QTransposeProxyModel::moveRows(const QModelIndex &sourceParent, int sourceRow, int count, const QModelIndex &destinationParent, int destinationChild)
391{
392 Q_D(QTransposeProxyModel);
393 Q_ASSERT(checkIndex(sourceParent));
394 Q_ASSERT(checkIndex(destinationParent));
395 if (!d->model)
396 return false;
397 return d->model->moveColumns(mapToSource(sourceParent), sourceRow, count, mapToSource(destinationParent), destinationChild);
398}
399
400/*!
401 \reimp
402*/
403bool QTransposeProxyModel::insertColumns(int column, int count, const QModelIndex &parent)
404{
405 Q_D(QTransposeProxyModel);
406 Q_ASSERT(checkIndex(parent));
407 if (!d->model)
408 return false;
409 return d->model->insertRows(column, count, mapToSource(parent));
410}
411
412/*!
413 \reimp
414*/
415bool QTransposeProxyModel::removeColumns(int column, int count, const QModelIndex &parent)
416{
417 Q_D(QTransposeProxyModel);
418 Q_ASSERT(checkIndex(parent));
419 if (!d->model)
420 return false;
421 return d->model->removeRows(column, count, mapToSource(parent));
422}
423
424/*!
425 \reimp
426*/
427bool QTransposeProxyModel::moveColumns(const QModelIndex &sourceParent, int sourceRow, int count, const QModelIndex &destinationParent, int destinationChild)
428{
429 Q_D(QTransposeProxyModel);
430 Q_ASSERT(checkIndex(sourceParent));
431 Q_ASSERT(checkIndex(destinationParent));
432 if (!d->model)
433 return false;
434 return d->model->moveRows(mapToSource(sourceParent), sourceRow, count, mapToSource(destinationParent), destinationChild);
435}
436
437/*!
438 \reimp
439 This method will perform no action. Use a QSortFilterProxyModel on top of this one if you require sorting.
440*/
441void QTransposeProxyModel::sort(int column, Qt::SortOrder order)
442{
443 Q_UNUSED(column);
444 Q_UNUSED(order);
445 return;
446}
447
448QT_END_NAMESPACE
449
450#include "moc_qtransposeproxymodel.cpp"
451