First commit of the simple IRC client for eric. It is usable but not yet complete.

Sun, 25 Nov 2012 18:40:15 +0100

author
Detlev Offenbach <detlev@die-offenbachs.de>
date
Sun, 25 Nov 2012 18:40:15 +0100
changeset 2227
b7aceb255831
parent 2225
0139003972cd
child 2228
5c59b9393306

First commit of the simple IRC client for eric. It is usable but not yet complete.

Cooperation/ChatWidget.ui file | annotate | diff | comparison | revisions
Network/IRC/IrcChannelWidget.py file | annotate | diff | comparison | revisions
Network/IRC/IrcChannelWidget.ui file | annotate | diff | comparison | revisions
Network/IRC/IrcNetworkManager.py file | annotate | diff | comparison | revisions
Network/IRC/IrcNetworkWidget.py file | annotate | diff | comparison | revisions
Network/IRC/IrcNetworkWidget.ui file | annotate | diff | comparison | revisions
Network/IRC/IrcUtilities.py file | annotate | diff | comparison | revisions
Network/IRC/IrcWidget.py file | annotate | diff | comparison | revisions
Network/IRC/IrcWidget.ui file | annotate | diff | comparison | revisions
Network/IRC/__init__.py file | annotate | diff | comparison | revisions
Network/__init__.py file | annotate | diff | comparison | revisions
Preferences/ConfigurationDialog.py file | annotate | diff | comparison | revisions
Preferences/ConfigurationPages/IrcPage.py file | annotate | diff | comparison | revisions
Preferences/ConfigurationPages/IrcPage.ui file | annotate | diff | comparison | revisions
Preferences/__init__.py file | annotate | diff | comparison | revisions
UI/UserInterface.py file | annotate | diff | comparison | revisions
Utilities/__init__.py file | annotate | diff | comparison | revisions
Utilities/crypto/__init__.py file | annotate | diff | comparison | revisions
changelog file | annotate | diff | comparison | revisions
eric5.e4p file | annotate | diff | comparison | revisions
icons/default/irc.png file | annotate | diff | comparison | revisions
icons/default/irc128.png file | annotate | diff | comparison | revisions
icons/default/irc48.png file | annotate | diff | comparison | revisions
icons/default/ircAdmin.png file | annotate | diff | comparison | revisions
icons/default/ircAway.png file | annotate | diff | comparison | revisions
icons/default/ircCloseChannel.png file | annotate | diff | comparison | revisions
icons/default/ircConfigure.png file | annotate | diff | comparison | revisions
icons/default/ircConnect.png file | annotate | diff | comparison | revisions
icons/default/ircDisconnect.png file | annotate | diff | comparison | revisions
icons/default/ircHalfop.png file | annotate | diff | comparison | revisions
icons/default/ircJoinChannel.png file | annotate | diff | comparison | revisions
icons/default/ircNormal.png file | annotate | diff | comparison | revisions
icons/default/ircOp.png file | annotate | diff | comparison | revisions
icons/default/ircOwner.png file | annotate | diff | comparison | revisions
icons/default/ircVoice.png file | annotate | diff | comparison | revisions
--- a/Cooperation/ChatWidget.ui	Wed Nov 14 18:58:51 2012 +0100
+++ b/Cooperation/ChatWidget.ui	Sun Nov 25 18:40:15 2012 +0100
@@ -50,8 +50,8 @@
      <property name="title">
       <string>Chat</string>
      </property>
-     <layout class="QGridLayout" name="gridLayout_2">
-      <item row="0" column="0" colspan="2">
+     <layout class="QVBoxLayout" name="verticalLayout_4">
+      <item>
        <widget class="QTextEdit" name="chatEdit">
         <property name="focusPolicy">
          <enum>Qt::NoFocus</enum>
@@ -64,14 +64,14 @@
         </property>
        </widget>
       </item>
-      <item row="1" column="0">
+      <item>
        <widget class="E5ClearableLineEdit" name="messageEdit">
         <property name="toolTip">
          <string>Enter the text to send</string>
         </property>
        </widget>
       </item>
-      <item row="2" column="0" colspan="2">
+      <item>
        <widget class="QPushButton" name="sendButton">
         <property name="toolTip">
          <string>Press to send the text above</string>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Network/IRC/IrcChannelWidget.py	Sun Nov 25 18:40:15 2012 +0100
