2019-09-08 22:27:57 +02:00
|
|
|
#pragma once
|
|
|
|
|
|
|
|
#include "common/SignalVector.hpp"
|
|
|
|
|
|
|
|
#include <QAbstractTableModel>
|
|
|
|
#include <QStandardItem>
|
|
|
|
#include <boost/optional.hpp>
|
|
|
|
|
|
|
|
#include <pajlada/signals/signalholder.hpp>
|
|
|
|
|
|
|
|
namespace chatterino {
|
|
|
|
|
|
|
|
template <typename TVectorItem>
|
|
|
|
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();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-02-23 19:37:02 +01:00
|
|
|
void initialize(SignalVector<TVectorItem> *vec)
|
2019-09-08 22:27:57 +02:00
|
|
|
{
|
|
|
|
this->vector_ = vec;
|
|
|
|
|
2020-02-23 19:37:02 +01:00
|
|
|
auto insert = [this](const SignalVectorItemEvent<TVectorItem> &args) {
|
2019-09-08 22:27:57 +02:00
|
|
|
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<QStandardItem *> 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;
|
2020-02-23 17:45:59 +01:00
|
|
|
for (const TVectorItem &item : vec->raw())
|
2019-09-08 22:27:57 +02:00
|
|
|
{
|
2020-02-23 19:37:02 +01:00
|
|
|
SignalVectorItemEvent<TVectorItem> args{item, i++, 0};
|
2019-09-08 22:27:57 +02:00
|
|
|
|
|
|
|
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
|
2020-09-25 20:43:45 +02:00
|
|
|
std::vector<QStandardItem *> items = this->rows_[row].items;
|
2019-09-08 22:27:57 +02:00
|
|
|
|
|
|
|
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();
|
|
|
|
}
|
|
|
|
|
2020-02-23 19:37:02 +01:00
|
|
|
SignalVectorModel<TVectorItem> *initialized(SignalVector<TVectorItem> *vec)
|
2020-02-23 19:31:43 +01:00
|
|
|
{
|
|
|
|
this->initialize(vec);
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
|
2019-09-08 22:27:57 +02:00
|
|
|
virtual ~SignalVectorModel()
|
|
|
|
{
|
|
|
|
for (Row &row : this->rows_)
|
|
|
|
{
|
|
|
|
for (QStandardItem *item : row.items)
|
|
|
|
{
|
|
|
|
delete item;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
int rowCount(const QModelIndex &parent) const override
|
|
|
|
{
|
2019-09-18 13:03:16 +02:00
|
|
|
(void)parent;
|
|
|
|
|
2019-09-08 22:27:57 +02:00
|
|
|
return this->rows_.size();
|
|
|
|
}
|
|
|
|
|
|
|
|
int columnCount(const QModelIndex &parent) const override
|
|
|
|
{
|
2019-09-18 13:03:16 +02:00
|
|
|
(void)parent;
|
|
|
|
|
2019-09-08 22:27:57 +02:00
|
|
|
return this->columnCount_;
|
|
|
|
}
|
|
|
|
|
|
|
|
QVariant data(const QModelIndex &index, int role) const override
|
|
|
|
{
|
|
|
|
int row = index.row(), column = index.column();
|
2019-09-18 13:03:16 +02:00
|
|
|
if (row < 0 || column < 0 || row >= this->rows_.size() ||
|
|
|
|
column >= this->columnCount_)
|
|
|
|
{
|
|
|
|
return QVariant();
|
|
|
|
}
|
2019-09-08 22:27:57 +02:00
|
|
|
|
|
|
|
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();
|
2019-09-18 13:03:16 +02:00
|
|
|
if (row < 0 || column < 0 || row >= this->rows_.size() ||
|
|
|
|
column >= this->columnCount_)
|
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
2019-09-08 22:27:57 +02:00
|
|
|
|
|
|
|
Row &rowItem = this->rows_[row];
|
|
|
|
|
2020-09-13 11:08:43 +02:00
|
|
|
assert(this->columnCount_ == rowItem.items.size());
|
|
|
|
|
|
|
|
auto &cell = rowItem.items[column];
|
|
|
|
|
|
|
|
cell->setData(value, role);
|
2019-09-08 22:27:57 +02:00
|
|
|
|
|
|
|
if (rowItem.isCustomRow)
|
|
|
|
{
|
|
|
|
this->customRowSetData(rowItem.items, column, value, role, row);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
int vecRow = this->getVectorIndexFromModelIndex(row);
|
2020-02-23 19:44:13 +01:00
|
|
|
this->vector_->removeAt(vecRow, this);
|
2019-09-08 22:27:57 +02:00
|
|
|
|
|
|
|
assert(this->rows_[row].original);
|
|
|
|
TVectorItem item = this->getItemFromRow(
|
|
|
|
this->rows_[row].items, this->rows_[row].original.get());
|
2020-02-23 19:44:13 +01:00
|
|
|
this->vector_->insert(item, vecRow, this);
|
2019-09-08 22:27:57 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
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();
|
2019-09-18 13:03:16 +02:00
|
|
|
|
|
|
|
if (row < 0 || column < 0 || row >= this->rows_.size() ||
|
|
|
|
column >= this->columnCount_)
|
|
|
|
{
|
|
|
|
return Qt::NoItemFlags;
|
|
|
|
}
|
|
|
|
|
2019-09-08 22:27:57 +02:00
|
|
|
assert(row >= 0 && row < this->rows_.size() && column >= 0 &&
|
|
|
|
column < this->columnCount_);
|
|
|
|
|
2020-09-13 11:08:43 +02:00
|
|
|
const auto &rowItem = this->rows_[row];
|
|
|
|
|
|
|
|
assert(this->columnCount_ == rowItem.items.size());
|
|
|
|
|
|
|
|
return rowItem.items[column]->flags();
|
2019-09-08 22:27:57 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
QStandardItem *getItem(int row, int column)
|
|
|
|
{
|
|
|
|
assert(row >= 0 && row < this->rows_.size() && column >= 0 &&
|
|
|
|
column < this->columnCount_);
|
|
|
|
|
2020-09-13 11:08:43 +02:00
|
|
|
const auto &rowItem = this->rows_[row];
|
|
|
|
|
|
|
|
assert(this->columnCount_ == rowItem.items.size());
|
|
|
|
|
|
|
|
return rowItem.items[column];
|
2019-09-08 22:27:57 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
void deleteRow(int row)
|
|
|
|
{
|
|
|
|
int signalVectorRow = this->getVectorIndexFromModelIndex(row);
|
2020-02-23 19:44:13 +01:00
|
|
|
this->vector_->removeAt(signalVectorRow);
|
2019-09-08 22:27:57 +02:00
|
|
|
}
|
|
|
|
|
2020-09-26 15:11:45 +02:00
|
|
|
bool moveRows(const QModelIndex &sourceParent, int sourceRow, int count,
|
2020-10-04 12:47:23 +02:00
|
|
|
const QModelIndex &destinationParent,
|
|
|
|
int destinationChild) override
|
2020-09-26 15:11:45 +02:00
|
|
|
{
|
|
|
|
if (count != 1)
|
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
assert(sourceRow >= 0 && sourceRow < this->rows_.size());
|
|
|
|
|
|
|
|
int signalVectorRow = this->getVectorIndexFromModelIndex(sourceRow);
|
|
|
|
this->beginMoveRows(sourceParent, sourceRow, sourceRow,
|
|
|
|
destinationParent, destinationChild);
|
|
|
|
|
|
|
|
TVectorItem item =
|
|
|
|
this->getItemFromRow(this->rows_[sourceRow].items,
|
|
|
|
this->rows_[sourceRow].original.get());
|
|
|
|
this->vector_->removeAt(signalVectorRow);
|
|
|
|
this->vector_->insert(
|
|
|
|
item, this->getVectorIndexFromModelIndex(destinationChild));
|
|
|
|
|
|
|
|
this->endMoveRows();
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2019-09-08 22:27:57 +02:00
|
|
|
bool removeRows(int row, int count, const QModelIndex &parent) override
|
|
|
|
{
|
2019-09-18 13:03:16 +02:00
|
|
|
(void)parent;
|
|
|
|
|
2019-09-08 22:27:57 +02:00
|
|
|
if (count != 1)
|
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
assert(row >= 0 && row < this->rows_.size());
|
|
|
|
|
|
|
|
int signalVectorRow = this->getVectorIndexFromModelIndex(row);
|
2020-02-23 19:44:13 +01:00
|
|
|
this->vector_->removeAt(signalVectorRow);
|
2019-09-08 22:27:57 +02:00
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2020-02-20 23:16:46 +01:00
|
|
|
QStringList mimeTypes() const override
|
|
|
|
{
|
|
|
|
return {"chatterino_row_id"};
|
|
|
|
}
|
|
|
|
|
2020-10-04 12:47:23 +02:00
|
|
|
QMimeData *mimeData(const QModelIndexList &list) const override
|
2020-02-20 23:16:46 +01:00
|
|
|
{
|
|
|
|
if (list.length() == 1)
|
|
|
|
{
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check if all indices are in the same row -> single row selected
|
|
|
|
for (auto &&x : list)
|
|
|
|
{
|
|
|
|
if (x.row() != list.first().row())
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
auto data = new QMimeData;
|
|
|
|
data->setData("chatterino_row_id", QByteArray::number(list[0].row()));
|
|
|
|
return data;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool dropMimeData(const QMimeData *data, Qt::DropAction action, int /*row*/,
|
|
|
|
int /*column*/, const QModelIndex &parent) override
|
|
|
|
{
|
|
|
|
if (data->hasFormat("chatterino_row_id") &&
|
|
|
|
action & (Qt::DropAction::MoveAction | Qt::DropAction::CopyAction))
|
|
|
|
{
|
|
|
|
int from = data->data("chatterino_row_id").toInt();
|
|
|
|
int to = parent.row();
|
|
|
|
|
2020-09-26 15:11:45 +02:00
|
|
|
int vectorFrom = this->getVectorIndexFromModelIndex(from);
|
|
|
|
int vectorTo = this->getVectorIndexFromModelIndex(to);
|
|
|
|
|
|
|
|
if (vectorFrom < 0 || vectorFrom > this->vector_->raw().size() ||
|
|
|
|
vectorTo < 0 || vectorTo > this->vector_->raw().size())
|
2020-02-20 23:16:46 +01:00
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (from != to)
|
|
|
|
{
|
2020-09-26 15:11:45 +02:00
|
|
|
this->moveRow(this->index(from, to), from, parent, to);
|
2020-02-20 23:16:46 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// We return false since we remove items ourselves.
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
Qt::DropActions supportedDropActions() const override
|
|
|
|
{
|
|
|
|
return this->vector_->isSorted()
|
|
|
|
? Qt::DropActions()
|
|
|
|
: Qt::DropAction::CopyAction | Qt::DropAction::MoveAction;
|
|
|
|
}
|
|
|
|
|
2019-09-08 22:27:57 +02:00
|
|
|
protected:
|
|
|
|
virtual void afterInit()
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
// turn a vector item into a model row
|
|
|
|
virtual TVectorItem getItemFromRow(std::vector<QStandardItem *> &row,
|
|
|
|
const TVectorItem &original) = 0;
|
|
|
|
|
|
|
|
// turns a row in the model into a vector item
|
|
|
|
virtual void getRowFromItem(const TVectorItem &item,
|
|
|
|
std::vector<QStandardItem *> &row) = 0;
|
|
|
|
|
|
|
|
virtual int beforeInsert(const TVectorItem &item,
|
|
|
|
std::vector<QStandardItem *> &row,
|
|
|
|
int proposedIndex)
|
|
|
|
{
|
2019-09-18 13:03:16 +02:00
|
|
|
(void)item, (void)row;
|
|
|
|
|
2019-09-08 22:27:57 +02:00
|
|
|
return proposedIndex;
|
|
|
|
}
|
|
|
|
|
|
|
|
virtual void afterRemoved(const TVectorItem &item,
|
|
|
|
std::vector<QStandardItem *> &row, int index)
|
|
|
|
{
|
2019-09-18 13:03:16 +02:00
|
|
|
(void)item, (void)row, (void)index;
|
2019-09-08 22:27:57 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
virtual void customRowSetData(const std::vector<QStandardItem *> &row,
|
|
|
|
int column, const QVariant &value, int role,
|
|
|
|
int rowIndex)
|
|
|
|
{
|
2019-09-18 13:03:16 +02:00
|
|
|
(void)row, (void)column, (void)value, (void)role, (void)rowIndex;
|
2019-09-08 22:27:57 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
void insertCustomRow(std::vector<QStandardItem *> 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<QStandardItem *> createRow()
|
|
|
|
{
|
|
|
|
std::vector<QStandardItem *> row;
|
|
|
|
for (int i = 0; i < this->columnCount_; i++)
|
|
|
|
{
|
|
|
|
row.push_back(new QStandardItem());
|
|
|
|
}
|
|
|
|
return row;
|
|
|
|
}
|
|
|
|
|
|
|
|
struct Row {
|
|
|
|
std::vector<QStandardItem *> items;
|
|
|
|
boost::optional<TVectorItem> original;
|
|
|
|
bool isCustomRow;
|
|
|
|
|
|
|
|
Row(std::vector<QStandardItem *> _items, bool _isCustomRow = false)
|
|
|
|
: items(std::move(_items))
|
|
|
|
, isCustomRow(_isCustomRow)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
Row(std::vector<QStandardItem *> _items, const TVectorItem &_original,
|
|
|
|
bool _isCustomRow = false)
|
|
|
|
: items(std::move(_items))
|
|
|
|
, original(_original)
|
|
|
|
, isCustomRow(_isCustomRow)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
private:
|
|
|
|
std::vector<QMap<int, QVariant>> headerData_;
|
2020-02-23 19:37:02 +01:00
|
|
|
SignalVector<TVectorItem> *vector_;
|
2019-09-08 22:27:57 +02:00
|
|
|
std::vector<Row> rows_;
|
|
|
|
|
2020-09-13 11:08:43 +02:00
|
|
|
const int columnCount_;
|
2019-09-08 22:27:57 +02:00
|
|
|
|
|
|
|
// 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
|