Widget used for infinitely resizable painting in librecad - python

Librecad uses a widget which can be infinitely resized, you can zoom in and out as much as you can. Which widget does it uses?
When I paint into a common widget, the painting is done at certain coordinates of the widget. However, I would like to draw at floating coordinates of the widget and use a line width which is fixed to certain pixels of the viewport.
Before resizing:
After resizing:
Which widget provides this functionality?

You have to use QGraphicsView and QGraphicsScene(see Graphics View Framework):
from PyQt5 import QtCore, QtGui, QtWidgets
class GraphicsView(QtWidgets.QGraphicsView):
def __init__(self, parent=None):
super(GraphicsView, self).__init__(parent)
self.factor = 1.2
self.setDragMode(QtWidgets.QGraphicsView.ScrollHandDrag)
self.setRenderHints(
QtGui.QPainter.Antialiasing | QtGui.QPainter.SmoothPixmapTransform
)
self.setMouseTracking(True)
self.setScene(
QtWidgets.QGraphicsScene(QtCore.QRectF(-400, -400, 800, 800), self)
)
QtWidgets.QShortcut(QtGui.QKeySequence.ZoomIn, self, activated=self.zoomIn) # Ctrl + +
QtWidgets.QShortcut(QtGui.QKeySequence.ZoomOut, self, activated=self.zoomOut) # Ctrl + -
#QtCore.pyqtSlot()
def zoomIn(self):
self.zoom(self.factor)
#QtCore.pyqtSlot()
def zoomOut(self):
self.zoom(1 / self.factor)
def zoom(self, f):
self.scale(f, f)
def drawForeground(self, painter, rect):
super(GraphicsView, self).drawForeground(painter, rect)
if not hasattr(self, "cursor_position"):
return
painter.save()
painter.setTransform(QtGui.QTransform())
pen = QtGui.QPen(QtGui.QColor("yellow"))
pen.setWidth(4)
painter.setPen(pen)
r = self.mapFromScene(rect).boundingRect()
linex = QtCore.QLine(
r.left(), self.cursor_position.y(), r.right(), self.cursor_position.y(),
)
liney = QtCore.QLine(
self.cursor_position.x(), r.top(), self.cursor_position.x(), r.bottom(),
)
for line in (linex, liney):
painter.drawLine(line)
painter.restore()
def mouseMoveEvent(self, event):
self.cursor_position = event.pos()
self.scene().update()
super(GraphicsView, self).mouseMoveEvent(event)
def wheelEvent(self, event):
angle = event.angleDelta().y()
if angle < 0:
self.zoomIn()
else:
self.zoomOut()
super(GraphicsView, self).wheelEvent(event)
if __name__ == "__main__":
import sys
import random
app = QtWidgets.QApplication(sys.argv)
w = GraphicsView()
for _ in range(4):
r = QtCore.QLineF(
*random.sample(range(-200, 200), 2), *random.sample(range(50, 150), 2)
)
it = w.scene().addLine(r)
pen = QtGui.QPen(QtGui.QColor(*random.sample(range(255), 3)))
pen.setWidthF(5.0)
pen.setCosmetic(True)
it.setPen(pen)
w.resize(640, 480)
w.show()
sys.exit(app.exec_())

Related

How do I stop the small spinning circles along the scanning line in QPainter?

