// For license of this file, see /LICENSE.md. #include "services/abstract/accountcheckmodel.h" #include "3rd-party/boolinq/boolinq.h" #include "definitions/definitions.h" #include "miscellaneous/application.h" #include "miscellaneous/iconfactory.h" AccountCheckModel::AccountCheckModel(QObject* parent) : QAbstractItemModel(parent), m_rootItem(nullptr), m_recursiveChange(false) {} RootItem* AccountCheckModel::itemForIndex(const QModelIndex& index) const { if (index.isValid() && index.model() == this) { return static_cast(index.internalPointer()); } else { return m_rootItem; } } RootItem* AccountCheckModel::rootItem() const { return m_rootItem; } void AccountCheckModel::setRootItem(RootItem* root_item, bool delete_previous_root, bool with_layout_change) { if (with_layout_change) { emit layoutAboutToBeChanged(); } if (delete_previous_root && m_rootItem != nullptr) { m_rootItem->deleteLater(); } m_checkStates.clear(); m_rootItem = root_item; if (with_layout_change) { emit layoutChanged(); } } void AccountCheckModel::checkAllItems() { if (m_rootItem != nullptr) { auto chi = m_rootItem->childItems(); for (RootItem* root_child : qAsConst(chi)) { if (root_child->kind() == RootItem::Kind::Feed || root_child->kind() == RootItem::Kind::Category) { setItemChecked(root_child, Qt::CheckState::Checked); } } } } void AccountCheckModel::uncheckAllItems() { if (m_rootItem != nullptr) { auto chi = m_rootItem->childItems(); for (RootItem* root_child : qAsConst(chi)) { if (root_child->kind() == RootItem::Kind::Feed || root_child->kind() == RootItem::Kind::Category) { setData(indexForItem(root_child), Qt::CheckState::Unchecked, Qt::ItemDataRole::CheckStateRole); } } } } QModelIndex AccountCheckModel::index(int row, int column, const QModelIndex& parent) const { if (!hasIndex(row, column, parent)) { return QModelIndex(); } RootItem* parent_item = itemForIndex(parent); RootItem* child_item = parent_item->child(row); if (child_item != nullptr) { return createIndex(row, column, child_item); } else { return QModelIndex(); } } QModelIndex AccountCheckModel::indexForItem(RootItem* item) const { if (item == nullptr || item->kind() == RootItem::Kind::ServiceRoot || item->kind() == RootItem::Kind::Root) { // Root item lies on invalid index. return QModelIndex(); } QList parents; // Start with root item (which obviously has invalid index). parents << indexForItem(m_rootItem); while (!parents.isEmpty()) { QModelIndex active_index = parents.takeFirst(); int row_count = rowCount(active_index); if (row_count > 0) { // This index has children. // Lets take a look if our target item is among them. RootItem* active_item = itemForIndex(active_index); int candidate_index = active_item->childItems().indexOf(item); if (candidate_index >= 0) { // We found our item. return index(candidate_index, 0, active_index); } else { // Item is not found, add all "categories" from active_item. for (int i = 0; i < row_count; i++) { RootItem* possible_category = active_item->child(i); if (possible_category->kind() == RootItem::Kind::Category) { parents << index(i, 0, active_index); } } } } } return QModelIndex(); } QModelIndex AccountCheckModel::parent(const QModelIndex& child) const { if (!child.isValid()) { return QModelIndex(); } RootItem* child_item = itemForIndex(child); RootItem* parent_item = child_item->parent(); if (parent_item == m_rootItem) { return QModelIndex(); } else { return createIndex(parent_item->row(), 0, parent_item); } } int AccountCheckModel::rowCount(const QModelIndex& parent) const { if (parent.column() > 0) { return 0; } else { RootItem* item = itemForIndex(parent); if (item != nullptr) { return item->childCount(); } else { return 0; } } } int AccountCheckModel::columnCount(const QModelIndex& parent) const { Q_UNUSED(parent) return 1; } QVariant AccountCheckModel::data(const QModelIndex& index, int role) const { if (index.column() != 0) { return QVariant(); } RootItem* item = itemForIndex(index); if (role == Qt::ItemDataRole::CheckStateRole) { if (m_checkStates.contains(item)) { return m_checkStates.value(item); } else { return static_cast(Qt::Unchecked); } } else if (role == Qt::ItemDataRole::DecorationRole) { return item->data(0, Qt::ItemDataRole::DecorationRole); } else if (role == Qt::ItemDataRole::EditRole) { return QVariant::fromValue(item); } else if (role == Qt::ItemDataRole::DisplayRole) { switch (item->kind()) { case RootItem::Kind::Category: return QVariant(item->data(index.column(), role).toString() + QSL(" ") + tr("(category)")); case RootItem::Kind::Feed: return QVariant(item->data(index.column(), role).toString() + QSL(" ") + tr("(feed)")); default: return item->title(); } } else { return QVariant(); } } bool AccountCheckModel::setData(const QModelIndex& index, const QVariant& value, int role) { if (index.isValid() && index.column() == 0 && role == Qt::CheckStateRole) { RootItem* item = itemForIndex(index); if (item == m_rootItem) { // Cannot set data on root item. return false; } // Change data for the actual item. m_checkStates[item] = static_cast(value.toInt()); emit dataChanged(index, index); emit checkStateChanged(item, m_checkStates[item]); if (m_recursiveChange) { return true; } // Set new data for all descendants of this actual item. auto chi = item->childItems(); for (RootItem* child : qAsConst(chi)) { setData(indexForItem(child), value, Qt::CheckStateRole); } // Now we need to change new data to all parents. QModelIndex parent_index = index; m_recursiveChange = true; // Iterate all valid parents. while ((parent_index = parent_index.parent()).isValid()) { // We now have parent index. Get parent item too. item = item->parent(); // Check children of this new parent item. bool all_checked = true; bool all_unchecked = true; auto childr = item->childItems(); for (RootItem* child_of_parent : qAsConst(childr)) { if (m_checkStates.contains(child_of_parent)) { all_checked &= m_checkStates[child_of_parent] == Qt::CheckState::Checked; all_unchecked &= m_checkStates[child_of_parent] == Qt::CheckState::Unchecked; } else { all_checked = false; } } if (all_checked) { setData(parent_index, Qt::CheckState::Checked, Qt::CheckStateRole); } else if (all_unchecked) { setData(parent_index, Qt::CheckState::Unchecked, Qt::CheckStateRole); } else { setData(parent_index, Qt::CheckState::PartiallyChecked, Qt::CheckStateRole); } } m_recursiveChange = false; return true; } return false; } Qt::ItemFlags AccountCheckModel::flags(const QModelIndex& index) const { if (!index.isValid() || (itemForIndex(index)->kind() != RootItem::Kind::Category && itemForIndex(index)->kind() != RootItem::Kind::Feed)) { return Qt::ItemFlag::NoItemFlags; } Qt::ItemFlags flags = Qt::ItemFlag::ItemIsEnabled | Qt::ItemFlag::ItemIsSelectable; if (index.column() == 0) { flags |= Qt::ItemFlag::ItemIsUserCheckable; } return flags; } QList AccountCheckModel::checkedItems() const { auto keys = m_checkStates.keys(); auto res = boolinq::from(keys) .where([&](const auto& key) { return m_checkStates.value(key) == Qt::CheckState::Checked; }) .toStdList(); return FROM_STD_LIST(QList, res); } bool AccountCheckModel::isItemChecked(RootItem* item) const { return m_checkStates.value(item, Qt::CheckState::Unchecked) != Qt::CheckState::Unchecked; } bool AccountCheckModel::setItemChecked(RootItem* item, Qt::CheckState check) { return setData(indexForItem(item), check, Qt::ItemDataRole::CheckStateRole); } AccountCheckSortedModel::AccountCheckSortedModel(QObject* parent) : QSortFilterProxyModel(parent), m_sourceModel(new AccountCheckModel(parent)) { setDynamicSortFilter(false); setSourceModel(m_sourceModel); sort(0, Qt::SortOrder::AscendingOrder); } bool AccountCheckSortedModel::lessThan(const QModelIndex& source_left, const QModelIndex& source_right) const { auto* lhs = m_sourceModel->itemForIndex(source_left); auto* rhs = m_sourceModel->itemForIndex(source_right); if (lhs != nullptr && rhs != nullptr) { QList priorities = {RootItem::Kind::Category, RootItem::Kind::Feed, RootItem::Kind::Labels, RootItem::Kind::Important, RootItem::Kind::Unread, RootItem::Kind::Bin}; if (lhs->keepOnTop()) { return sortOrder() == Qt::SortOrder::AscendingOrder; } else if (rhs->keepOnTop()) { return sortOrder() == Qt::SortOrder::DescendingOrder; } auto left_priority = priorities.indexOf(lhs->kind()); auto right_priority = priorities.indexOf(rhs->kind()); if (left_priority == right_priority) { return QString::localeAwareCompare(lhs->title().toLower(), rhs->title().toLower()) < 0; } else { return sortOrder() == Qt::SortOrder::AscendingOrder ? left_priority < right_priority : right_priority < left_priority; } } return false; } AccountCheckModel* AccountCheckSortedModel::sourceModel() const { return m_sourceModel; } void AccountCheckSortedModel::setRootItem(RootItem* root_item, bool delete_previous_root, bool with_layout_change) { setSourceModel(nullptr); m_sourceModel->setRootItem(root_item, delete_previous_root, with_layout_change); setSourceModel(m_sourceModel); } void AccountCheckSortedModel::checkAllItems() { m_sourceModel->checkAllItems(); } void AccountCheckSortedModel::uncheckAllItems() { m_sourceModel->uncheckAllItems(); } bool AccountCheckSortedModel::filterAcceptsRow(int source_row, const QModelIndex& source_parent) const { auto kind = m_sourceModel->itemForIndex(m_sourceModel->index(source_row, 0, source_parent))->kind(); return kind == RootItem::Kind::Root || kind == RootItem::Kind::ServiceRoot || kind == RootItem::Kind::Category || kind == RootItem::Kind::Feed; }