PageRenderTime 158ms CodeModel.GetById 40ms app.highlight 92ms RepoModel.GetById 19ms app.codeStats 0ms

/src/sip/jabber/xmlconsole.cpp

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