I'm trying to rotate a line in a circle like clock's second arm. When rotating the line, I also want to draw small circles here and there inside the circle. The problem is that the small circles I drew is rotating along the line. I want these circles stay at their location and move with my code. Can someone help me?
import sys
from PySide2.QtCore import *
from PySide2.QtGui import *
from PySide2.QtWidgets import *
from PySide2.QtOpenGL import *
from OpenGL import GL
class Helper:
def __init__(self):
gradient = QLinearGradient(QPointF(50, -20), QPointF(80, 20))
gradient.setColorAt(0.0, Qt.green)
gradient.setColorAt(1.0, Qt.blue)
self.background = QBrush(QColor(0, 0, 0))
self.circlePen = QPen(Qt.green)
self.circlePen.setWidth(2)
self.textPen = QPen(Qt.green,8,Qt.SolidLine)
self.textFont = QFont()
self.textFont.setPixelSize(100)
def paint(self, painter, event, elapsed):
painter.fillRect(event.rect(), self.background)
painter.setPen(self.circlePen)
painter.translate(400, 400)
painter.drawEllipse(-250,-250,500,500)
painter.rotate(elapsed * 0.35)
painter.setBrush(QColor(Qt.green))
dx = 100
dy = 100
for i in range(10):
painter.drawEllipse(dx,dy,8,8)
dx += 10
dy += 10
painter.save()
painter.drawLine(0,-250,0,0)
painter.restore()
class GLWidget(QGLWidget):
def __init__(self, helper, parent = None):
QGLWidget.__init__(self, QGLFormat(QGL.SampleBuffers), parent)
self.helper = helper
self.elapsed = 0
self.setFixedSize(800, 800)
def animate(self):
self.elapsed = (self.elapsed + self.sender().interval()) % 1000
self.repaint()
def paintEvent(self, event):
painter = QPainter()
painter.begin(self)
self.helper.paint(painter, event, self.elapsed)
painter.end()
class Window(QWidget):
def __init__(self, parent = None):
QWidget.__init__(self, parent)
helper = Helper()
openGL = GLWidget(helper, self)
openGLLabel = QLabel(self.tr("OpenGL"))
openGLLabel.setAlignment(Qt.AlignHCenter)
layout = QGridLayout()
layout.addWidget(openGL, 100, 100)
layout.addWidget(openGLLabel, 1, 0)
self.setLayout(layout)
timer = QTimer(self)
self.connect(timer, SIGNAL("timeout()"), openGL.animate)
timer.start(1)
self.setWindowTitle(self.tr("2D Painting on Native and OpenGL Widgets"))
if __name__ == '__main__':
app = QApplication(sys.argv)
window = Window()
window.show()
sys.exit(app.exec_())
small green circles are rotating with line
small green circles are rotating with line
1: The results of above code
The result I have
The result I want like this

How to properly scale GraphicsScene

