How to correctly override qscintilla mousePressEvent? - python

I have class MainWindow and it have qscintilla editor, i want to add listener to editor mousePressEvent
class MainWindow(QtWidgets.QMainWindow, gui.Ui_MainWindow):
def __init__(self):
super().__init__()
self.setupUi(self)
self.editor.mousePressEvent = self.on_editor_click
def on_editor_click(self, QMouseEvent):
// here i want add my code
return QsciScintilla.mousePressEvent(self, QMouseEvent)
If i override mousePressEvent - editor will broke (mouse clicks will not work). I tried call initial mousePressEvent, but it dont work, app crashing

Assigning the mousePressEvent method to another function is not correct, mousePressEvent is not a signal, it is a function that is part of QsciScintilla.
A possible solution is that you create a personalized QsciScintilla that emits a signal created as shown below:
class ClickQsciScintilla(QsciScintilla):
clicked = QtCore.pyqtSignal()
def mousePressEvent(self, event):
self.clicked.emit()
QsciScintilla.mousePressEvent(self, event)
Then you create an instance of ClickQsciScintilla and connect to that signal:
self.__editor = ClickQsciScintilla()
self.__editor.clicked.connect(self.on_editor_click)
Your handler:
def on_editor_click(self):
print "Editor was clicked!"

Related

PyQt5: How to use closeEvent with a custom message-box

Before I close my main window, I want to do some last operations.
I think the closeEvent is the right thing to do that, but the standard QMessageBox does not suit my design, so I want to make my own (and I did). But with the following code, the application closes directly without showing my message-box.
What here is the problem?
def closeEvent(self, event):
self.messageBox = lastWindow(self)
self.messageBox.show()
return super().closeEvent(event)
This is the lastWindow code: (subclass of a pyuic5 generated .ui file):
from PyQt5 import QtCore
from PyQt5.QtGui import QColor
from PyQt5.QtCore import Qt, QPoint
from PyQt5.QtWidgets import QDialog, QGraphicsDropShadowEffect
from UIs.UI_quit import Ui_Quit
class lastWindow(QDialog, Ui_Quit):
def __init__(self, parent = None):
super(lastWindow, self).__init__(parent)
self.setupUi(self)
self._parent = parent
self.setWindowFlag(QtCore.Qt.WindowType.FramelessWindowHint)
self.setAttribute(QtCore.Qt.WidgetAttribute.WA_TranslucentBackground)
self.setAttribute(QtCore.Qt.WidgetAttribute.WA_DeleteOnClose)
self.shadow = QGraphicsDropShadowEffect(self)
self.shadow.setBlurRadius(10)
self.shadow.setXOffset(5)
self.shadow.setYOffset(5)
self.shadow.setColor(QColor(0, 0, 0, 80))
self.mainFrame.setGraphicsEffect(self.shadow)
self.btn_close.clicked.connect(self.abort)
self.btn_quit.clicked.connect(self.destroy)
self.btn_abort.clicked.connect(self.abort)
self._parent.blur.setEnabled(True)
def destroy(self):
self._parent.close()
self.close()
def abort(self):
self._parent.startService()
self.close()
Use the built-in accept and reject slots to close the dialog, and then use the return value of exec in your closeEvent to decide what to do next:
class lastWindow(QDialog, Ui_Quit):
...
def destroy(self):
self.accept()
def abort(self):
self.reject()
class MainWindow(QMainWindow):
...
def closeEvent(self, event):
dialog = lastWindow(self)
if dialog.exec() == QDialog.Rejected:
self.startService()
event.ignore()
Calling event.ignore() will prevent the window closing; otherwise, the event will be accepted by default and the window will close. Note that, unlike show(), the exec() call blocks (i.e. it waits for user interaction), which explains why your current example doesn't work as you expected.

PyQt5 QWidget event blocks other events. How to avoid it?

