Network/IRC/IrcChannelWidget.py

changeset 2227
b7aceb255831
child 2240
11445430c553
equal deleted inserted replaced
2225:0139003972cd 2227:b7aceb255831
1 # -*- coding: utf-8 -*-
2
3 # Copyright (c) 2012 Detlev Offenbach <detlev@die-offenbachs.de>
4 #
5
6 """
7 Module implementing the IRC channel widget.
8 """
9
10 import re
11
12 from PyQt4.QtCore import pyqtSlot, pyqtSignal, QDateTime
13 from PyQt4.QtGui import QWidget, QListWidgetItem, QIcon, QPainter
14
15 from E5Gui import E5MessageBox
16 from E5Gui.E5Application import e5App
17
18 from .Ui_IrcChannelWidget import Ui_IrcChannelWidget
19
20 from .IrcUtilities import ircFilter, ircTimestamp, getChannelModesDict
21
22 import Utilities
23 import UI.PixmapCache
24 import Preferences
25
26
27 class IrcUserItem(QListWidgetItem):
28 """
29 Class implementing a list widget item containing an IRC channel user.
30 """
31 Normal = 0x00 # no privileges
32 Operator = 0x01 # channel operator
33 Voice = 0x02 # voice operator
34 Admin = 0x04 # administrator
35 Halfop = 0x08 # half operator
36 Owner = 0x10 # channel owner
37 Away = 0x80 # user away
38
39 PrivilegeMapping = {
40 "a": Away,
41 "o": Operator,
42 "O": Owner,
43 "v": Voice,
44
45 }
46
47 def __init__(self, name, parent=None):
48 """
49 Constructor
50
51 @param name string with user name and privilege prefix (string)
52 @param parent reference to the parent widget (QListWidget or QListWidgetItem)
53 """
54 super().__init__(name, parent)
55
56 self.__privilege = IrcUserItem.Normal
57 self.__name = name
58
59 self.__setIcon()
60
61 def name(self):
62 """
63 Public method to get the user name.
64
65 @return user name (string)
66 """
67 return self.__name
68
69 def setName(self, name):
70 """
71 Public method to set a new nick name.
72
73 @param name new nick name for the user (string)
74 """
75 self.__name = name
76 self.setText(name)
77
78 def changePrivilege(self, privilege):
79 """
80 Public method to set or unset a user privilege.
81
82 @param privilege privilege to set or unset (string)
83 """
84 oper = privilege[0]
85 priv = privilege[1]
86 if oper == "+":
87 if priv in IrcUserItem.PrivilegeMapping:
88 self.__privilege |= IrcUserItem.PrivilegeMapping[priv]
89 elif oper == "-":
90 if priv in IrcUserItem.PrivilegeMapping:
91 self.__privilege &= ~IrcUserItem.PrivilegeMapping[priv]
92 self.__setIcon()
93
94 def clearPrivileges(self):
95 """
96 Public method to clear the user privileges.
97 """
98 self.__privilege = IrcUserItem.Normal
99 self.__setIcon()
100
101 def __setIcon(self):
102 """
103 Private method to set the icon dependent on user privileges.
104 """
105 # step 1: determine the icon
106 if self.__privilege & IrcUserItem.Voice:
107 icon = UI.PixmapCache.getIcon("ircVoice.png")
108 elif self.__privilege & IrcUserItem.Owner:
109 icon = UI.PixmapCache.getIcon("ircOwner.png")
110 elif self.__privilege & IrcUserItem.Operator:
111 icon = UI.PixmapCache.getIcon("ircOp.png")
112 elif self.__privilege & IrcUserItem.Halfop:
113 icon = UI.PixmapCache.getIcon("ircHalfop.png")
114 elif self.__privilege & IrcUserItem.Admin:
115 icon = UI.PixmapCache.getIcon("ircAdmin.png")
116 else:
117 icon = UI.PixmapCache.getIcon("ircNormal.png")
118 if self.__privilege & IrcUserItem.Away:
119 icon = self.__awayIcon(icon)
120
121 # step 2: set the icon
122 self.setIcon(icon)
123
124 def __awayIcon(self, icon):
125 """
126 Private method to convert an icon to an away icon.
127
128 @param icon icon to be converted (QIcon)
129 @param away icon (QIcon)
130 """
131 pix1 = icon.pixmap(16, 16)
132 pix2 = UI.PixmapCache.getPixmap("ircAway.png")
133 painter = QPainter(pix1)
134 painter.drawPixmap(0, 0, pix2)
135 painter.end()
136 return QIcon(pix1)
137
138
139 class IrcChannelWidget(QWidget, Ui_IrcChannelWidget):
140 """
141 Class implementing the IRC channel widget.
142 """
143 sendData = pyqtSignal(str)
144 channelClosed = pyqtSignal(str)
145
146 UrlRe = re.compile(r"""((?:http|ftp|https):\/\/[\w\-_]+(?:\.[\w\-_]+)+"""
147 r"""(?:[\w\-\.,@?^=%&amp;:/~\+#]*[\w\-\@?^=%&amp;/~\+#])?)""")
148
149 JoinIndicator = "--&gt;"
150 LeaveIndicator = "&lt;--"
151 MessageIndicator = "***"
152
153 def __init__(self, parent=None):
154 """
155 Constructor
156
157 @param parent reference to the parent widget (QWidget)
158 """
159 super().__init__(parent)
160 self.setupUi(self)
161
162 self.__ui = e5App().getObject("UserInterface")
163
164 self.__name = ""
165 self.__userName = ""
166 self.__partMessage = ""
167 self.__prefixToPrivilege = {}
168
169 self.__patterns = [
170 # :foo_!n=foo@foohost.bar.net PRIVMSG #eric-ide :some long message
171 (re.compile(r":([^!]+).*\sPRIVMSG\s([^ ]+)\s:(.*)"), self.__message),
172 # :foo_!n=foo@foohost.bar.net JOIN :#eric-ide
173 # :detlev_!~detlev@mnch-5d876cfa.pool.mediaWays.net JOIN #eric-ide
174 (re.compile(r":([^!]+)!([^ ]+)\sJOIN\s:?([^ ]+)"), self.__userJoin),
175 # :foo_!n=foo@foohost.bar.net PART #eric-ide :part message
176 (re.compile(r":([^!]+).*\sPART\s([^ ]+)\s:(.*)"), self.__userPart),
177 # :foo_!n=foo@foohost.bar.net PART #eric-ide
178 (re.compile(r":([^!]+).*\sPART\s([^ ]+)\s*"), self.__userPart),
179 # :foo_!n=foo@foohost.bar.net QUIT :quit message
180 (re.compile(r":([^!]+).*\sQUIT\s:(.*)"), self.__userQuit),
181 # :foo_!n=foo@foohost.bar.net QUIT
182 (re.compile(r":([^!]+).*\sQUIT\s*"), self.__userQuit),
183 # :foo_!n=foo@foohost.bar.net NICK :newnick
184 (re.compile(r":([^!]+).*\sNICK\s:(.*)"), self.__userNickChange),
185 # :barty!n=foo@foohost.bar.net MODE #eric-ide +o foo_
186 (re.compile(r":([^!]+).*\sMODE\s([^ ]+)\s([^ ]+)\s([^ ]+).*"),
187 self.__setUserPrivilege),
188 # :zelazny.freenode.net 324 foo_ #eric-ide +cnt
189 (re.compile(r":.*\s324\s.*\s([^ ]+)\s(.+)"), self.__channelModes),
190 # :zelazny.freenode.net 328 foo_ #eric-ide :http://www.buggeroff.com/
191 (re.compile(r":.*\s328\s.*\s([^ ]+)\s:(.+)"), self.__channelUrl),
192 # :zelazny.freenode.net 329 foo_ #eric-ide 1353001005
193 (re.compile(r":.*\s329\s.*\s([^ ]+)\s(.+)"), self.__channelCreated),
194 # :zelazny.freenode.net 332 foo_ #eric-ide :eric support channel
195 (re.compile(r":.*\s332\s.*\s([^ ]+)\s:(.*)"), self.__setTopic),
196 # :zelazny.freenode.net foo_ 333 #eric-ide foo 1353089020
197 (re.compile(r":.*\s333\s.*\s([^ ]+)\s([^ ]+)\s(\d+)"), self.__topicCreated),
198 # :zelazny.freenode.net 353 foo_ @ #eric-ide :@user1 +user2 user3
199 (re.compile(r":.*\s353\s.*\s.\s([^ ]+)\s:(.*)"), self.__userList),
200 # :zelazny.freenode.net 366 foo_ #eric-ide :End of /NAMES list.
201 (re.compile(r":.*\s366\s.*\s([^ ]+)\s:(.*)"), self.__ignore),
202 ]
203
204 @pyqtSlot()
205 def on_messageEdit_returnPressed(self):
206 """
207 Private slot to send a message to the channel.
208 """
209 msg = self.messageEdit.text()
210 self.messages.append(
211 '<font color="{0}">{2} <b>&lt;</b><font color="{1}">{3}</font>'
212 '<b>&gt;</b> {4}</font>'.format(
213 Preferences.getIrc("ChannelMessageColour"),
214 Preferences.getIrc("OwnNickColour"),
215 ircTimestamp(), self.__userName, Utilities.html_encode(msg)))
216 self.sendData.emit("PRIVMSG " + self.__name + " :" + msg)
217 self.messageEdit.clear()
218
219 def requestLeave(self):
220 """
221 Public method to leave the channel.
222 """
223 ok = E5MessageBox.yesNo(self,
224 self.trUtf8("Leave IRC channel"),
225 self.trUtf8("""Do you really want to leave the IRC channel <b>{0}</b>?""")\
226 .format(self.__name))
227 if ok:
228 self.sendData.emit("PART " + self.__name + " :" + self.__partMessage)
229 self.channelClosed.emit(self.__name)
230
231 def name(self):
232 """
233 Public method to get the name of the channel.
234
235 @return name of the channel (string)
236 """
237 return self.__name
238
239 def setName(self, name):
240 """
241 Public method to set the name of the channel.
242
243 @param name of the channel (string)
244 """
245 self.__name = name
246
247 def getUsersCount(self):
248 """
249 Public method to get the users count of the channel.
250
251 @return users count of the channel (integer)
252 """
253 return self.usersList.count()
254
255 def userName(self):
256 """
257 Public method to get the nick name of the user.
258
259 @return nick name of the user (string)
260 """
261 return self.__userName
262
263 def setUserName(self, name):
264 """
265 Public method to set the user name for the channel.
266
267 @param name user name for the channel (string)
268 """
269 self.__userName = name.lower()
270
271 def partMessage(self):
272 """
273 Public method to get the part message.
274
275 @return part message (string)
276 """
277 return self.__partMessage
278
279 def setPartMessage(self, message):
280 """
281 Public method to set the part message.
282
283 @param message message to be used for PART messages (string)
284 """
285 self.__partMessage = message
286
287 def handleMessage(self, line):
288 """
289 Public method to handle the message sent by the server.
290
291 @param line server message (string)
292 @return flag indicating, if the message was handled (boolean)
293 """
294 for patternRe, patternFunc in self.__patterns:
295 match = patternRe.match(line)
296 if match is not None:
297 if patternFunc(match):
298 return True
299
300 return False
301
302 def __message(self, match):
303 """
304 Private method to handle messages to the channel.
305
306 @param match match object that matched the pattern
307 @return flag indicating whether the message was handled (boolean)
308 """
309 if match.group(2).lower() == self.__name:
310 msg = ircFilter(match.group(3))
311 self.messages.append(
312 '<font color="{0}">{2} <b>&lt;</b><font color="{1}">{3}</font>'
313 '<b>&gt;</b> {4}</font>'.format(
314 Preferences.getIrc("ChannelMessageColour"),
315 Preferences.getIrc("NickColour"),
316 ircTimestamp(), match.group(1),
317 msg))
318 if Preferences.getIrc("ShowNotifications"):
319 if Preferences.getIrc("NotifyMessage"):
320 self.__ui.showNotification(UI.PixmapCache.getPixmap("irc48.png"),
321 self.trUtf8("Channel Message"), msg)
322 elif Preferences.getIrc("NotifyNick") and self.__userName in msg:
323 self.__ui.showNotification(UI.PixmapCache.getPixmap("irc48.png"),
324 self.trUtf8("Nick mentioned"), msg)
325 return True
326
327 return False
328
329 def __userJoin(self, match):
330 """
331 Private method to handle a user joining the channel.
332
333 @param match match object that matched the pattern
334 @return flag indicating whether the message was handled (boolean)
335 """
336 if match.group(3).lower() == self.__name:
337 if self.__userName != match.group(1):
338 IrcUserItem(match.group(1), self.usersList)
339 msg = self.trUtf8("{0} has joined the channel {1} ({2}).").format(
340 match.group(1), self.__name, match.group(2))
341 self.__addManagementMessage(IrcChannelWidget.JoinIndicator, msg)
342 else:
343 msg = self.trUtf8("You have joined the channel {0} ({1}).").format(
344 self.__name, match.group(2))
345 self.__addManagementMessage(IrcChannelWidget.JoinIndicator, msg)
346 if Preferences.getIrc("ShowNotifications") and \
347 Preferences.getIrc("NotifyJoinPart"):
348 self.__ui.showNotification(UI.PixmapCache.getPixmap("irc48.png"),
349 self.trUtf8("Join Channel"), msg)
350 return True
351
352 return False
353
354 def __userPart(self, match):
355 """
356 Private method to handle a user leaving the channel.
357
358 @param match match object that matched the pattern
359 @return flag indicating whether the message was handled (boolean)
360 """
361 if match.group(2).lower() == self.__name:
362 itm = self.__findUser(match.group(1))
363 self.usersList.takeItem(self.usersList.row(itm))
364 del itm
365 if match.lastindex == 2:
366 msg = self.trUtf8("{0} has left {1}.").format(
367 match.group(1), self.__name)
368 self.__addManagementMessage(IrcChannelWidget.LeaveIndicator, msg)
369 else:
370 msg = self.trUtf8("{0} has left {1}: {2}.").format(
371 match.group(1), self.__name,
372 ircFilter(match.group(3)))
373 self.__addManagementMessage(IrcChannelWidget.LeaveIndicator, msg)
374 if Preferences.getIrc("ShowNotifications") and \
375 Preferences.getIrc("NotifyJoinPart"):
376 self.__ui.showNotification(UI.PixmapCache.getPixmap("irc48.png"),
377 self.trUtf8("Leave Channel"), msg)
378 return True
379
380 return False
381
382 def __userQuit(self, match):
383 """
384 Private method to handle a user logging off the server.
385
386 @param match match object that matched the pattern
387 @return flag indicating whether the message was handled (boolean)
388 """
389 itm = self.__findUser(match.group(1))
390 if itm:
391 self.usersList.takeItem(self.usersList.row(itm))
392 del itm
393 if match.lastindex == 1:
394 msg = self.trUtf8("{0} has quit {1}.").format(
395 match.group(1), self.__name)
396 self.__addManagementMessage(IrcChannelWidget.MessageIndicator, msg)
397 else:
398 msg = self.trUtf8("{0} has quit {1}: {2}.").format(
399 match.group(1), self.__name, ircFilter(match.group(2)))
400 self.__addManagementMessage(IrcChannelWidget.MessageIndicator, msg)
401 if Preferences.getIrc("ShowNotifications") and \
402 Preferences.getIrc("NotifyJoinPart"):
403 self.__ui.showNotification(UI.PixmapCache.getPixmap("irc48.png"),
404 self.trUtf8("Quit"), msg)
405
406 # always return False for other channels and server to process
407 return False
408
409 def __userNickChange(self, match):
410 """
411 Private method to handle a nickname change of a user.
412
413 @param match match object that matched the pattern
414 @return flag indicating whether the message was handled (boolean)
415 """
416 itm = self.__findUser(match.group(1))
417 if itm:
418 itm.setName(match.group(2))
419 if match.group(1) == self.__userName:
420 self.__addManagementMessage(IrcChannelWidget.MessageIndicator,
421 self.trUtf8("You are now known as {0}.").format(
422 match.group(2)))
423 self.__userName = match.group(2)
424 else:
425 self.__addManagementMessage(IrcChannelWidget.MessageIndicator,
426 self.trUtf8("User {0} is now known as {1}.").format(
427 match.group(1), match.group(2)))
428
429 # always return False for other channels and server to process
430 return False
431
432 def __userList(self, match):
433 """
434 Private method to handle the receipt of a list of users of the channel.
435
436 @param match match object that matched the pattern
437 @return flag indicating whether the message was handled (boolean)
438 """
439 if match.group(1).lower() == self.__name:
440 users = match.group(2).split()
441 for user in users:
442 userPrivileges, userName = self.__extractPrivilege(user)
443 itm = self.__findUser(userName)
444 if itm is None:
445 itm = IrcUserItem(userName, self.usersList)
446 for privilege in userPrivileges:
447 itm.changePrivilege(privilege)
448 return True
449
450 return False
451
452 def __setTopic(self, match):
453 """
454 Private method to handle a topic change of the channel.
455
456 @param match match object that matched the pattern
457 @return flag indicating whether the message was handled (boolean)
458 """
459 if match.group(1).lower() == self.__name:
460 self.topicLabel.setText(match.group(2))
461 self.__addManagementMessage(IrcChannelWidget.MessageIndicator,
462 ircFilter(self.trUtf8('The channel topic is: "{0}".').format(
463 match.group(2))))
464 return True
465
466 return False
467
468 def __topicCreated(self, match):
469 """
470 Private method to handle a topic created message.
471
472 @param match match object that matched the pattern
473 @return flag indicating whether the message was handled (boolean)
474 """
475 if match.group(1).lower() == self.__name:
476 self.__addManagementMessage(IrcChannelWidget.MessageIndicator,
477 self.trUtf8("The topic was set by {0} on {1}.").format(
478 match.group(2), QDateTime.fromTime_t(int(match.group(3)))\
479 .toString("yyyy-MM-dd hh:mm")))
480 return True
481
482 return False
483
484 def __channelUrl(self, match):
485 """
486 Private method to handle a channel URL message.
487
488 @param match match object that matched the pattern
489 @return flag indicating whether the message was handled (boolean)
490 """
491 if match.group(1).lower() == self.__name:
492 self.__addManagementMessage(IrcChannelWidget.MessageIndicator,
493 ircFilter(self.trUtf8("Channel URL: {0}").format(match.group(2))))
494 return True
495
496 return False
497
498 def __channelModes(self, match):
499 """
500 Private method to handle a message reporting the channel modes.
501
502 @param match match object that matched the pattern
503 @return flag indicating whether the message was handled (boolean)
504 """
505 if match.group(1).lower() == self.__name:
506 modesDict = getChannelModesDict()
507 modesParameters = match.group(2).split()
508 modeString = modesParameters.pop(0)
509 modes = []
510 for modeChar in modeString:
511 if modeChar == "+":
512 continue
513 elif modeChar == "k":
514 parameter = modesParameters.pop(0)
515 modes.append(
516 self.trUtf8("password protected ({0})").format(parameter))
517 elif modeChar == "l":
518 parameter = modesParameters.pop(0)
519 modes.append(
520 self.trUtf8("limited to %n user(s)", "", int(parameter)))
521 elif modeChar in modesDict:
522 modes.append(modesDict[modeChar])
523 else:
524 modes.append(modeChar)
525
526 self.__addManagementMessage(IrcChannelWidget.MessageIndicator,
527 self.trUtf8("Channel modes: {0}.").format(", ".join(modes)))
528
529 return True
530
531 return False
532
533 def __channelCreated(self, match):
534 """
535 Private method to handle a channel created message.
536
537 @param match match object that matched the pattern
538 @return flag indicating whether the message was handled (boolean)
539 """
540 if match.group(1).lower() == self.__name:
541 self.__addManagementMessage(IrcChannelWidget.MessageIndicator,
542 self.trUtf8("This channel was created on {0}.").format(
543 QDateTime.fromTime_t(int(match.group(2)))\
544 .toString("yyyy-MM-dd hh:mm")))
545 return True
546
547 return False
548
549 def __setUserPrivilege(self, match):
550 """
551 Private method to handle a change of user privileges for the channel.
552
553 @param match match object that matched the pattern
554 @return flag indicating whether the message was handled (boolean)
555 """
556 if match.group(2).lower() == self.__name:
557 itm = self.__findUser(match.group(4))
558 if itm:
559 itm.changePrivilege(match.group(3))
560 self.__addManagementMessage(IrcChannelWidget.MessageIndicator,
561 self.trUtf8("{0} sets mode for {1}: {2}.").format(
562 match.group(1), match.group(4), match.group(3)))
563 return True
564
565 return False
566
567 def __ignore(self, match):
568 """
569 Private method to handle a channel message we are not interested in.
570
571 @param match match object that matched the pattern
572 @return flag indicating whether the message was handled (boolean)
573 """
574 if match.group(1).lower() == self.__name:
575 return True
576
577 return False
578
579 def setUserPrivilegePrefix(self, prefixes):
580 """
581 Public method to set the user privilege to prefix mapping.
582
583 @param prefixes dictionary with privilege as key and prefix as value
584 """
585 self.__prefixToPrivilege = {}
586 for privilege, prefix in prefixes.items():
587 if prefix:
588 self.__prefixToPrivilege[prefix] = privilege
589
590 def __findUser(self, name):
591 """
592 Private method to find the user in the list of users.
593
594 @param name user name to search for (string)
595 @return reference to the list entry (QListWidgetItem)
596 """
597 for row in range(self.usersList.count()):
598 itm = self.usersList.item(row)
599 if itm.name() == name:
600 return itm
601
602 return None
603
604 def __extractPrivilege(self, name):
605 """
606 Private method to extract the user privileges out of the name.
607
608 @param name user name and prefixes (string)
609 return list of privileges and user name (list of string, string)
610 """
611 privileges = []
612 while name[0] in self.__prefixToPrivilege:
613 prefix = name[0]
614 privileges.append(self.__prefixToPrivilege[prefix])
615 name = name[1:]
616 if name[0] == ",":
617 name = name[1:]
618
619 return privileges, name
620
621 def __addManagementMessage(self, indicator, message):
622 """
623 Private method to add a channel management message to the list.
624
625 @param indicator indicator to be shown (string)
626 @param message message to be shown (string)
627 @keyparam isLocal flag indicating a message related to the local user (boolean)
628 """
629 if indicator == self.JoinIndicator:
630 color = Preferences.getIrc("JoinChannelColour")
631 elif indicator == self.LeaveIndicator:
632 color = Preferences.getIrc("LeaveChannelColour")
633 else:
634 color = Preferences.getIrc("ChannelInfoColour")
635 self.messages.append(
636 '<font color="{0}">{1} <b>[</b>{2}<b>]</b> {3}</font>'.format(
637 color, ircTimestamp(), indicator, message))

eric ide

mercurial