QTextEdit change font of individual paragraph/block - c++

Using a QTextEdit, I need to change the font attributes of each paragraph individually. This is similar to how many word processors change the font of a paragraph when the user select a style from a menu (not a specific formatting).
Ideally, I would like to apply a QTextCharFormat (or equivalent) to a block (paragraph) just before it is laid out and rendered, but I would prefer that no font attribute be actually inserted in the text, as I don't want this information in the file but I need to preserve any bold/italic/underline attributes that the user might have set to words within paragraphs (I intend to save the needed information in a QTextBlock::userData). However, I can't figure where I would need to insert a function to perform this task.
I figured I could not change the QTextCharFormat of a paragraph from either QTextBlock nor QTextCursor as this only applies to new blocks, it doesn't affect blocks with existing text.
I checked out QTextLayout but I don't think my answer is there.
I have been looking for a solution to this problem for a few days now. I would be really gracious for any pointer in the right direction.
I have years of experience with C++, but I'm somewhat new to Qt. Using Qt 4.8.
Edit:
I added emphasize (bold) above to an important part of what I'm trying to do. In other word, what I'd really like to do is be able to apply the font attributes to the block of text (perhaps a temporary copy) just before it is displayed. I'm totally comfortable with deriving and modifying (even reimplement) any class that I need to in order to achieve that goal, but I need to be pointed to the right direction as to what I actually need to change. As a last resort, I could also modify some Qt class directly if that is necessary for the task, but again would need to know what class I need to touch. I hope this is clearer. I find it difficult to explain this without being allowed to tell you what the application will do exactly.