@@ -0,0 +1,637 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2012 Detlev Offenbach <detlev@die-offenbachs.de>
+#
+
+"""
+Module implementing the IRC channel widget.
+"""
+
+import re
+
+from PyQt4.QtCore import pyqtSlot, pyqtSignal, QDateTime
+from PyQt4.QtGui import QWidget, QListWidgetItem, QIcon, QPainter
+
+from E5Gui import E5MessageBox
+from E5Gui.E5Application import e5App
+
+from .Ui_IrcChannelWidget import Ui_IrcChannelWidget
+
+from .IrcUtilities import ircFilter, ircTimestamp, getChannelModesDict
+
+import Utilities
+import UI.PixmapCache
+import Preferences
+
+
+class IrcUserItem(QListWidgetItem):
+    """
+    Class implementing a list widget item containing an IRC channel user.
+    """
+    Normal = 0x00       # no privileges
+    Operator = 0x01     # channel operator
+    Voice = 0x02        # voice operator
+    Admin = 0x04        # administrator
+    Halfop = 0x08       # half operator
+    Owner = 0x10        # channel owner
+    Away = 0x80         # user away
+    
+    PrivilegeMapping = {
+        "a": Away,
+        "o": Operator,
+        "O": Owner,
+        "v": Voice,
+        
+    }
+    
+    def __init__(self, name, parent=None):
+        """
+        Constructor
+        
+        @param name string with user name and privilege prefix (string)
+        @param parent reference to the parent widget (QListWidget or QListWidgetItem)
+        """
+        super().__init__(name, parent)
+        
+        self.__privilege = IrcUserItem.Normal
+        self.__name = name
+        
+        self.__setIcon()
+    
+    def name(self):
+        """
+        Public method to get the user name.
+        
+        @return user name (string)
+        """
+        return self.__name
+    
+    def setName(self, name):
+        """
+        Public method to set a new nick name.
+        
+        @param name new nick name for the user (string)
+        """
+        self.__name = name
+        self.setText(name)
+    
+    def changePrivilege(self, privilege):
+        """
+        Public method to set or unset a user privilege.
+        
+        @param privilege privilege to set or unset (string)
+        """
+        oper = privilege[0]
+        priv = privilege[1]
+        if oper == "+":
+            if priv in IrcUserItem.PrivilegeMapping:
+                self.__privilege |= IrcUserItem.PrivilegeMapping[priv]
+        elif oper == "-":
+            if priv in IrcUserItem.PrivilegeMapping:
+                self.__privilege &= ~IrcUserItem.PrivilegeMapping[priv]
+        self.__setIcon()
+    
+    def clearPrivileges(self):
+        """
+        Public method to clear the user privileges.
+        """
+        self.__privilege = IrcUserItem.Normal
+        self.__setIcon()
+    
+    def __setIcon(self):
+        """
+        Private method to set the icon dependent on user privileges.
+        """
+        # step 1: determine the icon
+        if self.__privilege & IrcUserItem.Voice:
+            icon = UI.PixmapCache.getIcon("ircVoice.png")
+        elif self.__privilege & IrcUserItem.Owner:
+            icon = UI.PixmapCache.getIcon("ircOwner.png")
+        elif self.__privilege & IrcUserItem.Operator:
+            icon = UI.PixmapCache.getIcon("ircOp.png")
+        elif self.__privilege & IrcUserItem.Halfop:
+            icon = UI.PixmapCache.getIcon("ircHalfop.png")
+        elif self.__privilege & IrcUserItem.Admin:
+            icon = UI.PixmapCache.getIcon("ircAdmin.png")
+        else:
+            icon = UI.PixmapCache.getIcon("ircNormal.png")
+        if self.__privilege & IrcUserItem.Away:
+            icon = self.__awayIcon(icon)
+        
+        # step 2: set the icon
+        self.setIcon(icon)
+    
+    def __awayIcon(self, icon):
+        """
+        Private method to convert an icon to an away icon.
+        
+        @param icon icon to be converted (QIcon)
+        @param away icon (QIcon)
+        """
+        pix1 = icon.pixmap(16, 16)
+        pix2 = UI.PixmapCache.getPixmap("ircAway.png")
+        painter = QPainter(pix1)
+        painter.drawPixmap(0, 0, pix2)
+        painter.end()
+        return QIcon(pix1)
+
+
+class IrcChannelWidget(QWidget, Ui_IrcChannelWidget):
+    """
+    Class implementing the IRC channel widget.
+    """
+    sendData = pyqtSignal(str)
+    channelClosed = pyqtSignal(str)
+    
+    UrlRe = re.compile(r"""((?:http|ftp|https):\/\/[\w\-_]+(?:\.[\w\-_]+)+"""
+        r"""(?:[\w\-\.,@?^=%&amp;:/~\+#]*[\w\-\@?^=%&amp;/~\+#])?)""")
+    
+    JoinIndicator = "--&gt;"
+    LeaveIndicator = "&lt;--"
+    MessageIndicator = "***"
+    
+    def __init__(self, parent=None):
+        """
+        Constructor
+        
+        @param parent reference to the parent widget (QWidget)
+        """
+        super().__init__(parent)
+        self.setupUi(self)
+        
+        self.__ui = e5App().getObject("UserInterface")
+        
+        self.__name = ""
+        self.__userName = ""
+        self.__partMessage = ""
+        self.__prefixToPrivilege = {}
+        
+        self.__patterns = [
+            # :foo_!n=foo@foohost.bar.net PRIVMSG #eric-ide :some long message
+            (re.compile(r":([^!]+).*\sPRIVMSG\s([^ ]+)\s:(.*)"), self.__message),
+            # :foo_!n=foo@foohost.bar.net JOIN :#eric-ide
+            # :detlev_!~detlev@mnch-5d876cfa.pool.mediaWays.net JOIN #eric-ide
+            (re.compile(r":([^!]+)!([^ ]+)\sJOIN\s:?([^ ]+)"), self.__userJoin),
+            # :foo_!n=foo@foohost.bar.net PART #eric-ide :part message
+            (re.compile(r":([^!]+).*\sPART\s([^ ]+)\s:(.*)"), self.__userPart),
+            # :foo_!n=foo@foohost.bar.net PART #eric-ide
+            (re.compile(r":([^!]+).*\sPART\s([^ ]+)\s*"), self.__userPart),
+            # :foo_!n=foo@foohost.bar.net QUIT :quit message
+            (re.compile(r":([^!]+).*\sQUIT\s:(.*)"), self.__userQuit),
+            # :foo_!n=foo@foohost.bar.net QUIT
+            (re.compile(r":([^!]+).*\sQUIT\s*"), self.__userQuit),
+            # :foo_!n=foo@foohost.bar.net NICK :newnick
+            (re.compile(r":([^!]+).*\sNICK\s:(.*)"), self.__userNickChange),
+            # :barty!n=foo@foohost.bar.net MODE #eric-ide +o foo_
+            (re.compile(r":([^!]+).*\sMODE\s([^ ]+)\s([^ ]+)\s([^ ]+).*"),
+                self.__setUserPrivilege),
+            # :zelazny.freenode.net 324 foo_ #eric-ide +cnt
+            (re.compile(r":.*\s324\s.*\s([^ ]+)\s(.+)"), self.__channelModes),
+            # :zelazny.freenode.net 328 foo_ #eric-ide :http://www.buggeroff.com/
+            (re.compile(r":.*\s328\s.*\s([^ ]+)\s:(.+)"), self.__channelUrl),
+            # :zelazny.freenode.net 329 foo_ #eric-ide 1353001005
+            (re.compile(r":.*\s329\s.*\s([^ ]+)\s(.+)"), self.__channelCreated),
+            # :zelazny.freenode.net 332 foo_ #eric-ide :eric support channel
+            (re.compile(r":.*\s332\s.*\s([^ ]+)\s:(.*)"), self.__setTopic),
+            # :zelazny.freenode.net foo_ 333 #eric-ide foo 1353089020
+            (re.compile(r":.*\s333\s.*\s([^ ]+)\s([^ ]+)\s(\d+)"), self.__topicCreated), 
+            # :zelazny.freenode.net 353 foo_ @ #eric-ide :@user1 +user2 user3
+            (re.compile(r":.*\s353\s.*\s.\s([^ ]+)\s:(.*)"), self.__userList),
+            # :zelazny.freenode.net 366 foo_ #eric-ide :End of /NAMES list.
+            (re.compile(r":.*\s366\s.*\s([^ ]+)\s:(.*)"), self.__ignore),
+        ]
+    
+    @pyqtSlot()
+    def on_messageEdit_returnPressed(self):
+        """
+        Private slot to send a message to the channel.
+        """
+        msg = self.messageEdit.text()
+        self.messages.append(
+            '<font color="{0}">{2} <b>&lt;</b><font color="{1}">{3}</font>'
+            '<b>&gt;</b> {4}</font>'.format(
+            Preferences.getIrc("ChannelMessageColour"),
+            Preferences.getIrc("OwnNickColour"),
+            ircTimestamp(), self.__userName, Utilities.html_encode(msg)))
+        self.sendData.emit("PRIVMSG " + self.__name + " :" + msg)
+        self.messageEdit.clear()
+    
+    def requestLeave(self):
+        """
+        Public method to leave the channel.
+        """
+        ok = E5MessageBox.yesNo(self,
+            self.trUtf8("Leave IRC channel"),
+            self.trUtf8("""Do you really want to leave the IRC channel <b>{0}</b>?""")\
+                .format(self.__name))
+        if ok:
+            self.sendData.emit("PART " + self.__name + " :" + self.__partMessage)
+            self.channelClosed.emit(self.__name)
+    
+    def name(self):
+        """
+        Public method to get the name of the channel.
+        
+        @return name of the channel (string)
+        """
+        return self.__name
+    
+    def setName(self, name):
+        """
+        Public method to set the name of the channel.
+        
+        @param name of the channel (string)
+        """
+        self.__name = name
+    
+    def getUsersCount(self):
+        """
+        Public method to get the users count of the channel.
+        
+        @return users count of the channel (integer)
+        """
+        return self.usersList.count()
+    
+    def userName(self):
+        """
+        Public method to get the nick name of the user.
+        
+        @return nick name of the user (string)
+        """
+        return self.__userName
+    
+    def setUserName(self, name):
+        """
+        Public method to set the user name for the channel.
+        
+        @param name user name for the channel (string)
+        """
+        self.__userName = name.lower()
+    
+    def partMessage(self):
+        """
+        Public method to get the part message.
+        
+        @return part message (string)
+        """
+        return self.__partMessage
+    
+    def setPartMessage(self, message):
+        """
+        Public method to set the part message.
+        
+        @param message message to be used for PART messages (string)
+        """
+        self.__partMessage = message
+    
+    def handleMessage(self, line):
+        """
+        Public method to handle the message sent by the server.
+        
+        @param line server message (string)
+        @return flag indicating, if the message was handled (boolean)
+        """
+        for patternRe, patternFunc in self.__patterns:
+            match = patternRe.match(line)
+            if match is not None:
+                if patternFunc(match):
+                    return True
+        
+        return False
+    
+    def __message(self, match):
+        """
+        Private method to handle messages to the channel.
+        
+        @param match match object that matched the pattern
+        @return flag indicating whether the message was handled (boolean)
+        """
+        if match.group(2).lower() == self.__name:
+            msg = ircFilter(match.group(3))
+            self.messages.append(
+                '<font color="{0}">{2} <b>&lt;</b><font color="{1}">{3}</font>'
+                '<b>&gt;</b> {4}</font>'.format(
+                Preferences.getIrc("ChannelMessageColour"),
+                Preferences.getIrc("NickColour"),
+                ircTimestamp(), match.group(1),
+                msg))
+            if Preferences.getIrc("ShowNotifications"):
+                if Preferences.getIrc("NotifyMessage"):
+                    self.__ui.showNotification(UI.PixmapCache.getPixmap("irc48.png"),
+                        self.trUtf8("Channel Message"), msg)
+                elif Preferences.getIrc("NotifyNick") and self.__userName in msg:
+                    self.__ui.showNotification(UI.PixmapCache.getPixmap("irc48.png"),
+                        self.trUtf8("Nick mentioned"), msg)
+            return True
+        
+        return False
+    
+    def __userJoin(self, match):
+        """
+        Private method to handle a user joining the channel.
+        
+        @param match match object that matched the pattern
+        @return flag indicating whether the message was handled (boolean)
+        """
+        if match.group(3).lower() == self.__name:
+            if self.__userName != match.group(1):
+                IrcUserItem(match.group(1), self.usersList)
+                msg = self.trUtf8("{0} has joined the channel {1} ({2}).").format(
+                    match.group(1), self.__name, match.group(2))
+                self.__addManagementMessage(IrcChannelWidget.JoinIndicator, msg)
+            else:
+                msg = self.trUtf8("You have joined the channel {0} ({1}).").format(
+                    self.__name, match.group(2))
+                self.__addManagementMessage(IrcChannelWidget.JoinIndicator, msg)
+            if Preferences.getIrc("ShowNotifications") and \
+               Preferences.getIrc("NotifyJoinPart"):
+                self.__ui.showNotification(UI.PixmapCache.getPixmap("irc48.png"),
+                    self.trUtf8("Join Channel"), msg)
+            return True
+        
+        return False
+    
+    def __userPart(self, match):
+        """
+        Private method to handle a user leaving the channel.
+        
+        @param match match object that matched the pattern
+        @return flag indicating whether the message was handled (boolean)
+        """
+        if match.group(2).lower() == self.__name:
+            itm = self.__findUser(match.group(1))
+            self.usersList.takeItem(self.usersList.row(itm))
+            del itm
+            if match.lastindex == 2:
+                msg = self.trUtf8("{0} has left {1}.").format(
+                    match.group(1), self.__name)
+                self.__addManagementMessage(IrcChannelWidget.LeaveIndicator, msg)
+            else:
+                msg = self.trUtf8("{0} has left {1}: {2}.").format(
+                    match.group(1), self.__name,
+                    ircFilter(match.group(3)))
+                self.__addManagementMessage(IrcChannelWidget.LeaveIndicator, msg)
+            if Preferences.getIrc("ShowNotifications") and \
+               Preferences.getIrc("NotifyJoinPart"):
+                self.__ui.showNotification(UI.PixmapCache.getPixmap("irc48.png"),
+                    self.trUtf8("Leave Channel"), msg)
+            return True
+        
+        return False
+    
+    def __userQuit(self, match):
+        """
+        Private method to handle a user logging off the server.
+        
+        @param match match object that matched the pattern
+        @return flag indicating whether the message was handled (boolean)
+        """
+        itm = self.__findUser(match.group(1))
+        if itm:
+            self.usersList.takeItem(self.usersList.row(itm))
+            del itm
+            if match.lastindex == 1:
+                msg = self.trUtf8("{0} has quit {1}.").format(
+                    match.group(1), self.__name)
+                self.__addManagementMessage(IrcChannelWidget.MessageIndicator, msg)
+            else:
+                msg = self.trUtf8("{0} has quit {1}: {2}.").format(
+                    match.group(1), self.__name, ircFilter(match.group(2)))
+                self.__addManagementMessage(IrcChannelWidget.MessageIndicator, msg)
+            if Preferences.getIrc("ShowNotifications") and \
+               Preferences.getIrc("NotifyJoinPart"):
+                self.__ui.showNotification(UI.PixmapCache.getPixmap("irc48.png"),
+                    self.trUtf8("Quit"), msg)
+        
+        # always return False for other channels and server to process
+        return False
+    
+    def __userNickChange(self, match):
+        """
+        Private method to handle a nickname change of a user.
+        
+        @param match match object that matched the pattern
+        @return flag indicating whether the message was handled (boolean)
+        """
+        itm = self.__findUser(match.group(1))
+        if itm:
+            itm.setName(match.group(2))
+            if match.group(1) == self.__userName:
+                self.__addManagementMessage(IrcChannelWidget.MessageIndicator,
+                    self.trUtf8("You are now known as {0}.").format(
+                        match.group(2)))
+                self.__userName = match.group(2)
+            else:
+                self.__addManagementMessage(IrcChannelWidget.MessageIndicator,
+                    self.trUtf8("User {0} is now known as {1}.").format(
+                        match.group(1), match.group(2)))
+        
+        # always return False for other channels and server to process
+        return False
+    
+    def __userList(self, match):
+        """
+        Private method to handle the receipt of a list of users of the channel.
+        
+        @param match match object that matched the pattern
+        @return flag indicating whether the message was handled (boolean)
+        """
+        if match.group(1).lower() == self.__name:
+            users = match.group(2).split()
+            for user in users:
+                userPrivileges, userName = self.__extractPrivilege(user)
+                itm = self.__findUser(userName)
+                if itm is None:
+                    itm = IrcUserItem(userName, self.usersList)
+                for privilege in userPrivileges:
+                    itm.changePrivilege(privilege)
+            return True
+        
+        return False
+    
+    def __setTopic(self, match):
+        """
+        Private method to handle a topic change of the channel.
+        
+        @param match match object that matched the pattern
+        @return flag indicating whether the message was handled (boolean)
+        """
+        if match.group(1).lower() == self.__name:
+            self.topicLabel.setText(match.group(2))
+            self.__addManagementMessage(IrcChannelWidget.MessageIndicator,
+                ircFilter(self.trUtf8('The channel topic is: "{0}".').format(
+                    match.group(2))))
+            return True
+        
+        return False
+    
+    def __topicCreated(self, match):
+        """
+        Private method to handle a topic created message.
+        
+        @param match match object that matched the pattern
+        @return flag indicating whether the message was handled (boolean)
+        """
+        if match.group(1).lower() == self.__name:
+            self.__addManagementMessage(IrcChannelWidget.MessageIndicator,
+                self.trUtf8("The topic was set by {0} on {1}.").format(
+                    match.group(2), QDateTime.fromTime_t(int(match.group(3)))\
+                                    .toString("yyyy-MM-dd hh:mm")))
+            return True
+        
+        return False
+    
+    def __channelUrl(self, match):
+        """
+        Private method to handle a channel URL message.
+        
+        @param match match object that matched the pattern
+        @return flag indicating whether the message was handled (boolean)
+        """
+        if match.group(1).lower() == self.__name:
+            self.__addManagementMessage(IrcChannelWidget.MessageIndicator,
+                ircFilter(self.trUtf8("Channel URL: {0}").format(match.group(2))))
+            return True
+        
+        return False
+    
+    def __channelModes(self, match):
+        """
+        Private method to handle a message reporting the channel modes.
+        
+        @param match match object that matched the pattern
+        @return flag indicating whether the message was handled (boolean)
+        """
+        if match.group(1).lower() == self.__name:
+            modesDict = getChannelModesDict()
+            modesParameters = match.group(2).split()
+            modeString = modesParameters.pop(0)
+            modes = []
+            for modeChar in modeString:
+                if modeChar == "+":
+                    continue
+                elif modeChar == "k":
+                    parameter = modesParameters.pop(0)
+                    modes.append(
+                        self.trUtf8("password protected ({0})").format(parameter))
+                elif modeChar == "l":
+                    parameter = modesParameters.pop(0)
+                    modes.append(
+                        self.trUtf8("limited to %n user(s)", "", int(parameter)))
+                elif modeChar in modesDict:
+                    modes.append(modesDict[modeChar])
+                else:
+                    modes.append(modeChar)
+            
+            self.__addManagementMessage(IrcChannelWidget.MessageIndicator,
+                self.trUtf8("Channel modes: {0}.").format(", ".join(modes)))
+            
+            return True
+        
+        return False
+    
+    def __channelCreated(self, match):
+        """
+        Private method to handle a channel created message.
+        
+        @param match match object that matched the pattern
+        @return flag indicating whether the message was handled (boolean)
+        """
+        if match.group(1).lower() == self.__name:
+            self.__addManagementMessage(IrcChannelWidget.MessageIndicator,
+                self.trUtf8("This channel was created on {0}.").format(
+                    QDateTime.fromTime_t(int(match.group(2)))\
+                        .toString("yyyy-MM-dd hh:mm")))
+            return True
+        
+        return False
+    
+    def __setUserPrivilege(self, match):
+        """
+        Private method to handle a change of user privileges for the channel.
+        
+        @param match match object that matched the pattern
+        @return flag indicating whether the message was handled (boolean)
+        """
+        if match.group(2).lower() == self.__name:
+            itm = self.__findUser(match.group(4))
+            if itm:
+                itm.changePrivilege(match.group(3))
+                self.__addManagementMessage(IrcChannelWidget.MessageIndicator,
+                    self.trUtf8("{0} sets mode for {1}: {2}.").format(
+                        match.group(1), match.group(4), match.group(3)))
+            return True
+        
+        return False
+    
+    def __ignore(self, match):
+        """
+        Private method to handle a channel message we are not interested in.
+        
+        @param match match object that matched the pattern
+        @return flag indicating whether the message was handled (boolean)
+        """
+        if match.group(1).lower() == self.__name:
+            return True
+        
+        return False
+    
+    def setUserPrivilegePrefix(self, prefixes):
+        """
+        Public method to set the user privilege to prefix mapping.
+        
+        @param prefixes dictionary with privilege as key and prefix as value
+        """
+        self.__prefixToPrivilege = {}
+        for privilege, prefix in prefixes.items():
+            if prefix:
+                self.__prefixToPrivilege[prefix] = privilege
+    
+    def __findUser(self, name):
+        """
+        Private method to find the user in the list of users.
+        
+        @param name user name to search for (string)
+        @return reference to the list entry (QListWidgetItem)
+        """
+        for row in range(self.usersList.count()):
+            itm = self.usersList.item(row)
+            if itm.name() == name:
+                return itm
+        
+        return None
+    
+    def __extractPrivilege(self, name):
+        """
+        Private method to extract the user privileges out of the name.
+        
+        @param name user name and prefixes (string)
+        return list of privileges and user name (list of string, string)
+        """
+        privileges = []
+        while name[0] in self.__prefixToPrivilege:
+            prefix = name[0]
+            privileges.append(self.__prefixToPrivilege[prefix])
+            name = name[1:]
+            if name[0] == ",":
+                name = name[1:]
+        
+        return privileges, name
+    
+    def __addManagementMessage(self, indicator, message):
+        """
+        Private method to add a channel management message to the list.
+        
+        @param indicator indicator to be shown (string)
+        @param message message to be shown (string)
+        @keyparam isLocal flag indicating a message related to the local user (boolean)
+        """
+        if indicator == self.JoinIndicator:
+            color = Preferences.getIrc("JoinChannelColour")
+        elif indicator == self.LeaveIndicator:
+            color = Preferences.getIrc("LeaveChannelColour")
+        else:
+            color = Preferences.getIrc("ChannelInfoColour")
+        self.messages.append(
+            '<font color="{0}">{1} <b>[</b>{2}<b>]</b> {3}</font>'.format(
+            color, ircTimestamp(), indicator, message))
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Network/IRC/IrcChannelWidget.ui	Sun Nov 25 18:40:15 2012 +0100
@@ -0,0 +1,91 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>IrcChannelWidget</class>
+ <widget class="QWidget" name="IrcChannelWidget">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>400</width>
+    <height>685</height>
+   </rect>
+  </property>
+  <property name="windowTitle">
+   <string/>
+  </property>
+  <layout class="QVBoxLayout" name="verticalLayout">
+   <item>
+    <widget class="QLabel" name="topicLabel">
+     <property name="text">
+      <string/>
+     </property>
+     <property name="wordWrap">
+      <bool>true</bool>
+     </property>
+     <property name="openExternalLinks">
+      <bool>true</bool>
+     </property>
+    </widget>
+   </item>
+   <item>
+    <widget class="QSplitter" name="splitter">
+     <property name="orientation">
+      <enum>Qt::Vertical</enum>
+     </property>
+     <widget class="QListWidget" name="usersList">
+      <property name="toolTip">
+       <string>Shows the list of users</string>
+      </property>
+      <property name="alternatingRowColors">
+       <bool>true</bool>
+      </property>
+      <property name="sortingEnabled">
+       <bool>true</bool>
+      </property>
+     </widget>
+     <widget class="QTextBrowser" name="messages">
+      <property name="sizePolicy">
+       <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+        <horstretch>0</horstretch>
+        <verstretch>1</verstretch>
+       </sizepolicy>
+      </property>
+      <property name="toolTip">
+       <string>Shows the channel messages</string>
+      </property>
+      <property name="tabChangesFocus">
+       <bool>true</bool>
+      </property>
+      <property name="openExternalLinks">
+       <bool>true</bool>
+      </property>
+     </widget>
+    </widget>
+   </item>
+   <item>
+    <widget class="E5ClearableLineEdit" name="messageEdit">
+     <property name="toolTip">
+      <string>Enter a message, send by pressing Return or Enter</string>
+     </property>
+     <property name="placeholderText">
+      <string>Enter a message, send by pressing Return or Enter</string>
+     </property>
+    </widget>
+   </item>
+  </layout>
+ </widget>
+ <customwidgets>
+  <customwidget>
+   <class>E5ClearableLineEdit</class>
+   <extends>QLineEdit</extends>
+   <header>E5Gui/E5LineEdit.h</header>
+  </customwidget>
+ </customwidgets>
+ <tabstops>
+  <tabstop>usersList</tabstop>
+  <tabstop>messages</tabstop>
+  <tabstop>messageEdit</tabstop>
+ </tabstops>
+ <resources/>
+ <connections/>
+</ui>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Network/IRC/IrcNetworkManager.py	Sun Nov 25 18:40:15 2012 +0100
@@ -0,0 +1,621 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2012 Detlev Offenbach <detlev@die-offenbachs.de>
+#
+
+"""
+Module implementing the IRC data structures and their manager.
+"""
+
+from PyQt4.QtCore import pyqtSignal, QObject
+
+import Utilities
+from Utilities.AutoSaver import AutoSaver
+from Utilities.crypto import pwConvert
+import Preferences
+
+
+class IrcIdentity(QObject):
+    """
+    Class implementing the IRC identity object.
+    """
+    def __init__(self, name, parent=None):
+        """
+        Constructor
+        
+        @param name name of the identity (string)
+        @param parent reference to the parent object (QObject)
+        """
+        super().__init__(parent)
+        
+        self.__name = name
+        self.__realName = ""
+        self.__nickNames = []
+        self.__serviceName = ""
+        self.__password = ""
+    
+    def save(self, settings):
+        """
+        Public method to save the identity data.
+        
+        @param settings reference to the settings object (QSettings)
+        """
+        # no need to save the name because that is the group key
+        settings.setValue("RealName", self.__realName)
+        settings.setValue("NickNames", self.__nickNames)
+        settings.setValue("ServiceName", self.__serviceName)
+        settings.setValue("Password", self.__password)
+    
+    def load(self, settings):
+        """
+        Public method to load the identity data.
+        
+        @param settings reference to the settings object (QSettings)
+        """
+        self.__realName = settings.value("RealName", "")
+        self.__nickNames = Preferences.toList(settings.value("NickNames"), [])
+        self.__serviceName = settings.value("ServiceName", "")
+        self.__password = settings.value("Password", "")
+    
+    def getName(self):
+        """
+        Public method to get the identity name.
+        
+        @return identity name (string)
+        """
+        return self.__name
+    
+    def setRealName(self, name):
+        """
+        Public method to set the real name of the identity.
+        
+        @param name real name (string)
+        """
+        self.__realName = name
+    
+    def getRealName(self):
+        """
+        Public method to get the real name.
+        
+        @return real name (string)
+        """
+        return self.__realName
+    
+    def setNickNames(self, names):
+        """
+        Public method to set the nick names of the identity.
+        
+        @param name nick names (list of string)
+        """
+        self.__nickNames = names[:]
+    
+    def getNickNames(self):
+        """
+        Public method to get the nick names.
+        
+        @return nick names (list of string)
+        """
+        return self.__nickNames
+    
+    def setServiceName(self, name):
+        """
+        Public method to set the service name of the identity used for identification.
+        
+        @param name service name (string)
+        """
+        self.__serviceName = name
+    
+    def getServiceName(self):
+        """
+        Public method to get the service name of the identity used for identification.
+        
+        @return service name (string)
+        """
+        return self.__serviceName
+    
+    def setPassword(self, password):
+        """
+        Public method to set a new password.
+        
+        @param password password to set (string)
+        """
+        self.__password = pwConvert(password, encode=True)
+    
+    def getPassword(self):
+        """
+        Public method to get the password.
+        
+        @return password (string)
+        """
+        return pwConvert(self.__password, encode=False)
+
+
+class IrcServer(QObject):
+    """
+    Class implementing the IRC identity object.
+    """
+    DefaultPort = 6667
+    
+    def __init__(self, name, parent=None):
+        """
+        Constructor
+        
+        @param name name of the server (string)
+        @param parent reference to the parent object (QObject)
+        """
+        super().__init__(parent)
+        
+        self.__server = name
+        self.__port = IrcServer.DefaultPort
+        self.__ssl = False
+        self.__password = ""
+    
+    def save(self, settings):
+        """
+        Public method to save the server data.
+        
+        @param settings reference to the settings object (QSettings)
+        """
+        # no need to save the server name because that is the group key
+        settings.setValue("Port", self.__port)
+        settings.setValue("SSL", self.__ssl)
+        settings.setValue("Password", self.__password)
+    
+    def load(self, settings):
+        """
+        Public method to load the server data.
+        
+        @param settings reference to the settings object (QSettings)
+        """
+        self.__port = int(settings.value("Port", IrcServer.DefaultPort))
+        self.__ssl = Preferences.toBool(settings.value("SSL", False))
+        self.__password = settings.value("Password", "")
+    
+    def getServer(self):
+        """
+        Public method to get the server name.
+        
+        @return server name (string)
+        """
+        return self.__server
+    
+    def getPort(self):
+        """
+        Public method to get the server port number.
+        
+        @return port number (integer)
+        """
+        return self.__port
+    
+    def setPort(self, port):
+        """
+        Public method to set the server port number.
+        
+        @param server port number (integer)
+        """
+        self.__port = port
+    
+    def useSSL(self):
+        """
+        Public method to check for SSL usage.
+        
+        @return flag indicating SSL usage (boolean)
+        """
+        return self.__ssl
+    
+    def setUseSSL(self, on):
+        """
+        Public method to set the SSL usage.
+        
+        @param on flag indicating SSL usage (boolean)
+        """
+        self.__ssl = on
+    
+    def setPassword(self, password):
+        """
+        Public method to set a new password.
+        
+        @param password password to set (string)
+        """
+        self.__password = pwConvert(password, encode=True)
+    
+    def getPassword(self):
+        """
+        Public method to get the password.
+        
+        @return password (string)
+        """
+        return pwConvert(self.__password, encode=False)
+
+
+class IrcNetwork(QObject):
+    """
+    Class implementing the IRC identity object.
+    """
+    def __init__(self, name, parent=None):
+        """
+        Constructor
+        
+        @param name name of the network (string)
+        @param parent reference to the parent object (QObject)
+        """
+        super().__init__(parent)
+        
+        self.__name = name
+        self.__identity = ""
+        self.__server = ""
+        self.__channels = []
+        self.__autoJoinChannels = False
+    
+    def save(self, settings):
+        """
+        Public method to save the network data.
+        
+        @param settings reference to the settings object (QSettings)
+        """
+        # no need to save the network name because that is the group key
+        settings.setValue("Identity", self.__identity)
+        settings.setValue("Server", self.__server)
+        settings.setValue("Channels", self.__channels)
+        settings.setValue("AutoJoinChannels", self.__autoJoinChannels)
+    
+    def load(self, settings):
+        """
+        Public method to load the network data.
+        
+        @param settings reference to the settings object (QSettings)
+        """
+        self.__identity = settings.value("Identity", "")
+        self.__server = settings.value("Server", "")
+        self.__channels = Preferences.toList(settings.value("Channels", []))
+        self.__autoJoinChannels = Preferences.toBool(
+            settings.value("AutoJoinChannels", False))
+    
+    def getName(self):
+        """
+        Public method to get the network name.
+        
+        @return network name (string)
+        """
+        return self.__name
+    
+    def setIdentityName(self, name):
+        """
+        Public method to set the name of the identity.
+        
+        @param name identity name (string)
+        """
+        self.__identity = name
+    
+    def getIdentityName(self):
+        """
+        Public method to get the name of the identity.
+        
+        @return identity name (string)
+        """
+        return self.__identity
+    
+    def setServerName(self, name):
+        """
+        Public method to set the server name.
+        
+        @param name server name (string)
+        """
+        self.__server = name
+    
+    def getServerName(self):
+        """
+        Public method to get the server name.
+        
+        @return server name (string)
+        """
+        return self.__server
+    
+    def setChannels(self, channels):
+        """
+        Public method to set the list of channels.
+        
+        @param channels list of channels (list of string)
+        """
+        self.__channels = channels[:]
+    
+    def getChannels(self):
+        """
+        Public method to get the list of channels.
+        
+        @return list of channels (list of string)
+        """
+        return self.__channels[:]
+    
+    def setAutoJoinChannels(self, on):
+        """
+        Public method to enable channel auto joining.
+        
+        @param on flag indicating to join the channels after connecting
+            to the server (boolean)
+        """
+        self.__autoJoinChannels = on
+    
+    def autoJoinChannels(self):
+        """
+        Public method to check, if channel auto joining is enabled.
+        
+        @return flag indicating to join the channels after connecting
+            to the server (boolean)
+        """
+        return self.__autoJoinChannels
+
+
+class IrcNetworkManager(QObject):
+    """
+    Class implementing the IRC identity object.
+    """
+    dataChanged = pyqtSignal()
+    
+    def __init__(self, parent=None):
+        """
+        Constructor
+        
+        @param parent reference to the parent object (QObject)
+        """
+        super().__init__(parent)
+        
+        self.__loaded = False
+        self.__saveTimer = AutoSaver(self, self.save)
+        
+        self.__settings = Preferences.Prefs.settings
+        
+        self.__networks = {}
+        self.__identities = {}
+        self.__servers = {}
+        
+        self.dataChanged.connect(self.__saveTimer.changeOccurred)
+    
+    def close(self):
+        """
+        Public method to close the open search engines manager.
+        """
+        self.__saveTimer.saveIfNeccessary()
+    
+    def save(self):
+        """
+        Public slot to save the IRC data.
+        """
+        if not self.__loaded:
+            return
+        
+        # save IRC data
+        self.__settings.beginGroup("IRC")
+        
+        # identities
+        self.__settings.beginGroup("Identities")
+        for key in self.__identities:
+            self.__settings.beginGroup(key)
+            self.__identities[key].save(self.__settings)
+            self.__settings.endGroup()
+        self.__settings.endGroup()
+        
+        # servers
+        self.__settings.beginGroup("Servers")
+        for key in self.__servers:
+            self.__settings.beginGroup(key)
+            self.__servers[key].save(self.__settings)
+            self.__settings.endGroup()
+        self.__settings.endGroup()
+        
+        # networks
+        self.__settings.beginGroup("Networks")
+        for key in self.__networks:
+            self.__settings.beginGroup(key)
+            self.__networks[key].save(self.__settings)
+            self.__settings.endGroup()
+        self.__settings.endGroup()
+        
+        self.__settings.endGroup()
+    
+    def __load(self):
+        """
+        Private slot to load the IRC data.
+        """
+        if self.__loaded:
+            return
+        
+        # load IRC data
+        self.__settings.beginGroup("IRC")
+        
+        # identities
+        self.__settings.beginGroup("Identities")
+        for key in self.__settings.childKeys():
+            self.__identities[key] = IrcIdentity(key, self)
+            self.__settings.beginGroup(key)
+            self.__identities[key].load(self.__settings)
+            self.__settings.endGroup()
+        self.__settings.endGroup()
+        
+        # servers
+        self.__settings.beginGroup("Servers")
+        for key in self.__settings.childKeys():
+            self.__servers[key] = IrcServer(key, self)
+            self.__settings.beginGroup(key)
+            self.__servers[key].load(self.__settings)
+            self.__settings.endGroup()
+        self.__settings.endGroup()
+        
+        # networks
+        self.__settings.beginGroup("Networks")
+        for key in self.__settings.childKeys():
+            self.__networks[key] = IrcNetwork(key, self)
+            self.__settings.beginGroup(key)
+            self.__networks[key].load(self.__settings)
+            self.__settings.endGroup()
+        self.__settings.endGroup()
+        
+        self.__settings.endGroup()
+        
+        if not self.__identities or \
+           not self.__servers or \
+           not self.__networks:
+            # data structures got corrupted; load defaults
+            self.__loadDefaults()
+        
+        self.__loaded = True
+    
+    def __loadDefaults(self):
+        """
+        Private method to load default values.
+        """
+        self.__networks = {}
+        self.__identities = {}
+        self.__servers = {}
+        
+        # identity
+        userName = Utilities.getUserName()
+        identity = IrcIdentity(userName, self)
+        identity.setNickNames([userName, userName + "_", userName + "__"])
+        self.__identities[userName] = identity
+        
+        # server
+        serverName = "chat.freenode.net"
+        server = IrcServer(serverName, self)
+        server.setPort(8001)
+        self.__servers[serverName] = server
+        
+        # network
+        networkName = "Freenode"
+        network = IrcNetwork(networkName, self)
+        network.setIdentityName(userName)
+        network.setServerName(serverName)
+        network.setChannels(["#eric-ide"])
+        self.__networks[networkName] = network
+        
+        self.dataChanged.emit()
+    
+    def getIdentity(self, name, create=False):
+        """
+        Public method to get an identity object.
+        
+        @param name name of the identity to get (string)
+        @param create flag indicating to create a new object,
+            if none exists (boolean)
+        @return reference to the identity (IrcIdentity)
+        """
+        if not name:
+            return None
+        
+        if not self.__loaded:
+            self.__load()
+        
+        if name in self.__identities:
+            return self.__identities[name]
+        elif create:
+            id = IrcIdentity(name, self)
+            self.__identities[name] = id
+            
+            self.dataChanged.emit()
+            
+            return id
+        else:
+            return None
+    
+    def identityChanged(self):
+        """
+        Public method to indicate a change of an identity object.
+        """
+        self.dataChanged.emit()
+    
+    def getServer(self, name, create=False):
+        """
+        Public method to get a server object.
+        
+        @param name name of the server to get (string)
+        @param create flag indicating to create a new object,
+            if none exists (boolean)
+        @return reference to the server (IrcServer)
+        """
+        if not name:
+            return None
+        
+        if not self.__loaded:
+            self.__load()
+        
+        if name in self.__servers:
+            return self.__servers[name]
+        elif create:
+            server = IrcServer(name, self)
+            self.__servers[name] = server
+            
+            self.dataChanged.emit()
+            
+            return server
+        else:
+            return None
+    
+    def serverChanged(self):
+        """
+        Public method to indicate a change of a server object.
+        """
+        self.dataChanged.emit()
+    
+    def getNetwork(self, name):
+        """
+        Public method to get a network object.
+        
+        @param name name of the network (string)
+        @return reference to the network object (IrcNetwork)
+        """
+        if not self.__loaded:
+            self.__load()
+        
+        if name in self.__networks:
+            return self.__networks[name]
+        else:
+            return None
+    
+    def createNetwork(self, name, identity, server, channels=None,
+                      autoJoinChannels=False):
+        """
+        Public method to create a new network object.
+        
+        @param name name of the network (string)
+        @param identity reference to an identity object to associate with
+            this network (IrcIdentity)
+        @param server reference to a server object to associate with this
+            network (IrcServer)
+        @param channels list of channels for the network (list of string)
+        @param autoJoinChannels flag indicating to join the channels
+            automatically (boolean)
+        @return reference to the created network object (IrcNetwork)
+        """
+        if not self.__loaded:
+            self.__load()
+        
+        if name in self.__networks:
+            return None
+        
+        network = IrcNetwork(name)
+        network.setIdentityName(identity.getName())
+        network.setServerName(server.getServer())
+        network.setChannels(channels[:])
+        network.setAutoJoinChannels(autoJoinChannels)
+        self.__networks[name] = network
+        
+        self.networkChanged()
+        
+        return network
+    
+    def networkChanged(self):
+        """
+        Public method to indicate a change of a network object.
+        """
+        self.dataChanged.emit()
+    
+    def getNetworkNames(self):
+        """
+        Public method to get a list of all known network names.
+        
+        @return list of network names (list of string)
+        """
+        if not self.__loaded:
+            self.__load()
+        
+        return sorted(self.__networks.keys())
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Network/IRC/IrcNetworkWidget.py	Sun Nov 25 18:40:15 2012 +0100
@@ -0,0 +1,200 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2012 Detlev Offenbach <detlev@die-offenbachs.de>
+#
+
+"""
+Module implementing the network part of the IRC widget.
+"""
+
+from PyQt4.QtCore import pyqtSlot, pyqtSignal
+from PyQt4.QtGui import QWidget
+
+from .Ui_IrcNetworkWidget import Ui_IrcNetworkWidget
+
+from .IrcUtilities import ircFilter, ircTimestamp
+
+import UI.PixmapCache
+import Preferences
+
+
+class IrcNetworkWidget(QWidget, Ui_IrcNetworkWidget):
+    """
+    Class implementing the network part of the IRC widget.
+    
+    @signal connectNetwork(str,bool) emitted to connect or disconnect from a network
+    @signal editNetwork(str) emitted to edit a network configuration
+    @signal joinChannel(str) emitted to join a channel
+    @signal nickChanged(str) emitted to change the nick name
+    """
+    connectNetwork = pyqtSignal(str, bool)
+    editNetwork = pyqtSignal(str)
+    joinChannel = pyqtSignal(str)
+    nickChanged = pyqtSignal(str)
+    
+    
+    def __init__(self, parent=None):
+        """
+        Constructor
+        
+        @param parent reference to the parent widget (QWidget)
+        """
+        super().__init__(parent)
+        self.setupUi(self)
+        
+        self.connectButton.setIcon(UI.PixmapCache.getIcon("ircConnect.png"))
+        self.editButton.setIcon(UI.PixmapCache.getIcon("ircConfigure.png"))
+        self.joinButton.setIcon(UI.PixmapCache.getIcon("ircJoinChannel.png"))
+        
+        self.__manager = None
+        self.__connected = False
+    
+    def initialize(self, manager):
+        """
+        Public method to initialize the widget.
+        
+        @param manager reference to the network manager (IrcNetworkManager)
+        """
+        self.__manager = manager
+        
+        self.networkCombo.addItems(self.__manager.getNetworkNames())
+    
+    @pyqtSlot()
+    def on_connectButton_clicked(self):
+        """
+        Private slot to connect to a network.
+        """
+        network = self.networkCombo.currentText()
+        self.connectNetwork.emit(network, not self.__connected)
+    
+    @pyqtSlot()
+    def on_editButton_clicked(self):
+        """
+        Private slot to edit a network.
+        """
+        network = self.networkCombo.currentText()
+        self.editNetwork.emit(network)
+    
+    @pyqtSlot(str)
+    def on_channelCombo_editTextChanged(self, txt):
+        """
+        Private slot to react upon changes of the channel.
+        
+        @param txt current text of the channel combo (string)
+        """
+        on = bool(txt)
+        self.joinButton.setEnabled(on)
+    
+    @pyqtSlot()
+    def on_joinButton_clicked(self):
+        """
+        Private slot to join a channel.
+        """
+        channel = self.channelCombo.currentText()
+        self.joinChannel.emit(channel)
+    
+    @pyqtSlot(str)
+    def on_networkCombo_currentIndexChanged(self, networkName):
+        """
+        Private slot to handle selections of a network.
+        
+        @param networkName selected network name (string)
+        """
+        network = self.__manager.getNetwork(networkName)
+        self.channelCombo.clear()
+        if network:
+            channels = network.getChannels()
+            self.channelCombo.addItems(channels)
+            self.channelCombo.setEnabled(True)
+            identity = self.__manager.getIdentity(
+                network.getIdentityName())
+            if identity:
+                self.nickCombo.addItems(identity.getNickNames())
+        else:
+            self.channelCombo.setEnabled(False)
+    
+    @pyqtSlot(str)
+    def on_nickCombo_activated(self, nick):
+        """
+        Private slot to use another nick name.
+        
+        @param nick nick name to use (string)
+        """
+        if self.__connected:
+            self.nickChanged.emit(self.nickCombo.currentText())
+    
+    def getNickname(self):
+        """
+        Public method to get the currently selected nick name.
+        
+        @return selected nick name (string)
+        """
+        return self.nickCombo.currentText()
+    
+    def setNickName(self, nick):
+        """
+        Public slot to set the nick name in use.
+        
+        @param nick nick name in use (string)
+        """
+        self.nickCombo.blockSignals(True)
+        self.nickCombo.setEditText(nick)
+        self.nickCombo.blockSignals(False)
+    
+    def addMessage(self, msg):
+        """
+        Public method to add a message.
+        
+        @param msg message to be added (string)
+        """
+        s = '<font color="{0}">{1} {2}</font>'.format(
+            Preferences.getIrc("NetworkMessageColour"),
+            ircTimestamp(),
+            msg
+        )
+        self.messages.append(s)
+    
+    def addServerMessage(self, msgType, msg, filterMsg=True):
+        """
+        Public method to add a server message.
+        
+        @param msgType txpe of the message (string)
+        @param msg message to be added (string)
+        @keyparam filterMsg flag indicating to filter the message (boolean)
+        """
+        if filterMsg:
+            msg = ircFilter(msg)
+        s = '<font color="{0}">{1} <b>[</b>{2}<b>]</b> {3}</font>'.format(
+            Preferences.getIrc("ServerMessageColour"),
+            ircTimestamp(),
+            msgType,
+            msg
+        )
+        self.messages.append(s)
+    
+    def addErrorMessage(self, msgType, msg):
+        """
+        Public method to add an error message.
+        
+        @param msgType txpe of the message (string)
+        @param msg message to be added (string)
+        """
+        s = '<font color="{0}">{1} <b>[</b>{2}<b>]</b> {3}</font>'.format(
+            Preferences.getIrc("ErrorMessageColour"),
+            ircTimestamp(),
+            msgType,
+            msg
+        )
+        self.messages.append(s)
+    
+    def setConnected(self, connected):
+        """
+        Public slot to set the connection state.
+        
+        @param connected flag indicating the connection state (boolean)
+        """
+        self.__connected = connected
+        if self.__connected:
+            self.connectButton.setIcon(UI.PixmapCache.getIcon("ircDisconnect.png"))
+        else:
+            self.connectButton.setIcon(UI.PixmapCache.getIcon("ircConnect.png"))
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Network/IRC/IrcNetworkWidget.ui	Sun Nov 25 18:40:15 2012 +0100
@@ -0,0 +1,133 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>IrcNetworkWidget</class>
+ <widget class="QWidget" name="IrcNetworkWidget">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>400</width>
+    <height>322</height>
+   </rect>
+  </property>
+  <property name="windowTitle">
+   <string/>
+  </property>
+  <layout class="QVBoxLayout" name="verticalLayout">
+   <item>
+    <widget class="QTextBrowser" name="messages">
+     <property name="toolTip">
+      <string>Shows the network messages</string>
+     </property>
+     <property name="tabChangesFocus">
+      <bool>true</bool>
+     </property>
+     <property name="openExternalLinks">
+      <bool>true</bool>
+     </property>
+    </widget>
+   </item>
+   <item>
+    <layout class="QHBoxLayout" name="horizontalLayout_2">
+     <item>
+      <widget class="QComboBox" name="networkCombo">
+       <property name="toolTip">
+        <string>Select a network to connect to</string>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <widget class="QToolButton" name="connectButton">
+       <property name="toolTip">
+        <string>Press to connect to the selected network</string>
+       </property>
+       <property name="text">
+        <string/>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <widget class="QToolButton" name="editButton">
+       <property name="toolTip">
+        <string>Press to edit the networks</string>
+       </property>
+       <property name="text">
+        <string/>
+       </property>
+      </widget>
+     </item>
+    </layout>
+   </item>
+   <item>
+    <layout class="QHBoxLayout" name="horizontalLayout">
+     <item>
+      <widget class="QComboBox" name="nickCombo">
+       <property name="sizePolicy">
+        <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
+         <horstretch>0</horstretch>
+         <verstretch>0</verstretch>
+        </sizepolicy>
+       </property>
+       <property name="toolTip">
+        <string>Select a nick name for the channel</string>
+       </property>
+       <property name="editable">
+        <bool>true</bool>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <widget class="Line" name="line">
+       <property name="orientation">
+        <enum>Qt::Vertical</enum>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <widget class="QComboBox" name="channelCombo">
+       <property name="enabled">
+        <bool>false</bool>
+       </property>
+       <property name="sizePolicy">
+        <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
+         <horstretch>0</horstretch>
+         <verstretch>0</verstretch>
+        </sizepolicy>
+       </property>
+       <property name="toolTip">
+        <string>Enter the channel to join</string>
+       </property>
+       <property name="editable">
+        <bool>true</bool>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <widget class="QToolButton" name="joinButton">
+       <property name="enabled">
+        <bool>false</bool>
+       </property>
+       <property name="toolTip">
+        <string>Press to join the channel</string>
+       </property>
+       <property name="text">
+        <string/>
+       </property>
+      </widget>
+     </item>
+    </layout>
+   </item>
+  </layout>
+ </widget>
+ <tabstops>
+  <tabstop>networkCombo</tabstop>
+  <tabstop>connectButton</tabstop>
+  <tabstop>editButton</tabstop>
+  <tabstop>nickCombo</tabstop>
+  <tabstop>channelCombo</tabstop>
+  <tabstop>joinButton</tabstop>
+  <tabstop>messages</tabstop>
+ </tabstops>
+ <resources/>
+ <connections/>
+</ui>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Network/IRC/IrcUtilities.py	Sun Nov 25 18:40:15 2012 +0100
@@ -0,0 +1,151 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2012 Detlev Offenbach <detlev@die-offenbachs.de>
+#
+
+"""
+Module implementing functions used by several IRC objects.
+"""
+
+import re
+
+from PyQt4.QtCore import QTime, QCoreApplication
+from PyQt4.QtGui import QApplication
+
+import Utilities
+import Preferences
+
+
+__UrlRe = re.compile(
+    r"""((?:http|ftp|https):\/\/[\w\-_]+(?:\.[\w\-_]+)+"""
+    r"""(?:[\w\-\.,@?^=%&amp;:/~\+#]*[\w\-\@?^=%&amp;/~\+#])?)""")
+__ColorRe = re.compile(
+    r"""((?:\x03(?:0[0-9]|1[0-5]|[0-9])?(?:,?(?:0[0-9]|1[0-5]|[0-9])))"""
+    r"""|\x02|\x03|\x13|\x15|\x16|\x17|\x1d|\x1f)""")
+
+def ircTimestamp():
+    """
+    Module method to generate a time stamp string.
+    
+    @return time stamp (string)
+    """
+    if Preferences.getIrc("ShowTimestamps"):
+        if Preferences.getIrc("TimestampIncludeDate"):
+            if QApplication.isLeftToRight():
+                f = "{0} {1}"
+            else:
+                f = "{1} {0}"
+            formatString = f.format(Preferences.getIrc("DateFormat"),
+                                    Preferences.getIrc("TimeFormat"))
+        else:
+            formatString = Preferences.getIrc("TimeFormat")
+        return '<font color="{0}">[{1}]</font> '.format(
+            Preferences.getIrc("TimestampColour"),
+            QTime.currentTime().toString(formatString))
+    else:
+        return ""
+
+def ircFilter(msg):
+    """
+    Module method to make the message HTML compliant and detect URLs.
+    
+    @param msg message to process (string)
+    @return processed message (string)
+    """
+    # step 1: cleanup message
+    msg = Utilities.html_encode(msg)
+    
+    # step 2: replace IRC formatting characters
+    openTags = []
+    parts = __ColorRe.split(msg)
+    msgParts = []
+    for part in parts:
+        if part == "\x02":                                  # bold
+            if openTags and openTags[-1] == "b":
+                msgParts.append("</" + openTags.pop(-1) +">")
+            else:
+                msgParts.append("<b>")
+                openTags.append("b")
+        elif part in ["\x03", "\x17"]:
+            # TODO: implement color reset
+            continue
+        elif part == "\x0f":                                # reset
+            while openTags:
+                msgParts.append("</" + openTags.pop(-1) +">")
+        elif part == "\x13":                                # strikethru
+            if openTags and openTags[-1] == "s":
+                msgParts.append("</" + openTags.pop(-1) +">")
+            else:
+                msgParts.append("<s>")
+                openTags.append("s")
+        elif part in ["\x15", "\x1f"]:                      # underline
+            if openTags and openTags[-1] == "u":
+                msgParts.append("</" + openTags.pop(-1) +">")
+            else:
+                msgParts.append("<u>")
+                openTags.append("u")
+        elif part == "\x16":
+            # TODO: implement color reversal
+            continue
+        elif part == "\x1d":                                # italic
+            if openTags and openTags[-1] == "i":
+                msgParts.append("</" + openTags.pop(-1) +">")
+            else:
+                msgParts.append("<i>")
+                openTags.append("i")
+        elif part.startswith("\x03"):
+            # TODO: implement color support
+            continue
+        else:
+            msgParts.append(part)
+    msg = "".join(msgParts)
+    
+    # step 3: find http and https links
+    parts = __UrlRe.split(msg)
+    msgParts = []
+    for part in parts:
+        if part.startswith(("http://", "https://", "ftp://")):
+            msgParts.append('<a href="{0}" style="color:{1}">{0}</a>'.format(
+                part, Preferences.getIrc("HyperlinkColour")))
+        else:
+            msgParts.append(part)
+    
+    return "".join(msgParts)
+
+
+__channelModesDict = None
+
+
+def __initChannelModesDict():
+    """
+    Private module function to initialize the channels modes dictionary.
+    """
+    global __channelModesDict
+    
+    modesDict = {
+        "t": QCoreApplication.translate("IrcUtilities", "topic protection"),
+        "n": QCoreApplication.translate("IrcUtilities", "no messages from outside"),
+        "s": QCoreApplication.translate("IrcUtilities", "secret"),
+        "i": QCoreApplication.translate("IrcUtilities", "invite only"),
+        "p": QCoreApplication.translate("IrcUtilities", "private"),
+        "m": QCoreApplication.translate("IrcUtilities", "moderated"),
+        "k": QCoreApplication.translate("IrcUtilities", "password protected"),
+        "a": QCoreApplication.translate("IrcUtilities", "anonymous"),
+        "c": QCoreApplication.translate("IrcUtilities", "no colors allowed"),
+        "l": QCoreApplication.translate("IrcUtilities", "user limit"),
+    }
+    __channelModesDict = modesDict
+
+
+def getChannelModesDict():
+    """
+    Module function to get the dictionary with the channel modes mappings.
+    
+    @return dictionary with channel modes mapping (dict)
+    """
+    global __channelModesDict
+    
+    if __channelModesDict is None:
+        __initChannelModesDict()
+    
+    return __channelModesDict
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Network/IRC/IrcWidget.py	Sun Nov 25 18:40:15 2012 +0100
@@ -0,0 +1,599 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2012 Detlev Offenbach <detlev@die-offenbachs.de>
+#
+
+"""
+Module implementing the IRC window.
+"""
+
+import re
+import logging
+
+from PyQt4.QtCore import pyqtSlot, Qt, QByteArray
+from PyQt4.QtGui import QWidget, QToolButton, QLabel
+from PyQt4.QtNetwork import QTcpSocket, QAbstractSocket
+
+from E5Gui import E5MessageBox
+
+from .Ui_IrcWidget import Ui_IrcWidget
+
+from .IrcNetworkManager import IrcNetworkManager
+from .IrcChannelWidget import IrcChannelWidget
+
+import Preferences
+import UI.PixmapCache
+
+
+class IrcWidget(QWidget, Ui_IrcWidget):
+    """
+    Class implementing the IRC window.
+    """
+    def __init__(self, parent=None):
+        """
+        Constructor
+        
+        @param parent reference to the parent widget (QWidget)
+        """
+        super().__init__(parent)
+        self.setupUi(self)
+        
+        self.__ircNetworkManager = IrcNetworkManager(self)
+        
+        self.__leaveButton = QToolButton(self)
+        self.__leaveButton.setIcon(UI.PixmapCache.getIcon("ircCloseChannel.png"))
+        self.__leaveButton.setToolTip(self.trUtf8("Press to leave the current channel"))
+        self.__leaveButton.clicked[()].connect(self.__leaveChannel)
+        self.__leaveButton.setEnabled(False)
+        self.channelsWidget.setCornerWidget(self.__leaveButton, Qt.BottomRightCorner)
+        self.channelsWidget.setTabsClosable(False)
+        
+        self.networkWidget.initialize(self.__ircNetworkManager)
+        self.networkWidget.connectNetwork.connect(self.__connectNetwork)
+        self.networkWidget.editNetwork.connect(self.__editNetwork)
+        self.networkWidget.joinChannel.connect(self.__joinChannel)
+        self.networkWidget.nickChanged.connect(self.__changeNick)
+        
+        self.__channelList = []
+        self.__channelTypePrefixes = ""
+        self.__userName = ""
+        self.__nickIndex = -1
+        self.__nickName = ""
+        self.__server = None
+        self.__registering = False
+        
+        self.__buffer = ""
+        self.__userPrefix = {}
+        
+        # create TCP socket
+        self.__socket = QTcpSocket(self)
+        self.__socket.hostFound.connect(self.__hostFound)
+        self.__socket.connected.connect(self.__hostConnected)
+        self.__socket.disconnected.connect(self.__hostDisconnected)
+        self.__socket.readyRead.connect(self.__readyRead)
+        self.__socket.error.connect(self.__tcpError)
+        
+        self.__patterns = [
+            # :foo.bar.net COMMAND some message
+            (re.compile(r""":([^ ]+)\s+([A-Z]+)\s+(.+)"""), self.__handleNamedMessage),
+            # :foo.bar.net 123 * :info
+            (re.compile(r""":([^ ]+)\s+(\d{3})\s+(.+)"""), self.__handleNumericMessage),
+            # PING :ping message
+            (re.compile(r"""PING\s+:(.*)"""), self.__ping),
+        ]
+        self.__prefixRe = re.compile(r""".*\sPREFIX=\((.*)\)([^ ]+).*""")
+        self.__chanTypesRe = re.compile(r""".*\sCHANTYPES=([^ ]+).*""")
+        
+        ircPic = UI.PixmapCache.getPixmap("irc128.png")
+        self.__emptyLabel = QLabel()
+        self.__emptyLabel.setPixmap(ircPic)
+        self.__emptyLabel.setAlignment(Qt.AlignVCenter | Qt.AlignHCenter)
+        self.channelsWidget.addTab(self.__emptyLabel, "")
+        
+    
+    def shutdown(self):
+        """
+        Public method to shut down the widget.
+        
+        @return flag indicating successful shutdown (boolean)
+        """
+        if self.__server:
+            ok = E5MessageBox.yesNo(self,
+                self.trUtf8("Disconnect from Server"),
+                self.trUtf8("""<p>Do you really want to disconnect from"""
+                            """ <b>{0}</b>?</p><p>All channels will be closed.</p>""")\
+                    .format(self.__server.getServer()))
+            if ok:
+                self.__socket.blockSignals(True)
+                
+                self.__send("QUIT :" + self.trUtf8("IRC for eric IDE"))
+                self.__socket.close()
+                self.__socket.deleteLater()
+        else:
+            ok = True
+        
+        if ok:
+            self.__ircNetworkManager.close()
+        return ok
+    
+    def __connectNetwork(self, name, connect):
+        """
+        Private slot to connect to or disconnect from the given network.
+        
+        @param name name of the network to connect to (string)
+        @param connect flag indicating to connect (boolean)
+        """
+        if connect:
+            network = self.__ircNetworkManager.getNetwork(name)
+            self.__server = self.__ircNetworkManager.getServer(network.getServerName())
+            self.__userName = network.getIdentityName()
+            if self.__server:
+                self.networkWidget.addServerMessage(self.trUtf8("Info"),
+                    self.trUtf8("Looking for server {0} (port {1})...").format(
+                        self.__server.getServer(), self.__server.getPort()))
+                self.__socket.connectToHost(self.__server.getServer(),
+                                            self.__server.getPort())
+        else:
+            ok = E5MessageBox.yesNo(self,
+                self.trUtf8("Disconnect from Server"),
+                self.trUtf8("""<p>Do you really want to disconnect from"""
+                            """ <b>{0}</b>?</p><p>All channels will be closed.</p>""")\
+                    .format(self.__server.getServer()))
+            if ok:
+                self.networkWidget.addServerMessage(self.trUtf8("Info"),
+                    self.trUtf8("Disconnecting from server {0}...").format(
+                        self.__server.getServer()))
+                while self.__channelList:
+                    channel = self.__channelList.pop()
+                    self.channelsWidget.removeTab(self.channelsWidget.indexOf(channel))
+                    channel.deleteLater()
+                    channel = None
+                self.__send("QUIT :" + self.trUtf8("IRC for eric IDE"))
+                self.__socket.close()
+    
+    def __editNetwork(self, name):
+        """
+        Private slot to edit the network configuration.
+        
+        @param name name of the network to edit (string)
+        """
+        # TODO: implement this
+    
+    def __joinChannel(self, name):
+        """
+        Private slot to join a channel.
+        
+        @param name name of the channel (string)
+        """
+        # step 1: check, if this channel is already joined
+        for channel in self.__channelList:
+            if channel.name() == name:
+                return
+        
+        channel = IrcChannelWidget(self)
+        channel.setName(name)
+        channel.setUserName(self.__nickName)
+        channel.setPartMessage(self.trUtf8("IRC for eric IDE"))
+        channel.setUserPrivilegePrefix(self.__userPrefix)
+        
+        channel.sendData.connect(self.__send)
+        channel.channelClosed.connect(self.__closeChannel)
+        
+        self.channelsWidget.addTab(channel, name)
+        self.__channelList.append(channel)
+        
+        self.__send("JOIN " + name)
+        self.__send("MODE " + name)
+        
+        emptyIndex = self.channelsWidget.indexOf(self.__emptyLabel)
+        if emptyIndex > -1:
+            self.channelsWidget.removeTab(emptyIndex)
+            self.__leaveButton.setEnabled(True)
+        self.channelsWidget.setTabsClosable(True)
+    
+    @pyqtSlot()
+    def __leaveChannel(self):
+        """
+        Private slot to leave a channel and close the associated tab.
+        """
+        channel = self.channelsWidget.currentWidget()
+        channel.requestLeave()
+    
+    def __closeChannel(self, name):
+        """
+        Private slot handling the closing of a channel.
+        
+        @param name name of the closed channel (string) 
+        """
+        for channel in self.__channelList:
+            if channel.name() == name:
+                self.channelsWidget.removeTab(self.channelsWidget.indexOf(channel))
+                self.__channelList.remove(channel)
+                channel.deleteLater()
+        
+        if self.channelsWidget.count() == 0:
+            self.channelsWidget.addTab(self.__emptyLabel, "")
+            self.__leaveButton.setEnabled(False)
+            self.channelsWidget.setTabsClosable(False)
+    
+    @pyqtSlot(int)
+    def on_channelsWidget_tabCloseRequested(self, index):
+        """
+        Private slot to close a channel by pressing the close button of
+        the channels widget.
+        
+        @param index index of the tab to be closed (integer)
+        """
+        channel = self.channelsWidget.widget(index)
+        channel.requestLeave()
+    
+    def __send(self, data):
+        """
+        Private slot to send data to the IRC server.
+        
+        @param data data to be sent (string)
+        """
+        self.__socket.write(QByteArray("{0}\r\n".format(data).encode("utf-8")))
+    
+    def __hostFound(self):
+        """
+        Private slot to indicate the host was found.
+        """
+        self.networkWidget.addServerMessage(self.trUtf8("Info"),
+            self.trUtf8("Server found,connecting..."))
+    
+    def __hostConnected(self):
+        """
+        Private slot to log in to the server after the connection was established.
+        """
+        self.networkWidget.addServerMessage(self.trUtf8("Info"),
+            self.trUtf8("Connected,logging in..."))
+        self.networkWidget.setConnected(True)
+        
+        self.__registering = True
+        serverPassword = self.__server.getPassword()
+        if serverPassword:
+            self.__send("PASS " + serverPassword)
+        nick = self.networkWidget.getNickname()
+        if not nick:
+            self.__nickIndex = 0
+            try:
+                nick = self.__ircNetworkManager.getIdentity(self.__userName)\
+                    .getNickNames()[self.__nickIndex]
+            except IndexError:
+                nick = ""
+        if not nick:
+            nick = self.__userName
+        self.__nickName = nick
+        self.networkWidget.setNickName(nick)
+        self.__send("NICK " + nick)
+        self.__send("USER " + self.__userName + " 0 * :eric IDE chat")
+    
+    def __hostDisconnected(self):
+        """
+        Private slot to indicate the host was disconnected.
+        """
+        self.networkWidget.addServerMessage(self.trUtf8("Info"),
+            self.trUtf8("Server disconnected."))
+        self.networkWidget.setConnected(False)
+        self.__server = None
+        self.__nickName = ""
+        self.__nickIndex = -1
+        self.__channelTypePrefixes = ""
+    
+    def __readyRead(self):
+        """
+        Private slot to read data from the socket.
+        """
+        self.__buffer += str(self.__socket.readAll(),
+                Preferences.getSystem("IOEncoding"),
+                'replace')
+        if self.__buffer.endswith("\r\n"):
+            for line in self.__buffer.splitlines():
+                line = line.strip()
+                if line:
+                    logging.debug("<IRC> " + line)
+                    handled = False
+                    # step 1: give channels a chance to handle the message
+                    for channel in self.__channelList:
+                        handled = channel.handleMessage(line)
+                        if handled:
+                            break
+                    else:
+                        # step 2: try to process the message ourselves
+                        for patternRe, patternFunc in self.__patterns:
+                            match = patternRe.match(line)
+                            if match is not None:
+                                if patternFunc(match):
+                                    break
+                        else:
+                            # Oops, the message wasn't handled
+                            self.networkWidget.addErrorMessage(
+                                self.trUtf8("Message Error"),
+                                self.trUtf8("Unknown message received from server:"
+                                            "<br/>{0}").format(line))
+            
+            self.__updateUsersCount()
+            self.__buffer = ""
+    
+    def __handleNamedMessage(self, match):
+        """
+        Private method to handle a server message containing a message name.
+        
+        @param reference to the match object
+        @return flag indicating, if the message was handled (boolean)
+        """
+        name = match.group(2)
+        if name == "NOTICE":
+            try:
+                msg = match.group(3).split(":", 1)[1]
+            except IndexError:
+                msg = match.group(3)
+            if "!" in match.group(1):
+                name = match.group(1).split("!", 1)[0]
+                msg = "-{0}- {1}".format(name, msg)
+            self.networkWidget.addServerMessage(self.trUtf8("Notice"), msg)
+            return True
+        elif name == "MODE":
+            self.__registering = False
+            if ":" in match.group(3):
+                # :detlev_ MODE detlev_ :+i
+                name, modes = match.group(3).split(" :")
+                sourceNick = match.group(1)
+                if not self.__isChannelName(name):
+                    if name == self.__nickName:
+                        if sourceNick == self.__nickName:
+                            msg = self.trUtf8(
+                                "You have set your personal modes to <b>[{0}]</b>")\
+                                .format(modes)
+                        else:
+                            msg = self.trUtf8(
+                                "{0} has changed your personal modes to <b>[{1}]</b>")\
+                                .format(sourceNick, modes)
+                        self.networkWidget.addServerMessage(
+                            self.trUtf8("Mode"), msg, filterMsg=False)
+                        return True
+        elif name == "PART":
+            nick = match.group(1).split("!", 1)[0]
+            if nick == self.__nickName:
+                channel = match.group(3).split(None, 1)[0]
+                self.networkWidget.addMessage(
+                    self.trUtf8("You have left channel {0}.").format(channel))
+                return True
+        elif name == "NICK":
+            # :foo_!n=foo@foohost.bar.net NICK :newnick
+            oldNick = match.group(1).split("!", 1)[0]
+            newNick = match.group(3).split(":", 1)[1]
+            if oldNick == self.__nickName:
+                self.networkWidget.addMessage(
+                    self.trUtf8("You are now known as {0}.").format(newNick))
+                self.__nickName = newNick
+                self.networkWidget.setNickName(newNick)
+            else:
+                self.networkWidget.addMessage(
+                    self.trUtf8("User {0} is now known as {1}.").format(
+                    oldNick, newNick))
+            return True
+        
+        return False
+    
+    def __handleNumericMessage(self, match):
+        """
+        Private method to handle a server message containing a numeric code.
+        
+        @param reference to the match object
+        @return flag indicating, if the message was handled (boolean)
+        """
+        code = int(match.group(2))
+        if code < 400:
+            return self.__handleServerReply(code, match.group(1), match.group(3))
+        else:
+            return self.__handleServerError(code, match.group(1), match.group(3))
+    
+    def __handleServerError(self, code, server, message):
+        """
+        Private slot to handle a server error reply.
+        
+        @param code numerical code sent by the server (integer)
+        @param server name of the server (string)
+        @param message message sent by the server (string)
+        @return flag indicating, if the message was handled (boolean)
+        """
+        if code == 433:
+            if self.__registering:
+                self.__handleNickInUseLogin()
+            else:
+                self.__handleNickInUse()
+        else:
+            self.networkWidget.addServerMessage(self.trUtf8("Error"), message)
+        
+        return True
+    
+    def __handleServerReply(self, code, server, message):
+        """
+        Private slot to handle a server reply.
+        
+        @param code numerical code sent by the server (integer)
+        @param server name of the server (string)
+        @param message message sent by the server (string)
+        @return flag indicating, if the message was handled (boolean)
+        """
+        # determine message type
+        if code in [1, 2, 3, 4]:
+            msgType = self.trUtf8("Welcome")
+        elif code == 5:
+            msgType = self.trUtf8("Support")
+        elif code in [250, 251, 252, 253, 254, 255, 265, 266]:
+            msgType = self.trUtf8("User")
+        elif code in [372, 375, 376]:
+            msgType = self.trUtf8("MOTD")
+        else:
+            msgType = self.trUtf8("Info ({0})").format(code)
+        
+        # special treatment for some messages
+        if code == 375:
+            message = self.trUtf8("Message of the day")
+        elif code == 376:
+            message = self.trUtf8("End of message of the day")
+        elif code == 4:
+            parts = message.strip().split()
+            message = self.trUtf8("Server {0} (Version {1}), User-Modes: {2},"
+                " Channel-Modes: {3}").format(parts[1], parts[2], parts[3], parts[4])
+        elif code == 265:
+            parts = message.strip().split()
+            message = self.trUtf8("Current users on {0}: {1}, max. {2}").format(
+                server, parts[1], parts[2])
+        elif code == 266:
+            parts = message.strip().split()
+            message = self.trUtf8("Current users on the network: {0}, max. {1}").format(
+                parts[1], parts[2])
+        else:
+            first, message = message.split(None, 1)
+            if message.startswith(":"):
+                message = message[1:]
+            else:
+                message = message.replace(":", "", 1)
+        
+        self.networkWidget.addServerMessage(msgType, message)
+        
+        if code == 1:
+            # register with services after the welcome message
+            self.__registerWithServices()
+        elif code == 5:
+            # extract the user privilege prefixes
+            # ... PREFIX=(ov)@+ ...
+            m = self.__prefixRe.match(message)
+            if m:
+                self.__setUserPrivilegePrefix(m.group(1), m.group(2))
+            # extract the channel type prefixes
+            # ... CHANTYPES=# ...
+            m = self.__chanTypesRe.match(message)
+            if m:
+                self.__setChannelTypePrefixes(m.group(1))
+        
+        return True
+    
+    def __registerWithServices(self):
+        """
+        Private method to register to services.
+        """
+        identity = self.__ircNetworkManager.getIdentity(self.__userName)
+        service = identity.getName()
+        password = identity.getPassword()
+        if service and password:
+            self.__send("PRIVMSG " + service + " :identify " + password)
+    
+    def __tcpError(self, error):
+        """
+        Private slot to handle errors reported by the TCP socket.
+        
+        @param error error code reported by the socket
+            (QAbstractSocket.SocketError)
+        """
+        if error == QAbstractSocket.RemoteHostClosedError:
+            # ignore this one, it's a disconnect
+            pass
+        elif error == QAbstractSocket.HostNotFoundError:
+            self.networkWidget.addErrorMessage(self.trUtf8("Socket Error"),
+                self.trUtf8("The host was not found. Please check the host name"
+                            " and port settings."))
+        elif error == QAbstractSocket.ConnectionRefusedError:
+            self.networkWidget.addErrorMessage(self.trUtf8("Socket Error"),
+                self.trUtf8("The connection was refused by the peer. Please check the"
+                            " host name and port settings."))
+        else:
+            self.networkWidget.addErrorMessage(self.trUtf8("Socket Error"),
+                self.trUtf8("The following network error occurred:<br/>{0}").format(
+                self.__socket.errorString()))
+    
+    def __setUserPrivilegePrefix(self, prefix1, prefix2):
+        """
+        Private method to set the user privilege prefix.
+        
+        @param prefix1 first part of the prefix (string)
+        @param prefix2 indictors the first part gets mapped to (string)
+        """
+        # PREFIX=(ov)@+
+        # o = @ -> @ircbot , channel operator
+        # v = + -> +userName , voice operator
+        for i in range(len(prefix1)):
+            self.__userPrefix["+" + prefix1[i]] = prefix2[i]
+            self.__userPrefix["-" + prefix1[i]] = ""
+    
+    def __ping(self, match):
+        """
+        Private method to handle a PING message.
+        
+        @param reference to the match object
+        @return flag indicating, if the message was handled (boolean)
+        """
+        self.__send("PONG " + match.group(1))
+        return True
+    
+    def __updateUsersCount(self):
+        """
+        Private method to update the users count on the channel tabs.
+        """
+        for channel in self.__channelList:
+            index = self.channelsWidget.indexOf(channel)
+            self.channelsWidget.setTabText(index,
+                self.trUtf8("{0} ({1})", "channel name, users count").format(
+                channel.name(), channel.getUsersCount()))
+    
+    def __handleNickInUseLogin(self):
+        """
+        Private method to handle a 443 server error at login.
+        """
+        self.__nickIndex += 1
+        try:
+            nick = self.__ircNetworkManager.getIdentity(self.__userName)\
+                .getNickNames()[self.__nickIndex]
+            self.__nickName = nick
+        except IndexError:
+            self.networkWidget.addServerMessage(self.trUtf8("Critical"),
+                self.trUtf8("No nickname acceptable to the server configured"
+                            " for <b>{0}</b>. Disconnecting...").format(self.__userName))
+            self.__connectNetwork("", False)
+            self.__nickName = ""
+            self.__nickIndex = -1
+            return
+        
+        self.networkWidget.setNickName(nick)
+        self.__send("NICK " + nick)
+    
+    def __handleNickInUse(self):
+        """
+        Private method to handle a 443 server error.
+        """
+        self.networkWidget.addServerMessage(self.trUtf8("Critical"),
+            self.trUtf8("The given nickname is already in use."))
+    
+    def __changeNick(self, nick):
+        """
+        Private slot to use a new nick name.
+        
+        @param nick nick name to use (str)
+        """
+        self.__send("NICK " + nick)
+    
+    def __setChannelTypePrefixes(self, prefixes):
+        """
+        Private method to set the channel type prefixes.
+        
+        @param prefixes channel prefix characters (string)
+        """
+        self.__channelTypePrefixes = prefixes
+    
+    def __isChannelName(self, name):
+        """
+        Private method to check, if the given name is a channel name.
+        
+        @return flag indicating a channel name (boolean)
+        """
+        if not name:
+            return False
+        
+        if self.__channelTypePrefixes:
+            return name[0] in self.__channelTypePrefixes
+        else:
+            return name[0] in "#&"
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Network/IRC/IrcWidget.ui	Sun Nov 25 18:40:15 2012 +0100
@@ -0,0 +1,68 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>IrcWidget</class>
+ <widget class="QWidget" name="IrcWidget">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>400</width>
+    <height>941</height>
+   </rect>
+  </property>
+  <property name="windowTitle">
+   <string/>
+  </property>
+  <layout class="QVBoxLayout" name="verticalLayout">
+   <item>
+    <widget class="QTabWidget" name="channelsWidget">
+     <property name="sizePolicy">
+      <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+       <horstretch>0</horstretch>
+       <verstretch>3</verstretch>
+      </sizepolicy>
+     </property>
+     <property name="tabPosition">
+      <enum>QTabWidget::South</enum>
+     </property>
+     <property name="documentMode">
+      <bool>true</bool>
+     </property>
+     <property name="tabsClosable">
+      <bool>true</bool>
+     </property>
+     <property name="movable">
+      <bool>true</bool>
+     </property>
+    </widget>
+   </item>
+   <item>
+    <widget class="IrcNetworkWidget" name="networkWidget" native="true">
+     <property name="sizePolicy">
+      <sizepolicy hsizetype="Preferred" vsizetype="Expanding">
+       <horstretch>0</horstretch>
+       <verstretch>2</verstretch>
+      </sizepolicy>
+     </property>
+     <property name="focusPolicy">
+      <enum>Qt::TabFocus</enum>
+     </property>
+    </widget>
+   </item>
+  </layout>
+ </widget>
+ <customwidgets>
+  <customwidget>
+   <class>IrcNetworkWidget</class>
+   <extends>QWidget</extends>
+   <header>Network/IRC/IrcNetworkWidget.h</header>
+   <container>1</container>
+  </customwidget>
+ </customwidgets>
+ <tabstops>
+  <tabstop>networkWidget</tabstop>
+  <tabstop>channelsWidget</tabstop>
+ </tabstops>
+ <resources/>
+ <connections/>
+</ui>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Network/IRC/__init__.py	Sun Nov 25 18:40:15 2012 +0100
@@ -0,0 +1,8 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2012 Detlev Offenbach <detlev@die-offenbachs.de>
+#
+
+"""
+Package containing the IRC support.
+"""
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Network/__init__.py	Sun Nov 25 18:40:15 2012 +0100
@@ -0,0 +1,8 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2012 Detlev Offenbach <detlev@die-offenbachs.de>
+#
+
+"""
+Package containing network related services.
+"""
--- a/Preferences/ConfigurationDialog.py	Wed Nov 14 18:58:51 2012 +0100
+++ b/Preferences/ConfigurationDialog.py	Sun Nov 25 18:40:15 2012 +0100
@@ -134,6 +134,9 @@
                 "iconsPage": \
                     [self.trUtf8("Icons"), "preferences-icons.png",
                     "IconsPage", None, None],
