How to control focus of QWidgetAction's child widgets? - qt

for my application I'm trying to put login control into a QMenu and am struggling with controlling the focus policy.
This is my custom login widget:
class LoginWidget(QWidget):
def __init__(self, parent=None):
super(LoginWidget, self).__init__(parent)
mainLayout = QVBoxLayout()
layoutH = QHBoxLayout()
nameField = QLineEdit()
pwdField = QLineEdit()
pwdField.setEchoMode(QLineEdit.EchoMode(2))
btnSubmit = QPushButton('log in')
btnSubmit.setIcon(IconCache.getIcon('login'))
for w in (nameField, pwdField):
layoutH.addWidget(w)
mainLayout.addLayout(layoutH)
mainLayout.addWidget(btnSubmit)
self.setLayout(mainLayout)
I then add the above widget to my menu like this:
app = QApplication([])
menu = QMenu()
settingsAction = QAction('settings', menu)
loginAction = QWidgetAction(menu)
loginAction.setDefaultWidget(LoginWidget())
menu.addAction(settingsAction)
menu.addAction(loginAction)
btn = QToolButton()
btn.setText('menu button')
btn.setMenu(menu)
btn.setPopupMode(QToolButton.InstantPopup)
btn.show()
sys.exit(app.exec_())
The problem is that when you open the menu, click into the name field to fill in the user name, then hit the tab key, focus jumps to the "settings" action rather than to the password widget inside the LoginWidget.
I tried setFocusPolicy(Qt.StrongFocus) on the LoginWidget as well as it's pwdField but to no avail.
Can this be done?
Thanks in advance,
frank

The QMenu has special handling for Tab / Backtab, which effectively converts them into Up / Down arrow key presses.
However, the real source of the problematic behaviour is the focusNextPrevChild method, which keeps forcing the focus back to the menu. Fortunately, this method is virtual, so it can be overridden in a subclass, like so:
class Menu(QtGui.QMenu):
def focusNextPrevChild(self, next):
return QtGui.QWidget.focusNextPrevChild(self, next)
This will restore normal tabbing between child widgets.
To also enable keyboard navigation from child widgets back to normal menu items, make sure the LoginWidget has a focus proxy, like so:
class LoginWidget(QtGui.QWidget):
def __init__(self, parent=None):
...
self.setFocusPolicy(QtCore.Qt.TabFocus)
self.setFocusProxy(nameField)

