Pyside2 - Linenumbers em codeeditor incorreto quando alterado família de tipos de letra/tamanho

0

Pergunta

Eu olhei para este editor de código de exemplo a partir de oficial Qt5 site https://doc.qt.io/qt-5/qtwidgets-widgets-codeeditor-example.html. Ele é escrito em C++, mas eu implementado em Python usando Pyside2.

O código de exemplo funciona bem como é, no entanto, quando tento alterar a família de tipos de letra e o tamanho do QPlainTextEdit as coisas começam a ficar confuso. Eu tentei fuçar um monte de diferentes campos, como a fontMetrics para determinar a altura etc.

Aqui está um exemplo mínimo para reproduzir o problema

import sys
import signal
from PySide2.QtCore import Qt, QSize, QRect
from PySide2.QtGui import QPaintEvent, QPainter, QColor, QResizeEvent
from PySide2.QtWidgets import QWidget, QPlainTextEdit, QVBoxLayout
from PySide2 import QtCore
from PySide2.QtWidgets import QApplication


FONT_SIZE = 20
FONT_FAMILY = 'Source Code Pro'


class PlainTextEdit(QPlainTextEdit):
    def __init__(self, parent=None):
        super().__init__(parent)

        self.init_settings_font()

    def init_settings_font(self):
        font = self.document().defaultFont()

        font.setFamily(FONT_FAMILY)
        font.setFixedPitch(True)
        font.setPixelSize(FONT_SIZE)
        self.document().setDefaultFont(font)


class LineNumberArea(QWidget):
    TMP = dict()

    def __init__(self, editor):
        super().__init__(editor)
        self._editor = editor

        self._editor.blockCountChanged.connect(lambda new_count: self._update_margin())
        self._editor.updateRequest.connect(lambda rect, dy: self._update_request(rect, dy))

        self._update_margin()

    def width(self) -> int:
        # we use 1000 as a default size, so from 0-9999 this length will be applied
        _max = max(1000, self._editor.blockCount())
        digits = len(f'{_max}')
        space = self._editor.fontMetrics().horizontalAdvance('0', -1) * (digits + 1) + 6
        return QSize(space, 0).width()

    def _update_line_geometry(self):
        content_rect = self._editor.contentsRect()
        self._update_geometry(content_rect)

    def _update_geometry(self, content_rect: QRect):
        self.setGeometry(
            QRect(content_rect.left(), content_rect.top(), self.width(), content_rect.height())
        )

    def _update_margin(self):
        self._editor.setViewportMargins(self.width(), 0, 0, 0)

    def _update_request(self, rect: QRect, dy: int):
        self._update(0, rect.y(), self.width(), rect.height(), self._editor.contentsRect())

        if rect.contains(self._editor.viewport().rect()):
            self._update_margin()

    def _update(self, x: int, y: int, w: int, h: int, content_rect: QRect):
        self.update(x, y, w, h)
        self._update_geometry(content_rect)

    # override
    def resizeEvent(self, event: QResizeEvent) -> None:
        self._update_line_geometry()

    # override
    def paintEvent(self, event: QPaintEvent):
        painter = QPainter(self)
        area_color = QColor('darkgrey')

        # Clearing rect to update
        painter.fillRect(event.rect(), area_color)

        visible_block_num = self._editor.firstVisibleBlock().blockNumber()
        block = self._editor.document().findBlockByNumber(visible_block_num)
        top = self._editor.blockBoundingGeometry(block).translated(self._editor.contentOffset()).top()
        bottom = top + self._editor.blockBoundingRect(block).height()
        active_line_number = self._editor.textCursor().block().blockNumber() + 1

        # font_size = storage.get_setting(Constants.Editor_font_size).value
        font = self._editor.font()

        while block.isValid() and top <= event.rect().bottom():
            if block.isVisible() and bottom >= event.rect().top():
                number_to_draw = visible_block_num + 1

                if number_to_draw == active_line_number:
                    painter.setPen(QColor('black'))
                else:
                    painter.setPen(QColor('white'))

                font.setPixelSize(self._editor.document().defaultFont().pixelSize())
                painter.setFont(font)

                painter.drawText(
                    -5,
                    top,
                    self.width(),
                    self._editor.fontMetrics().height(),
                    int(Qt.AlignRight | Qt.AlignHCenter),
                    str(number_to_draw)
                )

            block = block.next()
            top = bottom
            bottom = top + self._editor.blockBoundingGeometry(block).height()
            visible_block_num += 1

        painter.end()

