/src/sip/jabber/xmlconsole.cpp

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