Continued with the implementation of the simple IRC client.

Fri, 07 Dec 2012 19:48:23 +0100

author
Detlev Offenbach <detlev@die-offenbachs.de>
date
Fri, 07 Dec 2012 19:48:23 +0100
changeset 2252
1fc32bd13be3
parent 2250
810e9c7b61e3
child 2253
7ba2af1ff785

Continued with the implementation of the simple IRC client.

IconEditor/cursors/cursors_rc.py file | annotate | diff | comparison | revisions
Network/IRC/IrcChannelWidget.py file | annotate | diff | comparison | revisions
Network/IRC/IrcNetworkWidget.py file | annotate | diff | comparison | revisions
Network/IRC/IrcNetworkWidget.ui file | annotate | diff | comparison | revisions
Network/IRC/IrcWidget.py file | annotate | diff | comparison | revisions
--- a/IconEditor/cursors/cursors_rc.py	Fri Dec 07 18:35:54 2012 +0100
+++ b/IconEditor/cursors/cursors_rc.py	Fri Dec 07 19:48:23 2012 +0100
@@ -2,8 +2,8 @@
 
 # Resource object code
 #
-# Created: Do. Dez 31 16:47:09 2009
-#      by: The Resource Compiler for PyQt (Qt v4.5.3)
+# Created: Fr. Dez 7 18:40:19 2012
+#      by: The Resource Compiler for PyQt (Qt v4.8.3)
 #
 # WARNING! All changes made in this file will be lost!
 
@@ -62,50 +62,16 @@
 \x2e\x2e\x2e\x2e\x2e\x22\x2c\x0a\x22\x23\x69\x23\x2e\x2e\x2e\x2e\
 \x2e\x2e\x2e\x2e\x2e\x2e\x2e\x2e\x2e\x2e\x2e\x2e\x2e\x2e\x2e\x22\
 \x7d\x3b\x0a\