http://qt-project.org/doc/qt-4.8/qwidget.html#setTabOrder
http://qt-project.org/doc/qt-4.8/focus.html#tab-or-shift-tab
Tab or Shift+Tab
Pressing Tab is by far the most common way to move focus using the keyboard. (Sometimes in data-entry applications Enter does the same as Tab; this can easily be achieved in Qt by implementing an event filter.)
Pressing Tab, in all window systems in common use today, moves the keyboard focus to the next widget in a circular per-window list. Tab moves focus along the circular list in one direction, Shift+Tab in the other. The order in which Tab presses move from widget to widget is called the tab order.
You can customize the tab order using QWidget::setTabOrder(). (If you don't, Tab generally moves focus in the order of widget construction.) Qt Designer provides a means of visually changing the tab order.
Since pressing Tab is so common, most widgets that can have focus should support tab focus. The major exception is widgets that are rarely used, and where there is some keyboard accelerator or error handler that moves the focus.
For example, in a data entry dialog, there might be a field that is only necessary in one per cent of all cases. In such a dialog, Tab could skip this field, and the dialog could use one of these mechanisms:
If the program can determine whether the field is needed, it can move focus there when the user finishes entry and presses OK, or when the user presses Enter after finishing the other fields. Alternately, include the field in the tab order but disable it. Enable it if it becomes appropriate in view of what the user has set in the other fields.
The label for the field can include a keyboard shortcut that moves focus to this field.
Another exception to Tab support is text-entry widgets that must support the insertion of tabs; almost all text editors fall into this class. Qt treats Ctrl+Tab as Tab and Ctrl+Shift+Tab as Shift+Tab, and such widgets can reimplement QWidget::event() and handle Tab before calling QWidget::event() to get normal processing of all other keys. However, since some systems use Ctrl+Tab for other purposes, and many users aren't aware of Ctrl+Tab anyway, this isn't a complete solution.
So you probably will want to use:
QWidget.setTabOrder( nameField, pwdField )
QWidget.setTabOrder( pwdField, btnSubmit )
or something similar.
Hope that helps.

Related

How can I click multiple buttons by dragging in QtGui?

I have a program that creates a grid of buttons using PyQt (all QPushButtons in a QGridLayout), like this:
I was wondering how I can drag my mouse while holding mouse1 down to run each button's function for each one that the mouse is dragged over. Right now, when dragging and holding, only the button the click was started on is triggered, and no other buttons can be selected until mouse release. Is there a function that just runs it on press or release, or unselects the button after the function is ran?
For reference, my code for the currently selected button is as follows:
self.button.pressed.connect(lambda: self.click_func(self_global, x, y,
btn_id))
The function isn't important, and would just take up more room here, but I hope you get the point.
You could use an eventFilter. This allows you to send events to another QObject (usually the parent widget of the buttons) to decide what to do with them.
class MyWidget(QtGui.QWidget):
def createButton(self):
button = QtGui.QPushButton()
button.setMouseTracking(True)
button.installEventFilter(self)
return button
def eventFilter(self, obj, event):
if isinstance(obj, QtGui.QPushButton):
if event.type() == QtCore.QEvent.Enter:
if event.buttons() & QtCore.Qt.LeftButton:
print 'Mouse Pressed Over Button'
return False
Looking at your example, you may want to also consider using a QGraphicsView and QGraphicsScene. They are good building blocks for creating truly custom widgets. It's possible you may be able to hack QPushButtons to behave in a way they weren't intended, but for custom drawn widgets, QGraphicsViews are the way to go.

How to capture Tab Key in Qt widget

I want to implement my own sequence for changing the focus of the active child widget using the Tab key. How to capture the Tab Key press event? I am using Qt5.2
If you want to change focus with Tab , you don't need to do those works, Qt has it as a feature.
First: set the desired widgets to be Qt::TabFocus or Qt::StrongFocus by QWidget::setFocusPolicy( Qt::FocusPolicy policy )
For example, if you want to rotate between 3 QLineEdit and 1 QCombobox, you have to assure that their focus policy have been set right. (Normally either Qt::TabFocus or Qt::StrongFocus will be set as default, but sometimes you might want to escape some widgets from being tabbed)
Second: go to designer mode and click "Edit Tab Order" to enter the tab-order editing mode
Third: After seeing the numbers, click on them until you got the desired sequence order.
(Picture from Qt official site)
Have a mouse press event or a event filter, get to the point where you have a QKeyEvent
Then only do something if tab was pressed
key_event->button() == Qt::Key_Tab

QTableView: Best way to change activation-trigger to double-click

In my application, I have one tableview of items, and a side-panel "preview":ing the latest selected item.
I want clicking on an item to change the selection, and double-clicking to cause a "run"-action to be performed. More specifically, I want the "run"-action (including key-navigation and pressing enter) to be bound to the "activation" of the item in the table-row.
My problem is; single-clicks does not only change the selection, but fires the "activated" signal on the item. I would like to tweak it such that:
Navigation Keys, Single Mouse Click: Selection-change, update preview-panel
Enter Key, Double Mouse Click: Activate/run/open action triggered.
Is there a nice clean way to do it, or are overriding the onclick/doubleclick events my best option? Or is there some other tabular list-widget better suiting my needs?
I would connect the slot for the preview action to the currentChanged() signal of the table view's selectionModel(). This covers single clicks and key navigation.
Then there's two options for the double clicks and Enter key presses:
Subclass your tableview, override doubleClickEvent() and keyPressEvent() and fire your custom signal in there, with maybe the model index or something else as an argument. Then just connect your run method to your own signal as you have full control over when it is fired.
If you don't want to subclass, you can use the installEventFilter() mechanism.
Either I'm getting your approach wrong or I'm too tired, but if you want to trigger a run event you should avoid the activated signal completely. Set the signal slot mechanism so that your double click and Enter key press event trigger your run() function, and then the single click/nav buttons should trigger the 'activated' slot which will return your index in the tableview.
I'm pretty certain Qt wants you to be explicit about which signal points to which slot or it'll ignore it or point to a default.

Keyboard control of GUI dialogs - Should default button change with focus?

In GUI dialogs, most applications provide for keyboard control as follows:
Enter key - presses the default button. (Default is usually indicated with a bold button border.)
Esc key - presses the Cancel or close button.
Space key - presses widget that currently has keyboard focus.
Tab key - advances focus to next widget.
Question is, when keyboard focus is on a widget that is a button, should the default button be changed to be the one with focus?
I see some issues with this behavior:
The display noise of redrawing buttons to unbold the outline of original default button and rebold the button under focus as being new default.
The Space key is now somewhat redundant with Enter key.
There is no keyboard accelerator to get the normal default button now (Usually the OK button).
However, it seems the trend has been in this direction to change the default button with focus change to another button. What is the rationale for this departure from the early GUIs? It would seem to provide less functionality given there is no way to press the original default button. Did people find that the original model was too complicated for users to understand? I would think keyboard control of dialogs would be a task for advanced users who would have no trouble understanding the model and prefer to have accelerator for current button (Space) and original default button (Enter) at all times.
Note that Qt for one is supporting the change: QPushButton's autoDefault property is responsible for the behavior of changing the default button. By default its value is true. Therefore, you must take extra action to set it to false for all buttons, to prevent them from becoming the default button when focused.
This is not a "departure from the early GUIs", at least not if by "early GUIs", you mean Windows 1.0. The behavior that you describe has been this way since the beginning.
The focused button is always "pushed" when the Enter key is pressed. The default button is only triggered in the following two situations:
The default button has the focus (which it does by default), or
The focus is on a control that does not process Enter key presses (such as a static control, or a single-line textbox that does not have the ES_WANTRETURN style flag set).
The famous Win32 blogger Raymond Chen has a post explaining this behavior (focus specifically on the last two quoted paragraphs):
A dialog box maintains the concept of a "default button" (which is always a pushbutton). The default button is typically drawn with a distinctive look (a heavy outline or a different color) and indicates what action the dialog box will take when you hit Enter. Note that this is not the same as the control that has the focus.
For example, open the Run dialog from the Start menu. Observe that the OK button is the default button; it has a different look from the other buttons. But focus is on the edit control. Your typing goes to the edit control, until you hit Enter; the Enter activates the default button, which is OK.
As you tab through the dialog, observe what happens to the default button. When the dialog box moves focus to a pushbutton, that pushbutton becomes the new default button. But when the dialog box moves focus to something that isn't a pushbutton at all, the OK button resumes its position as the default button.
The dialog manager remebers which control was the default button when the dialog was initially created, and when it moves focus to something that isn't a button, it restores that original button as the default button.
The behavior that I would expect is:
If I press enter when the window just pop up, it should press the default button
If I press tab, I start navigating through the widgets. In this case there are two options:
2.1 I press enter - this event should be delivered to the focused widget. There's no need to change the default button - simply hand the event to the focused widget.
2.2 I press escape. In this case, everything should go back to the state after the window is created.
Notes:
I come from a mixed background - I don't know if I learned this in windows, linux or Mobile OSes! This is just how I expect things to work out.
I don't use the space key (didn't know it's functionality)

How to remove focus from a QLineEdit when anyplace else on the window is clicked

I'm working on a custom Qt button that allows you to edit the text on the button if you double click it. When the button is double clicked, a QLineEdit appears where the text on the button is allowing the user to edit the text on the button. My requirement is that if the user clicks anywhere in the application window, the QLineEdit should disappear and cancel the edit operation. This works in some cases. Specifically, it works if I click on anything that is capable of text entry. Other portions of the window don't work as expected. I'll click on a blank portion of the application window, and the QLineEdit retains its focus. How can I remove its focus in these cases?
I've found a solution that seems to work, though I'm still open to other options if there are any. I'm using PyQt4, so my example is in python:
Create a subclass of QLineEdit just so I have a new type. I don't want or need this behavior on all QLineEdit instances; just these specific ones.
class MyLineEdit(QtGui.QLineEdit):
pass
Now, in my QMainWindow subclass, I override the mousePressEvent() implementation. It gets the currently focused widget. If that widget is of type MyLineEdit, clear the focus.
class MyMainWindow(QtGui.QMainWindow):
def ...
def mousePressEvent(self, event):
focused_widget = QtGui.QApplication.focusWidget()
if isinstance(focused_widget, MyLineEdit):
focused_widget.clearFocus()
QtGui.QMainWindow.mousePressEvent(self, event)
def ...
This gets me the behavior I'm looking for so that if the user clicks anywhere on the application's window, the focus is cleared.
Edit: I did find one caveat to this. I have a QTreeView in the main window. If the user clicks on the tree view, focus is not removed from the text edit field.
Catch the clicked() signal of your parent widget and call yourLabel->clearFocus() (that unfortunatelly happens to not be a slot, making things more complicated) there.
I followed Grant Limberg instruction here but figured out that, in my case, a simple:
QApplication.focusWidget().clearFocus()
would fix the problem.
I'm not sure if this also works in Qt4 (I'm using PyQt5) but you can change the FocusPolicy of the QMainWindow or parent widget to clear the focus in the QLineEdit. As per https://doc.qt.io/qt-5/qwidget.html#focusPolicy-prop
I've changed the policy of my QMainWindow to Qt.StrongFocus and it worked like the functionality described in the question.
If done in C++ I would do something along the lines of:
connect(myWidgets->MyLineEdit, SIGNAL(returnPressed()), this, SLOT(onLineEditDone());
void onLineEditDone()
{
myWidgets->MyLineEdit->clearFocus();
}
For this particular case I would use editingFinished() instead of returnPressed(), probably, but I would NOT use textChanged(QString).

Resources