I have developed a gui in Qt Designer, that among other things, displays a QTextEdit widget.
I need to handle the user entering the certain key combinations (TAB key, SHIFT + ENTER and SHIFT + RETURN so far) in such a way that it doesn't display those characters in the QTextEdit widget and implements some custom functionality.
I have created a handler to detect keyReleaseEvents, but that seems to be too late to prevent tabs, carriage returns etc. getting displayed, so then I tried handling keyPressEvents, but that doesn't seem to work at all. From what I've read online, people are saying that the keyPressEvent is getting consumed by the widget and so never gets propagated up to the gui. I don't know enough about QTextEdit widgets to say if that's correct, but for now I'm assuming it is.
So then I tried this in the class __init__ method for the gui (I have left out the check for the RETURN key to keep the code short below:
self.textEdit.event = self.eventX
Here's the code for the eventX method:
def eventX(self, event):
if event.type() == QEvent.KeyPress and event.key() == Qt.Key_Tab:
print 'CODE WINDOW: Tab pressed'
self.textEdit.insertPlainText(QString('| |'))
self.textEdit.update()
event.accept()
return True
elif event.type() == QEvent.KeyPress and (event.key() == Qt.Key_Enter) and event.modifiers() & Qt.ShiftModifier:
print 'CODE WINDOW: Shift pressed with Enter'
self.submitCode()
self.textEdit.update()
event.accept()
return True
else:
event.ignore()
return QWidget.event(self, event)
This is where things get strange. The code above will sort of work if I have specified that:
verticalScrollBarPolicy = Qt.ScrollBarAsNeeded
for the QTextEdit widget, except that it won't actually display the characters in the widget, but it does seem to handle the TAB and SHIFT-ENTER/SHIFT-RETURN keys being pressed?
And then, if I set verticalScrollBarPolicy = Qt.ScrollBarAlwaysOn, the eventX method seems to get completely ignored!
This is driving me crazy at this stage. Does anyone know what I'm doing wrong?
Thanks.
UPDATE: Based on the information from MDURANT, I now have the code below that works for me:
def handleEditorKeyPress(self, event):
if event.key() == Qt.Key_Tab:
# My custom code goes here.
event.accept()
return
elif event.key() == Qt.Key_Enter and event.modifiers() & Qt.ShiftModifier:
# My custom code goes here.
event.accept()
return
elif event.key() == Qt.Key_Return and event.modifiers() & Qt.ShiftModifier:
# My custom code goes here.
event.accept()
return
else:
event.ignore()
return QTextEdit.keyPressEvent(self.textEdit, event)
An example of capturing key events. In this case, I simply pass the event through to another identical widget, but in general, you can set the event function to anything
class win(QtGui.QWidget):
def __init__(self):
QtGui.QWidget.__init__(self, parent=None)
self.box = QtGui.QHBoxLayout(self)
self.setLayout(self.box)
self.ed1 = QtGui.QTextEdit(self)
self.ed2 = QtGui.QTextEdit(self)
self.ed1.keyPressEvent = self.ed2.keyPressEvent
self.ed1.keyReleaseEvent = self.ed2.keyReleaseEvent
self.box.addWidget(self.ed1)
self.box.addWidget(self.ed2)
See the documentation of the event: http://qt-project.org/doc/qt-4.8/qtextedit.html#keyPressEvent
Related
I have a small GUI app made using pyqt5.
I found a strange problem while using eventFilter...
def eventFilter(self, source, event):
if event.type() == QtCore.QEvent.KeyPress:
# RETURN
if event.key() == QtCore.Qt.Key_Return:
if source is self.userLineEdit:
self.pswLineEdit.setFocus()
elif source is self.pswLineEdit:
self.LoginButton.click()
# TAB
elif event.key() == QtCore.Qt.Key_Tab:
if source is self.userLineEdit:
self.pswLineEdit.setFocus()
return super().eventFilter(source, event)
While pressing enter key just behave normally, tab key does not.
I don't know where the problem could be. I'm going to link a video to show the exact problem as I'm not able to describe how this is not working
Link to video
I know it's pixelated (sorry) but the important thing is the behavior of the cursor
SMALL EXAMPLE
import sys
from PyQt5.QtWidgets import QApplication, QWidget, QVBoxLayout, QLineEdit
from PyQt5 import QtCore
class App(QWidget):
def __init__(self):
super().__init__()
self.title = 'Hello, world!'
self.left = 10
self.top = 10
self.width = 640
self.height = 480
self.initUI()
def initUI(self):
self.setWindowTitle(self.title)
self.setGeometry(self.left, self.top, self.width, self.height)
self.userEdit = QLineEdit(self)
self.pswEdit = QLineEdit(self)
self.userEdit.setPlaceholderText("Username")
self.pswEdit.setPlaceholderText("Password")
self.userEdit.installEventFilter(self)
self.pswEdit.installEventFilter(self)
mainLayout = QVBoxLayout()
mainLayout.addWidget(self.userEdit)
mainLayout.addWidget(self.pswEdit)
self.setLayout(mainLayout)
self.show()
def eventFilter(self, source, event):
if event.type() == QtCore.QEvent.KeyPress:
# RETURN
if event.key() == QtCore.Qt.Key_Return:
if source is self.userEdit:
self.pswEdit.setFocus()
# TAB
elif event.key() == QtCore.Qt.Key_Tab:
if source is self.userEdit:
self.pswEdit.setFocus()
return super().eventFilter(source, event)
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = App()
sys.exit(app.exec_())
If you don't filter events, they are processed by the object and eventually propagated back to the parent.
By default, QWidget will try to focus the next (or previous, for Shift+Tab) child widget in the focus chain, by calling its focusNextPrevChild(). If it can do it, it will actually set the focus on that widget, otherwise the event is ignored and propagated to the parent.
Since most widgets (including QLineEdit) don't handle the tab keys for focus changes on their own, as they don't have children, their parent will receive it, which will call focusNextPrevChild() looking for another child widget, and so on, up to the object tree, until a widget finally can handle the key, eventually changing the focus.
In your case, this is what's happening:
you check events and find that the tab key event is received by the first line edit;
you set the focus on the other line edit, the password field;
you let the event be handled anyway by the widget, since you're not ignoring or filtering it out;
the first line edit calls focusNextPrevChild() but is not able to do anything with it;
the event is propagated to the parent, which then calls its own focusNextPrevChild();
the function checks the current focused child widget, which is the password field you just focused, and finds the next, which coincidentally is the first line edit, which gets focused again;
The simple solution is to just add return True after changing the focus, so that the event doesn't get propagated to the parent causing a further focus change:
if event.key() == QtCore.Qt.Key_Tab:
if source is self.userEdit:
self.pswEdit.setFocus()
return True
Note that overriding the focus behavior is quite complex, and you have to be very careful about how focus and events are handled, especially for specific widgets that might deal with events in particular ways (studying the Qt sources is quite useful for this), otherwise you'll get unexpected behavior or even fatal recursion.
For instance, there's normally no need for an event filter for the return key, as QLineEdit already provides the returnPressed signal:
self.userEdit.returnPressed.connect(self.pswEdit.setFocus)
Qt already has a quite thorough focus management system, if you just want more control over the way the tab chain works use existing functions like setTabOrder() on the parent or top level window, and if you want to have more control over how (or if) they get it, use setFocusPolicy().
I have a QMainWindow application that has multiple widgets (buttons, labels, etc.) inside it.
How can I get an event when the user presses ANYWHERE of the app?
I tried to customize mousePressEvent() function, but this doesn't accept the event when other widgets (buttons, labels, etc.) are pressed.
Explanation:
The handling of mouse events between the widgets goes from children to parents, that is, if the child does not accept the event (it does not use it) then it will pass the event to the parent. For example, if you press on the QPushButton, it accepts the event and the parent is not notified, unlike QLabel that does not consume it, so the event passes to the parent.
Tools:
On the other hand, there are several methods to listen to events in general, such as:
Override any method like mousePressEvent, keyPressEvent, etc or the event or customEvent method.
Use an eventFilter.
Solution:
An alternative is to apply some previous method to all the widgets but you can discard all of them for the current objective, for example:
In the first and second method it would involve detecting when a widget is added or removed (using QEvent::ChildAdded or QEvent::ChildRemoved).
In the first method it would imply override the methods that many times is impossible.
With the above, the problem is attacked on the widgets side, but there are other alternatives:
Override the notify() method of Q{Core, GUi,}Application, verify that it is a widget and that it belongs to the window, it also implies discriminating if the event has already been consumed.
Listen to the mouse event associated with the window (QWindow).
In this case the most reasonable is the second method.
import sys
from PyQt5 import QtCore, QtWidgets
class MouseObserver(QtCore.QObject):
pressed = QtCore.pyqtSignal(QtCore.QPoint)
released = QtCore.pyqtSignal(QtCore.QPoint)
moved = QtCore.pyqtSignal(QtCore.QPoint)
def __init__(self, window):
super().__init__(window)
self._window = window
self.window.installEventFilter(self)
#property
def window(self):
return self._window
def eventFilter(self, obj, event):
if self.window is obj:
if event.type() == QtCore.QEvent.MouseButtonPress:
self.pressed.emit(event.pos())
elif event.type() == QtCore.QEvent.MouseMove:
self.moved.emit(event.pos())
elif event.type() == QtCore.QEvent.MouseButtonRelease:
self.released.emit(event.pos())
return super().eventFilter(obj, event)
class MainWindow(QtWidgets.QMainWindow):
pass
def main(args):
app = QtWidgets.QApplication(args)
w = MainWindow()
w.show()
mouse_observer = MouseObserver(w.window().windowHandle())
mouse_observer.pressed.connect(lambda pos: print(f"pressed: {pos}"))
mouse_observer.released.connect(lambda pos: print(f"released: {pos}"))
mouse_observer.moved.connect(lambda pos: print(f"moved: {pos}"))
app.exec_()
if __name__ == "__main__":
main(sys.argv)
Due to an astronomically atrocious bug on PyQt4, I need to fire a mousePressEvent artificially on a QWebView as a last breath of hope. For some obscure reason, QWebView's linkClicked and urlChanged signals do not pass a QUrl when the clicked link has Javascript involved (It does not work on Youtube videos, for example) and when the link was clicked with the left mouse button. The QUrl can be perfectly accessed when the link was clicked with the middle and right buttons.
class CQWebView(QtWebKit.QWebView):
def mousePressEvent(self, event):
if type(event) == QtGui.QMouseEvent:
if event.button() == QtCore.Qt.LeftButton:
# FIRE A QtCore.Qt.MiddleButton EVENT HERE,
# SO THAT I CAN GET THE BLOODY LINK AFTERWARDS.
elif event.button() == QtCore.Qt.MiddleButton:
self.emit(QtCore.SIGNAL("OPEN_IN_NEW_TAB")) # Example
elif event.button() == QtCore.Qt.RightButton:
self.emit(QtCore.SIGNAL("XXX")) # Example
So, I literally want to "click artificially", click without clicking, just trigger the event of clicking with the middle right button on a link, so that I can catch the QUrl correctly.
You can do it using the QtTest module as well. Set an event filter on your web view and check for left click mouse events and send a middle mouse click
def __init__(...)
self.ui_web_view.installEventFilter(self)
def eventFilter(self, obj, event):
if obj == self.ui_web_view:
if event.type() == QtCore.QEvent.MouseButtonPress:
if event.button() == QtCore.Qt.LeftButton:
print 'Handled'
QtTest.QTest.mouseClick(self.ui_web_view, QtCore.Qt.MiddleButton, QtCore.Qt.NoModifier, event.pos())
return True
return False
I've managed to click with the middle button by clicking with the left button using the sendEvent method from QtGui.QApplication.
class CQWebView(QtWebKit.QWebView):
app = None
def __init__(self, app):
QtWebKit.QWebView.__init__(self)
CQWebView.app = app
def mousePressEvent(self, event):
if type(event) == QtGui.QMouseEvent:
if event.button() == QtCore.Qt.LeftButton:
CQWebView.app.sendEvent(self, QtGui.QMouseEvent(event.type(), event.pos(), QtCore.Qt.MiddleButton, event.buttons(), event.modifiers()))
elif event.button() == QtCore.Qt.MiddleButton:
self.emit(QtCore.SIGNAL("linkClicked(const QUrl&)")) # Problem
elif event.button() == QtCore.Qt.RightButton:
self.emit(QtCore.SIGNAL("XXX")) # Example
I just needed to pass the instance of the QApplication to the instance of the custom QWebView on instantiation:
def compose_tab(self, index):
self.tabs[index].append(CQWebView(self))
This way, I click on the QWebView with the left mouse button and I click artificially on it with the middle button. The problem is that doing so I'm overriding the default behavior of the linkClicked and urlChanged signals from the QWebView. Even though I can emit these signals, I can't have access to the address of the link that was clicked, since by accessing QWebView.url() only gives me the present QUrl, and not the future clicked one.
Really frustrating.
I am trying to capture all of key pressed in my program
def keyPressEvent(self, event):
event = event.key()
if (event == QtCore.Qt.Key_Q):
print ('Q!.')
That function works fine when i am trying to capture keys in my window. (in this case Q_Key)
But if the key was pressed in a Text Widget (for example in a: QListView, QlistWidget, QLineEdit, and so many more ) it don't work. The function prints nothing. I am doing something wrong...
What can i do to fix it?
You will need to install an event-filter on the application object to get all key-press events:
class Window(QMainWindow):
def __init__(self):
...
QApplication.instance().installEventFilter(self)
def eventFilter(self, source, event):
if event.type() == QtCore.QEvent.KeyPress:
print('KeyPress: %s [%r]' % (event.key(), source))
return super().eventFilter(source, event)
I am trying to handle a drop event on a TreeWidget from itself by overriding the dropEvent method. Ultimately, I need to get a handle to the TreeWidgetItem being dropped. So far, the only useful information I get from the event regarding the dropped item is a QByteArray that seems to contain text from the item being dropped, except that it's poorly formatted with lots of spaces and a bunch of non-printable characters.
Any help would be greatly appreciated.
edit:
Here is the code, as asked, but I'm really not doing anything special, I'm literally just reading the only type of data contained in the mimeData of the drop event. It sounds as though I'm going to have to override the Drag event?? And add some type of identifier to allow me to get a handle back to the original QTreeWidget??
def dropEvent( self, event ):
data = event.mimeData().data( 'application/x-qabstractitemmodeldatalist' )
print data
not quite sure I'm understanding the question correctly, but your mime data comes from the startDrag method, where you've created a QMimeData object, set it's type and supplied data accordingly. In your dropEvent method check the type of the incoming data and process it accordingly or ignore if you don't recognize the type.
Also take a look at the documentation here: Drag and Drop it should give you an idea on how drag and drop works in qt
I also made a small example here, see if it would work for you:
import sys
from PyQt4 import QtGui, QtCore
class TestTreeWidget(QtGui.QTreeWidget):
def __init__(self, parent = None):
super(TestTreeWidget, self).__init__(parent)
self.setDragEnabled(True)
self.setAcceptDrops(True)
def startDrag(self, dropAction):
# create mime data object
mime = QtCore.QMimeData()
mime.setData('application/x-item', '???')
# start drag
drag = QtGui.QDrag(self)
drag.setMimeData(mime)
drag.start(QtCore.Qt.CopyAction | QtCore.Qt.CopyAction)
def dragMoveEvent(self, event):
if event.mimeData().hasFormat("application/x-item"):
event.setDropAction(QtCore.Qt.CopyAction)
event.accept()
else:
event.ignore()
def dragEnterEvent(self, event):
if (event.mimeData().hasFormat('application/x-item')):
event.accept()
else:
event.ignore()
def dropEvent(self, event):
if (event.mimeData().hasFormat('application/x-item')):
event.acceptProposedAction()
data = QtCore.QString(event.mimeData().data("application/x-item"))
item = QtGui.QTreeWidgetItem(self)
item.setText(0, data)
self.addTopLevelItem(item)
else:
event.ignore()
class MainForm(QtGui.QMainWindow):
def __init__(self, parent=None):
super(MainForm, self).__init__(parent)
self.view = TestTreeWidget(self)
self.view.setColumnCount(1)
item0 = QtGui.QTreeWidgetItem(self.view)
item0.setText(0, 'item0')
item1 = QtGui.QTreeWidgetItem(self.view)
item1.setText(0, 'item1')
self.view.addTopLevelItems([item0, item1])
self.setCentralWidget(self.view)
def main():
app = QtGui.QApplication(sys.argv)
form = MainForm()
form.show()
app.exec_()
if __name__ == '__main__':
main()
also you may want to take a look at the similar post here: QTreeView with drag and drop support in PyQt
hope this helps, regards