1 /**
2 * \file checkablelistmodel.h
3 * Proxy model to use QAbstractItemModel with QML.
4 *
5 * \b Project: Kid3
6 * \author Urs Fleisch
7 * \date 23 Sep 2014
8 *
9 * Copyright (C) 2014-2018 Urs Fleisch
10 *
11 * This file is part of Kid3.
12 *
13 * Kid3 is free software; you can redistribute it and/or modify
14 * it under the terms of the GNU General Public License as published by
15 * the Free Software Foundation; either version 2 of the License, or
16 * (at your option) any later version.
17 *
18 * Kid3 is distributed in the hope that it will be useful,
19 * but WITHOUT ANY WARRANTY; without even the implied warranty of
20 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 * GNU General Public License for more details.
22 *
23 * You should have received a copy of the GNU General Public License
24 * along with this program. If not, see <http://www.gnu.org/licenses/>.
25 */
26
27 #include "checkablelistmodel.h"
28 #include <QItemSelectionModel>
29
CheckableListModel(QObject * parent)30 CheckableListModel::CheckableListModel(QObject* parent)
31 : QAbstractProxyModel(parent), m_selModel(nullptr)
32 {
33 }
34
selectionModel() const35 QItemSelectionModel* CheckableListModel::selectionModel() const
36 {
37 return m_selModel;
38 }
39
setSelectionModel(QItemSelectionModel * selModel)40 void CheckableListModel::setSelectionModel(QItemSelectionModel* selModel)
41 {
42 if (m_selModel != selModel) {
43 if (m_selModel) {
44 disconnect(m_selModel, nullptr, this, nullptr);
45 }
46 m_selModel = selModel;
47 if (m_selModel) {
48 connect(m_selModel,
49 &QItemSelectionModel::selectionChanged,
50 this, &CheckableListModel::onSelectionChanged);
51 connect(m_selModel, &QItemSelectionModel::currentChanged,
52 this, &CheckableListModel::onCurrentChanged);
53 }
54 emit selectionModelChanged();
55 }
56 }
57
setSelectionModelObject(QObject * obj)58 void CheckableListModel::setSelectionModelObject(QObject *obj)
59 {
60 if (auto selModel = qobject_cast<QItemSelectionModel*>(obj)) {
61 setSelectionModel(selModel);
62 }
63 }
64
rootIndex() const65 QModelIndex CheckableListModel::rootIndex() const
66 {
67 return m_rootIndex;
68 }
69
setRootIndex(const QModelIndex & rootIndex)70 void CheckableListModel::setRootIndex(const QModelIndex& rootIndex)
71 {
72 if (m_rootIndex != rootIndex) {
73 beginResetModel();
74 m_rootIndex = rootIndex;
75 endResetModel();
76 emit rootIndexChanged();
77 }
78 }
79
modelIndex(int row) const80 QModelIndex CheckableListModel::modelIndex(int row) const
81 {
82 QAbstractItemModel* srcModel = sourceModel();
83 return srcModel ? srcModel->index(row, 0, m_rootIndex) : QModelIndex();
84 }
85
parentModelIndex() const86 QModelIndex CheckableListModel::parentModelIndex() const
87 {
88 return m_rootIndex.parent();
89 }
90
setDataValue(int row,const QByteArray & roleName,const QVariant & value)91 bool CheckableListModel::setDataValue(int row, const QByteArray& roleName,
92 const QVariant& value)
93 {
94 QHash<int,QByteArray> roleHash = roleNames();
95 for (auto it = roleHash.constBegin(); it != roleHash.constEnd(); ++it) {
96 if (it.value() == roleName) {
97 return setData(index(row, 0), value, it.key());
98 }
99 }
100 return false;
101 }
102
getDataValue(int row,const QByteArray & roleName) const103 QVariant CheckableListModel::getDataValue(int row,
104 const QByteArray& roleName) const
105 {
106 QHash<int,QByteArray> roleHash = roleNames();
107 for (auto it = roleHash.constBegin(); it != roleHash.constEnd(); ++it) {
108 if (it.value() == roleName) {
109 return data(index(row, 0), it.key());
110 }
111 }
112 return QVariant();
113 }
114
hasModelChildren(int row) const115 bool CheckableListModel::hasModelChildren(int row) const
116 {
117 QAbstractItemModel* srcModel = sourceModel();
118 return srcModel && srcModel->hasChildren(mapToSource(index(row, 0)));
119 }
120
currentRow() const121 int CheckableListModel::currentRow() const
122 {
123 return m_selModel ? mapFromSource(m_selModel->currentIndex()).row() : -1;
124 }
125
setCurrentRow(int row)126 void CheckableListModel::setCurrentRow(int row)
127 {
128 if (m_selModel) {
129 m_selModel->setCurrentIndex(
130 mapToSource(index(row, 0)),
131 QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows);
132 }
133 }
134
flags(const QModelIndex & index) const135 Qt::ItemFlags CheckableListModel::flags(const QModelIndex& index) const
136 {
137 Qt::ItemFlags itemFlags = QAbstractProxyModel::flags(index);
138 if (index.isValid() && index.column() == 0 && m_selModel) {
139 itemFlags |= Qt::ItemIsUserCheckable;
140 }
141 return itemFlags;
142 }
143
data(const QModelIndex & index,int role) const144 QVariant CheckableListModel::data(const QModelIndex& index, int role) const
145 {
146 if (role == Qt::CheckStateRole) {
147 if (index.column() != 0)
148 return QVariant();
149 if (!m_selModel)
150 return Qt::Unchecked;
151 return m_selModel->selection().contains(mapToSource(index))
152 ? Qt::Checked : Qt::Unchecked;
153 }
154 return QAbstractProxyModel::data(index, role);
155 }
156
setData(const QModelIndex & index,const QVariant & value,int role)157 bool CheckableListModel::setData(const QModelIndex& index,
158 const QVariant& value, int role)
159 {
160 if (role == Qt::CheckStateRole) {
161 if (index.column() != 0)
162 return false;
163 if (!m_selModel)
164 return false;
165
166 auto state = static_cast<Qt::CheckState>(value.toInt());
167 const QModelIndex srcIndex = mapToSource(index);
168 m_selModel->setCurrentIndex(srcIndex, state == Qt::Checked
169 ? QItemSelectionModel::Select | QItemSelectionModel::Rows
170 : QItemSelectionModel::Deselect | QItemSelectionModel::Rows);
171 emit dataChanged(index, index);
172 return true;
173 }
174 return QAbstractProxyModel::setData(index, value, role);
175 }
176
setSourceModel(QAbstractItemModel * srcModel)177 void CheckableListModel::setSourceModel(QAbstractItemModel* srcModel)
178 {
179 if (sourceModel() != srcModel) {
180 QAbstractProxyModel::setSourceModel(srcModel);
181 emit sourceModelChanged();
182
183 if (sourceModel()) {
184 disconnect(sourceModel(), nullptr, this, nullptr);
185 }
186 QAbstractProxyModel::setSourceModel(srcModel);
187 if (srcModel) {
188 connect(srcModel, &QAbstractItemModel::modelAboutToBeReset,
189 this, &CheckableListModel::onModelAboutToBeReset);
190 connect(srcModel, &QAbstractItemModel::modelReset,
191 this, &CheckableListModel::onModelReset);
192 connect(srcModel, &QAbstractItemModel::layoutAboutToBeChanged,
193 this, &QAbstractItemModel::layoutAboutToBeChanged);
194 connect(srcModel, &QAbstractItemModel::layoutChanged,
195 this, &QAbstractItemModel::layoutChanged);
196 connect(srcModel, &QAbstractItemModel::dataChanged,
197 this, &CheckableListModel::onDataChanged);
198 connect(srcModel, &QAbstractItemModel::rowsAboutToBeRemoved,
199 this, &CheckableListModel::onRowsAboutToBeRemoved);
200 connect(srcModel, &QAbstractItemModel::rowsRemoved,
201 this, &CheckableListModel::onRowsRemoved);
202 connect(srcModel, &QAbstractItemModel::rowsAboutToBeInserted,
203 this, &CheckableListModel::onRowsAboutToBeInserted);
204 connect(srcModel, &QAbstractItemModel::rowsInserted,
205 this, &CheckableListModel::onRowsInserted);
206 }
207 }
208 }
209
setSourceModelObject(QObject * obj)210 void CheckableListModel::setSourceModelObject(QObject* obj)
211 {
212 if (auto srcModel = qobject_cast<QAbstractItemModel*>(obj)) {
213 setSourceModel(srcModel);
214 }
215 }
216
onModelAboutToBeReset()217 void CheckableListModel::onModelAboutToBeReset()
218 {
219 beginResetModel();
220 }
221
onModelReset()222 void CheckableListModel::onModelReset()
223 {
224 endResetModel();
225 }
226
onDataChanged(const QModelIndex & topLeft,const QModelIndex & bottomRight)227 void CheckableListModel::onDataChanged(const QModelIndex& topLeft,
228 const QModelIndex& bottomRight)
229 {
230 QModelIndex first = mapFromSource(topLeft);
231 QModelIndex last = mapFromSource(bottomRight);
232 if (first.isValid() && last.isValid() &&
233 first.parent() == last.parent() && first.column() == last.column()) {
234 emit dataChanged(first, last);
235 }
236 }
237
onRowsAboutToBeRemoved(const QModelIndex & parent,int first,int last)238 void CheckableListModel::onRowsAboutToBeRemoved(const QModelIndex& parent,
239 int first, int last)
240 {
241 if (parent == m_rootIndex) {
242 beginRemoveRows(mapFromSource(parent), first, last);
243 }
244 }
245
onRowsRemoved(const QModelIndex & parent,int first,int last)246 void CheckableListModel::onRowsRemoved(const QModelIndex &parent,
247 int first, int last)
248 {
249 Q_UNUSED(first);
250 Q_UNUSED(last);
251 if (parent == m_rootIndex) {
252 endRemoveRows();
253 }
254 }
255
onRowsAboutToBeInserted(const QModelIndex & parent,int first,int last)256 void CheckableListModel::onRowsAboutToBeInserted(const QModelIndex& parent,
257 int first, int last)
258 {
259 if (parent == m_rootIndex) {
260 beginInsertRows(mapFromSource(parent), first, last);
261 }
262 }
263
onRowsInserted(const QModelIndex & parent,int first,int last)264 void CheckableListModel::onRowsInserted(const QModelIndex& parent,
265 int first, int last)
266 {
267 Q_UNUSED(first);
268 Q_UNUSED(last);
269 if (parent == m_rootIndex) {
270 endInsertRows();
271 }
272 }
273
onSelectionChanged(const QItemSelection & selected,const QItemSelection & deselected)274 void CheckableListModel::onSelectionChanged(const QItemSelection& selected,
275 const QItemSelection& deselected)
276 {
277 const auto selectedRanges = mapSelectionFromSource(selected);
278 for (const QItemSelectionRange& range : selectedRanges)
279 emit dataChanged(range.topLeft(), range.bottomRight());
280 const auto deselectedRanges = mapSelectionFromSource(deselected);
281 for (const QItemSelectionRange& range : deselectedRanges)
282 emit dataChanged(range.topLeft(), range.bottomRight());
283 }
284
onCurrentChanged(const QModelIndex & current,const QModelIndex & previous)285 void CheckableListModel::onCurrentChanged(const QModelIndex& current,
286 const QModelIndex& previous)
287 {
288 QModelIndex idx = mapFromSource(current);
289 emit currentRowChanged(idx.row());
290 emit dataChanged(idx, idx);
291 idx = mapFromSource(previous);
292 emit dataChanged(idx, idx);
293 }
294
index(int row,int column,const QModelIndex & parent) const295 QModelIndex CheckableListModel::index(int row, int column,
296 const QModelIndex& parent) const
297 {
298 return parent.isValid() ? QModelIndex() : createIndex(row, column);
299 }
300
parent(const QModelIndex &) const301 QModelIndex CheckableListModel::parent(const QModelIndex&) const
302 {
303 return QModelIndex();
304 }
305
rowCount(const QModelIndex & parent) const306 int CheckableListModel::rowCount(const QModelIndex& parent) const
307 {
308 QAbstractItemModel* srcModel = sourceModel();
309 return !parent.isValid() && srcModel ? srcModel->rowCount(m_rootIndex) : 0;
310 }
311
columnCount(const QModelIndex & parent) const312 int CheckableListModel::columnCount(const QModelIndex& parent) const
313 {
314 QAbstractItemModel* srcModel = sourceModel();
315 return !parent.isValid() && srcModel ?srcModel->columnCount(m_rootIndex) : 0;
316 }
317
mapToSource(const QModelIndex & proxyIndex) const318 QModelIndex CheckableListModel::mapToSource(const QModelIndex& proxyIndex) const
319 {
320 QAbstractItemModel* srcModel = sourceModel();
321 return proxyIndex.isValid() && srcModel
322 ? srcModel->index(proxyIndex.row(), proxyIndex.column(), m_rootIndex)
323 : QModelIndex();
324 }
325
mapFromSource(const QModelIndex & srcIndex) const326 QModelIndex CheckableListModel::mapFromSource(const QModelIndex& srcIndex) const
327 {
328 return srcIndex.parent() == m_rootIndex
329 ? createIndex(srcIndex.row(), srcIndex.column()) : QModelIndex();
330 }
331