How to implement a zooming function with header and footer support in PagesTextEdit forked by dimkanovikov on github?

89 Views Asked by At

I am now currently working on a MS Word clone with Python 3 and want some effect with pagination support. I saw some implementations in https://forum.qt.io/topic/847/paginating-a-qtextedit/2, and I found the repository of dimkanovikov (https://github.com/dimkanovikov/PagesTextEdit). After that, I found the implementation of the translation for this code (https://github.com/BrightSoftwareFoundation/PagesTextEdit), but it is buggy, so I made a few changes to it. Here is a Minimal Reproducible Example:

QPageMetrics.py
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *


class PageMetrics:

    def __init__(self):

        self.m_mmPageSize = QSizeF()
        self.m_mmPageMargins = QMarginsF()
        self.m_pxPageSize = QSizeF()
        self.m_pxPageMargins = QMarginsF()
        self.m_zoomRange = 0.0

    def mmToInches(self, mm):
        return mm * 0.039370147

    def mmToPx(self, _mm, _x=True):
        return self.mmToInches(_mm) * \
               (qApp.desktop().logicalDpiX() if _x else
                qApp.desktop().logicalDpiY())

    def pageMetrics(self, _pageFormat=QPageSize.PageSizeId, _mmPageMargins=QMarginsF()):
        self.update(_pageFormat, _mmPageMargins)

    def setPxPageSize(self, _width, _height):
        self.m_mmPageSize = QSizeF(_width, _height)
        self.m_pxPageSize = QSizeF(self.mmToPx(self.m_mmPageSize.width(), True),
                                   self.mmToPx(self.m_mmPageSize.height(), False))

    def update(self, _pageFormat, _mmPageMargins=QMarginsF()):
        self.m_pageFormat = _pageFormat

        self.m_mmPageSize = QPageSize(self.m_pageFormat).rect(QPageSize.Unit.Millimeter).size()
        self.m_mmPageMargins = _mmPageMargins

        x = True
        y = False

        self.m_pxPageSize = QSizeF(self.mmToPx(self.m_mmPageSize.width(), x),
                                   self.mmToPx(self.m_mmPageSize.height(), y))

        self.m_pxPageMargins = QMarginsF(self.mmToPx(self.m_mmPageMargins.left(), x),
                                         self.mmToPx(self.m_mmPageMargins.top(), y),
                                         self.mmToPx(self.m_mmPageMargins.right(), x),
                                         self.mmToPx(self.m_mmPageMargins.bottom(), y))

    def pageFormat(self):
        return self.m_pageFormat

    def mmPageSize(self):
        return self.m_mmPageSize

    def mmPageMargins(self):
        return self.m_mmPageMargins

    def pxPageSize(self):
        return QSizeF(self.m_pxPageSize.width(),
                      self.m_pxPageSize.height())

    def pxPageMargins(self):
        return QMarginsF(self.m_pxPageMargins.left(),
                         self.m_pxPageMargins.top(),
                         self.m_pxPageMargins.right(),
                         self.m_pxPageMargins.bottom())

    def zoomIn(self, _zoomRange=float()):
        self.m_zoomRange = _zoomRange
PagesTextEdit.py
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *

import QPageMetrics as pe

import sys


