Get focus (or tab) order - qt

I have designed a user interface by using Qt Designer, and I have set the tab order using the "edit tab order" mode.
Now what I'd like to know (for an other reason, not so important) is how to get the tab order of a specific QWidget in the ui?
I mean if I have several widgets, and say the tab order has been set, is there a way to do something like :
int nb = widget1->getTabOrder();

There is no way to get the tab order as an integer.
If you look into the C++ code that the uic tool creates from your ui file, it will call QWidget::setTabOrder() a few times, and that method just takes two QWidget pointers. Thus, Qt internally doesn't even store the tab order as an integer, but rather as a chained list of QWidget pointers.
You can query that chained list with QWidget::nextInFocusChain() and QWidget::previousInFocusChain(). This gives you the whole focus chain of the widget, containing all child widgets inside it, in the right order. Then you can get the real tab order list by checking their focusPolicy, enabled state and visible state, just like the inside implementation of the QWidget::focusNextPrevChild() function. If you really need an integer index here, you need to devise an algorithm yourself that calculates indices from that obtained tab order list.

(A bit late.) I had an a-ha moment: it's actually not at all difficult to determine the position of a widget in the tab sequence. It requires the use of Dynamic Properties, which allow you to "annotate" any QObject. The (default) focus list is simply a circular linked list with no distinguished (that I've seen identified) node. The function below annotates all the items in that linked list with a sequence number starting at the distinguished node (your choice) and provides a pointer to that in the parent widget/dialog.
Call it from your setupUi (etc.). (After any changes you may make to the focus sequence!) The parameters are the widget/dialog you want to sequence (and find the distinguished node in) and the widget you've decided should be "first" (distinguished).
// Add sequence numbers in the tab focus list starting at distinguishedNode
void sequenceFocus(QWidget *root, QWidget* distinguishedNode)
{
QVariant v;
v.setValue(distinguishedNode);
root->setProperty("focusRoot", v);
int itemCtr = 0;
QWidget* i = distinguishedNode;
do {
i->setProperty("focusPosition", itemCtr);
i = i->nextInFocusChain();
itemCtr++;
} while (i != distinguishedNode);
}
You can then get the distinguished (first) item from the list from the parent with:
QWidget* start = activeDialog->property("focusRoot").value<QWidget*>();
And get the sequence position of a widget with:
my_widget->property("focusPosition").toInt()
Note that (at least) Designer ends up putting more entries in the focus list than just those marked in the focus sequence as seen in Designer. The actual focusable widgets will be sparsely numbered. (Add debugging printouts in the function above to see everything.)

Related

How is the concept of a "parent" meaningful in a QAbstractListModel?

I have a subclass of QAbstractListModel that is the model for a QML ListView (using PySide6). Each row of the list has a checkbox in it, and when the user checks/unchecks a box, it updates a boolean in that row of the listmodel using my override of setData(), which works as expected. I also have Buttons that should select/clear all of the checkboxes in the list.
My model subclass provides the following method to select all, which can be called when the user hits the Button or when other things happen in the application:
def select_all(self):
for i in range(self.rowCount()):
row = self._rows[i]
row['selected'] = True
# Emitting this for each row works...
self.dataChanged.emit(self.index(i), self.index(i), [])
# ... whereas emitting just one signal for ALL rows does NOT work
# self.dataChanged.emit(self.index(0), self.index(self.rowCount()), [])
As you can see from the comments, I need to emit dataChanged for each row in order for the checkboxes in the ListView to be updated. Emitting the signal once and using the topLeft and bottomRight parameters does not update the state of the checkboxes in the ListView (but the model data is correctly updated).
According to the documentation, the dataChanged signal provided by QAbstractItemModel (and inherited by QAbstractListModel) has this caveat:
If the items are of the same parent, the affected ones are those between topLeft and bottomRight inclusive. If the items do not have the same parent, the behavior is undefined.
It seems likely that I'm running into the scenario where the rows in my model do NOT have the same parent, and therefore, "the behavior is undefined." I suppose that makes sense, because I never do anything to establish a parent/child relationship for any of my rows. I also saw this answer which implies that Qt behaves differently when the topLeft and bottomRight indices are the same. So, I would like to understand this better, and I have a few questions:
Am I correct that this parent concept is the reason this does work when emitted for each row and does not work for all rows?
Is the concept of a parent/child relationship only meaningful for tree-like models that would extend QAbstractItemModel instead of QAbstractListModel? Does it make any kind of sense for a list?
If it does make sense for a list, then what would be the "parent" and what would be the "child?" How would I configure a subclass of QAbstractListModel such that dataChanged can be emitted once to update multiple rows?
Thanks!
While the base implementation of Qt item views just updates indiscriminately the view whenever the topLeft and bottomRight indexes doesn't match (so you can just provide two "random", but still sibling indexes), the indexes must not only share a common parent (which for one and two dimensional models is always an invalid index), but also both valid.
With this line, the second index is not valid:
self.dataChanged.emit(self.index(0), self.index(self.rowCount()), [])
This is because the indexes are always 0-based, and the index of the last row is actually rowCount - 1. While they theoretically are siblings, since they share the same parent (the parent of a root index is invalid, as it is the parent of an invalid index), they are not both valid.
So, the correct syntax is:
self.dataChanged.emit(self.index(0), self.index(self.rowCount() - 1), [])
^^^^
Note that the roles argument is optional and already defaults to an empty list (QVector in C++), so unless you actually want to specify the changed roles, you don't need to provide that.

