How to construct a QModelIndex with valid parent? - qt

I'm trying to unittest my implementation of QAbstractTableModel. I have implemented rowCount(), columnCount() and data() methods.
After instantiating my model, no matter how many nestings deep, the parent index is always invalid:
parent = model->index(0, 0);
i = model->index(0, 0, parent); // i.parent().IsValid() == false!
Now, i is valid. But i.parent() is not.
Even if I do further nesting:
ancestor = model->index(0, 0);
parent = model->index(0, 0, ancestor);
i = model->index(0, 0, parent); // i.parent().IsValid() == false!
even then, i is valid but i.parent() is not.
I have unit tested the rowCount and columnCount methods and I've asserted that the model is a tree model that has one row with, nested, two rows. Also, the column count is nonzero.
Why is my parent index always invalid?

It's a table. It's not supposed to be a tree. Because of that, the parent will always be invalid. The QAbstractTableModel::index implementation always sets an invalid parent, and it's supposed to.
Your expectations apply to a tree model, not a table model. And they only apply to a tree model if the given parent element has children. Your test wrongly assumes that the parent it is using has children, when it has none. You can easily check that: model->hasChildren(parent) will always return false for a table. Trying to create an index with a childless parent is undefined. Ideally your model should assert on it. Thus, your test would be generally wrong for a tree too.
If you want to implement a tree, derive from QAbstractItemModel. You'll then be forced to correctly implement bool hasChildren(const QModelIndex& parent) const - that's the method that the tree view (and your tests!) should use to know whether it's valid to request an index for a given parent's child.
Generally speaking, if model.hasChildren(parent) == false then you're never supposed to call model.index(row, col, parent). In fact, your model should assert that it is so:
QModelIndex MyModel::index(int row, int col, const QModelIndex & parent) {
Q_ASSERT(hasChildren(parent));
Q_ASSERT(row >= 0 && row < rowCount(parent));
Q_ASSERT(col >= 0 && col < columnCount(parent));
void * ptr = ...; // or quintptr ptr = ...;
return createIndex(row, col, ptr);
}

Related

How do you assign a pointer to an existing variable in a std::vector?

I'm trying to create a tree-like hierarchy of nodes.
Each Node is stored in a std::vector and has an int value and a Node* parent, pointing to another Node in the vector, in this case the most recently added element. It looks like the parent values are uninitialised but not sure how this is the case. Any help would be appreciated.
struct Node
{
Node(int v = -1, Node* p = nullptr) : value(v), parent(p) {}
int value;
Node* parent;
};
int main()
{
std::vector<Node> m_vector;
m_vector.push_back(Node(1));
Node n1 = m_vector.back();
m_vector.push_back(Node(2, &m_vector[0]));
}
// m_vector[1].parent->value = -572662307.
Short answer: !!! DON'T !!!
It is entirely possible that when you call push_back, if the array needs to be resized, every pointer will now be invalid. If you erase an element from the start of the array, every pointer value you store there will be invalid.
In this particular instance, you'd be better off using an integer index. That will still cause issues when you remove elements from the vector (i.e. decrement each index that is greater than the index you are erasing), but at least you wont have issues when you are adding the elements.

combine QAbstractItemModels

