QLineEdit not updating text with QKeyEvent - python

I'm trying to implement a virtual keyboard widget. The simplest way I could think of is to create QKeyEvent instances and send them with QApplication.postEvent() to the widget in focus.
First, I'm trying to update a fixed QLineEdit that I have, so the code is:
self.pushButton_A.clicked.connect(self.virtualKeyPress)
[...]
def virtualKeyPress(self):
self.keyPress = QKeyEvent(QEvent.KeyPress, Qt.Key_A, Qt.NoModifier)
QApplication.postEvent(self.lineEdit, self.keyPress)
But the QLineEdit instance won't update its text in the GUI!
Clues? Cheers and thanks!
RESOLVED: (kudos to HeyYO)
self.pushButton_A.clicked.connect(self.virtualKeyPress)
[...]
def virtualKeyPress(self):
self.keyPress = QKeyEvent(QEvent.KeyPress, Qt.Key_A, Qt.NoModifier, 'A')
QApplication.postEvent(self.lineEdit, self.keyPress)
In my case, inplace of Qt.Key_A I set that argument to 0 so that I can connect all my buttons to the virtualKeyPress method. I also had to set the focus policy for all the buttons to 'no focus' (did it directly in Qt Designer). The final code was the following:
def virtualKeyPress(self):
self.keyPressed = QString(self.sender().text())
self.keyPress = QKeyEvent(QEvent.KeyPress, 0, Qt.NoModifier, self.keyPressed)
self.focusWidget = QApplication.focusWidget()
QApplication.postEvent(self.focusWidget, self.keyPress)

Have you tried specifying the text argument;
self.keyPress = QKeyEvent(QEvent.KeyPress, Qt.Key_A, Qt.NoModifier, "A")
It worked for me, in Qt5&C++, so I'm assuming it will work for you as well.

Related

Menubar sometimes does not become un-greyed when QFileDialog closes

