Network/IRC/IrcChannelWidget.py

changeset 2253
7ba2af1ff785
parent 2252
1fc32bd13be3
child 2254
14f923d13971
equal deleted inserted replaced
2252:1fc32bd13be3 2253:7ba2af1ff785
7 Module implementing the IRC channel widget. 7 Module implementing the IRC channel widget.
8 """ 8 """
9 9
10 import re 10 import re
11 11
12 from PyQt4.QtCore import pyqtSlot, pyqtSignal, QDateTime, QPoint, QFileInfo 12 from PyQt4.QtCore import pyqtSlot, pyqtSignal, QDateTime, QPoint, QFileInfo, QTimer
13 from PyQt4.QtGui import QWidget, QListWidgetItem, QIcon, QPainter, QMenu, QApplication 13 from PyQt4.QtGui import QWidget, QListWidgetItem, QIcon, QPainter, QMenu, QApplication
14 14
15 from E5Gui import E5MessageBox, E5FileDialog 15 from E5Gui import E5MessageBox, E5FileDialog
16 from E5Gui.E5Application import e5App 16 from E5Gui.E5Application import e5App
17 17
132 pix2 = UI.PixmapCache.getPixmap("ircAway.png") 132 pix2 = UI.PixmapCache.getPixmap("ircAway.png")
133 painter = QPainter(pix1) 133 painter = QPainter(pix1)
134 painter.drawPixmap(0, 0, pix2) 134 painter.drawPixmap(0, 0, pix2)
135 painter.end() 135 painter.end()
136 return QIcon(pix1) 136 return QIcon(pix1)
137
138 def parseWhoFlags(self, flags):
139 """
140 Public method to parse the user flags reported by a WHO command.
141
142 @param flags user flags as reported by WHO (string)
143 """
144 # H The user is not away.
145 # G The user is set away.
146 # * The user is an IRC operator.
147 # @ The user is a channel op in the channel listed in the first field.
148 # + The user is voiced in the channel listed.
149 if flags.endswith("@"):
150 privilege = IrcUserItem.Operator
151 elif flags.endswith("+"):
152 privilege = IrcUserItem.Voice
153 else:
154 privilege = IrcUserItem.Normal
155 if "*" in flags:
156 privilege = IrcUserItem.Admin
157 if flags.startswith("G"):
158 privilege |= IrcUserItem.Away
159 self.__privilege = privilege
160 self.__setIcon()
137 161
138 162
139 class IrcChannelWidget(QWidget, Ui_IrcChannelWidget): 163 class IrcChannelWidget(QWidget, Ui_IrcChannelWidget):
140 """ 164 """
141 Class implementing the IRC channel widget. 165 Class implementing the IRC channel widget.
153 177
154 JoinIndicator = "-->" 178 JoinIndicator = "-->"
155 LeaveIndicator = "<--" 179 LeaveIndicator = "<--"
156 MessageIndicator = "***" 180 MessageIndicator = "***"
157 181
158 # TODO: add context menu to users list with these entries:
159 # Whois
160 # Private Message
161 # TODO: add "Auto WHO" functionality (WHO <channel> %nf)
162 # The possible combinations for this field are listed below:
163 # H The user is not away.
164 # G The user is set away.
165 # * The user is an IRC operator.
166 # @ The user is a channel op in the channel listed in the first field.
167 # + The user is voiced in the channel listed.
168 # TODO: Check away indication in the user list
169 def __init__(self, parent=None): 182 def __init__(self, parent=None):
170 """ 183 """
171 Constructor 184 Constructor
172 185
173 @param parent reference to the parent widget (QWidget) 186 @param parent reference to the parent widget (QWidget)
192 205
193 self.__patterns = [ 206 self.__patterns = [
194 # :foo_!n=foo@foohost.bar.net PRIVMSG #eric-ide :some long message 207 # :foo_!n=foo@foohost.bar.net PRIVMSG #eric-ide :some long message
195 (re.compile(r":([^!]+).*\sPRIVMSG\s([^ ]+)\s:(.*)"), self.__message), 208 (re.compile(r":([^!]+).*\sPRIVMSG\s([^ ]+)\s:(.*)"), self.__message),
196 # :foo_!n=foo@foohost.bar.net JOIN :#eric-ide 209 # :foo_!n=foo@foohost.bar.net JOIN :#eric-ide
197 # :detlev_!~detlev@mnch-5d876cfa.pool.mediaWays.net JOIN #eric-ide
198 (re.compile(r":([^!]+)!([^ ]+)\sJOIN\s:?([^ ]+)"), self.__userJoin), 210 (re.compile(r":([^!]+)!([^ ]+)\sJOIN\s:?([^ ]+)"), self.__userJoin),
199 # :foo_!n=foo@foohost.bar.net PART #eric-ide :part message 211 # :foo_!n=foo@foohost.bar.net PART #eric-ide :part message
200 (re.compile(r":([^!]+).*\sPART\s([^ ]+)\s:(.*)"), self.__userPart), 212 (re.compile(r":([^!]+).*\sPART\s([^ ]+)\s:(.*)"), self.__userPart),
201 # :foo_!n=foo@foohost.bar.net PART #eric-ide 213 # :foo_!n=foo@foohost.bar.net PART #eric-ide
202 (re.compile(r":([^!]+).*\sPART\s([^ ]+)\s*"), self.__userPart), 214 (re.compile(r":([^!]+).*\sPART\s([^ ]+)\s*"), self.__userPart),
204 (re.compile(r":([^!]+).*\sQUIT\s:(.*)"), self.__userQuit), 216 (re.compile(r":([^!]+).*\sQUIT\s:(.*)"), self.__userQuit),
205 # :foo_!n=foo@foohost.bar.net QUIT 217 # :foo_!n=foo@foohost.bar.net QUIT
206 (re.compile(r":([^!]+).*\sQUIT\s*"), self.__userQuit), 218 (re.compile(r":([^!]+).*\sQUIT\s*"), self.__userQuit),
207 # :foo_!n=foo@foohost.bar.net NICK :newnick 219 # :foo_!n=foo@foohost.bar.net NICK :newnick
208 (re.compile(r":([^!]+).*\sNICK\s:(.*)"), self.__userNickChange), 220 (re.compile(r":([^!]+).*\sNICK\s:(.*)"), self.__userNickChange),
209 # :barty!n=foo@foohost.bar.net MODE #eric-ide +o foo_ 221 # :foo_!n=foo@foohost.bar.net MODE #eric-ide +o foo_
210 (re.compile(r":([^!]+).*\sMODE\s([^ ]+)\s([^ ]+)\s([^ ]+).*"), 222 (re.compile(r":([^!]+).*\sMODE\s([^ ]+)\s([+-][ovO]+)\s([^ ]+).*"),
211 self.__setUserPrivilege), 223 self.__setUserPrivilege),
224 # :cameron.freenode.net MODE #testeric +ns
225 (re.compile(r":([^ ]+)\sMODE\s([^ ]+)\s(.+)"), self.__updateChannelModes),
212 # :sturgeon.freenode.net 301 foo_ bar :Gone away for now 226 # :sturgeon.freenode.net 301 foo_ bar :Gone away for now
213 (re.compile(r":.*\s301\s([^ ]+)\s([^ ]+)\s:(.+)"), self.__userAway), 227 (re.compile(r":.*\s301\s([^ ]+)\s([^ ]+)\s:(.+)"), self.__userAway),
228 # :sturgeon.freenode.net 315 foo_ #eric-ide :End of /WHO list.
229 (re.compile(r":.*\s315\s[^ ]+\s([^ ]+)\s:(.*)"), self.__whoEnd),
214 # :zelazny.freenode.net 324 foo_ #eric-ide +cnt 230 # :zelazny.freenode.net 324 foo_ #eric-ide +cnt
215 (re.compile(r":.*\s324\s.*\s([^ ]+)\s(.+)"), self.__channelModes), 231 (re.compile(r":.*\s324\s.*\s([^ ]+)\s(.+)"), self.__channelModes),
216 # :zelazny.freenode.net 328 foo_ #eric-ide :http://www.buggeroff.com/ 232 # :zelazny.freenode.net 328 foo_ #eric-ide :http://www.buggeroff.com/
217 (re.compile(r":.*\s328\s.*\s([^ ]+)\s:(.+)"), self.__channelUrl), 233 (re.compile(r":.*\s328\s.*\s([^ ]+)\s:(.+)"), self.__channelUrl),
218 # :zelazny.freenode.net 329 foo_ #eric-ide 1353001005 234 # :zelazny.freenode.net 329 foo_ #eric-ide 1353001005
219 (re.compile(r":.*\s329\s.*\s([^ ]+)\s(.+)"), self.__channelCreated), 235 (re.compile(r":.*\s329\s.*\s([^ ]+)\s(.+)"), self.__channelCreated),
220 # :zelazny.freenode.net 332 foo_ #eric-ide :eric support channel 236 # :zelazny.freenode.net 332 foo_ #eric-ide :eric support channel
221 (re.compile(r":.*\s332\s.*\s([^ ]+)\s:(.*)"), self.__setTopic), 237 (re.compile(r":.*\s332\s.*\s([^ ]+)\s:(.*)"), self.__setTopic),
222 # :zelazny.freenode.net foo_ 333 #eric-ide foo 1353089020 238 # :zelazny.freenode.net foo_ 333 #eric-ide foo 1353089020
223 (re.compile(r":.*\s333\s.*\s([^ ]+)\s([^ ]+)\s(\d+)"), self.__topicCreated), 239 (re.compile(r":.*\s333\s.*\s([^ ]+)\s([^ ]+)\s(\d+)"), self.__topicCreated),
240 # :cameron.freenode.net 352 detlev_ #eric-ide ~foo foohost.bar.net cameron.freenode.net foo_ H :0 Foo Bar
241 (re.compile(r":.*\s352\s[^ ]+\s([^ ]+)\s([^ ]+)\s([^ ]+)\s[^ ]+\s([^ ]+)"
242 r"\s([^ ]+)\s:\d+\s(.*)"), self.__whoEntry),
224 # :zelazny.freenode.net 353 foo_ @ #eric-ide :@user1 +user2 user3 243 # :zelazny.freenode.net 353 foo_ @ #eric-ide :@user1 +user2 user3
225 (re.compile(r":.*\s353\s.*\s.\s([^ ]+)\s:(.*)"), self.__userList), 244 (re.compile(r":.*\s353\s.*\s.\s([^ ]+)\s:(.*)"), self.__userList),
245 # :sturgeon.freenode.net 354 foo_ 42 ChanServ H@
246 (re.compile(r":.*\s354\s[^ ]+\s42\s([^ ]+)\s(.*)"), self.__autoWhoEntry),
226 # :zelazny.freenode.net 366 foo_ #eric-ide :End of /NAMES list. 247 # :zelazny.freenode.net 366 foo_ #eric-ide :End of /NAMES list.
227 (re.compile(r":.*\s366\s.*\s([^ ]+)\s:(.*)"), self.__ignore), 248 (re.compile(r":.*\s366\s.*\s([^ ]+)\s:(.*)"), self.__ignore),
228 # :sturgeon.freenode.net 704 foo_ index :Help topics available to users: 249 # :sturgeon.freenode.net 704 foo_ index :Help topics available to users:
229 (re.compile(r":.*\s70[456]\s[^ ]+\s([^ ]+)\s:(.*)"), self.__help), 250 (re.compile(r":.*\s70[456]\s[^ ]+\s([^ ]+)\s:(.*)"), self.__help),
230 ] 251 ]
252
253 self.__autoWhoTemplate = "WHO {0} %tnf,42"
254 self.__autoWhoTimer = QTimer()
255 self.__autoWhoTimer.setSingleShot(True)
256 self.__autoWhoTimer.timeout.connect(self.__sendAutoWhoCommand)
257 self.__autoWhoRequested = False
231 258
232 @pyqtSlot() 259 @pyqtSlot()
233 def on_messageEdit_returnPressed(self): 260 def on_messageEdit_returnPressed(self):
234 """ 261 """
235 Private slot to send a message to the channel. 262 Private slot to send a message to the channel.
259 if self.__private: 286 if self.__private:
260 self.sendData.emit("PRIVMSG " + self.__privatePartner + " :" + msg) 287 self.sendData.emit("PRIVMSG " + self.__privatePartner + " :" + msg)
261 else: 288 else:
262 self.sendData.emit("PRIVMSG " + self.__name + " :" + msg) 289 self.sendData.emit("PRIVMSG " + self.__name + " :" + msg)
263 self.messageEdit.clear() 290 self.messageEdit.clear()
291 self.unsetMarkerLine()
264 292
265 def requestLeave(self): 293 def requestLeave(self):
266 """ 294 """
267 Public method to leave the channel. 295 Public method to leave the channel.
268 """ 296 """
627 .toString("yyyy-MM-dd hh:mm"))) 655 .toString("yyyy-MM-dd hh:mm")))
628 return True 656 return True
629 657
630 return False 658 return False
631 659
660 def __updateChannelModes(self, match):
661 """
662 Private method to handle a message reporting the channel modes.
663
664 @param match match object that matched the pattern
665 @return flag indicating whether the message was handled (boolean)
666 """
667 # group(1) user or server
668 # group(2) channel
669 # group(3) modes and parameter list
670 if match.group(2).lower() == self.__name:
671 nick = match.group(1)
672 modesParameters = match.group(3).split()
673 modeString = modesParameters.pop(0)
674 isPlus = True
675 message = ""
676 for mode in modeString:
677 if mode == "+":
678 isPlus = True
679 continue
680 elif mode == "-":
681 isPlus = False
682 continue
683 elif mode == "a":
684 if isPlus:
685 message = self.trUtf8(
686 "{0} sets the channel mode to 'anonymous'.").format(nick)
687 else:
688 message = self.trUtf8(
689 "{0} removes the 'anonymous' mode from the channel.").format(
690 nick)
691 elif mode == "b":
692 if isPlus:
693 message = self.trUtf8(
694 "{0} sets a ban on {1}.").format(
695 nick, modesParameters.pop(0))
696 else:
697 message = self.trUtf8(
698 "{0} removes the ban on {1}.").format(
699 nick, modesParameters.pop(0))
700 elif mode == "c":
701 if isPlus:
702 message = self.trUtf8(
703 "{0} sets the channel mode to 'no colors allowed'.").format(
704 nick)
705 else:
706 message = self.trUtf8(
707 "{0} sets the channel mode to 'allow color codes'.").format(
708 nick)
709 elif mode == "e":
710 if isPlus:
711 message = self.trUtf8(
712 "{0} sets a ban exception on {1}.").format(
713 nick, modesParameters.pop(0))
714 else:
715 message = self.trUtf8(
716 "{0} removes the ban exception on {1}.").format(
717 nick, modesParameters.pop(0))
718 elif mode == "i":
719 if isPlus:
720 message = self.trUtf8(
721 "{0} sets the channel mode to 'invite only'.").format(nick)
722 else:
723 message = self.trUtf8(
724 "{0} removes the 'invite only' mode from the channel."
725 ).format(nick)
726 elif mode == "k":
727 if isPlus:
728 message = self.trUtf8(
729 "{0} sets the channel key to '{1}'.").format(
730 nick, modesParameters.pop(0))
731 else:
732 message = self.trUtf8(
733 "{0} removes the channel key.").format(nick)
734 elif mode == "l":
735 if isPlus:
736 message = self.trUtf8(
737 "{0} sets the channel limit to %n nick(s).", "",
738 int(modesParameters.pop(0))).format(nick)
739 else:
740 message = self.trUtf8(
741 "{0} removes the channel limit.").format(nick)
742 elif mode == "m":
743 if isPlus:
744 message = self.trUtf8(
745 "{0} sets the channel mode to 'moderated'.").format(nick)
746 else:
747 message = self.trUtf8(
748 "{0} sets the channel mode to 'unmoderated'.").format(nick)
749 elif mode == "n":
750 if isPlus:
751 message = self.trUtf8(
752 "{0} sets the channel mode to 'no messages from outside'."
753 ).format(nick)
754 else:
755 message = self.trUtf8(
756 "{0} sets the channel mode to 'allow messages from outside'."
757 ).format(nick)
758 elif mode == "p":
759 if isPlus:
760 message = self.trUtf8(
761 "{0} sets the channel mode to 'private'.").format(nick)
762 else:
763 message = self.trUtf8(
764 "{0} sets the channel mode to 'public'.").format(nick)
765 elif mode == "q":
766 if isPlus:
767 message = self.trUtf8(
768 "{0} sets the channel mode to 'quiet'.").format(nick)
769 else:
770 message = self.trUtf8(
771 "{0} removes the 'quiet' mode from the channel.").format(
772 nick)
773 elif mode == "r":
774 continue
775 elif mode == "s":
776 if isPlus:
777 message = self.trUtf8(
778 "{0} sets the channel mode to 'secret'.").format(nick)
779 else:
780 message = self.trUtf8(
781 "{0} sets the channel mode to 'visible'.").format(nick)
782 elif mode == "t":
783 if isPlus:
784 message = self.trUtf8(
785 "{0} switches on 'topic protection'.").format(nick)
786 else:
787 message = self.trUtf8(
788 "{0} switches off 'topic protection'.").format(nick)
789 elif mode == "I":
790 if isPlus:
791 message = self.trUtf8(
792 "{0} sets invitation mask {1}.").format(
793 nick, modesParameters.pop(0))
794 else:
795 message = self.trUtf8(
796 "{0} removes the invitation mask {1}.").format(
797 nick, modesParameters.pop(0))
798
799 self.__addManagementMessage(self.trUtf8("Mode"), message)
800
801 return True
802
803 return False
804
632 def __setUserPrivilege(self, match): 805 def __setUserPrivilege(self, match):
633 """ 806 """
634 Private method to handle a change of user privileges for the channel. 807 Private method to handle a change of user privileges for the channel.
635 808
636 @param match match object that matched the pattern 809 @param match match object that matched the pattern
638 """ 811 """
639 if match.group(2).lower() == self.__name: 812 if match.group(2).lower() == self.__name:
640 itm = self.__findUser(match.group(4)) 813 itm = self.__findUser(match.group(4))
641 if itm: 814 if itm:
642 itm.changePrivilege(match.group(3)) 815 itm.changePrivilege(match.group(3))
643 self.__addManagementMessage(IrcChannelWidget.MessageIndicator, 816 self.__addManagementMessage(IrcChannelWidget.MessageIndicator,
644 self.trUtf8("{0} sets mode for {1}: {2}.").format( 817 self.trUtf8("{0} sets mode for {1}: {2}.").format(
645 match.group(1), match.group(4), match.group(3))) 818 match.group(1), match.group(4), match.group(3)))
646 return True 819 return True
647 820
648 return False 821 return False
649 822
650 def __ignore(self, match): 823 def __ignore(self, match):
968 Protected method handling show events. 1141 Protected method handling show events.
969 1142
970 @param evt reference to the show event (QShowEvent) 1143 @param evt reference to the show event (QShowEvent)
971 """ 1144 """
972 self.__hidden = False 1145 self.__hidden = False
1146
1147 def initAutoWho(self):
1148 """
1149 Public method to initialize the Auto Who system.
1150 """
1151 if Preferences.getIrc("AutoUserInfoLookup"):
1152 self.__autoWhoTimer.setInterval(
1153 Preferences.getIrc("AutoUserInfoInterval") * 1000)
1154 self.__autoWhoTimer.start()
1155
1156 @pyqtSlot()
1157 def __sendAutoWhoCommand(self):
1158 """
1159 Private slot to send the WHO command to update the users list.
1160 """
1161 if self.usersList.count() <= Preferences.getIrc("AutoUserInfoMax"):
1162 self.__autoWhoRequested = True
1163 self.sendData.emit(self.__autoWhoTemplate.format(self.__name))
1164
1165 def __autoWhoEntry(self, match):
1166 """
1167 Private method to handle a WHO entry returned by the server as requested
1168 automatically.
1169
1170 @param match match object that matched the pattern
1171 @return flag indicating whether the message was handled (boolean)
1172 """
1173 # group(1) nick
1174 # group(2) user flags
1175 if self.__autoWhoRequested:
1176 itm = self.__findUser(match.group(1))
1177 if itm:
1178 itm.parseWhoFlags(match.group(2))
1179 return True
1180
1181 return False
1182
1183 def __whoEnd(self, match):
1184 """
1185 Private method to handle the end of the WHO list.
1186
1187 @param match match object that matched the pattern
1188 @return flag indicating whether the message was handled (boolean)
1189 """
1190 if match.group(1).lower() == self.__name:
1191 if self.__autoWhoRequested:
1192 self.__autoWhoRequested = False
1193 self.initAutoWho()
1194 else:
1195 self.__addManagementMessage(self.trUtf8("Who"),
1196 self.trUtf8("End of /WHO list for {0}.").format(match.group(1)))
1197 return True
1198
1199 return False
1200
1201 def __whoEntry(self, match):
1202 """
1203 Private method to handle a WHO entry returned by the server as requested
1204 manually.
1205
1206 @param match match object that matched the pattern
1207 @return flag indicating whether the message was handled (boolean)
1208 """
1209 # group(1) channel
1210 # group(2) user
1211 # group(3) host
1212 # group(4) nick
1213 # group(5) user flags
1214 # group(6) real name
1215 if match.group(2).lower() == self.__name:
1216 away = self.trUtf8(" (Away)") if match.group(5).startswith("G") else ""
1217 self.__addManagementMessage(self.trUtf8("Who"),
1218 self.trUtf8("{0} is {1}@{2} ({3}){4}").format(
1219 match.group(4), match.group(2), match.group(3), match.group(6), away))
1220 return True
1221
1222 return False
1223

eric ide

mercurial