How to associate QModelIndex with a new row? - qt

I've cooked up a QAbstractListModel whose model indexes contain a pointer I absolutely needed in order to process data. I add the data like so:
void PointListModel::addPoint(int frameNumber, QPoint const& pos)
{
PointItem *pointItem = new PointItem( frameNumber, pos );
QModelIndex newRow = this->createIndex( m_points.count(), 0, pointItem );
qDebug() << newRow.internalPointer();
beginInsertRows( newRow, m_points.count(), m_points.count() );
m_points.insert( m_points.count( ), pointItem );
endInsertRows();
emit pointAdded( pointItem, pos );
}
It was only later that I realized that the argument to beginInsertRows is asking for the parent model index of the new row, not the new row's actual model index.
So, at this point in time, Qt has given me no way of supplying a QModelIndex to associate with this particular row. How do I create my own model index for this new row?

Okay, I'm rewriting my answer as after some research I've found out that I got it wrong.
You shouldn't do anything special to create a new index when you add new data. You code should look like this:
PointItem *pointItem = new PointItem( frameNumber, pos );
// assume you insert a top level row, having no parent
beginInsertRows( QModelIndex(), m_points.count(), m_points.count() );
m_points.insert( m_points.count( ), pointItem );
endInsertRows();
Then you should implement the index() method which will create indexes on demand and the parent() method which will determine the parent of some index, but since you have a list model, it should probably always just return QModelIndex(). Here is a good article about creating custom models.
Here is a complete example of a working QAbstractListModel:
class MyModel: public QAbstractListModel {
Q_OBJECT
public:
virtual QModelIndex index(int row, int column = 0,
const QModelIndex &parent = QModelIndex()) const;
virtual int rowCount(const QModelIndex &parent = QModelIndex()) const;
virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const;
void add(int i);
private:
QList<int> list;
};
void MyModel::add(int i)
{
beginInsertRows(QModelIndex(), list.size(), list.size());
list.append(i);
endInsertRows();
}
QModelIndex MyModel::index(int row, int column,
const QModelIndex &parent) const
{
return hasIndex(row, column, parent) ? createIndex(row, column, (void*)&list[row])
: QModelIndex();
}
int MyModel::rowCount(const QModelIndex &parent) const
{
if (parent.isValid())
return 0;
return list.size();
}
QVariant MyModel::data(const QModelIndex &index,
int role) const
{
if (!index.isValid())
return QVariant();
if (role != Qt::DisplayRole)
return QVariant();
return QVariant(QString::number(*static_cast<int*>(index.internalPointer())));
}

