How to detect mouse hover event in PySide6 widget - python

I am trying to create a custom image viewer widget with zoom to the mouse position. So far I have managed to detect mouse scroll events, but I cannot detect a mouse hover events so I could determine a mouse position to zoom to.
I seems to me that the mouse hover event is not even occurring. I tried to print out all events, but the QHoverEvent is just not there. The only event occurring during mouse hover is QEvent::ToolTip which has the mouse position but it only occurs after the mouse hovering stops and it has quite a delay (~0.5s).
Here is the code:
import sys
from PySide6 import QtWidgets
from PySide6.QtWidgets import QDialog, QVBoxLayout, QLabel
from PySide6.QtGui import QPixmap
from PySide6.QtCore import Qt
from PIL.ImageQt import ImageQt
class ImageViewer(QDialog):
def eventFilter(self, object, event):
print("Event:" + str(event))
if str(event) == '<PySide6.QtGui.QWheelEvent(Qt::NoScrollPhase, pixelDelta=QPoint(0,0), angleDelta=QPoint(0,-120))>':
print("detected zoom out")
if str(event) == '<PySide6.QtGui.QWheelEvent(Qt::NoScrollPhase, pixelDelta=QPoint(0,0), angleDelta=QPoint(0,120))>':
print("detected zoom in")
if str(event) == '<PySide6.QtCore.QEvent(QEvent::ToolTip)>':
print("detected tooltip")
return True
def __init__(self, img: ImageQt):
super().__init__()
self.setWindowTitle('Image viewer example')
self.imageWidget = QLabel()
self.imageWidget.setAlignment(Qt.AlignCenter)
self.imageWidget.setPixmap(QPixmap.fromImage(img))
self.layout = QVBoxLayout()
self.layout.addWidget(self.imageWidget)
self.setLayout(self.layout)
self.imageWidget.installEventFilter(self)
if __name__ == '__main__':
# prepare app
app = QtWidgets.QApplication(sys.argv)
# create viewer widget
imageViewer = ImageViewer(ImageQt("img.png"))
imageViewer.show()
# close app
sys.exit(app.exec())
I am able to detect the mouse scrolling, enter widget, leave, mouse button press/release, mouse move (with mouse pressed). But the mouse hover is just not there. Could someone tell me how to detect the mouse hover event (with mouse position info)?

It works for me with self.setAttribute(Qt.WidgetAttribute.WA_Hover) in the __init__() constructor.

Related

Simulate mouse hover in PyQt5

How do you simulate mouse hover for PyQt5 to a coordinate? Perhaps with QPoint.
import sys
from PyQt5.QtWidgets import QApplication
from PyQt5.QtCore import Qt, QUrl, QTimer
from PyQt5.QtWebEngineWidgets import QWebEngineView, QWebEngineSettings
class Screenshot(QWebEngineView):
def capture(self, url, output_file):
self.output_file = output_file
self.load(QUrl(url))
self.loadFinished.connect(self.on_loaded)
# Create hidden view without scrollbars
#self.setAttribute(Qt.WA_DontShowOnScreen)
self.page().settings().setAttribute(
QWebEngineSettings.ShowScrollBars, False)
self.show()
def on_loaded(self):
size = self.page().contentsSize().toSize()
self.resize(size)
# Wait for resize
print("Capturing")
QTimer.singleShot(3000, self.take_screenshot)
def take_screenshot(self):
self.grab().save(self.output_file, b'PNG')
self.app.quit()
app = QApplication(sys.argv)
screenshots = {}
screenshot = Screenshot()
screenshot.app = app
screenshot.capture('https://zoom.earth/maps/wind-speed/#view=13.955336,121.109689,11z', 'wind.png')
Such that it shows a UI overlay like in the image.
Zoom Wind map, Mouse hovered at center
Faking a mouse event is not that difficult, it's just a matter of creating a QMouseEvent with the correct arguments:
event = QMouseEvent(QEvent.MouseMove,
QPoint(someX, someY),
Qt.NoButton,
Qt.MouseButtons(Qt.NoButton),
Qt.KeyboardModifiers(Qt.NoModifier))
the QPoint argument represents the point in which the cursor is supposed to be, in local coordinates: in the following example I'll use self.rect().center(), which is the exact center in the rectangle of the viewport;
the next argument is the mouse button that generates the QMouseEvent; since a mouse move event is not generated by a mouse button press, it should always be NoButton;
. the argument after represents the buttons that are pressed at the time of the event; since we suppose that this is just a "hover" movement, we still have a NoButton value (but using the MouseButton flag, since it represents a flag and multiple buttons could be pressed);
the last is obviously about the keyboard modifiers, and we again don't need any of that, but still we must use a flag;
The important part is to send the event to the correct widget. QWebEngineView uses a private QWidget subclass to show the actual content and receive user interaction, so we need to find that widget.
def on_loaded(self):
size = self.page().contentsSize().toSize()
self.resize(size)
# Get the actual web content widget
child = self.findChild(QWidget)
# Create a fake mouse move event
event = QMouseEvent(QEvent.MouseMove,
self.rect().center(),
Qt.NoButton, Qt.MouseButtons(Qt.NoButton),
Qt.KeyboardModifiers(Qt.NoModifier))
# Send the event
QApplication.postEvent(child, event)
# Wait for resize
print("Capturing")
QTimer.singleShot(3000, self.take_screenshot)