[Required Libraries]
#include <QTextEdit> // not needed if using the designer
#include <QTextDocument>
#include <QTextBlock>
#include <QTextCursor>
[Strategy]
QTextDocument
I need it to manage the blocks. The function QTextDocument::findBlockByNumber is quite handy to locate the previous blocks, and I think it is what you are after.
QTextBlock
Container for block texts. A nice and handy class.
QTextCursor
Surprisingly, there is no format-setter in QTextBlock class. Therefore I use QTextCursor as a workaround since there are four format-setters in this class.
[Code for formatting]
// For block management
QTextDocument *doc = new QTextDocument(this);
ui->textEdit->setDocument(doc); // from QTextEdit created by the Designer
//-------------------------------------------------
// Locate the 1st block
QTextBlock block = doc->findBlockByNumber(0);
// Initiate a copy of cursor on the block
// Notice: it won't change any cursor behavior of the text editor, since it
// just another copy of cursor, and it's "invisible" from the editor.
QTextCursor cursor(block);
// Set background color
QTextBlockFormat blockFormat = cursor.blockFormat();
blockFormat.setBackground(QColor(Qt::yellow));
cursor.setBlockFormat(blockFormat);
// Set font
for (QTextBlock::iterator it = cursor.block().begin(); !(it.atEnd()); ++it)
{
QTextCharFormat charFormat = it.fragment().charFormat();
charFormat.setFont(QFont("Times", 15, QFont::Bold));
QTextCursor tempCursor = cursor;
tempCursor.setPosition(it.fragment().position());
tempCursor.setPosition(it.fragment().position() + it.fragment().length(), QTextCursor::KeepAnchor);
tempCursor.setCharFormat(charFormat);
}
Reference:
How to change current line format in QTextEdit without selection?
[DEMO]
Building Environment: Qt 4.8 + MSVC2010 compiler + Windows 7 32 bit
The demo is just for showing the concept of setting the format on a specific block.
Plain text input
Format 1 (notice that it won't bother the current cursor in view)
Format 2

You can use QTextCursor to modify existing blocks.
Just get a cursor and move it to the beginning of the block. Then move it with anchor to create a selection.
Set this cursor to be the current cursor for the text edit and apply your changes.

QTextEdit accepts HTML so all you have to do is to format your paragraphs as HTML. See example below:
QString text = "<p><b>Paragraph 1</b></p><p><i>Paragraph 2</i></p>";
QTextCursor cursor = ui->textEdit->textCursor();
cursor.insertHtml(text);
That will create something like this:
Paragraph 1
Paragraph 2
Having said that, there is only a subset of HTML that is supported in Qt. See Supported HTML Subset

Related

How to append to QTextEdit without using the current paragraph style

When using myQTextEdit.append() the style of the inserted text is as follows (Qt 5.14 documentation):
"The new paragraph appended will have the same character format and block format as the current paragraph, determined by the position of the cursor."
However I would find it convenient to be able to append text with a neutral style.
What causes my problem is this:
I have a log window in the form of a QTextEdit where I append text (mostly neutral but some element may be coloured, etc.). Since it's for log purposes, the QTextEdit is read-only and the text elements are always added at the end (append()). This is fine as long as the user never clicks on the text. When clicking on a part of the QTextEdit, the cursor position is changed. It's not an issue for the position since I use append() which inserts text at the end, even if the cursor is somewhere else. However, if the user clicked on something with a non-neutral style, the afterwards appended text will have this style as well, which is not desirable.
What would be interesting for me would be to either block the cursor so that the user can't tamper with the styles or to append without basing the style on the current paragraph.
Is there a way to change this behaviour, other than by subclassing QTextEdit?
As mentioned, I could check the cursor position before doing any append() (and set the cursor at the end of the document if it has been moved) but if it exists, I would prefer a more "global" solution.
I tried to reproduce in an MCVE what OP described:
// Qt header:
#include <QtWidgets>
// main application
int main(int argc, char **argv)
{
qDebug() << "Qt Version:" << QT_VERSION_STR;
QApplication app(argc, argv);
// setup GUI
QTextEdit qTextEdit;
qTextEdit.show();
// populate text editor
qTextEdit.append(QString::fromUtf8(
"<p>This is some text...</p>"
"<p style='color: red'>...followed by more red text...</p>"
"<p style='color: blue; font-weight: bold'>...followed by more fat blue text.</p>"));
// test QTextEdit::append() like described by OP:
qTextEdit.setTextCursor(QTextCursor(qTextEdit.document()->findBlockByNumber(1)));
qTextEdit.append("TEST. (Reproduce what OP described.)");
qTextEdit.append("<p>TEST. (A possible fix.)</p>");
// runtime loop
return app.exec();
}
Output:
So, a possible fix is to provide the text to append with mark-up.
If it's just raw text the simplest solution is to wrap it in "<p>" and "</p>".
Btw. if it's just raw text I would recommend some additional adjustments to make it proper HTML according to the Supported HTML Subset.
Namely, I would search and replace the usual XML meta characters like I did it e.g. in my answer to SO: qt plaintextedit change message color.

Performantly appending (rich) text into QTextEdit or QTextBrowser in Qt

QTextEdit can be appended text to simply using append(). However, if the document is rich text, every time you append to the document, it is apparently reparsed. This seems like a bit of a trap in Qt.
If you're using the edit box as a log window and appending text in fast successions as a result of external signals, the appending can easily hang your app with no intermediate appends shown until each of the appends have completed.
How do I append rich text to a QTextEdit without it slowing down the entire UI?
If you want each append to actually show quickly & separately (instead of waiting until they've all been appended before they are shown), you need to access the internal QTextDocument:
void fastAppend(QString message,QTextEdit *editWidget)
{
const bool atBottom = editWidget->verticalScrollBar()->value() == editWidget->verticalScrollBar()->maximum();
QTextDocument* doc = editWidget->document();
QTextCursor cursor(doc);
cursor.movePosition(QTextCursor::End);
cursor.beginEditBlock();
cursor.insertBlock();
cursor.insertHtml(message);
cursor.endEditBlock();
//scroll scrollarea to bottom if it was at bottom when we started
//(we don't want to force scrolling to bottom if user is looking at a
//higher position)
if (atBottom) {
scrollLogToBottom(editWidget);
}
}
void scrollLogToBottom(QTextEdit *editWidget)
{
QScrollBar* bar = editWidget->verticalScrollBar();
bar->setValue(bar->maximum());
}
The scrolling to bottom is optional, but in logging use it's a reasonable default for UI behaviour.
Also, if your app is doing lots of other processing at the same time, appending this at the end of fastAppend, will prioritize actually getting the message displayed asap:
//show the message in output right away by triggering event loop
QCoreApplication::processEvents();
This actually seems a kind of trap in Qt. I would know why there isn't a fastAppend method directly in QTextEdit? Or are there caveats to this solution?
(My company actually paid KDAB for this advice, but this seems so silly that I thought this should be more common knowledge.)

QT QIcon properties for custom widget in designer

I have been working for a little while now on creating a QT custom designer widget for GUI menus. The idea being that you simply drag it into the designer, select the number of frames you'd like, how many buttons per frame, etc. and it generates and sizes everything for you.
The way the widget is structured there are properties to configure each button for the frame you are in. For example, you would use the button0Text field to enter text under Button0 while editing in frame 0, then use it again to edit Button0 which is in frame 1. Both buttons would retain the individual changes for each frame.
The Problem
Normally when I switch frames all of my properties are updated to reflect the status of the frame. The exception being QIcon. The correct icon is retained in the actual graphical representation and builds correctly, however the file path in the property list is always of the last edited for that property. I think this will be extremely confusing to an end user and I have found no way to fix it. So for example, if I set text and icons in frame 0 then switch to frame 1 the text in the property list will update to reflect the state of frame 1 but the icon path names will still show my last edit in frame 0 and not the actual icon in frame 1.
I have tried things as simple as:
setProperty("button0Icon", getButton0Icon());
That code works on properties like text, but not for the icon. I try executing it immediately after changing frames.
I have also tried:
#ifndef Q_WS_QWS
QDesignerFormWindowInterface *form = QDesignerFormWindowInterface::findFormWindow(this);
if(form){
QDesignerFormEditorInterface *editor = form->core();
QExtensionManager *manager = editor->extensionManager();
QDesignerPropertySheetExtension *sheet;
sheet = qt_extension<QDesignerPropertySheetExtension*>(manager, this);
int propertyIndex = sheet->indexOf("button0Icon");
sheet->setChanged(propertyIndex, true);
sheet->setProperty(propertyIndex, getButton0Icon());
}
#endif
And:
int propertyIndex = this->metaObject()->indexOfProperty("button0Icon");
QMetaProperty property = this->metaObject()->property(propertyIndex);
property.write(this, QIcon());
Nothing seems to update the property list in the designer.
I have all properties, including all QIcon properties properly declared in the header file with Q_PROPERTY and assigned getter and setter functions.
To be clear, the icon values are indeed retained through each frame when compiled. So it is functioning, just unclear for most users.
If anyone has any experience with this or any ideas please let me know. Thanks.
I have discovered that QIcon does not store file names/paths. The file names are only used for the creation of the QIcon. I think this is most likely the reason I do not get any feedback in the Property Browser for my QIcon properties.
Instead I have chosen to hide this property in the designer and add three new ones. Three QUrl properties, each of which is used to supply an image file. I use three because I want to construct a QIcon that contains Modes/States for normal, disabled, and pressed operations.
I take each of these QUrls and save them in QStringLists behind the scenes so their values are stored. I then construct my QIcon using the file names provided from the QUrls.
I would much prefer to be using the native QIcon in the designer for this, any thoughts or feedback are appreciated.

Custom text (code) areas in QTextEdit

I am interested in creating a text object type (inheriting QTextObjectInterface) that behaves like a code area:
distinctive background
border
fixed-width font
editable content
the instances need to be identifiable to code, so that content inside them may be extracted (separate code from surrounding content)
saving / loading (from regular html files)
syntax highlighting would be a plus, but is not really required
The other areas of the document would need to behave the usual way (font properties editable, colors editable, etc).
Qt provides an example for implementing custom text objects with QTextEdit. This looks like the hard way, since new text object can't make use of the existing infrastructure inside QTextEdit / QTextDocument.
QTextObject is
a base class for different kinds of objects that can group parts of a QTextDocument together
so inheriting it may be a choice, but neither its source files in Qt SDK package nor Google searches revealed helpful information.
QTextFrame inherits QTextObject so, again, it may be a feasible base class if some hints about this path are to be found.
In an simple HTML file all this (except syntax highlighting) would be easy. QTextEdit takes html as input and is able to export html, but the structure is lost in the process.
<code class="code-sample">
int i = 0;
</code>
QWebView is read-only, by the way. It advertises that:
Parts of HTML documents can be editable for example through the contenteditable attribute on HTML elements.
There may be other platforms where this is readily available, but the text editor needs to be used inside Qt Creator as a plug-in, so using Qt framework makes sense.
Bottom line: how does one implement code areas in a QTextEdit widget?
Later edits:
using Qt sdk from trunk (identifies itself as 4.8.4)
Qt Creator from trunk (Qt Creator 2.6.81)
I have found out that implementing this is possible using QTextEdit / QTextDocument.The most simple implementation that I can think of is presented in this answer for the reference of future seeker.
Notice that saving/loading needs to be customised as regular .toHtml() will not preserve the information needed.
Inserting a code block is simple:
QTextFrame * frame;
frame = cursor.insertFrame( code_block_format_ );
connect( frame, SIGNAL( destroyed() ),
this, SLOT( codeBlockDeleted() ) );
code_blocks_.append( frame );
notice the two variables that you can save in the class:
QTextFrameFormat code_block_format_;
QList<const QTextFrame*> code_blocks_;
We need the format for frame to be consistent and distinctive. It mat be initialised in constructor to something like:
code_block_format_.setBackground( QBrush( Qt::yellow ) );
code_block_format_.setBorder( 1 );
code_block_format_.setBorderStyle( QTextFrameFormat::BorderStyle_Inset);
code_block_format_.setMargin( 10 );
code_block_format_.setPadding( 4 );
We need the list so we can tell if a certain frame is a code box or not. Since all objects inheriting QTextObject need to be created by QTextDocument::createObject() we can't simply subclass the QTextFrame (actually I think we can, but not sure yet).
Now separating the code content from the rest may be done the usual way:
for ( it = frame->begin(); !(it.atEnd()); ++it ) {
child_frame = it.currentFrame();
child_block = it.currentBlock();
if ( child_frame != NULL )
{
if ( code_blocks_.contains( frame ) )
{
/* ... */
}
}
} /* for ( it = frame->begin(); !(it.atEnd()); ++it ) */
but notice that this is over-simplified for the sake of brevity. One needs to take into account nested frames.
If you are interested in a full implementation check out the git repository (work in progress, November 2012).

Using images in QListWidget, is this possible?

I am using QT to create a chat messenger client. To display the list of online users, I'm using a QListWidget, as created like this:
listWidget = new QListWidget(horizontalLayoutWidget);
listWidget->setObjectName("userList");
QSizePolicy sizePolicy1(QSizePolicy::Preferred, QSizePolicy::Expanding);
sizePolicy1.setHorizontalStretch(0);
sizePolicy1.setVerticalStretch(0);
sizePolicy1.setHeightForWidth(listWidget->sizePolicy().hasHeightForWidth());
listWidget->setSizePolicy(sizePolicy1);
listWidget->setMinimumSize(QSize(30, 0));
listWidget->setMaximumSize(QSize(150, 16777215));
listWidget->setBaseSize(QSize(100, 0));
listWidget->setContextMenuPolicy(Qt::CustomContextMenu);
Users are shown by constantly refreshing the list, like this: (Note: There are different channels, with different userlists, so refreshing it is the most efficient thing to do, as far as I know.)
void FMessenger::refreshUserlist()
{
if (currentPanel == 0)
return;
listWidget = this->findChild<QListWidget *>(QString("userList"));
listWidget->clear();
QList<FCharacter*> charList = currentPanel->charList();
QListWidgetItem* charitem = 0;
FCharacter* character;
foreach(character, charList)
{
charitem = new QListWidgetItem(character->name());
// charitem->setIcon(QIcon(":/Images/status.png"));
listWidget->addItem(charitem);
}
}
This has always worked perfectly. The line that I commented out is the one I have problems with: my current goal is to be able to display a user's online status with an image, which represents whether they are busy, away, available, etc. Using setIcon() does absolutely nothing though, apparently; the items still show up as they used to, without icons.
I'm aware that this is probably not the way this function needs to be used, but I have found little documentation about it online, and absolutely no useful examples of implementations. My question is, can anybody help me with fixing this problem?
This is how you may conduct your debugging:
Try the constructor that has both icon and text as arguments.
Try to use that icon in another context to ensure it is displayable (construct a QIcon with same argument and use it elsewhere, e.g. QLabel!).
Use icon() from the QListWidgetItem to receive back the icon and then look at that QIcon.
Create a new QListWidget, change nothing, and ordinarily add some stock items in your MainWidget's constructor. See if the icons show up there.