I have the next problem:
A created a custom widget (simple derived QWidget class). When I middle-mouse click on it - it creates another QWidget class (sort of context menu). When I release middle-mouse button - that context widget disappears. So that works. What does not work is - that context widget also has some content added, like other small widgets, icons, etc and they all have their own custom events (simple example - enterEvent and leaveEvent with prints indicating those events). But those inner widget events are not working, they are blocked while I keep middle-mouse pressed. When I release it - context widget disappears. Would like to know if there is any solution to let inner widgets'events work as expected.
Here is a minimal example where inner widget does not run mouse events as they are blocked by MainWidget event:
from PyQt5 import QtWidgets, QtGui, QtCore
class SomeSmallWidget(QtWidgets.QWidget):
def __init__(self, increment=1, globalValue=0):
super(SomeSmallWidget, self).__init__()
# UI
self.setMinimumWidth(40)
self.setMaximumWidth(40)
self.setMinimumHeight(40)
self.setMaximumHeight(40)
self.mainLayout = QtWidgets.QVBoxLayout()
self.setLayout(self.mainLayout)
def enterEvent(self, event):
print('Entered') # NOT WORKING
super(SomeSmallWidget, self).enterEvent(event)
def leaveEvent(self, event):
print('Leaved') # NOT WORKING
super(SomeSmallWidget, self).leaveEvent(event)
class ContextWidget(QtWidgets.QWidget):
def __init__(self, parent=None):
super(ContextWidget, self).__init__()
self.setMouseTracking(True)
# position
point = parent.rect().topLeft()
global_point = parent.mapToGlobal(point)
self.move(global_point - QtCore.QPoint(0, 0))
self.innerWidget = SomeSmallWidget() # Here is that small widget, which event does not work
self.mainLayout = QtWidgets.QVBoxLayout()
self.setLayout(self.mainLayout)
self.mainLayout.addWidget(self.innerWidget)
class MainWidget(QtWidgets.QLineEdit):
def __init__(self, value='0'):
super(MainWidget, self).__init__()
self.setMouseTracking(True)
self.popMenu = None
def mousePressEvent(self, event):
if event.button() == QtCore.Qt.MiddleButton: # while we keep MMB pressed - we see context widget
self.popMenu = ContextWidget(parent=self)
self.popMenu.show()
super(MainWidget, self).mousePressEvent(event)
def mouseReleaseEvent(self, event):
if event.button() == QtCore.Qt.MiddleButton:
if self.popMenu:
self.popMenu = None
Events are not blocked, all mouse events are sent to widget that was under cursor when mouse button was pressed. Usually (always) it makes more sense. Imagine two buttons next to each other. Suppose user pressed one, moved cursor and released over second button. What was his intention? He probably changed his mind. If button triggers action on mouse press - this options will not be available and it's probably too soon, if button triggers action on mouse release, which button should recieve mouserelease event? If we send mouseevent to second button - that was not pressed it will trigger action that used didn't want. If we dont send mouserelease event to first button - it will stay in sunken mode. Imagine user is selecting text in lineedit, and while selecting he leaves lineedit and goes to other widgets, should they react somehow, and should focus be switched? Probably not. So there is only one active window and only one focused widget at a time and it receives keyboard and mouse input and reacts to it. Most of menus are shown after mouserelease and closes on next mouseclick, providing better user experience.
However, If you still want your widget to receive mouse events, you can achive this by translating it from ContextWidget to SomeSmallWidget like this:
class SomeSmallWidget(QtWidgets.QWidget):
...
def paintEvent(self, event):
painter = QtGui.QPainter(self)
painter.fillRect(self.rect(), QtCore.Qt.blue)
def onMouseEnter(self):
print('onMouseEnter')
def onMouseLeave(self):
print('onMouseLeave')
class MainWidget(QtWidgets.QLineEdit):
...
def mouseMoveEvent(self, event):
if self.popMenu:
self.popMenu.mouseTest(event.globalPos())
class ContextWidget(QtWidgets.QWidget):
def __init__(self, parent=None):
...
self._inside = False
def mouseTest(self, p):
widget = self.innerWidget
rect = QtCore.QRect(widget.mapToGlobal(QtCore.QPoint(0,0)), widget.size())
inside = rect.contains(p)
if inside != self._inside:
if inside:
widget.onMouseEnter()
else:
widget.onMouseLeave()
self._inside = inside
Notice I added paintEvent to see the widget bounds.

Connecting to events of another widget

