PyQt: eventFilter to get mouse position in a semi-transparent window - qt

I want to make:
a semi-transparent fullscreen window (rgba(0,0,0,180)).
while moving mouse, display absolute position on label.
user can press on it to get the absolute position of the mouse.
However I cannot achieve the second one. When moving mouse on it, label won't update mouse's position. But I found when moving out of label (after removing layout.setMargin(0) and layout.setSpacing(0)), it works.
# -*- coding: utf-8 -*-
import sys, os, math
from PyQt4 import QtCore, QtGui
class ScreenPositionLabel(QtGui.QWidget):
def __init__(self):
super(ScreenPositionLabel, self).__init__()
self.setStyleSheet("background-color:rgba(0, 0, 0, 180); color:#fff;")
self.setAttribute(QtCore.Qt.WA_TranslucentBackground)
#self.setAttribute(QtCore.Qt.WA_OpaquePaintEvent, False)
#self.setStyleSheet("QMainWindow{opacity:0.5;}")
self.label = QtGui.QLabel("Please click on screen")
self.label.setAlignment(QtCore.Qt.AlignCenter)
layout = QtGui.QHBoxLayout()
layout.addWidget(self.label)
# remove margin and padding
layout.setMargin(0)
layout.setSpacing(0)
self.setLayout(layout)
self.setMouseTracking(True)
self.installEventFilter(self)
self.label.show()
self.show()
def eventFilter(self, source, event):
if (event.type() == QtCore.QEvent.MouseMove and
event.buttons() == QtCore.Qt.NoButton):
pos = event.pos()
self.label.setText('Please click on screen. ( %d : %d )' % (pos.x(), pos.y()))
elif event.type() == QtCore.QEvent.MouseButtonPress:
pos = event.pos()
print('( %d : %d )' % (pos.x(), pos.y()))
self.close()
return QtGui.QWidget.eventFilter(self, source, event)
app = QtGui.QApplication(sys.argv)
main_window = ScreenPositionLabel()
app.exec_()
Any way to solve this problem? Thanks!

Your 4 questions:
1) I want to make: a semi-transparent fullscreen window (rgba(0,0,0,180)).
Yes, you can. Please use QWidget.setWindowOpacity (self, float level).
2) I want to make: while moving mouse, display absolute position on label.
I recommend using QWidget.mouseMoveEvent (self, QMouseEvent) to get current position your mouse and enable QWidget.setMouseTracking (self, bool enable) for track all mouse movement.
QWidget.setMouseTracking (self, bool enable)
QWidget.mouseMoveEvent (self, QMouseEvent)
3) I want to make: user can press on it to get the absolute position of the mouse.
Using QWidget.mousePressEvent (self, QMouseEvent) to track when mouse press.
4) However I cannot achieve the second one. When moving mouse on it, label won't update mouse's position. But I found when moving out of label (after removing layout.setMargin(0) and layout.setSpacing(0)), it works.
Because in default layout height of QLabel has spacing & margin, then real area isn't all area widget solve it is your solution is OK.
Full example for your solution:
import sys
from PyQt4 import QtGui, QtCore
class QCustomLabel (QtGui.QLabel):
def __init__ (self, parent = None):
super(QCustomLabel, self).__init__(parent)
self.setMouseTracking(True)
self.setTextLabelPosition(0, 0)
self.setAlignment(QtCore.Qt.AlignCenter)
def mouseMoveEvent (self, eventQMouseEvent):
self.setTextLabelPosition(eventQMouseEvent.x(), eventQMouseEvent.y())
QtGui.QWidget.mouseMoveEvent(self, eventQMouseEvent)
def mousePressEvent (self, eventQMouseEvent):
if eventQMouseEvent.button() == QtCore.Qt.LeftButton:
QtGui.QMessageBox.information(self, 'Position', '( %d : %d )' % (self.x, self.y))
QtGui.QWidget.mousePressEvent(self, eventQMouseEvent)
def setTextLabelPosition (self, x, y):
self.x, self.y = x, y
self.setText('Please click on screen ( %d : %d )' % (self.x, self.y))
class QCustomWidget (QtGui.QWidget):
def __init__ (self, parent = None):
super(QCustomWidget, self).__init__(parent)
self.setWindowOpacity(0.7)
# Init QLabel
self.positionQLabel = QCustomLabel(self)
# Init QLayout
layoutQHBoxLayout = QtGui.QHBoxLayout()
layoutQHBoxLayout.addWidget(self.positionQLabel)
layoutQHBoxLayout.setMargin(0)
layoutQHBoxLayout.setSpacing(0)
self.setLayout(layoutQHBoxLayout)
self.showFullScreen()
myQApplication = QtGui.QApplication(sys.argv)
myQTestWidget = QCustomWidget()
myQTestWidget.show()
myQApplication.exec_()

