From 350be106c6e5bbc007168496be3539ad48d78258 Mon Sep 17 00:00:00 2001
From: Dominik Schmidt <dev@dominik-schmidt.de>
Date: Tue, 10 May 2011 02:21:15 +0200
Subject: [PATCH] sipjreen: Add secret XmlConsole

Imported from qutim. Ruslan Nigmatullin gave me his compliance with
licensing this file as GPL3(+) (right now it lacks license headers
completely and might be GPL2+ in qutim).
---
 src/sip/jreen/CMakeLists.txt               |   5 +-
 src/sip/jreen/googlewrapper/CMakeLists.txt |   4 +-
 src/sip/jreen/jabber.cpp                   |  38 ++-
 src/sip/jreen/jabber.h                     |   6 +-
 src/sip/jreen/xmlconsole.cpp               | 356 +++++++++++++++++++++
 src/sip/jreen/xmlconsole.h                 | 176 ++++++++++
 src/sip/jreen/xmlconsole.ui                |  51 +++
 7 files changed, 625 insertions(+), 11 deletions(-)
 create mode 100644 src/sip/jreen/xmlconsole.cpp
 create mode 100644 src/sip/jreen/xmlconsole.h
 create mode 100644 src/sip/jreen/xmlconsole.ui

diff --git a/src/sip/jreen/CMakeLists.txt b/src/sip/jreen/CMakeLists.txt
index d439d5164..a7cd6992e 100644
--- a/src/sip/jreen/CMakeLists.txt
+++ b/src/sip/jreen/CMakeLists.txt
@@ -11,6 +11,7 @@ set( jabberSources
     tomahawksipmessage.cpp
     tomahawksipmessagefactory.cpp
     avatarmanager.cpp
+    xmlconsole.cpp
 )
 
 set( jabberHeaders
@@ -18,10 +19,12 @@ set( jabberHeaders
     tomahawksipmessage.h
     tomahawksipmessagefactory.h
     avatarmanager.h
+    xmlconsole.h
 )
 
 set( jabberUI
-     configwidget.ui
+    configwidget.ui
+    xmlconsole.ui
 )
 
 include_directories( . ${CMAKE_CURRENT_BINARY_DIR} ..
diff --git a/src/sip/jreen/googlewrapper/CMakeLists.txt b/src/sip/jreen/googlewrapper/CMakeLists.txt
index f035266d0..3ebd653e0 100644
--- a/src/sip/jreen/googlewrapper/CMakeLists.txt
+++ b/src/sip/jreen/googlewrapper/CMakeLists.txt
@@ -6,6 +6,7 @@ set( googleHeaders
      ../tomahawksipmessage.h
      ../tomahawksipmessagefactory.h
      ../avatarmanager.h
+     ../xmlconsole.h
      googlewrapper.h )
 
 set( googleSources
@@ -13,13 +14,14 @@ set( googleSources
      ../tomahawksipmessage.cpp
      ../tomahawksipmessagefactory.cpp
      ../avatarmanager.cpp
+     ../xmlconsole.cpp
      googlewrapper.cpp )
 
 add_definitions(-DGOOGLE_WRAPPER)
 
 qt4_add_resources( RCX_SRCS "resources.qrc" )
 
-qt4_wrap_cpp( googleMoc ../jabber.h ../avatarmanager.h googlewrapper.h )
+qt4_wrap_cpp( googleMoc ${googleHeaders} )
 add_library( tomahawk_sipgoogle SHARED ${googleSources} ${googleMoc} ${googleMoc} ${RCX_SRCS} )
 
 target_link_libraries( tomahawk_sipgoogle
diff --git a/src/sip/jreen/jabber.cpp b/src/sip/jreen/jabber.cpp
index e99f32a63..c195f2ddd 100644
--- a/src/sip/jreen/jabber.cpp
+++ b/src/sip/jreen/jabber.cpp
@@ -18,6 +18,9 @@
  */
 
 #include "jabber.h"
+#include "ui_configwidget.h"
+
+#include "xmlconsole.h"
 
 #include "config.h"
 
@@ -43,8 +46,6 @@
 #include <QDateTime>
 #include <QTimer>
 
-#include "ui_configwidget.h"
-
 SipPlugin*
 JabberFactory::createPlugin( const QString& pluginId )
 {
@@ -60,7 +61,6 @@ JabberFactory::icon() const
 JabberPlugin::JabberPlugin( const QString& pluginId )
     : SipPlugin( pluginId )
     , m_menu( 0 )
-    , m_addFriendAction( 0 )
     , m_state( Disconnected )
 {
     qDebug() << Q_FUNC_INFO;
@@ -92,6 +92,13 @@ JabberPlugin::JabberPlugin( const QString& pluginId )
     m_currentResource = QString::fromAscii( "tomahawk%1" ).arg( QString::number( qrand() % 10000 ) );
     m_client->setResource( m_currentResource );
 
+    // instantiate XmlConsole
+    if( readXmlConsoleEnabled() )
+    {
+        m_xmlConsole =  new XmlConsole( m_client );
+        m_xmlConsole->show();
+    }
+
     // add VCardUpdate extension to own presence
     m_client->presence().addExtension( new Jreen::VCardUpdate() );
 
@@ -470,6 +477,12 @@ JabberPlugin::showAddFriendDialog()
     addContact( id );
 }
 
+void
+JabberPlugin::showXmlConsole()
+{
+   m_xmlConsole->show();
+}
+
 void
 JabberPlugin::checkSettings()
 {
@@ -531,9 +544,15 @@ void JabberPlugin::addMenuHelper()
     if( !m_menu )
     {
         m_menu = new QMenu( QString( "%1 (" ).arg( friendlyName() ).append( accountName() ).append(")" ) );
-        m_addFriendAction = m_menu->addAction( "Add Friend..." );
 
-        connect( m_addFriendAction, SIGNAL( triggered() ), this, SLOT( showAddFriendDialog() ) );
+        QAction* addFriendAction = m_menu->addAction( tr( "Add Friend..." ) );
+        connect( addFriendAction, SIGNAL( triggered() ), this, SLOT( showAddFriendDialog() ) );
+
+        if( readXmlConsoleEnabled() )
+        {
+            QAction* showXmlConsoleAction = m_menu->addAction( tr( "XML Console...") );
+            connect( showXmlConsoleAction, SIGNAL( triggered() ), this, SLOT( showXmlConsole() ) );
+        }
 
         emit addMenu( m_menu );
     }
@@ -541,13 +560,12 @@ void JabberPlugin::addMenuHelper()
 
 void JabberPlugin::removeMenuHelper()
 {
-    if( m_menu && m_addFriendAction )
+    if( m_menu )
     {
         emit removeMenu( m_menu );
 
         delete m_menu;
         m_menu = 0;
-        m_addFriendAction = 0; // deleted by menu
     }
 }
 
@@ -880,6 +898,12 @@ void JabberPlugin::onNewAvatar(const QString& jid)
         emit avatarReceived ( jid,  m_avatarManager->avatar( jid ) );
 }
 
+bool
+JabberPlugin::readXmlConsoleEnabled()
+{
+    return TomahawkSettings::instance()->value( pluginId() + "/xmlconsole", QVariant( false ) ).toBool();
+}
+
 
 QString
 JabberPlugin::readPassword()
diff --git a/src/sip/jreen/jabber.h b/src/sip/jreen/jabber.h
index 7f6f11393..6f8ea6403 100644
--- a/src/sip/jreen/jabber.h
+++ b/src/sip/jreen/jabber.h
@@ -23,6 +23,7 @@
 #include "sip/SipPlugin.h"
 
 #include "avatarmanager.h"
+#include "xmlconsole.h"
 
 #include <jreen/client.h>
 #include <jreen/disco.h>
@@ -99,6 +100,7 @@ protected:
 
 private slots:
     void showAddFriendDialog();
+    void showXmlConsole();
     void onConnect();
     void onDisconnect(Jreen::Client::DisconnectReason reason);
 
@@ -115,6 +117,7 @@ private slots:
     void onNewAvatar( const QString &jid );
 
 private:
+    bool readXmlConsoleEnabled();
     QString readPassword();
     QString readServer();
     int readPort();
@@ -128,8 +131,7 @@ private:
     void handlePeerStatus( const Jreen::JID &jid, Jreen::Presence::Type presenceType );
 
     QMenu* m_menu;
-    QAction* m_addFriendAction;
-
+    XmlConsole* m_xmlConsole;
     QString m_currentUsername;
     QString m_currentPassword;
     QString m_currentServer;
diff --git a/src/sip/jreen/xmlconsole.cpp b/src/sip/jreen/xmlconsole.cpp
new file mode 100644
index 000000000..fc6598bbb
--- /dev/null
+++ b/src/sip/jreen/xmlconsole.cpp
@@ -0,0 +1,356 @@
+/* === This file is part of Tomahawk Player - <http://tomahawk-player.org> ===
+ *
+ *   Copyright 2011, Ruslan Nigmatullin <euroelessar@ya.ru>
+ *   Copyright 2011, Dominik Schmidt <dev@dominik-schmidt.de>
+ *
+ *   Tomahawk is free software: you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation, either version 3 of the License, or
+ *   (at your option) any later version.
+ *
+ *   Tomahawk is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with Tomahawk. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "xmlconsole.h"
+#include "ui_xmlconsole.h"
+
+
+#include <QMenu>
+#include <QActionGroup>
+#include <QStringBuilder>
+#include <QDebug>
+#include <QTextLayout>
+#include <QPlainTextDocumentLayout>
+#include <QFileDialog>
+#include <QTextDocumentWriter>
+
+using namespace Jreen;
+
+XmlConsole::XmlConsole(Client* client, QWidget *parent) :
+	QWidget(parent),
+	m_ui(new Ui::XmlConsole),
+	m_client(client),	m_filter(0x1f)
+{
+	m_ui->setupUi(this);
+
+    m_client->addXmlStreamHandler(this);
+
+	QPalette pal = palette();
+	pal.setColor(QPalette::Base, Qt::black);
+	pal.setColor(QPalette::Text, Qt::white);
+	m_ui->xmlBrowser->viewport()->setPalette(pal);
+	QTextDocument *doc = m_ui->xmlBrowser->document();
+	doc->setDocumentLayout(new QPlainTextDocumentLayout(doc));
+	doc->clear();
+
+	QTextFrameFormat format = doc->rootFrame()->frameFormat();
+	format.setBackground(QColor(Qt::black));
+	format.setMargin(0);
+	doc->rootFrame()->setFrameFormat(format);
+	QMenu *menu = new QMenu(m_ui->filterButton);
+	menu->setSeparatorsCollapsible(false);
+	menu->addSeparator()->setText(tr("Filter"));
+	QActionGroup *group = new QActionGroup(menu);
+	QAction *disabled = group->addAction(menu->addAction(tr("Disabled")));
+	disabled->setCheckable(true);
+	disabled->setData(Disabled);
+	QAction *jid = group->addAction(menu->addAction(tr("By JID")));
+	jid->setCheckable(true);
+	jid->setData(ByJid);
+	QAction *xmlns = group->addAction(menu->addAction(tr("By namespace uri")));
+	xmlns->setCheckable(true);
+	xmlns->setData(ByXmlns);
+	QAction *attrb = group->addAction(menu->addAction(tr("By all attributes")));
+	attrb->setCheckable(true);
+	attrb->setData(ByAllAttributes);
+	disabled->setChecked(true);
+	connect(group, SIGNAL(triggered(QAction*)), this, SLOT(onActionGroupTriggered(QAction*)));
+	menu->addSeparator()->setText(tr("Visible stanzas"));
+	group = new QActionGroup(menu);
+	group->setExclusive(false);
+	QAction *iq = group->addAction(menu->addAction(tr("Information query")));
+	iq->setCheckable(true);
+	iq->setData(XmlNode::Iq);
+	iq->setChecked(true);
+	QAction *message = group->addAction(menu->addAction(tr("Message")));
+	message->setCheckable(true);
+	message->setData(XmlNode::Message);
+	message->setChecked(true);
+	QAction *presence = group->addAction(menu->addAction(tr("Presence")));
+	presence->setCheckable(true);
+	presence->setData(XmlNode::Presence);
+	presence->setChecked(true);
+	QAction *custom = group->addAction(menu->addAction(tr("Custom")));
+	custom->setCheckable(true);
+	custom->setData(XmlNode::Custom);
+	custom->setChecked(true);
+	connect(group, SIGNAL(triggered(QAction*)), this, SLOT(onActionGroupTriggered(QAction*)));
+	m_ui->filterButton->setMenu(menu);
+	m_stackBracketsColor = QColor(0x666666);
+	m_stackIncoming.bodyColor = QColor(0xbb66bb);
+	m_stackIncoming.tagColor = QColor(0x006666);
+	m_stackIncoming.attributeColor = QColor(0x009933);
+	m_stackIncoming.paramColor = QColor(0xcc0000);
+	m_stackOutgoing.bodyColor = QColor(0x999999);
+	m_stackOutgoing.tagColor = QColor(0x22aa22);
+	m_stackOutgoing.attributeColor = QColor(0xffff33);
+	m_stackOutgoing.paramColor = QColor(0xdd8811);
+
+	QAction *action = new QAction(tr("Close"),this);
+	action->setSoftKeyRole(QAction::NegativeSoftKey);
+	connect(action, SIGNAL(triggered()), SLOT(close()));
+	addAction(action);
+}
+
+XmlConsole::~XmlConsole()
+{
+	delete m_ui;
+}
+
+void XmlConsole::handleStreamBegin()
+{
+	m_stackIncoming.reader.clear();
+	m_stackOutgoing.reader.clear();
+	m_stackIncoming.depth = 0;
+	m_stackOutgoing.depth = 0;
+	qDeleteAll(m_stackIncoming.tokens);
+	qDeleteAll(m_stackOutgoing.tokens);
+	m_stackIncoming.tokens.clear();
+	m_stackOutgoing.tokens.clear();
+}
+
+void XmlConsole::handleStreamEnd()
+{
+	m_stackIncoming.reader.clear();
+	m_stackOutgoing.reader.clear();
+	m_stackIncoming.depth = 0;
+	m_stackOutgoing.depth = 0;
+	qDeleteAll(m_stackIncoming.tokens);
+	qDeleteAll(m_stackOutgoing.tokens);
+	m_stackIncoming.tokens.clear();
+	m_stackOutgoing.tokens.clear();
+}
+
+void XmlConsole::handleIncomingData(const char *data, qint64 size)
+{
+	stackProcess(QByteArray::fromRawData(data, size), true);
+}
+
+void XmlConsole::handleOutgoingData(const char *data, qint64 size)
+{
+	stackProcess(QByteArray::fromRawData(data, size), false);
+}
+
+QString generate_stacked_space(int depth)
+{
+	return QString(depth * 2, QLatin1Char(' '));
+}
+
+void XmlConsole::stackProcess(const QByteArray &data, bool incoming)
+{
+	StackEnvironment *d = &(incoming ? m_stackIncoming : m_stackOutgoing);
+	d->reader.addData(data);
+	StackToken *token;
+//	debug() << incoming << data;
+//	debug() << "==================================================================";
+	while (d->reader.readNext() > QXmlStreamReader::Invalid) {
+//		QDebug dbg = debug() << incoming << d->reader.tokenString();
+		switch(d->reader.tokenType()) {
+		case QXmlStreamReader::StartElement:
+//			dbg << d->reader.name().toString() << d->depth
+//					<< d->reader.attributes().value(QLatin1String("from")).toString();
+			d->depth++;
+			if (d->depth > 1) {
+				if (!d->tokens.isEmpty() && d->tokens.last()->type == QXmlStreamReader::Characters)
+					delete d->tokens.takeLast();
+				d->tokens << new StackToken(d->reader);
+			}
+			break;
+		case QXmlStreamReader::EndElement:
+//			dbg << d->reader.name().toString() << d->depth;
+			if (d->tokens.isEmpty())
+				break;
+			token = d->tokens.last();
+			if (token->type == QXmlStreamReader::StartElement && !token->startTag.empty)
+				token->startTag.empty = true;
+			else if (d->depth > 1)
+				d->tokens << new StackToken(d->reader);
+			if (d->depth == 2) {
+				QTextCursor cursor(m_ui->xmlBrowser->document());
+				cursor.movePosition(QTextCursor::End);
+				cursor.beginEditBlock();
+				QTextCharFormat zeroFormat = cursor.charFormat();
+				zeroFormat.setForeground(QColor(Qt::white));
+				QTextCharFormat bodyFormat = zeroFormat;
+				bodyFormat.setForeground(d->bodyColor);
+				QTextCharFormat tagFormat = zeroFormat;
+				tagFormat.setForeground(d->tagColor);
+				QTextCharFormat attributeFormat = zeroFormat;
+				attributeFormat.setForeground(d->attributeColor);
+				QTextCharFormat paramsFormat = zeroFormat;
+				paramsFormat.setForeground(d->paramColor);
+				QTextCharFormat bracketFormat = zeroFormat;
+				bracketFormat.setForeground(m_stackBracketsColor);
+				QString singleSpace = QLatin1String(" ");
+				cursor.insertBlock();
+				int depth = 0;
+				QString currentXmlns;
+				QXmlStreamReader::TokenType lastType = QXmlStreamReader::StartElement;
+				for (int i = 0; i < d->tokens.size(); i++) {
+					token = d->tokens.at(i);
+					if (token->type == QXmlStreamReader::StartElement) {
+						QString space = generate_stacked_space(depth);
+						cursor.insertText(QLatin1String("\n"));
+						cursor.insertText(space);
+						cursor.insertText(QLatin1String("<"), bracketFormat);
+						cursor.insertText(token->startTag.name->toString(), tagFormat);
+						const QStringRef &xmlns = *token->startTag.xmlns;
+						if (i == 0 || xmlns != currentXmlns) {
+							currentXmlns = xmlns.toString();
+							cursor.insertText(singleSpace);
+							cursor.insertText(QLatin1String("xmlns"), attributeFormat);
+							cursor.insertText(QLatin1String("="), zeroFormat);
+							cursor.insertText(QLatin1String("'"), paramsFormat);
+							cursor.insertText(currentXmlns, paramsFormat);
+							cursor.insertText(QLatin1String("'"), paramsFormat);
+						}
+						QXmlStreamAttributes *attributes = token->startTag.attributes;
+						for (int j = 0; j < attributes->count(); j++) {
+							const QXmlStreamAttribute &attr = attributes->at(j);
+							cursor.insertText(singleSpace);
+							cursor.insertText(attr.name().toString(), attributeFormat);
+							cursor.insertText(QLatin1String("="), zeroFormat);
+							cursor.insertText(QLatin1String("'"), paramsFormat);
+							cursor.insertText(attr.value().toString(), paramsFormat);
+							cursor.insertText(QLatin1String("'"), paramsFormat);
+						}
+						if (token->startTag.empty) {
+							cursor.insertText(QLatin1String("/>"), bracketFormat);
+						} else {
+							cursor.insertText(QLatin1String(">"), bracketFormat);
+							depth++;
+						}
+					} else if (token->type == QXmlStreamReader::EndElement) {
+						if (lastType == QXmlStreamReader::EndElement) {
+							QString space = generate_stacked_space(depth - 1);
+							cursor.insertText(QLatin1String("\n"));
+							cursor.insertText(space);
+						}
+						cursor.insertText(QLatin1String("</"), bracketFormat);
+						cursor.insertText(token->endTag.name->toString(), tagFormat);
+						cursor.insertText(QLatin1String(">"), bracketFormat);
+						depth--;
+					} else if (token->type == QXmlStreamReader::Characters) {
+						cursor.setCharFormat(bodyFormat);
+						QString text = token->charachters.text->toString();
+						if (text.contains(QLatin1Char('\n'))) {
+							QString space = generate_stacked_space(depth);
+							space.prepend(QLatin1Char('\n'));
+							QStringList lines = text.split(QLatin1Char('\n'));
+							for (int j = 0; j < lines.size(); j++) {
+								cursor.insertText(space);
+								cursor.insertText(lines.at(j));
+							}
+							space.chop(1);
+							cursor.insertText(space);
+						} else {
+							cursor.insertText(text);
+						}
+					}
+					lastType = token->type;
+					if (lastType == QXmlStreamReader::StartElement && token->startTag.empty)
+						lastType = QXmlStreamReader::EndElement;
+					delete token;
+				}
+				cursor.endEditBlock();
+				d->tokens.clear();
+			}
+			d->depth--;
+			break;
+		case QXmlStreamReader::Characters:
+			token = d->tokens.isEmpty() ? 0 : d->tokens.last();
+			if (token && token->type == QXmlStreamReader::StartElement && !token->startTag.empty)
+				d->tokens << new StackToken(d->reader);
+			break;
+		default:
+			break;
+		}
+	}
+//	QDebug dbg = debug() << d->reader.tokenString();
+//	if (d->reader.tokenType() == QXmlStreamReader::Invalid)
+//		dbg << d->reader.error() << d->reader.errorString();
+	if (!incoming && d->depth > 1) {
+		qFatal("outgoing depth %d on\n\"%s\"", d->depth,
+			   qPrintable(QString::fromUtf8(data, data.size())));
+	}
+}
+
+void XmlConsole::changeEvent(QEvent *e)
+{
+	QWidget::changeEvent(e);
+	switch (e->type()) {
+	case QEvent::LanguageChange:
+		m_ui->retranslateUi(this);
+		break;
+	default:
+		break;
+	}
+}
+
+void XmlConsole::onActionGroupTriggered(QAction *action)
+{
+	int type = action->data().toInt();
+	if (type >= 0x10) {
+		m_filter = (m_filter & 0xf) | type;
+		m_ui->lineEdit->setEnabled(type != 0x10);
+	} else {
+		m_filter = m_filter ^ type;
+	}
+	on_lineEdit_textChanged(m_ui->lineEdit->text());
+}
+
+void XmlConsole::on_lineEdit_textChanged(const QString &text)
+{
+	int filterType = m_filter & 0xf0;
+	JID filterJid = (filterType == ByJid) ? text : QString();
+    for (int i = 0; i < m_nodes.size(); i++) {
+		XmlNode &node = m_nodes[i];
+		bool ok = true;
+		switch (filterType) {
+		case ByXmlns:
+			ok = node.xmlns.contains(text);
+			break;
+		case ByJid:
+			ok = node.jid.full() == filterJid.full() || node.jid.bare() == filterJid.full();
+			break;
+		case ByAllAttributes:
+			ok = node.attributes.contains(text);
+			break;
+		default:
+			break;
+		}
+		ok &= bool(node.type & m_filter);
+		node.block.setVisible(ok);
+		node.block.setLineCount(ok ? node.lineCount : 0);
+		//		qDebug() << node.block.lineCount();
+	}
+	QAbstractTextDocumentLayout *layout = m_ui->xmlBrowser->document()->documentLayout();
+	Q_ASSERT(qobject_cast<QPlainTextDocumentLayout*>(layout));
+	qobject_cast<QPlainTextDocumentLayout*>(layout)->requestUpdate();
+}
+
+void XmlConsole::on_saveButton_clicked()
+{
+    QString fileName = QFileDialog::getSaveFileName(this, tr("Save XMPP log to file"),
+	                                                QString(), tr("OpenDocument Format (*.odf);;HTML file (*.html);;Plain text (*.txt)"));
+	if (!fileName.isEmpty()) {
+		QTextDocumentWriter writer(fileName);
+		writer.write(m_ui->xmlBrowser->document());
+	}
+}
diff --git a/src/sip/jreen/xmlconsole.h b/src/sip/jreen/xmlconsole.h
new file mode 100644
index 000000000..c44ab3302
--- /dev/null
+++ b/src/sip/jreen/xmlconsole.h
@@ -0,0 +1,176 @@
+/* === This file is part of Tomahawk Player - <http://tomahawk-player.org> ===
+ *
+ *   Copyright 2011, Ruslan Nigmatullin <euroelessar@ya.ru>
+ *   Copyright 2011, Dominik Schmidt <dev@dominik-schmidt.de>
+ *
+ *   Tomahawk is free software: you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation, either version 3 of the License, or
+ *   (at your option) any later version.
+ *
+ *   Tomahawk is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with Tomahawk. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef XMLCONSOLE_H
+#define XMLCONSOLE_H
+
+#include <jreen/client.h>
+#include <jreen/jid.h>
+
+#include <QWidget>
+#include <QXmlStreamReader>
+#include <QDateTime>
+#include <QTextBlock>
+
+namespace Ui {
+class XmlConsole;
+}
+
+class XmlConsole : public QWidget, public Jreen::XmlStreamHandler
+{
+    Q_OBJECT
+
+public:
+	XmlConsole(Jreen::Client* client, QWidget *parent = 0);
+	~XmlConsole();
+
+	virtual void handleStreamBegin();
+	virtual void handleStreamEnd();
+	virtual void handleIncomingData(const char *data, qint64 size);
+	virtual void handleOutgoingData(const char *data, qint64 size);
+
+protected:
+	void changeEvent(QEvent *e);
+private:
+	void stackProcess(const QByteArray &data, bool incoming);
+
+	struct XmlNode
+	{
+		enum Type
+		{
+			Iq = 1,
+			Presence = 2,
+			Message = 4,
+			Custom = 8
+		};
+		QDateTime time;
+		Type type;
+		bool incoming;
+		QSet<QString> xmlns;
+		Jreen::JID jid;
+		QSet<QString> attributes;
+		QTextBlock block;
+		int lineCount;
+	};
+	enum FilterType
+	{
+		Disabled = 0x10,
+		ByJid = 0x20,
+		ByXmlns = 0x30,
+		ByAllAttributes = 0x40
+	};
+
+	enum State
+	{
+		WaitingForStanza,
+		ReadFeatures,
+		ReadStanza,
+		ReadCustom
+	};
+
+	struct StackToken
+	{
+		StackToken(QXmlStreamReader &reader)
+		{
+			type = reader.tokenType();
+			if (type == QXmlStreamReader::StartElement) {
+				QStringRef tmp = reader.name();
+				startTag.namePointer = new QString(*tmp.string());
+				startTag.name = new QStringRef(startTag.namePointer, tmp.position(), tmp.length());
+				tmp = reader.namespaceUri();
+				startTag.xmlnsPointer = new QString(*tmp.string());
+				startTag.xmlns = new QStringRef(startTag.xmlnsPointer, tmp.position(), tmp.length());
+				startTag.attributes = new QXmlStreamAttributes(reader.attributes());
+				startTag.empty = false;
+			} else if (type == QXmlStreamReader::Characters) {
+				QStringRef tmp = reader.text();
+				charachters.textPointer = new QString(*tmp.string());
+				charachters.text = new QStringRef(charachters.textPointer, tmp.position(), tmp.length());
+			} else if (type == QXmlStreamReader::EndElement) {
+				QStringRef tmp = reader.name();
+				endTag.namePointer = new QString(*tmp.string());
+				endTag.name = new QStringRef(endTag.namePointer, tmp.position(), tmp.length());
+			}
+		}
+		~StackToken()
+		{
+			if (type == QXmlStreamReader::StartElement) {
+				delete startTag.namePointer;
+				delete startTag.name;
+				delete startTag.xmlnsPointer;
+				delete startTag.xmlns;
+				delete startTag.attributes;
+			} else if (type == QXmlStreamReader::Characters) {
+				delete charachters.textPointer;
+				delete charachters.text;
+			} else if (type == QXmlStreamReader::EndElement) {
+				delete endTag.namePointer;
+				delete endTag.name;
+			}
+		}
+
+		QXmlStreamReader::TokenType type;
+		union {
+			struct {
+				QString *namePointer;
+				QStringRef *name;
+				QString *xmlnsPointer;
+				QStringRef *xmlns;
+				QXmlStreamAttributes *attributes;
+				bool empty;
+			} startTag;
+			struct {
+				QString *textPointer;
+				QStringRef *text;
+			} charachters;
+			struct {
+				QString *namePointer;
+				QStringRef *name;
+			} endTag;
+		};
+	};
+
+	struct StackEnvironment
+	{
+		QXmlStreamReader reader;
+		State state;
+		int depth;
+		QList<StackToken*> tokens;
+		QColor bodyColor;
+		QColor tagColor;
+		QColor attributeColor;
+		QColor paramColor;
+	};
+
+	Ui::XmlConsole *m_ui;
+	Jreen::Client *m_client;
+	QList<XmlNode> m_nodes;
+	StackEnvironment m_stackIncoming;
+	StackEnvironment m_stackOutgoing;
+	QColor m_stackBracketsColor;
+	int m_filter;
+
+private slots:
+	void on_lineEdit_textChanged(const QString &);
+	void onActionGroupTriggered(QAction *action);
+	void on_saveButton_clicked();
+};
+
+
+#endif // XMLCONSOLE_H
diff --git a/src/sip/jreen/xmlconsole.ui b/src/sip/jreen/xmlconsole.ui
new file mode 100644
index 000000000..dbbe3e33a
--- /dev/null
+++ b/src/sip/jreen/xmlconsole.ui
@@ -0,0 +1,51 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>XmlConsole</class>
+ <widget class="QWidget" name="XmlConsole">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>400</width>
+    <height>300</height>
+   </rect>
+  </property>
+  <property name="windowTitle">
+   <string>Xml stream console</string>
+  </property>
+  <layout class="QGridLayout" name="gridLayout">
+   <property name="margin">
+    <number>0</number>
+   </property>
+   <property name="spacing">
+    <number>0</number>
+   </property>
+   <item row="1" column="0">
+    <widget class="QLineEdit" name="lineEdit">
+     <property name="enabled">
+      <bool>false</bool>
+     </property>
+    </widget>
+   </item>
+   <item row="1" column="1">
+    <widget class="QPushButton" name="filterButton">
+     <property name="text">
+      <string>Filter</string>
+     </property>
+    </widget>
+   </item>
+   <item row="0" column="0" colspan="3">
+    <widget class="QPlainTextEdit" name="xmlBrowser"/>
+   </item>
+   <item row="1" column="2">
+    <widget class="QPushButton" name="saveButton">
+     <property name="text">
+      <string>Save log</string>
+     </property>
+    </widget>
+   </item>
+  </layout>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>