+                "ircPage": \
+                    [self.trUtf8("IRC"), "irc.png",
+                    "IrcPage", None, None],
                 "networkPage": \
                     [self.trUtf8("Network"), "preferences-network.png",
                     "NetworkPage", None, None],
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Preferences/ConfigurationPages/IrcPage.py	Sun Nov 25 18:40:15 2012 +0100
@@ -0,0 +1,217 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2012 Detlev Offenbach <detlev@die-offenbachs.de>
+#
+
+"""
+Module implementing the IRC configuration page.
+"""
+
+from PyQt4.QtCore import pyqtSlot
+##from PyQt4.QtGui import QWidget
+
+from .ConfigurationPageBase import ConfigurationPageBase
+from .Ui_IrcPage import Ui_IrcPage
+
+import Preferences
+
+
+class IrcPage(ConfigurationPageBase, Ui_IrcPage):
+    """
+    Class implementing the IRC configuration page.
+    """
+    TimeFormats = ["hh:mm", "hh:mm:ss", "h:mm ap", "h:mm:ss ap"]
+    DateFormats = ["yyyy-MM-dd", "dd.MM.yyyy", "MM/dd/yyyy",
+                   "yyyy MMM. dd", "dd MMM. yyyy", "MMM. dd, yyyy"]
+    
+    def __init__(self):
+        """
+        Constructor
+        """
+        super().__init__()
+        self.setupUi(self)
+        self.setObjectName("IrcPage")
+        
+        self.timeFormatCombo.addItems(IrcPage.TimeFormats)
+        self.dateFormatCombo.addItems(IrcPage.DateFormats)
+        
+        self.ircColours = {}
+        
+        # set initial values
+        # timestamps
+        self.timestampGroup.setChecked(Preferences.getIrc("ShowTimestamps"))
+        self.showDateCheckBox.setChecked(Preferences.getIrc("TimestampIncludeDate"))
+        self.timeFormatCombo.setCurrentIndex(
+            self.timeFormatCombo.findText(Preferences.getIrc("TimeFormat")))
+        self.dateFormatCombo.setCurrentIndex(
+            self.dateFormatCombo.findText(Preferences.getIrc("DateFormat")))
+        
+        # colours
+        self.ircColours["NetworkMessageColour"] = \
+            self.initColour("NetworkMessageColour", self.networkButton,
+                Preferences.getIrc)
+        self.ircColours["ServerMessageColour"] = \
+            self.initColour("ServerMessageColour", self.serverButton,
+                Preferences.getIrc)
+        self.ircColours["ErrorMessageColour"] = \
+            self.initColour("ErrorMessageColour", self.errorButton,
+                Preferences.getIrc)
+        self.ircColours["TimestampColour"] = \
+            self.initColour("TimestampColour", self.timestampButton,
+                Preferences.getIrc)
+        self.ircColours["HyperlinkColour"] = \
+            self.initColour("HyperlinkColour", self.hyperlinkButton,
+                Preferences.getIrc)
+        self.ircColours["ChannelMessageColour"] = \
+            self.initColour("ChannelMessageColour", self.channelButton,
+                Preferences.getIrc)
+        self.ircColours["OwnNickColour"] = \
+            self.initColour("OwnNickColour", self.ownNickButton,
+                Preferences.getIrc)
+        self.ircColours["NickColour"] = \
+            self.initColour("NickColour", self.nickButton,
+                Preferences.getIrc)
+        self.ircColours["JoinChannelColour"] = \
+            self.initColour("JoinChannelColour", self.joinButton,
+                Preferences.getIrc)
+        self.ircColours["LeaveChannelColour"] = \
+            self.initColour("LeaveChannelColour", self.leaveButton,
+                Preferences.getIrc)
+        self.ircColours["ChannelInfoColour"] = \
+            self.initColour("ChannelInfoColour", self.infoButton,
+                Preferences.getIrc)
+        
+        # notifications
+        self.notificationsGroup.setChecked(Preferences.getIrc("ShowNotifications"))
+        self.joinLeaveCheckBox.setChecked(Preferences.getIrc("NotifyJoinPart"))
+        self.messageCheckBox.setChecked(Preferences.getIrc("NotifyMessage"))
+        self.ownNickCheckBox.setChecked(Preferences.getIrc("NotifyNick"))
+    
+    def save(self):
+        """
+        Public slot to save the IRC configuration.
+        """
+        # timestamps
+        Preferences.setIrc("ShowTimestamps", self.timestampGroup.isChecked())
+        Preferences.setIrc("TimestampIncludeDate", self.showDateCheckBox.isChecked())
+        Preferences.setIrc("TimeFormat", self.timeFormatCombo.currentText())
+        Preferences.setIrc("DateFormat", self.dateFormatCombo.currentText())
+        
+        # notifications
+        Preferences.setIrc("ShowNotifications", self.notificationsGroup.isChecked())
+        Preferences.setIrc("NotifyJoinPart", self.joinLeaveCheckBox.isChecked())
+        Preferences.setIrc("NotifyMessage", self.messageCheckBox.isChecked())
+        Preferences.setIrc("NotifyNick", self.ownNickCheckBox.isChecked())
+        
+        # colours
+        for key in self.ircColours:
+            Preferences.setIrc(key, self.ircColours[key].name())
+    
+    @pyqtSlot()
+    def on_networkButton_clicked(self):
+        """
+        Private slot to set the color for network messages.
+        """
+        self.ircColours["NetworkMessageColour"] = \
+            self.selectColour(self.networkButton,
+                self.ircColours["NetworkMessageColour"])
+    
+    @pyqtSlot()
+    def on_nickButton_clicked(self):
+        """
+        Private slot to set the color for nick names.
+        """
+        self.ircColours["NickColour"] = \
+            self.selectColour(self.nickButton,
+                self.ircColours["NickColour"])
+    
+    @pyqtSlot()
+    def on_serverButton_clicked(self):
+        """
+        Private slot to set the color for server messages.
+        """
+        self.ircColours["ServerMessageColour"] = \
+            self.selectColour(self.serverButton,
+                self.ircColours["ServerMessageColour"])
+    
+    @pyqtSlot()
+    def on_ownNickButton_clicked(self):
+        """
+        Private slot to set the color for own nick name.
+        """
+        self.ircColours["OwnNickColour"] = \
+            self.selectColour(self.ownNickButton,
+                self.ircColours["OwnNickColour"])
+    
+    @pyqtSlot()
+    def on_channelButton_clicked(self):
+        """
+        Private slot to set the color for channel messages.
+        """
+        self.ircColours["ChannelMessageColour"] = \
+            self.selectColour(self.channelButton,
+                self.ircColours["ChannelMessageColour"])
+    
+    @pyqtSlot()
+    def on_joinButton_clicked(self):
+        """
+        Private slot to set the color for join events.
+        """
+        self.ircColours["JoinChannelColour"] = \
+            self.selectColour(self.joinButton,
+                self.ircColours["JoinChannelColour"])
+    
+    @pyqtSlot()
+    def on_errorButton_clicked(self):
+        """
+        Private slot to set the color for error messages.
+        """
+        self.ircColours["ErrorMessageColour"] = \
+            self.selectColour(self.errorButton,
+                self.ircColours["ErrorMessageColour"])
+    
+    @pyqtSlot()
+    def on_leaveButton_clicked(self):
+        """
+        Private slot to set the color for leave events.
+        """
+        self.ircColours["LeaveChannelColour"] = \
+            self.selectColour(self.leaveButton,
+                self.ircColours["LeaveChannelColour"])
+    
+    @pyqtSlot()
+    def on_timestampButton_clicked(self):
+        """
+        Private slot to set the color for timestamps.
+        """
+        self.ircColours["TimestampColour"] = \
+            self.selectColour(self.timestampButton,
+                self.ircColours["TimestampColour"])
+    
+    @pyqtSlot()
+    def on_infoButton_clicked(self):
+        """
+        Private slot to set the color for info messages.
+        """
+        self.ircColours["ChannelInfoColour"] = \
+            self.selectColour(self.infoButton,
+                self.ircColours["ChannelInfoColour"])
+    
+    @pyqtSlot()
+    def on_hyperlinkButton_clicked(self):
+        """
+        Private slot to set the color for hyperlinks.
+        """
+        self.ircColours["HyperlinkColour"] = \
+            self.selectColour(self.hyperlinkButton,
+                self.ircColours["HyperlinkColour"])
+
+
+def create(dlg):
+    """
+    Module function to create the configuration page.
+    
+    @param dlg reference to the configuration dialog
+    """
+    page = IrcPage()
+    return page
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Preferences/ConfigurationPages/IrcPage.ui	Sun Nov 25 18:40:15 2012 +0100
@@ -0,0 +1,458 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>IrcPage</class>
+ <widget class="QWidget" name="IrcPage">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>501</width>
+    <height>651</height>
+   </rect>
+  </property>
+  <layout class="QVBoxLayout" name="verticalLayout">
+   <item>
+    <widget class="QLabel" name="headerLabel">
+     <property name="text">
+      <string>&lt;b&gt;Configure IRC&lt;/b&gt;</string>
+     </property>
+    </widget>
+   </item>
+   <item>
+    <widget class="Line" name="line3">
+     <property name="frameShape">
+      <enum>QFrame::HLine</enum>
+     </property>
+     <property name="frameShadow">
+      <enum>QFrame::Sunken</enum>
+     </property>
+     <property name="orientation">
+      <enum>Qt::Horizontal</enum>
+     </property>
+    </widget>
+   </item>
+   <item>
+    <widget class="QGroupBox" name="timestampGroup">
+     <property name="toolTip">
+      <string>Select to show timestamps</string>
+     </property>
+     <property name="title">
+      <string>Show Timestamps</string>
+     </property>
+     <property name="checkable">
+      <bool>true</bool>
+     </property>
+     <layout class="QGridLayout" name="gridLayout">
+      <item row="0" column="0">
+       <widget class="QLabel" name="label">
+        <property name="text">
+         <string>Time Format:</string>
+        </property>
+       </widget>
+      </item>
+      <item row="0" column="1">
+       <widget class="QComboBox" name="timeFormatCombo">
+        <property name="sizePolicy">
+         <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
+          <horstretch>0</horstretch>
+          <verstretch>0</verstretch>
+         </sizepolicy>
+        </property>
+        <property name="toolTip">
+         <string>Select the time format to use</string>
+        </property>
+       </widget>
+      </item>
+      <item row="0" column="2">
+       <widget class="QLabel" name="label_2">
+        <property name="text">
+         <string>Date Format</string>
+        </property>
+       </widget>
+      </item>
+      <item row="0" column="3">
+       <widget class="QComboBox" name="dateFormatCombo">
+        <property name="sizePolicy">
+         <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
+          <horstretch>0</horstretch>
+          <verstretch>0</verstretch>
+         </sizepolicy>
+        </property>
+        <property name="toolTip">
+         <string>Select the date format to use</string>
+        </property>
+       </widget>
+      </item>
+      <item row="1" column="0" colspan="4">
+       <widget class="QCheckBox" name="showDateCheckBox">
+        <property name="toolTip">
+         <string>Select to show the date in timestamps</string>
+        </property>
+        <property name="text">
+         <string>Show Date</string>
+        </property>
+       </widget>
+      </item>
+     </layout>
+    </widget>
+   </item>
+   <item>
+    <widget class="QGroupBox" name="coloursGroup">
+     <property name="title">
+      <string>Colours</string>
+     </property>
+     <layout class="QGridLayout" name="gridLayout_2">
+      <item row="0" column="0">
+       <widget class="QLabel" name="label_3">
+        <property name="text">
+         <string>Network Messages:</string>
+        </property>
+       </widget>
+      </item>
+      <item row="0" column="1">
+       <widget class="QPushButton" name="networkButton">
+        <property name="minimumSize">
+         <size>
+          <width>100</width>
+          <height>0</height>
+         </size>
+        </property>
+        <property name="toolTip">
+         <string>Select the colour for network messages</string>
+        </property>
+        <property name="text">
+         <string/>
+        </property>
+       </widget>
+      </item>
+      <item row="0" column="2">
+       <widget class="QLabel" name="label_9">
+        <property name="text">
+         <string>Nick Names:</string>
+        </property>
+       </widget>
+      </item>
+      <item row="0" column="3">
+       <widget class="QPushButton" name="nickButton">
+        <property name="minimumSize">
+         <size>
+          <width>100</width>
+          <height>0</height>
+         </size>
+        </property>
+        <property name="toolTip">
+         <string>Select the colour for nick names</string>
+        </property>
+        <property name="text">
+         <string/>
+        </property>
+       </widget>
+      </item>
+      <item row="1" column="0">
+       <widget class="QLabel" name="label_4">
+        <property name="text">
+         <string>Server Messages:</string>
+        </property>
+       </widget>
+      </item>
+      <item row="1" column="1">
+       <widget class="QPushButton" name="serverButton">
+        <property name="minimumSize">
+         <size>
+          <width>100</width>
+          <height>0</height>
+         </size>
+        </property>
+        <property name="toolTip">
+         <string>Select the colour for server messages</string>
+        </property>
+        <property name="text">
+         <string/>
+        </property>
+       </widget>
+      </item>
+      <item row="1" column="2">
+       <widget class="QLabel" name="label_10">
+        <property name="text">
+         <string>Own Nick Name:</string>
+        </property>
+       </widget>
+      </item>
+      <item row="1" column="3">
+       <widget class="QPushButton" name="ownNickButton">
+        <property name="minimumSize">
+         <size>
+          <width>100</width>
+          <height>0</height>
+         </size>
+        </property>
+        <property name="toolTip">
+         <string>Select the colour for own nick name</string>
+        </property>
+        <property name="text">
+         <string/>
+        </property>
+       </widget>
+      </item>
+      <item row="2" column="0">
+       <widget class="QLabel" name="label_5">
+        <property name="text">
+         <string>Channel Messages:</string>
+        </property>
+       </widget>
+      </item>
+      <item row="2" column="1">
+       <widget class="QPushButton" name="channelButton">
+        <property name="minimumSize">
+         <size>
+          <width>100</width>
+          <height>0</height>
+         </size>
+        </property>
+        <property name="toolTip">
+         <string>Select the colour for channel messages</string>
+        </property>
+        <property name="text">
+         <string/>
+        </property>
+       </widget>
+      </item>
+      <item row="2" column="2">
+       <widget class="QLabel" name="label_11">
+        <property name="text">
+         <string>Join Channel:</string>
+        </property>
+       </widget>
+      </item>
+      <item row="2" column="3">
+       <widget class="QPushButton" name="joinButton">
+        <property name="minimumSize">
+         <size>
+          <width>100</width>
+          <height>0</height>
+         </size>
+        </property>
+        <property name="toolTip">
+         <string>Select the colour for join channel messages</string>
+        </property>
+        <property name="text">
+         <string/>
+        </property>
+       </widget>
+      </item>
+      <item row="3" column="0">
+       <widget class="QLabel" name="label_6">
+        <property name="text">
+         <string>Error Messages:</string>
+        </property>
+       </widget>
+      </item>
+      <item row="3" column="1">
+       <widget class="QPushButton" name="errorButton">
+        <property name="minimumSize">
+         <size>
+          <width>100</width>
+          <height>0</height>
+         </size>
+        </property>
+        <property name="toolTip">
+         <string>Select the colour for error messages</string>
+        </property>
+        <property name="text">
+         <string/>
+        </property>
+       </widget>
+      </item>
+      <item row="3" column="2">
+       <widget class="QLabel" name="label_12">
+        <property name="text">
+         <string>Leave Channel:</string>
+        </property>
+       </widget>
+      </item>
+      <item row="3" column="3">
+       <widget class="QPushButton" name="leaveButton">
+        <property name="minimumSize">
+         <size>
+          <width>100</width>
+          <height>0</height>
+         </size>
+        </property>
+        <property name="toolTip">
+         <string>Select the colour for leave channel messages</string>
+        </property>
+        <property name="text">
+         <string/>
+        </property>
+       </widget>
+      </item>
+      <item row="4" column="0">
+       <widget class="QLabel" name="label_7">
+        <property name="text">
+         <string>Timestamp:</string>
+        </property>
+       </widget>
+      </item>
+      <item row="4" column="1">
+       <widget class="QPushButton" name="timestampButton">
+        <property name="minimumSize">
+         <size>
+          <width>100</width>
+          <height>0</height>
+         </size>
+        </property>
+        <property name="toolTip">
+         <string>Select the colour for timestamps</string>
+        </property>
+        <property name="text">
+         <string/>
+        </property>
+       </widget>
+      </item>
+      <item row="4" column="2">
+       <widget class="QLabel" name="label_13">
+        <property name="text">
+         <string>Channel Info:</string>
+        </property>
+       </widget>
+      </item>
+      <item row="4" column="3">
+       <widget class="QPushButton" name="infoButton">
+        <property name="minimumSize">
+         <size>
+          <width>100</width>
+          <height>0</height>
+         </size>
+        </property>
+        <property name="toolTip">
+         <string>Select the colour for channel info messages</string>
+        </property>
+        <property name="text">
+         <string/>
+        </property>
+       </widget>
+      </item>
+      <item row="5" column="0">
+       <widget class="QLabel" name="label_8">
+        <property name="text">
+         <string>Hyperlink:</string>
+        </property>
+       </widget>
+      </item>
+      <item row="5" column="1">
+       <widget class="QPushButton" name="hyperlinkButton">
+        <property name="minimumSize">
+         <size>
+          <width>100</width>
+          <height>0</height>
+         </size>
+        </property>
+        <property name="toolTip">
+         <string>Select the colour for hyperlinks</string>
+        </property>
+        <property name="text">
+         <string/>
+        </property>
+       </widget>
+      </item>
+     </layout>
+    </widget>
+   </item>
+   <item>
+    <widget class="QGroupBox" name="notificationsGroup">
+     <property name="title">
+      <string>Show Notifications</string>
+     </property>
+     <property name="checkable">
+      <bool>true</bool>
+     </property>
+     <layout class="QGridLayout" name="gridLayout_3">
+      <item row="0" column="0" colspan="2">
+       <widget class="QLabel" name="label_14">
+        <property name="text">
+         <string>&lt;b&gt;Note:&lt;/b&gt; Notifications will only be shown, if the global usage of notifications is enabled on the notifications configuration page.</string>
+        </property>
+        <property name="wordWrap">
+         <bool>true</bool>
+        </property>
+       </widget>
+      </item>
+      <item row="1" column="0" colspan="2">
+       <widget class="Line" name="line">
+        <property name="orientation">
+         <enum>Qt::Horizontal</enum>
+        </property>
+       </widget>
+      </item>
+      <item row="2" column="0">
+       <widget class="QCheckBox" name="joinLeaveCheckBox">
+        <property name="toolTip">
+         <string>Select to show a notification for join and leave events</string>
+        </property>
+        <property name="text">
+         <string>Join/Leave Event</string>
+        </property>
+       </widget>
+      </item>
+      <item row="2" column="1">
+       <widget class="QCheckBox" name="ownNickCheckBox">
+        <property name="toolTip">
+         <string>Select to show a notification for every mentioning of your nick</string>
+        </property>
+        <property name="text">
+         <string>Mentioning of Own Nick</string>
+        </property>
+       </widget>
+      </item>
+      <item row="3" column="0">
+       <widget class="QCheckBox" name="messageCheckBox">
+        <property name="toolTip">
+         <string>Select to show a notification for every message</string>
+        </property>
+        <property name="text">
+         <string>Every Message</string>
+        </property>
+       </widget>
+      </item>
+     </layout>
+    </widget>
+   </item>
+   <item>
+    <spacer name="verticalSpacer">
+     <property name="orientation">
+      <enum>Qt::Vertical</enum>
+     </property>
+     <property name="sizeHint" stdset="0">
+      <size>
+       <width>20</width>
+       <height>130</height>
+      </size>
+     </property>
+    </spacer>
+   </item>
+  </layout>
+ </widget>
+ <tabstops>
+  <tabstop>timestampGroup</tabstop>
+  <tabstop>timeFormatCombo</tabstop>
+  <tabstop>dateFormatCombo</tabstop>
+  <tabstop>showDateCheckBox</tabstop>
+  <tabstop>networkButton</tabstop>
+  <tabstop>serverButton</tabstop>
+  <tabstop>channelButton</tabstop>
+  <tabstop>errorButton</tabstop>
+  <tabstop>timestampButton</tabstop>
+  <tabstop>hyperlinkButton</tabstop>
+  <tabstop>nickButton</tabstop>
+  <tabstop>ownNickButton</tabstop>
+  <tabstop>joinButton</tabstop>
+  <tabstop>leaveButton</tabstop>
+  <tabstop>infoButton</tabstop>
+  <tabstop>notificationsGroup</tabstop>
+  <tabstop>joinLeaveCheckBox</tabstop>
+  <tabstop>messageCheckBox</tabstop>
+  <tabstop>ownNickCheckBox</tabstop>
+ </tabstops>
+ <resources/>
+ <connections/>
+</ui>
--- a/Preferences/__init__.py	Wed Nov 14 18:58:51 2012 +0100
+++ b/Preferences/__init__.py	Sun Nov 25 18:40:15 2012 +0100
@@ -911,6 +911,31 @@
 
     # if true, revert layouts to factory defaults
     resetLayout = False