Related

Lock resize direction of QSizeGrip to Vertical/Horizontal [duplicate]

Good night.
I have seen some programs with new borderless designs and still you can make use of resizing.
At the moment I know that to remove the borders of a pyqt program we use:
QtCore.Qt.FramelessWindowHint
And that to change the size of a window use QSizeGrip.
But how can we resize a window without borders?
This is the code that I use to remove the border of a window but after that I have not found information on how to do it in pyqt5.
I hope you can help me with an example of how to solve this problem
from PyQt5.QtWidgets import QMainWindow,QApplication
from PyQt5 import QtCore
class Main(QMainWindow):
def __init__(self):
QMainWindow.__init__(self)
self.setWindowFlags(QtCore.Qt.FramelessWindowHint)
app = QApplication([])
m = Main()
m.show()
m.resize(800,600)
app.exec_()
If you use a QMainWindow you can add a QStatusBar (which automatically adds a QSizeGrip) just by calling statusBar():
This function creates and returns an empty status bar if the status bar does not exist.
Otherwise, you can manually add grips, and their interaction is done automatically based on their position. In the following example I'm adding 4 grips, one for each corner, and then I move them each time the window is resized.
class Main(QMainWindow):
def __init__(self):
QMainWindow.__init__(self)
self.setWindowFlags(QtCore.Qt.FramelessWindowHint)
self.gripSize = 16
self.grips = []
for i in range(4):
grip = QSizeGrip(self)
grip.resize(self.gripSize, self.gripSize)
self.grips.append(grip)
def resizeEvent(self, event):
QMainWindow.resizeEvent(self, event)
rect = self.rect()
# top left grip doesn't need to be moved...
# top right
self.grips[1].move(rect.right() - self.gripSize, 0)
# bottom right
self.grips[2].move(
rect.right() - self.gripSize, rect.bottom() - self.gripSize)
# bottom left
self.grips[3].move(0, rect.bottom() - self.gripSize)
UPDATE
Based on comments, also side-resizing is required. To do so a good solution is to create a custom widget that behaves similarly to QSizeGrip, but for vertical/horizontal resizing only.
For better implementation I changed the code above, used a gripSize to construct an "inner" rectangle and, based on it, change the geometry of all widgets, for both corners and sides.
Here you can see the "outer" rectangle and the "inner" rectangle used for geometry computations:
Then you can create all geometries, for QSizeGrip widgets (in light blue):
And for custom side widgets:
from PyQt5 import QtCore, QtGui, QtWidgets
class SideGrip(QtWidgets.QWidget):
def __init__(self, parent, edge):
QtWidgets.QWidget.__init__(self, parent)
if edge == QtCore.Qt.LeftEdge:
self.setCursor(QtCore.Qt.SizeHorCursor)
self.resizeFunc = self.resizeLeft
elif edge == QtCore.Qt.TopEdge:
self.setCursor(QtCore.Qt.SizeVerCursor)
self.resizeFunc = self.resizeTop
elif edge == QtCore.Qt.RightEdge:
self.setCursor(QtCore.Qt.SizeHorCursor)
self.resizeFunc = self.resizeRight
else:
self.setCursor(QtCore.Qt.SizeVerCursor)
self.resizeFunc = self.resizeBottom
self.mousePos = None
def resizeLeft(self, delta):
window = self.window()
width = max(window.minimumWidth(), window.width() - delta.x())
geo = window.geometry()
geo.setLeft(geo.right() - width)
window.setGeometry(geo)
def resizeTop(self, delta):
window = self.window()
height = max(window.minimumHeight(), window.height() - delta.y())
geo = window.geometry()
geo.setTop(geo.bottom() - height)
window.setGeometry(geo)
def resizeRight(self, delta):
window = self.window()
width = max(window.minimumWidth(), window.width() + delta.x())
window.resize(width, window.height())
def resizeBottom(self, delta):
window = self.window()
height = max(window.minimumHeight(), window.height() + delta.y())
window.resize(window.width(), height)
def mousePressEvent(self, event):
if event.button() == QtCore.Qt.LeftButton:
self.mousePos = event.pos()
def mouseMoveEvent(self, event):
if self.mousePos is not None:
delta = event.pos() - self.mousePos
self.resizeFunc(delta)
def mouseReleaseEvent(self, event):
self.mousePos = None
class Main(QtWidgets.QMainWindow):
_gripSize = 8
def __init__(self):
QtWidgets.QMainWindow.__init__(self)
self.setWindowFlags(QtCore.Qt.FramelessWindowHint)
self.sideGrips = [
SideGrip(self, QtCore.Qt.LeftEdge),
SideGrip(self, QtCore.Qt.TopEdge),
SideGrip(self, QtCore.Qt.RightEdge),
SideGrip(self, QtCore.Qt.BottomEdge),
]
# corner grips should be "on top" of everything, otherwise the side grips
# will take precedence on mouse events, so we are adding them *after*;
# alternatively, widget.raise_() can be used
self.cornerGrips = [QtWidgets.QSizeGrip(self) for i in range(4)]
#property
def gripSize(self):
return self._gripSize
def setGripSize(self, size):
if size == self._gripSize:
return
self._gripSize = max(2, size)
self.updateGrips()
def updateGrips(self):
self.setContentsMargins(*[self.gripSize] * 4)
outRect = self.rect()
# an "inner" rect used for reference to set the geometries of size grips
inRect = outRect.adjusted(self.gripSize, self.gripSize,
-self.gripSize, -self.gripSize)
# top left
self.cornerGrips[0].setGeometry(
QtCore.QRect(outRect.topLeft(), inRect.topLeft()))
# top right
self.cornerGrips[1].setGeometry(
QtCore.QRect(outRect.topRight(), inRect.topRight()).normalized())
# bottom right
self.cornerGrips[2].setGeometry(
QtCore.QRect(inRect.bottomRight(), outRect.bottomRight()))
# bottom left
self.cornerGrips[3].setGeometry(
QtCore.QRect(outRect.bottomLeft(), inRect.bottomLeft()).normalized())
# left edge
self.sideGrips[0].setGeometry(
0, inRect.top(), self.gripSize, inRect.height())
# top edge
self.sideGrips[1].setGeometry(
inRect.left(), 0, inRect.width(), self.gripSize)
# right edge
self.sideGrips[2].setGeometry(
inRect.left() + inRect.width(),
inRect.top(), self.gripSize, inRect.height())
# bottom edge
self.sideGrips[3].setGeometry(
self.gripSize, inRect.top() + inRect.height(),
inRect.width(), self.gripSize)
def resizeEvent(self, event):
QtWidgets.QMainWindow.resizeEvent(self, event)
self.updateGrips()
app = QtWidgets.QApplication([])
m = Main()
m.show()
m.resize(240, 160)
app.exec_()
to hide the QSizeGrip on the corners where they shouldn't be showing, you can just change the background color of the QSizeGrip to camouflage them to the background. add this to each of the corners of musicamante's answer:
self.cornerGrips[0].setStyleSheet("""
background-color: transparent;
""")

