Sun, 25 Nov 2012 18:40:15 +0100
First commit of the simple IRC client for eric. It is usable but not yet complete.
--- 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\-\.,@?^=%&:/~\+#]*[\w\-\@?^=%&/~\+#])?)""") + + JoinIndicator = "-->" + LeaveIndicator = "<--" + 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><</b><font color="{1}">{3}</font>' + '<b>></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><</b><font color="{1}">{3}</font>' + '<b>></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\-\.,@?^=%&:/~\+#]*[\w\-\@?^=%&/~\+#])?)""") +__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><b>Configure IRC</b></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><b>Note:</b> 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>