QGraphicsProxyWidget messes up mouse cursor changes

I have a QGraphicsView where I do various mouse operations and while doing mouse operations I change mouse cursor to give user visual clues.
I recently put some QWidgets onto my scene which automatically creates QGraphicsProxyWidget objects. And after putting these widgets I started having problems with my mouse cursor changes. I can change mouse cursor until I hover over a QGraphicsProxyWidget item. After that, my mouse cursor changes stop taking effect.
I created a minimal example in PySide2:
import sys
from PySide2.QtGui import QMouseEvent, Qt
from PySide2.QtWidgets import QApplication, QLabel, QGraphicsView, QGraphicsScene
class CustomGraphicsView(QGraphicsView):
def mousePressEvent(self, event: QMouseEvent):
super().mousePressEvent(event)
self.setCursor(Qt.ClosedHandCursor)
event.accept()
def mouseReleaseEvent(self, event: QMouseEvent):
super().mouseReleaseEvent(event)
self.setCursor(Qt.ArrowCursor)
event.accept()
if __name__ == "__main__":
app = QApplication(sys.argv)
graphics_view = CustomGraphicsView()
scene = QGraphicsScene(0, 0, 1000, 1000)
graphics_view.setScene(scene)
label = QLabel("Hello World")
label_font = label.font()
label_font.setPointSize(50)
label.setFont(label_font)
label_proxy = scene.addWidget(label)
label_proxy.setPos(400, 450)
graphics_view.show()
app.exec_()
To reproduce: first, click on anywhere in the scene without hovering your mouse over the "Hello World" text. You will see mouse cursor changing into a closed hand. Then hover your mouse over "Hello World" text and try again clicking anywhere on scene. You will see that the cursor is not changing anymore.
I am not sure if this is an expected behavior or bug. What might be the reason for this behavior?
System
OS: Windows 10 20H2
Python 3.8.6 64-bit
PySide2 5.15.2
Disclaimer: Still investigating the cause of that behavior so at the time of writing this answer I will only give the solution. But it seems is that the propagation of the events is not handled correctly by the QGraphicsProxyWidget. See this bug for more information.
Instead of setting the cursor in the QGraphicsView it should be set in the viewport.
class CustomGraphicsView(QGraphicsView):
def mousePressEvent(self, event: QMouseEvent):
super().mousePressEvent(event)
self.viewport().setCursor(Qt.ClosedHandCursor)
event.accept()
def mouseReleaseEvent(self, event: QMouseEvent):
super().mouseReleaseEvent(event)
self.viewport().unsetCursor()
event.accept()

