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

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;
""")

Related

Should I use QGraphicsView to display an image and some decorated text side by side?

I want to create a "details" view for books I have downloaded.
With the attached image as an example, imagine the red block to the left is the book's cover page, and metadata related to it is displayed to the right.
With the way I have it done right now:
from PySide6 import QtWidgets as qtw
from PySide6 import QtGui as qtg
from PySide6 import QtCore as qtc
class Details:
def __init__(self):
self.location = "/home/user/Desktop/Untitled.png"
self.title = "Some title"
self.subtitle = "Sub title"
self.id = 123124
def to_html(self):
return """
<p>
<b>Author =</b> author<br/>
<b>Published Date =</b> 2000-1-1<br/>
<b>Pages =</b> 500<br/>
</p>
"""
class DetailsWidget(qtw.QWidget):
_title_font = qtg.QFont()
_title_font.setBold(True)
_title_font.setPixelSize(24)
_subtitle_font = qtg.QFont()
_subtitle_font.setBold(True)
_subtitle_font.setPixelSize(19)
_id_font = qtg.QFont()
_id_font.setBold(True)
_id_font.setPixelSize(15)
_redacted_details_font = qtg.QFont()
_redacted_details_font.setPixelSize(12)
def __init__(self, *args, **kwargs) -> None:
super().__init__(*args, **kwargs)
self.setFixedSize(1000, 500)
self.setWindowFlag(qtc.Qt.WindowType.Dialog, True)
self.setLayout(qtw.QGridLayout())
self.layout().setContentsMargins(0, 0, 0, 0)
self._details: Details = Details()
self._thumbnail_image = qtg.QImage(self._details.location)
self._thumbnail_image = self._thumbnail_image.scaled(
500,
500,
qtc.Qt.AspectRatioMode.KeepAspectRatio,
qtc.Qt.TransformationMode.SmoothTransformation,
)
self._details_rect = qtc.QRect(
self._get_actual_geometry().left() + self._thumbnail_image.width() + 10,
self._get_actual_geometry().top(),
self._get_actual_geometry().width() - self._thumbnail_image.width() - 20,
self._get_actual_geometry().height(),
)
height = 0
self._title_rects = []
font_metrics_rect = qtg.QFontMetrics(self._title_font).boundingRect(
self._details_rect, qtc.Qt.TextFlag.TextWordWrap, self._details.title, 0
)
drawing_rect = qtc.QRect(self._details_rect)
self._title_rects.append(drawing_rect)
height += font_metrics_rect.height() + 10
drawing_rect = qtc.QRect(self._details_rect)
drawing_rect.moveTop(height)
self._title_rects.append(drawing_rect)
font_metrics_rect = qtg.QFontMetrics(self._title_font).boundingRect(
self._details_rect, qtc.Qt.TextFlag.TextWordWrap, self._details.subtitle, 0
)
drawing_rect = qtc.QRect(self._details_rect)
height += font_metrics_rect.height() - 3
drawing_rect.moveTop(height)
self._title_rects.append(drawing_rect)
font_metrics_rect = qtg.QFontMetrics(self._title_font).boundingRect(
self._details_rect,
qtc.Qt.TextFlag.TextWordWrap,
str(self._details.id),
0,
)
self._title_rects.append(drawing_rect)
height += font_metrics_rect.height() + 10
self._details_rect.moveTop(height)
self._redacted_details_text_document = qtg.QTextDocument()
self._redacted_details_text_document.setHtml(self._details.to_html())
# First set the width,
self._redacted_details_text_document.setTextWidth(self._details_rect.width())
# then get the height of the QTextDocument based on the given width and set
# that + the titles heights + bottom padding as the total height.
if (total_height:=height + self._redacted_details_text_document.size().height() + 10) > self.height():
self.setFixedHeight(total_height)
def _get_actual_geometry(self) -> qtc.QRect:
# Probably not needed for normal desktop environments with window
# managers but I'm an epik i3 user so self.geometry() does not work as
# intended when full screening the window with $mod + F. Or I'm just
# retarded and this is not even a problem.
geometry = self.geometry()
geometry.setTopLeft(qtc.QPoint(0, 0))
return geometry
def paintEvent(self, event: qtg.QPaintEvent) -> None:
total_height = 0
painter = qtg.QPainter(self)
painter.setRenderHint(qtg.QPainter.RenderHint.TextAntialiasing)
painter.drawImage(0, 0, self._thumbnail_image)
painter.save()
painter.setFont(self._title_font)
painter.drawText(
self._title_rects[0], qtc.Qt.TextFlag.TextWordWrap, self._details.title
)
painter.setFont(self._subtitle_font)
painter.drawText(
self._title_rects[1], qtc.Qt.TextFlag.TextWordWrap, self._details.subtitle
)
painter.setFont(self._id_font)
painter.drawText(
self._title_rects[2],
qtc.Qt.TextFlag.TextWordWrap,
str(self._details.id),
)
painter.translate(self._details_rect.topLeft())
painter.setFont(self._redacted_details_font)
self._redacted_details_text_document.drawContents(painter)
painter.restore()
app = qtw.QApplication()
widget = DetailsWidget()
widget.show()
app.exec()
I can display the text and the image next to each other just fine, but the text is not selectable. Looking around for a way to do so, I stumbled upon QGraphicsTextItem. Should I re-do the whole thing in a QGraphicsView instead of using the paintEvent on a QWidget? The reason I'm hesitant to do so is because I don't know of the cons of using a QGraphicsView, maybe it's a lot more resource heavy and not the best for this use case?
You're complicating things unnecessarily.
Just use a basic QHBoxLayout and two QLabels, with the one on the left for the image, and the one on the right for the details.
If you want to allow text selection, use QLabel.setTextInteractionFlags(Qt.TextSelectableByMouse).
An even better solution would be to use a QGraphicsView with a QGraphicsPixmapItem for the image (using fitInView() in the resizeEvent to always show it as large as possible) and a QTextEdit for the details, set in read only mode.
Note that your usage of _get_actual_geometry is wrong in principle (besides the fact that you're calling 4 times in a row, while you could just use a local variable instead), because when a widget has not been shown yet it always has a default size (100x30 for widgets created with a parent, otherwise 640x480), so not only you'll be getting a wrong geometry, but you're also changing it, since setTopLeft() will only move the corner, not translate the rectangle: if you want the basic rectangle of the widget, just use rect(). Obviously, if you properly use layouts as suggested above, this won't be necessary in the first place.

QDockWidget with QPixmap - how to prevent QPixmap limiting the downsizing of parent widget while maintaining aspect ration?

I haven't worked with images in labels for a long time so I'm stuck with an issue - once resized a QPixmap (loaded inside a QLabel or similar widget) cannot return to a smaller (downsized) version of itself. This is particularly annoying when working with docked widgets in a QMainWindow or similar setting:
from PySide2.QtCore import *
from PySide2.QtGui import *
from PySide2.QtWidgets import *
from random import seed
from random import random
class CentralWidget(QWidget):
def __init__(self):
QWidget.__init__(self)
vb_layout = QVBoxLayout()
self.label = QLabel('Central Widget')
self.label.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
vb_layout.addWidget(self.label)
self.setLayout(vb_layout)
class DockedWidget(QDockWidget):
class Widget(QWidget):
def __init__(self):
QWidget.__init__(self)
vb_layout = QVBoxLayout()
self.label = QLabel()
# Enable scaled contents, otherwise enjoy artifacts and visual glitches
self.label.setScaledContents(True)
self.rimg = QImage(self.width(),self.height(), QImage.Format_Grayscale8)
self.rimg.fill(Qt.black)
print(self.rimg.width(), self.rimg.height())
for j in range(self.height()):
for i in range(self.width()):
r = round(random()* 255)
if r % 2 == 0:
self.rimg.setPixel(i, j, qRgb(255, 0, 0))
self.label.setPixmap(QPixmap.fromImage(self.rimg))
vb_layout.addWidget(self.label)
self.setLayout(vb_layout)
def resizeEvent(self, e: QResizeEvent) -> None:
super().resizeEvent(e)
preview = self.label.pixmap()
# FIXME Trying to figure out a way to scale image inside label up and down
self.label.setPixmap(preview.scaled(self.label.width(),self.label.height(),Qt.KeepAspectRatio))
def __init__(self):
QDockWidget.__init__(self)
self.setWindowTitle('Docked Widget')
self.widget = DockedWidget.Widget()
self.setWidget(self.widget)
class MyWindow(QMainWindow):
def __init__(self):
QMainWindow.__init__(self)
self.setGeometry(300, 100, 270, 100)
self.setWindowTitle('Test')
dockedwidget = DockedWidget()
self.addDockWidget(Qt.LeftDockWidgetArea, dockedwidget)
widget = CentralWidget()
self.setCentralWidget(widget)
seed(1)
app = QApplication([])
win = MyWindow()
win.show()
app.exec_()
I've tried to link the pixmap's scaling to the parent label, which in terms should be controlled by the behaviour of the docked widget. Initially I was facing the issue that the image would stretch and create weird artifacts:
I figured out I had to enable scaled contents (QLabel.setScaledContents()) but I'm still facing the issue that I cannot go below the initial size of the image:
Minimum size restricts resizing beyond the initially set image size
Increasing the size is not a problem
I need to make the image capable of downsizing properly, otherwise it compromises the rest of the components in the layout in my actual setup. I'm thinking that the solution lies somewhere between the resize event and the size policy.
QLabel does not provide support for aspect ratio, and it normally only allows to scale it to sizes bigger than the image size (but this can be worked around using setMinimumSize(1, 1)).
While you could create a subclass that actually paints the proper aspect ratio no matter of the widget size, it's often not necessary, as you could use a QGraphicsView and a basic pixmap item.
class ImageWidget(QGraphicsView):
def __init__(self):
super().__init__()
self.setFrameShape(0)
self.setStyleSheet('''
QGraphicsView {
background: transparent;
}
''')
rimg = QImage(640, 480, QImage.Format_Grayscale8)
rimg.fill(Qt.black)
for j in range(480):
for i in range(640):
r = round(random()* 255)
if r % 2 == 0:
rimg.setPixel(i, j, qRgb(255, 0, 0))
scene = QGraphicsScene()
self.setScene(scene)
self.pixmapItem = scene.addPixmap(QPixmap.fromImage(rimg))
def resizeEvent(self, event):
super().resizeEvent(event)
self.fitInView(self.pixmapItem, Qt.KeepAspectRatio)
class DockedWidget(QDockWidget):
def __init__(self):
QDockWidget.__init__(self)
self.setWindowTitle('Docked Widget')
self.widget = ImageWidget()
self.setWidget(self.widget)
Note: 1. use nested classes only when actually necessary, otherwise they only make the code cumbersome and difficult to read; 2. all widgets have a default size when created (640x480, or 100x30 for widgets created with a parent), so using self.width() and self.height() is pointless.

Extending selection in either direction in a QTextEdit

Currently, QTextEdit permits selecting text and then altering that selection with shift-click-drag only on the side of the selection opposite the anchor. The anchor is placed where the selection started. If the user tries to alter the selection near the start, the selection pivots around the anchor point instead of extending. I'd like to permit changing the selection from either side.
My first attempt is to simply set the anchor on the opposite side from where the cursor is located. Say, for example, the selection is from 10 to 20. If the cursor is shift-click-dragged at position 8, then the anchor would be set to 20. If the cursor is shift-click-dragged at position 22, then the anchor would be set to 10. Later, I'll try something more robust, perhaps based on the center point of the selection.
I thought this code would work, but it does not seem to affect the default behavior at all. What have I missed?
import sys
from PySide.QtCore import *
from PySide.QtGui import *
class TextEditor(QTextEdit):
def __init__(self, parent=None):
super().__init__(parent)
self.setReadOnly(True)
self.setMouseTracking(True)
def mouseMoveEvent(self, event):
point = QPoint()
x = event.x() #these are relative to the upper left corner of the text edit window
y = event.y()
point.setX(x)
point.setY(y)
self.mousepos = self.cursorForPosition(point).position() # get character position of current mouse using local window coordinates
if event.buttons()==Qt.LeftButton:
modifiers = QApplication.keyboardModifiers()
if modifiers == Qt.ShiftModifier:
start = -1 #initialize to something impossible
end = -1
cursor = self.textCursor()
select_point1 = cursor.selectionStart()
select_point2 = cursor.selectionEnd()
if select_point1 < select_point2: # determine order of selection points
start = select_point1
end = select_point2
elif select_point2 < select_point1:
start = select_point2
end = select_point1
if self.mousepos > end: # if past end when shift-click then trying to extend right
cursor.setPosition(start, mode=QTextCursor.MoveAnchor)
elif self.mousepos < start: # if before start when shift-click then trying to extend left
cursor.setPosition(end, mode=QTextCursor.MoveAnchor)
if start != -1 and end != -1: #if selection exists then this should trigger
self.setTextCursor(cursor)
super().mouseMoveEvent(event)
Here's a first stab at implementing shift+click extension of the current selection. It seems to work okay, but I have not tested it to death, so there may be one or two glitches. The intended behaviour is that a shift+click above or below the selection should extend the whole selection in that direction; and a shift+click with drag should do the same thing, only continuously.
Note that I have also set the text-interaction flags so that the caret is visible in read-only mode, and the selection can also be manipulated with the keyboard in various ways (e.g. ctrl+shift+right extends the selection to the next word).
import sys
from PySide.QtCore import *
from PySide.QtGui import *
class TextEditor(QTextEdit):
def __init__(self, parent=None):
super().__init__(parent)
self.setReadOnly(True)
self.setTextInteractionFlags(
Qt.TextSelectableByMouse |
Qt.TextSelectableByKeyboard)
def mouseMoveEvent(self, event):
if not self.setShiftSelection(event, True):
super().mouseMoveEvent(event)
def mousePressEvent(self, event):
if not self.setShiftSelection(event):
super().mousePressEvent(event)
def setShiftSelection(self, event, moving=False):
if (event.buttons() == Qt.LeftButton and
QApplication.keyboardModifiers() == Qt.ShiftModifier):
cursor = self.textCursor()
start = cursor.selectionStart()
end = cursor.selectionEnd()
if not moving or start != end:
anchor = cursor.anchor()
pos = self.cursorForPosition(event.pos()).position()
if pos <= start:
start = pos
elif pos >= end:
end = pos
elif anchor == start:
end = pos
else:
start = pos
if pos <= anchor:
start, end = end, start
cursor.setPosition(start, QTextCursor.MoveAnchor)
cursor.setPosition(end, QTextCursor.KeepAnchor)
self.setTextCursor(cursor)
return True
return False
if __name__ == '__main__':
app = QApplication(sys.argv)
window = TextEditor()
window.setText(open(__file__).read())
window.setGeometry(600, 50, 800, 800)
window.show()
sys.exit(app.exec_())

How do I mouse-drag-zoom a QGraphicsView anchored to the mouse drag starting position?

I'm trying to click and drag to zoom in and out of a QGraphicsView like you see in graphics applications like Maya and Nuke. There is a lot of information about using the mouse wheel but I haven't found anything related to dragging to zoom.
Is there an easy way to do this or do I need to roll my own implementation of the "anchor" effect?
The following will work but the view follows the mouse around as I drag the zoom rather than appearing to zoom in and out of a fixed point in space (the point where the mouse was clicked to start the drag-zoom.
(This is a bunch of copy and paste from my more complex source code. It is intended to be illustrative though it should run)
def mousePressEvent(self, event):
self.press_mouse_pos = event.pos()
transform = self.transform()
self.press_translate = [transform.m31(), transform.m32()]
self.press_scale = transform.m11()
if event.button() == QtCore.Qt.RightButton and \
event.modifiers() == QtCore.Qt.AltModifier:
self.scaling = True
event.accept()
else:
super(GraphView, self).mousePressEvent(event)
def mouseMoveEvent(self, event):
if self.scaling:
delta_pos = event.pos() - self.press_mouse_pos
amount = delta_pos.x() + delta_pos.y()
speed = 0.001
scl = self.press_scale - (amount * speed)
scl = min(1.0, max(scl, 0.1)) # Clamp so we don't go to far in or out
transform = QtGui.QTransform(
scl, 0, 0,
0, scl, 0,
self.press_translate[0], self.press_translate[1], 1
)
# If interactive is True then some double calculations are triggered
prev_interactive_state = self.isInteractive()
prev_anchor_mode = self.transformationAnchor()
self.setInteractive(False)
self.setTransformationAnchor(QtGui.QGraphicsView.AnchorUnderMouse)
self.setTransform(transform)
self.setInteractive(prev_interactive_state)
self.setTransformationAnchor(prev_anchor_mode)
else:
super(GraphView, self).mouseMoveEvent(event)
def mouseReleaseEvent(self, event):
self.scaling = False
super(GraphView, self).mouseReleaseEvent(event)
Change this:
self.setTransformationAnchor(QtGui.QGraphicsView.AnchorUnderMouse)
to that:
self.setTransformationAnchor(QtGui.QGraphicsView.NoAnchor)
setTransformation() is a subject to transformation anchor; AnchorUnderMouse makes sure that a scene point corresponding the current (at the moment when transform is applied) mouse position remains untranslated.

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

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_()

Resources