QGraphicsView: how to make rubber band selection appear only on left mouse button?

I want to make a QGraphicsScene and show it in QGraphicsView. I want to scroll the scene by middle mouse button and make rubber band selection by left button. But I don't know how to make the rubber band selection appear only by left mouse button.
Here's my code:
# -*- coding: utf-8 -*-
import os, sys
from PyQt5 import QtWidgets, QtCore, QtGui, QtSvg
class MegaSceneView(QtWidgets.QGraphicsView):
def __init__(self, parent=None):
super(MegaSceneView, self).__init__(parent)
self._scale_factor = 1.0
self._scale_by = 1.2
self.setAcceptDrops(True)
self.setRenderHint(QtGui.QPainter.Antialiasing)
self.setMouseTracking(True)
self.setRubberBandSelectionMode(QtCore.Qt.IntersectsItemShape)
self.setDragMode(QtWidgets.QGraphicsView.RubberBandDrag)
self._prev_mouse_scene_pos = None
def mousePressEvent(self, event):
if (event.buttons() & QtCore.Qt.MidButton) != QtCore.Qt.NoButton:
self._prev_mouse_scene_pos = (event.pos())
super(MegaSceneView, self).mousePressEvent(event)
def mouseReleaseEvent(self, event):
super(MegaSceneView, self).mouseReleaseEvent(event)
self._prev_mouse_scene_pos = None
def mouseMoveEvent(self, event):
super(MegaSceneView, self).mouseMoveEvent(event)
if (event.buttons() & QtCore.Qt.MidButton) != QtCore.Qt.NoButton:
cur_mouse_pos = (event.pos())
if self._prev_mouse_scene_pos is not None:
delta_x = cur_mouse_pos.x() - self._prev_mouse_scene_pos.x()
delta_y = cur_mouse_pos.y() - self._prev_mouse_scene_pos.y()
self.horizontalScrollBar().setValue(self.horizontalScrollBar().value() - delta_x)
self.verticalScrollBar().setValue(self.verticalScrollBar().value() - delta_y)
self._prev_mouse_scene_pos = (event.pos())
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
mega_view = MegaSceneView()
mega_scene = QtWidgets.QGraphicsScene(-500, -500, 1000, 1000)
# mega_scene = QtWidgets.QGraphicsScene()
rect_item_1 = QtWidgets.QGraphicsRectItem(-30, -20, 60, 40)
mega_scene.addItem(rect_item_1)
rect_item_2 = QtWidgets.QGraphicsRectItem(-20, -30, 40, 60)
mega_scene.addItem(rect_item_2)
rect_item_2.setPos(300, 200)
mega_view.setScene(mega_scene)
mega_view.show()
sys.exit(app.exec_())
What should I add to make the rubber band appear only by left button?
You can set the drag mode in the mousePressEvent and mouseReleaseEvent functions in your view class, so it stays in the RubberBandDrag as default, but switches to NoDrag mode when the middle mouse button is held.
Like so - c++, but the idea is the same in all languages - :
void YourViewClass::mousePressEvent(QMouseEvent* event) {
if (event->buttons() == Qt::MiddleButton)
setDragMode(QGraphicsView::NoDrag);
}
void YourViewClass::mouseReleaseEvent(QMouseEvent* event) {
if (event->button() == Qt::MiddleButton)
setDragMode(QGraphicsView::RubberBandDrag);
}
There isn't a built-in way to do this. You will need to subclass the mousePressEvent, mouseMoveEvent, and mouseReleaseEvent for your graphics view and create the visible rubber band yourself. (QRubberBand works well for this.) When the user releases the mouse, you then need to convert the rubber band extents into scene coordinates and call QGraphicsScene::setSelectionArea.