if __name__ == "__main__":
    app = QApplication(sys.argv)

    signal.signal(signal.SIGINT, signal.SIG_DFL)

    window = QWidget()
    layout = QVBoxLayout()
    editor = PlainTextEdit()
    line_num = LineNumberArea(editor)

    layout.addWidget(editor)
    window.setLayout(layout)

    window.show()

    sys.exit(app.exec_())

Um dos maiores problemas são que parece haver uma margem superior deslocamento em texto simples saída que eu sou incapaz de obter dinamicamente no linenumber widget. E quando a definição do editor de tipos de letra para o pintor, os números não será desenhado o mesmo tamanho!?

Alguém sabe como ajustar os números de linha para o mesmo nível horizontal como o texto correspondente e também levá-los a ter o mesmo tamanho de uma forma dinâmica, o que significa que, se o tipo de letra vai ser definido para algo que eles devem ser ajustados automaticamente.

pyside2 python-3.x
2021-11-20 05:34:22
1

Melhor resposta

1

O problema vem do fato de que você está usando duas fontes para diferentes propósitos: o widget do tipo de letra e o documento fonte.

Cada fonte tem diferentes aspectos, e o seu alinhamento pode ser diferente se você considerar as fontes como base para a elaboração de coordenadas.

Desde que você está desenhando com o documento fonte, mas usando o widget de fonte como referência, o resultado é que você vai ter desenho questões:

  • mesmo com o mesmo tamanho de ponto, diferentes tipos de letra são desenhadas em alturas diferentes, especialmente se o alinhamento do texto retângulo não é correto (observe também que você usou uma inconsistentes alinhamento, como Qt.AlignRight | Qt.AlignHCenter sempre considerar o alinhamento à direita e padrões para alinhamento superior (top)
  • você está usando o widget de métricas de fonte para definir o texto retângulo de altura, o que difere do documento de métricas, assim você poderá limitar a altura do desenho.

Ao contrário de outros widgets, editores de rich text em Qt tem duas definições de tipo de letra:

  • o widget do tipo de letra;
  • (padrão) documento fonte, que pode ser substituído por um QTextOption no documento;

O documento será sempre herdar o widget do tipo de letra (ou fonte do aplicativo) para o padrão, e isso vai acontecer também quando a definição do tipo de letra para o widget em runtime, e até mesmo para a aplicação (a menos que uma fonte tenha sido explicitamente definidas para o widget).

A definição do tipo de letra para o editor é geralmente bem para situações simples, mas você tem que lembrar que as fontes propagar, para que as crianças widget irá herdar o tipo de letra também.

Por outro lado, definir a fonte padrão para o documento não irá propagar para as crianças, mas, como explicado acima, pode ser substituída pela aplicação de fonte se que é alterado em tempo de execução.

A solução mais simples, no seu caso, seria a de definir o tipo de letra para o editor de widget em vez do documento. Desta forma, você está certo de que o LineNumberArea (que é o editor da criança) também vai herdar o mesmo tipo de letra. Com esta abordagem, você nem mesmo precisa definir o tipo de letra do pintor, como ele vai sempre usar o widget do tipo de letra.

No caso de você querer utilizar um tipo de letra diferente e ainda manter o alinhamento correto, você tem que considerar a situação actual do tipo de letra utilizado para o documento, e usar essa referência para a linha de base do widget do tipo de letra. Para fazer isso, você tem que traduzir a posição de bloqueio, com a diferença de ascent() das duas métricas de fonte.

2021-11-20 13:08:21

Em outros idiomas

Esta página está em outros idiomas

Русский
..................................................................................................................
Italiano
..................................................................................................................
Polski
..................................................................................................................
Română
..................................................................................................................
한국어
..................................................................................................................
हिन्दी
..................................................................................................................
Français
..................................................................................................................
Türk
..................................................................................................................
Česk
..................................................................................................................
ไทย
..................................................................................................................
中文
..................................................................................................................
Español
..................................................................................................................
Slovenský
..................................................................................................................