I've created a small app and I'm trying to make it so that when the main window is resized (and the GraphicsView and scene are too) that the whole scene (pixmap and rectangles) scale vertically to fit completely inside the GraphicsView. I don't want a vertical scrollbar and I don't want it to scale horizontally.
I can't figure out how to scale the scene properly. I use a GraphicsScene to contain a graph and a couple vertical rectangle "markers". When I can scale the graph to fit by redrawing the pixmap and then reattach it, the z-order is wrong AND the rectangle widgets are not scaled with it.
I need to keep track of the rectangle widgets, so I can't just keep deleting and re-adding them as there's meta data along with each one.
I know about fitInView (from here: Issue with fitInView of QGraphicsView when ItemIgnoresTransformations is on) that applies to the containing GraphicsView, but I don't understand why it needs a parameter. I just want the scene to fit in the GraphicsView (vertically but not horizontally) so why doesn't GraphicsView just scale everything in the scene to fit inside it's current size? What should the parameter look like to get the scene to fit vertically?
In the resizeEvent I can redraw the pixmap and re-add, but then it covers the rectangles as the z-order is messed up. Also, it doesn't stay centered vertically in the scene and I would need to copy over the meta data.
import sys
import os
from PyQt5 import QtCore, QtGui, QtWidgets
import PyQt5 as qt
from PyQt5.QtGui import QColor
from PyQt5.QtCore import Qt, QPoint
from PyQt5.QtWidgets import QApplication, QWidget, QPushButton, QHBoxLayout, QGroupBox, QDialog, QVBoxLayout
from PyQt5.QtWidgets import QVBoxLayout, QGridLayout, QStackedWidget, QTabWidget
import numpy as np
class GraphicsScene(QtWidgets.QGraphicsScene):
def __init__(self, parent=None):
super(GraphicsScene, self).__init__(parent)
def minimumSizeHint(self):
return QtCore.QSize(300, 200)
def dragMoveEvent(self, event):
print("dragMoveEvent", event)
class MainWindow(QtWidgets.QMainWindow):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
#super(MainWindow).__init__()
layout = QVBoxLayout()
layout.setContentsMargins(0, 0, 0, 0)
max_x, max_y = 2400, 700
max_x_view = 1200
self.max_x = max_x
self.max_y = max_y
self.first = True
self.setGeometry(200, 200, max_x_view, self.max_y)
self.gv = QtWidgets.QGraphicsView(self)
self.gv.setGeometry(0, 0, max_x_view, self.max_y)
self.gv2 = QtWidgets.QGraphicsView(self)
layout.addWidget(self.gv)
layout.addWidget(self.gv2)
scene = GraphicsScene()
self.scene = scene
self.gv.setScene(scene)
tab_widget = QTabWidget()
tab_widget.setTabPosition(QTabWidget.West)
widget = QWidget()
widget.setLayout(layout)
tab_widget.addTab(widget, "main")
self.setCentralWidget(tab_widget)
np.random.seed(777)
self.x_time = np.linspace(0, 12.56, 3000)
rand_data = np.random.uniform(0.0, 1.0, 3000)
self.data = .45*(np.sin(2*self.x_time) + rand_data) - .25*(np.sin(3*self.x_time))
self.first = True
pixmap_height = max_y//2 - 2*22 # 22 to take care of scrollbar height
pixmap = self.draw_graph()
pen = QtGui.QPen()
pen.setWidth(2)
pen.setColor(QtGui.QColor("red"))
self.gv1_pixmap = scene.addPixmap(pixmap)
rect = scene.sceneRect()
print("scene rect = {}".format(rect))
scene.setSceneRect(rect)
side, offset = 50, 200
for i in range(2):
r = QtCore.QRectF(QtCore.QPointF((i + 1)*offset + i * 2 * side, 2), QtCore.QSizeF(side, pixmap_height - 4))
rect_ref = scene.addRect(r, pen, QColor(255, 0, 0, 127))
rect_ref.setFlag(QtWidgets.QGraphicsItem.ItemIsMovable)
all_items = scene.items()
print(all_items)
def draw_graph(self):
print("draw_graph: main Window size {}:".format(self.size()))
pixmap_height = self.height()//2 - 2*22 # 22 to take care of scrollbar height
x_final = self.x_time[-1]
data = self.data / np.max(np.abs(self.data))
data = [abs(int(k * pixmap_height)) for k in self.data]
x_pos = [int(self.x_time[i] * self.max_x / x_final) for i in range(len(data))]
pixmap = QtGui.QPixmap(self.max_x, pixmap_height)
painter = QtGui.QPainter(pixmap)
pen = QtGui.QPen()
pen.setWidth(2)
rect = pixmap.rect()
pen.setColor(QtGui.QColor("red"))
painter.drawRect(rect)
print("pixmap rect = {}".format(rect))
painter.fillRect(rect, QtGui.QColor('lightblue'))
pen.setWidth(2)
pen.setColor(QtGui.QColor("green"))
painter.setPen(pen)
for x, y in zip(x_pos, data):
painter.drawLine(x, pixmap_height, x, pixmap_height - y)
painter.end()
return pixmap
def resizeEvent(self, a0: QtGui.QResizeEvent):
#print("main Window resizeEvent")
print("main Window size {}:".format(a0.size()))
redraw = False
if redraw:
pixmap = self.draw_graph()
self.scene.removeItem(self.gv1_pixmap)
self.gv1_pixmap = self.scene.addPixmap(pixmap)
self.gv1_pixmap.moveBy(0, 30)
else:
#rect = QtCore.QRect(self.gv.startPos, self.gv.endPos)
#sceneRect = self.gv.mapToScene(rect).boundingRect()
#print 'Selected area: viewport coordinate:', rect,', scene coordinate:', sceneRect
#self.gv.fitInView(sceneRect)
pass
app = QtWidgets.QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec_()
My solution will fit the height of the smallest rectangle that encapsulates all the items (sceneRect) to the viewport of the QGraphicsView. So set the height of the items a value not so small so that the image quality is not lost. I have also scaled the items using the QTransforms. In addition, the QGraphicsView coordinate system was inverted since by default the vertical axis is top-bottom and I have inverted it so that the painting is more consistent with the data.
I have refactored the OP code to make it more scalable, there is a GraphItem that takes the data (x, y) and the image dimensions.
Considering the above, the solution is:
import numpy as np
from PyQt5 import QtCore, QtGui, QtWidgets
class GraphItem(QtWidgets.QGraphicsPixmapItem):
def __init__(self, xdata, ydata, width, height, parent=None):
super(GraphItem, self).__init__(parent)
self._xdata = xdata
self._ydata = ydata
self._size = QtCore.QSize(width, height)
self.redraw()
def redraw(self):
x_final = self._xdata[-1]
pixmap = QtGui.QPixmap(self._size)
pixmap_height = pixmap.height()
pixmap.fill(QtGui.QColor("lightblue"))
painter = QtGui.QPainter(pixmap)
pen = QtGui.QPen(QtGui.QColor("green"))
pen.setWidth(2)
painter.setPen(pen)
for i, (x, y) in enumerate(
zip(self._xdata, self._ydata / np.max(np.abs(self._ydata)))
):
x_pos = int(x * self._size.width() / x_final)
y_pos = abs(int(y * pixmap_height))
painter.drawLine(x_pos, 0, x_pos, y_pos)
painter.end()
self.setPixmap(pixmap)
class HorizontalRectItem(QtWidgets.QGraphicsRectItem):
def itemChange(self, change, value):
if change == QtWidgets.QGraphicsItem.ItemPositionChange and self.scene():
newPos = self.pos()
newPos.setX(value.x())
return newPos
return super(HorizontalRectItem, self).itemChange(change, value)
class GraphicsView(QtWidgets.QGraphicsView):
def __init__(self, parent=None):
super(GraphicsView, self).__init__(parent)
scene = QtWidgets.QGraphicsScene(self)
self.setScene(scene)
self.scale(1, -1)
def resizeEvent(self, event):
h = self.mapToScene(self.viewport().rect()).boundingRect().height()
r = self.sceneRect()
r.setHeight(h)
self.setSceneRect(r)
height = self.viewport().height()
for item in self.items():
item_height = item.boundingRect().height()
tr = QtGui.QTransform()
tr.scale(1, height / item_height)
item.setTransform(tr)
super(GraphicsView, self).resizeEvent(event)
class MainWindow(QtWidgets.QMainWindow):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
tab_widget = QtWidgets.QTabWidget(tabPosition=QtWidgets.QTabWidget.West)
self.setCentralWidget(tab_widget)
self.graphics_view_top = GraphicsView()
self.graphics_view_bottom = QtWidgets.QGraphicsView()
container = QtWidgets.QWidget()
lay = QtWidgets.QVBoxLayout(container)
lay.addWidget(self.graphics_view_top)
lay.addWidget(self.graphics_view_bottom)
tab_widget.addTab(container, "main")
self.resize(640, 480)
side, offset, height = 50, 200, 400
np.random.seed(777)
x_time = np.linspace(0, 12.56, 3000)
rand_data = np.random.uniform(0.0, 1.0, 3000)
data = 0.45 * (np.sin(2 * x_time) + rand_data) - 0.25 * (np.sin(3 * x_time))
graph_item = GraphItem(x_time, data, 3000, height)
self.graphics_view_top.scene().addItem(graph_item)
for i in range(2):
r = QtCore.QRectF(
QtCore.QPointF((i + 1) * offset + i * 2 * side, 2),
QtCore.QSizeF(side, height),
)
it = HorizontalRectItem(r)
it.setPen(QtGui.QPen(QtGui.QColor("red"), 2))
it.setBrush(QtGui.QColor(255, 0, 0, 127))
self.graphics_view_top.scene().addItem(it)
it.setFlags(
it.flags()
| QtWidgets.QGraphicsItem.ItemIsMovable
| QtWidgets.QGraphicsItem.ItemSendsGeometryChanges
)
def main():
import sys
app = QtWidgets.QApplication(sys.argv)
w = MainWindow()
w.show()
ret = app.exec_()
sys.exit(ret)
if __name__ == "__main__":
main()