Emit a signal when any button is pressed

In a pyqt5 app I want to untoggle a button when any other button is clicked.
Is it possible to have a signal emitted when any button inside a layout or a widget is clicked?
I can connect each button click to an untoggle function but that would be hard to maintain if the buttons ever change. Is there a clean way to achieve the same functionality without having to connect the buttons one by one?
How to point out a possible solution is to inherit from the button and implement the indicated logic, but in the case of Qt it can be taken advantage of the fact that the clicked signal is associated with the QEvent.MouseButtonRelease event when a button is pressed. Considering the above, notify can be used to implement the logic:
import sys
from PyQt5.QtCore import pyqtSignal, QCoreApplication, QEvent
from PyQt5.QtWidgets import (
QAbstractButton,
QApplication,
QPushButton,
QVBoxLayout,
QWidget,
)
class Application(QApplication):
buttonClicked = pyqtSignal(QAbstractButton)
def notify(self, receiver, event):
if (
isinstance(receiver, QAbstractButton)
and receiver.isEnabled()
and event.type() == QEvent.MouseButtonRelease
):
self.buttonClicked.emit(receiver)
return super().notify(receiver, event)
def main(argv):
app = Application(argv)
view = QWidget()
lay = QVBoxLayout(view)
special_button = QPushButton("Special")
lay.addWidget(special_button)
for i in range(10):
button = QPushButton(f"button{i}")
lay.addWidget(button)
view.show()
def handle_clicked(button):
if button is not special_button:
print(f"{button} clicked")
QCoreApplication.instance().buttonClicked.connect(handle_clicked)
sys.exit(app.exec_())
if __name__ == "__main__":
main(sys.argv)
What you want is added functionality for an existing type. This suggests that you want to write an extension of the Button class. Write a class that inherits from Button. Write your own function to handle button clicks: this function will call your toggle function and then call the parent's button click handler.

PyQt5 widgets intended to be transparent to show QVideoPlayer pass through video and adopt QMainWindow background

I have a QMainWindow with a QVideoWidget set as the central widget. The video is acting like a background to the main window of the blackjack game I'm making. I have two buttons I'd like to place on top of the video with partial transparency.
This is what I want the window to look like:
but I've tried seemingly dozens of ways to achieve the transparent button background and can't. Any attempt at transparency 'cuts' straight through the video and shows the background of the MainWindow instead. For example, I have the main window background set to blue.
This is what shows when run:
How can I make the video appear behind the transparent buttons instead?
Things I've tried without success: stacked layouts set to StackAll, inserting transparent background images in the button stylesheets, using transparent color codes (rgba(0,0,0,0)), setting the videowidget as the parent widget to the buttons.
The relevant portion of my code:
from PyQt5 import QtWidgets, QtMultimediaWidgets, QtMultimedia, QtCore, QtGui, Qt
from PyQt5.QtWidgets import QApplication, QMainWindow, QLabel, QPushButton, QLineEdit, QComboBox
from PyQt5.QtGui import QTransform
import sys
import random
class MyWindow(QMainWindow):
def __init__(self):
super(MyWindow, self).__init__()
self.setGeometry(0, 0, 1920, 1080)
self.setWindowTitle("Cutie Pie Casino")
self.setStyleSheet('background-color: blue;')
self.Welcome()
def Welcome(self):
welcome_font = QtGui.QFont()
welcome_font.setPointSize(30)
welcome_font.setFamily('Broadway')
welcome_font_small = QtGui.QFont()
welcome_font_small.setPointSize(20)
welcome_font_small.setFamily('Broadway')
# create link to movie file
movie_file = QtCore.QUrl.fromLocalFile('C:/Users/Owner/PycharmProjects/Black Jack/video/'
'Cutie Pie Casino Video.mp4')
vid_media = QtMultimedia.QMediaContent(movie_file)
# create video widget
self.videoWidget = QtMultimediaWidgets.QVideoWidget()
self.videoWidget.setGeometry(0,0,1920,1080)
self.videoWidget.setStyleSheet('background-color: blue')
# create media player object (video widget goes in media player)
self.mediaPlayer = QtMultimedia.QMediaPlayer(None,
QtMultimedia.QMediaPlayer.VideoSurface)
self.mediaPlayer.setVideoOutput(self.videoWidget)
# playlist
self.playlist = QtMultimedia.QMediaPlaylist()
self.playlist.setCurrentIndex(0)
self.playlist.setPlaybackMode(QtMultimedia.QMediaPlaylist.Loop)
self.playlist.addMedia(vid_media)
# add content to media player
self.mediaPlayer.setPlaylist(self.playlist)
self.mediaPlayer.play()
self.setCentralWidget(self.videoWidget)
self.PlayCardsButton = QPushButton(self)
self.PlayCardsButton.setText('Play Blackjack')
self.PlayCardsButton.setFont(welcome_font_small)
self.PlayCardsButton.setGeometry(765,500,400,150)
self.PlayCardsButton.clicked.connect(self.InitializeTable)
self.PlayCardsButton.setStyleSheet('background-color: rgba(0,0,0,0); color: black')
self.GameSetupButton = QPushButton(self)
self.GameSetupButton.setGeometry(765, 700, 400, 150)
self.GameSetupButton.setFont(welcome_font_small)
self.GameSetupButton.setText('Game Setup')
self.GameSetupButton.setStyleSheet('background-color: rgba(0,0,0,0); color: black')
You could try with:
self.PlayCardsButton.setFlat(True)

