Home > Mobile >  QTextDocument and selection painting
QTextDocument and selection painting

Time:05-02

I am painting a block of text on a widget with QTextDocument::drawContents. My current goal is to intercept mouse events and emulate text selection. It's pretty clear how to handle the mouse, but displaying the result puzzles me a lot.

Just before we start: I can not use QLabel and let it handle selection on it's own (it has no idea how to draw unusual characters and messes up line height (https://git.macaw.me/blue/squawk/issues/59)), nor I can not use QTextBrowser there - it's just a message bubble, I'm not ready to sacrifice performance there.

There is a very rich framework around QTextDocument, but I can not find any way to make it color the background of some fragment of text that I would consider selected. Found a way to make a frame around a text, found a way to draw under-over-lined text, but it looks like there is simply just no way this framework can draw a background behind text.

I have tried doing this, to see if I can take selected fragment under some QTextFrame and set it's style:

QTextDocument* bodyRenderer = new QTextDocument();
bodyRenderer->setHtml("some text");
bodyRenderer->setTextWidth(50);
painter->setBackgroundMode(Qt::BGMode::OpaqueMode); //this at least makes it color background under all text
QTextFrameFormat format = bodyRenderer->rootFrame()->frameFormat();
format.setBackground(option.palette.brush(QPalette::Active, QPalette::Highlight));
bodyRenderer->rootFrame()->setFrameFormat(format);
bodyRenderer->drawContents(painter);

Nothing of this works too:

QTextBlock b = bodyRenderer->begin();
QTextBlockFormat format = b.blockFormat();
format.setBackground(option.palette.brush(QPalette::Active, QPalette::Highlight));
format.setProperty(QTextFormat::BackgroundBrush, option.palette.brush(QPalette::Active, QPalette::Highlight));
QTextCursor cursor(bodyRenderer);
cursor.setBlockFormat(format);
b = bodyRenderer->begin();
while (b.isValid() > 0) {
    QTextLayout* lay = b.layout();
    QTextLayout::FormatRange range;
    range.format = b.charFormat();
    range.start = 0;
    range.length = 2;
    lay->draw(painter, option.rect.topLeft(), {range});
    b = b.next();
}

Is there any way I can make this framework do a simple thing - draw a selection background behind some text? If not - is there a way I can unproject cursor position into coordinate translation, like can I do reverse operation from QAbstractTextDocumentLayout::hitTest just to understand where to draw that selection rectangle myself?

CodePudding user response:

You can use QTextCursor to change the background of the selected text. You only need to select one character at a time to keep the formatting. Here is an example of highlighting in blue (the color of the text is highlighted in white for contrast):

void MainWindow::paintEvent(QPaintEvent *event) {
  QPainter painter(this);
  painter.fillRect(contentsRect(), QBrush(QColor("white")));
  QTextDocument document;
  document.setHtml(QString("Hello <font size='20'>world</font> with Qt!"));

  int selectionStart = 3;
  int selectionEnd = selectionStart   10;
  QTextCursor cursor(&document);
  cursor.setPosition(selectionStart);
  while (cursor.position() < selectionEnd) {
    cursor.movePosition(QTextCursor::Right, QTextCursor::KeepAnchor); // select one symbol
    QTextCharFormat selectFormat = cursor.charFormat();
    selectFormat.setBackground(Qt::blue);
    selectFormat.setForeground(Qt::white);
    cursor.setCharFormat(selectFormat);                                // set format for selection
    cursor.movePosition(QTextCursor::Right, QTextCursor::MoveAnchor, 1);
  }

  document.drawContents(&painter, contentsRect());
  QMainWindow::paintEvent(event);
}
  • Related