Change text of QStandardItem when editing starts - qt

Using Qt4's model/view framework, I have a list of items displayed as "Foo (38 bars)". I want to make the text editable, but when the user starts editing, I want the control to just show "Foo" (the name), and then to reinstate the extra info "(38 bars)" when editing is complete.
I think I can work out how to add the info back, but is there a way to change the text of the item when editing starts?
The model is a QStandardItemModel, and the items are fairly trivial subclasses of QStandardItem. They are displayed primarily in a QListView.
The UI is written in PyQt, and I'd prefer not to dive into C++ just for this, but I'll do my best to translate C++ solutions if they appear.

After #Chris put me on the right track, I found this note in the docs for QStandardItem:
The default implementation treats Qt::EditRole and Qt::DisplayRole as
referring to the same data.
So I needed to override the method QStandardItem.data(). My Python code looked like this:
def data(self, role=QtCore.Qt.UserRole+1):
if role == QtCore.Qt.DisplayRole:
return "{} ({} bars)".format(self.name, len(self.ds))
return super().data(role) # Fall back to the default method
On the model's itemChanged signal, I updated the .name attribute I'm using:
def update_name(self):
self.name = self.data(QtCore.Qt.EditRole)
Finally, the number of "bars" can be changed elsewhere, and this should trigger any views to update the item. So after changing that, I called the item.emitDataChanged() method (docs).
With this done, it seems to be working as intended.

You should be able to set the Qt::EditRole data on your items to achieve this. In C++ it would be:
myStandareItem->setText( "Foo (38 bars)" );
myStandardItem->setData( Qt::EditRole, "Foo" );
When Qt sees something in the EditRole, it will use that for editing purposes instead of text (which is stored under the Qt::DisplayRole).
You can then use things like the QStandardItemModel::itemChanged() signal to update the display text of the item when it gets edited.

Related

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

Change checkbox state to not checked when other checkbox is checked pyqt

I'm using Qt Designer and pyqt code to write an app.
I have a very simple question: in my UI I have 2 checkboxes and what I need is to set the first checkbox as always unchecked when the second checkbox is unchecked.
In other words the first checkbox can be checked only when the second checkbox is checked, but the second checkbox can be checked also if the first one is not checked.
In Qt Designer I have not find an easy way to do that with the Signals/Slots function.
I had a look in the Qt API and I tried to write some code:
class CreateRIVLayerDialog(QDialog, FORM_CLASS):
def __init__(self, iface)
# some more code here...
if self.addCheckDB.isChecked():
self.addCheck.setChecked(False)
but without results.
Does anybody have some hints?
Thanks
The simplest way is with two signal connections, one of which can be done in Qt Designer.
Your design requires that "the first checkbox can be checked only when the second checkbox is checked". This is equivalent to saying the first checkbox should be disabled when the second checkbox is unchecked. Disabling the checkbox makes it clear to the user that the option is unavailable.
So in Qt Designer, you should connect the toggled signal of checkbox2 to the setEnabled slot of checkbox1. (It may also be necessary to set the initial enabled state of checkbox1 in Qt Designer as well).
Then, in your code, you should make a second connection, like this:
self.checkbox2.toggled.connect(
lambda checked: not checked and self.checkbox1.setChecked(False))
This will clear the checked state of checkbox1 whenever checkbox2 is unchecked.
If you wanted to do the whole thing in code, it would look like this:
self.checkbox1.setEnabled(False)
self.checkbox2.toggled.connect(self.checkbox1.setEnabled)
self.checkbox2.toggled.connect(
lambda checked: not checked and self.checkbox1.setChecked(False))
Just use signals. You are correct when saying that you cannot directly do that via the designer since you have to invert the checked property. The easiest and most readable way that comes to my mind is using a common slot plus an internal member variable that holds the checked state for both:
Add self._toggle = INITIAL_VALUE to your class - the INITIAL_VALUE holds a boolean value, which you use to check/uncheck your checkboxes
self._toggle = True
self.check1 = QCheckBox('Check 1', self)
self.check1.setChecked(self._toggle)
self.check2 = QCheckBox('Check 2', self)
self.check2.setChecked(not self._toggle)
Add a slot:
#pyqtSlot()
def toggle(self):
self._toggle = not self._toggle
self.check1.setChecked(self._toggle)
self.check2.setChecked(not self._toggle)
Connect the clicked signal of both checkboxes to this slot.
Warning! Do not use the stateChanged signal here or you will start an infinite recursion. :3
self.check1.clicked.connect(self.toggle)
self.check2.clicked.connect(self.toggle)
What I'm doing here is basically taking over the change of the state of both checkboxes and do it manually using the value of self._toggle for the first checkbox and the inverted value of self._toggle for the second checkbox.
If you want less inverting inside the slot the following also works though it is less readable omho:
#pyqtSlot()
def toggle(self):
self.check2.setChecked(self._toggle) # Old value of our check1 becomes the value of check2
self._toggle = not self._toggle # Invert
self.check1.setChecked(self._toggle) # New value is assigned to check1
Note: You can also use isChecked() inside the slot and do all of the above without the extra variable however I find this more readable and with much less function calls (every isChecked() and setChecked() is a function call)
I know I am late to this party but there is a way to accomplish this easier and I think how the original person wanted. Here you go. If you have a checkbox_1 and you check/uncheck it, it connects to the following function.
def Change_the_Checkbox_Function(self):
if self.checkbox_1.isChecked():
if self.checkbox_2.isChecked():
pass
else:
self.checkbox_2.setChecked(True)
else:
self.checkbox_2.setChecked(False)
If you want checkbox_1 to check or uncheck many other checkboxes (like a select all), then instead of checkbox_1 directly calling the above function, you have another function that calls other functions to check/uncheck the boxes, as follows:
def Select_ALL_checkboxes(self):
self.Change_the_Checkbox_1_Function()
self.Change_the_Checkbox_2_Function()
self.Change_the_Checkbox_3_Function()
....etc
To me, this follows the most logical way without getting too technical. It works well and is fast. May not be the most efficient or prettiest, but I found this to be the best way to check/uncheck many checkboxes at once.