Hover issue in PyQt

I want to do hover. I saw an example and then write a script which will be use as I made program. I am facing one problem that hover only occur if you put mouse on the left corner of button. I want that it will happen for all the button that if i move cursor on button then it should change.
Here is my code:
from PyQt4 import QtGui, QtCore
from PyQt4.QtCore import pyqtSignal
import os,sys
class HoverButton(QtGui.QToolButton):
def enterEvent(self,event):
print("Enter")
button.setStyleSheet("background-color:#45b545;")
def leaveEvent(self,event):
button.setStyleSheet("background-color:yellow;")
print("Leave")
app = QtGui.QApplication(sys.argv)
widget = QtGui.QWidget()
button = QtGui.QToolButton(widget)
button.setMouseTracking(True)
buttonss = HoverButton(button)
button.setIconSize(QtCore.QSize(200,200))
widget.show()
sys.exit(app.exec_())
Is this what you're looking for
from PyQt4 import QtGui, QtCore
from PyQt4.QtCore import pyqtSignal
import os,sys
class Main(QtGui.QWidget):
def __init__(self, parent=None):
super(Main, self).__init__(parent)
layout = QtGui.QVBoxLayout(self) # layout of main widget
button = HoverButton(self)
button.setIconSize(QtCore.QSize(200,200))
layout.addWidget(button) # set your button to the widgets layout
# this will size the button nicely
class HoverButton(QtGui.QToolButton):
def __init__(self, parent=None):
super(HoverButton, self).__init__(parent)
self.setMouseTracking(True)
def enterEvent(self,event):
print("Enter")
self.setStyleSheet("background-color:#45b545;")
def leaveEvent(self,event):
self.setStyleSheet("background-color:yellow;")
print("Leave")
app = QtGui.QApplication(sys.argv)
main = Main()
main.show()
sys.exit(app.exec_())
In your code you had a button in a button and the nested button wasn't assigned to a QLayout widget. Although, I'm not sure why you're adding a button inside of a button. One thing that I've learned from working with GUI's is that it's so much easier if you modularize your code. Now you can take this custom button and apply it somewhere else.
You should use the stylesheet as
QToolButton:hover
{
background-color: rgb(175,175,175);
}
You probably want focus and blur, rather than enter and leave will only fire when the mouse actually enters or leaves the boundary of the button and will probably just be short duration impulses rather than toggles. focus and blur will toggle with the hover.

Categories