Qtreewidget -- undo the selection change - qt

In wx-widget we can undo any event by calling VETO().
Here i am doing my first GUI in QT. I have created a new node test case 3.
Now i want that selection on tree should be only allowed to changed till i have not saved this new node. If i have not saved this node at least once, selection change should revert back to this unsaved node test case 3. To indicate that i have saved the node i am using a global variable signal.
How can i achive it?
I tried something like this but no result.
Inside selection changed event handler when new node created signal is set, then if i change the treewidget selection i am trying to set the selection of the treewidget to the last item of the treewidget:
QPoint prevPoint;
QModelIndex index;
int count = ui->treeWidgetLeft->topLevelItemCount();
//prevPoint.setX(currentXmlRootNodeNumber +1 );
prevPoint.setX(count);
prevPoint.setY(0);
index = ui->treeWidgetLeft->indexAt(prevPoint);
abortEvent = TRUE;
ui->treeWidgetLeft->selectionModel()->select(index ,QItemSelectionModel::Select);
And check at begning of selection changed event handler:
if (abortEvent) {
abortEvent = false;
return;
}

That's not how the users expect treewidget and selections to work. I suggest that you reconsider the workflow of your program.
What if you let the user change the test case without saving when the user selects another test case from the treewidget? Show a messagebox and ask if the user wants to save. Then save/discard and change the test case to the one the user clicked.
Or remove the "Save" button completely. Always save the data the user enters to the test case. If you like, you could add an undo button instead. It would restore the test case to the state it was when the user selected it. That is quite easy to implement, just create a copy of the test case when editing starts and revert to that if the user selects the undo operation.
And I really recommend that you change the "Perv" button to "Prev".

Related

Python pyqt - determine if signal is emitted by user action

I have a QtableView which column are resized when new data is added. User can also change the column width by direct interaction with the horizontalHeader (click-drag). Both events emit the signal QtableView.horizontalHeader().sectionResized().
I would like to determine if this signal was emitted by the user changing the column width or by a refresh from a function call. How can I distinguish the events?
I tried using sender() but both action refer to the same sender.
This is something you must do yourself - there is no magic method for it.
It would probably be possible to subclass QHeaderView and monitor the various user-initiated events that may trigger a resize, but that kind of solution might be hard to get right. A much simpler and more obvious solution would be to set a flag when new data is being added to the table and unset it once the columns have been resized. Then you can check the flag inside any slots connected to the sectionResized signal to see what triggered it:
def updateTable(self):
self._updating = True
# add new data...
# resize columns...
self._updating = False
def handleSectionResized(self):
if self._updating:
print('updating')
else:
print('user interaction')

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

Qt: Check mark next to a menu item

In my application a user is able to select a comport from a list of all available comports. This is to be done in a menu that is populated with QAction widgets. However, the current code does not show which port has been selected, and in order to make the application more intuitive, I would like to indicate which port has been selected, thus showing the user what state the program is in. By including some sort of icon (like a check mark, for example) in the menu right next to the selected port, it would be obvious which port has been selected. What is the correct way to do this?
My code:
# Populate the serial port menu with all the available ports.
for port in comports():
_port = QtWidgets.QAction(port[0], mainWindow)
_port.setCheckable(True) # WRONG!
self.menuChoose_port.addAction(_port)
_port.triggered.connect(self.comportSelect)
This code obviously doesn't do what I want, because it puts checkboxes next to each menu item. Additionally, it allows the user to check more than one comport at time, which is not at all desirable.
Coming to this years late, and in C++, but in the event someone down the line gets here, this is what I did (disclaimer: this is a bit of a hack):
Keep a reference to the action that's currently selected
When selected, set the checked and checkable flags to true for the selected item.
Once the selection changes, remove the check from the selected action, and update the reference
This seems to allow for items to be checked, but only one item at a time. This solution really only works for trying to maintain a single selection.
// in the header:
QAction *selectedAction = nullptr;
// in the cpp file
QAction *act = new QAction("exampleItem");
connect(act, &QAction::triggered, [this, act] {
if (selectedAction != nullptr) {
selectedAction->setChecked(false);
selectedAction->setCheckable(false);
}
act->setCheckable(true);
act->setChecked(true);
selectedAction = newAction;
})
menu->addAction(act);

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