Search code examples
pythonpyqtpyqt5librecad

Widget used for infinitely resizable painting in librecad


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:

before resizing:

After resizing:

after resizing:

Which widget provides this functionality?


Solution

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