I am using pyqt's Qshortcut in trying to detect a key combination to trigger some action i.e when
the user types -> into a QtextEdit widget, I would like to print "changing mode".
I have seen other key sequence examples that involve CTRL+E or some other CTRL or shift key
combination,
self.shcut1 = QtGui.QShortcut(self)
self.shcut1.setKey("CTRL+E")
self.connect(self.shcut1, QtCore.SIGNAL("activated()"), self.close)
But I really want to trap ->(hyphen followed by a greater than sign). Any suggestions on how to do this
QShortCut only accepts combinations of QtCore.Qt::KeyboardModifiers. Try using an event filter:
#!/usr/bin/env python
#-*- coding:utf-8 -*-
from PyQt4 import QtGui, QtCore
class MyWindow(QtGui.QTextEdit):
modeChanged = QtCore.pyqtSignal(bool)
_seenMinus = False
def __init__(self, parent=None):
super(MyWindow, self).__init__(parent)
self.installEventFilter(self)
self.modeChanged.connect(self.on_modeChanged)
def on_modeChanged(self):
print "Changing Mode."
def eventFilter(self, obj, event):
if event.type() == QtCore.QEvent.KeyPress:
if event.key() == QtCore.Qt.Key_Minus:
self._seenMinus = True
elif event.key() == QtCore.Qt.Key_Greater \
and event.modifiers() == QtCore.Qt.ShiftModifier \
and self._seenMinus:
self.modeChanged.emit(True)
self.setStyleSheet("""
background-color: lightgray;
""")
elif event.modifiers() != QtCore.Qt.ShiftModifier:
if self._seenMinus == True:
self.modeChanged.emit(False)
self._seenMinus = False
self.setStyleSheet("")
return super(MyWindow, self).eventFilter(obj, event)
if __name__ == "__main__":
import sys
app = QtGui.QApplication(sys.argv)
app.setApplicationName('MyWindow')
main = MyWindow()
main.show()
sys.exit(app.exec_())
Just catch the signal QTextEdit::textChanged(), and every the user makes a change, scan the text for '->'. Granter the brute force approach of scanning the entire text block every time isn't pretty; another option is scanning only the last two characters of the text. However this misses the case where the user creates '->' by deleting text in between a '-' and a '>' character. If you're not worried about that case, then just go with the last two. QTextEdit::cursorPositionChanged might allow you to test precisely at the insertion/deletion point.
Related
I am trying to make the user not switch to the next TAB where "Form 2" is located until they fill in Form 1.
I tried the "currentChange" event but it doesn't work the way I want as it shows the alert when it was already changed from TAB.
Is there a way to leave the current TAB fixed until the task is complete?
I attach the code and an image
import sys
from PyQt5.QtCore import Qt
from PyQt5 import QtWidgets
class MyWidget(QtWidgets.QWidget):
def __init__(self):
super(MyWidget, self).__init__()
self.setGeometry(0, 0, 800, 500)
self.setLayout(QtWidgets.QVBoxLayout())
#flag to not show the alert when starting the program
self.flag = True
#changes to True when the form is completed
self.form_completed = False
#WIDGET TAB 1
self.widget_form1 = QtWidgets.QWidget()
self.widget_form1.setLayout(QtWidgets.QVBoxLayout())
self.widget_form1.layout().setAlignment(Qt.AlignHCenter)
label_form1 = QtWidgets.QLabel("FORM 1")
self.widget_form1.layout().addWidget(label_form1)
#WIDGET TAB 2
self.widget_form2 = QtWidgets.QWidget()
self.widget_form2.setLayout(QtWidgets.QVBoxLayout())
self.widget_form2.layout().setAlignment(Qt.AlignHCenter)
label_form2 = QtWidgets.QLabel("FORM 2")
self.widget_form2.layout().addWidget(label_form2)
#QTABWIDGET
self.tab_widget = QtWidgets.QTabWidget()
self.tab_widget.currentChanged.connect(self.changed)
self.tab_widget.addTab(self.widget_form1,"Form 1")
self.tab_widget.addTab(self.widget_form2, "Form 2")
self.layout().addWidget(self.tab_widget)
def changed(self,index):
if self.flag:
self.flag = False
return
if not self.form_completed:
QtWidgets.QMessageBox.about(self, "Warning", "You must complete the form")
return
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
mw = MyWidget()
mw.show()
sys.exit(app.exec_())
The currentChanged signal is emitted when the index is already changed (the verb is in past tense: Changed), so if you want to prevent the change, you have to detect any user attempt to switch tab.
In order to do so, you must check both mouse and keyboard events:
left mouse clicks on the tab bar;
Ctrl+Tab and Ctrl+Shift+Tab on the tab widget;
Since you have to control that behavior from the main window, the only solution is to install an event filter on both the tab widget and its tabBar(), then if the action would change the index but the form is not completed, you must return True so that the event won't be handled by the widget.
Please consider that the following assumes that the tab that has to be kept active is the current (the first added tab, or the one set using setCurrentIndex()).
class MyWidget(QtWidgets.QWidget):
def __init__(self):
# ...
self.tab_widget.installEventFilter(self)
self.tab_widget.tabBar().installEventFilter(self)
def eventFilter(self, source, event):
if event.type() == event.KeyPress and \
event.key() in (Qt.Key_Left, Qt.Key_Right):
return not self.form_completed
elif source == self.tab_widget.tabBar() and \
event.type() == event.MouseButtonPress and \
event.button() == Qt.LeftButton:
tab = self.tab_widget.tabBar().tabAt(event.pos())
if tab >= 0 and tab != self.tab_widget.currentIndex():
return self.isInvalid()
elif source == self.tab_widget and \
event.type() == event.KeyPress and \
event.key() in (Qt.Key_Tab, Qt.Key_Backtab) and \
event.modifiers() & Qt.ControlModifier:
return self.isInvalid()
return super().eventFilter(source, event)
def isInvalid(self):
if not self.form_completed:
QTimer.singleShot(0, lambda: QtWidgets.QMessageBox.about(
self, "Warning", "You must complete the form"))
return True
return False
Note that I showed the message box using a QTimer in order to properly return the event filter immediately.
Also consider that it's good practice to connect signals at the end of an object creation and configuration, and this is much more important for signals that notify property changes: you should not connect it before setting the property that could trigger it.
Since an empty QTabWidget has a -1 index, as soon as you add the first tab the index is changed to 0, thus triggering the signal. Just move the currentChanged signal connection after adding the tabs, and you can get rid of the self.flag check.
I'm writing Chat gui for client on Python using PyQt5.
I have a QTextEdit, which the client can write messages in it.
I wan't to know when the 'Enter' key is being pressed while the focus is on the QTextEdit.
I tried using installEventFilter function but it detects keys being pressed on all of the other widgets but the QTextEdit one.
What can I do to fix that?
def initUI(self):
# ...
self.text_box = QtWidgets.QTextEdit(self)
self.installEventFilter(self)
# ...
def keyPressEvent(self, qKeyEvent):
print(qKeyEvent.key())
if qKeyEvent.key() == Qt.Key_Return:
if self.text_box.hasFocus():
print('Enter pressed')
When you override keyPressEvent you are listening to the events of the window, instead install an eventFilter to the QTextEdit, not to the window as you have done in your code, and check if the object passed as an argument is the QTextEdit:
def initUI(self):
# ...
self.text_box = QtWidgets.QTextEdit(self)
self.text_box.installEventFilter(self)
# ...
def eventFilter(self, obj, event):
if event.type() == QtCore.QEvent.KeyPress and obj is self.text_box:
if event.key() == QtCore.Qt.Key_Return and self.text_box.hasFocus():
print('Enter pressed')
return super().eventFilter(obj, event)
The answer from #eyllanesc is very good if you are determined to use QTextEdit.
If you can get away with QLineEdit and its limitations, you can use the returnPressed() signal. The biggest drawback for QLineEdit is you are limited to one line of text. And there is no word wrap. But the advantage is you don't have to mess with eventFilters or think too hard about how keyPress signals fall through all of the widgets in your app.
Here is a minimal example that copies from one QLineEdit to another:
import sys
from PyQt5.QtWidgets import *
class PrintWindow(QMainWindow):
def __init__(self):
super().__init__()
self.left=50
self.top=50
self.width=300
self.height=300
self.initUI()
def initUI(self):
self.setGeometry(self.left,self.top,self.width,self.height)
self.line_edit1 = QLineEdit(self)
self.line_edit1.move(50, 50)
self.line_edit1.returnPressed.connect(self.on_line_edit1_returnPressed)
self.line_edit2 = QLineEdit(self)
self.line_edit2.move(50, 100)
self.show()
def on_line_edit1_returnPressed(self):
self.line_edit2.setText(self.line_edit1.text())
if __name__ == '__main__':
app = QApplication(sys.argv)
window = PrintWindow()
sys.exit(app.exec_())
In this example, I have manually connected to the signal in line 22 (self.line_edit1.returnPressed.connect). If you are using a ui file, this connection can be left out and your program will automatically call the on__returnPressed method.
When you override keyPressEvent you are listening to the events of the window, instead install an eventFilter to the QTextEdit, not to the window as you have done in your code, and check if the object passed as an argument is the QTextEdit:
def initUI(self):
# ...
self.text_box = QtWidgets.QTextEdit(self)
self.text_box.installEventFilter(self)
# ...
def eventFilter(self, obj, event):
if event.type() == QtCore.QEvent.KeyPress and obj is self.text_box:
if event.key() == QtCore.Qt.Key_Return and self.text_box.hasFocus():
print('Enter pressed')
return True
return False
This is building upon the answer of #eyllanesc and the problem #Daniel Segal faced. Adding the correct return values as such to the eventFilter solves the problem.
I have an QLineEdit that will take the value of an RFID tag and the log the user in with the value that is received, I have setup the QLineEdit so that it calls a login function when the enter key is pressed.
The only issue that I am left with is that the QLineEdit is visible which is not necessary as the user will not be typing the value of their RFID tag, they will just scan it and the scanner will enter the value and press enter.
rfid_enter = QLineEdit()
rfid_enter.returnPressed.connect(lambda: log_user_in(rfid_enter.text()))
def log_user_in(value):
print(value) (THIS WILL LOG THE USER IN)
QLineEdit needs to have the focus to get the keyboard events, but to have the focus it needs to be visible, so hiding it will not be the solution.
As pointed out by the OP in the comments in the window there are only: two labels, and a few spacers that do not handle the keyboard event so there are no widgets that intercept that event so the window can get them without problems (if there are other widget like QLineEdits, QTextEdit, QSpinBox, etc. the logic could change).
Considering the above, I have implemented the following logic:
import string
from PyQt5 import QtCore, QtWidgets
class Widget(QtWidgets.QWidget):
returnPressed = QtCore.pyqtSignal(str)
def __init__(self, parent=None):
super(Widget, self).__init__(parent)
lay = QtWidgets.QVBoxLayout(self)
lay.addWidget(
QtWidgets.QLabel("My Label", alignment=QtCore.Qt.AlignHCenter),
alignment=QtCore.Qt.AlignTop,
)
self.m_text = ""
self.returnPressed.connect(self.log_user_in)
def keyPressEvent(self, event):
if event.text() in string.ascii_letters + string.digits:
self.m_text += event.text()
if event.key() in (QtCore.Qt.Key_Return, QtCore.Qt.Key_Enter):
self.returnPressed.emit(self.m_text)
# clear text
self.m_text = ""
super(Widget, self).keyPressEvent(event)
#QtCore.pyqtSlot(str)
def log_user_in(self, text):
print(text)
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
w = Widget()
w.resize(240, 320)
w.show()
sys.exit(app.exec_())
The answer from eyllanesc would work if the entire window was the class that he had created, but in my case the layout will change and therefore I could not use that as the main window.
I went for a cheat approach of just trying to hide the box as much as possible and ended up with this result.
class LogInRFIDListener(QtWidgets.QPlainTextEdit):
def __init__(self):
super(LogInRFIDListener, self).__init__()
self.setTextInteractionFlags(QtCore.Qt.TextEditable)
self.setCursor(QtCore.Qt.ArrowCursor)
self.setStyleSheet("border: none; color: transparent;") # Hide the border around the text box
self.setCursorWidth(0) # Hide the cursor
def keyPressEvent(self, event): # Major restricting needed
if event.key() in (QtCore.Qt.Key_Return, QtCore.Qt.Key_Enter):
log_user_in(self.toPlainText())
super(LogInRFIDListener, self).keyPressEvent(event)
This is my keyPressEvent
def keyPressEvent(self , e):
key = e.key()
if key == QtCore.Qt.Key_Escape:
self.close()
elif key == QtCore.Qt.Key_A:
print 'Im here'
However if I click on A , it doesn't print. However the window is closing if I click on Escape.Where am I going wrong?
EDIT:
Basically I have a window with a lineedit and a pushbutton. I want to link the button to a function by clicking on Enter, lets say fun. This is my code
import sys
from PyQt4 import QtGui , QtCore
class Example(QtGui.QWidget):
def __init__(self):
super(Example , self).__init__()
self.window()
def window(self):
self.setWindowTitle('Trial')
self.layout = QtGui.QGridLayout()
self.text = QtGui.QLineEdit()
self.first = QtGui.QPushButton('Button')
self.layout.addWidget(self.text , 0 , 0)
self.layout.addWidget(self.first , 1 , 0)
self.setLayout(self.layout)
self.first.clicked.connect(self.fun)
self.show()
def fun(self):
//do something
def keyPressEvent(self , e):
key = e.key()
if key == QtCore.Qt.Key_Escape:
self.close()
elif key == QtCore.Qt.Key_Enter:
self.fun()
def main():
app = QtGui.QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
I would add more keys later on. But none of them except Escape are working/
The method you're looking for is called keyPressEvent, not KeyPressEvent.
It seems that your QLineEdit is stealing your KeyPress events. If handling the enter key from the line edit is all you want to do, you could connect the returnPressed signal to self.fun:
self.text.returnPressed.connect(self.fun) # in PySide
Otherwise, you will have to mess around with event filters. I'll try posting some code later on.
Your final edit made it clearer. You can safely drop keyPressEvent and just use:
self.text.returnPressed.connect(self.fun)
self.button.clicked.connect(self.fun)
What a messy answer this turned out to be :)
You are making the GUI application, right? if yes then printing like that will print in the console. try this...
QtGui.QMessageBox.information(self,"hello","I m here")
I have a text editor which converts latin keybord keypresses to russian characters. I have reimplemented a QTextEdit class:
class MyTextEdit(QTextEdit):
def __init__(self, *args):
QTextEdit.__init__(self, *args)
leftMousePressedSignal = pyqtSignal(QPoint)
rightMousePressedSignal = pyqtSignal(QPoint, QEvent)
mouseMovedSignal = pyqtSignal(QPoint)
mouseDoubleClickedSignal = pyqtSignal(QPoint)
keyPressedSignal = pyqtSignal(QEvent)
def mousePressEvent(self, event):
pos = event.pos()
if event.button() == Qt.LeftButton:
self.leftMousePressedSignal.emit(pos)
elif event.button() == Qt.RightButton:
self.rightMousePressedSignal.emit(pos, event)
def mouseMoveEvent(self, event):
if event.buttons() & Qt.LeftButton:
pos = event.pos()
self.mouseMovedSignal.emit(pos)
def mouseDoubleClickEvent(self, event):
if event.button() == Qt.LeftButton:
pos = event.pos()
self.mouseDoubleClickedSignal.emit(pos)
def keyPressEvent(self, event):
if event.type() == QEvent.KeyPress:
self.keyPressedSignal.emit(event)
which I then use with the reimplemented keyPressEvent. So I had to reimplement the Backspace action as well:
self.textEdit = MyTextEdit(self)
...
self.textEdit.keyPressedSignal.connect(self.OnKeyPressed)
self.actionSelectAll.triggered.connect(self.textEdit.selectAll)
...
def OnKeyPressed(self, event):
key = event.key()
txt = str(event.text())
if key == Qt.Key_Backspace:
if self.cursor.hasSelection():
self.cursor.movePosition(QTextCursor.NoMove, QTextCursor.KeepAnchor, self.cursor.selectionStart() - self.cursor.selectionStart())
else:
self.cursor.movePosition(QTextCursor.PreviousCharacter, QTextCursor.KeepAnchor, 1)
self.textEdit.setTextCursor(self.cursor)
self.textEdit.cut()
elif key == Qt.Key_A and (event.modifiers() & Qt.ControlModifier):
self.textEdit.selectAll()
Now, if nothing's selected and I press Backspace, it deletes a single character to the left of the cursor. When a word is selected with a mouse and I press Backspace, it deletes the word. When a few words or the whole text is selected with the mouse and I press Backspace, it deletes the whole selection. So, it works fine. When I press a Select All button (or Ctrl+A) - it selects the whole text. But if I then press Backspace it only deletes 1 character to the left of the cursor, not the whole text.
I will greatly appreciate it if you cold tell me what I'm doing wrong here. Thank you.
Why are you reimplimenting selection and deleting behaviour, and why it's done not in MyTextEdit? If you just need to
convert latin keybord keypresses to russian characters
you can only do this conversion, and let other work (selection, deliting, copying, etc) for QTextEdit.
I'd write it like this:
class TransliterationEdit(QTextEdit):
latin_alphabet = unicode(string.ascii_letters) # or dict with transliteration table
def translateLetter(self, letter):
return u'ะช' # just example, you need translate letter somehow
def keyPressEvent(self, event):
text = unicode(event.text())
if text and text in self.latin_alphabet:
self.insertPlainText(self.translateLetter(text))
event.ignore()
else:
QTextEdit.keyPressEvent(self, event)
UPDATE
Here's my rough implementation of TextEdit which converts Latin keypresses to Russian characters, hope it will help.
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import sys
from PyQt4.QtGui import *
from transliterator import SMTransliterator
# transliterator available at https://gist.github.com/1600988
class TransliterationEdit(QTextEdit):
def __init__(self, *args, **kwargs):
QTextEdit.__init__(self, *args, **kwargs)
self._transliterator = SMTransliterator()
def keyPressEvent(self, event):
text = unicode(event.text())
if not self._transliterator.add_letter(text):
if text and self._transliterator.result:
self.insertPlainText(self._transliterator.result)
return QTextEdit.keyPressEvent(self, event)
if self._transliterator.need_next:
return # skip it
self.insertPlainText(self._transliterator.result)
if __name__ == '__main__':
app = QApplication(sys.argv)
window = TransliterationEdit()
window.show()
sys.exit(app.exec_())
transliterator available at gist