Logo Search packages:      
Sourcecode: kmess version File versions  Download package

networkwindow.cpp

/***************************************************************************
                          networkwindow.cpp  -  description
                             -------------------
    begin                : Wed Jan 28 2005
    copyright            : (C) 2005 by Diederik van der Boor
    email                : "vdboor" --at-- "codingdomain.com"
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   This program 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 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 ***************************************************************************/

#include "../kmessdebug.h"
#include "../network/p2pmessage.h"

#include "networkwindow.h"

#include <qstylesheet.h>
#include <qregexp.h>
#include <qsize.h>
#include <qdatetime.h>
#include <qtextstream.h>

#include <qfile.h>
#include <qtextcodec.h>
#include <kfiledialog.h>
#include <kmessagebox.h>

#include <kdeversion.h>
#include <klocale.h>

#if KDE_IS_VERSION(3,2,0)
#include <ktabwidget.h>
#else
#include <qtabwidget.h>
#endif


// Declare the instance
NetworkWindow * NetworkWindow::instance_(0);


// Constructor
NetworkWindow::NetworkWindow(QWidget *parent, const char *name)
  : KDialogBase(parent, name, false, i18n("Network Window"),
                KDialogBase::Close | KDialogBase::User1 | KDialogBase::User2 | KDialogBase::User3,
                KDialogBase::Close,
                true,
                i18n("Save tab"),
                i18n("Clear tab"),
                i18n("Close tab") )
  , connectionTabs_(0)
{
#ifdef KMESS_NETWORK_WINDOW

  // Create main tab layout.
  // KTabBar is suppored as of KDE 3.2, so revert to QTabBar for KDE 3.1
  #if KDE_IS_VERSION(3,2,0)
    connectionTabs_ = new KTabWidget(this, "ConnectionTabs");
  #else
    connectionTabs_ = new QTabWidget(this, "ConnectionTabs");
  #endif

  setMainWidget(connectionTabs_);
  setInitialSize( QSize(800, 600) );

  connectionViews_.setAutoDelete(true);

#endif
}