OS: W10. This may be significant. If you have different results on a different platform, feedback would be helpful.
Here is an MRE. If you run it and go Ctrl+O, the menu labels become greyed. If you select a file in the QFileDialog by clicking the "Open" button or using its mnemonic (Alt+O), the open-file dialog is dismissed and the "Files" and "Help" menus become un-greyed.
However, if you go Ctrl+O again, and this time enter the name of a file in the "File name" box (QLineEdit), and then press Return, the dialog is dismissed (with a successful selection result) but the "Files" and "Help" menus remain greyed-out. It looks like this:
import sys, os
from PyQt5 import QtWidgets, QtCore, QtGui
class MainWindow(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle('Greying of menus MRE')
self.setGeometry(QtCore.QRect(100, 100, 400, 200))
menubar = QtWidgets.QMenuBar(self)
self.setMenuBar(menubar)
self.files_menu = QtWidgets.QMenu('&Files', self)
menubar.addMenu(self.files_menu)
self.help_menu = QtWidgets.QMenu('&Help', self)
menubar.addMenu(self.help_menu)
self.new_action = QtWidgets.QAction('&New', self)
self.files_menu.addAction(self.new_action)
self.open_action = QtWidgets.QAction('&Open', self)
self.files_menu.addAction(self.open_action)
self.open_action.setShortcut("Ctrl+O")
self.open_action.triggered.connect(self.open_file)
def focusInEvent(self, event ):
print('main_window focusInEvent')
super().focusInEvent(event)
def focusOutEvent(self, event ):
print('main_window focusOutEvent')
super().focusInEvent(event)
def activateWindow(self):
print('main_window activateWindow')
super().activateWindow()
def open_file(self):
print('open file')
main_window_self = self
# open_doc_dialog = QtWidgets.QFileDialog(self.get_main_window())
class OpenDocFileDialog(QtWidgets.QFileDialog):
def accepted(self):
print('accepted')
super().accepted()
def accept(self):
print('accept')
super().accept()
def close(self):
print('close')
super().close()
def done(self, r):
print(f'done r {r}')
# neither of these solves the problem:
# main_window_self.activateWindow()
# main_window_self.files_menu.activateWindow()
super().done(r)
def hide(self):
print(f'hide')
super().hide()
def focusInEvent(self, event ):
print('focusInEvent')
super().focusInEvent(event)
def focusOutEvent(self, event ):
print('focusOutEvent')
super().focusInEvent(event)
def activateWindow(self):
print('activateWindow')
super().activateWindow()
open_doc_dialog = OpenDocFileDialog(self)
open_doc_dialog.setWindowTitle('Choose file')
open_doc_dialog.setDirectory(os.getcwd())
# we cannot use the native dialog, because we need control over the UI
options = open_doc_dialog.Options(open_doc_dialog.DontUseNativeDialog)
open_doc_dialog.setOptions(options)
open_doc_button = open_doc_dialog.findChild(QtWidgets.QDialogButtonBox).button(QtWidgets.QDialogButtonBox.Open)
lineEdit = open_doc_dialog.findChild(QtWidgets.QLineEdit)
# this does not solve the problem
# lineEdit.returnPressed.disconnect()
# lineEdit.returnPressed.connect(open_doc_button.click)
print(f'open_doc_button {open_doc_button}, lineEdit {lineEdit}')
# show the dialog
dialog_code = open_doc_dialog.exec()
if dialog_code != QtWidgets.QDialog.Accepted: return
sel_files = open_doc_dialog.selectedFiles()
print(f'sel_files: {sel_files}')
app = QtWidgets.QApplication([])
main_window = MainWindow()
main_window.show()
sys.exit(app.exec())
This problem can be understood, if not solved, with reference to this answer.
Note that this greying-out is not disablement. As explained in the above link, this has to do with "active/inactive states" of the menus (or their labels). The menus remain enabled throughout, although in this case it's impossible to know that while the open-file dialog is showing because it is modal. Clicking on one menu after the dialog has gone, or just hovering over it, is enough to un-grey them both...
The explanation, as I understand it, is that the "File name" box QLineEdit has a signal, returnPressed, which appears to activate something subtley different to the slot which is invoked when you use the "Choose" button. You can see I have experimented with trying to re-wire that signal, to no avail.
The method done of the QFileDialog appears to be called however the dialog closes (unlike close!), so I tried "activating" the main window... and then the individual QMenus... Doesn't work.
I am not clear how to get a handle on this "active state" business or why the slot connected to returnPressed is (seemingly) unable to give the "active state" back to the menus when the other slot manages to do so.
Edit
Searching on Musicamante's "unpolishing" suggestion led me to this:
lineEdit.returnPressed.disconnect()
def return_pressed():
style = main_window_self.menubar.style()
style.unpolish(main_window_self.menubar)
open_doc_button.click()
lineEdit.returnPressed.connect(return_pressed)
... unfortunately this doesn't work.
This looks like a possible Windows-related bug, since I can't reproduce it on Linux. As a work-around, you could try forcing a repaint after the dialog closes:
# show the dialog
dialog_code = open_doc_dialog.exec()
self.menubar.repaint()
Finally got it, thanks to Musicamante's suggestion:
lineEdit.returnPressed.disconnect()
def return_pressed():
style = main_window_self.menubar.style()
style.unpolish(main_window_self.menubar)
open_doc_button.click()
main_window_self.menubar.repaint()
lineEdit.returnPressed.connect(return_pressed)
... I actually tried this several times, just to make sure it was doing what was intended. So in fact, fortunately, no single-shot timer was needed in this case.

How to work with the "?" (what's this widget) on the title bar of a PyQT Dialog

On the right of the title bar of a PyQt QDialog (see below, next to the "x") there is a "?" that is supposed to help the user query help for any other widget on the Dialog window.
What should I do (programmatically) to get it to work. Once the "?" isClicked, one should be able to capture the next widget clicked and provide a ToolTip or something like that. In PyQt, I do not know how to capture the isClicked event on the "?".
I have seen a couple of posts where the question was how to make the "?" disappear, but the discussion there uses Qt, not PyQt, so I do not understand it, and they are not talking about what I need. I need to make it work as intended. See How can I hide/delete the "?" help button on the "title bar" of a Qt Dialog? and PyQt4 QInputDialog and QMessageBox window flags
You can set the whatsThis property to any widget you want:
self.someWidget.setWhatsThis('hello!')
From that point on, whenever you click on the "?" button and then click on that widget, a tooltip with that text will be shown.
Since the "what's this" mode is individually set to widgets, there's no easy way to capture it globally (as far as I know of) because if the widget has no whatsthis property set that feature won't be available for it.
Also, whenever you enter the "what's this" mode, the cursor will probably change according to the contents of the whatsthis property: if it's not set, the cursor will probably show a "disabled" icon.
I've created a basic workaround for this issue, which automatically enables any child widget's whatsthis (if none is already set) whenever the mode is activated: as soon as the EnterWhatsThisMode is fired, it automatically installs a custom object that acts as an event filter, and emits a signal if the whatsthis event is called; as soon as the mode exits, the filter is removed.
I used a separate object for the event filter because there's no way to know what event filter have been already installed to a widget, and if you already installed the parent's one, removing it automatically would be an issue.
class WhatsThisWatcher(QtCore.QObject):
whatsThisRequest = QtCore.pyqtSignal(QtWidgets.QWidget)
def eventFilter(self, source, event):
if event.type() == QtCore.QEvent.WhatsThis:
self.whatsThisRequest.emit(source)
return super(WhatsThisWatcher, self).eventFilter(source, event)
class W(QtWidgets.QWidget):
def __init__(self):
QtWidgets.QWidget.__init__(self)
layout = QtWidgets.QVBoxLayout(self)
hasWhatsThisButton = QtWidgets.QPushButton('Has whatsThis')
layout.addWidget(hasWhatsThisButton)
hasWhatsThisButton.setWhatsThis('I am a button!')
noWhatsThisButton = QtWidgets.QPushButton('No whatsThis')
layout.addWidget(noWhatsThisButton)
self.whatsThisWatchedWidgets = []
self.whatsThisWatcher = WhatsThisWatcher()
self.whatsThisWatcher.whatsThisRequest.connect(self.showCustomWhatsThis)
whatsThisButton = QtWidgets.QPushButton('Set "What\'s this" mode')
layout.addWidget(whatsThisButton)
whatsThisButton.clicked.connect(QtWidgets.QWhatsThis.enterWhatsThisMode)
def event(self, event):
if event.type() == QtCore.QEvent.EnterWhatsThisMode:
for widget in self.findChildren(QtWidgets.QWidget):
if not widget.whatsThis():
# install the custom filter
widget.installEventFilter(self.whatsThisWatcher)
# set an arbitrary string to ensure that the "whatsThis" is
# enabled and the cursor is correctly set
widget.setWhatsThis('whatever')
self.whatsThisWatchedWidgets.append(widget)
elif event.type() == QtCore.QEvent.LeaveWhatsThisMode:
while self.whatsThisWatchedWidgets:
widget = self.whatsThisWatchedWidgets.pop()
# reset the whatsThis string to none and uninstall the filter
widget.setWhatsThis('')
widget.removeEventFilter(self.whatsThisWatcher)
return super(W, self).event(event)
def showCustomWhatsThis(self, widget):
widgetPos = widget.mapTo(self, QtCore.QPoint())
QtWidgets.QWhatsThis.showText(
QtGui.QCursor.pos(),
'There is no "what\'s this" for {} widget at coords {}, {}'.format(
widget.__class__.__name__, widgetPos.x(), widgetPos.y()),
widget)
A couple of notes about this:
I used a button to activate the whatsthis mode, as on my window manager on Linux there's no window title button for that;
Some widgets may contain subwidgets, and you'll get those instead of the "main" one (the most common case are QAbstractScrollArea descendands, like QTextEdit or QGraphicsView, which might return the viewport, the inner "widget" or the scrollbars);
By default the task of that button is to enable whatsThis: press "?", then press the widget and you will see the message associated with whatsThis property.
If you want to add other actions(open url, add QToolTip, etc) you can monitor the QEvent::EnterWhatsThisMode and QEvent::LeaveWhatsThisMode events overriding the event() method or using an eventFilter().
from PyQt5 import QtCore, QtGui, QtWidgets
class Dialog(QtWidgets.QDialog):
def event(self, event):
if event.type() == QtCore.QEvent.EnterWhatsThisMode:
print("enter")
QtGui.QDesktopServices.openUrl(QtCore.QUrl("foo_url"))
elif event.type() == QtCore.QEvent.LeaveWhatsThisMode:
print("leave")
return super().event(event)
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
w = Dialog()
w.setWhatsThis("Whats this")
w.setWindowFlags(
QtCore.Qt.WindowContextHelpButtonHint | QtCore.Qt.WindowCloseButtonHint
)
w.resize(640, 480)
w.show()
sys.exit(app.exec_())

IPython nbwidgets: Toggle Visiblity by button click

I'm using the ToggleButton and want to link it's value to the visilbiity of another widget.
I've come accross the Widget Events, but it is unclear to me how to bind the style property of the other widget to the value of the ToggleButton.
Has anyone done something similar?
I would use an observe call on the ToggleButton to change the visibility of the other widget. A simple example below.
toggle = ipyw.ToggleButton(description='Toggle visible')
to_hide = ipyw.IntRangeSlider(description = 'hide me')
display(to_hide)
display(toggle)
def hide_slider(widg):
if widg['new']:
to_hide.layout.display = 'none'
else:
to_hide.layout.display = ''
toggle.observe(hide_slider, names=['value'])

Validate if editable QCombobox input is a directory via QValidator

I try to validate if a editable QCombobox input is a directory or not before it gets added to the QCombobox.
from PySide import QtGui, QtCore
class DirValidator(QtGui.QValidator):
def __init__(self, cb_input):
super(DirValidator, self).__init__()
self._input = cb_input
def validate(self, _text, _pos):
_dir = QtCore.QDir(_text)
if self._input.hasFocus(): # ignore validation while editing not complete
return QtGui.QValidator.Acceptable
if QtCore.QDir.exists(_dir):
return QtGui.QValidator.Acceptable
return QtGui.QValidator.Invalid
dir_validator = DirValidator(self.cb_path.lineEdit())
self.cb_path.setValidator(dir_validator)
sadly it does not work properly because every input still gets added to the combobox when i hit enter.
Any suggestions?
EDIT: i also tried to use the validator on the QLineEdit like so:
dir_validator = DirValidator(self.cb_path.lineEdit())
self.cb_path.lineEdit().setValidator(dir_validator)
Does not work either.
EDIT: It kinda works...but when i press return "hasFocus" is still True so it is just accepting the input and then of course adding it to the combobox. if i get rid of "if self._input.hasFocus():" it does not accept any input if i type it in...just if a paste a complete directory path.
So what i need is a way to check if the edit is finished to then check if it is a directory.
And as far as i understand i can only check this in a combobox via QValidator...because it adds the input to the box right away...before i can intercept it in any way.
EDIT: i did find solution for my case. I just abandoned the whole validator approach. The purpose of that was to prevent the combobox from creating a new item if it was no valid directory...what i now did instead was to validate the input after it was finished by taking advantage of the QLineEdit().editingFinished() signal. After it created the new Item i just removed it again if the input was not valid and it also gave me the opportunity to add a error Popup telling the user that the input was no directory.
I do not see the need for hasFocus(), because if you are writing to the QLineEdit it obviously has the focus. If the path is incorrect then you must return a QValidator::Intermediate:
from PySide import QtGui, QtCore
class DirValidator(QtGui.QValidator):
def validate(self, _text, _pos):
_dir = QtCore.QDir(_text)
if _dir.exists():
return QtGui.QValidator.Acceptable
return QtGui.QValidator.Intermediate
if __name__ == '__main__':
import sys
app = QtGui.QApplication(sys.argv)
combo = QtGui.QComboBox(editable=True)
dir_validator = DirValidator(combo.lineEdit())
combo.setValidator(dir_validator)
combo.show()
sys.exit(app.exec_())

Getting Information Which QLabel Clicked in PyQT

I have a list of QLabel and want to learn which QLabel clicked . When i look for the making QLabel clickable , this code has worked :
labels[i].mousePressEvent = self.print_some
def print_some(self, event):
print("Clicked")
But I didn't figure out which object clicked . How can i do that ?
You can easily make custom receivers for events, which would contain the event source information:
import functools
labels[i].mousePressEvent = functools.partial(self.print_some, source_object=labels[i])
def print_some(self, event, source_object=None):
print("Clicked, from", source_object)

Categories