+    
+    # defaults for IRC
+    ircDefaults = {
+        "ShowTimestamps": True,
+        "TimestampIncludeDate": False,
+        "TimeFormat": "hh:mm",
+        "DateFormat": "yyyy-MM-dd",
+        
+        "NetworkMessageColour": "#0000FF",
+        "ServerMessageColour": "#91640A",
+        "ErrorMessageColour": "#FF0000",
+        "TimestampColour": "#709070",
+        "HyperlinkColour": "#0000FF",
+        "ChannelMessageColour": "#000000",
+        "OwnNickColour": "#000000",
+        "NickColour": "#18B33C",
+        "JoinChannelColour": "#72D672",
+        "LeaveChannelColour": "#B00000",
+        "ChannelInfoColour": "#9E54B3",
+        
+        "ShowNotifications": True,
+        "NotifyJoinPart": True,
+        "NotifyMessage": False,
+        "NotifyNick": False,
+    }
 
 
 def readToolGroups(prefClass=Prefs):
@@ -2535,6 +2560,33 @@
     prefClass.settings.setValue("TrayStarter/" + key, value)
     
 
+def getIrc(key, prefClass=Prefs):
+    """
+    Module function to retrieve the IRC related settings.
+    
+    @param key the key of the value to get
+    @param prefClass preferences class used as the storage area
+    @return the requested user setting
+    """
+    if key in ["TimestampIncludeDate", "ShowTimestamps", "ShowNotifications",
+               "NotifyJoinPart", "NotifyMessage", "NotifyNick"]:
+        return toBool(prefClass.settings.value("IRC/" + key,
+                prefClass.ircDefaults[key]))
+    else:
+        return prefClass.settings.value("IRC/" + key, prefClass.ircDefaults[key])
+
+
+def setIrc(key, value, prefClass=Prefs):
+    """
+    Module function to store the IRC related settings.
+    
+    @param key the key of the setting to be set
+    @param value the value to be set
+    @param prefClass preferences class used as the storage area
+    """
+    prefClass.settings.setValue("IRC/" + key, value)
+
+
 def getGeometry(key, prefClass=Prefs):
     """
     Module function to retrieve the display geometry.
--- a/UI/UserInterface.py	Wed Nov 14 18:58:51 2012 +0100
+++ b/UI/UserInterface.py	Sun Nov 25 18:40:15 2012 +0100
@@ -66,6 +66,8 @@
 
 from Cooperation.ChatWidget import ChatWidget
 
+from Network.IRC.IrcWidget import IrcWidget
+
 from .Browser import Browser
 from .Info import Version, BugAddress, Program, FeatureAddress
 from . import Config
@@ -490,6 +492,7 @@
         e5App().registerObject("ToolbarManager", self.toolbarManager)
         e5App().registerObject("Terminal", self.terminal)
         e5App().registerObject("Cooperation", self.cooperation)
+        e5App().registerObject("IRC", self.irc)
         e5App().registerObject("Symbols", self.symbolsViewer)
         e5App().registerObject("Numbers", self.numbersViewer)
         
@@ -676,6 +679,12 @@
                               UI.PixmapCache.getIcon("cooperation.png"),
                               self.trUtf8("Cooperation"))
         
+        # Create the IRC part of the user interface
+        self.irc = IrcWidget(self)
+        self.rToolbox.addItem(self.irc,
+                              UI.PixmapCache.getIcon("irc.png"),
+                              self.trUtf8("IRC"))
+        
         # Create the terminal part of the user interface
         self.terminalAssembly = TerminalAssembly(self.viewmanager)
         self.terminal = self.terminalAssembly.terminal()
@@ -783,6 +792,12 @@
         self.rightSidebar.addTab(self.cooperation,
             UI.PixmapCache.getIcon("cooperation.png"), self.trUtf8("Cooperation"))
         
+        # Create the IRC part of the user interface
+        logging.debug("Creating IRC Widget...")
+        self.irc = IrcWidget(self)
+        self.rightSidebar.addTab(self.irc,
+            UI.PixmapCache.getIcon("irc.png"), self.trUtf8("IRC"))
+        
         # Create the terminal part of the user interface
         logging.debug("Creating Terminal...")
         self.terminalAssembly = TerminalAssembly(self.viewmanager)
@@ -1414,6 +1429,23 @@
         self.actions.append(self.cooperationViewerActivateAct)
         self.addAction(self.cooperationViewerActivateAct)
 
+        self.ircActivateAct = E5Action(
+                self.trUtf8('IRC'),
+                self.trUtf8('&IRC'),
+                QKeySequence(self.trUtf8("Meta+Shift+I")),
+                0, self,
+                'irc_widget_activate')
+        self.ircActivateAct.setStatusTip(self.trUtf8(
+            "Switch the input focus to the IRC window."))
+        self.ircActivateAct.setWhatsThis(self.trUtf8(
+            """<b>Activate IRC</b>"""
+            """<p>This switches the input focus to the IRC window.</p>"""
+        ))
+        self.ircActivateAct.triggered[()].connect(
+            self.__activateIRC)
+        self.actions.append(self.ircActivateAct)
+        self.addAction(self.ircActivateAct)
+
         self.symbolsViewerActivateAct = E5Action(
                 self.trUtf8('Symbols-Viewer'),
                 self.trUtf8('S&ymbols-Viewer'),
@@ -2154,18 +2186,22 @@
         
         self.__menus["subwindow"] = QMenu(self.trUtf8("&Windows"), self.__menus["window"])
         self.__menus["subwindow"].setTearOffEnabled(True)
+        # left side
         self.__menus["subwindow"].addAction(self.pbActivateAct)
         self.__menus["subwindow"].addAction(self.mpbActivateAct)
+        self.__menus["subwindow"].addAction(self.templateViewerActivateAct)
         self.__menus["subwindow"].addAction(self.browserActivateAct)
-        self.__menus["subwindow"].addAction(self.debugViewerActivateAct)
+        self.__menus["subwindow"].addAction(self.symbolsViewerActivateAct)
+        # bottom side
         self.__menus["subwindow"].addAction(self.shellActivateAct)
         self.__menus["subwindow"].addAction(self.terminalActivateAct)
+        self.__menus["subwindow"].addAction(self.taskViewerActivateAct)
         self.__menus["subwindow"].addAction(self.logViewerActivateAct)
-        self.__menus["subwindow"].addAction(self.taskViewerActivateAct)
-        self.__menus["subwindow"].addAction(self.templateViewerActivateAct)
+        self.__menus["subwindow"].addAction(self.numbersViewerActivateAct)
+        # right side
+        self.__menus["subwindow"].addAction(self.debugViewerActivateAct)
         self.__menus["subwindow"].addAction(self.cooperationViewerActivateAct)
-        self.__menus["subwindow"].addAction(self.symbolsViewerActivateAct)
-        self.__menus["subwindow"].addAction(self.numbersViewerActivateAct)
+        self.__menus["subwindow"].addAction(self.ircActivateAct)
         
         self.__menus["toolbars"] = \
             QMenu(self.trUtf8("&Toolbars"), self.__menus["window"])
@@ -3504,6 +3540,20 @@
             self.cooperation.show()
         self.cooperation.setFocus(Qt.ActiveWindowFocusReason)
         
+    def __activateIRC(self):
+        """
+        Public slot to handle the activation of the IRC window.
+        """
+        if self.layout == "Toolboxes":
+            self.rToolboxDock.show()
+            self.rToolbox.setCurrentWidget(self.irc)
+        elif self.layout == "Sidebars":
+            self.rightSidebar.show()
+            self.rightSidebar.setCurrentWidget(self.irc)
+        else:
+            self.irc.show()
+        self.irc.setFocus(Qt.ActiveWindowFocusReason)
+        
     def __activateSymbolsViewer(self):
         """
         Private slot to handle the activation of the Symbols Viewer.