This is most likely a duplicate question, but I have to ask it because other answers aren't helping in my case, since I am new to pyqt (switched from tkinter few days ago).
I am wondering if is it possible to connect to an event of a widget like this:
self.lineEdit = QtGui.QLineEdit(self.frame)
self.lineEdit.keyReleaseEvent(lambda: someFunction(QtCore.Qt.Key_A ))
self.lineEdit.setObjectName(_fromUtf8("lineEdit"))
self.horizontalLayout.addWidget(self.lineEdit)
and then...
def someFunction(event):
print(event)
...
My question is how to bind to a specific event from another widget, and connect that event with a function - like btn.clicked.connect(function_goes_here).
In tkinter it's something be like this:
self.Entry.bind("<KeyRelease-a>", lambda event: someFunction(event))
There are a number of different ways to achieve this. A generic way to listen to all events for a given widget, is to install an event-filter on it. All protected functions have a corresponding event type that can be accessed in this way:
class MainmWindow(QMainWindow):
def __init__(self):
...
self.lineEdit = QLineEdit(self.frame)
self.lineEdit.installEventFilter(self)
def eventFilter(self, source, event):
if source is self.lineEdit:
if event.type() == QEvent.KeyRelease:
print('key release:', event.key())
# the following line will eat the key event
# return True
return super(MainmWindow, self).eventFilter(source, event)
Alternatively, you can sub-class the widget, re-implement the relevant event handler, and emit a custom signal:
class LineEdit(QLineEdit):
keyReleased = pyqtSignal(int)
def keyReleaseEvent(self, event):
self.keyReleased.emit(event.key())
super(LineEdit, self).keyReleaseEvent(event)
class MainmWindow(QMainWindow):
def __init__(self):
...
self.lineEdit = LineEdit(self.frame)
self.lineEdit.keyReleased.connect(self.handleKeyRelease)
def handleKeyRelease(self, key):
print('key release:' key)
A more hackish variation on this is to overwrite the method directly:
class MainmWindow(QMainWindow):
def __init__(self):
...
self.lineEdit = QLineEdit(self.frame)
self.lineEdit.keyReleaseEvent = self.handleKeyRelease
def handleKeyRelease(self, event):
print('key release:', event.key())
QLineEdit.keyReleaseEvent(self.lineEdit, event)
Note that if you don't want to invoke the default event handling, you can omit the call to the base-class method.
I don't have enough reputation to reply to #ekhumoro, but wanted to add to their answer with an applied answer specific to MouseEvents for those who might benefit, based on a similar issue I was having.
Scenario
Display an image in the central widget, and connect a custom signal to capture mouse double click actions in the central widget.
Source for test image: https://en.wikipedia.org/wiki/Standard_test_image#/media/File:SIPI_Jelly_Beans_4.1.07.tiff
Consider the following:
A basic QMainWindow class which loads a supplied image_file and shows the image in a custom sub-class of the QLabel class, fit to the window:
import sys
from PyQt5.QtCore import Qt, pyqtSignal
from PyQt5.QtGui import QPixmap, QImage, QPalette, QMouseEvent
from PyQt5.QtWidgets import (QApplication, QMainWindow, QLabel, QScrollArea)
class ImageView(QMainWindow):
def __init__(self):
super().__init__()
self.initializeUI()
self.image = QImage()
def initializeUI(self):
self.setMinimumSize(300, 200)
self.setWindowTitle("Image Viewer")
# self.showMaximized()
self.createMainQLabel()
self.show()
def createMainQLabel(self):
"""Create an instance of the imageQLabel class and set it
as the main window's central widget."""
self.image_qlabel = imageQLabel(self)
self.image_qlabel.resize(self.image_qlabel.pixmap().size())
self.scroll_area = QScrollArea()
self.scroll_area.setBackgroundRole(QPalette.Dark)
self.scroll_area.setAlignment(Qt.AlignCenter)
self.scroll_area.setWidget(self.image_qlabel)
self.setCentralWidget(self.scroll_area)
class imageQLabel(QLabel):
"""Subclass of QLabel for displaying image"""
def __init__(self, parent, image_file="images/SIPI_Jelly_Beans_4.1.07.tiff"):
super().__init__(parent)
self.openImage(image_file)
self.setScaledContents(True)
self.setAlignment(Qt.AlignCenter)
def openImage(self, image_file):
# Get image, create the pixmap and resize to fit window
self.image = QImage(image_file)
self.setPixmap(QPixmap().fromImage(self.image))
self.resize(self.pixmap().size())
"""
Other methods used to customize the class, for example:
- Resize
- Rotate
- Crop
- ...
...
"""
if __name__ == "__main__":
app = QApplication(sys.argv)
app.setAttribute(Qt.AA_DontShowIconsInMenus, True)
window = ImageView()
sys.exit(app.exec_())
When executed, yield a simple window with the image:
SIPI Test Image shown in PyQT5 Window
What we would like to do is add a mouseDoubleClickEvent to the imageQLabel sub-class, but ensure that other widgets or classes in the program can access the event signals. To do this, you can modify #ekhumoro's answer #2 to accommodate a pyqtSignal.
First, add the signal to the attributes of the imageQLabel class. Here, we need the full QMouseEvent rather than just an int:
import sys
from PyQt5.QtCore import Qt, pyqtSignal
from PyQt5.QtGui import QPixmap, QImage, QPalette, QMouseEvent
from PyQt5.QtWidgets import (QApplication, QMainWindow, QLabel, QScrollArea)
class imageQLabel(QLabel):
"""Subclass of QLabel for displaying image"""
# Class attributes
mouseDoubleClickSignal = pyqtSignal(QMouseEvent)
We wish to pass the event from that pyqtSignal to some other method in our ImageView main window class. As an example, let's make a method to print the cursor position when the user double clicks in the imageQLabel sub-class.
In ImageView:
class ImageView(QMainWindow):
"""
...
The rest of the class code
...
"""
def print_mouse(self, event):
print("print_mouse event from QMainWindow: {}".format((event.x(), event.y())))
Now we just need to connect print_mouse to the custom pyqtSignal we made. It makes the most sense to do this in the initializeUI method:
def initializeUI(self):
self.setMinimumSize(300, 200)
self.setWindowTitle("Image Viewer")
# self.showMaximized()
self.createMainQLabel()
# Connect the signal from custom imageQLabel class
# to the QMainWindow
self.image_qlabel.mouseDoubleClickSignal.connect(self.print_mouse)
self.show()
These modifications now print the cursor position when double clicking inside of the imageQLabel sub-class in the central widget. The call to print_mouse is outside of the sub-class. Moreover, double-clicking outside of the central widget (like outside of the frame of the image) does not call print_mouse because the mouseDoubleClickEvent is only active for the central widget.
%user profile%\AppData\Local\Programs\Python\Python39\python.exe
%script path%\stackoverflowsolution.py
print_mouse event from QMainWindow: (115, 140)
print_mouse event from QMainWindow: (54, 55)
override using python lambda magic
def on_key(line_edit : QLineEdit, key):
print(key)
...
line_edit = self._lineedit = QLineEdit()
line_edit.keyReleaseEvent = lambda ev, self=line_edit, super=line_edit.keyReleaseEvent: \
(super(ev), on_key(self, ev.key()), None)[-1]
We save current keyReleaseEvent function in lambda kwarg super.
Also we need to save line_edit in lambda kwarg self, because line_edit will be removed from locals during lambda execution.
Finally we need to return None, thus get it from last element from our Tuple.

