QTextBrowser + DOM Access?
Hello,
I am trying to create a somewhat versatile chat history message box, and QTextBrowser appears to be the right thing to start with. However, I'd like to be able to manipulate the DOM, the text-color style-attribute specifically. Now I have read QTextBrowser does not support as advanced things as CSS classes, hence provides no sort of DOM-interface. Are there any good alternatives to this? The only way out I see is to keep track of inserted Strings in a seperate data structure and reinsert all the text when a change is required. (Which would work in my case, since colors would only need to change when the user changed a program setting, but is a horribly non-performant thing to do otherwise)
Thanks in advance.
Re: QTextBrowser + DOM Access?
Extract a pointer to the QTextDocument using QTextEdit::document() and go to town using QTextCursor. See also Rich Text Processing
Or, less directly, maintain your HTML document in a QDomDocument and copy the HTML across to the browser.
Re: QTextBrowser + DOM Access?
Thanks for the answer. So you are indeed suggesting to manage the DOM seperately? I tried to do this, but somehow it won't go back in. After changeColor() the textbrowser is empty:
Code:
#include "mainwindow.h"
#include "ui_mainwindow.h"
MainWindow
::MainWindow(QWidget *parent
) : ui(new Ui::MainWindow)
{
ui->setupUi(this);
connect(ui->pushButtonInsert, SIGNAL(clicked()), this, SLOT(receiveInput()));
connect(ui->lineEditInput, SIGNAL(returnPressed()), this, SLOT(receiveInput()));
connect(ui->pushButtonSpecial, SIGNAL(clicked()), this, SLOT(changeColor()));
root = doc->createElement("ChatHistory");
doc->appendChild(root);
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::receiveInput() {
// Get text from input lineEdit
QString message
= ui
->lineEditInput
->text
();
// Clear the lineEdit
ui->lineEditInput->clear();
// Create a Message-tag
// Set attribute sender to dummy client "abc", later to hold actual client names
tag.setAttribute("Sender", "abc");
// Add this tag to QDomElement root, which is already part of the QDomDocument doc
root.appendChild(tag);
// Create a Node containing the text and place it into the Message-tag
QDomText text
= doc
->createTextNode
(message
);
tag.appendChild(text);
// Normally append the message to the QTextBrowser, which knows nothing
// about us managing stuff in a seperate QDomDocument
ui->textBrowser->append(message);
}
void MainWindow::changeColor() {
ui->textBrowser->clear();
for (int i = 0; i < list.size(); i++) {
if (e.attribute("Sender") == "abc") {
e.setAttribute("style", "color:#0000ff");
}
ui->textBrowser->append(list.at(i).toDocument().toString(-1));
}
}
Any ideas?
Re: QTextBrowser + DOM Access?
The text browser will certainly NOT understand a full xml document. There is no magic wand here, you need to get your hands dirty.
Re: QTextBrowser + DOM Access?
Aye. Is this the kind of dirt you were thinking of?
Code:
bool addTag = !e.tagName().isEmpty();
if (addTag) output = "<" + e.tagName();
int len = atts.length();
for (int i = 0; i < len; i++) {
QString name
= atts.
item(i
).
toAttr().
name();
QString value
= atts.
item(i
).
toAttr().
value();
output += " " + name + "=\"" + value + "\"";
}
if (addTag) output += ">";
len = children.length();
for (int i = 0; i < len; i++) {
output += makeTextFromElement(children.item(i).toElement());
}
output += e.text();
if (addTag) output += "</" + e.tagName() + ">";
return output;
}
(It's working)
Re: QTextBrowser + DOM Access?
Could be. I guess there are many possible approaches.
Re: QTextBrowser + DOM Access?
Still, it'd be preferrable to operate on the Document itself. QTextCursor won't operate on the Html. Selected text is always rich text and never any of the XML elements. That makes it fairly useless.
Here's what I tried:
.h
Code:
#ifndef STYLEDCHATTEXTBROWSER_H
#define STYLEDCHATTEXTBROWSER_H
#include <QTextBrowser>
#include <QTextDocument>
#include <QTextCursor>
#include <QMap>
#include <QLinkedList>
#include <QColor>
{
Q_OBJECT
public:
explicit StyledChatTextBrowser
(QWidget *parent
= 0);
void setColorizeClientNames(bool doColor);
bool getColorizeClientNames();
signals:
public slots:
private:
bool doColor;
QMap<QString, QLinkedList<int>* >* clientMessagePositions;
void saveClientColorPosition
(QString client,
int position
);
};
#endif // STYLEDCHATTEXTBROWSER_H
.cpp
Code:
#include "styledchattextbrowser.h"
#include <QDebug>
StyledChatTextBrowser
::StyledChatTextBrowser(QWidget *parent
) :{
doc->clear();
this->setDocument(doc);
this->doColor = false;
clientMessagePositions = new QMap<QString, QLinkedList<int>* >();
}
void StyledChatTextBrowser
::logClientMessage(QString clientName,
QString message
) { QString timestamp
= "[DUMMYSTAMP]";
QString tagOpen
= "<font style=\"color:#000000\">";
QString line
= timestamp
+ " " + tagOpen
+ clientName
+ tagClose
+ ": " + message
+ "\n";
int chars = doc->characterCount();
qDebug() << "Chars: " << chars;
cursor->setPosition(chars - 1);
cursor->insertHtml(line);
cursor->setPosition(chars - 1);
int pos = searchCursor.position() + 13;
cursor->setPosition(chars - 1);
saveClientColorPosition(clientName, pos);
qDebug() << doc->toHtml();
}
void StyledChatTextBrowser
::logEventMessage(QString eventType,
QString message
) {
}
void StyledChatTextBrowser::setColorizeClientNames(bool doColor) {
if (doColor != this->doColor) {
QList<QString> clients = clientMessagePositions->keys();
for (int i = 0; i < clientMessagePositions->size(); i++) {
QLinkedList<int>* positions = clientMessagePositions->value(clients.at(i));
QString hexColor
= doColor ? makeColorFromClientName
(clients.
at(i
)).
name() : "#000000";
QLinkedList<int>::iterator it;
for (it = positions->begin(); it != positions->end(); it++) {
cursor->insertText(hexColor);
}
}
}
this->doColor = doColor;
}
bool StyledChatTextBrowser::getColorizeClientNames() {
return doColor;
}
QColor StyledChatTextBrowser
::makeColorFromClientName(QString clientName
) { int r = 70; int g = 120; int b = 170;
for (int i = 0; i < clientName.length(); i++) {
switch (i % 3) {
case 0: r = ((r + b + clientName.at(i).unicode()) % 200) + 50; break;
case 1: g = ((g + b + clientName.at(i).unicode()) % 200) + 50; break;
case 2: b = ((b + r + g + clientName.at(i).unicode()) % 200) + 50; break;
}
}
c.setRed(r);
c.setGreen(g);
c.setBlue(b);
return c;
}
void StyledChatTextBrowser
::saveClientColorPosition(QString client,
int position
) { QLinkedList<int>* clientMessages = clientMessagePositions->value(client);
if (clientMessages == NULL || clientMessages->empty()) { // Nothing would have been in there if it was empty
clientMessages = new QLinkedList<int>();
clientMessagePositions->insert(client, clientMessages);
}
clientMessages->append(position);
}
Gives output such as:
Starting D:\Projects\Qt\TestTextEdit-build-desktop-Qt_4_7_4_for_Desktop_-_MinGW_4_4__Qt_SDK__Release\release\TestTextEdit.e xe...
Chars: 1
"<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
<html><head><meta name="qrichtext" content="1" /><style type="text/css">
p, li { white-space: pre-wrap; }
</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;">
<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">[DUMMYSTAMP] <span style=" color:#000000;">asd</span>: qwe </p></body></html>"
Chars: 23
"<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
<html><head><meta name="qrichtext" content="1" /><style type="text/css">
p, li { white-space: pre-wrap; }
</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;">
<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">[DUMMYSTAMP]#bb37a2we [DUMMYSTAMP] <span style=" color:#000000;">asd</span>: </p></body></html>"
D:\Projects\Qt\TestTextEdit-build-desktop-Qt_4_7_4_for_Desktop_-_MinGW_4_4__Qt_SDK__Release\release\TestTextEdit.e xe exited with code 0
, when doing logClientMessage("asd", "qwe"), setColorizeClientNames(true), logClientMessage("asd", "").
Re: QTextBrowser + DOM Access?
I would probably use QTextBlockUserData to store DOM related information in the document. QTextCursor is meant to operate on rich text, that is its purpose. Thus you need to have a mapping between the DOM and QTextDocument. If you add text to the Qt document, you should update the respective DOM node and vice versa.
Re: QTextBrowser + DOM Access?
Ah. QTextBlockUserData sounds like the way to go. The QTextBlock deal irritates me though. How is determined how text blocks are formed within the QTextDocument? It says one line = one text block, but you wouldn't call it text block if this wasn't more than just some default setting.
Re: QTextBrowser + DOM Access?
One block is one paragraph.
Re: QTextBrowser + DOM Access?
I got it working better now, but performance is horrible. Changing color of one word in each of a thousand lines takes just a little under one second. Doing the same for 4000 lines takes above 8 seconds. It appears Qt manages the whole document in one large QString. How can I change this behaviour to manage i.e. single text blocks in their own QString?
EDIT: Use of beginEditBlock and endEditBlock helped, but didn't ultimately result in excellent performance. (I have disabled undo/redo on the TextBrowser)
Re: QTextBrowser + DOM Access?
Quote:
Originally Posted by
Zyl
It appears Qt manages the whole document in one large QString.
No, it doesn't. It manages it as a series of QTextObject instances exactly as is said in the Rich Text Document Structure document in the manual.