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

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); }

Related

How to construct a QModelIndex with valid parent?

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);
}

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.

QTableView: how to edit non-editable cells in the program?

How should this be done by using the model->setData() method call?
I have derived a class called "MyStandardItemModel" from QStandardItemModel. I have made my third and fourth columns non-editable by overriding the protected virtual flags method. This is how it goes:
#define TX_PACKET_COLUMN (4u)
#define RX_PACKET_COLUMN (5u)
Qt::ItemFlags MyStandardItemModel::flags(const QModelIndex& index) const
{
if (index.column() == TX_PACKET_COLUMN || index.column() == RX_PACKET_COLUMN)
{
return (QStandardItemModel::flags(index) & ~Qt::ItemIsEditable);
}
else
{
return QStandardItemModel::flags(index);
}
}
...
//Set model
ui->testCaseTableView->setModel(model);
Having this done, I am not able to edit the cells in the third and fourth column.
Now, I want that when I double click on these cells, a pop-up dialog comes up. I will modify some data in the editable field of that dialog, and then copy it back to the non editable cells inside the code.
I tried to just write a doubleclick() handler for the QTreeView and just copy some data to the cells just to see if it is possible to copy data to the non-editable cells.
This operation is failing, and the data is not written into the non-editable cells.
Here you can find the double click handler:
void MainWindow::on_testCaseTableView_doubleClicked(const QModelIndex &index)
{
QVariant variant;
variant.toString() = "AA";
if((index.column() == TX_PACKET_COLUMN)||(index.column() == RX_PACKET_COLUMN))
{
model->setData(index, variant); // set new value
}
}
The setData(..) operation is clearing the already written data in the cells, but string "AA" is not getting written. Please suggest how to copy some data to non-editable cells inside the code.
QVariant set is empty. Nothing needs to be wrong in your model. Error is on this line:
variant.toString() = "AA";
change to:
QVariant variant("AA"); // just for testing anyway
As I indicated in my comment, you have to fix this first issue:
instead of:
QVariant variant;
variant.toString() = "AA";
you should write
QVariant variant = QLatin1String("AA");
In general, you would look into the setData(...) implementation for such cases whether you emit the data changed signal properly and so forth, but here you are entering a prior issue which can lead to problems, so let us fix that.
Note, you should use QLatin1String to avoid the unnecessary explicit conversion from raw char* to QString. This is a good practice in general, and this is available with Qt 4 as well as Qt 5.
Although, you could also use the QStringLiteral macro for creating a QString very efficiently with template magic out of your raw literal, but that requires C++11.

What's the right way to remove multiple indices for a QStandardItemModel?

I was trying to remove all selected indices of a QTableView,
Now I use:
foreach (const QModelIndex & idx, model->selectionModel()->selectedIndexes())
{
model->removeRow (idx.row()); // Obviously bug
}
There's a obvious problem that once you remove the row, the row id is invalidated, w
As there's no function that takes directly the index (or does the index act like a iterator that will get invalidated when data changed?), I don't know what to do.
There is QPersistanceModelIndex class which keeps valid state of index. I tried and it seems to be working.
QList<QPersistentModelIndex> indexes;
foreach (const QModelIndex &i, ui->tableView->selectionModel()->selectedIndexes())
indexes << i;
foreach (const QPersistentModelIndex &i, indexes)
ui->tableView->model()->removeRow(i.row());
I hope it will help.

QFileSystemModel sorting DirsFirst

How do you do to sort a QFileSystemModel with QDir::DirsFirst like in QDirModel?
The QFileSystemModel does not have a setSorting method.
Maybe somebody will need this. I have implemented directories first sorting using QSortFilterProxyModel for QFileSystemModel as Kuba Ober mention in comment.
Might be not perfect yet, but still right direction.
bool MySortFilterProxyModel::lessThan(const QModelIndex &left, const QModelIndex &right) const
{
// If sorting by file names column
if (sortColumn() == 0) {
QFileSystemModel *fsm = qobject_cast<QFileSystemModel*>(sourceModel());
bool asc = sortOrder() == Qt::AscendingOrder ? true : false;
QFileInfo leftFileInfo = fsm->fileInfo(left);
QFileInfo rightFileInfo = fsm->fileInfo(right);
// If DotAndDot move in the beginning
if (sourceModel()->data(left).toString() == "..")
return asc;
if (sourceModel()->data(right).toString() == "..")
return !asc;
// Move dirs upper
if (!leftFileInfo.isDir() && rightFileInfo.isDir()) {
return !asc;
}
if (leftFileInfo.isDir() && !rightFileInfo.isDir()) {
return asc;
}
}
return QSortFilterProxyModel::lessThan(left, right);
}
As far as I can tell, you can't (in Qt4).
The default sort order (by the "name" column), or sorting by size behaves like QDir::DirsFirst (or DirsLast if in reverse order for ), but sorting by time or type doesn't treat directories differently from ordinary files.
The QFileSystemModel doesn't expose an API for changing the sort order, and I don't see any opportunity for influencing it in the QFileSystemModel code.
(I don't see anything in the current Qt5 docs to indicate that this has changed, but those aren't final and I haven't looked very closely.)

Resources