-\x00\x00\x02\x93\
-\x2f\
-\x2a\x20\x58\x50\x4d\x20\x2a\x2f\x0a\x73\x74\x61\x74\x69\x63\x20\
-\x63\x68\x61\x72\x20\x2a\x61\x69\x6d\x5b\x5d\x3d\x7b\x0a\x22\x32\
-\x32\x20\x32\x32\x20\x33\x20\x31\x22\x2c\x0a\x22\x2e\x20\x63\x20\
-\x4e\x6f\x6e\x65\x22\x2c\x0a\x22\x61\x20\x63\x20\x23\x30\x30\x30\
-\x30\x30\x30\x22\x2c\x0a\x22\x23\x20\x63\x20\x23\x66\x66\x66\x66\
-\x66\x66\x22\x2c\x0a\x22\x2e\x2e\x2e\x2e\x2e\x2e\x2e\x2e\x2e\x2e\
-\x2e\x2e\x2e\x2e\x2e\x2e\x2e\x2e\x2e\x2e\x2e\x2e\x22\x2c\x0a\x22\
-\x2e\x2e\x2e\x2e\x2e\x2e\x2e\x2e\x2e\x2e\x23\x2e\x2e\x2e\x2e\x2e\
-\x2e\x2e\x2e\x2e\x2e\x2e\x22\x2c\x0a\x22\x2e\x2e\x2e\x2e\x2e\x2e\
-\x2e\x2e\x2e\x23\x61\x23\x2e\x2e\x2e\x2e\x2e\x2e\x2e\x2e\x2e\x2e\
-\x22\x2c\x0a\x22\x2e\x2e\x2e\x2e\x2e\x2e\x2e\x2e\x2e\x23\x61\x23\
-\x2e\x2e\x2e\x2e\x2e\x2e\x2e\x2e\x2e\x2e\x22\x2c\x0a\x22\x2e\x2e\
-\x2e\x2e\x2e\x2e\x2e\x2e\x2e\x23\x61\x23\x2e\x2e\x2e\x2e\x2e\x2e\
-\x2e\x2e\x2e\x2e\x22\x2c\x0a\x22\x2e\x2e\x2e\x2e\x2e\x2e\x2e\x2e\
-\x2e\x23\x61\x23\x2e\x2e\x2e\x2e\x2e\x2e\x2e\x2e\x2e\x2e\x22\x2c\
-\x0a\x22\x2e\x2e\x2e\x2e\x2e\x2e\x2e\x2e\x2e\x23\x61\x23\x2e\x2e\
-\x2e\x2e\x2e\x2e\x2e\x2e\x2e\x2e\x22\x2c\x0a\x22\x2e\x2e\x2e\x2e\
-\x2e\x2e\x2e\x2e\x2e\x23\x61\x23\x2e\x2e\x2e\x2e\x2e\x2e\x2e\x2e\
-\x2e\x2e\x22\x2c\x0a\x22\x2e\x2e\x2e\x2e\x2e\x2e\x2e\x2e\x2e\x23\
-\x61\x23\x2e\x2e\x2e\x2e\x2e\x2e\x2e\x2e\x2e\x2e\x22\x2c\x0a\x22\
-\x2e\x2e\x23\x23\x23\x23\x23\x23\x23\x2e\x2e\x2e\x23\x23\x23\x23\
-\x23\x23\x23\x2e\x2e\x2e\x22\x2c\x0a\x22\x2e\x23\x61\x61\x61\x61\
-\x61\x61\x61\x2e\x2e\x2e\x61\x61\x61\x61\x61\x61\x61\x23\x2e\x2e\
-\x22\x2c\x0a\x22\x2e\x2e\x23\x23\x23\x23\x23\x23\x23\x2e\x2e\x2e\
-\x23\x23\x23\x23\x23\x23\x23\x2e\x2e\x2e\x22\x2c\x0a\x22\x2e\x2e\
-\x2e\x2e\x2e\x2e\x2e\x2e\x2e\x2e\x61\x2e\x2e\x2e\x2e\x2e\x2e\x2e\
-\x2e\x2e\x2e\x2e\x22\x2c\x0a\x22\x2e\x2e\x2e\x2e\x2e\x2e\x2e\x2e\
-\x2e\x23\x61\x23\x2e\x2e\x2e\x2e\x2e\x2e\x2e\x2e\x2e\x2e\x22\x2c\
-\x0a\x22\x2e\x2e\x2e\x2e\x2e\x2e\x2e\x2e\x2e\x23\x61\x23\x2e\x2e\
-\x2e\x2e\x2e\x2e\x2e\x2e\x2e\x2e\x22\x2c\x0a\x22\x2e\x2e\x2e\x2e\
-\x2e\x2e\x2e\x2e\x2e\x23\x61\x23\x2e\x2e\x2e\x2e\x2e\x2e\x2e\x2e\
-\x2e\x2e\x22\x2c\x0a\x22\x2e\x2e\x2e\x2e\x2e\x2e\x2e\x2e\x2e\x23\
-\x61\x23\x2e\x2e\x2e\x2e\x2e\x2e\x2e\x2e\x2e\x2e\x22\x2c\x0a\x22\
-\x2e\x2e\x2e\x2e\x2e\x2e\x2e\x2e\x2e\x23\x61\x23\x2e\x2e\x2e\x2e\
-\x2e\x2e\x2e\x2e\x2e\x2e\x22\x2c\x0a\x22\x2e\x2e\x2e\x2e\x2e\x2e\
-\x2e\x2e\x2e\x23\x61\x23\x2e\x2e\x2e\x2e\x2e\x2e\x2e\x2e\x2e\x2e\
-\x22\x2c\x0a\x22\x2e\x2e\x2e\x2e\x2e\x2e\x2e\x2e\x2e\x2e\x23\x2e\
-\x2e\x2e\x2e\x2e\x2e\x2e\x2e\x2e\x2e\x2e\x22\x2c\x0a\x22\x2e\x2e\
-\x2e\x2e\x2e\x2e\x2e\x2e\x2e\x2e\x2e\x2e\x2e\x2e\x2e\x2e\x2e\x2e\
-\x2e\x2e\x2e\x2e\x22\x2c\x0a\x22\x2e\x2e\x2e\x2e\x2e\x2e\x2e\x2e\
-\x2e\x2e\x2e\x2e\x2e\x2e\x2e\x2e\x2e\x2e\x2e\x2e\x2e\x2e\x22\x7d\
-\x3b\x0a\
+\x00\x00\x00\x7d\
+\x00\
+\x00\x02\x93\x78\x9c\xd3\xd7\x52\x88\x08\xf0\x55\xd0\xd2\xe7\x2a\
+\x2e\x49\x2c\xc9\x4c\x56\x48\xce\x48\x2c\x52\xd0\x4a\xcc\xcc\x8d\
+\x8e\xb5\xad\xe6\x52\x32\x32\x52\x00\x22\x63\x05\x43\x25\x1d\x2e\
+\x25\x3d\x85\x64\x05\xbf\xfc\xbc\x54\x10\x3b\x11\xc8\x56\x36\x00\
+\x03\x10\x57\x19\xc4\x4d\x03\x03\xb0\x4a\xac\x00\x55\x46\x19\x97\
+\x8c\x72\xa2\xf2\x50\x92\x51\x86\x00\x3d\x64\x16\x58\x46\x39\x11\
+\x02\x80\x7c\x28\x4b\x99\xa0\x1e\x38\x48\x1c\x8a\xa1\x83\x3b\x4e\
+\x51\x00\x1e\x99\x5a\x6b\x2e\x00\x12\x9a\x79\xb2\
 \x00\x00\x02\xe1\
 \x2f\
 \x2a\x20\x58\x50\x4d\x20\x2a\x2f\x0a\x73\x74\x61\x74\x69\x63\x20\
