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 #include -#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 #include @@ -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 - === + * + * Copyright 2011, Ruslan Nigmatullin + * Copyright 2011, Dominik Schmidt + * + * 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 . + */ + +#include "xmlconsole.h" +#include "ui_xmlconsole.h" + + +#include +#include +#include +#include +#include +#include +#include +#include + +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("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(layout)); + qobject_cast(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 - === + * + * Copyright 2011, Ruslan Nigmatullin + * Copyright 2011, Dominik Schmidt + * + * 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 . + */ + +#ifndef XMLCONSOLE_H +#define XMLCONSOLE_H + +#include +#include + +#include +#include +#include +#include + +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 xmlns; + Jreen::JID jid; + QSet 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 tokens; + QColor bodyColor; + QColor tagColor; + QColor attributeColor; + QColor paramColor; + }; + + Ui::XmlConsole *m_ui; + Jreen::Client *m_client; + QList 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 @@ + + + XmlConsole + + + + 0 + 0 + 400 + 300 + + + + Xml stream console + + + + 0 + + + 0 + + + + + false + + + + + + + Filter + + + + + + + + + + Save log + + + + + + + +