QAbstractItemView asks model for invalid index - qt

I'm writting a program using Qt5 which works fine on Linux but on Windows I was observing strange behaviour:
When QTreeView::setModel was called, it asked model for index (QAbstractItemModel::index) with some row and col and invalid parent.
It never happend on Linux, view always asked for hasChildren, rowCount etc before calling index.
I've downloaded Qt5's sources to see what's going on and I can see:
// These asserts do basic sanity checking of the model
Q_ASSERT_X(d->model->index(0,0) == d->model->index(0,0),
"QAbstractItemView::setModel",
"A model should return the exact same index "
"(including its internal id/pointer) when asked for it twice in a row.");
Q_ASSERT_X(!d->model->index(0,0).parent().isValid(),
"QAbstractItemView::setModel",
"The parent of a top level index should be invalid");
I cannot find a single word about those sanity checks in documentation of view classes nor model classes.
Where are they defined?
Another interesting thing here is that I could deduce by observation of model/view classes I've written that top index should be invalid but I couldn't find this information directly in docs.

From the documentation of QAbstractItemModel::parent():
If the item has no parent, an invalid QModelIndex is returned.
This means that calling index() with an invalid QModelIndex asks for top level items.
The sanity checks you encountered may have been disabled in Linux (maybe release build?) - but your model's functionality should in no way depend on the order of function calls.
If index() is called with invalid row / column parameters (also if your model is not populated yet), return QModelIndex().

Related

What is the effect of calling QAbstractItemModel::beginInsertRows() and endInsertRows() if no insertion actually takes place?

I'm implementing drag/drop behavior in my model, which is derived from QAbstractItemModel. My code (C++) for the drop event looks something like this:
beginInsertRows(destination_index, row, row);
destination->AcquireDroppedComponent(component);
endInsertRows();
The call to AcquireDroppedComponent can fail for a number of reasons and reject the drop, in which case no new rows will be inserted in the index stored in destination_index. My question is will calling begin/endInsertRows cause problems if this happens? My limited testing on Windows 7 so far shows no undesirable behavior, but I want to be thorough and not rely on the specific behavior of one platform. I can check beforehand if the drop will succeed or not, but I'd like to avoid the extra code if I can. My question also applies for the other begin/end functions like beginRemoveRows, beginInsertColumns, etc.
Calling these methods without doing the actions you indicate breaks their contract. How the clients of your model will cope with that is essentially undefined.
I can check beforehand if the drop will succeed or not, but I'd like to avoid the extra code if I can.
That "extra" code is absolutely necessary.
I'd refactor your code to perform acquisition and model change separately:
if (destination->acquireDroppedComponent(component)) {
beginInsertRows(destination_index, row, row);
destination->insertDroppedComponent(component);
endInsertRows();
}
The acquireDroppedComponent would store the data of the dropped object without modifying the model, and return true if it was successful and the data is usable. You then would call insertDroppedComponent to perform the model change.

Qt QAbstractItemModel function data() called with undefined role

I want to create a custom list using QListView and so I had to extend QListView, QItemDelegate and QAbstractListModel and then implement the specific methods, along with QAbstractItemModel::data(const QModelIndex & index, int role = Qt::DisplayRole) const.
It displays correctly on the screen at first glance, but the problem occurs after populating the list model.,The function data(index,role) is called 4-5 times per item model with different roles (some of them undefined roles/out of range/probably random). And it occurs not only after the initialization of the list model! When I hover a list element, the view calls data(index,role) with the correct index and role but right afterwards it is called again with an unexpected role value.
It seems an awkward behavior. I couldn't find the source of the strange calls. I put logs in every method of my custom classes to be sure that I'm not calling by mistake the data(index,role) method with wrong values.
Does anyone have some ideas where to look at or why this strange calls occur?
EDIT
The source of the "strange" calls is in:
QSize CDelegate::sizeHint(const QStyleOptionViewItem & option, const QModelIndex & index) const
{
return QSize(QItemDelegate::sizeHint(option, index));
}
somehow when calling QItemDelegate::sizeHint() it triggers data() with different role values.
In my case I have defined role values starting from 0(0,1,2,3). According to #Jens those values are reserved. Changing the starting value of my custom roles solved my problem.
Custom roles start at 0x100, 0..3 are roles defined by Qt. See the list of roles in qnamespace.h. 13 for example ist SizeHintRole.
The different role calls stem from the interns of QListView, which is trying to figure out, how the cells should be displayed. Example: If you want to change the font of a row, you simply add the switch for FontRole to your data() implementation and pass back a bold font whenever something is important and should be displayed in bold.

A clean and intuitive way to implement a list of clickable strings to invoke functions?

