diff options
author | Roberto C. Sanchez <roberto@connexer.com> | 2014-10-21 22:48:27 -0400 |
---|---|---|
committer | Roberto C. Sanchez <roberto@connexer.com> | 2014-10-21 22:48:27 -0400 |
commit | dd2f7ce46df53f2c377c02d1bf4df8adcf092072 (patch) | |
tree | 51d4bd5d66a45a24784695c4e99b452b417dc3d1 /src/backend/bookshelfmodel/btbookshelftreemodel.cpp | |
parent | b954e6dbcceaba3b50aca624e1bddc6db4830829 (diff) |
Imported Upstream version 2.3
Diffstat (limited to 'src/backend/bookshelfmodel/btbookshelftreemodel.cpp')
-rw-r--r-- | src/backend/bookshelfmodel/btbookshelftreemodel.cpp | 474 |
1 files changed, 474 insertions, 0 deletions
diff --git a/src/backend/bookshelfmodel/btbookshelftreemodel.cpp b/src/backend/bookshelfmodel/btbookshelftreemodel.cpp new file mode 100644 index 0000000..47526e9 --- /dev/null +++ b/src/backend/bookshelfmodel/btbookshelftreemodel.cpp @@ -0,0 +1,474 @@ +/********* +* +* In the name of the Father, and of the Son, and of the Holy Spirit. +* +* This file is part of BibleTime's source code, http://www.bibletime.info/. +* +* Copyright 1999-2009 by the BibleTime developers. +* The BibleTime source code is licensed under the GNU General Public License +* version 2.0. +* +**********/ + +#include "backend/bookshelfmodel/btbookshelftreemodel.h" + +#include <QQueue> +#include <QSet> +#include "backend/bookshelfmodel/categoryitem.h" +#include "backend/bookshelfmodel/distributionitem.h" +#include "backend/bookshelfmodel/languageitem.h" +#include "backend/bookshelfmodel/moduleitem.h" + +using namespace BookshelfModel; + +BtBookshelfTreeModel::BtBookshelfTreeModel(QObject *parent) + : QAbstractItemModel(parent), m_sourceModel(0), m_rootItem(new RootItem), + m_checkable(false), m_defaultChecked(false) +{ + m_groupingOrder.push_back(GROUP_CATEGORY); + m_groupingOrder.push_back(GROUP_LANGUAGE); +} + +BtBookshelfTreeModel::~BtBookshelfTreeModel() { + delete m_rootItem; +} + +int BtBookshelfTreeModel::rowCount(const QModelIndex &parent) const { + return getItem(parent)->children().size(); +} + +int BtBookshelfTreeModel::columnCount(const QModelIndex &parent) const { + return 1; +} + +bool BtBookshelfTreeModel::hasChildren(const QModelIndex &parent) const { + return !getItem(parent)->children().isEmpty(); +} + +QModelIndex BtBookshelfTreeModel::index(int row, int column, + const QModelIndex &parent) const +{ + if (!hasIndex(row, column, parent)) return QModelIndex(); + + Item *parentItem(getItem(parent)); + Item *childItem(parentItem->childAt(row)); + if (childItem != 0) { + return createIndex(row, column, childItem); + } else { + return QModelIndex(); + } +} + +QModelIndex BtBookshelfTreeModel::parent(const QModelIndex &index) const { + if (!index.isValid()) return QModelIndex(); + + Item *childItem(static_cast<Item*>(index.internalPointer())); + Q_ASSERT(childItem != 0); + Item *parentItem(childItem->parent()); + Q_ASSERT(parentItem != 0); + + if (parentItem == m_rootItem) { + return QModelIndex(); + } + return createIndex(parentItem->childIndex(), 0, parentItem); +} + +QVariant BtBookshelfTreeModel::data(const QModelIndex &index, int role) const { + typedef util::filesystem::DirectoryUtil DU; + + if (!index.isValid() || index.column() != 0) { + return QVariant(); + } + + Item *i(static_cast<Item*>(index.internalPointer())); + Q_ASSERT(i != 0); + switch (role) { + case Qt::DisplayRole: + return i->name(); + case Qt::CheckStateRole: + if (!m_checkable) break; + case BtBookshelfTreeModel::CheckStateRole: + return i->checkState(); + case Qt::DecorationRole: + return i->icon(); + case BtBookshelfModel::ModulePointerRole: + if (i->type() == Item::ITEM_MODULE) { + ModuleItem *mi(dynamic_cast<ModuleItem *>(i)); + if (mi != 0) { + return qVariantFromValue((void*) mi->moduleInfo()); + } + } + return 0; + default: + break; + } + return QVariant(); +} + +bool BtBookshelfTreeModel::setData(const QModelIndex &itemIndex, + const QVariant &value, + int role) +{ + typedef QPair<Item *, QModelIndex> IP; + + if (role == Qt::CheckStateRole) { + bool ok; + Qt::CheckState newState((Qt::CheckState) value.toInt(&ok)); + if (ok && newState != Qt::PartiallyChecked) { + Item *i(static_cast<Item*>(itemIndex.internalPointer())); + Q_ASSERT(i != 0); + // Recursively (un)check all children: + QList<IP> q; + q.append(IP(i, itemIndex)); + while (!q.isEmpty()) { + const IP p(q.takeFirst()); + Item *item(p.first); + item->setCheckState(newState); + emit dataChanged(p.second, p.second); + const QList<Item*> &children(item->children()); + for (int i(0); i < children.size(); i++) { + q.append(IP(children.at(i), index(i, 0, p.second))); + } + } + + // Change check state of the item itself + i->setCheckState(newState); + emit dataChanged(itemIndex, itemIndex); + + // Recursively change parent check states. + resetParentCheckStates(itemIndex.parent()); + + return true; + } // if (ok && newState != Qt::PartiallyChecked) + } // if (role == Qt::CheckStateRole) + return false; +} + +Qt::ItemFlags BtBookshelfTreeModel::flags(const QModelIndex &index) const { + if (!index.isValid()) return 0; + + Qt::ItemFlags f(Qt::ItemIsEnabled | Qt::ItemIsSelectable); + + if (m_checkable) { + f |= Qt::ItemIsUserCheckable; + + Item *i(static_cast<Item*>(index.internalPointer())); + Q_ASSERT(i != 0); + + if (i->type() != Item::ITEM_MODULE) { + f |= Qt::ItemIsTristate; + } + } + + return f; +} + +QVariant BtBookshelfTreeModel::headerData(int section, + Qt::Orientation orientation, + int role) const +{ + if (orientation == Qt::Horizontal) { + return m_sourceModel->headerData(section, orientation, role); + } + return QVariant(); +} + +void BtBookshelfTreeModel::setSourceModel(QAbstractListModel *sourceModel) { + if (m_sourceModel == sourceModel) return; + + if (m_sourceModel != 0) { + disconnect(this, SLOT(moduleInserted(QModelIndex,int,int))); + disconnect(this, SLOT(moduleRemoved(QModelIndex,int,int))); + disconnect(this, SLOT(moduleDataChanged(QModelIndex,QModelIndex))); + beginRemoveRows(QModelIndex(), 0, m_rootItem->children().size() - 1); + delete m_rootItem; + m_modules.clear(); + m_rootItem = new RootItem; + endRemoveRows(); + } + + m_sourceModel = sourceModel; + + if (sourceModel != 0) { + connect(sourceModel, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)), + this, SLOT(moduleRemoved(QModelIndex,int,int))); + connect(sourceModel, SIGNAL(rowsInserted(QModelIndex,int,int)), + this, SLOT(moduleInserted(QModelIndex,int,int))); + connect(sourceModel, SIGNAL(dataChanged(QModelIndex, QModelIndex)), + this, SLOT(moduleDataChanged(QModelIndex, QModelIndex))); + + BtBookshelfModel *m(dynamic_cast<BtBookshelfModel*>(sourceModel)); + if (m != 0) { + Q_FOREACH(CSwordModuleInfo *module, m->modules()) { + addModule(module, m_defaultChecked); + } + } else { + for (int i(0); i < sourceModel->rowCount(); i++) { + CSwordModuleInfo *module( + static_cast<CSwordModuleInfo *>( + sourceModel->data( + sourceModel->index(i), + BtBookshelfModel::ModulePointerRole + ).value<void*>() + ) + ); + Q_ASSERT(module != 0); + addModule( + module, + m_defaultChecked + ); + } + } + } +} + +void BtBookshelfTreeModel::setGroupingOrder(const Grouping &groupingOrder) { + if (m_groupingOrder == groupingOrder) return; + m_groupingOrder = groupingOrder; + + if (m_sourceModel != 0) { + QSet<CSwordModuleInfo*> checked(checkedModules().toSet()); + beginRemoveRows(QModelIndex(), 0, m_rootItem->children().size() - 1); + delete m_rootItem; + m_modules.clear(); + m_rootItem = new RootItem; + endRemoveRows(); + + BtBookshelfModel *m(dynamic_cast<BtBookshelfModel*>(m_sourceModel)); + if (m != 0) { + Q_FOREACH(CSwordModuleInfo *module, m->modules()) { + addModule(module, checked.contains(module)); + } + } else { + for (int i(0); i < m_sourceModel->rowCount(); i++) { + CSwordModuleInfo *module( + static_cast<CSwordModuleInfo *>( + m_sourceModel->data( + m_sourceModel->index(i), + BtBookshelfModel::ModulePointerRole + ).value<void*>() + ) + ); + Q_ASSERT(module != 0); + addModule(module, checked.contains(module)); + } + } + } +} + +void BtBookshelfTreeModel::setCheckable(bool checkable) { + if (m_checkable == checkable) return; + m_checkable = checkable; + if (m_sourceModel != 0) { + QModelIndexList queue; + queue.append(QModelIndex()); + do { + QModelIndex parent(queue.takeFirst()); + int numChildren(rowCount(parent)); + emit dataChanged(index(0, 0, parent), + index(numChildren - 1, 0, parent)); + for (int i(0); i < numChildren; i++) { + QModelIndex childIndex(index(i, 0, parent)); + if (rowCount(childIndex) > 0) { + queue.append(childIndex); + } + } + } while (!queue.isEmpty()); + } +} + +QList<CSwordModuleInfo*> BtBookshelfTreeModel::checkedModules() const { + typedef ModuleItemMap::const_iterator MMCI; + + QList<CSwordModuleInfo*> modules; + for (MMCI it(m_modules.constBegin()); it != m_modules.constEnd(); it++) { + if (it.value()->checkState() == Qt::Checked) { + modules.append(it.key()); + } + } + return modules; +} + +void BtBookshelfTreeModel::addModule(CSwordModuleInfo *module, bool checked) { + if (m_modules.contains(module)) return; + Grouping g(m_groupingOrder); + addModule(module, QModelIndex(), g, checked); + + /** + \bug Calling reset() shouldn't be necessary here, but omitting it will + will break things like switching to a grouped layout or installing + new modules. As a side effect, all attached views will also reset + themselves. + */ + reset(); +} + +void BtBookshelfTreeModel::addModule(CSwordModuleInfo *module, + QModelIndex parentIndex, + Grouping &intermediateGrouping, + bool checked) +{ + Q_ASSERT(module != 0); + + if (!intermediateGrouping.empty()) { + QModelIndex newIndex; + switch (intermediateGrouping.front()) { + case GROUP_DISTRIBUTION: + newIndex = getGroup<DistributionItem>(module, parentIndex); + break; + case GROUP_CATEGORY: + newIndex = getGroup<CategoryItem>(module, parentIndex); + break; + case GROUP_LANGUAGE: + newIndex = getGroup<LanguageItem>(module, parentIndex); + break; + } + intermediateGrouping.pop_front(); + addModule(module, newIndex, intermediateGrouping, checked); + } else { + Item *parentItem(getItem(parentIndex)); + ModuleItem *newItem(new ModuleItem(module)); + newItem->setCheckState(checked ? Qt::Checked : Qt::Unchecked); + const int newIndex(parentItem->indexFor(newItem)); + beginInsertRows(parentIndex, newIndex, newIndex); + parentItem->insertChild(newIndex, newItem); + m_modules.insert(module, newItem); + endInsertRows(); + resetParentCheckStates(parentIndex); + } +} + +void BtBookshelfTreeModel::removeModule(CSwordModuleInfo *module) { + Item *i(m_modules.value(module, 0)); + if (i == 0) return; + + // Set i to be the lowest item (including empty groups) to remove: + Q_ASSERT(i->parent() != 0); + while (i->parent() != m_rootItem && i->parent()->children().size() <= 1) { + i = i->parent(); + } + Q_ASSERT(i != 0); + + // Calculate index of parent item: + QModelIndex parentIndex; + { + QList<int> indexes; + for (Item *j(i->parent()); j != m_rootItem; j = j->parent()) { + Q_ASSERT(j != 0); + indexes.push_back(j->childIndex()); + } + while (!indexes.isEmpty()) { + parentIndex = index(indexes.takeLast(), 0, parentIndex); + } + } + + // Remove item: + int index(i->childIndex()); + beginRemoveRows(parentIndex, index, index); + i->parent()->deleteChildAt(index); + m_modules.remove(module); + endRemoveRows(); + resetParentCheckStates(parentIndex); +} + +Item *BtBookshelfTreeModel::getItem(const QModelIndex &index) const { + if (index.isValid()) { + Item *item(static_cast<Item*>(index.internalPointer())); + Q_ASSERT(item != 0); + return item; + } else { + return m_rootItem; + } +} + +void BtBookshelfTreeModel::resetParentCheckStates(QModelIndex parentIndex) { + for ( ; parentIndex.isValid(); parentIndex = parentIndex.parent()) { + Item *parentItem(static_cast<Item*>(parentIndex.internalPointer())); + Q_ASSERT(parentItem != 0); + + Qt::CheckState oldState(parentItem->checkState()); + bool haveCheckedChildren(false); + bool haveUncheckedChildren(false); + for (int i(0); i < parentItem->children().size(); i++) { + Qt::CheckState state(parentItem->childAt(i)->checkState()); + if (state == Qt::PartiallyChecked) { + haveCheckedChildren = true; + haveUncheckedChildren = true; + break; + } else if (state == Qt::Checked) { + haveCheckedChildren = true; + if (haveUncheckedChildren) break; + } else if (state == Qt::Unchecked) { + haveUncheckedChildren = true; + if (haveCheckedChildren) break; + } + } + + Qt::CheckState newState; + if (haveCheckedChildren) { + if (haveUncheckedChildren) { + newState = Qt::PartiallyChecked; + } else { + newState = Qt::Checked; + } + } else { + newState = Qt::Unchecked; + } + if (newState == oldState) break; + + parentItem->setCheckState(newState); + emit dataChanged(parentIndex, parentIndex); + } // for ( ; parentIndex.isValid(); parentIndex = parentIndex.parent()) +} + +void BtBookshelfTreeModel::moduleDataChanged(const QModelIndex &topLeft, + const QModelIndex &bottomRight) +{ + typedef BtBookshelfModel BM; + static const BM::ModuleRole PR(BM::ModulePointerRole); + + Q_ASSERT(!topLeft.parent().isValid()); + Q_ASSERT(!bottomRight.parent().isValid()); + Q_ASSERT(topLeft.column() == 0 && bottomRight.column() == 0); + + for (int i(topLeft.row()); i <= bottomRight.row(); i++) { + QModelIndex moduleIndex(m_sourceModel->index(i, 0, topLeft.parent())); + QVariant data(m_sourceModel->data(moduleIndex, PR)); + CSwordModuleInfo *module((CSwordModuleInfo *) (data.value<void*>())); + + /// \todo There might be a better way to do this. + bool checked(m_modules.value(module)->checkState() == Qt::Checked); + removeModule(module); + addModule(module, checked); + } +} + +void BtBookshelfTreeModel::moduleInserted(const QModelIndex &parent, int start, + int end) +{ + typedef BtBookshelfModel BM; + static const BM::ModuleRole PR(BM::ModulePointerRole); + + for (int i(start); i <= end; i++) { + QModelIndex moduleIndex(m_sourceModel->index(i, 0, parent)); + QVariant data(m_sourceModel->data(moduleIndex, PR)); + CSwordModuleInfo *module((CSwordModuleInfo *) (data.value<void*>())); + + addModule(module, m_defaultChecked); + } +} + +void BtBookshelfTreeModel::moduleRemoved(const QModelIndex &parent, int start, + int end) +{ + typedef BtBookshelfModel BM; + static const BM::ModuleRole PR(BM::ModulePointerRole); + + for (int i(start); i <= end; i++) { + QModelIndex moduleIndex(m_sourceModel->index(i, 0, parent)); + QVariant data(m_sourceModel->data(moduleIndex, PR)); + CSwordModuleInfo *module((CSwordModuleInfo *) (data.value<void*>())); + + removeModule(module); + } +} |