I've cooked up a QAbstractListModel whose model indexes contain a pointer I absolutely needed in order to process data.
If you start with wrong requirements, you end up with wrong solutions :)
A list model is simple enough so that you don't need more than the QModelIndex's row() to uniquely define the data the index addresses.
So, given a QModelIndex mi, when you before did
PointItem * item = static_cast<PointItem*>(mi.internalPointer());
you can instead do
PointItem * item = plm->pointItemFromIndex(mi);
where plm is your PointListModel. If you don't have a pointer to it lying around when you need to access the PointItem, you can reconstruct it like this:
PointItemModel * plm = qobject_cast<PointItemModel*>(mi.model());
// check for !plm here (!mi.isValid() || qobject_cast fails)
In turn, PointListMode::pointItemFromIndex() would do the actual work:
PointItem * PointListMode::pointItemFromindex(const QModelIndex &mi) const {
return mi.isValid() ? m_points[mi.row()] : 0 ;
}
This is the most important thing to realize when working with QAbstractListModel in Qt: Mentally replace QModelIndex with int row, ignore everything else it has (an invalid QModelIndex has row() == -1).
Same for QAbstractTableModel: mentally reduce the QModelIndex to int row, int column. Forget everything else.
The only time you need the full QModelIndex (including its internalPointer() or internalId() is when you implement a tree model (QAbstractItemModel).

Related

how to implement a QAbstractItemModel for an already existing tree-like data structure without copy?

After reading the docs and examples of QAbstractItemModel and QModelIndex, I am still confused on how to properly implement the model for a QTreeView.
Since I want to provide a model for an existing hierarchical data structure, I'm avoiding using QTreeWidget or QStandardItemModel and the related issues with data duplication and synchronization. However, I failed to implement a working item model (too many issues, it is not useful to post my code).
After reading this answer it seems clear that QModelIndex does not contain hierarchical information, but simply relies on the model to tell the parent of a given index. Consequently it does not seem possible to provide an abstract tree model for an existing data structure without defining at least another helper class for storing such relation. But still, I cannot implement properly the model.
Suppose the data structure is this simple one:
struct Property {
QString name;
QString value;
};
struct Node {
QString name;
QVector<Property> properties;
};
struct Track {
int length;
QString channel;
};
struct Model {
QVector<Node> nodes;
QVector<Track> tracks;
};
where Model is the toplevel one, and it resembles a tree.
The tree displayed in the QTreeView could look like this:
Model
├─Nodes
│ ├─Node "node1"
│ │ └─Properties
│ │ ├─Property property1 = value1
│ │ └─Property property2 = value2
│ └─Node "node2"
│ └─Properties
│ └─Property property1 = someValue
└─Tracks
├─Track 1, ...
├─Track 2, ...
└─Track 3, ...
How the QAbstractItemModel subclass should be implemented to access the existing data without copy?
Here's my solution to the problem.
First of all, my initial guess that QModelIndex is incapable of storing the parent-child relationship is correct. In fact the method QModelIndex::parent simply calls QAbstractItemModel::parent, and the task of implementing the parent method is left to the model class. When the underlying model is a proper tree, pointer to tree nodes can be stored in the QModelIndex class, but in my case we are dealing with a "virtual" tree and this relationship is not available. Thus we are forced to introduce some kind of extra storage to be able to tell where we are in the tree. If QModelIndex natively supported having a pointer to the parent index, this problem would have been solved much more easily. But since QModelIndex is a value class, we cannot have a pointer to parent, but rather we have to store all the parent indices inside the QModelIndex class, and maybe the Qt developers had some good way to not do so. So I stored a QVector<QModelIndex> in the internal-pointer field of QModelIndex. There are some things to take care of, like avoiding allocating more than necessary of such indices, and also to remember freeing the memory when those are no more needed (we can't use QObject hierarchy here). There may be additional problems to take care of when the model is read-write, but in this case I'm dealing with a read-only model.
My implementation follows. Methods rowCount and data define this specific virtual tree. The other methods can be abstracted away in a class that can be re-used.
class MyModel : public QAbstractItemModel
{
Q_OBJECT
private:
struct IndexData
{
QVector<QModelIndex> parents;
};
public:
explicit MyModel(QObject *parent = nullptr);
~MyModel();
QVariant data(const QModelIndex &index, int role) const override;
QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override;
QModelIndex parent(const QModelIndex &index) const override;
protected:
IndexData * indexData(const QModelIndex &index) const;
QList<int> indexPath(const QModelIndex &index) const;
QString indexString(const QModelIndex &index) const;
QString indexString(int row, int column, const QModelIndex &parent) const;
public:
int indexDepth(const QModelIndex &index) const;
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
int columnCount(const QModelIndex &parent = QModelIndex()) const override;
Qt::ItemFlags flags(const QModelIndex &index) const override;
private:
QMap<QString, IndexData*> indexData_;
Model model;
};
implementation:
MyModel::MyModel(QObject *parent)
: QAbstractItemModel(parent)
{
model.nodes.resize(2);
model.nodes[0].name = "node1";
model.nodes[0].properties.resize(2);
model.nodes[0].properties[0].name = "property1";
model.nodes[0].properties[0].value = "value1";
model.nodes[0].properties[1].name = "property2";
model.nodes[0].properties[1].value = "value2";
model.nodes[1].name = "node2";
model.nodes[1].properties.resize(1);
model.nodes[1].properties[0].name = "property1";
model.nodes[1].properties[0].value = "someValue";
model.tracks.resize(3);
model.tracks[0].length = 2;
model.tracks[0].channel = "A";
model.tracks[1].length = 4;
model.tracks[1].channel = "B";
model.tracks[2].length = 3;
model.tracks[2].channel = "C";
}
MyModel::~MyModel()
{
for(auto v : indexData_) delete v;
}
QVariant MyModel::data(const QModelIndex &index, int role) const
{
if(!index.isValid() || role != Qt::DisplayRole) return {};
int d = indexDepth(index);
auto path = indexPath(index);
if(d == 1) return "Model";
if(d == 2 && path[0] == 0 && path[1] == 0) return "Nodes";
if(d == 2 && path[0] == 0 && path[1] == 1) return "Tracks";
if(d == 3 && path[0] == 0 && path[1] == 0) return QString("Node \"%1\"").arg(model.nodes[path[2]].name);
if(d == 4 && path[0] == 0 && path[1] == 0) return "Properties";
if(d == 5 && path[0] == 0 && path[1] == 0 && path[3] == 0) return QString("Property %1 = %2").arg(model.nodes[path[2]].properties[path[4]].name, model.nodes[path[2]].properties[path[4]].value);
if(d == 3 && path[0] == 0 && path[1] == 1) return QString("Track %1...").arg(index.row() + 1);
return {};
}
QModelIndex MyModel::index(int row, int column, const QModelIndex &parent) const
{
QString dataKey = indexString(row, column, parent);
auto it = indexData_.find(dataKey);
IndexData *data;
if(it == indexData_.end())
{
data = new IndexData;
const_cast<MyModel*>(this)->indexData_.insert(dataKey, data);
if(parent.isValid())
{
data->parents.append(parent);
data->parents.append(indexData(parent)->parents);
}
}
else
{
data = it.value();
}
return createIndex(row, column, data);
}
QModelIndex MyModel::parent(const QModelIndex &index) const
{
if(!index.isValid()) return {};
auto data = indexData(index);
if(data->parents.empty()) return {};
return data->parents.at(0);
}
MyModel::IndexData * MyModel::indexData(const QModelIndex &index) const
{
if(!index.internalPointer()) return nullptr;
return reinterpret_cast<IndexData*>(index.internalPointer());
}
QList<int> MyModel::indexPath(const QModelIndex &index) const
{
QList<int> path;
auto data = indexData(index);
for(int i = data->parents.size() - 1; i >= 0; i--)
path.push_back(data->parents[i].row());
path.push_back(index.row());
return path;
}
QString MyModel::indexString(const QModelIndex &index) const
{
return indexString(index.row(), index.column(), index.parent());
}
QString MyModel::indexString(int row, int column, const QModelIndex &parent) const
{
QString pre = parent.isValid() ? indexString(parent) + "." : "";
return pre + QString("[%1,%2]").arg(row).arg(column);
}
int MyModel::indexDepth(const QModelIndex &index) const
{
if(!index.isValid()) return 0;
return 1 + indexDepth(index.parent());
}
int MyModel::rowCount(const QModelIndex &parent) const
{
if(!parent.isValid()) return 1; // root item
int d = indexDepth(parent);
auto path = indexPath(parent);
//if(d == 0) return 1; // root item
if(d == 1) return 2;
if(d == 2 && path[0] == 0 && path[1] == 0) return model.nodes.size();
if(d == 2 && path[0] == 0 && path[1] == 1) return model.tracks.size();
if(d == 3 && path[0] == 0 && path[1] == 0) return 1;
if(d == 4 && path[0] == 0 && path[1] == 0 && path[3] == 0) return model.nodes[path[2]].properties.size();
return 0;
}
int MyModel::columnCount(const QModelIndex &parent) const
{
return 1;
}
Qt::ItemFlags MyModel::flags(const QModelIndex &index) const
{
if(index.isValid()) return Qt::ItemIsEnabled | Qt::ItemIsSelectable;
return {};
}
You are incorrect, you can totally expose any tree structure as QAbstractItemModel without any additional structuring information. You do however have to bend the rules and abuse things a little. You can in fact have a perfectly usable model with an implementation of parent() that returns a default constructed invalid index, while it is an abstract method and it has to be implemented, it doesn't really need to do actually do anything meaningul.
As you already mentioned - the index does give you an opaque pointer to store a data reference. And since tree models have a fixed column count of 1, this leaves the column in the index free to use as some typeid or index. Now you have a way of knowing what each of those opaque pointers actually points to, so you can select the proper code path to retrieve row count and data. Those are the ones that you really do need to implement. Most likely index() too if you want to use some stock model view, but if you explicitly request children indices in a parent delegate, you can even get away with a dud implementation for index() as well.
Now, if your tree nodes do not keep references to their parents, obviously you will not be able to go down the tree directly. What you can do tho is, whatever your current index is, do not have it just free floating, but keep a stack of all the parent indices up from the root node. And now you can go down, by simply keeping breadcrumbs as you went up. You may not even need an explicit stack, in most cases you will be able to get the relevant information to construct the parent index from the parent delegate.

QListView internal move without serialization

I'm trying to support rearranging a list of large objects in a QListView. It seems that the model/view framework insists on serializing the items which, in this case, is highly inefficient. I've tried to shorten things by using shared_ptr's, so only one bit of data needs passing, but even so, it feels very messy and is not at all reliable.
Worse still, during the move I notice the serialization functions are called repeatedly (dozens/hundreds of times). Surely they should only be called once each.
I'm sure I'm missing a trick, what's the correct way to achieve simple, internal move, list ordering without heavy serialize overhead?
class Big
{
public:
Big();
~Big();
...
};
using BigPtr = std::shared_ptr<Big>;
Q_DECLARE_METATYPE(BigPtr) // and qRegisterMetaTypeStreamOperators
// Required serialization operators
// This is very ugly, and what I want rid of...
QDataStream &operator<<(QDataStream &out, const BigPtr &big)
{
qDebug() << "Out <<";
out << (quint64)big.get();
return out;
}
// ...This too...
QDataStream &operator>>(QDataStream &in, BigPtr &big)
{
qDebug() << "In >>";
quint64 raw = 0;
in >> raw;
li.reset((LayerPtr::element_type *)raw);
return in;
}
class BigModel : public QAbstractListModel
{
Q_OBJECT
public:
BigModel (std::vector<BigPtr> &bigs, QObject *parent = Q_NULLPTR);
int rowCount(const QModelIndex &parent = QModelIndex()) const;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const;
Qt::ItemFlags flags(const QModelIndex &index) const; // Qt::ItemIsDragEnabled or Qt::ItemIsDropEnabled depending on valid index
Qt::DropActions supportedDropActions() const; // return Qt::MoveAction
bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole);
bool insertRows(int row, int count, const QModelIndex &parent = QModelIndex());
bool removeRows(int row, int count, const QModelIndex &parent = QModelIndex());
private:
std::vector<BigPtr> &_bigs;
};
...
// dialog setup...
ui->listView->setSelectionMode(QAbstractItemView::SingleSelection);
ui->listView->setSelectionBehavior(QAbstractItemView::SelectRows);
ui->listView->setEditTriggers(QAbstractItemView::DoubleClicked);
ui->listView->setDragEnabled(true);
ui->listView->setDragDropMode(QAbstractItemView::InternalMove);
ui->listView->setDropIndicatorShown(true);
ui->listView->viewport()->setAcceptDrops(true);
EDIT:
After more head scratching, I decided to overload the mimeData and dropMimeData functions and sub-class QMimeData to carry the BigPtr directly.
class BigMimeData : public QMimeData
{
Q_OBJECT
public:
BigMimeData(const QVariant &_big)
: big(_big)
{ }
bool hasFormat(const QString &/*mimeType*/) const { return true; }
QVariant big;
};
...
QMimeData *BigModel::mimeData(const QModelIndexList &indexes) const
{
if(indexes.count() > 0) {
return new BigMimeData(indexes.at(0).data());
}
return Q_NULLPTR;
}
bool BigModel::dropMimeData(const QMimeData *data, Qt::DropAction action,
int row, int /*column*/, const QModelIndex &parent)
{
if(!data || !(action == Qt::CopyAction || action == Qt::MoveAction)) {
return false;
}
// start of list
if(row == -1) {
row = rowCount(parent);
}
auto dragged = static_cast<const BigMimeData *>(data);
// setData with stored value
insertRow(row, parent);
setData(index(row, 0, parent), dragged->big);
return true;
}
Which works, but seriously!? All that just to enable simple list reordering in the UI? It can't be this complex surely?
BTW, a lot of these answer seem to be focusing on the underlying data structure, that's not the question. The problem is that simple internal move drag/drop rearrangement in a list control seem to be ridiculously complex in QListView because it requires costly serialization. It's a UI/Widgets/MVC question.

Why is MyModel::data() not being called (subclassing QSqlQueryModel)

Hi I can't figure out why my data() function is never called when populating QTableView
I subclassed QSqlQueryModel. The header is like:
class TicketModel : public QSqlQueryModel
{
Q_OBJECT
public:
explicit TicketModel(QObject *parent = 0);
QVariant data(const QModelIndex &index, int role);
QVariant headerData(int section, Qt::Orientation orientation, int role) const;
};
In the main window I set my model to the table
TicketModel *model = new TicketModel();
QSortFilterProxyModel *proxyModel = new QSortFilterProxyModel(this);
proxyModel->setSourceModel(model);
QSqlQuery *query = _tf->search(1);
model->setQuery(*query);
_ui->dashTable->setModel(proxyModel); // <<<<<<<<<<<<<< I setting model here too, didn't work
_ui->dashTable->setSortingEnabled(true);
_ui->dashTable->horizontalHeader()->setVisible(true);
_ui->dashTable->setSelectionBehavior(QAbstractItemView::SelectRows);
The TicketModel::headerData(...) is called but TicketModel::data(...) is never called when the table is created. Why? How can I get it to be called?
I hope I just overlooked something simple but I have been trying for a few hours to figure it out.
Thanks for the help!
You've got the signature wrong. You need a const.
QVariant data( const QModelIndex & index, int role = Qt::DisplayRole ) const

Set color to a QTableView row

void MyWindow::initializeModelBySQL(QSqlQueryModel *model,QTableView *table,QString sql){
model = new QSqlQueryModel(this);
model->setQuery(sql);
}
With this method i can set a QSQlQueryModels to my QTableviews.
But How i can set color to a row based on a cell value?
The view draws the background based on the Qt::BackgroundRole role of the cell which is the QBrush value returned by QAbstractItemModel::data(index, role) for that role.
You can subclass the QSqlQueryModel to redefine data() to return your calculated color, or if you have Qt > 4.8, you can use a QIdentityProxyModel:
class MyModel : public QIdentityProxyModel
{
QColor calculateColorForRow(int row) const {
...
}
QVariant data(const QModelIndex &index, int role)
{
if (role == Qt::BackgroundRole) {
int row = index.row();
QColor color = calculateColorForRow(row);
return QBrush(color);
}
return QIdentityProxyModel::data(index, role);
}
};
And use that model in the view, with the sql model set as source with QIdentityProxyModel::setSourceModel.
OR
You can keep the model unchanged and modify the background with a delegate set on the view with QAbstractItemView::setItemDelegate:
class BackgroundColorDelegate : public QStyledItemDelegate {
public:
BackgroundColorDelegate(QObject *parent = 0)
: QStyledItemDelegate(parent)
{
}
QColor calculateColorForRow(int row) const;
void initStyleOption(QStyleOptionViewItem *option,
const QModelIndex &index) const
{
QStyledItemDelegate::initStyleOption(option, index);
QStyleOptionViewItemV4 *optionV4 =
qstyleoption_cast<QStyleOptionViewItemV4*>(option);
optionV4->backgroundBrush = QBrush(calculateColorForRow(index.row()));
}
};
As the last method is not always obvious to translate from C++ code, here is the equivalent in python:
def initStyleOption(self, option, index):
super(BackgroundColorDelegate,self).initStyleOption(option, index)
option.backgroundBrush = calculateColorForRow(index.row())
Your best bet is to define a custom model (QAbstractTableModel subclass). You probably want to have a QSqlQueryModel as a member in this custom class.
If it's a read-only model, you need to implement at least these methods:
int rowCount(const QModelIndex &parent) const;
int columnCount(const QModelIndex &parent) const;
QVariant data(const QModelIndex &index, int role) const;
and for well behaved models also
QVariant headerData(int section, Qt::Orientation orientation, int role) const;
If you need the model to be able to edit/submit data, things get a bit more involved and you will also need to implement these methods:
Qt::ItemFlags flags(const QModelIndex &index) const;
bool setData(const QModelIndex &index, const QVariant &value, int role=Qt::EditRole);
bool insertRows(int position, int rows, const QModelIndex &index=QModelIndex());
bool removeRows(int position, int rows, const QModelIndex &index=QModelIndex());
What will actually change a row appearance lies in the return value of this method:
QVariant data(const QModelIndex &index, int role) const;
A dumb example:
QVariant MyCustomModel::data(const QModelIndex &index, int role) const
{
if ( !index.isValid() )
return QVariant();
int row = index.row();
int col = index.column();
switch ( role )
{
case Qt::BackgroundRole:
{
if(somecondition){
// background for this row,col is blue
return QVariant(QBrush (QColor(Qt::blue)));
}
// otherwise background is white
return QVariant(QBrush (QColor(Qt::white)));
}
case Qt::DisplayRole:
{
// return actual content for row,col here, ie. text, numbers
}
case Qt::TextAlignmentRole:
{
if (1==col)
return QVariant ( Qt::AlignVCenter | Qt::AlignLeft );
if (2==col)
return QVariant ( Qt::AlignVCenter | Qt::AlignTrailing );
return QVariant ( Qt::AlignVCenter | Qt::AlignHCenter );
}
}
}

QComboBox with NULL support

I'm using QComboBox to select unit (an arbitrary property of the object) from Units table. The problem is that object in my data model can have no unit (NULL in the table), in which case QComboBox shows value happened on top of the list. Select 'none' is not possible.
What do you suggest to add NULL support? I have few versions:
Insert special record in Units table named '--' or 'N/A'. Not exactly NULL - will have its own id.
Set items in QComboBox and update model manually. Possible but tedious - goodbye automatic update of unit list.
What else is possible - subclassing QComboBox (overriding what)? I don't see anything similar to setEditorData/setModelData like in QAbstractItemDelegate to control items.
You can subclass the model, so that data will return a special value for NULL and then setData will check for the special value and substitute a NULL.
Example sketch of code:
class MyModel : public QSqlTableModel
{
Q_OBJECT
public:
MyModel();
virtual QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const;
virtual bool setData(const QModelIndex &index, const QVariant &value, int role);
};
QVariant MyModel::data(const QModelIndex& idx, int role)
{
QVariant var = QSqlTableModel::data(idx, role);
if (var.isNull())
var = QVariant(QString("NULL"));
return var;
}
bool MyModel::setData(const QModelIndex& idx, const QVariant& value, int role)
{
QVariant var(value);
if (var == QString("NULL"))
var == QVariant(record().field(idx.column()).type());
return QSqlTableModel::setData(idx, var, role);
}

Resources