How can I detect whether a tooltip is visible at a given moment?

I'm looking for a way to detect whether a Qt widget's ToolTip is visible at the moment when a particular key combination is pressed. If it is, I want to copy the ToolTip's text to the clipboard.
Specifically, I have a QListView containing abbreviated strings, which is set up (via the Qt::ToolTipRole of the associated model) to show the full string of the appropriate list item when the mouse is hovered over it. The behaviour I'm looking for is that if the user presses CTRL-C (as detected by a QShortcut) while the tooltip is visible, then the tooltip text is copied to the clipboard.
My original idea was to use the children() method of the QListView widget to see if there was a tooltip preset among them:
// Inisde the slot connected to QShortcut::activated...
auto children = _ui -> myListView -> children();
QString selectionText;
for (const auto & child : children)
{
if (qobject_cast<QToolTip *>(child))
{
selectionText = qobject_cast<QToolTip *>(child) -> text();
break;
}
}
...but this failed because it turns out that QToolTip does not inherit from QObject.
I've also thought of screening for QEvent::QToolTip events in the ListView's main event handler, and while I could probably get this to work it seems excessively low-level; I'd need to use screen co-ordinates to determine which item in the list was being hovered over and look for the widget's timeout to check that the tooltip hadn't disappeared again by the time that the QShortcut was fired. I'd be disappointed if there weren't a simpler way.
Is there an obvious way forward that I've failed to see?
There are probably several possible solutions, but I am afraid none of them is simple. What I would do is to use the implementation detail that the tooltip actual widget is called QTipLabel. See https://code.woboq.org/qt5/qtbase/src/widgets/kernel/qtooltip.cpp.html#QTipLabel and it inherits from QLabel so you can easily get the text from it.
I am afraid the following solution is just a savage hack. I have not tested it, but it should work.
I would override the data model for your view, specifically override method data() which would call the data() method of the original model class but cache the last value which was returned when this method is called with role == Qt::ToolTipRole.
Then you need to catch the shortcut you are interested in. After it is caught, you get all qApp->topLevelWidgets() https://doc.qt.io/qt-5/qapplication.html#topLevelWidgets` and go through them and check if any of them has class name equal to QTipLabel (use QMetaObject::className()) and is visible, i.e. isVisible() == true.
If you get this visible QTipLabel widget (you hold it via QWidget*), qobject_cast it to QLabel* (you cannot cast it to QTipLabel beause you do not have access to the definition of QTipLabel class because it is in private Qt source file) and get the text with QLabel::text(). If the text is the same as the text which you stored in step 1, then yes, this is the text you are looking for and you can copy it to clipboard or do whatever yo want with it.
Nasty, isn't it? But it is the simplest what I can think of.
PS: I believe that step 1 can be implemented also by catching QEvent::QToolTip for your view and then do some magic to get the text, but I think that overriding data() for model can be a bit easier.
PPS: One obvious drawback is that Qt can rename QTipLabel class in the future. But I would not be worry about it. That won't happen becaus ethey do not change QtWidgets module any more. And if it happens, then you just rename the class in your code. No problem.
PPPS: Another potential corner-case is that some other widget (whose tooltip you do NOT want to capture with that shortcut) actually has the same tooltip text as any of the items in your list view (which you DO want to capture). Then if you display tooltip for your list item, then you move your mouse over to that other widget and hover so that its tooltip gets shown (but you do NOT want to capture it) and then you press that shortcut... But I guess that in reality this will not be your case. I doubt there will be this unlikely clash of tooltips.
With thanks to #V.K., this is what worked:
auto candidates = qApp->topLevelWidgets();
QString selectionText;
for (const auto & candidate : candidates)
{
if (strcmp(candidate->metaObject()->className(), "QTipLabel") == 0)
{
QLabel * label = qobject_cast<QLabel *>(candidate);
if (label->isVisible())
{
selectionText = label -> text();
break;
}
}
}
if (!selectionText.isEmpty())
QGuiApplication::clipboard() -> setText(selectionText);

