Search code examples
pythonpyqtpyqt5qgraphicsviewqgraphicssimpletextitem

Making a rectangle box behind text in python


I am working on a text editor. The only problem left for me is to draw a rectangle box (colored with opacity 50%) behind a text that should be able to drag and drop with text anywhere as the text is working. Moreover, the text should always fit in that rectangle box. Thanks in advance.

import os
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtGui import *
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *

from PIL import Image, ImageFont, ImageDraw, ImageEnhance


class PhotoViewer(QtWidgets.QGraphicsView):
    photoClicked = QtCore.pyqtSignal(QtCore.QPoint)

    def __init__(self, parent):
        super(PhotoViewer, self).__init__(parent)
        self._zoom = 0
        self._empty = True
        self._scene = QtWidgets.QGraphicsScene(self)
        self._photo = QtWidgets.QGraphicsPixmapItem()

        self._textLayer = QtWidgets.QGraphicsSimpleTextItem ()


        #self._textLayer.setFont(QFont ())

        self._scene.addItem(self._photo)
        self._scene.addItem(self._textLayer)

        self.setScene(self._scene)
        self.setTransformationAnchor(QtWidgets.QGraphicsView.AnchorUnderMouse)
        self.setResizeAnchor(QtWidgets.QGraphicsView.AnchorUnderMouse)
        self.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
        self.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
        self.setBackgroundBrush(QtGui.QBrush(QtGui.QColor(80, 30, 30)))
        self.setFrameShape(QtWidgets.QFrame.NoFrame)
        self._textLayer.setFlags(QGraphicsItem.ItemIsMovable)

    def hasPhoto(self):
        return not self._empty

    def fitInView(self, scale=True):
        rect = QtCore.QRectF(self._photo.pixmap().rect())
        if not rect.isNull():
            self.setSceneRect(rect)
            if self.hasPhoto():
                unity = self.transform().mapRect(QtCore.QRectF(0, 0, 1, 1))
                self.scale(1 / unity.width(), 1 / unity.height())
                viewrect = self.viewport().rect()
                scenerect = self.transform().mapRect(rect)
                factor = min(viewrect.width() / scenerect.width(),
                             viewrect.height() / scenerect.height())
                self.scale(factor, factor)
            self._zoom = 0


    def updateText(self,text,font_size=50):
        # Load the font:
        font_db = QFontDatabase()
        font_id = font_db.addApplicationFont("fonts/Summer's Victory Over Spring - TTF.ttf")
        #families = font_db.applicationFontFamilies(font_id)
        #print (families)
        myFont = QFont("Summers Victory Over Spring")
        myFont.setPixelSize(font_size*1.5)
        self._textLayer.setFont(myFont)
        self._textLayer.setText(text)


        self.begin = QtCore.QPoint()
        self.end = QtCore.QPoint()
        self.show()


    def setPhoto(self, pixmap=None):
        self._zoom = 0

        if not pixmap.isNull():
            self._empty = False
            self.setDragMode(QtWidgets.QGraphicsView.ScrollHandDrag)

            self._photo.setPixmap(pixmap)
        else:
            self._empty = True
            self.setDragMode(QtWidgets.QGraphicsView.NoDrag)
            self._photo.setPixmap(QPixmap())
        self.fitInView()

    def wheelEvent(self, event):
        if self.hasPhoto():
            if event.angleDelta().y() > 0:
                factor = 1.25
                self._zoom += 1
            else:
                factor = 0.8
                self._zoom -= 1
            if self._zoom > 0:
                self.scale(factor, factor)
            elif self._zoom == 0:
                self.fitInView()
            else:
                self._zoom = 0

    def toggleDragMode(self):
        if self.dragMode() == QtWidgets.QGraphicsView.ScrollHandDrag:
            self.setDragMode(QtWidgets.QGraphicsView.NoDrag)
        elif not self._photo.pixmap().isNull():
            self.setDragMode(QtWidgets.QGraphicsView.ScrollHandDrag)

    def mousePressEvent(self, event):
        if self._photo.isUnderMouse():
            self.photoClicked.emit(QtCore.QPoint(event.pos()))
        super(PhotoViewer, self).mousePressEvent(event)
    def keyReleaseEvent(self, event):
        print ('Key event: %d' % event.key())
        if event.key() == Qt.Key_A:
            #print (dir(self._scene))
            #print (dir(self._photo))
            #print (dir(self._textLayer))
            print ("scene Rect:",self._scene.sceneRect())
            print ("scene WH:",self._scene.width(),self._scene.height())
            print ("gfxPhoto pos:" ,self._photo.pos().x(),self._photo.pos().y())
            print ("gfxPhoto scenepos:" ,self._photo.scenePos())
            #print ("gfxPhoto Rect:",self._photo.boundingRect())
            print ("gfxPhoto XY:",self._photo.x(),self._photo.y())
            print ("gfxtext Rect:",self._textLayer.boundingRect())
            print ("gfxtext scenepos:" ,self._textLayer.scenePos().x(),self._textLayer.scenePos().y())
            print ("gfxtext pos:",self._textLayer.pos().x(),self._textLayer.pos().y())



