File indexing completed on 2024-05-19 04:56:28

0001 /**
0002  * \file checkablelistmodel.h
0003  * Proxy model to use QAbstractItemModel with QML.
0004  *
0005  * \b Project: Kid3
0006  * \author Urs Fleisch
0007  * \date 23 Sep 2014
0008  *
0009  * Copyright (C) 2014-2018  Urs Fleisch
0010  *
0011  * This file is part of Kid3.
0012  *
0013  * Kid3 is free software; you can redistribute it and/or modify
0014  * it under the terms of the GNU General Public License as published by
0015  * the Free Software Foundation; either version 2 of the License, or
0016  * (at your option) any later version.
0017  *
0018  * Kid3 is distributed in the hope that it will be useful,
0019  * but WITHOUT ANY WARRANTY; without even the implied warranty of
0020  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
0021  * GNU General Public License for more details.
0022  *
0023  * You should have received a copy of the GNU General Public License
0024  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
0025  */
0026 
0027 #include "checkablelistmodel.h"
0028 #include <QItemSelectionModel>
0029 
0030 CheckableListModel::CheckableListModel(QObject* parent)
0031   : QAbstractProxyModel(parent), m_selModel(nullptr)
0032 {
0033 }
0034 
0035 QItemSelectionModel* CheckableListModel::selectionModel() const
0036 {
0037   return m_selModel;
0038 }
0039 
0040 void CheckableListModel::setSelectionModel(QItemSelectionModel* selModel)
0041 {
0042   if (m_selModel != selModel) {
0043     if (m_selModel) {
0044       disconnect(m_selModel, nullptr, this, nullptr);
0045     }
0046     m_selModel = selModel;
0047     if (m_selModel) {
0048       connect(m_selModel,
0049               &QItemSelectionModel::selectionChanged,
0050               this, &CheckableListModel::onSelectionChanged);
0051       connect(m_selModel, &QItemSelectionModel::currentChanged,
0052               this, &CheckableListModel::onCurrentChanged);
0053     }
0054     emit selectionModelChanged();
0055   }
0056 }
0057 
0058 void CheckableListModel::setSelectionModelObject(QObject *obj)
0059 {
0060   if (auto selModel = qobject_cast<QItemSelectionModel*>(obj)) {
0061     setSelectionModel(selModel);
0062   }
0063 }
0064 
0065 QModelIndex CheckableListModel::rootIndex() const
0066 {
0067   return m_rootIndex;
0068 }
0069 
0070 void CheckableListModel::setRootIndex(const QModelIndex& rootIndex)
0071 {
0072   if (m_rootIndex != rootIndex) {
0073     beginResetModel();
0074     m_rootIndex = rootIndex;
0075     endResetModel();
0076     emit rootIndexChanged();
0077   }
0078 }
0079 
0080 QModelIndex CheckableListModel::modelIndex(int row) const
0081 {
0082   QAbstractItemModel* srcModel = sourceModel();
0083   return srcModel ? srcModel->index(row, 0, m_rootIndex) : QModelIndex();
0084 }
0085 
0086 QModelIndex CheckableListModel::parentModelIndex() const
0087 {
0088   return m_rootIndex.parent();
0089 }
0090 
0091 bool CheckableListModel::setDataValue(int row, const QByteArray& roleName,
0092                                       const QVariant& value)
0093 {
0094   QHash<int,QByteArray> roleHash = roleNames();
0095   for (auto it = roleHash.constBegin(); it != roleHash.constEnd(); ++it) {
0096     if (it.value() == roleName) {
0097       return setData(index(row, 0), value, it.key());
0098     }
0099   }
0100   return false;
0101 }
0102 
0103 QVariant CheckableListModel::getDataValue(int row,
0104                                           const QByteArray& roleName) const
0105 {
0106   QHash<int,QByteArray> roleHash = roleNames();
0107   for (auto it = roleHash.constBegin(); it != roleHash.constEnd(); ++it) {
0108     if (it.value() == roleName) {
0109       return data(index(row, 0), it.key());
0110     }
0111   }
0112   return QVariant();
0113 }
0114 
0115 bool CheckableListModel::hasModelChildren(int row) const
0116 {
0117   QAbstractItemModel* srcModel = sourceModel();
0118   return srcModel && srcModel->hasChildren(mapToSource(index(row, 0)));
0119 }
0120 
0121 int CheckableListModel::currentRow() const
0122 {
0123   return m_selModel ? mapFromSource(m_selModel->currentIndex()).row() : -1;
0124 }
0125 
0126 void CheckableListModel::setCurrentRow(int row)
0127 {
0128   if (m_selModel) {
0129     m_selModel->setCurrentIndex(
0130           mapToSource(index(row, 0)),
0131           QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows);
0132   }
0133 }
0134 
0135 Qt::ItemFlags CheckableListModel::flags(const QModelIndex& index) const
0136 {
0137   Qt::ItemFlags itemFlags = QAbstractProxyModel::flags(index);
0138   if (index.isValid() && index.column() == 0 && m_selModel) {
0139     itemFlags |= Qt::ItemIsUserCheckable;
0140   }
0141   return itemFlags;
0142 }
0143 
0144 QVariant CheckableListModel::data(const QModelIndex& index, int role) const
0145 {
0146   if (role == Qt::CheckStateRole) {
0147     if (index.column() != 0)
0148       return QVariant();
0149     if (!m_selModel)
0150       return Qt::Unchecked;
0151     return m_selModel->selection().contains(mapToSource(index))
0152         ? Qt::Checked : Qt::Unchecked;
0153   }
0154   return QAbstractProxyModel::data(index, role);
0155 }
0156 
0157 bool CheckableListModel::setData(const QModelIndex& index,
0158                                  const QVariant& value, int role)
0159 {
0160   if (role == Qt::CheckStateRole) {
0161     if (index.column() != 0)
0162       return false;
0163     if (!m_selModel)
0164       return false;
0165 
0166     auto state = static_cast<Qt::CheckState>(value.toInt());
0167     const QModelIndex srcIndex = mapToSource(index);
0168     m_selModel->setCurrentIndex(srcIndex, state == Qt::Checked
0169       ? QItemSelectionModel::Select | QItemSelectionModel::Rows
0170       : QItemSelectionModel::Deselect | QItemSelectionModel::Rows);
0171     emit dataChanged(index, index);
0172     return true;
0173   }
0174   return QAbstractProxyModel::setData(index, value, role);
0175 }
0176 
0177 void CheckableListModel::setSourceModel(QAbstractItemModel* srcModel)
0178 {
0179   if (sourceModel() != srcModel) {
0180     QAbstractProxyModel::setSourceModel(srcModel);
0181     emit sourceModelChanged();
0182 
0183     if (sourceModel()) {
0184       disconnect(sourceModel(), nullptr, this, nullptr);
0185     }
0186     QAbstractProxyModel::setSourceModel(srcModel);
0187     if (srcModel) {
0188       connect(srcModel, &QAbstractItemModel::modelAboutToBeReset,
0189               this, &CheckableListModel::onModelAboutToBeReset);
0190       connect(srcModel, &QAbstractItemModel::modelReset,
0191               this, &CheckableListModel::onModelReset);
0192       connect(srcModel, &QAbstractItemModel::layoutAboutToBeChanged,
0193               this, &QAbstractItemModel::layoutAboutToBeChanged);
0194       connect(srcModel, &QAbstractItemModel::layoutChanged,
0195               this, &QAbstractItemModel::layoutChanged);
0196       connect(srcModel, &QAbstractItemModel::dataChanged,
0197               this, &CheckableListModel::onDataChanged);
0198       connect(srcModel, &QAbstractItemModel::rowsAboutToBeRemoved,
0199               this, &CheckableListModel::onRowsAboutToBeRemoved);
0200       connect(srcModel, &QAbstractItemModel::rowsRemoved,
0201               this, &CheckableListModel::onRowsRemoved);
0202       connect(srcModel, &QAbstractItemModel::rowsAboutToBeInserted,
0203               this, &CheckableListModel::onRowsAboutToBeInserted);
0204       connect(srcModel, &QAbstractItemModel::rowsInserted,
0205               this, &CheckableListModel::onRowsInserted);
0206     }
0207   }
0208 }
0209 
0210 void CheckableListModel::setSourceModelObject(QObject* obj)
0211 {
0212   if (auto srcModel = qobject_cast<QAbstractItemModel*>(obj)) {
0213     setSourceModel(srcModel);
0214   }
0215 }
0216 
0217 void CheckableListModel::onModelAboutToBeReset()
0218 {
0219   beginResetModel();
0220 }
0221 
0222 void CheckableListModel::onModelReset()
0223 {
0224   endResetModel();
0225 }
0226 
0227 void CheckableListModel::onDataChanged(const QModelIndex& topLeft,
0228                                            const QModelIndex& bottomRight)
0229 {
0230   QModelIndex first = mapFromSource(topLeft);
0231   QModelIndex last = mapFromSource(bottomRight);
0232   if (first.isValid() && last.isValid() &&
0233       first.parent() == last.parent() && first.column() == last.column()) {
0234     emit dataChanged(first, last);
0235   }
0236 }
0237 
0238 void CheckableListModel::onRowsAboutToBeRemoved(const QModelIndex& parent,
0239                                                     int first, int last)
0240 {
0241   if (parent == m_rootIndex) {
0242     beginRemoveRows(mapFromSource(parent), first, last);
0243   }
0244 }
0245 
0246 void CheckableListModel::onRowsRemoved(const QModelIndex &parent,
0247                                            int first, int last)
0248 {
0249   Q_UNUSED(first);
0250   Q_UNUSED(last);
0251   if (parent == m_rootIndex) {
0252     endRemoveRows();
0253   }
0254 }
0255 
0256 void CheckableListModel::onRowsAboutToBeInserted(const QModelIndex& parent,
0257                                                      int first, int last)
0258 {
0259   if (parent == m_rootIndex) {
0260     beginInsertRows(mapFromSource(parent), first, last);
0261   }
0262 }
0263 
0264 void CheckableListModel::onRowsInserted(const QModelIndex& parent,
0265                                             int first, int last)
0266 {
0267   Q_UNUSED(first);
0268   Q_UNUSED(last);
0269   if (parent == m_rootIndex) {
0270     endInsertRows();
0271   }
0272 }
0273 
0274 void CheckableListModel::onSelectionChanged(const QItemSelection& selected,
0275                                           const QItemSelection& deselected)
0276 {
0277   const auto selectedRanges = mapSelectionFromSource(selected);
0278   for (const QItemSelectionRange& range : selectedRanges)
0279     emit dataChanged(range.topLeft(), range.bottomRight());
0280   const auto deselectedRanges = mapSelectionFromSource(deselected);
0281   for (const QItemSelectionRange& range : deselectedRanges)
0282     emit dataChanged(range.topLeft(), range.bottomRight());
0283 }
0284 
0285 void CheckableListModel::onCurrentChanged(const QModelIndex& current,
0286                                           const QModelIndex& previous)
0287 {
0288   QModelIndex idx = mapFromSource(current);
0289   emit currentRowChanged(idx.row());
0290   emit dataChanged(idx, idx);
0291   idx = mapFromSource(previous);
0292   emit dataChanged(idx, idx);
0293 }
0294 
0295 QModelIndex CheckableListModel::index(int row, int column,
0296                                       const QModelIndex& parent) const
0297 {
0298   return parent.isValid() ? QModelIndex() : createIndex(row, column);
0299 }
0300 
0301 QModelIndex CheckableListModel::parent(const QModelIndex&) const
0302 {
0303   return QModelIndex();
0304 }
0305 
0306 int CheckableListModel::rowCount(const QModelIndex& parent) const
0307 {
0308   QAbstractItemModel* srcModel = sourceModel();
0309   return !parent.isValid() && srcModel ? srcModel->rowCount(m_rootIndex) : 0;
0310 }
0311 
0312 int CheckableListModel::columnCount(const QModelIndex& parent) const
0313 {
0314   QAbstractItemModel* srcModel = sourceModel();
0315   return !parent.isValid() && srcModel ?srcModel->columnCount(m_rootIndex) : 0;
0316 }
0317 
0318 QModelIndex CheckableListModel::mapToSource(const QModelIndex& proxyIndex) const
0319 {
0320   QAbstractItemModel* srcModel = sourceModel();
0321   return proxyIndex.isValid() && srcModel
0322       ? srcModel->index(proxyIndex.row(), proxyIndex.column(), m_rootIndex)
0323       : QModelIndex();
0324 }
0325 
0326 QModelIndex CheckableListModel::mapFromSource(const QModelIndex& srcIndex) const
0327 {
0328   return srcIndex.parent() == m_rootIndex
0329       ? createIndex(srcIndex.row(), srcIndex.column()) : QModelIndex();
0330 }