Restrict item in PyQt4‏ using itemChange()

I tried using the Qt documentation example to restrict the rectangle to the area of the scene but it still fails, someone has an alternative to do this?
My code, the QGraphicsView instance was created in Qt Desginer:
# -*- coding: utf-8 -*-
from PyQt4.QtCore import *
from PyQt4.QtGui import *
import sys
from screen import *
class MovableItem(QGraphicsRectItem):
def __init__(self, rectang, *args, **kwargs):
QGraphicsRectItem.__init__(self, rectang, *args, **kwargs)
self.setFlags(QGraphicsItem.ItemIsMovable |
QGraphicsItem.ItemSendsGeometryChanges)
self.pen = QPen(Qt.darkMagenta)
self.pen.setWidth(4)
self.setPen(self.pen)
def itemChange(self, change, value):
if change == QGraphicsItem.ItemPositionChange and self.scene():
# value is the new position.
self.newPos = value.toPointF()
self.rect = self.scene().sceneRect()
if not(self.rect.contains(self.newPos)):
# Keep the item inside the scene rect.
self.newPos.setX(min(self.rect.right(), max(self.newPos.x(), self.rect.left())))
self.newPos.setY(min(self.rect.bottom(), max(self.newPos.y(), self.rect.top())))
return self.newPos
return QGraphicsRectItem.itemChange(self, change, value)
class Main(QWidget, Ui_Form):
def __init__(self, parent=None):
super(Main, self).__init__(parent)
self.setupUi(self)
self.scene = QGraphicsScene()
self.cena.setScene(self.scene)
self.scene.addPixmap(QPixmap("01.png"))
self. graph = MovableItem(2, 2, 300, 150)
self.scene.addItem(self.graph)
def showEvent(self, event):
self.cena.fitInView(self.scene.sceneRect(), Qt.IgnoreAspectRatio)
app = QApplication(sys.argv)
window = Main()
window.show()
sys.exit(app.exec_())
First:
Use setSceneRect() in your main Main(), to set the size of the scene.
Second:
Actually the example of the documentation is wrong, therefore, to adjust the rectangle to the scene, delete this if and subtract, in min, the parameters right and bottom by the rectangle dimensions right and bottom in setX and setY. Replace this part of your code:
if not(self.rect.contains(self.newPos)):
# Keep the item inside the scene rect.
self.newPos.setX(min(self.rect.right(), max(self.newPos.x(), self.rect.left())))
self.newPos.setY(min(self.rect.bottom(), max(self.newPos.y(), self.rect.top())))
return self.newPos
For:
self.newPos.setX(min(self.rect.right()-self.boundingRect().right(), max(self.newPos.x(), self.rect.left())))
self.newPos.setY(min(self.rect.bottom()-self.boundingRect().bottom(), max(self.newPos.y(), self.rect.top())))
return self.newPos