QT: How to show combo boxes as cells in a tree view

In a designer-based QT GUI application I'm using a QTreeView to show a tree of elements that is provided by an instance of QStandardItemModel.
The tree-view is multi-column and all elements in the first column are checkable. The screenshot shows an example of how this currently looks like:
Now let's say I'd like the user to be able to select different names for "point". The idea is to have a QComboBox right next to each of the checkboxes. And as alternatives to "point" he may chose from a set of strings, e.g. "point", "pt" and "coord2D". Later on I'd like all selections for all duplicates of "point" to be synchronized but let's start simple...
I'm not too familiar with the idea but to me it looks like the way to go is to create an ItemDelegate for the view as described in the QT Documentation or in this topic (both links refer to QTableWidgets instead of QTreeViews).
So what I did as a first step is I took the example delegate ComboBoxDelegate from the stack overflow question mentioned above and called it from within my application using this code also taken from a related question:
QStandardItemModel* model = new QStandardItemModel(20,2);
ui.tvStructures->setModel(model);
ui.tvStructures->setItemDelegate(new ComboBoxDelegate());
for (int row = 0 ; row < 20; ++row)
{
for (int col = 0; col < 2; ++col)
{
QModelIndex index = model->index(row, col, QModelIndex());
model->setData(index, QVariant((row+1) * (col+1)));
}
}
Note that I placed this code inside the constructor of the parent QDialog where the control element is located. What I ended up is a 2-column table as expected but without any combo boxes. In fact when debugging the code I observe that the constructor of the delegate is called (during the new operation) but none of createEditor, setEditorData, setModelData or updateEditorGeometry every get called. I thought this may be due to the fact that some connection magic is overwriting triggers required to do the drawing but even if I remove all code that refers to the tvStructures QTreeView apart from what I have posted I still can't see any combo boxes.
What's missing?
Note that I'm using the somewhat outdated QT 4.7.1
Looks like you're missing a parent for new QComboBoxDelegate. You can use the QDialog you mentioned as parent.
Also: follow this lengthy example to make sure you're not missing anything else.

QT Creator: Trigger a Slot with Code?