How to Catch Hover and Mouse Leave Signal In PyQt5

QPushButton has a signal which is named clicked(), and we can catch click events through it. Is there a method or signal which catches hover and leave events?
How can I catch mouse-over button and mouse-leave button, like this:
button = QPushButton(window)
button.clicked.connect(afunction)
Note: I use python3.
You need to subclass the QPushButton class and reimplement the enterEvent and leaveEvent:
class Button(QPushButton):
def __init__(self, parent=None):
super(Button, self).__init__(parent)
# other initializations...
def enterEvent(self, QEvent):
# here the code for mouse hover
pass
def leaveEvent(self, QEvent):
# here the code for mouse leave
pass
You can then handle the event locally, or emit a signal (if other widgets needs to react on this event you could use a signal to notify the event to other widgets).

Stealing wheelEvents from a QScrollArea

I want to put my custom widget in a QScrollArea, but in my custom widget, I reimplemented wheelEvent(e) and it never gets called.
I'm fine with the scroll area not having its mouse wheel scrolling functionality. I just need those wheelEvents to call my handler. I tried handling the events out at the level of the main window but I only got them when the scroll widget was at one of its extremes and couldn't have moved any further anyways, I need all of them.
Heres a simplified version of my code:
class custom(QWidget):
def __init__(self, parent=None):
super(custom, self).__init__(parent)
self.parent = parent
def wheelEvent(self,event):
print "Custom Widget's wheelEvent Handler"
class mainw(QMainWindow):
def __init__(self, parent=None):
super(mainw, self).__init__(parent)
scroll = QScrollArea()
self.tw = thread_widget(scroll)
scroll.setWidget(self.tw)
self.setCentralWidget(scroll)
def wheelEvent(self,event):
print "Main Window's wheelEvent Handler"
Can someone explain to me how it is determined which event handler gets the events in this situation?
I figured out that its got something to do with the installEventFilter method of QObject, but I couldn't get the example to work so I said to hell with this and changed my plan completely.
problem solved
You can install a eventFilter in your custom class
class custom(QWidget):
def __init__(self, parent=None):
super(custom, self).__init__(parent)
self.parent = parent
self.installEventFilter(self)
def eventFilter(self, qobject, qevent):
qtype = qevent.type()
if qtype == QEvent.Wheel:
... wheel event logic
return True
# parents event handler for all other events
return super(custom,self).eventFilter(qobject, qevent)

Categories