Attach a QToolButton to bottom right corner of the parent QWidget

I am faced with this problem and being a Qt noob am not able to fix it.
Basically, I instantiated a QToolButton and parented it to QTreeWidget. The QTreeWidget is inside a vertical layout and when I try to change the position of the tool button inside the QTreeWidget using QTreeWidget.size() it gives me very unexpected and wrong results.
Can anyone help me with this? Will deeply appreciate the help. Thanks!
You haven't posted any examples of what you are actually doing, but here is how to attach a button to the lower right of the tree widget:
Edit: I have replaced my answer after seeing that you want to composite the widget OVER the tree
Using an eventFilter
from PyQt4 import QtCore, QtGui
class Widget(QtGui.QWidget):
def __init__(self, parent=None):
super(Widget, self).__init__(parent)
self.resize(640,480)
self.layout = QtGui.QVBoxLayout(self)
self.layout.setSpacing(0)
self.tree = QtGui.QTreeWidget(self)
self.tree.installEventFilter(self)
self.layout.addWidget(self.tree)
self.button = QtGui.QToolButton(self.tree)
self.button.setText("FOO")
self.button.setMinimumSize(100, 30)
def eventFilter(self, obj, event):
if obj is self.tree and event.type() == event.Resize:
self.alignTreeButton()
return False
def alignTreeButton(self):
padding = QtCore.QSize(5,5) # optional
newSize = self.tree.size() - self.button.size() - padding
self.button.move(newSize.width(), newSize.height())
if __name__ == "__main__":
app = QtGui.QApplication([])
w = Widget()
w.show()
w.raise_()
app.exec_()
The button is just parented to the tree, and we install the event filter on the tree to catch resize events. Once the tree is resized, we take its size, subtract the size of the button, and then move the button.
Using composition
I believe its more efficient to actually subclass the QTreeWidget, compose it with the QToolButton as a member, and then overload the resizeEvent() locally to handle the resize. First off this makes the behavior handling local to the TreeWidget, which is cleaner. Also, I believe it reduces the overhead that an EventFilter would add to your main window. The eventFiler would be a python callable that is called many more times because of it handling every event for the object. Whereas the local resizeEvent() for the TreeWidget is only called during the resize.
class Widget(QtGui.QWidget):
def __init__(self, parent=None):
super(Widget, self).__init__(parent)
self.resize(640,480)
self.layout = QtGui.QVBoxLayout(self)
self.layout.setSpacing(0)
self.tree = TreeWidget(self)
self.layout.addWidget(self.tree)
class TreeWidget(QtGui.QTreeWidget):
def __init__(self, *args, **kwargs):
super(TreeWidget, self).__init__(*args, **kwargs)
self.button = QtGui.QToolButton(self)
self.button.setText("FOO")
self.button.setMinimumSize(100, 30)
def resizeEvent(self, event):
super(TreeWidget, self).resizeEvent(event)
self.alignTreeButton()
def alignTreeButton(self):
padding = QtCore.QSize(5,5) # optional
newSize = self.size() - self.button.size() - padding
self.button.move(newSize.width(), newSize.height())

