Commit 009e3078 authored by Nicolas Carion's avatar Nicolas Carion
Browse files

Switch to managed ptrs in Tree-like models

parent 424077da
......@@ -22,22 +22,30 @@
#include "abstracttreemodel.hpp"
#include "treeitem.hpp"
int AbstractTreeModel::currentTreeId = 0;
AbstractTreeModel::AbstractTreeModel(QObject *parent)
: QAbstractItemModel(parent)
, rootItem(new TreeItem(QList<QVariant>(), this))
{
}
std::shared_ptr<AbstractTreeModel> AbstractTreeModel::construct(QObject *parent)
{
std::shared_ptr<AbstractTreeModel> self(new AbstractTreeModel(parent));
self->rootItem.reset(new TreeItem(QList<QVariant>(), self, std::shared_ptr<TreeItem>()));
return self;
}
AbstractTreeModel::~AbstractTreeModel()
{
delete rootItem;
}
int AbstractTreeModel::columnCount(const QModelIndex &parent) const
{
if (parent.isValid()) return static_cast<TreeItem *>(parent.internalPointer())->columnCount();
if (!parent.isValid()) return rootItem->columnCount();
return rootItem->columnCount();
const int id = (int)parent.internalId();
auto item = getItemById(id);
return item->columnCount();
}
QVariant AbstractTreeModel::data(const QModelIndex &index, int role) const
......@@ -49,7 +57,7 @@ QVariant AbstractTreeModel::data(const QModelIndex &index, int role) const
if (role != Qt::DisplayRole) {
return QVariant();
}
TreeItem *item = static_cast<TreeItem *>(index.internalPointer());
auto item = getItemById((int)index.internalId());
return item->dataColumn(index.column());
}
......@@ -58,7 +66,7 @@ Qt::ItemFlags AbstractTreeModel::flags(const QModelIndex &index) const
const auto flags = QAbstractItemModel::flags(index);
if (index.isValid()) {
TreeItem *item = static_cast<TreeItem *>(index.internalPointer());
auto item = getItemById((int)index.internalId());
if (item->depth() == 1) {
return flags & ~Qt::ItemIsSelectable;
}
......@@ -77,15 +85,15 @@ QModelIndex AbstractTreeModel::index(int row, int column, const QModelIndex &par
{
if (!hasIndex(row, column, parent)) return QModelIndex();
TreeItem *parentItem;
std::shared_ptr<TreeItem> parentItem;
if (!parent.isValid())
parentItem = rootItem;
else
parentItem = static_cast<TreeItem *>(parent.internalPointer());
parentItem = getItemById((int)parent.internalId());
TreeItem *childItem = parentItem->child(row);
if (childItem) return createIndex(row, column, childItem);
std::shared_ptr<TreeItem> childItem = parentItem->child(row);
if (childItem) return createIndex(row, column, quintptr(childItem->getId()));
return QModelIndex();
}
......@@ -94,36 +102,38 @@ QModelIndex AbstractTreeModel::parent(const QModelIndex &index) const
{
if (!index.isValid()) return QModelIndex();
TreeItem *childItem = static_cast<TreeItem *>(index.internalPointer());
TreeItem *parentItem = childItem->parentItem();
std::shared_ptr<TreeItem> childItem = getItemById((int)index.internalId());
std::shared_ptr<TreeItem> parentItem = childItem->parentItem().lock();
Q_ASSERT(parentItem);
if (parentItem == rootItem) return QModelIndex();
return createIndex(parentItem->row(), 0, parentItem);
return createIndex(parentItem->row(), 0, quintptr(parentItem->getId()));
}
int AbstractTreeModel::rowCount(const QModelIndex &parent) const
{
TreeItem *parentItem;
if (parent.column() > 0) return 0;
std::shared_ptr<TreeItem> parentItem;
if (!parent.isValid())
parentItem = rootItem;
else
parentItem = static_cast<TreeItem *>(parent.internalPointer());
parentItem = getItemById((int)parent.internalId());
return parentItem->childCount();
}
QModelIndex AbstractTreeModel::getIndexFromItem(TreeItem *item) const
QModelIndex AbstractTreeModel::getIndexFromItem(std::shared_ptr<TreeItem> item) const
{
if (item == rootItem) {
return QModelIndex();
}
return index(item->row(), 0, getIndexFromItem(item->parentItem()));
return index(item->row(), 0, getIndexFromItem(item->parentItem().lock()));
}
void AbstractTreeModel::notifyRowAboutToAppend(TreeItem *item)
void AbstractTreeModel::notifyRowAboutToAppend(std::shared_ptr<TreeItem> item)
{
auto index = getIndexFromItem(item);
beginInsertRows(index, item->childCount(), item->childCount());
......@@ -134,7 +144,7 @@ void AbstractTreeModel::notifyRowAppended()
endInsertRows();
}
void AbstractTreeModel::notifyRowAboutToDelete(TreeItem *item, int row)
void AbstractTreeModel::notifyRowAboutToDelete(std::shared_ptr<TreeItem> item, int row)
{
auto index = getIndexFromItem(item);
beginRemoveRows(index, row, row);
......@@ -144,3 +154,31 @@ void AbstractTreeModel::notifyRowDeleted()
{
endRemoveRows();
}
// static
int AbstractTreeModel::getNextId()
{
return currentTreeId++;
}
void AbstractTreeModel::registerItem(std::shared_ptr<TreeItem> item)
{
int id = item->getId();
Q_ASSERT(m_allItems.count(id) == 0);
m_allItems[id] = item;
}
void AbstractTreeModel::deregisterItem(int id)
{
Q_ASSERT(m_allItems.count(id) > 0);
m_allItems.erase(id);
}
std::shared_ptr<TreeItem> AbstractTreeModel::getItemById(int id) const
{
if (id == rootItem->getId()) {
return rootItem;
}
Q_ASSERT(m_allItems.count(id) > 0);
return m_allItems.at(id);
}
......@@ -23,25 +23,37 @@
#define ABSTRACTTREEMODEL_H
#include <QAbstractItemModel>
#include <memory>
#include <unordered_map>
/* @brief This class represents a generic tree hierarchy
*/
class TreeItem;
class AbstractTreeModel : public QAbstractItemModel
class AbstractTreeModel : public QAbstractItemModel, public std::enable_shared_from_this<AbstractTreeModel>
{
Q_OBJECT
public:
/* @brief Construct a TreeModel
@param parent is the parent object of the model
@return a ptr to the created object
*/
static std::shared_ptr<AbstractTreeModel> construct(QObject *parent = nullptr);
protected:
// This is protected. Call construct instead.
explicit AbstractTreeModel(QObject *parent = nullptr);
public:
virtual ~AbstractTreeModel();
/* @brief Given an item from the hierarchy, construct the corresponding ModelIndex */
QModelIndex getIndexFromItem(TreeItem *item) const;
QModelIndex getIndexFromItem(std::shared_ptr<TreeItem> item) const;
/* @brief Send the appropriate notification related to a row that we are appending
@param item is the parent item to which row is appended
*/
void notifyRowAboutToAppend(TreeItem *item);
void notifyRowAboutToAppend(std::shared_ptr<TreeItem> item);
/* @brief Send the appropriate notification related to a row that we have appended
*/
......@@ -51,13 +63,16 @@ public:
@param item is the parent of the row being deleted
@param row is the index of the row being deleted
*/
void notifyRowAboutToDelete(TreeItem *item, int row);
void notifyRowAboutToDelete(std::shared_ptr<TreeItem> item, int row);
/* @brief Send the appropriate notification related to a row that we have appended
@param item is the item to which row are appended
*/
void notifyRowDeleted();
/* @brief Return a ptr to an item given its id */
std::shared_ptr<TreeItem> getItemById(int id) const;
QVariant data(const QModelIndex &index, int role) const override;
// This is reimplemented to prevent selection of the categories
Qt::ItemFlags flags(const QModelIndex &index) const override;
......@@ -67,8 +82,22 @@ public:
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
int columnCount(const QModelIndex &parent = QModelIndex()) const override;
friend class TreeItem;
protected:
TreeItem *rootItem;
/* @brief Register a new item. This is a call-back meant to be called from TreeItem */
void registerItem(std::shared_ptr<TreeItem> item);
/* @brief Deregister an item. This is a call-back meant to be called from TreeItem */
void deregisterItem(int id);
/* @brief Returns the next valid id to give to a new element */
static int getNextId();
protected:
std::shared_ptr<TreeItem> rootItem;
std::unordered_map<int, std::shared_ptr<TreeItem>> m_allItems;
static int currentTreeId;
};
#endif
......@@ -21,67 +21,120 @@
#include "treeitem.hpp"
#include "abstracttreemodel.hpp"
#include <QDebug>
TreeItem::TreeItem(const QList<QVariant> &data, AbstractTreeModel *model, TreeItem *parent)
TreeItem::TreeItem(const QList<QVariant> &data, std::shared_ptr<AbstractTreeModel> model, std::shared_ptr<TreeItem> parent, int id)
: m_itemData(data)
, m_parentItem(parent)
, m_model(model)
, m_depth(0)
, m_id(id == -1 ? AbstractTreeModel::getNextId() : id)
{
m_parentItem = parent;
m_itemData = data;
m_depth = 0;
m_model = model;
}
std::shared_ptr<TreeItem> TreeItem::construct(const QList<QVariant> &data, std::shared_ptr<AbstractTreeModel> model, std::shared_ptr<TreeItem> parent, int id)
{
std::shared_ptr<TreeItem> self(new TreeItem(data,model, parent, id));
id = self->m_id;
baseFinishConstruct(self);
return self;
}
// static
void TreeItem::baseFinishConstruct(std::shared_ptr<TreeItem> self)
{
if (auto ptr = self->m_model.lock()) {
ptr->registerItem(self);
} else {
qDebug() << "Error : construction of treeItem failed because parent model is not available anymore";
Q_ASSERT(false);
}
}
TreeItem::~TreeItem()
{
qDeleteAll(m_childItems);
if (auto ptr = m_model.lock()) {
ptr->deregisterItem(m_id);
} else {
qDebug() << "ERROR: Something went wrong when deleting TreeItem. Model is not available anymore";
}
}
TreeItem *TreeItem::appendChild(const QList<QVariant> &data)
std::shared_ptr<TreeItem> TreeItem::appendChild(const QList<QVariant> &data)
{
m_model->notifyRowAboutToAppend(this);
auto *child = new TreeItem(data, m_model, this);
child->m_depth = m_depth + 1;
m_childItems.append(child);
m_model->notifyRowAppended();
return child;
if (auto ptr = m_model.lock()) {
auto child = construct(data, ptr, shared_from_this());
child->m_depth = m_depth + 1;
int id = child->getId();
m_childItems.push_back(child);
auto it = std::prev(m_childItems.end());
m_iteratorTable[id] = it;
ptr->notifyRowAboutToAppend(shared_from_this());
ptr->notifyRowAppended();
return child;
}
qDebug() << "ERROR: Something went wrong when appending child in TreeItem. Model is not available anymore";
Q_ASSERT(false);
return std::shared_ptr<TreeItem>();
}
void TreeItem::appendChild(TreeItem *child)
void TreeItem::appendChild(std::shared_ptr<TreeItem> child)
{
m_model->notifyRowAboutToAppend(this);
child->m_depth = m_depth + 1;
child->m_parentItem = this;
m_childItems.append(child);
m_model->notifyRowAppended();
if (auto ptr = m_model.lock()) {
child->m_depth = m_depth + 1;
child->m_parentItem = shared_from_this();
int id = child->getId();
auto it = m_childItems.insert(m_childItems.end(), std::move(child));
m_iteratorTable[id] = it;
ptr->notifyRowAboutToAppend(shared_from_this());
ptr->notifyRowAppended();
} else {
qDebug() << "ERROR: Something went wrong when appending child in TreeItem. Model is not available anymore";
Q_ASSERT(false);
}
}
void TreeItem::removeChild(TreeItem *child)
void TreeItem::removeChild(std::shared_ptr<TreeItem> child)
{
m_model->notifyRowAboutToDelete(this, child->row());
bool success = m_childItems.removeAll(child) != 0;
Q_ASSERT(success);
child->m_depth = 0;
child->m_parentItem = nullptr;
m_model->notifyRowDeleted();
if (auto ptr = m_model.lock()) {
ptr->notifyRowAboutToDelete(shared_from_this(), child->row());
// get iterator corresponding to child
auto it = m_iteratorTable[child->getId()];
// deletion of child
m_childItems.erase(it);
// clean iterator table
m_iteratorTable.erase(child->getId());
child->m_depth = 0;
child->m_parentItem.reset();
ptr->notifyRowDeleted();
} else {
qDebug() << "ERROR: Something went wrong when removing child in TreeItem. Model is not available anymore";
Q_ASSERT(false);
}
}
void TreeItem::changeParent(TreeItem *newParent)
void TreeItem::changeParent(std::shared_ptr<TreeItem> newParent)
{
if (m_parentItem) {
m_parentItem->removeChild(this);
if (auto ptr = m_parentItem.lock()) {
ptr->removeChild(shared_from_this());
}
if (newParent) {
newParent->appendChild(this);
newParent->appendChild(shared_from_this());
m_parentItem = newParent;
}
}
TreeItem *TreeItem::child(int row) const
std::shared_ptr<TreeItem> TreeItem::child(int row) const
{
return m_childItems.value(row);
Q_ASSERT(row >= 0 && row < (int)m_childItems.size());
auto it = m_childItems.cbegin();
std::advance(it, row);
return (*it);
}
int TreeItem::childCount() const
{
return m_childItems.count();
return (int)m_childItems.size();
}
int TreeItem::columnCount() const
......@@ -94,15 +147,18 @@ QVariant TreeItem::dataColumn(int column) const
return m_itemData.value(column);
}
TreeItem *TreeItem::parentItem()
std::weak_ptr<TreeItem> TreeItem::parentItem() const
{
return m_parentItem;
}
int TreeItem::row() const
{
if (m_parentItem) return m_parentItem->m_childItems.indexOf(const_cast<TreeItem *>(this));
if (auto ptr = m_parentItem.lock()) {
// we compute the distance in the parent's children list
auto it = ptr->m_childItems.begin();
return (int)std::distance(it, (decltype(it))ptr->m_iteratorTable.at(m_id));
}
return -1;
}
......@@ -110,3 +166,8 @@ int TreeItem::depth() const
{
return m_depth;
}
int TreeItem::getId() const
{
return m_id;
}
......@@ -24,45 +24,56 @@
#include <QList>
#include <QVariant>
#include <memory>
#include <unordered_map>
/* @brief This class is a generic class to represent items of a tree-like model
*/
class AbstractTreeModel;
class TreeItem : public QObject
class TreeItem : public QObject, public std::enable_shared_from_this<TreeItem>
{
public:
/* @brief Construct a TreeItem
@param data List of data elements (columns) of the created item
@param model Pointer to the model to which this elem belongs to
@param parentItem address of the parent if the child is not orphan
@param id of the newly created item. If left to -1, the id is assigned automatically
@return a ptr to the constructed item
*/
explicit TreeItem(const QList<QVariant> &data, AbstractTreeModel *model, TreeItem *parentItem = nullptr);
static std::shared_ptr<TreeItem> construct(const QList<QVariant> &data, std::shared_ptr<AbstractTreeModel> model, std::shared_ptr<TreeItem> parent, int id = -1);
friend class AbstractTreeModel;
protected:
// This is protected. Call construct instead
explicit TreeItem(const QList<QVariant> &data, std::shared_ptr<AbstractTreeModel> model, std::shared_ptr<TreeItem> parent, int id = -1);
public:
virtual ~TreeItem();
/* @brief Creates a child of the current item
@param data: List of data elements (columns) to init the child with.
*/
TreeItem *appendChild(const QList<QVariant> &data);
std::shared_ptr<TreeItem> appendChild(const QList<QVariant> &data);
/* @brief Appends an already created child
Useful for example if the child should be a subclass of TreeItem
*/
void appendChild(TreeItem *child);
void appendChild(std::shared_ptr<TreeItem> child);
/* @brief Remove given child from children list. The parent of the child is updated
accordingly
*/
void removeChild(TreeItem *child);
void removeChild(std::shared_ptr<TreeItem> child);
/* @brief Change the parent of the current item. Structures are modified accordingly
*/
void changeParent(TreeItem *newParent);
void changeParent(std::shared_ptr<TreeItem> newParent);
/* @brief Retrieves a child of the current item
@param row is the index of the child to retrieve
*/
TreeItem *child(int row) const;
std::shared_ptr<TreeItem> child(int row) const;
/* @brief Return the number of children */
int childCount() const;
......@@ -82,18 +93,30 @@ public:
/* @brief Return a ptr to the parent item
*/
TreeItem *parentItem();
std::weak_ptr<TreeItem> parentItem() const;
/* @brief Return the depth of the current item*/
int depth() const;
/* @brief Return the id of the current item*/
int getId() const;
protected:
QList<TreeItem *> m_childItems;
/* @brief Finish construction of object given its pointer
This is a separated function so that it can be called from derived classes */
static void baseFinishConstruct(std::shared_ptr<TreeItem> self);
std::list<std::shared_ptr<TreeItem>> m_childItems;
std::unordered_map<int, std::list<std::shared_ptr<TreeItem>>::iterator>
m_iteratorTable; // this logs the iterator associated which each child id. This allows easy access of a child based on its id.
QList<QVariant> m_itemData;
TreeItem *m_parentItem;
std::weak_ptr<TreeItem> m_parentItem;
AbstractTreeModel *m_model;
std::weak_ptr<AbstractTreeModel> m_model;
int m_depth;
int m_id;
};
#endif
......@@ -20,6 +20,7 @@
***************************************************************************/
#include "assetfilter.hpp"
#include "abstractmodel/abstracttreemodel.hpp"
#include "abstractmodel/treeitem.hpp"
#include "assettreemodel.hpp"
......@@ -37,7 +38,7 @@ void AssetFilter::setFilterName(bool enabled, const QString &pattern)
sort(0);
}
bool AssetFilter::filterName(TreeItem *item) const
bool AssetFilter::filterName(std::shared_ptr<TreeItem> item) const
{
if (!m_name_enabled) {
return true;
......@@ -52,7 +53,8 @@ bool AssetFilter::filterName(TreeItem *item) const
bool AssetFilter::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const
{
QModelIndex row = sourceModel()->index(sourceRow, 0, sourceParent);
TreeItem *item = static_cast<TreeItem *>(row.internalPointer());
AbstractTreeModel *model = static_cast<AbstractTreeModel*>(sourceModel());
std::shared_ptr<TreeItem> item = model->getItemById((int)row.internalId());
if (item->dataColumn(AssetTreeModel::idCol) == QStringLiteral("root")) {
// In that case, we have a category. We hide it if it does not have children.
......@@ -72,7 +74,7 @@ bool AssetFilter::isVisible(const QModelIndex &sourceIndex)
return filterAcceptsRow(sourceIndex.row(), parent);
}
bool AssetFilter::applyAll(TreeItem *item) const
bool AssetFilter::applyAll(std::shared_ptr<TreeItem> item) const
{
return filterName(item);
}
......
......@@ -53,9 +53,9 @@ public:
protected:
bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override;
bool filterName(TreeItem *item) const;
bool filterName(std::shared_ptr<TreeItem> item) const;
/* @brief Apply all filter and returns true if the object should be kept after filtering */
virtual bool applyAll(TreeItem *item) const;
virtual bool applyAll(std::shared_ptr<TreeItem> item) const;
bool m_name_enabled;
QString m_name_value;
......
......@@ -47,7 +47,7 @@ QString AssetTreeModel::getName(const QModelIndex &index) const
if (!index.isValid()) {
return QString();
}
TreeItem *item = static_cast<TreeItem *>(index.internalPointer());
std::shared_ptr<TreeItem> item = getItemById((int)index.internalId());
if (item->depth() == 1) {
return item->dataColumn(0).toString();
}
......@@ -59,7 +59,7 @@ QString AssetTreeModel::getDescription(const QModelIndex &index) const
if (!index.isValid()) {
return QString();
}