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.
Related
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.)
I merge several classes and functions into only two classes,
so it looks strange and ugly.
The problem that my class MyW at the end of constructor
set background to white, but it's child QLabel has background
from Page, not from MyW.
And question why?
I know that if I remove magic with fonts in MyW,
or call setStyleSheet at the begining like this:
setStyleSheet("border:none;background:#ffffff;color:#000000;");
I get the right result (white background),
but I can not understand why font make influence to background,
and why there is difference between set stylesheet in two steps,
instead of one?
#include <QApplication>
#include <QLabel>
class MyW : public QWidget {
public:
MyW(QWidget *parent) : QWidget(parent) {
setStyleSheet("border:none;");
setFont(QFont{"Arial", 42});
setStyleSheet(styleSheet() + "background:#ffffff;color:#000000;");
auto lbl = new QLabel{"AAAA", this};
lbl->ensurePolished();
}
};
class Page : public QWidget {
public:
Page(QWidget *parent) : QWidget{parent} {
setStyleSheet("background:#f0f4f7;");
auto item = new MyW{this};
}
};
int main(int argc, char *argv[]) {
QApplication a(argc, argv);
Page p{nullptr};
p.resize(400, 800);
p.show();
return a.exec();
}
Update: I removed all not important parts, like layouts, QApplication::setFont and so on.
I am sure that reason is not in auto font = QApplication::font();, but
in QWidget::setFont call. You can check it, for example, by replacing font stuff with:
QFont font;
font.setPixelSize(42);
setFont(font);
The "magic" behind this is cache that is used inside Qt to deal with style sheets.
You can find hint in qtbase/src/widgets/styles/qstylesheetstyle.cpp, look at usage of
static QStyleSheetStyleCaches *styleSheetCaches = 0;
Qt does not use QWidget::styleSheet property directly,
it parses it and caches result.
There are several triggers for parsing(re-parsing) of QWidget::styleSheet:
Call of QWidget::ensurePolished (it is done automatically when your widget
becomes visible for the first time);
Call of setStyleSheet but only in the case if your widget called QWidget::ensurePolished at least once;
Call of QWidget::setFont or QWidget::setPalette (only if your widget does not have an empty styleSheet).
In your case your problem is a combination of (1-2) and (3):
after force caching of the parsed stylesheet via QWidget::setFont your widget is still not "polished",
so the next call of setStyleSheet does not update the cached style sheet that was created on setFont step,
so on the step with lbl->ensurePolished(); you actually have a style sheet with "border:none;" plus font, plus background of parent.
You can call this->ensurePoslished() before lbl->ensurePoslished() to fix this issue or as suggested by #William Miller use stylesheet to set font,
or place setFont after all calls of setStyleSheet
Per the docs on QApplication::setFont(),
Warning: Do not use this function in conjunction with Qt Style Sheets. The font of an application can be customized using the "font" style sheet property. To set a bold font for all QPushButtons, set the application styleSheet() as "QPushButton { font: bold }"
Since they explicitly warn against this I would expect there is an inheritance conflict when using style sheets in conjunction with the application level default font, so the line
QApplication::setFont(font);
and subsequently,
auto font = QApplication::font();
May not produce the expected behavior. Their alternative is set the application-level styleSheet() for your class, i.e.
setStyleSheet(" MyW { font-family: 'Garamond' }");
So it seems the short answer is that they don't support it.
The reason it makes a difference "between set stylesheet in two steps, instead of one" is not because you are setting the style sheet in two different steps but because you call QApplication::font() between the steps and as this is unsupported behavior, it is producing the unexpected result.
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
So I'm making a text editor using Qt and right now I have a button that opens a dialog called "Format text". I want it to work kind of like the dialog in notepad called "font" where you select a few text attributes from some drop down lists and it shows you what your text will look like. Right now I have it working where you can select the font style, font color, and font size and hit preview and it shows you in a box in the dialog what your text will look like. However, I have a button called "okay" which is supposed to change the highlighted text or the text you are about to type, but I can't figure out how to display those changes on the main window. The .ui files are private and a lot of the already made functions and pointers are the same in every ui file so if I change the ui file to pubic I have to change a whole bunch of things. Can anyway give me a simple answer? I'm trying to do this with as little confusion as possible. More coding and less confusion is better than less coding and more confusion for someone of my skill level. Sorry that this is all one giant paragraph and that I didn't provide any code, but I didn't think the code was necessary, however if you do need some of the code i'd be happy to share it.
Thank you for your help and your time. I hope you all have a nice evening.
QDialog have a signal called finished(), you can connect this signal with your slot. To accomplish your work, pass a QSettings or for simplicity QStringList to dialog settings (responsible for changing font, color ...), the QStringList will save user defined settings, after closing the dialog, iterate through QStringList member to alert Main window.
A pseudo code will look like this
Class Editor:
Editor::Editor()
{
TextSettings textSettings;
textSettings.setSettings(settings); // settings is a member
connect(textSettings, &finished(int)), this, SLOT(alertEditor(int)))
}
Editor::alertEditor(int s)
{
if(s == 0)
{
for (int i = 0; i < settings.size(); ++i)
settings.at(i).toLocal8Bit().constData(); // extract various user settings
}
}
Class TextSettings:
TextSettings::TextSettings(QStringList settings)
{
settings << ui->combobox->currentItem(); // font name as example
}
QMessageBox::about( this, "About Application",
"<h4>Application is a one-paragraph blurb</h4>\n\n"
"Copyright 1991-2003 Such-and-such. "
"For technical support, call 1234-56789 or see\n"
"http://www.such-and-such.com" );
This code is creating the About message box which I wanted to have with two exceptions:
1) I would like to change the icon in the message box with an aaa.png file
2) And I would like to have the link clickable. It looks like hyperlink (it is blue and underlined) but mouse click does not work
Any ideas?
I think you should create a custom QWidget for your about widget. By this way, you can put on the widget all you want. By example, you can place QLabel using the openExternalLinks property for clickable link.
To display a custom image on the QWidget, this example may help.
For the icon, you need to just set the application icon. Something like this:
QApplication::setWindowIcon(QIcon(":/aaa.png")); // from a resource file
As for making the links clickable, I don't think it can be done with the QMessageBox::about API directly.
QMessageBox msgBox;
msgBox.setTextFormat(Qt::RichText); // this does the magic trick and allows you to click the link
msgBox.setText("Text<br />http://www.such-and-such.com");
msgBox.setIcon(yourIcon);
msgBox.exec();
For future reference, the docs state that the default type for textFormat is Qt::AutoText. The docs further state that Qt::AutoText is interpreted as Qt::RichText if Qt::mightBeRichText() returns true, otherwise as Qt::PlainText. Finally, mightBeRichText uses a fast and therefore simple heuristic. It mainly checks whether there is something that looks like a tag before the first line break. So, since you dont have a tag in your first line, it assumes that it is plain text. Set it to RichText explicitely with msgBox.setTextFormat(Qt::RichText); to make it act accordingly.
there's a message in the qtcenter about it:
http://www.qtcentre.org/threads/17365-Clickable-URL-in-QMessageBox
Use http://doc.qt.nokia.com/latest/qlabel.html#setOpenExternalLinks
main.cpp
QApplication app(argc, argv);
app.setWindowIcon(QIcon(":/images/your_icon.png"));
mainwindow.cpp (into your slot if you have one)
void MainWindow::on_aboutAction_triggered()
{
QMessageBox::about(0, "window title", "<a href='http://www.jeffersonpalheta.com'>jeffersonpalheta.com</a>");
}