QML TableView access model properties from delegate

I have a TableView for which I've defined my own itemDelegate. Now, from within this delegate I can access the value for the column using styleData.value, but I'd also need to access the other properties in this same item but I can't find how to.
I need this, because the text styling needs to change depending on some other property of the item model.
Any ideas? thanks!
There is some documentation missing. Within the item delegate you can access the following (taken from the source code of TreeView.qml):
styleData (see documentation)
model (currently not documented)
modelData (currently not documented, not sure about this but I guess it's similar to ListView)
(By the way, what's also missing in the documentation but which is useful is styleData.role. Also, the documentation of the other delegates lacks some available properties too; the best is to peek into the source code of the QML file and have a look for the Loader element which instantiates your delegate. As a plus you learn how that creepy stuff works. ;))
With model and the row/column information you can then navigate to the item data. This code depends on the type of model.
If you're using QML's ListModel, then you can use model.get: model.get(styleData.row)[styleData.role] should then work (untested since I use it rarely, please give feedback).
If you're using a C++ QAbstractItemModel or friends, the best is to add a slot to the model class which takes just the row and role name, since that's the information the TableView works with (nor with role numbers nor with columns...).
However in both cases you shouldn't use the expression in a property binding! The notification system will not work since you don't use the property system for accessing the data. According to your question, I guess you wanted to use it in a expression with binding. I don't know how to properly listen to changes in the model manually.
An alternative approach is to access the other items of the row and provide a property there. Some hints:
From within one item, you can access other items of the same row by walking the object tree up twice (first to the Loader which instantiates your component, then to the actual row) and then down twice (first to the particular child object which is a Loader, then its instantiated item). You need to know the column number you want to access (not the role name), I assume you want to access the first column (index 0):
parent.parent.children[0].item
You can provide the model data using a property in each item. Assuming a simple Text element this might be:
Text {
property variant value: styleData.value // <-- Here you make it available
// your other stuff
}
Putting them together could look like the following. In this example I assume the first row contains an integer, and if it is zero, the second column should be red.
// (within TableView)
itemDelegate: Text {
property variant value: styleData.value
text: styleData.value
color: (styleData.column == 1 && parent.parent.children[0].item.value === 0)
"red" : "black"
}
I think it's pretty easy if you read the source code of TableViewItemDelegateLoader.qml (it is a private code in qtquickcontrol)
To access any role you use use : model[your_role_name] .
For exp: model["comment"]
Faced with same problem today, this is result of my investigations (Qt 5.2.x)
If you have hard limit to TableView, there is only one correct solution - use model.get(styleData.row)["roleForStyling"] as #leemes wrote. But it will very slow if you have big amount of data in model and using, for example, proxy model for sorting/filtering.
Direct solution from #leemes answer is great, but in general case not be working, because in TableView any Item wrapped in Loader and therefore independent from parent and other items:
When some item is created (where you want to change text style)
another element (from which to receive identity) cannot yet be
created
You may not have "parent" on item creation (i.e. binding will
be broken)
In my case, the best solution for deep customise was creation of the simple wrapper for ListView. In this case you have access for complete row data in delegate without the overhead. Highlights for making component ("My own ListView as table"):
Create standalone header (Rectangle or Item) - do not use header form ListView.This make it fixed for any amount of data.
Wrap ListView to ScrollView (if you need scrollbars)
Use Clip: true property in list for make correct
Set style for highlight and set highlightFollowsCurrentItem:true in ListView
As bonus in future this may be used for make "TreeTable" :)

Qt - signal for when QListWidget row is edited?

I am working in Qt4.7, and I have a QListWidget in my dialog. I have a QString that needs to match the current text in the row of this widget (the individual rows are editable). Looking at the signals associated with QListWidget, there seem to be signals for when a different index is selected but none for when the text of a the currently selected row changes. I thought currentTextChanged(QString) would do it, but it didn't. I also thought to try to connect each individual row to something, but QListWidgetItem doesn't have any built-in signals. Does anyone know of a way to do this? Thanks!
At first it seems like QListWidget::itemChanged is the way to go, but soon you run into a problem: the signal is sent for everything - inserts, changing colors, checking boxes, and anything else that "changes" the item! Predelnik pointed that out in his answer. Some people have tried to put in flags and filter everywhere by intercepting various signals to find out if editing was the actual event. It gets very messy.
There is also QAbstractItemModel::dataChanged , which would seem like a good solution. It even has a parameter "const QVector& lstRoles" so you could scan for Qt::EditRole and see if it was really edited. Alas, there's a catch - it gets called for everything just like QListWidget::itemChanged and unfortunately, for QListWidget anyway, the roles parameter is always empty when it's called (I tried it). So much for that idea...
Fortunately, there's still hope... This solution does the trick! :
http://falsinsoft.blogspot.com/2013/11/qlistwidget-and-item-edit-event.html
He uses QAbstractItemDelegate::closeEditor, but I prefer using QAbstractItemDelegate::commitData.
So make a connect like so...
connect(ui.pLstItems->itemDelegate(), &QAbstractItemDelegate::commitData, this, &MyWidget::OnLstItemsCommitData);
Then implement the slot like this...
void MyWidget::OnLstItemsCommitData(QWidget* pLineEdit)
{
QString strNewText = reinterpret_cast<QLineEdit*>(pLineEdit)->text();
int nRow = ui.pLstItems->currentRow();
// do whatever you need here....
}
Now you have a slot that gets called only when the list item's text has been edited!
I guess you need to look into the following signal:
void QListWidget::itemChanged(QListWidgetItem * item)
But be careful because it's being sent every time some property of item changed, not only text. I remember when we ran into the problem once when we changed item colors and got tons of false positive slots called because of that. If you need more fine tuning I guess it's better to write model/view classes yourself and not rely on QListWidget.

How can I programmatically commit data from QTableWidget, which contain some items in editing state?

How can I autocommit data from QTableWidget, that is in editing state, when I fire some command?
Assume, that there is some grid and data in it (editable thorough delegate that fires QComboBox editor). So, one starting to select option in combo, but do not finish editing, then hit some button, that executes action, that uses data from that combo, but new choise is not committed yet :\
How can I programmatically finish editing in table?
I mean some not strict 'loop all items and finish editing' way, that I consider as bad and ugly.
OOPS: worked too much, so, haven't realised, that there could be only one pending editor at time. Question is still here.
There is a protected slot named "commitData" in the tableWidget. You can inherit from tableWidget, then add your own public method (or slot) and send a signal (or simply call commitData method) from there.
There is one problem. You'll need to provide the editor object, but tableWidget gives you no way to get the pointer you need.
If you're using your own createEditor method, you can save the pointer to the editor somewhere, where your method can get it. It's a hack, but it's the only way i know.
The current editor does not seem to be accessible from outside of the view, but its content is committed when the current model index changes. So a simple way to force a commit seems to be to call
table->setCurrentIndex (QModelIndex ())
plus restoring your previous current index afterwards if the widget is not discarded yet.
This is quite an old question but it still came up quite high on Google so just in case anyone else needs the answer QTableView has a protected method
void currentChanged(const QModelIndex &current, const QModelIndex &previous)
which causes the data to be commited and QTableWidget is built on QTableView so that should still work. I found this info on the Qt Forum.

Resources