QMovie with border radius

How would I apply a border radius, or otherwise achieve a rounded-corner effect, with a QMovie in PyQt5? It doesn't seem to react to QSS. Although I do not believe it to be relevant, here is my current code anyways, to give an idea what I have tried:
image = QLabel()
image.setObjectName("rant-image")
movie = QMovie("image_cache/" + img_name)
image.setMovie(movie)
movie.start()
with QSS:
QLabel#rant-image{
border-radius: 5px;
}
I have also tried painting the current pixmap each paintEvent by subclassing a QWidget, but nothing appears, and the pixmap has dimensions of 0:
invisible_pen = QPen()
invisible_pen.setWidth(0)
invisible_pen.setStyle(Qt.NoPen)
class RoundedMovie(QWidget):
def __init__(self, movie, parent=None):
QWidget.__init__(self, parent)
self.movie = movie
def setMovie(self, movie):
self.movie = movie
def paintEvent(self, event):
painter = QPainter(self)
painter.setRenderHint(QPainter.Antialiasing, True)
pixmap = self.movie.currentPixmap()
brush = QBrush(pixmap)
rect = QRect(0, 0, pixmap.width() - 10, pixmap.height() - 10)
painter.setPen(invisible_pen)
painter.setBrush(brush)
painter.drawRoundedRect(rect, 5, 5)
I also know the above implementation would not work because a paintEvent will not occur often enough to play the movie as expected
A possible solution is to implement a QProxyStyle:
from PyQt5 import QtCore, QtGui, QtWidgets
class RoundPixmapStyle(QtWidgets.QProxyStyle):
def __init__(self, radius=10, *args, **kwargs):
super(RoundPixmapStyle, self).__init__(*args, **kwargs)
self._radius = radius
def drawItemPixmap(self, painter, rectangle, alignment, pixmap):
painter.save()
pix = QtGui.QPixmap(pixmap.size())
pix.fill(QtCore.Qt.transparent)
p = QtGui.QPainter(pix)
p.setBrush(QtGui.QBrush(pixmap))
p.setPen(QtCore.Qt.NoPen)
p.drawRoundedRect(pixmap.rect(), self._radius, self._radius)
p.end()
super(RoundPixmapStyle, self).drawItemPixmap(painter, rectangle, alignment, pix)
painter.restore()
if __name__ == '__main__':
import sys
app = QtWidgets.QApplication(sys.argv)
w = QtWidgets.QLabel(alignment=QtCore.Qt.AlignCenter)
proxy_style = RoundPixmapStyle(radius=20, style=w.style())
w.setStyle(proxy_style)
movie = QtGui.QMovie("foo.gif")
w.setMovie(movie)
movie.start()
w.resize(640, 480)
w.show()
sys.exit(app.exec_())

