Here is my simplified widget. I want to:
hide the mouse
lock it to the center of the window
get the relative mouse movement
I want similar mouse control to first person shooters.
class QGLControllerWidget(QtOpenGL.QGLWidget):
def __init__(self, app):
fmt = QtOpenGL.QGLFormat()
fmt.setVersion(3, 3)
fmt.setProfile(QtOpenGL.QGLFormat.CoreProfile)
fmt.setSampleBuffers(True)
fmt.setDepthBufferSize(24)
super(QGLControllerWidget, self).__init__(fmt, None)
self.setMouseTracking(True)
def mouseMoveEvent(self, event):
print(event.x(), event.y())
Related
This is a follow-up question. Here is the link to my previous question. The answer there works but the problem however I faced was that in the beginning, the line would be drawn to the mouse press taking it as the endpoint instead of been drawn from the mouse press taking it as the starting point. As suggested by a user there explicitly setting the sceneRect solves the problem, but however after explicitly setting the sceneRect the view stops adapting and showing scrollbar.
Here's a demo before adding sceneRect
As you could see the viewport automatically adjusts, when the mouse is over any of the edges, giving the user more space to draw. (Also note how the line is been drawn to the mouse press at the very beginning)
And Here is after if used self.setSceneRect(QtCore.QRectF(0, 0, 500, 500)) inside the constructor of the Scene class
As you could see it doesn't adjust the viewport itself when the mouse is near the edges of the screen unlike how it happened previously.
Now coming to my question, is there a way to make the view adapts to the changes inside the scene automatically after setting the sceneRect, or should I change sceneRect manually like shown here?
One possible solution is to update the sceneRect based on the size of the QGraphicsView, in addition to setting the view alignment to topleft:
class GraphicsView(QtWidgets.QGraphicsView):
def __init__(self, parent=None):
super(GraphicsView, self).__init__(parent)
self.setAlignment(QtCore.Qt.AlignTop | QtCore.Qt.AlignLeft)
self.setRenderHint(QtGui.QPainter.Antialiasing)
self.setMouseTracking(True)
scene = Scene()
self.setScene(scene)
def resizeEvent(self, event):
super().resizeEvent(event)
self.setSceneRect(
QtCore.QRectF(QtCore.QPointF(0, 0), QtCore.QSizeF(event.size()))
)
def main():
app = QtWidgets.QApplication(sys.argv)
view = GraphicsView()
view.show()
sys.exit(app.exec_())
if __name__ == "__main__":
main()
If you want to observe the scrollbars then you can calculate the maximum between the previous sceneRect and the sceneRect based on the size of the viewport:
class GraphicsView(QtWidgets.QGraphicsView):
def __init__(self, parent=None):
super(GraphicsView, self).__init__(parent)
self.setAlignment(QtCore.Qt.AlignTop | QtCore.Qt.AlignLeft)
self.setRenderHint(QtGui.QPainter.Antialiasing)
self.setMouseTracking(True)
scene = Scene()
self.setScene(scene)
def resizeEvent(self, event):
super().resizeEvent(event)
r = QtCore.QRectF(QtCore.QPointF(0, 0), QtCore.QSizeF(event.size()))
self.setSceneRect(self.sceneRect().united(r))
According to the drag and drop documentation, I was able to implement the drag functionality on my QToolButton, but that is overriding standard button click behaviour and I am unable to check if the button was pressed or there was an intent to start a drag by dragging mouse.
Here is my QToolBar..
class toolbarButton(QToolButton):
def __init__(self, parent = None, item = None):
super(toolbarButton, self).__init__(parent)
self.setIcon(...)
self.setIconSize(QSize(40, 40))
self.dragStartPosition = 0
...
def mousePressEvent(self, event):
if event.button() == Qt.LeftButton:
self.dragStartPosition = event.pos()
def mouseMoveEvent(self, event):
if not (event.buttons() and Qt.LeftButton):
return
if (event.pos() - self.dragStartPosition).manhattanLength() < QApplication.startDragDistance():
return
drag = QDrag(self)
mimeData = QMimeData()
...
drag.setMimeData(mimeData)
drag.exec(Qt.CopyAction)
def sizeHint(self):
return self.minimumSizeHint()
def minimumSizeHint(self):
return QSize(30, 30)
My initial thought, was to add the emit when the distance is less than startdragdistance but that would be incorrect as it would fire everytime I moved my mouse. Is there a way of achieving this in PyQt5? That I get standard QToolButton behaviour on button press and my custom behaviour on button drag?
When you override a method you are removing the default behavior, so that does not happen then you must call the parent method through super():
def mousePressEvent(self, event):
super().mousePressEvent(event)
if event.button() == Qt.LeftButton:
self.dragStartPosition = event.pos()
My actual application is much more complicated than this, but the example below sums up the majority of my problem. I have multiple QLabels that I've subclassed to make them clickable. The labels display 16x16 images which requires a process of loading the images via Pillow, converting them to ImageQt objects, and then setting the pixmap of the label. In the example, I have 3 clickable QLabels that run the print_something function each time I click on them. My goal is to be able to hold the mouse down, and for each label I hover over, the function gets called. Any pointers would be great.
from PyQt5 import QtCore, QtWidgets, QtGui
from PIL import Image
from PIL.ImageQt import ImageQt
import sys
class ClickableLabel(QtWidgets.QLabel):
def __init__(self):
super().__init__()
clicked = QtCore.pyqtSignal()
def mousePressEvent(self, ev):
if app.mouseButtons() & QtCore.Qt.LeftButton:
self.clicked.emit()
class MainWindow(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
central_widget = QtWidgets.QWidget()
self.setFixedSize(300, 300)
image = Image.open("16x16image.png")
image_imageqt = ImageQt(image)
hbox = QtWidgets.QHBoxLayout()
hbox.setSpacing(0)
hbox.addStretch()
label01 = ClickableLabel()
label01.setPixmap(QtGui.QPixmap.fromImage(image_imageqt))
label01.clicked.connect(self.print_something)
hbox.addWidget(label01)
label02 = ClickableLabel()
label02.setPixmap(QtGui.QPixmap.fromImage(image_imageqt))
label02.clicked.connect(self.print_something)
hbox.addWidget(label02)
label03 = ClickableLabel()
label03.setPixmap(QtGui.QPixmap.fromImage(image_imageqt))
label03.clicked.connect(self.print_something)
hbox.addWidget(label03)
hbox.addStretch()
central_widget.setLayout(hbox)
self.setCentralWidget(central_widget)
def print_something(self):
print("Printing something..")
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
main_window = MainWindow()
main_window.show()
sys.exit(app.exec_())
The cause of the problem is stated in the docs for QMouseEvent:
Qt automatically grabs the mouse when a mouse button is pressed inside
a widget; the widget will continue to receive mouse events until the
last mouse button is released.
It does not look like there is a simple way around this, so something hackish will be required. One idea is to initiate a fake drag and then use dragEnterEvent instead of enterEvent. Something like this should probably work:
class ClickableLabel(QtWidgets.QLabel):
clicked = QtCore.pyqtSignal()
def __init__(self):
super().__init__()
self.setAcceptDrops(True)
self.dragstart = None
def mousePressEvent(self, event):
if event.buttons() & QtCore.Qt.LeftButton:
self.dragstart = event.pos()
self.clicked.emit()
def mouseReleaseEvent(self, event):
self.dragstart = None
def mouseMoveEvent(self, event):
if (self.dragstart is not None and
event.buttons() & QtCore.Qt.LeftButton and
(event.pos() - self.dragstart).manhattanLength() >
QtWidgets.qApp.startDragDistance()):
self.dragstart = None
drag = QtGui.QDrag(self)
drag.setMimeData(QtCore.QMimeData())
drag.exec_(QtCore.Qt.LinkAction)
def dragEnterEvent(self, event):
event.acceptProposedAction()
if event.source() is not self:
self.clicked.emit()
just a simple question: I'm using pyqt4 to render a simple window. Here's the code, I post the whole thing so it easier to explain.
from PyQt4 import QtGui, QtCore, Qt
import time
import math
class FenixGui(QtGui.QWidget):
def __init__(self):
super(FenixGui, self).__init__()
# setting layout type
hboxlayout = QtGui.QHBoxLayout(self)
self.setLayout(hboxlayout)
# hiding title bar
self.setWindowFlags(QtCore.Qt.FramelessWindowHint)
# setting window size and position
self.setGeometry(200, 200, 862, 560)
self.setAttribute(Qt.Qt.WA_TranslucentBackground)
self.setAutoFillBackground(False)
# creating background window label
backgroundpixmap = QtGui.QPixmap("fenixbackground.png")
self.background = QtGui.QLabel(self)
self.background.setPixmap(backgroundpixmap)
self.background.setGeometry(0, 0, 862, 560)
# fenix logo
logopixmap = QtGui.QPixmap("fenixlogo.png")
self.logo = QtGui.QLabel(self)
self.logo.setPixmap(logopixmap)
self.logo.setGeometry(100, 100, 400, 150)
def main():
app = QtGui.QApplication([])
exm = FenixGui()
exm.show()
app.exec_()
if __name__ == '__main__':
main()
Now, you see that I put a background label in my window. I would like that the window could be dragged around the screen by dragging this label. I mean: you click on the label, you drag the label, and the whole window comes around the screen. Is this possible? I accept non-elegant ways as well, because as you can see I hid the title bar so it would be impossible to drag the window if I don't make it draggable via the background label.
Hope I explained my problem properly
Thank you very much!!
Matteo Monti
You can override mousePressEvent() and mouseMoveEvent() to get the location of the mouse cursor and move your widget to that location. mousePressEvent will give you the offset from the cursor position to the top left corner of your widget, and then you can calculate what the new position of the top left corner should be. You can add these methods to your FenixGui class.
def mousePressEvent(self, event):
self.offset = event.pos()
def mouseMoveEvent(self, event):
x=event.globalX()
y=event.globalY()
x_w = self.offset.x()
y_w = self.offset.y()
self.move(x-x_w, y-y_w)
So I have a frameless QDialog that I want to be able to move around simply by clicking and dragging it. Given the code below, dragging the dialog always snaps the very top-left (0,0) of the dialog to the mouse. How might I circumvent this, or rather, what might the math be for it?
Standard QDialog with the following basic subclass:
class Main(QtGui.QDialog):
def __init__(self, args):
QtGui.QDialog.__init__(self)
def mouseMoveEvent(self, event):
super(Main, self).mouseMoveEvent(event)
if self.leftClick == True: self.moveWindow(event.globalPos())
def mousePressEvent(self, event):
super(Main, self).mousePressEvent(event)
if event.button() == QtCore.Qt.LeftButton:
self.leftClick = True
def mouseReleaseEvent(self, event):
super(Main, self).mouseReleaseEvent(event)
self.leftClick = False
Instead of event.pos(), try calling event.globalPos(). From the QMouseEvent reference, "If you move the widget as a result of the mouse event, use the global position returned by globalPos() to avoid a shaking motion."
Proposed solution moves Window, but mouse cursor jumps to 0,0 of Window. I wanted mouse cursor to stay on x,y of the Window all the time.
Here is upgraded version of the code [in QT5]:
X=0
X2=8 #!!!!
Y=0
Y2=30 #!!!!
class Main(QtWidgets.QMainWindow):
leftClick = False #! IMPORTANT
def __init__(self, parent=None):
QtWidgets.QWidget.__init__(self, parent)
self.ui = Ui_MainWindow()
self.ui.setupUi(self)
def mouseMoveEvent(self, event):
super(Main, self).mouseMoveEvent(event)
if self.leftClick == True:
self.move(event.globalPos().x()-X-X2,event.globalPos().y()-Y-Y2)
def mousePressEvent(self, event):
super(Main, self).mousePressEvent(event)
if event.button() == QtCore.Qt.LeftButton:
self.leftClick = True
global X,Y
X=event.pos().x()
Y=event.pos().y()
def mouseReleaseEvent(self, event):
super(Main, self).mouseReleaseEvent(event)
self.leftClick = False
For frameless window (made with window.setMask()) I need some constants like X2 and Y2, because "masked" frameless window is a bit smaller than a real framed window. Don't know how to calculate this difference yet.
UP. After long time I've found one critical bug. If you press Left Mouse Button on any pushbutton (just after the application start) and drag the mouse cursor away from that pushbutton, your application will crash, because we refer to nonexistent variable LeftClick. That's why in class Main we need to create LeftClick.