1
/* === This file is part of Tomahawk Player - <http://tomahawk-player.org> ===
3
* Copyright 2011, Ruslan Nigmatullin <euroelessar@ya.ru>
4
* Copyright 2011, Dominik Schmidt <dev@dominik-schmidt.de>
5
* Copyright 2011, Jeff Mitchell <jeff@tomahawk-player.org>
7
* Tomahawk is free software: you can redistribute it and/or modify
8
* it under the terms of the GNU General Public License as published by
9
* the Free Software Foundation, either version 3 of the License, or
10
* (at your option) any later version.
12
* Tomahawk is distributed in the hope that it will be useful,
13
* but WITHOUT ANY WARRANTY; without even the implied warranty of
14
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
* GNU General Public License for more details.
17
* You should have received a copy of the GNU General Public License
18
* along with Tomahawk. If not, see <http://www.gnu.org/licenses/>.
21
#include "XmlConsole.h"
22
#include "ui_XmlConsole.h"
24
#include "utils/Logger.h"
27
#include <QActionGroup>
28
#include <QStringBuilder>
29
#include <QTextLayout>
30
#include <QPlainTextDocumentLayout>
31
#include <QFileDialog>
32
#include <QTextDocumentWriter>
34
using namespace Jreen;
37
XmlConsole::XmlConsole(Client* client, QWidget *parent) :
39
m_ui(new Ui::XmlConsole),
40
m_client(client), m_filter(0x1f)
44
m_client->addXmlStreamHandler(this);
46
QPalette pal = palette();
47
pal.setColor(QPalette::Base, Qt::black);
48
pal.setColor(QPalette::Text, Qt::white);
49
m_ui->xmlBrowser->viewport()->setPalette(pal);
50
QTextDocument *doc = m_ui->xmlBrowser->document();
51
doc->setDocumentLayout(new QPlainTextDocumentLayout(doc));
54
QTextFrameFormat format = doc->rootFrame()->frameFormat();
55
format.setBackground(QColor(Qt::black));
57
doc->rootFrame()->setFrameFormat(format);
58
QMenu *menu = new QMenu(m_ui->filterButton);
59
menu->setSeparatorsCollapsible(false);
60
menu->addSeparator()->setText(tr("Filter"));
61
QActionGroup *group = new QActionGroup(menu);
62
QAction *disabled = group->addAction(menu->addAction(tr("Disabled")));
63
disabled->setCheckable(true);
64
disabled->setData(Disabled);
65
QAction *jid = group->addAction(menu->addAction(tr("By JID")));
66
jid->setCheckable(true);
68
QAction *xmlns = group->addAction(menu->addAction(tr("By namespace uri")));
69
xmlns->setCheckable(true);
70
xmlns->setData(ByXmlns);
71
QAction *attrb = group->addAction(menu->addAction(tr("By all attributes")));
72
attrb->setCheckable(true);
73
attrb->setData(ByAllAttributes);
74
disabled->setChecked(true);
75
connect(group, SIGNAL(triggered(QAction*)), this, SLOT(onActionGroupTriggered(QAction*)));
76
menu->addSeparator()->setText(tr("Visible stanzas"));
77
group = new QActionGroup(menu);
78
group->setExclusive(false);
79
QAction *iq = group->addAction(menu->addAction(tr("Information query")));
80
iq->setCheckable(true);
81
iq->setData(XmlNode::Iq);
83
QAction *message = group->addAction(menu->addAction(tr("Message")));
84
message->setCheckable(true);
85
message->setData(XmlNode::Message);
86
message->setChecked(true);
87
QAction *presence = group->addAction(menu->addAction(tr("Presence")));
88
presence->setCheckable(true);
89
presence->setData(XmlNode::Presence);
90
presence->setChecked(true);
91
QAction *custom = group->addAction(menu->addAction(tr("Custom")));
92
custom->setCheckable(true);
93
custom->setData(XmlNode::Custom);
94
custom->setChecked(true);
95
connect(group, SIGNAL(triggered(QAction*)), this, SLOT(onActionGroupTriggered(QAction*)));
96
m_ui->filterButton->setMenu(menu);
97
m_stackBracketsColor = QColor(0x666666);
98
m_stackIncoming.bodyColor = QColor(0xbb66bb);
99
m_stackIncoming.tagColor = QColor(0x006666);
100
m_stackIncoming.attributeColor = QColor(0x009933);
101
m_stackIncoming.paramColor = QColor(0xcc0000);
102
m_stackOutgoing.bodyColor = QColor(0x999999);
103
m_stackOutgoing.tagColor = QColor(0x22aa22);
104
m_stackOutgoing.attributeColor = QColor(0xffff33);
105
m_stackOutgoing.paramColor = QColor(0xdd8811);
107
QAction *action = new QAction(tr("Close"),this);
108
action->setSoftKeyRole(QAction::NegativeSoftKey);
109
connect(action, SIGNAL(triggered()), SLOT(close()));
113
XmlConsole::~XmlConsole()
118
void XmlConsole::handleStreamBegin()
120
m_stackIncoming.reader.clear();
121
m_stackOutgoing.reader.clear();
122
m_stackIncoming.depth = 0;
123
m_stackOutgoing.depth = 0;
124
qDeleteAll(m_stackIncoming.tokens);
125
qDeleteAll(m_stackOutgoing.tokens);
126
m_stackIncoming.tokens.clear();
127
m_stackOutgoing.tokens.clear();
130
void XmlConsole::handleStreamEnd()
132
m_stackIncoming.reader.clear();
133
m_stackOutgoing.reader.clear();
134
m_stackIncoming.depth = 0;
135
m_stackOutgoing.depth = 0;
136
qDeleteAll(m_stackIncoming.tokens);
137
qDeleteAll(m_stackOutgoing.tokens);
138
m_stackIncoming.tokens.clear();
139
m_stackOutgoing.tokens.clear();
142
void XmlConsole::handleIncomingData(const char *data, qint64 size)
144
stackProcess(QByteArray::fromRawData(data, size), true);
147
void XmlConsole::handleOutgoingData(const char *data, qint64 size)
149
stackProcess(QByteArray::fromRawData(data, size), false);
152
QString generate_stacked_space(int depth)
154
return QString(depth * 2, QLatin1Char(' '));
157
void XmlConsole::stackProcess(const QByteArray &data, bool incoming)
159
StackEnvironment *d = &(incoming ? m_stackIncoming : m_stackOutgoing);
160
d->reader.addData(data);
162
// debug() << incoming << data;
163
// debug() << "==================================================================";
164
while (d->reader.readNext() > QXmlStreamReader::Invalid) {
165
// qDebug() << incoming << d->reader.tokenString();
166
switch(d->reader.tokenType()) {
167
case QXmlStreamReader::StartElement:
168
// dbg << d->reader.name().toString() << d->depth
169
// << d->reader.attributes().value(QLatin1String("from")).toString();
172
if (!d->tokens.isEmpty() && d->tokens.last()->type == QXmlStreamReader::Characters)
173
delete d->tokens.takeLast();
174
d->tokens << new StackToken(d->reader);
177
case QXmlStreamReader::EndElement:
178
// dbg << d->reader.name().toString() << d->depth;
179
if (d->tokens.isEmpty())
181
token = d->tokens.last();
182
if (token->type == QXmlStreamReader::StartElement && !token->startTag.empty)
183
token->startTag.empty = true;
184
else if (d->depth > 1)
185
d->tokens << new StackToken(d->reader);
187
QTextCursor cursor(m_ui->xmlBrowser->document());
188
cursor.movePosition(QTextCursor::End);
189
cursor.beginEditBlock();
190
QTextCharFormat zeroFormat = cursor.charFormat();
191
zeroFormat.setForeground(QColor(Qt::white));
192
QTextCharFormat bodyFormat = zeroFormat;
193
bodyFormat.setForeground(d->bodyColor);
194
QTextCharFormat tagFormat = zeroFormat;
195
tagFormat.setForeground(d->tagColor);
196
QTextCharFormat attributeFormat = zeroFormat;
197
attributeFormat.setForeground(d->attributeColor);
198
QTextCharFormat paramsFormat = zeroFormat;
199
paramsFormat.setForeground(d->paramColor);
200
QTextCharFormat bracketFormat = zeroFormat;
201
bracketFormat.setForeground(m_stackBracketsColor);
202
QString singleSpace = QLatin1String(" ");
203
cursor.insertBlock();
205
QString currentXmlns;
206
QXmlStreamReader::TokenType lastType = QXmlStreamReader::StartElement;
207
for (int i = 0; i < d->tokens.size(); i++) {
208
token = d->tokens.at(i);
209
if (token->type == QXmlStreamReader::StartElement) {
210
QString space = generate_stacked_space(depth);
211
cursor.insertText(QLatin1String("\n"));
212
cursor.insertText(space);
213
cursor.insertText(QLatin1String("<"), bracketFormat);
214
cursor.insertText(token->startTag.name->toString(), tagFormat);
215
const QStringRef &xmlns = *token->startTag.xmlns;
216
if (i == 0 || xmlns != currentXmlns) {
217
currentXmlns = xmlns.toString();
218
cursor.insertText(singleSpace);
219
cursor.insertText(QLatin1String("xmlns"), attributeFormat);
220
cursor.insertText(QLatin1String("="), zeroFormat);
221
cursor.insertText(QLatin1String("'"), paramsFormat);
222
cursor.insertText(currentXmlns, paramsFormat);
223
cursor.insertText(QLatin1String("'"), paramsFormat);
225
QXmlStreamAttributes *attributes = token->startTag.attributes;
226
for (int j = 0; j < attributes->count(); j++) {
227
const QXmlStreamAttribute &attr = attributes->at(j);
228
cursor.insertText(singleSpace);
229
cursor.insertText(attr.name().toString(), attributeFormat);
230
cursor.insertText(QLatin1String("="), zeroFormat);
231
cursor.insertText(QLatin1String("'"), paramsFormat);
232
cursor.insertText(attr.value().toString(), paramsFormat);
233
cursor.insertText(QLatin1String("'"), paramsFormat);
235
if (token->startTag.empty) {
236
cursor.insertText(QLatin1String("/>"), bracketFormat);
238
cursor.insertText(QLatin1String(">"), bracketFormat);
241
} else if (token->type == QXmlStreamReader::EndElement) {
242
if (lastType == QXmlStreamReader::EndElement) {
243
QString space = generate_stacked_space(depth - 1);
244
cursor.insertText(QLatin1String("\n"));
245
cursor.insertText(space);
247
cursor.insertText(QLatin1String("</"), bracketFormat);
248
cursor.insertText(token->endTag.name->toString(), tagFormat);
249
cursor.insertText(QLatin1String(">"), bracketFormat);
251
} else if (token->type == QXmlStreamReader::Characters) {
252
cursor.setCharFormat(bodyFormat);
253
QString text = token->charachters.text->toString();
254
if (text.contains(QLatin1Char('\n'))) {
255
QString space = generate_stacked_space(depth);
256
space.prepend(QLatin1Char('\n'));
257
QStringList lines = text.split(QLatin1Char('\n'));
258
for (int j = 0; j < lines.size(); j++) {
259
cursor.insertText(space);
260
cursor.insertText(lines.at(j));
263
cursor.insertText(space);
265
cursor.insertText(text);
268
lastType = token->type;
269
if (lastType == QXmlStreamReader::StartElement && token->startTag.empty)
270
lastType = QXmlStreamReader::EndElement;
273
cursor.endEditBlock();
278
case QXmlStreamReader::Characters:
279
token = d->tokens.isEmpty() ? 0 : d->tokens.last();
280
if (token && token->type == QXmlStreamReader::StartElement && !token->startTag.empty) {
281
if (*token->startTag.name == QLatin1String("auth")
282
&& *token->startTag.xmlns == QLatin1String("urn:ietf:params:xml:ns:xmpp-sasl")) {
283
d->tokens << new StackToken(QLatin1String("<<Private data>>"));
285
d->tokens << new StackToken(d->reader);
293
// qDebug() << d->reader.tokenString();
294
// if (d->reader.tokenType() == QXmlStreamReader::Invalid)
295
// dbg << d->reader.error() << d->reader.errorString();
296
if (!incoming && d->depth > 1) {
297
qFatal("outgoing depth %d on\n\"%s\"", d->depth,
298
qPrintable(QString::fromUtf8(data, data.size())));
302
void XmlConsole::changeEvent(QEvent *e)
304
QWidget::changeEvent(e);
306
case QEvent::LanguageChange:
307
m_ui->retranslateUi(this);
314
void XmlConsole::onActionGroupTriggered(QAction *action)
316
int type = action->data().toInt();
318
m_filter = (m_filter & 0xf) | type;
319
m_ui->lineEdit->setEnabled(type != 0x10);
321
m_filter = m_filter ^ type;
323
on_lineEdit_textChanged(m_ui->lineEdit->text());
326
void XmlConsole::on_lineEdit_textChanged(const QString &text)
328
int filterType = m_filter & 0xf0;
329
JID filterJid = (filterType == ByJid) ? text : QString();
330
for (int i = 0; i < m_nodes.size(); i++) {
331
XmlNode &node = m_nodes[i];
333
switch (filterType) {
335
ok = node.xmlns.contains(text);
338
ok = node.jid.full() == filterJid.full() || node.jid.bare() == filterJid.full();
340
case ByAllAttributes:
341
ok = node.attributes.contains(text);
346
ok &= bool(node.type & m_filter);
347
node.block.setVisible(ok);
348
node.block.setLineCount(ok ? node.lineCount : 0);
349
// qDebug() << node.block.lineCount();
351
QAbstractTextDocumentLayout *layout = m_ui->xmlBrowser->document()->documentLayout();
352
Q_ASSERT(qobject_cast<QPlainTextDocumentLayout*>(layout));
353
qobject_cast<QPlainTextDocumentLayout*>(layout)->requestUpdate();
356
void XmlConsole::on_saveButton_clicked()
358
QString fileName = QFileDialog::getSaveFileName(this, tr("Save XMPP log to file"),
359
QString(), tr("OpenDocument Format (*.odf);;HTML file (*.html);;Plain text (*.txt)"));
360
if (!fileName.isEmpty()) {
361
QTextDocumentWriter writer(fileName);
362
writer.write(m_ui->xmlBrowser->document());