For the Qt App I'm writing, I'd like to have a list of clickable functions, which, when clicked will allow the user to supply required input arguments.
What I'm specifically looking for is a selection of widgets which provide a clean and intuitive interface for the following tasks:
User scrolls through a list of functions for performing computations (in my case, from glm).
Once a function is found, the user clicks on the item; a popup window opens, which specifies the required input arguments (e.g., vec3, vec4, etc.).
The idea here is that the functions themselves already exist: they just need an interface, which in a nutshell, provides a pseudo interpreter to process and output their results to a GLWidget, which will update the data passed accordingly by sending it to its corresponding shader.
I've looked at QListView, and its Widget variant, but it appears to be more suited towards filesystem data, such as images or text files, though I'm not quite sure. So far, it seems to be the only thing which could be considered as realistically usable in this scenario.
Is there a recommended way to do this? I'm fairly new to Qt in general, thus my knowledge is pretty limited.
The view isn't really important in your case. You need to create/reuse a adapted model.
This model have to contain the relation between what your view displays and the action that you want to launch.
For example, if your commands are text like bash commands, you can create a view that displays "list files", "Copy files" and a model that contains the data ("list files" = 'ls -l'), ("copy files" = 'ls -l'), etc.
You can store different data (using QVariant) in a same item with different roles: Qt::DisplayRole corresponds to the data that the view displays and Qt::UserRole what you want.
So, if you only have to store a command line associated to a name, you can store the name in the item with the Qt::DisplayRole and the command line as a QString (or other) using Qt::UserRole.
See QAbstractItemModel::data() and QAbstractItemModel::setData(), for more information.

Can connectSlotsByName connect to selection model changes?

In my main window (QMainWindow) I have a QTableView (named commandsTableView). Now I want to react on its selection changes.
I made a slot and connected it manually to ui.commandsTableView->selectionModel(). All works fine.
But then I thought: why not use auto-connection (especially that there will be more connections to be done)? At least it will add more force to consistent naming rules.
Yet I wasn't able to find proper name syntax. I tried:
on_commandsTableView_selectionModel_selectionChanged,
on_commandsTableViewSelectionModel_selectionChanged,
on_commandsTableView_selectionChanged,
on_commandsTableView___selectionChanged
but neither worked. In all cases there is there is a message on output when running the app (with corresponding slot name, here only first given as an example):
QMetaObject::connectSlotsByName: No matching signal for on_commandsTableView_selectionModel_selectionChanged(QItemSelection,QItemSelection)
(Why there are no assertions in response for connection errors - that I cannot understand. I lost much time wondering what is wrong before I spotted those - and alike - messages on output.)
The object returned by ui.commandsTableView->selectionModel() has an empty name. But setting it to selectionModel prior to making a call to connectSlotsByName doesn't help either.
According to the documentation connectSlotsByName() only supports signatures like
void on_<object name>_<signal name>(<signal parameters>);
According to the sources that's the only form it checks (watch how it collects a list of children, then matches parent's method names against names of the children).
Hence, to be able to use auto-connection you would have needed a named selection model, which would continue existing from the call to connectSlotsByName() onwards. Each time you change the selection model (not likely) or the model (likely) you'd have to name the selection model and auto-connect again. But alas connectSlotsByName() will duplicate all other connections as it doesn't seem to check if connections are unique, so we have to connect signals to such dynamic children as models, scenes etc manually.
I think it's
on_selectionModel_selectionChanged(const QItemSelection & selected, const QItemSelection & deselected)

Qt what needs to be done for a custom model to enable drop?

I'm trying to enable drop on a custom model I have hooked up to QTreeView.
I've done the following:
Ensured that acceptDrops is enabled on the QTreeView
Implemented on my custom model supportedDropActions to return Qt::CopyAction | Qt::MoveAction
Implemented on my custom model mimeTypes to return a QStringList with text/uri-list
Implemented on my custom model dropMimeData to handle the drop if it ever occurred.
This is all I needed to get it working on a QTreeWidget.
I've gone on to:
Implemented flags to return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsDropEnabled;
Nevertheless, when I run my app, and attempt to drag a file in Windows onto my QTreeView, I just get that not allowed Ghostbusters-style circle with a slash through it.
What else do I need to do to enable drops?
Finally figured this one out on my own.
Turns out the issue was that in the flags function, in the case of invalid indexes I had failed to return the ItemIsDropEnabled flag. An invalid index is the signal for the root node, i.e. the area of the view where there were no items, and so the empty space was not droppable.
This had been hard to detect because I had been trying to use drop to add items to my tree, and so there were none without drop working, meaning all I saw was the circle with a slash.
For those facing similar problems, I want to point out that it is necessary to return Qt::CopyAction among the supported drop actions. Qt::MoveAction alone will not work.

Resources