#ifdef KMESS_NETWORK_WINDOW



  // The internal function to add a message
  void NetworkWindow::addMessage(ConnectionEntry *entry, QString html)
  {
    html = html.replace( "  ", " &nbsp;").replace( QRegExp("[\r\n] "), "\n&nbsp;");

    // Add spacer if previous message was some time ago.
    QDateTime now = QDateTime::currentDateTime();
    if( entry->lastMessage.secsTo( now ) > 20 )
    {
      entry->logView->append( "<font size=1><br></font>" );
    }

    entry->logView->append(html);
    entry->lastMessage = now;
  }



  // Add a log message to the last log entry
  void NetworkWindow::addLogMessage(QObject *connection, const QString &message)
  {
    ConnectionEntry *entry = getConnectionEntry(connection);
    QString html = "<font color='#333333'>&nbsp; " + message + "</font>\n";
    addMessage( entry, html );
  }



  // Add an incoming message to the log.
  void NetworkWindow::addIncomingMessage(QObject *connection, const QByteArray &message)
  {
    ConnectionEntry *entry = getConnectionEntry(connection);
    QString html = "<font size=1><hr></font><font color='#0033FF'>" + formatMessage(entry, true, message) + "</font>\n";
    addMessage(entry, html);
  }



  // Add an outgoing message to the log.
  void NetworkWindow::addOutgoingMessage(QObject *connection, const QByteArray &message)
  {
    ConnectionEntry *entry = getConnectionEntry(connection);
    QString html = "<font size=1><hr></font><font color='#FF3300'>" + formatMessage(entry, false, message) + "</font>\n";
    addMessage(entry, html);
  }



  // Inform the connection was closed
  void NetworkWindow::connectionClosed(QObject *connection)
  {
    // Add the message
    ConnectionEntry *entry = getConnectionEntry(connection);
    entry->logView->append("<p><font color='#C2C2C2'>the connection was closed</font><br>" + getTime() + "</p>\n");
    entry->lastMessage = QDateTime::currentDateTime();

//    // Unregister the connection, so new attempts from the same class generate a new object.
//    connectionViews_.remove(browser);
  }



  // Return a log description for a standard command.
  QString NetworkWindow::describeCommand(const QString &command)
  {
    if( command.startsWith("VER ") )
    {
      return formatDescription("Protocol version negotiation");
    }
    else if ( command.startsWith("CVR ") )
    {
      return formatDescription("Application version exchange");
    }
    else if ( command.startsWith("XFR ") )
    {
      return formatDescription("Server transfer request");
    }
    else if ( command.startsWith("USR ") )
    {
      return formatDescription("User login information");
    }
    else if ( command.startsWith("SYN ") )
    {
      return formatDescription("Contactlist sync request");
    }
    else if ( command.startsWith("CHG ") )
    {
      return formatDescription("Client status message (change of online state, client capabilities or msn object)");
    }
    else if ( command.startsWith("CHL ") )
    {
      return formatDescription("Challenge query");
    }
    else if ( command.startsWith("IRO ") )
    {
      return formatDescription("Contact already in chat");
    }
    else if ( command.startsWith("CAL ") )
    {
      return formatDescription("Calling a contact to join chat");
    }
    else if ( command.startsWith("RNG ") )
    {
      return formatDescription("Invitation for a chat");
    }
    else if ( command.startsWith("ANS ") )
    {
      return formatDescription("Chat invitation answer with user login information");
    }
    else if ( command.startsWith("JOI ") )
    {
      return formatDescription("Contact joins chat");
    }
    else if ( command.startsWith("FIL ") )
    {
      return formatDescription("Indicates the size of the entire file");
    }
    else if ( command.startsWith("TFR ") )
    {
      return formatDescription("Start of file transfer");
    }
    else
    {
//       return QString::null;
      return formatDescription( QString::null );  // always add time.
    }
  }



  // Return a log description for the direct connection preamble
  QString NetworkWindow::describeDirectConnectionPreamble(const QByteArray &message)
  {
    if( message.size() == 4 && memcmp(message.data(), "\x04" "\0\0\0", 4) == 0 )
    {
      return formatDescription("Indicates the size of the next block");
    }
    else if( message.size() == 4 && memcmp(message.data(), "foo\0", 4) == 0 )
    {
      return formatDescription("The preamble of the direct connection: foo\\0");
    }
    else if( message.size() == 8 && memcmp(message.data(), "\x04" "\0\0\0foo\0", 8) == 0 )
    {
      return formatDescription("The block size followed by the preamble of the direct connection: foo\\0");
    }
    else
    {
//       return QString::null;
      return formatDescription( QString::null );  // always add time.
    }
  }



  // Return a log description for the mime message
  QString NetworkWindow::describeMimeMessage(const QString &mimeMessage)
  {
    QRegExp rx("Content-Type: ([^\r\n; ]+)");
    if( rx.search(mimeMessage) == -1 )
    {
      return formatDescription("Message could not be parsed!");
    }

    // Return a description based on the content type
    QString contentType = rx.cap(1);


    // Notification server messages
    if( contentType == "text/x-msmsgsprofile" )
    {
      // Message with all meta-data of the profile
      return formatDescription("Profile data message");
    }
    else if( contentType == "text/x-msmsgsinitialemailnotification" )
    {
      // The initial status of the mailbox
      return formatDescription("Initial e-mail status message");
    }
    else if( contentType == "text/x-msmsgsemailnotification" )
    {
      // A new e-mail was received
      return formatDescription("New e-mail notification message");
    }
    else if( contentType == "text/x-msmsgsactivemailnotification" )
    {
      // The number of unread e-mails has changed
      return formatDescription("E-mail activity message");
    }
    else if( contentType == "application/x-msmsgssystemmessage" )
    {
      // The server is going down for maintenance
      return formatDescription("System maintenance notification");
    }
    else if( contentType == "text/x-msmsgsinitialmdatanotification" )
    {
      // Another contact sent an Offline-IM, which is received upon logging-in
      return formatDescription("Offline-IM notification or Hotmail e-mail status");
    }
    else if( contentType == "text/x-msmsgsoimnotification" )
    {
      // Another contact sent an Offline-IM while we're in invisible mode.
      return formatDescription("Offline-IM notification");
    }

    // Switchboard server messages
    if( contentType == "text/plain" )
    {
      // A standard chat message
      return formatDescription("Chat text message");
    }
    else if( contentType == "text/x-msmsgscontrol" )
    {
      // The contact is typing a message
      return formatDescription("Typing notification message");
    }
    else if( contentType == "text/x-mms-emoticon" )
    {
      // List of custom emoticons
      return formatDescription("Custom emoticon list");
    }
    else if( contentType == "text/x-mms-animemoticon" )
    {
      // List of animated custom emoticons
      return formatDescription("Custom animated emoticon list");
    }
    else if( contentType == "application/x-ms-ink" )
    {
      // Ink (drawing) message
      return formatDescription("Handwritten Ink message");
    }
    else if( contentType == "text/x-clientcaps" )
    {
      // Third party client-capabilities message
      return formatDescription("Third-party client capabilities message");
    }
    else if( contentType == "text/x-keepalive" )
    {
      // Third party keep-alive message
      return formatDescription("Third-party keep-alive message");
    }
    else if( contentType == "text/x-msmsgsinvite" )
    {
      // Old invitation method
      return formatDescription("Mime-style invitatation message");
    }
    else if( contentType == "application/x-msnmsgrp2p" )
    {
      // New invitation method
      return formatDescription("P2P-style invitation message");
    }
    else if( contentType == "text/x-msnmsgr-datacast" )
    {
      // Datacast (nudge, voice-clip)
      return formatDescription("Datacase message, nudge, wink or voice-clip");
    }


    // Unknown message type
    return formatDescription("Unknown content-type: " + contentType + "!");
  }



  // Return a log description for the message
  QString NetworkWindow::describeP2PMessage(const QByteArray &message, const int headerStart)
  {
    // Prepare to extract certain P2P header fields
    QDataStream binaryStream(message, IO_ReadOnly);
    binaryStream.setByteOrder( QDataStream::LittleEndian );

    // 0    4    8        16        24   28   32   36   40        48
    // |....|....|....|....|....|....|....|....|....|....|....|....|
    // |sid |mid |offset   |totalsize|size|flag|asid|auid|a-datasz |

    Q_UINT32 sessionId, messageId, dataSize, flags, ackSessionId, ackUniqueId;
    Q_UINT64 dataOffset, totalSize, ackDataSize;

    // Get header fields
    bool posFound = binaryStream.device()->at( headerStart );
    if( ! posFound )
    {
      return formatDescription("Message could not be parsed!");
    }

    binaryStream >> sessionId;
    binaryStream >> messageId;
    binaryStream >> dataOffset;
    binaryStream >> totalSize;
    binaryStream >> dataSize;
    binaryStream >> flags;
    binaryStream >> ackSessionId;
    binaryStream >> ackUniqueId;
    binaryStream >> ackDataSize;

    switch( flags )
    {
      case 0:
        // No flags means the contents is a string with SLP-like mime fields, used to negotiate the session.
        // datasize of 0 is not handed here, is an invalid packet.
        // KMess does parse it as ACK message for compatibility with broken clients.
        if( dataSize == 4 )
        {
          return formatDescription("P2P Data preparation message");
        }
        else if( dataOffset > 0 )
        {
          QString partName = ( dataOffset + dataSize ) >= totalSize ? "last part" : "next part";
          return formatDescription("P2P Session negotiation message, " + partName);
        }
        else if( dataSize > 4 )
        {
          int preambleStart  = headerStart + 48;
          QString slpMessage = QString::fromUtf8( message.data() + preambleStart, message.size() - preambleStart - 5 ); // last 5 is for the footer code.
          bool isTransfer    = slpMessage.contains("Content-Type: application/x-msnmsgr-trans");
          if( slpMessage.startsWith("INVITE ") )
          {
            return isTransfer ? formatDescription("P2P Transfer invitation message")
                              : formatDescription("P2P Session invititation message");
          }
          else if( slpMessage.startsWith("MSNSLP/") )
          {
            int statusCode = slpMessage.section(' ', 1, 1).toInt();
            if( statusCode == 200 )
            {
              return isTransfer ? formatDescription("P2P Transfer accept message")
                                : formatDescription("P2P Session accept message");
            }
            else if( statusCode == 404 )
            {
              return formatDescription("P2P Session error message");
            }
            else if( statusCode == 481 )
            {
              return formatDescription("P2P Session error message");
            }
            else if( statusCode == 500 )
            {
              return formatDescription("P2P Session error message");
            }
            else if( statusCode == 603 )
            {
              return isTransfer ? formatDescription("P2P Transfer decline message")
                                : formatDescription("P2P Session decline message");
            }
            else
            {
              formatDescription("P2P Session status message, type is unknown!");
            }
          }
          else if( slpMessage.startsWith("ACK ") )
          {
            return formatDescription("P2P Session acknowledgement message (meaning unknown!)");
          }
          else if( slpMessage.startsWith("BYE ") )
          {
            return formatDescription("P2P Session bye message");
          }
          else
          {
            return formatDescription("P2P Session negotiation message, type is unknown!");
          }
        }
        break;

      case 0x01:
        return formatDescription("P2P Negative ack, message with ID " + QString::number(ackSessionId) +
                                 " failed at byte " + QString::number(ackDataSize));

      case 0x02:
        if( dataSize == 4 )
        {
          return formatDescription("P2P Data preparation message");
        }
        else if ( ackDataSize == 4 )
        {
          return formatDescription("P2P Data preparation ack");
        }
        else
        {
          return formatDescription("P2P Ack message for message with"
                                   " ID "         + QString::number(ackSessionId) +
                                   " and ackSid " + QString::number(ackUniqueId));
        }

      case 0x04:
        return formatDescription("P2P Timeout message, session should terminate");

      case 0x06:
        return formatDescription("P2P Timeout message, still waiting for ACK message");

      case 0x08:
        // The logs of the official client call this a "RST" message.
        return formatDescription("P2P Reset message for message with "
                                 "ackSid " + QString::number(ackSessionId) + ", session should terminate");

      case 0x20:
        return formatDescription("P2P MsnObject data,"
                                 " bytes " + QString::number(dataOffset) + "-" + QString::number(dataOffset + dataSize) +
                                 " of " + QString::number(totalSize));

      case 0x40:
        return formatDescription("P2P Close ack, sender of message with ID " + QString::number(ackSessionId) + " aborted it's own data stream");

      case 0x80:
        return formatDescription("P2P Close ack, receiver of message with ID " + QString::number(ackSessionId) + " aborted the data stream");

      case 0x100:
        return formatDescription("P2P Direct-connection handshake");

      case 0x1000030:
        return formatDescription("P2P File data,"
                                 " bytes " + QString::number(dataOffset) + "-" + QString::number(dataOffset + dataSize) +
                                 " of " + QString::number(totalSize));
    }

    return formatDescription("P2P Message, type is unknown!");
  }



  // Format the description to be displayed
  QString NetworkWindow::formatDescription(const QString &description)
  {
    return "<br>" + getTime() + "<font color='#333333'>&nbsp; " + description + "</font>\n";
  }



  // Format the message to be displayed
  QString NetworkWindow::formatMessage(ConnectionEntry *entry, bool incoming, const QByteArray &message)
  {
    // If the connection type is unknown, auto-detect it.
    if( entry->type == TYPE_UNKNOWN )
    {
      // Try to detemine which type of connection this is.
      // TODO: As of Qt4 use QByteArray functions instead of memcmp().
      if( ( message.size() == 4 && memcmp(message.data(), "\x04" "\0\0\0",      4) == 0 )
       || ( message.size() == 8 && memcmp(message.data(), "\x04" "\0\0\0foo\0", 8) == 0 ) )
      {
        // Start of direct connection: size field of 4 + foo\0
        entry->type = TYPE_DC;
      }
      else if( message.size() >= 10 )
      {
        if( memcmp(message.data(), "VER MSNFTP", 10) == 0 )
        {
          entry->type = TYPE_FTP;
        }
        else if( memcmp(message.data(), "VER 0 MSNP", 10) == 0
              || memcmp(message.data(), "VER 1 MSNP", 10) == 0 )
        {
          entry->type = TYPE_NS;
        }
        else if( memcmp(message.data(), "USR ", 4) == 0
              || memcmp(message.data(), "ANS ", 4) == 0)
        {
          entry->type = TYPE_SB;
        }
        else if( memcmp(message.data(), "GET ",  4) == 0
              || memcmp(message.data(), "POST ", 5) == 0)
        {
          entry->type = TYPE_HTTP;
        }
      }

      // If no type is found, assume it's a standard MSN connection.
      if( entry->type == TYPE_UNKNOWN )
      {
        kdWarning() << "NetworkWindow: Could not determine connection type for message: " << message.data() << endl;
        entry->type = TYPE_NS;
      }
    }


    // Format the message.
    QString logMessage;
    switch( entry->type )
    {
      // Messages from a DC are always sent as binary
      case TYPE_DC:
        if( message.size() < 48 )
        {
          // Not a p2p message, but the length field sent in advance.
          logMessage += formatRawData(incoming, message, 0, message.size());
          logMessage += describeDirectConnectionPreamble(message);
        }
        else
        {
          // A p2p message from the direct connection
          logMessage += formatP2PMessage(incoming, message, 0);
          logMessage += describeP2PMessage(message, 0);
        }
        break;

      // After the TFR is received, the FTP connection sends raw data.
      case TYPE_FTP_RAW:
        if( message.size() == 3 )
        {
          // This is a control message
          logMessage += formatRawData(incoming, message, 0, message.size());
          if( message[0] == 0 )
          {
            logMessage += formatDescription("Indicates the size of the next block");
          }
          else if( memcmp(message.data(), "\x01" "\0\0", 3) == 0 )
          {
            logMessage += formatDescription("Cancel message");
          }
          break;
        }
        else if( message.size() == 14 && memcmp(message.data(), "BYE 16777989\r\n", 14) == 0 )
        {
          // This is the last BYE message
          entry->type = TYPE_FTP;
          // fallthrough
        }
        else
        {
          logMessage += "<font color=gray>(" + QString::number(message.size()) + " bytes of binary data)</font>";
          break;
        }

      // Assume it's a standard connection.
      default:
        // Convert the message to UTF-8
        QString utf8Message = QString::fromUtf8( message.data(), message.size() );

        // Treat Mime messages with P2P content different.
        if( utf8Message.contains("Content-Type: application/x-msnmsgrp2p") )
        {
          // Find end of Mime, start of binary P2P
          int mimeEnd = utf8Message.find("\r\n\r\n");
          if( mimeEnd != -1 )
          {
            // Extract the first utf-8 mime part, locate start of p2p header
            QString p2pMimePart = utf8Message.left(mimeEnd + 4);
            int     headerStart = p2pMimePart.utf8().size() - 1;  // convert mimeEnd index, utf-8 may have double characters

            // Add the mime part and binary p2p part
            logMessage += formatString( p2pMimePart );
            logMessage += formatP2PMessage(incoming, message, headerStart);
            logMessage += describeP2PMessage(message, headerStart);
          }
        }
        else
        {
          // Whole message is in utf-8
          // remove last <br> because KTextBrowser::append() already adds it.
          logMessage = formatString( utf8Message );
          logMessage = logMessage.replace(QRegExp("<br>$"), QString::null);

          if( entry->type == TYPE_FTP
           || entry->type == TYPE_NS
           || entry->type == TYPE_SB )
          {
            if( utf8Message.startsWith("MIME-Version:") )
            {
              logMessage += describeMimeMessage( utf8Message );
            }
            else
            {
              logMessage += describeCommand( utf8Message );
            }
          }
        }
    }

    // At a certain point, a MSNFTP connection starts sending raw data.
    // Detect this so the next formatMessage() invocation handles this.
    if( entry->type == TYPE_FTP )
    {
      if( message.size() >= 3 && memcmp(message.data(), "TFR", 3) == 0 )
      {
        entry->type = TYPE_FTP_RAW;
      }
    }

    return logMessage;
  }



  // Format a msnp2p message to be displayed.
  QString NetworkWindow::formatP2PMessage(bool incoming, const QByteArray &message, const int headerStart)
  {
    QString logMessage;

    // Prepare to extract certain P2P header fields
    QDataStream binaryStream(message, IO_ReadOnly);
    binaryStream.setByteOrder( QDataStream::LittleEndian );
    int p2pDataStart = headerStart + 48;
    bool posFound;

    // 0    4    8        16        24   28   32   36   40        48
    // |....|....|....|....|....|....|....|....|....|....|....|....|
    // |sid |mid |offset   |totalsize|size|flag|asid|auid|a-datasz |

    Q_UINT32 sessionId, messageId, dataSize, flags, ackSessionId, ackUniqueId;
    Q_UINT64 dataOffset, totalSize, ackDataSize;

    // Get header fields
    posFound = binaryStream.device()->at( headerStart ); // return value ignored.
    binaryStream >> sessionId;
    binaryStream >> messageId;
    binaryStream >> dataOffset;
    binaryStream >> totalSize;
    binaryStream >> dataSize;
    binaryStream >> flags;
    binaryStream >> ackSessionId;
    binaryStream >> ackUniqueId;
    binaryStream >> ackDataSize;

    #ifdef KMESSTEST
      ASSERT( posFound );
    #endif

    // Show header information
    logMessage += formatRawData(incoming, message, headerStart, 48, 16);

    // Add header debug details
    logMessage += "<font color='#333333'>";
    logMessage += "\n<br>session id: " + QString::number(sessionId);
    logMessage += "\n<br>message id: " + QString::number(messageId);
    logMessage += "\n<br>offset: "     + QString::number(dataOffset);
    logMessage += "\n<br>total: "      + QString::number(totalSize);
    logMessage += "\n<br>size: "       + QString::number(dataSize);
    logMessage += "\n<br>flags: 0x"    + QString::number(flags, 16);
    if( flags == 0x100 )
    {
      // For the handshake message, the last three fields are replaced with the nonce.
      logMessage += "\n<br>nonce: " + P2PMessage::extractNonce( message.data(), headerStart + 32 );
    }
    else
    {
      logMessage += "\n<br>ackSid: "  + QString::number(ackSessionId);
      logMessage += "\n<br>ackUid: "  + QString::number(ackUniqueId);
      logMessage += "\n<br>ackSize: " + QString::number(ackDataSize);
    }

    logMessage += "</font>";

    // The message a utf-8 (MSNSLP) payload if the session id is zero.
    bool hasSlpPayload = ( sessionId == 0 && dataSize > 0 );
    if( hasSlpPayload )
    {
      QString slpMessage = QString::fromUtf8( message.data() + p2pDataStart, QMIN(dataSize, message.size() - p2pDataStart));
      logMessage += "\n<br>" + formatString(slpMessage);
    }
    else
    {
      logMessage += "\n<br><font color=gray>(" + QString::number(dataSize) + " bytes of binary data)</font>";
    }

    // Add message footer
    int messageEnd     = p2pDataStart + dataSize;
    int remainingBytes = message.size() - messageEnd;
    if( hasSlpPayload && message[ messageEnd - 1 ] == 0x00 )
    {
      // Display the \0 that QString::fromUtf8() silently dropped
      logMessage += formatRawData( incoming, message, messageEnd - 1, 1 );
    }

    // Display the remaining bytes, if any.
    // This is the footer when the p2p message is sent over the SB.
    if( remainingBytes > 0 )
    {
      logMessage += "<br>" + formatRawData( incoming, message, messageEnd, remainingBytes );
    }

    return logMessage;
  }



  // Format a utf-8 string to be displayed
  QString NetworkWindow::formatRawData(bool incoming, const QByteArray &message, const int start, const int length, const int bytesPerRow)
  {
    QString logMessage;
    logMessage += "<tt><font color=";
    logMessage += (incoming? "#4747C2" : "#CC334D");
    logMessage += ">";

    for(int i = 0; i < length; i++)
    {
      // Add spacers between ints, make it span multiple rows.
      if( i > 0 )
      {
        if( i % bytesPerRow == 0 )
        {
          logMessage += "<br>";
        }
        else if( i % 4 == 0 )
        {
          logMessage += " ";
        }
      }

      // Convert to hex string
      const char *hexMap = "0123456789abcdef";
      char byte = message[start + i];
      int upper = (byte & 0xf0) >> 4;
      int lower = (byte & 0x0f);
      logMessage += hexMap[upper];
      logMessage += hexMap[lower];
    }
    logMessage += "</font></tt>";

    return logMessage;
  }



  // Format a utf-8 string to be displayed
  QString NetworkWindow::formatString(const QString &message)
  {
    // Parse any HTML characters.
    QString logMessage = QStyleSheet::escape( message );

    // Format newline characters
    logMessage = logMessage.replace("\r\n", "<tt><font color=gray>\\r\\n</font></tt><br>");
    logMessage = logMessage.replace("\n",   "<tt><font color=gray>\\n</font></tt><br>");
    logMessage = logMessage.replace("\r",   "<tt><font color=gray>\\r</font></tt><br>");

    return logMessage;
  }



  // Find the tab for the connection, or create a new one.
  NetworkWindow::ConnectionEntry * NetworkWindow::getConnectionEntry(QObject *connection)
  {
    #ifdef KMESSTEST
      ASSERT( connection != 0 );  // no KMESS_NULL(), this is debug code already.
    #endif

    // Find the entry
    ConnectionEntry *entry = connectionViews_.find( connection );

    // If the view didn't exist before, create a new one.
    if( entry == 0 )
    {
      // Create new entry
      entry = new ConnectionEntry;
      entry->logView = new KTextBrowser( connectionTabs_, QString(connection->name()) + "_log" );
      entry->logView->setTextFormat( Qt::RichText );
      entry->type        = TYPE_UNKNOWN;
      entry->lastMessage = QDateTime::currentDateTime();
      connectionViews_.insert( connection, entry );

      // Add new tab
      connectionTabs_->addTab( entry->logView, connection->name() );
    }

    return entry;
  }



  // Return the current time.
  QString NetworkWindow::getTime() const
  {
    return "<font color=black size=1>" + QTime::currentTime().toString( "hh:mm:ss:zzz" ) + "</font>";
  }



  // Set the title of the connection in the display
  void NetworkWindow::setConnectionTitle(QObject *connection, const QString &title)
  {
    ConnectionEntry *entry = getConnectionEntry(connection);

    // If empty, set the initial label. Otherwise add a IRC-like rename message
    if( entry->logView->length() == 0 )
    {
      connectionTabs_->setTabLabel( entry->logView, title );
      entry->logView->append("<p><font color='#C2C2C2'>Log started by " + QString(connection->name()) + "</font></p>");
    }
    else
    {
      entry->logView->append("<p><font color='#C2C2C2'>re-initialized as: " + title + "</font></p>");
    }
  }



  // Show the window if there are connection logs to show
  void NetworkWindow::show()
  {
    if( connectionTabs_ == 0 || connectionTabs_->count() < 1 )
    {
      KMessageBox::error( this, i18n("No connections are present. Cannot open the Network Window.") );
      return;
    }

    KDialogBase::show();
  }



  // The 'save tab' button was pressed.
  void NetworkWindow::slotUser1()
  {
    if( connectionTabs_ == 0 )
      return;

    QString path;
    QString name = connectionTabs_->tabLabel( connectionTabs_->currentPage() );

    path = KFileDialog::getSaveFileName( name + ".html" );
    if( path.isNull() )
      return;

#ifdef KMESSDEBUG_CHATWINDOW_GENERAL
    kdDebug() << "NetworkWindow - saving network log for window '" << name << "' to '" << path << "'." << endl;
#endif

    bool failed = false;

    // Create and open the file.
    QFile file( path );
    if( ! file.open( IO_WriteOnly ) )
    {
      failed = true;
      kdWarning() << "NetworkWindow: File save failed - couldn't open file." << endl;
    }

    // Output the HTML with the right text encoding
    QString text = ( (KTextBrowser *)connectionTabs_->currentPage() )->text();
    QTextStream textStream( &file );
    textStream.setCodec( QTextCodec::codecForLocale() );
    textStream << text;
    file.close();
    file.flush();

    if( failed || !file.exists() )
    {
      KMessageBox::sorry( this, i18n("Could not save the Network Window log. Make sure you have permission to write for folder where the log is being saved.") );
    }
  }



  // The 'clear tab' button was pressed.
  void NetworkWindow::slotUser2()
  {
    if( connectionTabs_ == 0 )
      return;

    ( (KTextBrowser *)connectionTabs_->currentPage() )->clear();
  }



  // The 'close tab' button was pressed.
  void NetworkWindow::slotUser3()
  {
    if( connectionTabs_ == 0 )
      return;

    if( connectionTabs_->currentPageIndex() == 0 )
    {
      KMessageBox::information( this, i18n("Cannot close the main connection tab.") );
      return;
    }

    emit connectionTabs_->removePage( connectionTabs_->currentPage() );
  }



  // Return the primary instance of the network window.
  NetworkWindow* NetworkWindow::instance()
  {
    // Create on demand
    if( instance_ == 0 )
    {
      instance_ = new NetworkWindow(0, "NetworkWindow");
    }

    // Return active instance
    return instance_;
  }


#endif // KMESS_NETWORK_WINDOW

Generated by  Doxygen 1.6.0   Back to index