I may have worked myself into a corner but this sounded to me like a good idea at the time.
I have been developing an interface that permits a user to modify settings of a robotic device, i.e. speed, directions, force, etc. with a very large series of options in the form of ComboBoxes. The problem is that there are about a thousand of these things, in sub categories. e.g. Speed category x1, x2, x3, Y1, y2, etc. So rather than create a thousand comboboxes in QT, I thought the good idea was to create one set of 50 (ish) and then provide a few button to switch between categories. So when the user selects speed QT, populates the comboboxes with the appropriate options, sets the style sheets and text for the labels etc. So it appears as though a dedicated page exists. Then if the user selects Direction, QT Writes the current index of each box to a dedicated array and then repopulates the boxes, labels etc with the appropriate content. I then do this over and over for the various needs of the system.
All of that seems to work fine. However I am now in a bind where the options provided to navigate to each page have grown. For instance I have forward / backward buttons (like you woudl expect in a set-up wizard), as well as action menus at the top to jump to a page. So now the code is becoming very repetitious. If you select the next button, I write the current values to array, then repopulate. If you jump to the page from anywhere, I look to see where I am, write it to array, and populate the boxes. Thus if I need to change anything I have to make the change in numerous places in the code.
I know that this is not optimal. What I woudl like to do is run a continuous loop as I woudl normally do with Micros in C. So the program can look at a variable in each pass and if it is then it does. I am not however skilled enough to figure this loop out in QT. So my new thought was...
Is it possible to trigger an action or slot with a variable. For example, if the user presses the Next button it triggers a slot for a button that does not exist, so that QT will execute a particular line of Code? Then I can have 1 dedicated section focused on reading and writing boxes, with a bunch of actions that will take me there.
You can make a signal that is triggered with an emit call in your code, so you'd hook up the next button signal of clicked to a slot that does some work and moves on, or directly calls another signal that you've created that triggers a slot elsewhere, or do some work in a lambda triggered by the button press.
I would first load all the ComboBoxes options in a QStringList array (or maybe an array of QList<QLatin1String> lists - for memory saving and code efficiency).
Then I would keep an array of a 1000 integers for current ComboBox indexes.
When the user changes a value in some ComboBox, the currentIndexChanged signal will trigger the corresponding slot (a single slot for all the ComboBoxes would be enough - sender()->objectName() to get the name of the ComboBox which had sent the signal):
void WindowWidget::on_ComboBox_currentIndexChanged(int index)
{
name = sender()->objectName();
/* here change the corresponding integer in the current
indexes array */
}
On Next/Back button push repopulate the ComboBoxes. Also, provide some 'Save' button for saving the ComboBoxes indexes (or trigger the Save slot on some action, i.e. on window close either even on a timer signal).

How do I keep two parallel Qsplitters equal sizes?

I have a Qt widget with four partitions, separated with splitters.
The top level is a vertical splitter, changing the heights of two horizontal splitters, topSplitter and bottomSplitter.
How can I keep both horizontal splitters positions equal, as if it was just one horizontal splitter?
I looked at linking signal for splitterMoved, and connecting it to a slot on the other splitter but there are no equivalent slots in the splitter class.
This would obviously have to avoid the issue of an infinite loop where one splitter's position updates the second, which updates the first.
It's pretty simple. Initialization (splitter1 and splitter2 are the splitters that need to be syncronized):
connect(ui->splitter1, SIGNAL(splitterMoved(int,int)), this, SLOT(splitterMoved()));
connect(ui->splitter2, SIGNAL(splitterMoved(int,int)), this, SLOT(splitterMoved()));
The slot:
void MainWindow::splitterMoved() {
QSplitter* senderSplitter = static_cast<QSplitter*>(sender());
QSplitter* receiverSplitter = senderSplitter == ui->splitter1 ?
ui->splitter2 : ui->splitter1;
receiverSplitter->blockSignals(true);
receiverSplitter->setSizes(senderSplitter->sizes());
receiverSplitter->blockSignals(false);
}
blockSignals ensures that calls will not go to infinite recursion. Actually, setSizes doesn't cause emitting splitterMoved, so you can remove both blockSigals calls and the code will still work. However, there is no note about this in the documentation, so I wouldn't rely on that.
In the containing widget you can create slots to connect to the splitterMoved signal.
There needs to be one slot for each splitter, where it needs to check if the splitter is already the correct size (to avoid the infinite loop) then update the size if necessary.
I am only including one of the example slots and connections, but one will be needed for each splitter that needs to be linked.
Putting the following content into the new slots for updating the splitter positions.
QList<int> OtherSize,Current;
OtherSize=topSplitter->sizes();
Current=bottomSplitter->sizes();
if(OtherSize!=Current)
{
bottomSplitter->setSizes(OtherSize);
}
This will create two lists ready to hold the sizes for the splitters.
It gets the current sizes of both splitters, and compares them.
The comparison is necessary to avoid the infinite loop.
Then, if they are different, it sets the sizes to match that of the other splitter.
Connecting that slot to the appropriate splitterMoved signal should work.
This connection is used in the constructor of the containing widget (where you created the new slots).
connect(topSplitter,SIGNAL(splitterMoved(int,int)),this,SLOT(updateBottomSplitter()));
It is safe to ignore the position and index supplied by the signal, because we check the sizes in a different way in this slot.
This will set all sizes, so all splitter bars will match position.
If there are only two, it doesn't matter but if there are more than two, when any bar is moved, all will be updated to match.

Resources