1
0
mirror of https://github.com/tomahawk-player/tomahawk.git synced 2025-03-24 09:49:42 +01:00

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).
This commit is contained in:
Dominik Schmidt 2011-05-10 02:21:15 +02:00
parent aeef7345af
commit 350be106c6
7 changed files with 625 additions and 11 deletions

View File

@ -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} ..

View File

@ -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

View File

@ -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()

View File

@ -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;

View File

@ -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());
}
}

176
src/sip/jreen/xmlconsole.h Normal file
View File

@ -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

View File

@ -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>