/* Copyright (C) 2013 * swift project Community / Contributors * * This file is part of swift project. It is subject to the license terms in the LICENSE file found in the top-level * directory of this distribution and at http://www.swift-project.org/license.html. No part of swift project, * including this file, may be copied, modified, propagated, or distributed except according to the terms * contained in the LICENSE file. */ #include "listmodelbase.h" #include "allmodelcontainers.h" #include "blackmisc/variant.h" #include "blackmisc/blackmiscfreefunctions.h" using namespace BlackMisc; namespace BlackGui { namespace Models { int CListModelBaseNonTemplate::columnCount(const QModelIndex &modelIndex) const { Q_UNUSED(modelIndex); int c = this->m_columns.size(); return c; } QVariant CListModelBaseNonTemplate::headerData(int section, Qt::Orientation orientation, int role) const { if (orientation != Qt::Horizontal) { return QVariant(); } bool handled = (role == Qt::DisplayRole || role == Qt::ToolTipRole || role == Qt::InitialSortOrderRole); if (!handled) {return QVariant();} if (section < 0 || section >= this->m_columns.size()) { return QVariant(); } if (role == Qt::DisplayRole) { QString header = this->m_columns.at(section).getColumnName(false); return QVariant(header); } else if (role == Qt::ToolTipRole) { QString header = this->m_columns.at(section).getColumnToolTip(false); return header.isEmpty() ? QVariant() : QVariant(header); } return QVariant(); } QModelIndex CListModelBaseNonTemplate::index(int row, int column, const QModelIndex &parent) const { Q_UNUSED(parent); return QAbstractItemModel::createIndex(row, column); } QModelIndex CListModelBaseNonTemplate::parent(const QModelIndex &child) const { Q_UNUSED(child); return QModelIndex(); } BlackMisc::CPropertyIndex CListModelBaseNonTemplate::columnToPropertyIndex(int column) const { return this->m_columns.columnToPropertyIndex(column); } int CListModelBaseNonTemplate::propertyIndexToColumn(const CPropertyIndex &propertyIndex) const { return m_columns.propertyIndexToColumn(propertyIndex); } BlackMisc::CPropertyIndex CListModelBaseNonTemplate::modelIndexToPropertyIndex(const QModelIndex &index) const { return this->columnToPropertyIndex(index.column()); } void CListModelBaseNonTemplate::setSortColumnByPropertyIndex(const BlackMisc::CPropertyIndex &propertyIndex) { this->m_sortedColumn = this->m_columns.propertyIndexToColumn(propertyIndex); } bool CListModelBaseNonTemplate::hasValidSortColumn() const { if (!(this->m_sortedColumn >= 0 && this->m_sortedColumn < this->m_columns.size())) { return false; } return this->m_columns.isSortable(this->m_sortedColumn); } Qt::ItemFlags CListModelBaseNonTemplate::flags(const QModelIndex &index) const { Qt::ItemFlags f = QAbstractItemModel::flags(index); if (!index.isValid()) { return f; } bool editable = this->m_columns.isEditable(index); f = editable ? (f | Qt::ItemIsEditable) : (f ^ Qt::ItemIsEditable); const CDefaultFormatter *formatter = this->m_columns.getFormatter(index); if (formatter) { return formatter->flags(f, editable); } // fallback behaviour with no formatter return f; } const QString &CListModelBaseNonTemplate::getTranslationContext() const { return m_columns.getTranslationContext(); } int CListModelBaseNonTemplate::ps_updateContainer(const CVariant &variant, bool sort) { return this->performUpdateContainer(variant, sort); } template int CListModelBase::rowCount(const QModelIndex &parentIndex) const { Q_UNUSED(parentIndex); return this->getContainerOrFilteredContainer().size(); } template bool CListModelBase::isValidIndex(const QModelIndex &index) const { if (!index.isValid()) { return false; } return (index.row() >= 0 && index.row() < this->rowCount(index) && index.column() >= 0 && index.column() < this->columnCount(index)); } template QVariant CListModelBase::data(const QModelIndex &index, int role) const { // check / init if (!this->isValidIndex(index)) { return QVariant(); } const CDefaultFormatter *formatter = this->m_columns.getFormatter(index); Q_ASSERT(formatter); if (!formatter) { return QVariant(); } //! Formatted data ObjectType obj = this->getContainerOrFilteredContainer()[index.row()]; BlackMisc::CPropertyIndex propertyIndex = this->columnToPropertyIndex(index.column()); return formatter->data(role, obj.propertyByIndex(propertyIndex)).getQVariant(); } template bool CListModelBase::setData(const QModelIndex &index, const QVariant &value, int role) { Qt::ItemDataRole dataRole = static_cast(role); if (!(dataRole == Qt::UserRole || dataRole == Qt::EditRole)) { return false; } // check / init if (!this->isValidIndex(index)) { return false; } if (!this->m_columns.isEditable(index)) { return false; } const CDefaultFormatter *formatter = this->m_columns.getFormatter(index); Q_ASSERT(formatter); if (!formatter) { return false; } ObjectType obj = this->m_container[index.row()]; ObjectType currentObject(obj); BlackMisc::CPropertyIndex propertyIndex = this->columnToPropertyIndex(index.column()); obj.setPropertyByIndex(value, propertyIndex); if (obj != currentObject) { QModelIndex topLeft = index.sibling(index.row(), 0); QModelIndex bottomRight = index.sibling(index.row(), this->columnCount() - 1); this->m_container[index.row()] = obj; const CVariant co = CVariant::from(obj); emit objectChanged(co, propertyIndex); emit dataChanged(topLeft, bottomRight); this->updateFilteredContainer(); return true; } return false; } template int CListModelBase::update(const ContainerType &container, bool sort) { // Keep sorting out of begin/end reset model ContainerType sortedContainer; int oldSize = this->m_container.size(); bool performSort = sort && container.size() > 1 && this->hasValidSortColumn(); if (performSort) { int sortColumn = this->getSortColumn(); sortedContainer = this->sortContainerByColumn(container, sortColumn, this->m_sortOrder); } this->beginResetModel(); this->m_container = performSort ? sortedContainer : container; this->updateFilteredContainer(); this->endResetModel(); int newSize = this->m_container.size(); if (oldSize != newSize) { this->emitRowCountChanged(); } return newSize; } template void CListModelBase::update(const QModelIndex &index, const ObjectType &object) { if (index.row() >= this->m_container.size()) { return; } this->m_container[index.row()] = object; QModelIndex i1 = index.sibling(index.row(), 0); QModelIndex i2 = index.sibling(index.row(), this->columnCount(index) - 1); emit this->dataChanged(i1, i2); // which range has been changed } template void CListModelBase::update(int rowIndex, const ObjectType &object) { this->update(this->index(rowIndex, 0), object); } template CWorker *CListModelBase::updateAsync(const ContainerType &container, bool sort) { Q_UNUSED(sort); auto sortColumn = this->getSortColumn(); auto sortOrder = this->getSortOrder(); CWorker *worker = BlackMisc::CWorker::fromTask(this, "ModelSort", [this, container, sortColumn, sortOrder]() { ContainerType sortedContainer = this->sortContainerByColumn(container, sortColumn, sortOrder); }); worker->thenWithResult(this, [this](const ContainerType &sortedContainer) { this->ps_updateContainer(CVariant::from(sortedContainer), false); }); worker->then(this, &CListModelBase::asyncUpdateFinished); return worker; } template void CListModelBase::updateContainerMaybeAsync(const ContainerType &container, bool sort) { if (container.size() > asyncThreshold && sort) { // larger container with sorting updateAsync(container, sort); } else { update(container, sort); } } template bool CListModelBase::hasFilter() const { return m_filter ? true : false; } template void CListModelBase::removeFilter() { if (!this->hasFilter()) { return; } this->m_filter.reset(nullptr); this->beginResetModel(); this->updateFilteredContainer(); this->endResetModel(); this->emitRowCountChanged(); } template void CListModelBase::setFilter(std::unique_ptr > &filter) { if (!filter) { this->removeFilter(); return; } // empty filter if (filter->isValid()) { this->m_filter = std::move(filter); this->beginResetModel(); this->updateFilteredContainer(); this->endResetModel(); this->emitRowCountChanged(); } else { this->removeFilter(); } } template const ObjectType &CListModelBase::at(const QModelIndex &index) const { if (index.row() < 0 || index.row() >= this->rowCount()) { static const ObjectType def {}; // default object return def; } else { return this->getContainerOrFilteredContainer()[index.row()]; } } template const ContainerType &CListModelBase::getContainer() const { return this->m_container; } template void CListModelBase::push_back(const ObjectType &object) { beginInsertRows(QModelIndex(), this->m_container.size(), this->m_container.size()); this->m_container.push_back(object); endInsertRows(); this->updateFilteredContainer(); this->emitRowCountChanged(); } template void CListModelBase::insert(const ObjectType &object) { beginInsertRows(QModelIndex(), 0, 0); this->m_container.insert(this->m_container.begin(), object); endInsertRows(); if (this->hasFilter()) { this->beginResetModel(); this->updateFilteredContainer(); this->endResetModel(); } this->emitRowCountChanged(); } template void CListModelBase::remove(const ObjectType &object) { int oldSize = this->m_container.size(); beginRemoveRows(QModelIndex(), 0, 0); this->m_container.remove(object); endRemoveRows(); int newSize = this->m_container.size(); if (oldSize != newSize) { this->emitRowCountChanged(); if (this->hasFilter()) { this->beginResetModel(); this->updateFilteredContainer(); this->endResetModel(); } } } template void CListModelBase::clear() { beginResetModel(); this->m_container.clear(); this->m_containerFiltered.clear(); endResetModel(); this->emitRowCountChanged(); } template bool CListModelBase::isEmpty() const { return this->m_container.isEmpty(); } template int CListModelBase::performUpdateContainer(const BlackMisc::CVariant &variant, bool sort) { ContainerType c(variant.to()); return this->update(c, sort); } template const ContainerType &CListModelBase::getContainerOrFilteredContainer() const { if (!this->hasFilter()) { return this->m_container; } return m_containerFiltered; } template void CListModelBase::updateFilteredContainer() { if (this->hasFilter()) { this->m_containerFiltered = this->m_filter->filter(this->m_container); } else { this->m_containerFiltered.clear(); } } template void CListModelBase::emitRowCountChanged() { int n = this->getContainerOrFilteredContainer().size(); emit this->rowCountChanged(n, this->hasFilter()); } template void CListModelBase::sort() { this->sort(this->getSortColumn(), this->getSortOrder()); } template void CListModelBase::sort(int column, Qt::SortOrder order) { if (column == this->m_sortedColumn && order == this->m_sortOrder) { return; } // new order this->m_sortedColumn = column; this->m_sortOrder = order; if (this->m_container.size() < 2) { return; } // nothing to do // sort the values this->updateContainerMaybeAsync(this->m_container, true); } template void CListModelBase::truncate(int maxNumber, bool forceSort) { if (this->rowCount() <= maxNumber) { return; } if (forceSort) { this->sort(); } // make sure container is sorted ContainerType container(this->getContainer()); container.truncate(maxNumber); this->updateContainerMaybeAsync(container, false); } template ContainerType CListModelBase::sortContainerByColumn(const ContainerType &container, int column, Qt::SortOrder order) const { if (container.size() < 2 || !this->m_columns.isSortable(column)) { return container; } // nothing to do // this is the only part not really thread safe, but columns do not change so far BlackMisc::CPropertyIndex propertyIndex = this->m_columns.columnToSortPropertyIndex(column); Q_ASSERT(!propertyIndex.isEmpty()); if (propertyIndex.isEmpty()) { return container; } // at release build do nothing // sort the values const auto p = [ = ](const ObjectType & a, const ObjectType & b) -> bool { BlackMisc::CVariant aQv = a.propertyByIndex(propertyIndex); BlackMisc::CVariant bQv = b.propertyByIndex(propertyIndex); return (order == Qt::AscendingOrder) ? (aQv < bQv) : (bQv < aQv); }; const ContainerType sorted = container.sorted(p); return sorted; } // see here for the reason of thess forward instantiations // http://www.parashift.com/c++-faq/separate-template-class-defn-from-decl.html template class CListModelBase; template class CListModelBase; template class CListModelBase; template class CListModelBase; template class CListModelBase; template class CListModelBase; template class CListModelBase; template class CListModelBase; template class CListModelBase; template class CListModelBase; template class CListModelBase; template class CListModelBase; template class CListModelBase; template class CListModelBase; template class CListModelBase; } // namespace } // namespace