How to update a QPixmap in a QGraphicsView with PyQt

I am trying to paint on a QPixmap inside a QGraphicsView. The painting works fine, but the QGraphicsView doesn't update it.
Here is some working code:
#!/usr/bin/env python
from PyQt4 import QtCore
from PyQt4 import QtGui
class Canvas(QtGui.QPixmap):
""" Canvas for drawing"""
def __init__(self, parent=None):
QtGui.QPixmap.__init__(self, 64, 64)
self.parent = parent
self.imH = 64
self.imW = 64
self.fill(QtGui.QColor(0, 255, 255))
self.color = QtGui.QColor(0, 0, 0)
def paintEvent(self, point=False):
if point:
p = QtGui.QPainter(self)
p.setPen(QtGui.QPen(self.color, 1, QtCore.Qt.SolidLine))
p.drawPoints(point)
def clic(self, mouseX, mouseY):
self.paintEvent(QtCore.QPoint(mouseX, mouseY))
class GraphWidget(QtGui.QGraphicsView):
""" Display, zoom, pan..."""
def __init__(self):
QtGui.QGraphicsView.__init__(self)
self.im = Canvas(self)
self.imH = self.im.height()
self.imW = self.im.width()
self.zoomN = 1
self.scene = QtGui.QGraphicsScene(self)
self.scene.setItemIndexMethod(QtGui.QGraphicsScene.NoIndex)
self.scene.setSceneRect(0, 0, self.imW, self.imH)
self.scene.addPixmap(self.im)
self.setScene(self.scene)
self.setTransformationAnchor(QtGui.QGraphicsView.AnchorUnderMouse)
self.setResizeAnchor(QtGui.QGraphicsView.AnchorViewCenter)
self.setMinimumSize(400, 400)
self.setWindowTitle("pix")
def mousePressEvent(self, event):
if event.buttons() == QtCore.Qt.LeftButton:
pos = self.mapToScene(event.pos())
self.im.clic(pos.x(), pos.y())
#~ self.scene.update(0,0,64,64)
#~ self.updateScene([QtCore.QRectF(0,0,64,64)])
self.scene.addPixmap(self.im)
print('items')
print(self.scene.items())
else:
return QtGui.QGraphicsView.mousePressEvent(self, event)
def wheelEvent(self, event):
if event.delta() > 0:
self.scaleView(2)
elif event.delta() < 0:
self.scaleView(0.5)
def scaleView(self, factor):
n = self.zoomN * factor
if n < 1 or n > 16:
return
self.zoomN = n
self.scale(factor, factor)
if __name__ == '__main__':
import sys
app = QtGui.QApplication(sys.argv)
widget = GraphWidget()
widget.show()
sys.exit(app.exec_())
The mousePressEvent does some painting on the QPixmap. But the only solution I have found to update the display is to make a new instance (which is not a good solution).
How do I just update it?
The pixmap can't be linked to your scene, the item uses an internal copy of it, so you have to update the QGraphicsPixmapItem with the new pixmap:
def __init__(self):
...
# save the item as a member
self.imItem = self.scene.addPixmap(self.im)
...
def mousePressEvent(self, event):
if event.buttons() == QtCore.Qt.LeftButton:
pos = self.mapToScene(event.pos())
self.im.clic(pos.x(), pos.y())
self.imItem.setPïxmap(self.im)
...
But it would make more sense to make your class Canvas inherit from QGraphicsPixmapItem instead of QPixmap, you would still have to get the pixmap with pixmap(), paint on it, and call setPixmap to update it. As a bonus, that code would be in the item own mousePressEvent method.