class Window(QtWidgets.QWidget):
    def __init__(self):
        super(Window, self).__init__()
        self.viewer = PhotoViewer(self)
        # 'Load image' button
        self.btnLoad = QtWidgets.QToolButton(self)
        self.btnLoad.setText('Load image')
        self.btnLoad.clicked.connect(self.loadImage)
        # Button to change from drag/pan to getting pixel info
        self.btnPixInfo = QtWidgets.QToolButton(self)
        self.btnPixInfo.setText('Create Text')
        self.btnPixInfo.clicked.connect(self.loadText)

        self.fontSize =QtWidgets.QSpinBox()
        self.fontSize.valueChanged.connect(self.loadText)

        self.editPixInfo = QtWidgets.QLineEdit(self)
        #self.editPixInfo.setReadOnly(True)
        self.viewer.photoClicked.connect(self.photoClicked)
        # Arrange layout
        VBlayout = QtWidgets.QVBoxLayout(self)
        HBlayout = QtWidgets.QHBoxLayout()
        HBlayout.setAlignment(QtCore.Qt.AlignLeft)
        HBlayout.addWidget(self.btnLoad)
        HBlayout.addWidget(self.btnPixInfo)
        HBlayout.addWidget(self.editPixInfo)
        VBlayout.addLayout(HBlayout)
        VBlayout.addWidget(self.viewer)
        HBlayout.addWidget(self.fontSize)
        self.editPixInfo.setText("Sheeda")
        self.fontSize.setValue(20)
        self.loadImage()
        self.loadText()
        self.frame = QFrame()
        self.frame.setFrameStyle(QFrame.StyledPanel)
        self.frame.setLineWidth(20)

    def loadImage(self):
        print(os.path.exists(os.path.abspath('E:\Dpinner\images\pic2.png')))
        self.viewer.setPhoto(QtGui.QPixmap(os.path.abspath('E:\Dpinner\images\pic2.png')))

    def loadText(self):
        #self.viewer.toggleDragMode()
        self.viewer.updateText(self.editPixInfo.text(),self.fontSize.value())
    def photoClicked(self, pos):
        if self.viewer.dragMode()  == QtWidgets.QGraphicsView.NoDrag:
            self.editPixInfo.setText('%d, %d' % (pos.x(), pos.y()))


if __name__ == '__main__':
    import sys
    app = QtWidgets.QApplication(sys.argv)
    window = Window()
    window.setGeometry(500, 300, 800, 600)
    window.show()
    sys.exit(app.exec_())

End of code. Thanks in advance


Solution

  • If you want a rectangle over the text you can create a custom QGraphicsSimpleTextItem as shown below:

    class GraphicsSimpleTextItem(QtWidgets.QGraphicsSimpleTextItem):
        def paint(self, painter, option, widget):
            super(GraphicsSimpleTextItem, self).paint(painter, option, widget)
            painter.save()
            color = QtGui.QColor(QtCore.Qt.red)
            color.setAlpha(127)
            painter.fillRect(self.boundingRect(), QtGui.QBrush(color))
            painter.restore()
    

    And then you put it in your code:

    self._textLayer = GraphicsSimpleTextItem()