align right-to-left text with QPainter::drawText - c++

I am trying to paint a right-to-left text with QPainter. It however still aligns the text to left despite the fact that it should be right-aligned. Or at least it is right-aligned when displayed in QTextEdit. That am I doing wrong? See the example:
QTextOption option;
option.setTextDirection(Qt::LayoutDirectionAuto);
painter->drawText(rect, "خامل\nخاملخامل", option); // this is just testing text, I have no idea what it means, hopefully it is not something offensive :)
This is what I get. The red arrow shows where it should be aligned.
Of course I cannot use fixed right alignment because I do not whether the text in question is left-to-right or right-to-left text. So it must work for both directions automatically depending on the text.
UPDATE: I want to emphasize that I need a solution which automatically recognizes the language/writing system of the text (left-to-right, such as English, versus right-to-left, such as Arabic) and aligns the text automatically.

you should check your text with isRightToLeft() function.
QString isRightToLeft():
Returns true if the string is read right to left.
This will help you to understand your text language.
I checked the QTextEdit source and understand it uses this function.
void MainWindow::paintEvent(QPaintEvent *event)
{
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing);
QRect rect = QRect(0, 0, width(), height());
QString txt = tr("خامل\nخاملخامل");
// QString txt = tr("ABCD\nABCD");
QTextOption option;
if (txt.isRightToLeft())
{
option.setTextDirection(Qt::RightToLeft);
}
else
{
option.setTextDirection(Qt::LeftToRight);
}
painter.drawText(rect, txt, option);
}

Related

QTextDocument and selection painting

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?
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);
}

Qt: Draw text with different fonts

I'm using void QPainter::drawText(const QRectF &rectangle, const QString &text, const QTextOption &option = QTextOption() method to draw some text.
It lets me to align text as i wish (in the center of rectangle, for example).
Now, i need to do the same thing, except i need to draw one part of text with
some font and the other part with another.
For example, if text is "Hello world", i want "Hello" to be drawn with Arial and "World" with Times New Roman, but it should still be aligned with the center of rectangle.
What is the best way to achieve that?
you can try with this :
QPainter painter(this);
painter.setFont(QFont("Arial", 12));
painter.drawText(rect(), Qt::AlignCenter, "Hello");

Qt Code editor display the line number in an area to the RIGHT

I am just learning Qt. I want to show line number of QPlainTextEdit. I found this link
and it worked. But now I want the editor displays the line numbers in an area to the RIGHTof the area for editing. I have been searching google very much, but I can't solve. How to solve?
In addition to GPPK's answer, you also need to change the viewport margins:
void CodeEditor::updateLineNumberAreaWidth(int /* newBlockCount */)
{
setViewportMargins(0, 0, lineNumberAreaWidth(), 0);
}
GPPK's code assigns the correct drawing rectangle to the sub-widget, my code makes sure, that the scrollview does not paint into that area.
In your link it shows you how it draws the line number area on the left:
void CodeEditor::resizeEvent(QResizeEvent *e)
{
QPlainTextEdit::resizeEvent(e);
QRect cr = contentsRect();
lineNumberArea->setGeometry(QRect(cr.left(), cr.top(), lineNumberAreaWidth(), cr.height()));
}
In order to draw the line numbers from the right you will (this is untested) do something like this:
void CodeEditor::resizeEvent(QResizeEvent *e)
{
QPlainTextEdit::resizeEvent(e);
QRect cr = contentsRect();
lineNumberArea->setGeometry(QRect(cr.right() - lineNumberArea->width() , cr.top(), lineNumberAreaWidth(), cr.height()));
}

Qt - Pop up a bubble when mouse is over QRect object

My QRect object is a fixed-sized containter of plain text, when there is too much text I would truncate the text and trail ... at the end. For example, Longlonglonglong is truncated to Longlong.... But I want to display the full-length text in a bubble when mouse pointer is over the rect.
The bubble is like the Go to Google Home:
Is this possible ?
Unfortunately QPainter can't do that for you, the drawText(..) flags don't support it. Thankfully, you can pre-elide the text for it ("eliding" is where you truncate with an elipsis) using QFontMetrics:
QFontMetrics fontM( QApplication::font() );
QRect r( 0, 0, 30, 10 );
QString text = "Longlonglonglong";
QString elidedText = fontM.elidedText( text, Qt::ElideRight, r.width() );
painter->drawText( r, Qt::AlignLeft, elidedText );
When you say "text in a bubble when mouse pointer is over", I presume you mean a tooltip - in which case implement it for the widget as normal and give the full text rather than the elided.

Qt: heightForWidth for word-wrapped text

I have a box with a varying width and a word-wrapped text. I need to set new height every time user changes box's width. The box is displayed by QPainter inside paintEvent(QPaintEvent *) function. There is several solutions, for example current (not very smart, i do this in resizeEvent(QResizeEvent *)):
unsigned new_height = 0; // the height i want to find out.
unsigned given_width = width();
QPainter painter (this); // i need painter, because i want to ask it's default font.
QLabel lab; // the widget that can do word-wrap.
lab.setText( "A word wrapped text" ); // the text
lab.setFont( painter.font() ); // set QPainter's default font.
lab.setWordWrap( true ); // enable word-wrap
new_height = lab.heightForWidth( given_width ); // tada! :)
But the code is overkill:
1) Creading QPainter is not good outside paintEvent(QPaintEvent *);
2) BUT i need QPainter to request what font is default for it to ask metrics for that font.
Should i change my code and do this operation with help of QPainter::boundingRect() inside the paintEvent(QPaintEvent *) function? But i'd like to reduce CPU consumption inside the paintEvent(QPaintEvent *) and calculate new height only when width changed, but not every time it displayed.
What is other solutions for the purpose of subject? QFontMectircs?
I think you have the right idea of using QFontMetrics. The whole idea of the class is to assist the situations like you have here. Take a look at QFontMetricsF::boundingRect()
Use your target paint rectangle as the input rect, but set the height to the max that your widget height. I'd just put something like INT_MAX in it just to be sure. :)