QT Creator: Trigger a Slot with Code? - qt

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

Related

what is the signal for qtreeview changed

There is a dictionary taken from JSON file, that is represented by QTreeView QStandardItemModel.
A user can reorganize QTreeView(add, delete, drag-n-drop) and rename items.
The goal is: call function that reads changed QTreeView, makes the dictionary and writes it to initial JSON file.
I can do it by pressing a QPushButton after changes occurred or by binding that function to every change e.g. call function when an item is deleted, call function when an item is added, call a function when an item is renamed and so on.
Is there any way to call a function if any of changes occur? Is there such a signal that corresponds to all of the mentioned changes?
The rowsMoved and itemChanged signals do what you think they do. See http://doc.qt.io/qt-4.8/qstandarditemmodel.html
As #vahancho suggests in the comments, you should connect to the layoutChanged signal. All models should emit this immedaitely after making any changes which could affect the view. So this will include sorting and filtering, as well as re-ordering, editing, deleting, etc.
The dataChanged signal is similar, but only really useful if you want to monitor specific items.

QDoubleSpinBox: Stop emitting intermediate values

I am subclassing QDoubleSpinBox to add some features (like incrementing the value based on the location of the cursor in the lineedit) and to change some features I find annoying. One of the latter is that intermediate values are emitted: e.g. if you want to enter the value 323 it will emit 3 then 32 then finally 323. I'd like to set it to only emit on entry (i.e. only actually change value on entry).
Anyway, I can't figure out how to capture these intermediate edits. I overrode setValue to see if I could stop it there somehow, but it apparently isn't called (or at least my override isn't). I'm not sure how the value is actually getting set while editing in line edit.
More generally, the logic of this box escapes me. Is there some documentation that explains e.g. "if you type a digit into the lineedit then this series of routines is called... while if you hit the up arrow, this series of routines is called?"
In case it matters, I'm using PyQt5
EDIT: Here is another case in which having access to this is important. Say I want to implement an undo/redo structure for the box. The only way I can think of to get access to the changed values is to connect to the valueChanged signal. But if I'm subclassing the box it seems a little convoluted to listen for a signal rather than just watch the value change 'internally' Or am I missing something here?
You could use the following signal:
void QAbstractSpinBox::editingFinished() [signal]
This signal is emitted editing is finished. This happens when the spinbox loses focus and when enter is pressed.
based on the documentation of QAbstractSpinBox:
http://qt-project.org/doc/qt-5.1/qtwidgets/qabstractspinbox.html#editingFinished
There is nothing that combines the arrow based changes and the editingFinished changes.
My use case was to let the user enter the value without getting the signal on each new digit, while still making ↑, ↓, Page Up, Page Down keys and arrow buttons work as usual, emitting the signal on each activation.
QAbstractSpinBox::editingFinished signal doesn't provide this functionality: it's only ever emitted when focus is lost or Return/Enter is pressed.
What does work exactly as I need is the keyboardTracking property of QAbstractSpinBox. If it's true (the default), the spinbox emits valueChanged on each digit typed. If you set it to false, it behaves exactly as I described in the beginning of this answer.

Trigger action on completion of drag action

I'm trying to allow items from a QListWidget to be dragged to a "Trash" (A subclassed widget which accepts drops and does nothing with them).
I know that if I setDropAction(Qt.MoveAction), the items I am removing from the source will be automatically deleted. This works correctly.
My problem is that I also need to trigger an action that updates other widgets who depend upon the contents of the source.
It seems to me that the dropEvent happens before any items are actually removed from the source. I'm having a terrible time trying to figure out this problem. I've thought of two possible solutions:
Find a way to embed the references to the actual QListWidgetItems that are being dragged in the event's QMimeData. This would allow me to do the deletions by hand, before I trigger updates.
Figure out how to wait until the source has been automatically cleared, but I can't find any signals that fire when items are removed from a list automatically.
Aha!
The key I was missing was the mimeData method. This method is called when a drag is started, and in it I am passed a list of all files being dragged.
I first built the meta object to be returned, then I deleted the files being dragged from the list, and called the refresh action that I needed.
Here's an example:
def mimeData(self, items):
m = QMimeData()
m.setUrls([QUrl(i.url) for i in items])
# Clean up the list:
[self.files.takeItem(self.files.indexFromItem(i).row()) for i in items]
self._update_meta()
return m

PyQt (or just QT). How to get QComboBox to fire a signal whenever it is set to a value (even if unchanged)

I am using PyQt4, but this is general enough that it could just apply to QT.
I have a series of QComboBoxes that I fill from left to right (i.e. selecting an item in the leftmost will populate the next one. Selecting an item in that one will populate the next, and so on)
I am having difficulty getting my signals to fire under all situations (i.e. regardless of whether the current index changes or not AND regardless of whether the item is set by the user or set programatically).
More detail:
I rely on the signals of the first QCombox to fire whenever an item is selected so that I can populate the next QCombobox in the gui. I then rely on THAT QCombobox to emit a signal so that I can populate the next one. And so on.
I want to pre-select an item in each QCombobox based on the user's last interaction with the gui.
I have a unique function per QCombobox that is responsible for populating and pre-selecting just that QCombobox. The code looks something like this:
comboBox1.blockSignals(True)
comboBox1.clear()
comboBox1.addItems(sorted(itemList))
comboBox1.blockSignals(False)
comboBox1.setCurrentIndex(intLastSavedState1)
where intLastSavedState1 is an integer that is derived from the text that was last selected by the user the last time they had used the app. I had hoped that the last line of this function would fire a signal that would cause the next combo box's function to load and pre-select an item (comboBox2). And that action would then cause the next comboBox's function to activate and it would cascade to the next and the next. But it is not working across all cases.
I have tried two versions of the signals:
self.connect(comboBox1, QtCore.SIGNAL("currentIndexChanged(const QString&)"), self.load_comboBox2)
and
self.connect(comboBox1, QtCore.SIGNAL("activated(const QString&)"), self.load_comboBox2)
In the first case, the signal will fire only if the intLastSavedState1 is different than whatever is currently selected in the combo box. This causes an issue if the user had last selected item 0 from that list. In this case QT does not recognize my script setting the the current index to 0 as being a change (since after loading the box it appears to think it is already on index 0), and so the signal does not fire.
In the second case, the signal will fire regardless of what is currently selected in the combo box... but only if activated by the user. It will not fire when my script tries to set the current index programatically.
These appear to be my only two options regarding the signals. So... is there another way of pre-selecting items in a QCombobox that will trigger a signal each and every time?
Well... sometimes just the act of asking a question can lead you to a (partial) answer.
I have a work-around but I am still interested in hearing if someone has a better idea.
I am now programatically setting the index of the QCombobox to -1 immediately after loading it up. Then, when I programatically set the actual index based on the user's history, it will always be considered a change (i.e. it will never be -1) and the signal will fire
using: currentIndexChanged(const QString&)
So my code looks like this now:
comboBox1.blockSignals(True)
comboBox1.clear()
comboBox1.addItems(sorted(itemList))
comboBox1.setCurrentIndex(-1)
comboBox1.blockSignals(False)
comboBox1.setCurrentIndex(intLastSavedState1)
and my signal looks like this:
self.connect(comboBox1, QtCore.SIGNAL("currentIndexChanged(const QString&)"), self.load_comboBox2)
This functions... does anyone have a better idea?
Thanks agian.
You can check current Index of your ComboBox and then either call your slot or do call setCurrentIndex().
Example:
if comboBox1.currentIndex() == index:
self.load_comboBox2(index)
else
comboBox1.setCurrentIndex(index)
This way you will not end up calling slot twice.

Implementing Undo/Redo within a TextArea

Im wondering how to implement undo redo functionality with a TextArea. I already have an undoredo framework functionality working, now I have two questions.
When do I start/stop a new undo/redo command, eg when a user hits undo, how far back do I go.
How do I implement this(1.) in a normal TextArea
My thinking:
I thinking that I should create a new undo command, when anything but a alphanumber+space is hit. To do this I would use the keyDown event and test if the key is alpha num if it is not I will reset the command.
Sound good?
Listening for keydown events would miss any text editing that user does with the mouse (cut/copy/paste).
I think a better approach would be to listen for 'change' event on the control (which fires whenever the content changes through user input), and just push the full content of the control (its 'text' or 'htmlText' attribute) with every change event into a undo-buffer (an Array of Strings). I assume that the memory usage is not an issue (it probably isn't, depending on the expected size of the controls content and number of undo levels).
This way, you implement undo/redo just by copying the corresponding control state (moving up and down through array, basically) in the undo buffer back into the control.
The 'proper' approach would be to track the actual edits, and would be condsiderably more complicated.
1.When do I start/stop a new undo/redo command, eg when a user hits undo, how far back do I go.
Do you think your users will need to undo multiple steps? If so, then you may want to have a history (e.g. Paint .NET) and allow infinite undo-s. Otherwise, just remember the most recently performed action.
1.) You should listen for the Event.CHANGE event on the TextField and create a history step each time the event is fired. A history step consists in your case of two values: old and new.
Old is the value of the TextField before change, new is its value after the change.
2.) Your history is a sequence of actions or you can use the Memento Pattern. I think actions are much easier to use. A history action has two methods, undo() and redo(). So in undo() you have to say textField.text = oldContent and in the redo() method you say textField.text = newContent. Your history will also need a pointer to the current action.
3.) To make it a little bit better. You should not listen only for Event.CHANGE but instead listen for the first CHANGE and then the next FOCUS_OUT for that TextField. In that case, a history step is only created once I stop editing the TextField. But it depends on your TextField and how you want to distribute history steps. A multiline TextField should not create a history step only on FOCUS_OUT :)

Resources