@@ -311,19 +277,17 @@
 
 qt_resource_struct = b"\
 \x00\x00\x00\x00\x00\x02\x00\x00\x00\x06\x00\x00\x00\x01\
-\x00\x00\x00\x54\x00\x00\x00\x00\x00\x01\x00\x00\x05\xaf\
-\x00\x00\x00\xa0\x00\x00\x00\x00\x00\x01\x00\x00\x0b\xa5\
-\x00\x00\x00\x32\x00\x00\x00\x00\x00\x01\x00\x00\x03\x18\
-\x00\x00\x00\xbc\x00\x00\x00\x00\x00\x01\x00\x00\x0c\xb0\
+\x00\x00\x00\x54\x00\x00\x00\x00\x00\x01\x00\x00\x03\x99\
+\x00\x00\x00\xa0\x00\x00\x00\x00\x00\x01\x00\x00\x09\x8f\
+\x00\x00\x00\x32\x00\x01\x00\x00\x00\x01\x00\x00\x03\x18\
+\x00\x00\x00\xbc\x00\x00\x00\x00\x00\x01\x00\x00\x0a\x9a\
 \x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\
-\x00\x00\x00\x7c\x00\x00\x00\x00\x00\x01\x00\x00\x08\x94\
+\x00\x00\x00\x7c\x00\x00\x00\x00\x00\x01\x00\x00\x06\x7e\
 "
 
-
 def qInitResources():
     QtCore.qRegisterResourceData(0x01, qt_resource_struct, qt_resource_name, qt_resource_data)
 
-
 def qCleanupResources():
     QtCore.qUnregisterResourceData(0x01, qt_resource_struct, qt_resource_name, qt_resource_data)
 
--- a/Network/IRC/IrcChannelWidget.py	Fri Dec 07 18:35:54 2012 +0100
+++ b/Network/IRC/IrcChannelWidget.py	Fri Dec 07 19:48:23 2012 +0100
@@ -9,10 +9,10 @@
 
 import re
 
-from PyQt4.QtCore import pyqtSlot, pyqtSignal, QDateTime, QPoint
-from PyQt4.QtGui import QWidget, QListWidgetItem, QIcon, QPainter, QMenu
+from PyQt4.QtCore import pyqtSlot, pyqtSignal, QDateTime, QPoint, QFileInfo
+from PyQt4.QtGui import QWidget, QListWidgetItem, QIcon, QPainter, QMenu, QApplication
 
-from E5Gui import E5MessageBox
+from E5Gui import E5MessageBox, E5FileDialog
 from E5Gui.E5Application import e5App
 
 from .Ui_IrcChannelWidget import Ui_IrcChannelWidget
@@ -142,9 +142,11 @@
     
     @signal sendData(str) emitted to send a message to the channel
     @signal channelClosed(str) emitted after the user has left the channel