class PagesTextEdit(QTextEdit):
    def __init__(self):
        super().__init__()
        self.m_document = QTextDocument()
        self.m_usePageMode = True
        self.m_addBottomSpace = True
        self.m_showPageNumbers = True
        self.m_pageNumbersAlignment = Qt.AlignTop | Qt.AlignRight
        self.m_pageMetrics = pe.PageMetrics()
        self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn)
        self.aboutDocumentChanged()
        self.textChanged.connect(self.aboutDocumentChanged)
        self.verticalScrollBar().rangeChanged.connect(self.aboutVerticalScrollRangeChanged)

    def setPageFormat(self, _pageFormat):
        self.m_pageMetrics.update(_pageFormat)
        self.repaint()

    def setPageMargins(self, _margins):
        self.m_pageMetrics.update(self.m_pageMetrics.pageFormat(), _margins)
        self.repaint()

    def paintEvent(self, _event):
        self.updateVerticalScrollRange()
        self.paintPageView()
        self.paintPageNumbers()
        super().paintEvent(_event)

    def resizeEvent(self, _event):
        self.updateViewportMargins()
        super().resizeEvent(_event)

    def updateViewportMargins(self):
        self.viewportMargins = QMargins()
        if self.m_usePageMode:
            self.pageWidth = int(self.m_pageMetrics.pxPageSize().width())
            self.pageHeight = int(self.m_pageMetrics.pxPageSize().height())

            self.DEFAULT_TOP_MARGIN = int(20)
            self.DEFAULT_BOTTOM_MARGIN = int(20)

            self.leftMargin = int(0)
            self.rightMargin = int(0)

            if self.width() > self.pageWidth:
                self.BORDERS_WIDTH = int(4)
                self.VERTICAL_SCROLLBAR_WIDTH = int(self.verticalScrollBar().width()
                                                    if self.verticalScrollBar().isVisible()
                                                    else 0)
                self.leftMargin = self.rightMargin = int((self.width() - self.pageWidth -
                                                      self.VERTICAL_SCROLLBAR_WIDTH - self.BORDERS_WIDTH) / 2)
                self.topMargin = int(self.DEFAULT_TOP_MARGIN)
                self.bottomMargin = int(self.DEFAULT_BOTTOM_MARGIN)
                self.documentHeight = int(self.pageHeight * self.document().pageCount())

                if (self.height() - self.documentHeight) > (self.DEFAULT_TOP_MARGIN + self.DEFAULT_BOTTOM_MARGIN):
                    self.BORDERS_HEIGHT = int(2)
                    self.HORIZONTAL_SCROLLBAR_HEIGHT = int(self.horizontalScrollBar().height()
                                                           if self.horizontalScrollBar().isVisible()
                                                           else 0)
                    self.bottomMargin = int(self.height() - self.documentHeight -
                                            self.HORIZONTAL_SCROLLBAR_HEIGHT - self.DEFAULT_TOP_MARGIN -
                                            self.BORDERS_HEIGHT)

                self.viewportMargins = QMargins(self.leftMargin, self.topMargin, self.rightMargin, self.bottomMargin)

        self.setViewportMargins(self.viewportMargins)
        self.aboutUpdateDocumentGeometry()

    def updateVerticalScrollRange(self):
        if self.m_usePageMode:
            self.pageHeight = int(self.m_pageMetrics.pxPageSize().height())
            self.documentHeight = int(self.pageHeight * self.document().pageCount())
            self.maximumValue = int(self.documentHeight - self.viewport().height())

            if self.verticalScrollBar().maximum() != self.maximumValue:
                self.verticalScrollBar().setMaximum(self.maximumValue)
        else:
            self.SCROLL_DELTA = int(800)
            self.maximumValue = int(self.document().size().height() - self.viewport().size().height() +
                                    (self.SCROLL_DELTA if self.m_addBottomSpace else 0))

            if self.verticalScrollBar().maximum() != self.maximumValue:
                self.verticalScrollBar().setMaximum(self.maximumValue)

    def paintPageView(self):
        if self.m_usePageMode:
            self.pageWidth = float(self.m_pageMetrics.pxPageSize().width())
            self.pageHeight = float(self.m_pageMetrics.pxPageSize().height())

            self.p = QPainter(self.viewport())
            self.spacePen = QPen(self.palette().window(), 9)
            self.borderPen = QPen(self.palette().dark(), 1)

            self.curHeight = float(self.pageHeight - (self.verticalScrollBar().value() %
                                                      int(self.pageHeight)))

            self.canDrawNextPageLine = self.verticalScrollBar().value() != \
                self.verticalScrollBar().maximum()

            self.xValueF = self.pageWidth + (1 if (self.width() % 2 == 0) else 0)
            self.xValue = int(self.xValueF)
            self.horizontalDelta = int(self.horizontalScrollBar().value())

            if (self.curHeight - self.pageHeight) >= 0:
                self.p.setPen(self.borderPen)
                self.p.drawLine(QLineF(float(0), self.curHeight - self.pageHeight,
                                       self.xValueF, self.curHeight - self.pageHeight))

            while self.curHeight <= self.height():
                self.p.setPen(self.spacePen)
                self.p.drawLine(QLineF(float(0), float(self.curHeight - 4),
                                       float(self.width()), float(self.curHeight - 4)))
                self.p.setPen(self.borderPen)
                self.p.drawLine(QLineF(float(0), float(self.curHeight - 0),
                                       self.xValueF, self.curHeight))
                if self.canDrawNextPageLine:
                    self.p.drawLine(QLineF(float(0),
                                           float(self.curHeight - 8),
                                           float(self.pageWidth),
                                           float(self.curHeight - 8)))

                self.p.drawLine(QLineF(float(0 - self.horizontalDelta), float(self.curHeight - self.pageHeight),
                                       float(0 - self.horizontalDelta), float(self.curHeight - 8)))
                self.p.drawLine(QLineF(float(self.xValueF - self.horizontalDelta),
                                       float(self.curHeight - self.pageHeight),
                                       float(self.xValueF - self.horizontalDelta),
                                       float(self.curHeight - 8)))
                self.curHeight += self.pageHeight

            if self.curHeight >= self.height():
                self.p.setPen(self.borderPen)
                self.p.drawLine(QLineF(float(0 - self.horizontalDelta), float(self.curHeight - self.pageHeight),
                                       float(0 - self.horizontalDelta), float(self.height())))
                self.p.drawLine(QLineF(float(self.xValueF - self.horizontalDelta),
                                       float(self.curHeight - self.pageHeight),
                                       float(self.xValueF - self.horizontalDelta),
                                       float(self.height())))

    def paintPageNumbers(self):
        if (self.m_usePageMode and not self.m_pageMetrics.pxPageMargins().isNull() and
                self.m_showPageNumbers):
            self.pageSize = QSizeF(self.m_pageMetrics.pxPageSize())
            self.pageMargins = QMarginsF(self.m_pageMetrics.pxPageMargins())

            self.p = QPainter(self.viewport())
            self.p.setFont(self.document().defaultFont())
            self.p.setPen(QPen(self.palette().text(), 1))

            self.curHeight = float(self.pageSize.height() - (self.verticalScrollBar().value() %
                                                             int(self.pageSize.height())))
            self.leftMarginPosition = float(self.pageMargins.left() - self.pageMargins.right())
            self.marginWidth = float(self.pageSize.width() - self.pageMargins.left() - self.pageMargins.right())
            self.pageNumber = int(self.verticalScrollBar().value() / self.pageSize.height() + 1)

            if self.curHeight - self.pageMargins.top() >= 0:
                self.topMarginRect = QRectF(float(self.leftMarginPosition),
                                            float(self.curHeight - self.pageSize.height()),
                                            float(self.marginWidth),
                                            float(self.pageMargins.top()))
                self.paintPageNumber(self.p, self.topMarginRect, True, self.pageNumber)

            while self.curHeight < self.height():
                self.bottomMarginRect = QRect(int(self.leftMarginPosition),
                                              int(self.curHeight - self.pageMargins.bottom()),
                                              int(self.marginWidth),
                                              int(self.pageMargins.bottom()))
                self.paintPageNumber(self.p, self.bottomMarginRect, False, self.pageNumber)
                self.pageNumber += 1
                self.topMarginRect = QRect(int(self.leftMarginPosition), int(self.curHeight), int(self.marginWidth),
                                           int(self.pageMargins.top()))
                self.paintPageNumber(self.p, self.topMarginRect, True, self.pageNumber)

                self.curHeight += self.pageSize.height()
            self.p.end()

    def paintPageNumber(self, _painter, _rect, _isHeader, _number):
        if _isHeader:
            if self.m_pageNumbersAlignment == Qt.AlignTop:
                _painter.drawText(_rect, Qt.AlignVCenter | (self.m_pageNumbersAlignment ^ Qt.AlignTop),
                                  str(_number))
        else:
            if self.m_pageNumbersAlignment == Qt.AlignBottom:
                _painter.drawText(_rect, Qt.AlignVCenter | (self.m_pageNumbersAlignment ^ Qt.AlignBottom),
                                  str(_number))

    def aboutVerticalScrollRangeChanged(self, _minimum, _maximum):
        self.updateViewportMargins()
        self.scrollValue = int(self.verticalScrollBar().value())

        if self.scrollValue > _maximum:
            self.updateVerticalScrollRange()

    def aboutDocumentChanged(self):
        if self.m_document != self.document():
            self.m_document = self.document()
            self.document().documentLayout().update.connect(self.aboutUpdateDocumentGeometry)

    def aboutUpdateDocumentGeometry(self):
        self.documentSize = QSizeF(float(self.width() - self.verticalScrollBar().width()), float(-1))

        if self.m_usePageMode:
            self.pageWidth = (self.m_pageMetrics.pxPageSize().width())
            self.pageHeight = (self.m_pageMetrics.pxPageSize().height())
            self.documentSize = QSizeF(self.pageWidth, self.pageHeight)

            if self.width() > self.pageWidth:
                self.viewportMargins = QMargins(
                    int(int(self.width() - self.pageWidth - self.verticalScrollBar().width() - 2) / 2),
                    20,
                    int(int(self.width() - self.pageWidth - self.verticalScrollBar().width() - 2) / 2),
                    20
                )
            else:
                self.viewportMargins = QMargins(0, 20, 0, 20)

        if self.document().pageSize() != self.documentSize:
            self.document().setPageSize(self.documentSize)

        if self.document().documentMargin() != 0:
            self.document().setDocumentMargin(0)

        rootFrameMargin = self.m_pageMetrics.pxPageMargins()
        rootFrameFormat = self.document().rootFrame().frameFormat()

        self.setViewportMargins(self.viewportMargins)

        if ((rootFrameFormat.leftMargin() != rootFrameMargin.left())
            | (rootFrameFormat.topMargin() != rootFrameMargin.top())
            | (rootFrameFormat.rightMargin() != rootFrameMargin.right())
            | (rootFrameFormat.bottomMargin() != rootFrameMargin.bottom())):
            rootFrameFormat.setLeftMargin(rootFrameMargin.left())
            rootFrameFormat.setTopMargin(rootFrameMargin.top())
            rootFrameFormat.setRightMargin(rootFrameMargin.right())
            rootFrameFormat.setBottomMargin(rootFrameMargin.bottom())
            self.document().rootFrame().setFrameFormat(rootFrameFormat)

Here are some few questions I want to ask:

  1. How to implement a zooming function in the paged text edit like MS Word that zooms the whole page but not only text? I have tried different methods: using QGraphicsProxyWidget, delete the resizeEvent, try to implement a QScrollArea with setFrameRect, ... It crashes often, Process finished with exit code -1073740791 (0xC0000409) / RESTART: SHELL or RecursionError often appears.

  2. How to change the page size? It does not crash, only for self.setPageSize(QPageSize.PageSizeId.A4) or .A5). For another size or custom one, it crashes with Process finished with exit code -1073740791 (0xC0000409) / RESTART: SHELL.

  3. My page numbering / header and footer cannot be shown for an unknown reason. Can somebody help me to solve this?

Any comments / answers are appreciated!

0

There are 0 best solutions below