#include "./checklistmodel.h" #include /*! \namespace Models \brief Provides common models. */ namespace QtUtilities { /*! * \class Models::ChecklistItem * \brief The ChecklistItem class provides an item for use with the * ChecklistModel class. */ /*! * \class Models::ChecklistModel * \brief The ChecklistModel class provides a generic model for storing * checkable items. */ /*! * \brief Constructs a new checklist model. */ ChecklistModel::ChecklistModel(QObject *parent) : QAbstractListModel(parent) { } int ChecklistModel::rowCount(const QModelIndex &parent) const { if (!parent.isValid()) { return static_cast(m_items.size()); } return 0; } Qt::ItemFlags ChecklistModel::flags(const QModelIndex &index) const { if (!index.isValid() || index.row() >= m_items.count() || index.model() != this) { return Qt::ItemIsDropEnabled; // allows drops outside the items } return QAbstractListModel::flags(index) | Qt::ItemIsUserCheckable | Qt::ItemIsDragEnabled; } QVariant ChecklistModel::data(const QModelIndex &index, int role) const { if (index.isValid() && index.row() < m_items.size()) { switch (role) { case Qt::DisplayRole: return m_items.at(index.row()).label(); case Qt::CheckStateRole: return m_items.at(index.row()).checkState(); case idRole(): return m_items.at(index.row()).id(); default:; } } return QVariant(); } QMap ChecklistModel::itemData(const QModelIndex &index) const { QMap roles; roles.insert(Qt::DisplayRole, data(index, Qt::DisplayRole)); roles.insert(Qt::CheckStateRole, data(index, Qt::CheckStateRole)); roles.insert(idRole(), data(index, idRole())); return roles; } bool ChecklistModel::setData(const QModelIndex &index, const QVariant &value, int role) { bool success = false; QVector roles{ role }; if (index.isValid() && index.row() < m_items.size()) { switch (role) { case Qt::DisplayRole: m_items[index.row()].m_label = value.toString(); success = true; break; case Qt::CheckStateRole: if (value.canConvert( #if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0)) QMetaType::Int #else QMetaType::fromType() #endif )) { m_items[index.row()].m_checkState = static_cast(value.toInt()); success = true; } break; case idRole(): { m_items[index.row()].m_id = value; success = true; auto label = labelForId(value); if (!label.isEmpty()) { m_items[index.row()].m_label = std::move(label); roles << Qt::DisplayRole; } break; } default:; } } if (success) { emit dataChanged(index, index, roles); } return success; } bool ChecklistModel::setItemData(const QModelIndex &index, const QMap &roles) { for (QMap::ConstIterator it = roles.constBegin(); it != roles.constEnd(); ++it) { setData(index, it.value(), it.key()); } return true; } /*! * \brief Sets the checked state of the specified item. */ bool ChecklistModel::setChecked(int row, Qt::CheckState checked) { if (row < 0 || row >= m_items.size()) { return false; } m_items[row].m_checkState = checked ? Qt::Checked : Qt::Unchecked; const auto index(this->index(row)); emit dataChanged(index, index, QVector{ Qt::CheckStateRole }); return true; } /*! * \brief Returns the label for the specified \a id. * * This method might be reimplemented when subclassing to provide labels * for the item IDs. * * If an item's ID is set (using setData() with idRole() or setItems()) this method * is called to update or initialize the item's label as well. If this method returns * an empty string (default behaviour) the item's label will not be updated. * * This is useful when items are moved by the view (eg. for Drag & Drop) and to * initialize the ChecklistItem labels more conveniently. */ QString ChecklistModel::labelForId(const QVariant &) const { return QString(); } Qt::DropActions ChecklistModel::supportedDropActions() const { return Qt::MoveAction; } bool ChecklistModel::insertRows(int row, int count, const QModelIndex &parent) { if (count < 1 || row < 0 || row > rowCount() || parent.isValid()) { return false; } beginInsertRows(QModelIndex(), row, row + count - 1); for (int index = row, end = row + count; index < end; ++index) { m_items.insert(index, ChecklistItem()); } endInsertRows(); return true; } bool ChecklistModel::removeRows(int row, int count, const QModelIndex &parent) { if (count < 1 || row < 0 || (row + count) > rowCount() || parent.isValid()) { return false; } beginRemoveRows(QModelIndex(), row, row + count - 1); for (int index = row, end = row + count; index < end; ++index) { m_items.removeAt(index); } endRemoveRows(); return true; } /*! * \brief Sets the items. Resets the model. */ void ChecklistModel::setItems(const QList &items) { beginResetModel(); m_items = items; for (auto &item : m_items) { if (item.m_label.isEmpty()) { item.m_label = labelForId(item.id()); } } endResetModel(); } /*! * \brief Restores the IDs and checkstates read from the specified \a settings * object. * * The items will be read from the array with the specified \a name. * * Resets the model (current items are cleared). * * Does not restore any labels. Labels are meant to be restored from the ID. */ void ChecklistModel::restore(QSettings &settings, const QString &name) { beginResetModel(); auto currentItems = m_items; QList restoredIds; m_items.clear(); int rows = settings.beginReadArray(name); m_items.reserve(rows); for (int i = 0; i < rows; ++i) { settings.setArrayIndex(i); const auto id = settings.value(QStringLiteral("id")); const auto isIdValid = [&] { for (const auto &item : currentItems) { if (item.id() == id) { return true; } } return false; }(); if (!isIdValid) { continue; } const auto selected = settings.value(QStringLiteral("selected")); if (!id.isNull() && !selected.isNull() && selected.canConvert( #if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0)) QMetaType::Bool #else QMetaType::fromType() #endif ) && !restoredIds.contains(id)) { m_items << ChecklistItem(id, labelForId(id), selected.toBool() ? Qt::Checked : Qt::Unchecked); restoredIds << id; } } settings.endArray(); for (const ChecklistItem &item : currentItems) { if (!restoredIds.contains(item.id())) { m_items << item; } } endResetModel(); } /*! * \brief Saves the IDs and checkstates to the specified \a settings object. * * The items will be stored using an array with the specified \a name. * * Does not save any labels. */ void ChecklistModel::save(QSettings &settings, const QString &name) const { settings.beginWriteArray(name, static_cast(m_items.size())); int index = 0; for (const ChecklistItem &item : m_items) { settings.setArrayIndex(index); settings.setValue(QStringLiteral("id"), item.id()); settings.setValue(QStringLiteral("selected"), item.isChecked()); ++index; } settings.endArray(); } /*! * \brief Returns the checked IDs. */ QVariantList ChecklistModel::toVariantList() const { QVariantList checkedIds; checkedIds.reserve(m_items.size()); for (const auto &item : m_items) { if (item.isChecked()) { checkedIds << item.id(); } } return checkedIds; } /*! * \brief Checks all items contained by \a checkedIds and unchecks other items. */ void ChecklistModel::applyVariantList(const QVariantList &checkedIds) { for (auto &item : m_items) { item.m_checkState = checkedIds.contains(item.id()) ? Qt::Checked : Qt::Unchecked; } emit dataChanged(index(0), index(static_cast(m_items.size())), { Qt::CheckStateRole }); } } // namespace QtUtilities