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