I have a map = std::map<std::string, myItemModel *>, where myItemModel inherits QAbstractItemModel.
I want now to combine all myItemModel in one single myItemModel (every other item model would be fine too).
So that there is one big myItemModel.
Is there a 'qt-way' to do this?
It can be done, but it's not trivial. It depends on your implementation of QAbstractItemModel and that's why it hasn't been done in Qt.
Here are steps to implement a model which is a collection of models:
Create a new class inherited from QAbstractItemModel
Add methods to add other models to that
Process all signals from child models which contains indexes (you'll need to change them, look #10)
Forward all signals which doesn't contain indexes.
Implement rowCount and provide a sum of all models rows.
Implement columnCount and provide a number of columns in your models.
Implement index, return createIndex(row, column, NULL);
Implement parent, return QModelIndex(); I hope your models are not trees
Implement data,setData etc. addressing calls to the right model. Use methods from #10 to convert indexes.
Create methods to convert a child model index to a base model index and back.
Example (indexes):
BaseModel ChildModel1 ChildModel2
0,0 0,0
1,0 1,0
2,0 0,0
3,0 1,0
4,0 2,0
p.s. Think about creating a cache of indexes mapping.
This is an example of a method to convert a base model index to a child model index:
const QModelIndex childModelIndex(const QModelIndex& baseModelIndex) const
{
if (!baseModelIndex.isValid())
{
return QModelIndex();
}
int count = 0;
const int row = baseModelIndex.row();
for (QList<QAbstractTableModel*>::const_iterator it = m_models.begin();
it != m_models.end(); it++)
{
const int currentCount = (*it)->rowCount();
if (row >= count && row < count + currentCount)
{
return (*it)->index(row - count, 0);
}
count += currentCount;
}
ASSERT(false);
return QModelIndex();
}
This is an example of a method to convert a child model index to a base model index:
QModelIndex baseModelIndex(const QModelIndex& childModelIndex) const
{
int row = childModelIndex.row();
for (QList<QAbstractTableModel*>::const_iterator it = m_models.begin();
it != m_models.end(); it++)
{
if (childModelIndex.model() == *it)
{
return index(row, ind.column());
}
row += (*it)->rowCount();
}
return QModelIndex();
}
The KDE Frameworks project contains a module called KItemModels, which includes a class called KConcatenateRowsProxyModel. It does exactly what you want. The library is released every month as part of the [KDE Frameworks releases], the code is continuously unit tested on https://build.kde.org. All this is licensed under LGPL v2 or later.

Should QAbstractItemModel::index(row, column, parent) check for invalid inputs?

Subclassing QAbstractItemModel, I have implemented my own index() method as required. I currently check for valid inputs, but I'm wondering if that's correct. I'm wondering if it's ever valid to create an index for a non-existent piece of data? Perhaps while inserting a row or column?
Code:
QModelIndex LicenseDataModel::index(int row, int column, const QModelIndex & /*parent*/) const
{
/// TODO: Is this necessary? Should we avoid creating invalid indexes? Or could this
/// be a bug?
if (validRowColumn(row, column))
return createIndex(row, column);
return QModelIndex();
}
[ I have a better answer. :-) ]
Although I merely repeat what Giuseppe D'Angelo from KDAB has to say about this...
You must distinguish between an invalid and an ill-formed QModelIndex:
About invalid indices (from Navigation and model index creation in Qt's Model/View Programming guide):
QAbstractItemModel::parent(): Provides a model index corresponding to the parent of any given child item. If the model index specified corresponds to a top-level item in the model, or if there is no valid parent item in the model, the function must return an invalid model index, created with the empty QModelIndex() constructor.
This explains the meaning of index.isValid():
A valid index refers to an existing item, the invalid index refers to the root of all items.
Giuseppe D'Angelo first notes that an invalid index (with .isValid() returning false) is still a valid input to functions like rowCount(), hasChildren() etc.
But a QModelIndex can also be ill-formed. It can have a non-existing row or column index, and it can even be from a different model. And QModelIndex::isValid() does not check that.
Giuseppe D'Angelo says:
I personally maintain quite a strong point of view about this issue: passing such indices is a violation of the API contract. A model should never be assumed to be able to handle illegal indices. In other words, in my (not so humble) opinion, the QAbstractItemModel API has a narrow contract.
But since All Programmers Make Mistakes (TM), it facilitates the debugging process to check indices for being well-formed. For this purpose, Giuseppe D'Angelo introduced QAbstractItemModel::checkIndex() into Qt 5.11.
If you're still using a lower Qt version, you can simply write that function yourself.
[ If anyone has a better answer, I'll gladly accept it. ]
Looking at the source for QListWidget, it seems that checking the inputs is what Qt does itself:
QModelIndex QListModel::index(int row, int column, const QModelIndex &parent) const
{
if (hasIndex(row, column, parent))
return createIndex(row, column, items.at(row));
return QModelIndex();
}
It also appears that I didn't know about hasIndex() which will do what my validRowColumn() method does.
bool QAbstractItemModel::hasIndex(int row, int column, const QModelIndex &parent) const
{
if (row < 0 || column < 0)
return false;
return row < rowCount(parent) && column < columnCount(parent);
}
For that matter, I'm not sure why the documentation uses index.isValid() everywhere when hasIndex(index.row(), index.column(), index.parent()) would be more appropriate. Then, I'm sure a hasIndex(QModelIndex &) method would be added. hasIndex() does the same check as QModelIndex::isValid() and more:
inline bool isValid() const { return (r >= 0) && (c >= 0) && (m != 0); }

How can I get the size/depth/branches of a QTreeView?

I cannot find any method to get the size, depth, and number of branches of a QTreeView.
I do not mean the size of its graphical representation, but the size of the data stored in the tree.
You should work with data model, not with view.
To get number of root branches you should use rowColumn( QModelIndex() ) method.
To get depth of any index (item) you should pick up parent index, untill it stay valid. Pseudo-code:
QModelIndex index = /*your item*/;
int depth = 0;
while ( index.parent().isValid() )
{
index = index.parent();
depth++;
}
qDebug() << depth;
It is not clear, what you mean by "size" of item? Is it length of text data? You should understand, that it depends on your implementation of data model. Because each item may have a lot of roles.

Why doesn't QTableView row count update?

I created a QAbstractTableModel called PresetTableModel, and connected it to a QTableView. I implemented the rowCount, columnCount, and data functions. Everything works if I have rowCount return a fixed number, but as soon as I get it to return a variable value, the list view doesn't show any rows. The debug statement in the code below shows the size value starting at 0, but changing to 9 once the list gets populated.
int PresetTableModel::rowCount(const QModelIndex & /*parent*/) const
{
qDebug() << preset_list.count();
return preset_list.size();
}
Is there something else I need to do to force it to update the number of rows?
When modifying the underlying data, you must use the model's notification mechanism to notify the views. E.g, when appending data:
beginInsertRows(QModelIndex(), preset_list.size(), preset_list.size()+1); //notify that two rows will be appended (rows size() and size() + 1)
preset_list.append(something);
preset_list.append(somethingelse);
endInsertRows(); //notify views that you're done with modifying the underlying data
Accordingly, you have to call beginRemoveRows() and endRemoveRows() when removing rows, and emit dataChanged() when existing entries are updated.
On a side note, your rowCount() method should read
if (!parent.isValid())
return preset_list.size(); //top-level: return list size
else
return 0; //list item: no further children (flat list)
to limit the depth. Otherwise each item in the list has again preset_list.size() entries.
i use:
void refresh() {
emit dataChanged(index(0, 0),
index(rowCount(), columnCount())); // update whole view
emit layoutChanged();
}

Resources