Qt/PyQt: QGraphicsItem vs. QGraphicsWidget geometry, position, mouse interaction

I am converting a larger program of QGraphicsItems to QGraphicsWidgets (let's call them item and widget for typing sake). Mouse hover fails now because the position and/or rect of the widgets are not the same as the old items. I've boiled down to a simple case with a view, scene, an item and a widget. The blue item renders at 100x50 pix, and hoverEnterEvent occurs as expected. However, the red widget is rendered at half intended width. I can fix this if I reimplement the pure virtual function boundingRect for the widget but the hover event is still only triggered atop the 50x50 left half. What pos/rect/geometry methods do I need to use/override to get the widget to interact properly with the mouse just like the item? Thanks. Here's my sample code
#!/usr/local/bin/python
import os, sys
from PyQt4.Qt import *
from PyQt4.QtCore import *
from PyQt4.QtGui import *
class MyView(QGraphicsView):
def __init__(self):
QGraphicsView.__init__(self)
self.setWindowFlags(Qt.WindowStaysOnTopHint)
self.scene = QGraphicsScene(self)
self.item = GraphicsItem('item', 100, 50)
self.item.moveBy(50, 50)
self.scene.addItem(self.item)
self.widget = GraphicsWidget('widget', 100, 50)
self.scene.addItem(self.widget)
self.setScene(self.scene)
class GraphicsItem(QGraphicsItem):
def __init__(self, name, width, height):
QGraphicsItem.__init__(self)
self.setAcceptHoverEvents(True)
self.name = name
self.__width = width
self.__height = height
def boundingRect(self):
return QRectF(0, 0, self.__width, self.__height)
def hoverEnterEvent(self, event):
self.__printGeometryDetails()
def paint(self, painter, option, widget):
bgRect = self.boundingRect()
painter.drawRects(bgRect)
painter.fillRect(bgRect, QColor('blue'))
def __printGeometryDetails(self):
print self.name
print ' pos (%.0f, %0.0f)' % (self.pos().x(), self.pos().y())
print ' boundingRect (%.0f, %0.0f, %.0f, %0.0f)' % (self.boundingRect().x(), self.boundingRect().y(), self.boundingRect().width(), self.boundingRect().height())
class GraphicsWidget(QGraphicsWidget):
def __init__(self, name, width, height):
QGraphicsWidget.__init__(self)
self.setAcceptHoverEvents(True)
self.name = name
self.__width = width
self.__height = height
def boundingRect(self):
return QRectF(0, 0, self.__width, self.__height)
def hoverEnterEvent(self, event):
self.__printGeometryDetails()
def paint(self, painter, option, widget):
bgRect = self.boundingRect()
painter.drawRects(bgRect)
painter.fillRect(bgRect, QColor('red'))
def __printGeometryDetails(self):
print self.name
print ' pos (%.0f, %0.0f)' % (self.pos().x(), self.pos().y())
print ' boundingRect (%.0f, %0.0f, %.0f, %0.0f)' % (self.boundingRect().x(), self.boundingRect().y(), self.boundingRect().width(), self.boundingRect().height())
print ' geometry (%.0f, %0.0f, %.0f, %0.0f)' % (self.geometry().x(), self.geometry().y(), self.geometry().width(), self.geometry().height())
print ' rect (%.0f, %0.0f, %.0f, %0.0f)' % (self.rect().x(), self.rect().y(), self.rect().width(), self.rect().height())
if __name__ == '__main__':
app = QApplication(sys.argv)
view = MyView()
view.setGeometry(600, 100, 400, 370)
view.show()
sys.exit(app.exec_())
It does seem to work correctly if you use self.resize(width, height) instead of redefining boundingRect.

Resources