@@ -5132,6 +5182,9 @@
         if not self.viewmanager.closeViewManager():
             return False
         
+        if not self.irc.shutdown():
+            return False
+        
         self.shell.closeShell()
         self.terminal.closeTerminal()
         
--- a/Utilities/__init__.py	Wed Nov 14 18:58:51 2012 +0100
+++ b/Utilities/__init__.py	Sun Nov 25 18:40:15 2012 +0100
@@ -1134,6 +1134,8 @@
     
     if not user and isWindowsPlatform():
         return win32_GetUserName()
+    
+    return user
 
 
 def getHomeDir():
--- a/Utilities/crypto/__init__.py	Wed Nov 14 18:58:51 2012 +0100
+++ b/Utilities/crypto/__init__.py	Sun Nov 25 18:40:15 2012 +0100
@@ -208,7 +208,7 @@
     
     @param pw password to encode (string)
     @param encode flag indicating an encode or decode function (boolean)
-    @return encode or decoded password (string)
+    @return encoded or decoded password (string)
     """
     if pw == "":
         return pw
--- a/changelog	Wed Nov 14 18:58:51 2012 +0100
+++ b/changelog	Sun Nov 25 18:40:15 2012 +0100
@@ -11,6 +11,8 @@
   -- added actions to search for the next/previous occurence of the current word
      (default shortcuts Ctrl+. and Ctrl+,)
   -- added some icons to the "Languages" and "End-of-Line Type" context menus
+- IRC
+  -- added a simple IRC client
 - Spell Checker
   -- added a dialog to edit the various spell checking dictionaries
 - Syntax Checker
--- a/eric5.e4p	Wed Nov 14 18:58:51 2012 +0100
+++ b/eric5.e4p	Sun Nov 25 18:40:15 2012 +0100
@@ -1054,6 +1054,14 @@
     <Source>Preferences/ConfigurationPages/NotificationsPage.py</Source>
     <Source>QScintilla/SpellingDictionaryEditDialog.py</Source>
     <Source>E5Gui/E5ComboBox.py</Source>
+    <Source>Network/__init__.py</Source>
+    <Source>Network/IRC/__init__.py</Source>
+    <Source>Network/IRC/IrcNetworkManager.py</Source>
+    <Source>Network/IRC/IrcChannelWidget.py</Source>
+    <Source>Network/IRC/IrcNetworkWidget.py</Source>
+    <Source>Network/IRC/IrcWidget.py</Source>
+    <Source>Network/IRC/IrcUtilities.py</Source>
+    <Source>Preferences/ConfigurationPages/IrcPage.py</Source>
   </Sources>
   <Forms>
     <Form>PyUnit/UnittestDialog.ui</Form>
@@ -1358,6 +1366,10 @@
     <Form>UI/NotificationWidget.ui</Form>
     <Form>Preferences/ConfigurationPages/NotificationsPage.ui</Form>
     <Form>QScintilla/SpellingDictionaryEditDialog.ui</Form>
+    <Form>Network/IRC/IrcChannelWidget.ui</Form>
+    <Form>Network/IRC/IrcWidget.ui</Form>
+    <Form>Network/IRC/IrcNetworkWidget.ui</Form>
+    <Form>Preferences/ConfigurationPages/IrcPage.ui</Form>
   </Forms>
   <Translations>
     <Translation>i18n/eric5_cs.qm</Translation>
Binary file icons/default/irc.png has changed
Binary file icons/default/irc128.png has changed
Binary file icons/default/irc48.png has changed
Binary file icons/default/ircAdmin.png has changed
Binary file icons/default/ircAway.png has changed
Binary file icons/default/ircCloseChannel.png has changed
Binary file icons/default/ircConfigure.png has changed
Binary file icons/default/ircConnect.png has changed
Binary file icons/default/ircDisconnect.png has changed
Binary file icons/default/ircHalfop.png has changed
Binary file icons/default/ircJoinChannel.png has changed
Binary file icons/default/ircNormal.png has changed
Binary file icons/default/ircOp.png has changed
Binary file icons/default/ircOwner.png has changed
Binary file icons/default/ircVoice.png has changed

eric ide

mercurial