PyQT: Rotate a QLabel so that it's positioned diagonally instead of horizontally

I'm working on a touch screen app where gui space is very tight. I'd like to rotate a QLabel a bit so that it's either vertical, or offset at a diagonal a bit. Any suggestions? I couldn't find anything relevant on the QLabel interface.
Thanks so much!
QLabel does not do this. But you can easily create your own widget containing a bit of text:
class MyLabel(QtGui.QWidget):
def paintEvent(self, event):
painter = QtGui.QPainter(self)
painter.setPen(QtCore.Qt.black)
painter.translate(20, 100)
painter.rotate(-90)
painter.drawText(0, 0, "hellos")
painter.end()
Another option is to draw a QGraphicsView, you then have the liberty to map real widgets (i.e. a QLabel) through any coordinate transformation.
I used this post to make another solution that I think is maybe better. Here it is:
class VerticalLabel(QLabel):
def __init__(self, *args):
QLabel.__init__(self, *args)
def paintEvent(self, event):
QLabel.paintEvent(self, event)
painter = QPainter (self)
painter.translate(0, self.height()-1)
painter.rotate(-90)
self.setGeometry(self.x(), self.y(), self.height(), self.width())
QLabel.render(self, painter)
def minimumSizeHint(self):
size = QLabel.minimumSizeHint(self)
return QSize(size.height(), size.width())
def sizeHint(self):
size = QLabel.sizeHint(self)
return QSize(size.height(), size.width())
Try this
class myStyle(QCommonStyle):
def __init__(self, angl=0, point=QPoint(0, 0)):
super(myStyle, self).__init__()
self.angl = angl
self.point = point
def drawItemText(self, painter, rect, flags, pal, enabled, text, textRole):
if not text:
return
savedPen = painter.pen()
if textRole != QPalette.NoRole:
painter.setPen(QPen(pal.brush(textRole), savedPen.widthF()))
if not enabled:
pen = painter.pen()
painter.setPen(pen)
painter.translate(self.point)
painter.rotate(self.angl)
painter.drawText(rect, flags, text)
painter.setPen(savedPen)
and
label = QLabel()
label.setStyle(myStyle(-45, QPoint(0, 100)))
Answer from #Controlix is a generic implementation, however, I was getting a "recursive painting call" warning. I was able to address that by combining approaches from #Controlix and #Ivo. Here is my implementation:
from PyQt5.Qt import QLabel
from PyQt5 import QtGui
class VerticalLabel(QLabel):
def __init__(self, *args):
QLabel.__init__(self, *args)
def paintEvent(self, event):
painter = QtGui.QPainter(self)
painter.translate(0, self.height())
painter.rotate(-90)
painter.drawText(0, self.width()/2, self.text())
painter.end()
I took all of the above posts, and tested each one.
I believe this is the best combination of them all.
This centers the text horizontally and vertically, and sets size hints correctly.
from PyQt5 import QtGui
from PyQt5 import QtWidgets
from PyQt5 import QtCore
class VerticalLabel(QtWidgets.QLabel):
def __init__(self, *args):
QtWidgets.QLabel.__init__(self, *args)
def paintEvent(self, event):
painter = QtGui.QPainter(self)
painter.translate(0, self.height())
painter.rotate(-90)
# calculate the size of the font
fm = QtGui.QFontMetrics(painter.font())
xoffset = int(fm.boundingRect(self.text()).width()/2)
yoffset = int(fm.boundingRect(self.text()).height()/2)
x = int(self.width()/2) + yoffset
y = int(self.height()/2) - xoffset
# because we rotated the label, x affects the vertical placement, and y affects the horizontal
painter.drawText(y, x, self.text())
painter.end()
def minimumSizeHint(self):
size = QtWidgets.QLabel.minimumSizeHint(self)
return QtCore.QSize(size.height(), size.width())
def sizeHint(self):
size = QtWidgets.QLabel.sizeHint(self)
return QtCore.QSize(size.height(), size.width())
class Example(QtWidgets.QWidget):
def __init__(self):
super(Example, self).__init__()
self.initUI()
def initUI(self):
lbl1 = VerticalLabel('ABSOLUTE')
lbl1.setFont(QtGui.QFont('Arial', 20))
lbl1.setStyleSheet("QLabel { background-color : black; color : orange; }");
lbl2 = VerticalLabel('lbl 2')
lbl3 = VerticalLabel('lbl 3')
hBoxLayout = QtWidgets.QHBoxLayout()
hBoxLayout.addWidget(lbl1)
hBoxLayout.addWidget(lbl2)
hBoxLayout.addWidget(lbl3)
self.setLayout(hBoxLayout)
self.setGeometry(300, 300, 250, 150)
self.show()
def main():
app = QtWidgets.QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())
if __name__ == '__main__':
main()

Categories

Resources