+    @signal openPrivateChat(str) emitted to open a "channel" for private messages
     """
     sendData = pyqtSignal(str)
     channelClosed = pyqtSignal(str)
+    openPrivateChat = pyqtSignal(str)
     
     UrlRe = re.compile(r"""((?:http|ftp|https):\/\/[\w\-_]+(?:\.[\w\-_]+)+"""
         r"""(?:[\w\-\.,@?^=%&amp;:/~\+#]*[\w\-\@?^=%&amp;/~\+#])?)""")
@@ -163,14 +165,6 @@
     #       * The user is an IRC operator.
     #       @ The user is a channel op in the channel listed in the first field.
     #       + The user is voiced in the channel listed.
-    # TODO: add context menu to messages pane with these entries:
-    #       Copy
-    #       Copy Link Location
-    #       Copy All
-    #       Clear
-    #       Save
-    #       Remember Position
-    # TODO: Remember current position with <hr/> when widget is invisible
     # TODO: Check away indication in the user list
     def __init__(self, parent=None):
         """
@@ -184,13 +178,17 @@
         self.__ui = e5App().getObject("UserInterface")
         
         self.__initMessagesMenu()
+        self.__initUsersMenu()
         
         self.__name = ""
         self.__userName = ""
         self.__partMessage = ""
         self.__prefixToPrivilege = {}
+        self.__private = False
+        self.__privatePartner = ""
         
         self.__markerLine = ""
+        self.__hidden = True
         
         self.__patterns = [
             # :foo_!n=foo@foohost.bar.net PRIVMSG #eric-ide :some long message
@@ -211,6 +209,8 @@
             # :barty!n=foo@foohost.bar.net MODE #eric-ide +o foo_
             (re.compile(r":([^!]+).*\sMODE\s([^ ]+)\s([^ ]+)\s([^ ]+).*"),
                 self.__setUserPrivilege),
+            # :sturgeon.freenode.net 301 foo_ bar :Gone away for now
+            (re.compile(r":.*\s301\s([^ ]+)\s([^ ]+)\s:(.+)"), self.__userAway),
             # :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/
@@ -225,6 +225,8 @@
             (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),
+            # :sturgeon.freenode.net 704 foo_ index :Help topics available to users:
+            (re.compile(r":.*\s70[456]\s[^ ]+\s([^ ]+)\s:(.*)"), self.__help),
         ]
     
     @pyqtSlot()
@@ -240,7 +242,24 @@
                 Preferences.getIrc("ChannelMessageColour"),
                 Preferences.getIrc("OwnNickColour"),
                 ircTimestamp(), self.__userName, Utilities.html_encode(msg)))
-            self.sendData.emit("PRIVMSG " + self.__name + " :" + msg)
+            if msg.startswith("/"):
+                if self.__private:
+                    E5MessageBox.information(self,
+                        self.trUtf8("Send Message"),
+                        self.trUtf8("""Messages starting with a '/' are not allowed"""
+                                    """ in private chats."""))
+                else:
+                    msgList = msg.split(None, 1)
+                    cmd = msgList[0][1:].upper()
+                    if cmd == "MSG":
+                        cmd = "PRIVMSG"
+                    msgList[0] = cmd
+                    self.sendData.emit(" ".join(msgList))
+            else:
+                if self.__private:
+                    self.sendData.emit("PRIVMSG " + self.__privatePartner + " :" + msg)
+                else:
+                    self.sendData.emit("PRIVMSG " + self.__name + " :" + msg)
             self.messageEdit.clear()
     
     def requestLeave(self):
@@ -311,6 +330,16 @@
         """
         self.__partMessage = message
     
+    def setPrivate(self, private, partner=""):
+        """
+        Public method to set the private chat mode.
+        
+        @param private flag indicating private chat mode (boolean)
+        @param partner name of the partner user (string)
+        """
+        self.__private = private
+        self.__privatePartner = partner
+    
     def handleMessage(self, line):
         """
         Public method to handle the message sent by the server.
@@ -335,7 +364,7 @@
         """
         if match.group(2).lower() == self.__name:
             msg = ircFilter(match.group(3))
-            self.messages.append(
+            self.__appendMessage(
                 '<font color="{0}">{2} <b>&lt;</b><font color="{1}">{3}</font>'
                 '<b>&gt;</b> {4}</font>'.format(
                 Preferences.getIrc("ChannelMessageColour"),
@@ -353,6 +382,17 @@
         
         return False
     
+    def addUsers(self, users):
+        """
+        Public method to add users to the channel.
+        
+        @param users list of user names to add (list of string)
+        """
+        for user in users:
+            itm = self.__findUser(user)
+            if itm is None:
+                IrcUserItem(user, self.usersList)
+    
     def __userJoin(self, match):
         """
         Private method to handle a user joining the channel.
@@ -478,6 +518,20 @@
         
         return False
     
+    def __userAway(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.__addManagementMessage(self.trUtf8("Away"),
+                self.trUtf8("{0} is away: {1}").format(match.group(2), match.group(3)))
+            return True
+        
+        return False
+    
     def __setTopic(self, match):
         """
         Private method to handle a topic change of the channel.
@@ -605,6 +659,17 @@
         
         return False
     
+    def __help(self, match):
+        """
+        Private method to handle a help message.
+        
+        @param match match object that matched the pattern
+        @return flag indicating whether the message was handled (boolean)
+        """
+        self.__addManagementMessage(self.trUtf8("Help"),
+            "{0} {1}".format(match.group(1), ircFilter(match.group(2))))
+        return True
+    
     def setUserPrivilegePrefix(self, prefixes):
         """
         Public method to set the user privilege to prefix mapping.
@@ -661,10 +726,18 @@
             color = Preferences.getIrc("LeaveChannelColour")
         else:
             color = Preferences.getIrc("ChannelInfoColour")
-        self.messages.append(
+        self.__appendMessage(
             '<font color="{0}">{1} <b>[</b>{2}<b>]</b> {3}</font>'.format(
             color, ircTimestamp(), indicator, message))
     
+    def __appendMessage(self, message):
+        """
+        Private slot to append a message.
+        """
+        if self.__hidden and self.__markerLine == "":
+            self.setMarkerLine()
+        self.messages.append(message)
+    
     def setMarkerLine(self):
         """
         Public method to draw a line to mark the current position.
@@ -691,58 +764,209 @@
             self.messages.setHtml(txt)
             self.__markerLine = ""
     
+    def __clearMessages(self):
+        """
+        Private slot to clear the contents of the messages display.
+        """
+        self.messages.clear()
+    
+    def __copyMessages(self):
+        """
+        Private slot to copy the selection of the messages display to the clipboard.
+        """
+        self.messages.copy()
+    
+    def __cutMessages(self):
+        """
+        Private slot to cut the selection of the messages display to the clipboard.
+        """
+        self.messages.cut()
+    
+    def __copyAllMessages(self):
+        """
+        Private slot to copy the contents of the messages display to the clipboard.
+        """
+        txt = self.messages.toPlainText()
+        if txt:
+            cb = QApplication.clipboard()
+            cb.setText(txt)
+    
+    def __cutAllMessages(self):
+        """
+        Private slot to cut the contents of the messages display to the clipboard.
+        """
+        txt = self.messages.toPlainText()
+        if txt:
+            cb = QApplication.clipboard()
+            cb.setText(txt)
+        self.messages.clear()
+    
+    def __saveMessages(self):
+        """
+        Private slot to save the contents of the messages display.
+        """
+        hasText = not self.messages.document().isEmpty()
+        if hasText:
+            if Utilities.isWindowsPlatform():
+                htmlExtension = "htm"
+            else:
+                htmlExtension = "html"
+            fname, selectedFilter = E5FileDialog.getSaveFileNameAndFilter(
+                self,
+                self.trUtf8("Save Messages"),
+                "",
+                self.trUtf8(
+                    "HTML Files (*.{0});;Text Files (*.txt);;All Files (*)").format(
+                    htmlExtension),
+                None,
+                E5FileDialog.Options(E5FileDialog.DontConfirmOverwrite))
+            if fname:
+                ext = QFileInfo(fname).suffix()
+                if not ext:
+                    ex = selectedFilter.split("(*")[1].split(")")[0]
+                    if ex:
+                        fname += ex
+                    ext = QFileInfo(fname).suffix()
+                if QFileInfo(fname).exists():
+                    res = E5MessageBox.yesNo(self,
+                        self.trUtf8("Save Messages"),
+                        self.trUtf8("<p>The file <b>{0}</b> already exists."
+                                    " Overwrite it?</p>").format(fname),
+                        icon=E5MessageBox.Warning)
+                    if not res:
+                        return
+                    fname = Utilities.toNativeSeparators(fname)
+                
+                try:
+                    if ext.lower() in ["htm", "html"]:
+                        txt = self.messages.toHtml()
+                    else:
+                        txt = self.messages.toPlainText()
+                    f = open(fname, "w", encoding="utf-8")
+                    f.write(txt)
+                    f.close()
+                except IOError as err:
+                    E5MessageBox.critical(self,
+                        self.trUtf8("Error saving Messages"),
+                        self.trUtf8("""<p>The messages contents could not be written"""
+                                    """ to <b>{0}</b></p><p>Reason: {1}</p>""")\
+                            .format(fname, str(err)))
+    
     def __initMessagesMenu(self):
         """
         Private slot to initialize the context menu of the messages pane.
         """
         self.__messagesMenu = QMenu(self)
-##        self.__cutMessagesAct = \
-##            self.__messagesMenu.addAction(
-##                UI.PixmapCache.getIcon("editCut.png"),
-##                self.trUtf8("Cut"), self.__cutMessages)
-##        self.__copyMessagesAct = \
-##            self.__messagesMenu.addAction(
-##                UI.PixmapCache.getIcon("editCopy.png"),
-##                self.trUtf8("Copy"), self.__copyMessages)
-##        self.__messagesMenu.addSeparator()
-##        self.__cutAllMessagesAct = \
-##            self.__messagesMenu.addAction(
-##                UI.PixmapCache.getIcon("editCut.png"),
-##                self.trUtf8("Cut all"), self.__cutAllMessages)
-##        self.__copyAllMessagesAct = \
-##            self.__messagesMenu.addAction(
-##                UI.PixmapCache.getIcon("editCopy.png"),
-##                self.trUtf8("Copy all"), self.__copyAllMessages)
-##        self.__messagesMenu.addSeparator()
-##        self.__clearMessagesAct = \
-##            self.__messagesMenu.addAction(
-##                UI.PixmapCache.getIcon("editDelete.png"),
-##                self.trUtf8("Clear"), self.__clearMessages)
-##        self.__messagesMenu.addSeparator()
-##        self.__saveMessagesAct = \
-##            self.__messagesMenu.addAction(
-##                UI.PixmapCache.getIcon("fileSave.png"),
-##                self.trUtf8("Save"), self.__saveMessages)
+        self.__cutMessagesAct = \
+            self.__messagesMenu.addAction(
+                UI.PixmapCache.getIcon("editCut.png"),
+                self.trUtf8("Cut"), self.__cutMessages)
+        self.__copyMessagesAct = \
+            self.__messagesMenu.addAction(
+                UI.PixmapCache.getIcon("editCopy.png"),
+                self.trUtf8("Copy"), self.__copyMessages)
+        self.__messagesMenu.addSeparator()
+        self.__cutAllMessagesAct = \
+            self.__messagesMenu.addAction(
+                UI.PixmapCache.getIcon("editCut.png"),
+                self.trUtf8("Cut all"), self.__cutAllMessages)
+        self.__copyAllMessagesAct = \
+            self.__messagesMenu.addAction(
+                UI.PixmapCache.getIcon("editCopy.png"),
+                self.trUtf8("Copy all"), self.__copyAllMessages)
+        self.__messagesMenu.addSeparator()
+        self.__clearMessagesAct = \
+            self.__messagesMenu.addAction(
+                UI.PixmapCache.getIcon("editDelete.png"),
+                self.trUtf8("Clear"), self.__clearMessages)
+        self.__messagesMenu.addSeparator()
+        self.__saveMessagesAct = \
+            self.__messagesMenu.addAction(
+                UI.PixmapCache.getIcon("fileSave.png"),
+                self.trUtf8("Save"), self.__saveMessages)
+        self.__messagesMenu.addSeparator()
         self.__setMarkerMessagesAct = \
             self.__messagesMenu.addAction(self.trUtf8("Mark Current Position"),
                 self.setMarkerLine)
         self.__unsetMarkerMessagesAct = \
             self.__messagesMenu.addAction(self.trUtf8("Remove Position Marker"),
                 self.unsetMarkerLine)
+        
+        self.on_messages_copyAvailable(False)
+    
+    @pyqtSlot(bool)
+    def on_messages_copyAvailable(self, yes):
+        """
+        Private slot to react to text selection/deselection of the messages edit.
+        
+        @param yes flag signaling the availability of selected text (boolean)
+        """
+        self.__copyMessagesAct.setEnabled(yes)
+        self.__cutMessagesAct.setEnabled(yes)
     
     @pyqtSlot(QPoint)
     def on_messages_customContextMenuRequested(self, pos):
         """
         Private slot to show the context menu of the messages pane.
+        
+        @param pos the position of the mouse pointer (QPoint)
         """
+        enable = not self.messages.document().isEmpty()
+        self.__cutAllMessagesAct.setEnabled(enable)
+        self.__copyAllMessagesAct.setEnabled(enable)
+        self.__saveMessagesAct.setEnabled(enable)
         self.__setMarkerMessagesAct.setEnabled(self.__markerLine == "")
         self.__unsetMarkerMessagesAct.setEnabled(self.__markerLine != "")
         self.__messagesMenu.popup(self.messages.mapToGlobal(pos))
     
+    def __whoIs(self):
+        """
+        Private slot to get information about the selected user.
+        """
+        # TODO: not implemented yet
+        return
+    
+    def __openPrivateChat(self):
+        """
+        Private slot to open a chat with the selected user.
+        """
+        user = self.usersList.selectedItems()[0].text()
+        self.openPrivateChat.emit(user)
+    
+    def __initUsersMenu(self):
+        """
+        Private slot to initialize the users list context menu.
+        """
+        self.__usersMenu = QMenu(self)
+        self.__usersMenu.addAction(self.trUtf8("Who Is"), self.__whoIs)
+        self.__usersMenu.addSeparator()
+        self.__privateChatAct = \
+            self.__usersMenu.addAction(self.trUtf8("Private Chat"),
+            self.__openPrivateChat)
+    
     @pyqtSlot(QPoint)
     def on_usersList_customContextMenuRequested(self, pos):
         """
         Private slot to show the context menu of the users list.
+        
+        @param pos the position of the mouse pointer (QPoint)
         """
-        # TODO: not implemented yet
-        return
+        self.__privateChatAct.setEnabled(not self.__private)
+        if len(self.usersList.selectedItems()) > 0:
+            self.__usersMenu.popup(self.usersList.mapToGlobal(pos))
+    
+    def hideEvent(self, evt):
+        """
+        Protected method handling hide events.
+        
+        @param evt reference to the hide event (QHideEvent)
+        """
+        self.__hidden = True
+    
+    def showEvent(self, evt):
+        """
+        Protected method handling show events.
+        
+        @param evt reference to the show event (QShowEvent)
+        """
+        self.__hidden = False
--- a/Network/IRC/IrcNetworkWidget.py	Fri Dec 07 18:35:54 2012 +0100
+++ b/Network/IRC/IrcNetworkWidget.py	Fri Dec 07 19:48:23 2012 +0100
@@ -7,8 +7,10 @@
 Module implementing the network part of the IRC widget.
 """
 
-from PyQt4.QtCore import pyqtSlot, pyqtSignal
-from PyQt4.QtGui import QWidget
+from PyQt4.QtCore import pyqtSlot, pyqtSignal, QPoint, QFileInfo
+from PyQt4.QtGui import QWidget, QApplication, QMenu
+
+from E5Gui import E5MessageBox, E5FileDialog
 
 from .Ui_IrcNetworkWidget import Ui_IrcNetworkWidget
 
@@ -16,6 +18,7 @@
 
 import UI.PixmapCache
 import Preferences
+import Utilities
 
 
 class IrcNetworkWidget(QWidget, Ui_IrcNetworkWidget):
@@ -37,12 +40,6 @@
     away = pyqtSignal(bool)
     
     
-    # TODO: add context menu to messages pane with these entries:
-    #       Copy
-    #       Copy Link Location
-    #       Copy All
-    #       Clear
-    #       Save
     def __init__(self, parent=None):
         """
         Constructor
@@ -61,6 +58,8 @@
         self.nickCombo.setEnabled(False)
         self.awayButton.setEnabled(False)
         
+        self.__initMessagesMenu()
+        
         self.__manager = None
         self.__connected = False
         self.__registered = False
@@ -291,3 +290,147 @@
         if registered:
             self.awayButton.setIcon(UI.PixmapCache.getIcon("ircUserPresent.png"))
             self.__away = False
+    
+    def __clearMessages(self):
+        """
+        Private slot to clear the contents of the messages display.
+        """
+        self.messages.clear()
+    
+    def __copyMessages(self):
+        """
+        Private slot to copy the selection of the messages display to the clipboard.
+        """
+        self.messages.copy()
+    
+    def __cutMessages(self):
+        """
+        Private slot to cut the selection of the messages display to the clipboard.
+        """
+        self.messages.cut()
+    
+    def __copyAllMessages(self):
+        """
+        Private slot to copy the contents of the messages display to the clipboard.
+        """
+        txt = self.messages.toPlainText()
+        if txt:
+            cb = QApplication.clipboard()
+            cb.setText(txt)
+    
+    def __cutAllMessages(self):
+        """
+        Private slot to cut the contents of the messages display to the clipboard.
+        """
+        txt = self.messages.toPlainText()
+        if txt:
+            cb = QApplication.clipboard()
+            cb.setText(txt)
+        self.messages.clear()
+    
+    def __saveMessages(self):
+        """
+        Private slot to save the contents of the messages display.
+        """
+        hasText = not self.messages.document().isEmpty()
+        if hasText:
+            if Utilities.isWindowsPlatform():
+                htmlExtension = "htm"
+            else:
+                htmlExtension = "html"
+            fname, selectedFilter = E5FileDialog.getSaveFileNameAndFilter(
+                self,
+                self.trUtf8("Save Messages"),
+                "",
+                self.trUtf8(
+                    "HTML Files (*.{0});;Text Files (*.txt);;All Files (*)").format(
+                    htmlExtension),
+                None,
+                E5FileDialog.Options(E5FileDialog.DontConfirmOverwrite))
+            if fname:
+                ext = QFileInfo(fname).suffix()
+                if not ext:
+                    ex = selectedFilter.split("(*")[1].split(")")[0]
+                    if ex:
+                        fname += ex
+                    ext = QFileInfo(fname).suffix()
+                if QFileInfo(fname).exists():
+                    res = E5MessageBox.yesNo(self,
+                        self.trUtf8("Save Messages"),
+                        self.trUtf8("<p>The file <b>{0}</b> already exists."
+                                    " Overwrite it?</p>").format(fname),
+                        icon=E5MessageBox.Warning)
+                    if not res:
+                        return
+                    fname = Utilities.toNativeSeparators(fname)
+                
+                try:
+                    if ext.lower() in ["htm", "html"]:
+                        txt = self.messages.toHtml()
+                    else:
+                        txt = self.messages.toPlainText()
+                    f = open(fname, "w", encoding="utf-8")
+                    f.write(txt)
+                    f.close()
+                except IOError as err:
+                    E5MessageBox.critical(self,
+                        self.trUtf8("Error saving Messages"),
+                        self.trUtf8("""<p>The messages contents could not be written"""
+                                    """ to <b>{0}</b></p><p>Reason: {1}</p>""")\
+                            .format(fname, str(err)))
+    
+    def __initMessagesMenu(self):
+        """
+        Private slot to initialize the context menu of the messages pane.
+        """
+        self.__messagesMenu = QMenu(self)
+        self.__cutMessagesAct = \
+            self.__messagesMenu.addAction(
+                UI.PixmapCache.getIcon("editCut.png"),
+                self.trUtf8("Cut"), self.__cutMessages)
+        self.__copyMessagesAct = \
+            self.__messagesMenu.addAction(
+                UI.PixmapCache.getIcon("editCopy.png"),
+                self.trUtf8("Copy"), self.__copyMessages)
+        self.__messagesMenu.addSeparator()
+        self.__cutAllMessagesAct = \
+            self.__messagesMenu.addAction(
+                UI.PixmapCache.getIcon("editCut.png"),
+                self.trUtf8("Cut all"), self.__cutAllMessages)
+        self.__copyAllMessagesAct = \
+            self.__messagesMenu.addAction(
+                UI.PixmapCache.getIcon("editCopy.png"),
+                self.trUtf8("Copy all"), self.__copyAllMessages)
+        self.__messagesMenu.addSeparator()
+        self.__clearMessagesAct = \
+            self.__messagesMenu.addAction(
+                UI.PixmapCache.getIcon("editDelete.png"),
+                self.trUtf8("Clear"), self.__clearMessages)
+        self.__messagesMenu.addSeparator()
+        self.__saveMessagesAct = \
+            self.__messagesMenu.addAction(
+                UI.PixmapCache.getIcon("fileSave.png"),
+                self.trUtf8("Save"), self.__saveMessages)
+        
+        self.on_messages_copyAvailable(False)
+    
+    @pyqtSlot(bool)
+    def on_messages_copyAvailable(self, yes):
+        """
+        Private slot to react to text selection/deselection of the messages edit.
+        
+        @param yes flag signaling the availability of selected text (boolean)
+        """
+        self.__copyMessagesAct.setEnabled(yes)
+        self.__cutMessagesAct.setEnabled(yes)
+    
+    @pyqtSlot(QPoint)
+    def on_messages_customContextMenuRequested(self, pos):
+        """
+        Private slot to show the context menu of the messages pane.
+        """
+        enable = not self.messages.document().isEmpty()
+        self.__cutAllMessagesAct.setEnabled(enable)
+        self.__copyAllMessagesAct.setEnabled(enable)
+        self.__saveMessagesAct.setEnabled(enable)
+        self.__messagesMenu.popup(self.messages.mapToGlobal(pos))
--- a/Network/IRC/IrcNetworkWidget.ui	Fri Dec 07 18:35:54 2012 +0100
+++ b/Network/IRC/IrcNetworkWidget.ui	Fri Dec 07 19:48:23 2012 +0100
@@ -19,6 +19,9 @@
    </property>
    <item>
     <widget class="QTextBrowser" name="messages">
+     <property name="contextMenuPolicy">
+      <enum>Qt::CustomContextMenu</enum>
+     </property>
      <property name="toolTip">
       <string>Shows the network messages</string>
      </property>
--- a/Network/IRC/IrcWidget.py	Fri Dec 07 18:35:54 2012 +0100
+++ b/Network/IRC/IrcWidget.py	Fri Dec 07 19:48:23 2012 +0100
@@ -248,6 +248,7 @@
         
         channel.sendData.connect(self.__send)
         channel.channelClosed.connect(self.__closeChannel)
+        channel.openPrivateChat.connect(self.__openPrivate)
         
         self.channelsWidget.addTab(channel, name)
         self.__channelList.append(channel)
@@ -264,6 +265,28 @@
             self.__leaveButton.setEnabled(True)
         self.channelsWidget.setTabsClosable(True)
     
+    @pyqtSlot(str)
+    def __openPrivate(self, name):
+        """
+        Private slot to open a private chat with the given user.
+        
+        @param name name of the user (string)
+        """
+        channel = IrcChannelWidget(self)
+        channel.setName(self.__nickName)
+        channel.setUserName(self.__nickName)
+        identity = self.__ircNetworkManager.getIdentity(self.__identityName)
+        channel.setPartMessage(identity.getPartMessage())
+        channel.setUserPrivilegePrefix(self.__userPrefix)
+        channel.setPrivate(True, name)
+        channel.addUsers([name, self.__nickName])
+        
+        channel.sendData.connect(self.__send)
+        channel.channelClosed.connect(self.__closeChannel)
+        
+        self.channelsWidget.addTab(channel, name)
+        self.__channelList.append(channel)
+    
     @pyqtSlot()
     def __leaveChannel(self):
         """
@@ -461,6 +484,10 @@
                     self.trUtf8("User {0} is now known as {1}.").format(
                     oldNick, newNick))
             return True
+        elif name == "ERROR":
+            self.networkWidget.addErrorMessage(
+                self.trUtf8("Server Error"), match.group(3).split(":", 1)[1])
+            return True
         
         return False
     

eric ide

mercurial