#pragma once #include "common/SignalVector.hpp" #include #include #include #include namespace chatterino { template class SignalVectorModel : public QAbstractTableModel, pajlada::Signals::SignalHolder { public: SignalVectorModel(int columnCount, QObject *parent = nullptr) : QAbstractTableModel(parent) , columnCount_(columnCount) { for (int i = 0; i < columnCount; i++) { this->headerData_.emplace_back(); } } void init(BaseSignalVector *vec) { this->vector_ = vec; auto insert = [this](const SignalVectorItemArgs &args) { if (args.caller == this) { return; } // get row index int index = this->getModelIndexFromVectorIndex(args.index); assert(index >= 0 && index <= this->rows_.size()); // get row items std::vector row = this->createRow(); this->getRowFromItem(args.item, row); // insert row index = this->beforeInsert(args.item, row, index); this->beginInsertRows(QModelIndex(), index, index); this->rows_.insert(this->rows_.begin() + index, Row(row, args.item)); this->endInsertRows(); }; int i = 0; for (const TVectorItem &item : vec->getVector()) { SignalVectorItemArgs args{item, i++, 0}; insert(args); } this->managedConnect(vec->itemInserted, insert); this->managedConnect(vec->itemRemoved, [this](auto args) { if (args.caller == this) { return; } int row = this->getModelIndexFromVectorIndex(args.index); assert(row >= 0 && row <= this->rows_.size()); // remove row std::vector items = std::move(this->rows_[row].items); this->beginRemoveRows(QModelIndex(), row, row); this->rows_.erase(this->rows_.begin() + row); this->endRemoveRows(); this->afterRemoved(args.item, items, row); for (QStandardItem *item : items) { delete item; } }); this->afterInit(); } virtual ~SignalVectorModel() { for (Row &row : this->rows_) { for (QStandardItem *item : row.items) { delete item; } } } int rowCount(const QModelIndex &parent) const override { return this->rows_.size(); } int columnCount(const QModelIndex &parent) const override { return this->columnCount_; } QVariant data(const QModelIndex &index, int role) const override { int row = index.row(), column = index.column(); assert(row >= 0 && row < this->rows_.size() && column >= 0 && column < this->columnCount_); return rows_[row].items[column]->data(role); } bool setData(const QModelIndex &index, const QVariant &value, int role) override { int row = index.row(), column = index.column(); assert(row >= 0 && row < this->rows_.size() && column >= 0 && column < this->columnCount_); Row &rowItem = this->rows_[row]; rowItem.items[column]->setData(value, role); if (rowItem.isCustomRow) { this->customRowSetData(rowItem.items, column, value, role); } else { int vecRow = this->getVectorIndexFromModelIndex(row); this->vector_->removeItem(vecRow, this); assert(this->rows_[row].original); TVectorItem item = this->getItemFromRow( this->rows_[row].items, this->rows_[row].original.get()); this->vector_->insertItem(item, vecRow, this); } return true; } QVariant headerData(int section, Qt::Orientation orientation, int role) const override { if (orientation != Qt::Horizontal) { return QVariant(); } auto it = this->headerData_[section].find(role); if (it == this->headerData_[section].end()) { return QVariant(); } else { return it.value(); } } bool setHeaderData(int section, Qt::Orientation orientation, const QVariant &value, int role = Qt::DisplayRole) override { if (orientation != Qt::Horizontal) { return false; } this->headerData_[section][role] = value; emit this->headerDataChanged(Qt::Horizontal, section, section); return true; } Qt::ItemFlags flags(const QModelIndex &index) const override { int row = index.row(), column = index.column(); assert(row >= 0 && row < this->rows_.size() && column >= 0 && column < this->columnCount_); return this->rows_[index.row()].items[index.column()]->flags(); } QStandardItem *getItem(int row, int column) { assert(row >= 0 && row < this->rows_.size() && column >= 0 && column < this->columnCount_); return rows_[row].items[column]; } void deleteRow(int row) { int signalVectorRow = this->getVectorIndexFromModelIndex(row); this->vector_->removeItem(signalVectorRow); } bool removeRows(int row, int count, const QModelIndex &parent) override { if (count != 1) { return false; } assert(row >= 0 && row < this->rows_.size()); int signalVectorRow = this->getVectorIndexFromModelIndex(row); this->vector_->removeItem(signalVectorRow); return true; } protected: virtual void afterInit() { } // turn a vector item into a model row virtual TVectorItem getItemFromRow(std::vector &row, const TVectorItem &original) = 0; // turns a row in the model into a vector item virtual void getRowFromItem(const TVectorItem &item, std::vector &row) = 0; virtual int beforeInsert(const TVectorItem &item, std::vector &row, int proposedIndex) { return proposedIndex; } virtual void afterRemoved(const TVectorItem &item, std::vector &row, int index) { } virtual void customRowSetData(const std::vector &row, int column, const QVariant &value, int role) { } void insertCustomRow(std::vector row, int index) { assert(index >= 0 && index <= this->rows_.size()); this->beginInsertRows(QModelIndex(), index, index); this->rows_.insert(this->rows_.begin() + index, Row(std::move(row), true)); this->endInsertRows(); } void removeCustomRow(int index) { assert(index >= 0 && index <= this->rows_.size()); assert(this->rows_[index].isCustomRow); this->beginRemoveRows(QModelIndex(), index, index); this->rows_.erase(this->rows_.begin() + index); this->endRemoveRows(); } std::vector createRow() { std::vector row; for (int i = 0; i < this->columnCount_; i++) { row.push_back(new QStandardItem()); } return row; } struct Row { std::vector items; boost::optional original; bool isCustomRow; Row(std::vector _items, bool _isCustomRow = false) : items(std::move(_items)) , isCustomRow(_isCustomRow) { } Row(std::vector _items, const TVectorItem &_original, bool _isCustomRow = false) : items(std::move(_items)) , original(_original) , isCustomRow(_isCustomRow) { } }; private: std::vector> headerData_; BaseSignalVector *vector_; std::vector rows_; int columnCount_; // returns the related index of the SignalVector int getVectorIndexFromModelIndex(int index) { int i = 0; for (auto &row : this->rows_) { if (row.isCustomRow) { index--; continue; } if (i == index) { return i; } i++; } return i; } // returns the related index of the model int getModelIndexFromVectorIndex(int index) { int i = 0; for (auto &row : this->rows_) { if (row.isCustomRow) { index++; } if (i == index) { return i; } i++; } return i; } }; } // namespace chatterino