eric6/Network/IRC/IrcChannelWidget.py

changeset 6942
2602857055c5
parent 6645
ad476851d7e0
child 7192
a22eee00b052
equal deleted inserted replaced
6941:f99d60d6b59b 6942:2602857055c5
1 # -*- coding: utf-8 -*-
2
3 # Copyright (c) 2012 - 2019 Detlev Offenbach <detlev@die-offenbachs.de>
4 #
5
6 """
7 Module implementing the IRC channel widget.
8 """
9
10 from __future__ import unicode_literals
11
12 try:
13 from itertools import izip_longest as zip_longest # __IGNORE_EXCEPTION__
14 except ImportError:
15 from itertools import zip_longest
16
17 import re
18
19 from PyQt5.QtCore import pyqtSlot, pyqtSignal, QDateTime, QPoint, QFileInfo, \
20 QTimer, QUrl, QCoreApplication
21 from PyQt5.QtGui import QIcon, QPainter, QTextCursor, QDesktopServices
22 from PyQt5.QtWidgets import QWidget, QListWidgetItem, QMenu, QApplication, \
23 QInputDialog, QLineEdit
24
25 from E5Gui import E5MessageBox, E5FileDialog
26 from E5Gui.E5Application import e5App
27
28 from .Ui_IrcChannelWidget import Ui_IrcChannelWidget
29
30 from .IrcUtilities import ircFilter, ircTimestamp, getChannelModesDict
31
32 import Utilities
33 import UI.PixmapCache
34 import Preferences
35
36 from UI.Info import Version, Copyright
37
38
39 class IrcUserItem(QListWidgetItem):
40 """
41 Class implementing a list widget item containing an IRC channel user.
42 """
43 Normal = 0x00 # no privileges
44 Operator = 0x01 # channel operator
45 Voice = 0x02 # voice operator
46 Admin = 0x04 # administrator
47 Halfop = 0x08 # half operator
48 Owner = 0x10 # channel owner
49 Away = 0x80 # user away
50
51 PrivilegeMapping = {
52 "a": Away,
53 "o": Operator,
54 "O": Owner,
55 "v": Voice,
56
57 }
58
59 def __init__(self, name, parent=None):
60 """
61 Constructor
62
63 @param name string with user name and privilege prefix (string)
64 @param parent reference to the parent widget (QListWidget or
65 QListWidgetItem)
66 """
67 super(IrcUserItem, self).__init__(name, parent)
68
69 self.__privilege = IrcUserItem.Normal
70 self.__name = name
71 self.__ignored = False
72
73 self.__setText()
74 self.__setIcon()
75
76 def name(self):
77 """
78 Public method to get the user name.
79
80 @return user name (string)
81 """
82 return self.__name
83
84 def setName(self, name):
85 """
86 Public method to set a new nick name.
87
88 @param name new nick name for the user (string)
89 """
90 self.__name = name
91 self.__setText()
92
93 def changePrivilege(self, privilege):
94 """
95 Public method to set or unset a user privilege.
96
97 @param privilege privilege to set or unset (string)
98 """
99 oper = privilege[0]
100 priv = privilege[1]
101 if oper == "+":
102 if priv in IrcUserItem.PrivilegeMapping:
103 self.__privilege |= IrcUserItem.PrivilegeMapping[priv]
104 elif oper == "-":
105 if priv in IrcUserItem.PrivilegeMapping:
106 self.__privilege &= ~IrcUserItem.PrivilegeMapping[priv]
107 self.__setIcon()
108
109 def clearPrivileges(self):
110 """
111 Public method to clear the user privileges.
112 """
113 self.__privilege = IrcUserItem.Normal
114 self.__setIcon()
115
116 def __setText(self):
117 """
118 Private method to set the user item text.
119 """
120 if self.__ignored:
121 self.setText(QCoreApplication.translate(
122 "IrcUserItem",
123 "{0} (ignored)").format(self.__name))
124 else:
125 self.setText(self.__name)
126
127 def __setIcon(self):
128 """
129 Private method to set the icon dependent on user privileges.
130 """
131 # step 1: determine the icon
132 if self.__privilege & IrcUserItem.Voice:
133 icon = UI.PixmapCache.getIcon("ircVoice.png")
134 elif self.__privilege & IrcUserItem.Owner:
135 icon = UI.PixmapCache.getIcon("ircOwner.png")
136 elif self.__privilege & IrcUserItem.Operator:
137 icon = UI.PixmapCache.getIcon("ircOp.png")
138 elif self.__privilege & IrcUserItem.Halfop:
139 icon = UI.PixmapCache.getIcon("ircHalfop.png")
140 elif self.__privilege & IrcUserItem.Admin:
141 icon = UI.PixmapCache.getIcon("ircAdmin.png")
142 else:
143 icon = UI.PixmapCache.getIcon("ircNormal.png")
144 if self.__privilege & IrcUserItem.Away:
145 icon = self.__awayIcon(icon)
146
147 # step 2: set the icon
148 self.setIcon(icon)
149
150 def __awayIcon(self, icon):
151 """
152 Private method to convert an icon to an away icon.
153
154 @param icon icon to be converted (QIcon)
155 @return away icon (QIcon)
156 """
157 pix1 = icon.pixmap(16, 16)
158 pix2 = UI.PixmapCache.getPixmap("ircAway.png")
159 painter = QPainter(pix1)
160 painter.drawPixmap(0, 0, pix2)
161 painter.end()
162 return QIcon(pix1)
163
164 def parseWhoFlags(self, flags):
165 """
166 Public method to parse the user flags reported by a WHO command.
167
168 @param flags user flags as reported by WHO (string)
169 """
170 # H The user is not away.
171 # G The user is set away.
172 # * The user is an IRC operator.
173 # @ The user is a channel op in the channel listed in the first field.
174 # + The user is voiced in the channel listed.
175 if flags.endswith("@"):
176 privilege = IrcUserItem.Operator
177 elif flags.endswith("+"):
178 privilege = IrcUserItem.Voice
179 else:
180 privilege = IrcUserItem.Normal
181 if "*" in flags:
182 privilege = IrcUserItem.Admin
183 if flags.startswith("G"):
184 privilege |= IrcUserItem.Away
185 self.__privilege = privilege
186 self.__setIcon()
187
188 def canChangeTopic(self):
189 """
190 Public method to check, if the user is allowed to change the topic.
191
192 @return flag indicating that the topic can be changed (boolean)
193 """
194 return(bool(self.__privilege & IrcUserItem.Operator) or
195 bool(self.__privilege & IrcUserItem.Admin) or
196 bool(self.__privilege & IrcUserItem.Owner))
197
198 def setIgnored(self, ignored):
199 """
200 Public method to set the user status to ignored.
201
202 @param ignored flag indicating the new ignored status
203 @type bool
204 """
205 self.__ignored = ignored
206 self.__setText()
207
208 def isIgnored(self):
209 """
210 Public method to check, if this user is ignored.
211
212 @return flag indicating the ignored status
213 @rtype bool
214 """
215 return self.__ignored
216
217
218 class IrcChannelWidget(QWidget, Ui_IrcChannelWidget):
219 """
220 Class implementing the IRC channel widget.
221
222 @signal sendData(str) emitted to send a message to the channel
223 @signal sendCtcpRequest(str, str, str) emitted to send a CTCP request
224 @signal sendCtcpReply(str, str) emitted to send a CTCP reply
225 @signal channelClosed(str) emitted after the user has left the channel
226 @signal openPrivateChat(str) emitted to open a "channel" for private
227 messages
228 @signal awayCommand(str) emitted to set the away status via the /away
229 command
230 @signal leaveChannels(list) emitted to leave a list of channels
231 @signal leaveAllChannels() emitted to leave all channels
232 """
233 sendData = pyqtSignal(str)
234 sendCtcpRequest = pyqtSignal(str, str, str)
235 sendCtcpReply = pyqtSignal(str, str)
236 channelClosed = pyqtSignal(str)
237 openPrivateChat = pyqtSignal(str)
238 awayCommand = pyqtSignal(str)
239 leaveChannels = pyqtSignal(list)
240 leaveAllChannels = pyqtSignal()
241
242 UrlRe = re.compile(
243 r"""((?:http|ftp|https):\/\/[\w\-_]+(?:\.[\w\-_]+)+"""
244 r"""(?:[\w\-\.,@?^=%&amp;:/~\+#]*[\w\-\@?^=%&amp;/~\+#])?)""")
245
246 JoinIndicator = "--&gt;"
247 LeaveIndicator = "&lt;--"
248 MessageIndicator = "***"
249
250 def __init__(self, parent=None):
251 """
252 Constructor
253
254 @param parent reference to the parent widget (QWidget)
255 """
256 super(IrcChannelWidget, self).__init__(parent)
257 self.setupUi(self)
258
259 self.__ui = e5App().getObject("UserInterface")
260 self.__ircWidget = parent
261
262 self.editTopicButton.setIcon(
263 UI.PixmapCache.getIcon("ircEditTopic.png"))
264 self.editTopicButton.hide()
265
266 height = self.usersList.height() + self.messages.height()
267 self.splitter.setSizes([height * 0.3, height * 0.7])
268
269 self.__initMessagesMenu()
270 self.__initUsersMenu()
271
272 self.__name = ""
273 self.__userName = ""
274 self.__partMessage = ""
275 self.__prefixToPrivilege = {}
276 self.__private = False
277 self.__privatePartner = ""
278 self.__whoIsNick = ""
279
280 self.__markerLine = ""
281 self.__hidden = True
282
283 self.__serviceNamesLower = ["nickserv", "chanserv", "memoserv"]
284
285 self.__patterns = [
286 # :foo_!n=foo@foohost.bar.net PRIVMSG #eric-ide :some long message
287 # :foo_!n=foo@foohost.bar.net PRIVMSG bar_ :some long message
288 (re.compile(r":([^!]+)!([^ ]+)\sPRIVMSG\s([^ ]+)\s:(.*)"),
289 self.__message),
290 # :foo_!n=foo@foohost.bar.net JOIN :#eric-ide
291 (re.compile(r":([^!]+)!([^ ]+)\sJOIN\s:?([^ ]+)"),
292 self.__userJoin),
293 # :foo_!n=foo@foohost.bar.net PART #eric-ide :part message
294 (re.compile(r":([^!]+).*\sPART\s([^ ]+)\s:(.*)"), self.__userPart),
295 # :foo_!n=foo@foohost.bar.net PART #eric-ide
296 (re.compile(r":([^!]+).*\sPART\s([^ ]+)\s*"), self.__userPart),
297 # :foo_!n=foo@foohost.bar.net QUIT :quit message
298 (re.compile(r":([^!]+).*\sQUIT\s:(.*)"), self.__userQuit),
299 # :foo_!n=foo@foohost.bar.net QUIT
300 (re.compile(r":([^!]+).*\sQUIT\s*"), self.__userQuit),
301 # :foo_!n=foo@foohost.bar.net NICK :newnick
302 (re.compile(r":([^!]+).*\sNICK\s:(.*)"), self.__userNickChange),
303 # :foo_!n=foo@foohost.bar.net MODE #eric-ide +o foo_
304 (re.compile(r":([^!]+).*\sMODE\s([^ ]+)\s([+-][ovO]+)\s([^ ]+).*"),
305 self.__setUserPrivilege),
306 # :cameron.freenode.net MODE #eric-ide +ns
307 (re.compile(r":([^ ]+)\sMODE\s([^ ]+)\s(.+)"),
308 self.__updateChannelModes),
309 # :foo_!n=foo@foohost.bar.net TOPIC #eric-ide :eric - Python IDE
310 (re.compile(r":.*\sTOPIC\s([^ ]+)\s:(.*)"), self.__setTopic),
311 # :sturgeon.freenode.net 301 foo_ bar :Gone away for now
312 (re.compile(r":.*\s301\s([^ ]+)\s([^ ]+)\s:(.+)"),
313 self.__userAway),
314 # :sturgeon.freenode.net 315 foo_ #eric-ide :End of /WHO list.
315 (re.compile(r":.*\s315\s[^ ]+\s([^ ]+)\s:(.*)"), self.__whoEnd),
316 # :zelazny.freenode.net 324 foo_ #eric-ide +cnt
317 (re.compile(r":.*\s324\s.*\s([^ ]+)\s(.+)"), self.__channelModes),
318 # :zelazny.freenode.net 328 foo_ #eric-ide :http://www.bugger.com/
319 (re.compile(r":.*\s328\s.*\s([^ ]+)\s:(.+)"), self.__channelUrl),
320 # :zelazny.freenode.net 329 foo_ #eric-ide 1353001005
321 (re.compile(r":.*\s329\s.*\s([^ ]+)\s(.+)"),
322 self.__channelCreated),
323 # :zelazny.freenode.net 332 foo_ #eric-ide :eric support channel
324 (re.compile(r":.*\s332\s.*\s([^ ]+)\s:(.*)"), self.__setTopic),
325 # :zelazny.freenode.net foo_ 333 #eric-ide foo 1353089020
326 (re.compile(r":.*\s333\s.*\s([^ ]+)\s([^ ]+)\s(\d+)"),
327 self.__topicCreated),
328 # :cameron.freenode.net 352 detlev_ #eric-ide ~foo foohost.bar.net
329 # cameron.freenode.net foo_ H :0 Foo Bar
330 (re.compile(
331 r":.*\s352\s[^ ]+\s([^ ]+)\s([^ ]+)\s([^ ]+)\s[^ ]+\s([^ ]+)"
332 r"\s([^ ]+)\s:\d+\s(.*)"), self.__whoEntry),
333 # :zelazny.freenode.net 353 foo_ @ #eric-ide :@user1 +user2 user3
334 (re.compile(r":.*\s353\s.*\s.\s([^ ]+)\s:(.*)"), self.__userList),
335 # :sturgeon.freenode.net 354 foo_ 42 ChanServ H@
336 (re.compile(r":.*\s354\s[^ ]+\s42\s([^ ]+)\s(.*)"),
337 self.__autoWhoEntry),
338 # :zelazny.freenode.net 366 foo_ #eric-ide :End of /NAMES list.
339 (re.compile(r":.*\s366\s.*\s([^ ]+)\s:(.*)"), self.__ignore),
340 # :sturgeon.freenode.net 704 foo_ index :Help topics available:
341 (re.compile(r":.*\s70[456]\s[^ ]+\s([^ ]+)\s:(.*)"), self.__help),
342
343 # WHOIS replies
344 # :sturgeon.freenode.net 311 foo_ bar ~bar barhost.foo.net * :Bar
345 (re.compile(
346 r":.*\s311\s[^ ]+\s([^ ]+)\s([^ ]+)\s([^ ]+)\s\*\s:(.*)"),
347 self.__whoIsUser),
348 # :sturgeon.freenode.net 319 foo_ bar :@#eric-ide
349 (re.compile(r":.*\s319\s[^ ]+\s([^ ]+)\s:(.*)"),
350 self.__whoIsChannels),
351 # :sturgeon.freenode.net 312 foo_ bar sturgeon.freenode.net :London
352 (re.compile(r":.*\s312\s[^ ]+\s([^ ]+)\s([^ ]+)\s:(.*)"),
353 self.__whoIsServer),
354 # :sturgeon.freenode.net 671 foo_ bar :is using a secure connection
355 (re.compile(r":.*\s671\s[^ ]+\s([^ ]+)\s:.*"), self.__whoIsSecure),
356 # :sturgeon.freenode.net 317 foo_ bar 3758 1355046912 :seconds
357 # idle, signon time
358 (re.compile(r":.*\s317\s[^ ]+\s([^ ]+)\s(\d+)\s(\d+)\s:.*"),
359 self.__whoIsIdle),
360 # :sturgeon.freenode.net 330 foo_ bar bar :is logged in as
361 (re.compile(r":.*\s330\s[^ ]+\s([^ ]+)\s([^ ]+)\s:.*"),
362 self.__whoIsAccount),
363 # :sturgeon.freenode.net 318 foo_ bar :End of /WHOIS list.
364 (re.compile(r":.*\s318\s[^ ]+\s([^ ]+)\s:(.*)"), self.__whoIsEnd),
365 # :sturgeon.freenode.net 307 foo_ bar :is an identified user
366 (re.compile(r":.*\s307\s[^ ]+\s([^ ]+)\s:(.*)"),
367 self.__whoIsIdentify),
368 # :sturgeon.freenode.net 320 foo_ bar :is an identified user
369 (re.compile(r":.*\s320\s[^ ]+\s([^ ]+)\s:(.*)"),
370 self.__whoIsIdentify),
371 # :sturgeon.freenode.net 310 foo_ bar :is available for help
372 (re.compile(r":.*\s310\s[^ ]+\s([^ ]+)\s:(.*)"),
373 self.__whoIsHelper),
374 # :sturgeon.freenode.net 338 foo_ bar real.ident@real.host
375 # 12.34.56.78 :Actual user@host, Actual IP
376 (re.compile(r":.*\s338\s[^ ]+\s([^ ]+)\s([^ ]+)\s([^ ]+)\s:.*"),
377 self.__whoIsActually),
378 # :sturgeon.freenode.net 313 foo_ bar :is an IRC Operator
379 (re.compile(r":.*\s313\s[^ ]+\s([^ ]+)\s:(.*)"),
380 self.__whoIsOperator),
381 # :sturgeon.freenode.net 378 foo_ bar :is connecting from
382 # *@mnch-4d044d5a.pool.mediaWays.net 77.4.77.90
383 (re.compile(r":.*\s378\s[^ ]+\s([^ ]+)\s:.*\s([^ ]+)\s([^ ]+)"),
384 self.__whoIsConnection),
385 ]
386
387 self.__autoWhoTemplate = "WHO {0} %tnf,42"
388 self.__autoWhoTimer = QTimer()
389 self.__autoWhoTimer.setSingleShot(True)
390 self.__autoWhoTimer.timeout.connect(self.__sendAutoWhoCommand)
391 self.__autoWhoRequested = False
392
393 @pyqtSlot()
394 def on_messageEdit_returnPressed(self):
395 """
396 Private slot to send a message to the channel.
397 """
398 msg = self.messageEdit.text()
399 if msg:
400 self.__processUserMessage(msg)
401
402 def __processUserMessage(self, msg):
403 """
404 Private method to process a message entered by the user or via the
405 user list context menu.
406
407 @param msg message to be processed
408 @type str
409 """
410 self.messages.append(
411 '<font color="{0}">{2} <b>&lt;</b><font color="{1}">{3}</font>'
412 '<b>&gt;</b> {4}</font>'.format(
413 Preferences.getIrc("ChannelMessageColour"),
414 Preferences.getIrc("OwnNickColour"),
415 ircTimestamp(), self.__userName,
416 Utilities.html_encode(msg)))
417
418 if msg.startswith("/"):
419 if self.__private:
420 E5MessageBox.information(
421 self,
422 self.tr("Send Message"),
423 self.tr(
424 """Messages starting with a '/' are not allowed"""
425 """ in private chats."""))
426 else:
427 sendData = True
428 # flag set to False, if command was handled
429
430 msgList = msg.split()
431 cmd = msgList[0][1:].upper()
432 if cmd in ["MSG", "QUERY"]:
433 cmd = "PRIVMSG"
434 if len(msgList) > 1:
435 if msgList[1].strip().lower() in \
436 self.__serviceNamesLower:
437 msg = "PRIVMSG " + \
438 msgList[1].strip().lower() + \
439 " :" + " ".join(msgList[2:])
440 else:
441 msg = "PRIVMSG {0} :{1}".format(
442 msgList[1], " ".join(msgList[2:]))
443 else:
444 msgList[0] = cmd
445 msg = " ".join(msgList)
446 elif cmd == "NOTICE":
447 if len(msgList) > 2:
448 msg = "NOTICE {0} :{1}".format(
449 msgList[1], " ".join(msgList[2:]))
450 else:
451 msg = "NOTICE {0}".format(" ".join(msgList[1:]))
452 elif cmd == "PING":
453 receiver = msgList[1]
454 msg = "PING {0} "
455 self.sendCtcpRequest.emit(receiver, "PING", "")
456 sendData = False
457 elif cmd == "IGNORE":
458 sendData = False
459 if len(msgList) > 1:
460 if msgList[1] == "-r":
461 ignored = False
462 userNamesList = msgList[2:]
463 else:
464 ignored = True
465 userNamesList = msgList[1:]
466 else:
467 userNamesList = []
468 userNames = ",".join(
469 u.rstrip(",") for u in userNamesList).split(",")
470 for userName in userNames:
471 itm = self.__findUser(userName)
472 if itm:
473 itm.setIgnored(ignored)
474 elif cmd == "UNIGNORE":
475 sendData = False
476 if len(msgList) > 1:
477 userNamesList = msgList[1:]
478 else:
479 userNamesList = []
480 userNames = ",".join(
481 u.rstrip(",") for u in userNamesList).split(",")
482 for userName in userNames:
483 itm = self.__findUser(userName)
484 if itm:
485 itm.setIgnored(False)
486 elif cmd == "AWAY":
487 sendData = False
488 if len(msgList) > 1:
489 msg = " ".join(msgList[1:])
490 else:
491 msg = ""
492 self.awayCommand.emit(msg)
493 elif cmd == "JOIN":
494 sendData = False
495 if len(msgList) > 1:
496 channels = msgList[1].split(",")
497 if len(msgList) > 2:
498 keys = msgList[2].split(",")
499 else:
500 keys = []
501 for channel, key in zip_longest(
502 channels, keys, fillvalue=""):
503 self.__ircWidget.joinChannel(channel, key)
504 elif cmd == "PART":
505 sendData = False
506 if len(msgList) == 1:
507 self.leaveChannel()
508 else:
509 self.leaveChannels.emit(msgList[1:])
510 elif cmd == "PARTALL":
511 sendData = False
512 self.leaveAllChannels.emit()
513 else:
514 msg = msg[1:]
515 if sendData:
516 self.sendData.emit(msg)
517 else:
518 if self.__private:
519 self.sendData.emit(
520 "PRIVMSG " + self.__privatePartner + " :" + msg)
521 else:
522 self.sendData.emit(
523 "PRIVMSG " + self.__name + " :" + msg)
524
525 self.messageEdit.clear()
526 self.unsetMarkerLine()
527
528 def requestLeave(self):
529 """
530 Public method to leave the channel.
531 """
532 ok = E5MessageBox.yesNo(
533 self,
534 self.tr("Leave IRC channel"),
535 self.tr(
536 """Do you really want to leave the IRC channel"""
537 """ <b>{0}</b>?""").format(self.__name))
538 if ok:
539 self.leaveChannel()
540
541 def leaveChannel(self):
542 """
543 Public slot to leave the channel.
544 """
545 if not self.__private:
546 self.sendData.emit(
547 "PART " + self.__name + " :" + self.__partMessage)
548 self.channelClosed.emit(self.__name)
549
550 def name(self):
551 """
552 Public method to get the name of the channel.
553
554 @return name of the channel (string)
555 """
556 return self.__name
557
558 def setName(self, name):
559 """
560 Public method to set the name of the channel.
561
562 @param name of the channel (string)
563 """
564 self.__name = name
565
566 def getUsersCount(self):
567 """
568 Public method to get the users count of the channel.
569
570 @return users count of the channel (integer)
571 """
572 return self.usersList.count()
573
574 def userName(self):
575 """
576 Public method to get the nick name of the user.
577
578 @return nick name of the user (string)
579 """
580 return self.__userName
581
582 def setUserName(self, name):
583 """
584 Public method to set the user name for the channel.
585
586 @param name user name for the channel (string)
587 """
588 self.__userName = name
589
590 def partMessage(self):
591 """
592 Public method to get the part message.
593
594 @return part message (string)
595 """
596 return self.__partMessage
597
598 def setPartMessage(self, message):
599 """
600 Public method to set the part message.
601
602 @param message message to be used for PART messages (string)
603 """
604 self.__partMessage = message
605
606 def setPrivate(self, private, partner=""):
607 """
608 Public method to set the private chat mode.
609
610 @param private flag indicating private chat mode (boolean)
611 @param partner name of the partner user (string)
612 """
613 self.__private = private
614 self.__privatePartner = partner
615 self.editTopicButton.setEnabled(private)
616
617 def setPrivateInfo(self, infoText):
618 """
619 Public method to set some info text for private chat mode.
620
621 @param infoText info text to be shown (string)
622 """
623 if self.__private:
624 self.topicLabel.setText(infoText)
625
626 def handleMessage(self, line):
627 """
628 Public method to handle the message sent by the server.
629
630 @param line server message (string)
631 @return flag indicating, if the message was handled (boolean)
632 """
633 for patternRe, patternFunc in self.__patterns:
634 match = patternRe.match(line)
635 if match is not None:
636 if patternFunc(match):
637 return True
638
639 return False
640
641 def __message(self, match):
642 """
643 Private method to handle messages to the channel.
644
645 @param match match object that matched the pattern
646 @return flag indicating whether the message was handled (boolean)
647 """
648 # group(1) sender user name
649 # group(2) sender user@host
650 # group(3) target nick
651 # group(4) message
652 if match.group(3).lower() == self.__name.lower():
653 senderName = match.group(1)
654 itm = self.__findUser(senderName)
655 if itm and itm.isIgnored():
656 # user should be ignored
657 return True
658
659 if match.group(4).startswith("\x01"):
660 return self.__handleCtcp(match)
661
662 self.addMessage(senderName, match.group(4))
663 if self.__private and not self.topicLabel.text():
664 self.setPrivateInfo(
665 "{0} - {1}".format(match.group(1), match.group(2)))
666 return True
667
668 return False
669
670 def addMessage(self, sender, msg):
671 """
672 Public method to add a message from external.
673
674 @param sender nick name of the sender (string)
675 @param msg message received from sender (string)
676 """
677 self.__appendMessage(
678 '<font color="{0}">{2} <b>&lt;</b><font color="{1}">{3}</font>'
679 '<b>&gt;</b> {4}</font>'.format(
680 Preferences.getIrc("ChannelMessageColour"),
681 Preferences.getIrc("NickColour"),
682 ircTimestamp(), sender, ircFilter(msg)))
683 if Preferences.getIrc("ShowNotifications"):
684 if Preferences.getIrc("NotifyMessage"):
685 self.__ui.showNotification(
686 UI.PixmapCache.getPixmap("irc48.png"),
687 self.tr("Channel Message"), msg)
688 elif Preferences.getIrc("NotifyNick") and \
689 self.__userName.lower() in msg.lower():
690 self.__ui.showNotification(
691 UI.PixmapCache.getPixmap("irc48.png"),
692 self.tr("Nick mentioned"), msg)
693
694 def addUsers(self, users):
695 """
696 Public method to add users to the channel.
697
698 @param users list of user names to add (list of string)
699 """
700 for user in users:
701 itm = self.__findUser(user)
702 if itm is None:
703 IrcUserItem(user, self.usersList)
704
705 def __userJoin(self, match):
706 """
707 Private method to handle a user joining the channel.
708
709 @param match match object that matched the pattern
710 @return flag indicating whether the message was handled (boolean)
711 """
712 if match.group(3).lower() == self.__name.lower():
713 if self.__userName != match.group(1):
714 IrcUserItem(match.group(1), self.usersList)
715 msg = self.tr(
716 "{0} has joined the channel {1} ({2}).").format(
717 match.group(1), self.__name, match.group(2))
718 self.__addManagementMessage(
719 IrcChannelWidget.JoinIndicator, msg)
720 else:
721 msg = self.tr(
722 "You have joined the channel {0} ({1}).").format(
723 self.__name, match.group(2))
724 self.__addManagementMessage(
725 IrcChannelWidget.JoinIndicator, msg)
726 if Preferences.getIrc("ShowNotifications") and \
727 Preferences.getIrc("NotifyJoinPart"):
728 self.__ui.showNotification(
729 UI.PixmapCache.getPixmap("irc48.png"),
730 self.tr("Join Channel"), msg)
731 return True
732
733 return False
734
735 def __userPart(self, match):
736 """
737 Private method to handle a user leaving the channel.
738
739 @param match match object that matched the pattern
740 @return flag indicating whether the message was handled (boolean)
741 """
742 if match.group(2).lower() == self.__name.lower():
743 itm = self.__findUser(match.group(1))
744 self.usersList.takeItem(self.usersList.row(itm))
745 del itm
746 if match.lastindex == 2:
747 msg = self.tr("{0} has left {1}.").format(
748 match.group(1), self.__name)
749 nmsg = msg
750 self.__addManagementMessage(
751 IrcChannelWidget.LeaveIndicator, msg)
752 else:
753 msg = self.tr("{0} has left {1}: {2}.").format(
754 match.group(1), self.__name, ircFilter(match.group(3)))
755 nmsg = self.tr("{0} has left {1}: {2}.").format(
756 match.group(1), self.__name, match.group(3))
757 self.__addManagementMessage(
758 IrcChannelWidget.LeaveIndicator, msg)
759 if Preferences.getIrc("ShowNotifications") and \
760 Preferences.getIrc("NotifyJoinPart"):
761 self.__ui.showNotification(
762 UI.PixmapCache.getPixmap("irc48.png"),
763 self.tr("Leave Channel"), nmsg)
764 return True
765
766 return False
767
768 def __userQuit(self, match):
769 """
770 Private method to handle a user logging off the server.
771
772 @param match match object that matched the pattern
773 @return flag indicating whether the message was handled (boolean)
774 """
775 itm = self.__findUser(match.group(1))
776 if itm:
777 self.usersList.takeItem(self.usersList.row(itm))
778 del itm
779 if match.lastindex == 1:
780 msg = self.tr("{0} has quit {1}.").format(
781 match.group(1), self.__name)
782 self.__addManagementMessage(
783 IrcChannelWidget.MessageIndicator, msg)
784 else:
785 msg = self.tr("{0} has quit {1}: {2}.").format(
786 match.group(1), self.__name, ircFilter(match.group(2)))
787 self.__addManagementMessage(
788 IrcChannelWidget.MessageIndicator, msg)
789 if Preferences.getIrc("ShowNotifications") and \
790 Preferences.getIrc("NotifyJoinPart"):
791 self.__ui.showNotification(
792 UI.PixmapCache.getPixmap("irc48.png"),
793 self.tr("Quit"), msg)
794
795 # always return False for other channels and server to process
796 return False
797
798 def __userNickChange(self, match):
799 """
800 Private method to handle a nickname change of a user.
801
802 @param match match object that matched the pattern
803 @return flag indicating whether the message was handled (boolean)
804 """
805 itm = self.__findUser(match.group(1))
806 if itm:
807 itm.setName(match.group(2))
808 if match.group(1) == self.__userName:
809 self.__addManagementMessage(
810 IrcChannelWidget.MessageIndicator,
811 self.tr("You are now known as {0}.").format(
812 match.group(2)))
813 self.__userName = match.group(2)
814 else:
815 self.__addManagementMessage(
816 IrcChannelWidget.MessageIndicator,
817 self.tr("User {0} is now known as {1}.").format(
818 match.group(1), match.group(2)))
819
820 # always return False for other channels and server to process
821 return False
822
823 def __userList(self, match):
824 """
825 Private method to handle the receipt of a list of users of the channel.
826
827 @param match match object that matched the pattern
828 @return flag indicating whether the message was handled (boolean)
829 """
830 if match.group(1).lower() == self.__name.lower():
831 users = match.group(2).split()
832 for user in users:
833 userPrivileges, userName = self.__extractPrivilege(user)
834 itm = self.__findUser(userName)
835 if itm is None:
836 itm = IrcUserItem(userName, self.usersList)
837 for privilege in userPrivileges:
838 itm.changePrivilege(privilege)
839
840 self.__setEditTopicButton()
841 return True
842
843 return False
844
845 def __userAway(self, match):
846 """
847 Private method to handle a topic change of the channel.
848
849 @param match match object that matched the pattern
850 @return flag indicating whether the message was handled (boolean)
851 """
852 if match.group(1).lower() == self.__name.lower():
853 self.__addManagementMessage(
854 self.tr("Away"),
855 self.tr("{0} is away: {1}").format(
856 match.group(2), match.group(3)))
857 return True
858
859 return False
860
861 def __setTopic(self, match):
862 """
863 Private method to handle a topic change of the channel.
864
865 @param match match object that matched the pattern
866 @return flag indicating whether the message was handled (boolean)
867 """
868 if match.group(1).lower() == self.__name.lower():
869 self.topicLabel.setText(match.group(2))
870 self.__addManagementMessage(
871 IrcChannelWidget.MessageIndicator,
872 ircFilter(self.tr('The channel topic is: "{0}".').format(
873 match.group(2))))
874 return True
875
876 return False
877
878 def __topicCreated(self, match):
879 """
880 Private method to handle a topic created message.
881
882 @param match match object that matched the pattern
883 @return flag indicating whether the message was handled (boolean)
884 """
885 if match.group(1).lower() == self.__name.lower():
886 self.__addManagementMessage(
887 IrcChannelWidget.MessageIndicator,
888 self.tr("The topic was set by {0} on {1}.").format(
889 match.group(2), QDateTime.fromTime_t(int(match.group(3)))
890 .toString("yyyy-MM-dd hh:mm")))
891 return True
892
893 return False
894
895 def __channelUrl(self, match):
896 """
897 Private method to handle a channel URL message.
898
899 @param match match object that matched the pattern
900 @return flag indicating whether the message was handled (boolean)
901 """
902 if match.group(1).lower() == self.__name.lower():
903 self.__addManagementMessage(
904 IrcChannelWidget.MessageIndicator,
905 ircFilter(self.tr("Channel URL: {0}").format(
906 match.group(2))))
907 return True
908
909 return False
910
911 def __channelModes(self, match):
912 """
913 Private method to handle a message reporting the channel modes.
914
915 @param match match object that matched the pattern
916 @return flag indicating whether the message was handled (boolean)
917 """
918 if match.group(1).lower() == self.__name.lower():
919 modesDict = getChannelModesDict()
920 modesParameters = match.group(2).split()
921 modeString = modesParameters.pop(0)
922 modes = []
923 for modeChar in modeString:
924 if modeChar == "+":
925 continue
926 elif modeChar == "k":
927 parameter = modesParameters.pop(0)
928 modes.append(self.tr(
929 "password protected ({0})").format(parameter))
930 elif modeChar == "l":
931 parameter = modesParameters.pop(0)
932 modes.append(self.tr(
933 "limited to %n user(s)", "", int(parameter)))
934 elif modeChar in modesDict:
935 modes.append(modesDict[modeChar])
936 else:
937 modes.append(modeChar)
938
939 self.__addManagementMessage(
940 IrcChannelWidget.MessageIndicator,
941 self.tr("Channel modes: {0}.").format(", ".join(modes)))
942
943 return True
944
945 return False
946
947 def __channelCreated(self, match):
948 """
949 Private method to handle a channel created message.
950
951 @param match match object that matched the pattern
952 @return flag indicating whether the message was handled (boolean)
953 """
954 if match.group(1).lower() == self.__name.lower():
955 self.__addManagementMessage(
956 IrcChannelWidget.MessageIndicator,
957 self.tr("This channel was created on {0}.").format(
958 QDateTime.fromTime_t(int(match.group(2)))
959 .toString("yyyy-MM-dd hh:mm")))
960 return True
961
962 return False
963
964 def __updateChannelModes(self, match):
965 """
966 Private method to handle a message reporting the channel modes.
967
968 @param match match object that matched the pattern
969 @return flag indicating whether the message was handled (boolean)
970 """
971 # group(1) user or server
972 # group(2) channel
973 # group(3) modes and parameter list
974 if match.group(2).lower() == self.__name.lower():
975 nick = match.group(1)
976 modesParameters = match.group(3).split()
977 modeString = modesParameters.pop(0)
978 isPlus = True
979 message = ""
980 for mode in modeString:
981 if mode == "+":
982 isPlus = True
983 continue
984 elif mode == "-":
985 isPlus = False
986 continue
987 elif mode == "a":
988 if isPlus:
989 message = self.tr(
990 "{0} sets the channel mode to 'anonymous'.")\
991 .format(nick)
992 else:
993 message = self.tr(
994 "{0} removes the 'anonymous' mode from the"
995 " channel.").format(nick)
996 elif mode == "b":
997 if isPlus:
998 message = self.tr(
999 "{0} sets a ban on {1}.").format(
1000 nick, modesParameters.pop(0))
1001 else:
1002 message = self.tr(
1003 "{0} removes the ban on {1}.").format(
1004 nick, modesParameters.pop(0))
1005 elif mode == "c":
1006 if isPlus:
1007 message = self.tr(
1008 "{0} sets the channel mode to 'no colors"
1009 " allowed'.").format(nick)
1010 else:
1011 message = self.tr(
1012 "{0} sets the channel mode to 'allow color"
1013 " codes'.").format(nick)
1014 elif mode == "e":
1015 if isPlus:
1016 message = self.tr(
1017 "{0} sets a ban exception on {1}.").format(
1018 nick, modesParameters.pop(0))
1019 else:
1020 message = self.tr(
1021 "{0} removes the ban exception on {1}.").format(
1022 nick, modesParameters.pop(0))
1023 elif mode == "i":
1024 if isPlus:
1025 message = self.tr(
1026 "{0} sets the channel mode to 'invite only'.")\
1027 .format(nick)
1028 else:
1029 message = self.tr(
1030 "{0} removes the 'invite only' mode from the"
1031 " channel.").format(nick)
1032 elif mode == "k":
1033 if isPlus:
1034 message = self.tr(
1035 "{0} sets the channel key to '{1}'.").format(
1036 nick, modesParameters.pop(0))
1037 else:
1038 message = self.tr(
1039 "{0} removes the channel key.").format(nick)
1040 elif mode == "l":
1041 if isPlus:
1042 message = self.tr(
1043 "{0} sets the channel limit to %n nick(s).", "",
1044 int(modesParameters.pop(0))).format(nick)
1045 else:
1046 message = self.tr(
1047 "{0} removes the channel limit.").format(nick)
1048 elif mode == "m":
1049 if isPlus:
1050 message = self.tr(
1051 "{0} sets the channel mode to 'moderated'.")\
1052 .format(nick)
1053 else:
1054 message = self.tr(
1055 "{0} sets the channel mode to 'unmoderated'.")\
1056 .format(nick)
1057 elif mode == "n":
1058 if isPlus:
1059 message = self.tr(
1060 "{0} sets the channel mode to 'no messages from"
1061 " outside'.").format(nick)
1062 else:
1063 message = self.tr(
1064 "{0} sets the channel mode to 'allow messages"
1065 " from outside'.").format(nick)
1066 elif mode == "p":
1067 if isPlus:
1068 message = self.tr(
1069 "{0} sets the channel mode to 'private'.")\
1070 .format(nick)
1071 else:
1072 message = self.tr(
1073 "{0} sets the channel mode to 'public'.")\
1074 .format(nick)
1075 elif mode == "q":
1076 if isPlus:
1077 message = self.tr(
1078 "{0} sets the channel mode to 'quiet'.")\
1079 .format(nick)
1080 else:
1081 message = self.tr(
1082 "{0} removes the 'quiet' mode from the channel.")\
1083 .format(nick)
1084 elif mode == "r":
1085 continue
1086 elif mode == "s":
1087 if isPlus:
1088 message = self.tr(
1089 "{0} sets the channel mode to 'secret'.")\
1090 .format(nick)
1091 else:
1092 message = self.tr(
1093 "{0} sets the channel mode to 'visible'.")\
1094 .format(nick)
1095 elif mode == "t":
1096 if isPlus:
1097 message = self.tr(
1098 "{0} switches on 'topic protection'.").format(nick)
1099 else:
1100 message = self.tr(
1101 "{0} switches off 'topic protection'.")\
1102 .format(nick)
1103 elif mode == "I":
1104 if isPlus:
1105 message = self.tr(
1106 "{0} sets invitation mask {1}.").format(
1107 nick, modesParameters.pop(0))
1108 else:
1109 message = self.tr(
1110 "{0} removes the invitation mask {1}.").format(
1111 nick, modesParameters.pop(0))
1112
1113 self.__addManagementMessage(self.tr("Mode"), message)
1114
1115 return True
1116
1117 return False
1118
1119 def __setUserPrivilege(self, match):
1120 """
1121 Private method to handle a change of user privileges for the channel.
1122
1123 @param match match object that matched the pattern
1124 @return flag indicating whether the message was handled (boolean)
1125 """
1126 if match.group(2).lower() == self.__name.lower():
1127 itm = self.__findUser(match.group(4))
1128 if itm:
1129 itm.changePrivilege(match.group(3))
1130 self.__setEditTopicButton()
1131 self.__addManagementMessage(
1132 IrcChannelWidget.MessageIndicator,
1133 self.tr("{0} sets mode for {1}: {2}.").format(
1134 match.group(1), match.group(4), match.group(3)))
1135 return True
1136
1137 return False
1138
1139 def __ignore(self, match):
1140 """
1141 Private method to handle a channel message we are not interested in.
1142
1143 @param match match object that matched the pattern
1144 @return flag indicating whether the message was handled (boolean)
1145 """
1146 if match.group(1).lower() == self.__name.lower():
1147 return True
1148
1149 return False
1150
1151 def __help(self, match):
1152 """
1153 Private method to handle a help message.
1154
1155 @param match match object that matched the pattern
1156 @return flag indicating whether the message was handled (boolean)
1157 """
1158 self.__addManagementMessage(
1159 self.tr("Help"),
1160 "{0} {1}".format(match.group(1), ircFilter(match.group(2))))
1161 return True
1162
1163 def __handleCtcp(self, match):
1164 """
1165 Private method to handle a CTCP channel command.
1166
1167 @param match reference to the match object
1168 @return flag indicating, if the message was handled (boolean)
1169 """
1170 # group(1) sender user name
1171 # group(2) sender user@host
1172 # group(3) target nick
1173 # group(4) message
1174 if match.group(4).startswith("\x01"):
1175 ctcpCommand = match.group(4)[1:].split("\x01", 1)[0]
1176 if " " in ctcpCommand:
1177 ctcpRequest, ctcpArg = ctcpCommand.split(" ", 1)
1178 else:
1179 ctcpRequest, ctcpArg = ctcpCommand, ""
1180 ctcpRequest = ctcpRequest.lower()
1181 if ctcpRequest == "version":
1182 msg = "Eric IRC client {0}, {1}".format(Version, Copyright)
1183 self.__addManagementMessage(
1184 self.tr("CTCP"),
1185 self.tr("Received Version request from {0}.").format(
1186 match.group(1)))
1187 self.sendCtcpReply.emit(match.group(1), "VERSION " + msg)
1188 elif ctcpRequest == "ping":
1189 self.__addManagementMessage(
1190 self.tr("CTCP"),
1191 self.tr(
1192 "Received CTCP-PING request from {0},"
1193 " sending answer.").format(match.group(1)))
1194 self.sendCtcpReply.emit(
1195 match.group(1), "PING {0}".format(ctcpArg))
1196 elif ctcpRequest == "clientinfo":
1197 self.__addManagementMessage(
1198 self.tr("CTCP"),
1199 self.tr(
1200 "Received CTCP-CLIENTINFO request from {0},"
1201 " sending answer.").format(match.group(1)))
1202 self.sendCtcpReply.emit(
1203 match.group(1), "CLIENTINFO CLIENTINFO PING VERSION")
1204 else:
1205 self.__addManagementMessage(
1206 self.tr("CTCP"),
1207 self.tr("Received unknown CTCP-{0} request from {1}.")
1208 .format(ctcpRequest, match.group(1)))
1209 return True
1210
1211 return False
1212
1213 def setUserPrivilegePrefix(self, prefixes):
1214 """
1215 Public method to set the user privilege to prefix mapping.
1216
1217 @param prefixes dictionary with privilege as key and prefix as value
1218 """
1219 self.__prefixToPrivilege = {}
1220 for privilege, prefix in prefixes.items():
1221 if prefix:
1222 self.__prefixToPrivilege[prefix] = privilege
1223
1224 def __findUser(self, name):
1225 """
1226 Private method to find the user in the list of users.
1227
1228 @param name user name to search for (string)
1229 @return reference to the list entry (QListWidgetItem)
1230 """
1231 for row in range(self.usersList.count()):
1232 itm = self.usersList.item(row)
1233 if itm.name() == name:
1234 return itm
1235
1236 return None
1237
1238 def __extractPrivilege(self, name):
1239 """
1240 Private method to extract the user privileges out of the name.
1241
1242 @param name user name and prefixes (string)
1243 @return list of privileges and user name (list of string, string)
1244 """
1245 privileges = []
1246 while name[0] in self.__prefixToPrivilege:
1247 prefix = name[0]
1248 privileges.append(self.__prefixToPrivilege[prefix])
1249 name = name[1:]
1250 if name[0] == ",":
1251 name = name[1:]
1252
1253 return privileges, name
1254
1255 def __addManagementMessage(self, indicator, message):
1256 """
1257 Private method to add a channel management message to the list.
1258
1259 @param indicator indicator to be shown (string)
1260 @param message message to be shown (string)
1261 """
1262 if indicator == self.JoinIndicator:
1263 color = Preferences.getIrc("JoinChannelColour")
1264 elif indicator == self.LeaveIndicator:
1265 color = Preferences.getIrc("LeaveChannelColour")
1266 else:
1267 color = Preferences.getIrc("ChannelInfoColour")
1268 self.__appendMessage(
1269 '<font color="{0}">{1} <b>[</b>{2}<b>]</b> {3}</font>'.format(
1270 color, ircTimestamp(), indicator, message))
1271
1272 def __appendMessage(self, message):
1273 """
1274 Private slot to append a message.
1275
1276 @param message message to be appended (string)
1277 """
1278 if self.__hidden and \
1279 self.__markerLine == "" and \
1280 Preferences.getIrc("MarkPositionWhenHidden"):
1281 self.setMarkerLine()
1282 self.messages.append(message)
1283
1284 def setMarkerLine(self):
1285 """
1286 Public method to draw a line to mark the current position.
1287 """
1288 self.unsetMarkerLine()
1289 self.__markerLine = \
1290 '<span style=" color:{0}; background-color:{1};">{2}</span>'\
1291 .format(Preferences.getIrc("MarkerLineForegroundColour"),
1292 Preferences.getIrc("MarkerLineBackgroundColour"),
1293 self.tr('--- New From Here ---'))
1294 self.messages.append(self.__markerLine)
1295
1296 def unsetMarkerLine(self):
1297 """
1298 Public method to remove the marker line.
1299 """
1300 if self.__markerLine:
1301 txt = self.messages.toHtml()
1302 if txt.endswith(self.__markerLine + "</p></body></html>"):
1303 # remove empty last paragraph
1304 pos = txt.rfind("<p")
1305 txt = txt[:pos] + "</body></html>"
1306 else:
1307 txt = txt.replace(self.__markerLine, "")
1308 self.messages.setHtml(txt)
1309 self.__markerLine = ""
1310 self.messages.moveCursor(QTextCursor.End)
1311
1312 def __clearMessages(self):
1313 """
1314 Private slot to clear the contents of the messages display.
1315 """
1316 self.messages.clear()
1317
1318 def __copyMessages(self):
1319 """
1320 Private slot to copy the selection of the messages display to the
1321 clipboard.
1322 """
1323 self.messages.copy()
1324
1325 def __copyAllMessages(self):
1326 """
1327 Private slot to copy the contents of the messages display to the
1328 clipboard.
1329 """
1330 txt = self.messages.toPlainText()
1331 if txt:
1332 cb = QApplication.clipboard()
1333 cb.setText(txt)
1334
1335 def __cutAllMessages(self):
1336 """
1337 Private slot to cut the contents of the messages display to the
1338 clipboard.
1339 """
1340 txt = self.messages.toPlainText()
1341 if txt:
1342 cb = QApplication.clipboard()
1343 cb.setText(txt)
1344 self.messages.clear()
1345
1346 def __saveMessages(self):
1347 """
1348 Private slot to save the contents of the messages display.
1349 """
1350 hasText = not self.messages.document().isEmpty()
1351 if hasText:
1352 if Utilities.isWindowsPlatform():
1353 htmlExtension = "htm"
1354 else:
1355 htmlExtension = "html"
1356 fname, selectedFilter = E5FileDialog.getSaveFileNameAndFilter(
1357 self,
1358 self.tr("Save Messages"),
1359 "",
1360 self.tr(
1361 "HTML Files (*.{0});;Text Files (*.txt);;All Files (*)")
1362 .format(htmlExtension),
1363 None,
1364 E5FileDialog.Options(E5FileDialog.DontConfirmOverwrite))
1365 if fname:
1366 ext = QFileInfo(fname).suffix()
1367 if not ext:
1368 ex = selectedFilter.split("(*")[1].split(")")[0]
1369 if ex:
1370 fname += ex
1371 ext = QFileInfo(fname).suffix()
1372 if QFileInfo(fname).exists():
1373 res = E5MessageBox.yesNo(
1374 self,
1375 self.tr("Save Messages"),
1376 self.tr("<p>The file <b>{0}</b> already exists."
1377 " Overwrite it?</p>").format(fname),
1378 icon=E5MessageBox.Warning)
1379 if not res:
1380 return
1381 fname = Utilities.toNativeSeparators(fname)
1382
1383 try:
1384 if ext.lower() in ["htm", "html"]:
1385 txt = self.messages.toHtml()
1386 else:
1387 txt = self.messages.toPlainText()
1388 f = open(fname, "w", encoding="utf-8")
1389 f.write(txt)
1390 f.close()
1391 except IOError as err:
1392 E5MessageBox.critical(
1393 self,
1394 self.tr("Error saving Messages"),
1395 self.tr(
1396 """<p>The messages contents could not be written"""
1397 """ to <b>{0}</b></p><p>Reason: {1}</p>""")
1398 .format(fname, str(err)))
1399
1400 def __initMessagesMenu(self):
1401 """
1402 Private slot to initialize the context menu of the messages pane.
1403 """
1404 self.__messagesMenu = QMenu(self)
1405 self.__copyMessagesAct = \
1406 self.__messagesMenu.addAction(
1407 UI.PixmapCache.getIcon("editCopy.png"),
1408 self.tr("Copy"), self.__copyMessages)
1409 self.__messagesMenu.addSeparator()
1410 self.__cutAllMessagesAct = \
1411 self.__messagesMenu.addAction(
1412 UI.PixmapCache.getIcon("editCut.png"),
1413 self.tr("Cut all"), self.__cutAllMessages)
1414 self.__copyAllMessagesAct = \
1415 self.__messagesMenu.addAction(
1416 UI.PixmapCache.getIcon("editCopy.png"),
1417 self.tr("Copy all"), self.__copyAllMessages)
1418 self.__messagesMenu.addSeparator()
1419 self.__clearMessagesAct = \
1420 self.__messagesMenu.addAction(
1421 UI.PixmapCache.getIcon("editDelete.png"),
1422 self.tr("Clear"), self.__clearMessages)
1423 self.__messagesMenu.addSeparator()
1424 self.__saveMessagesAct = \
1425 self.__messagesMenu.addAction(
1426 UI.PixmapCache.getIcon("fileSave.png"),
1427 self.tr("Save"), self.__saveMessages)
1428 self.__messagesMenu.addSeparator()
1429 self.__setMarkerMessagesAct = self.__messagesMenu.addAction(
1430 self.tr("Mark Current Position"), self.setMarkerLine)
1431 self.__unsetMarkerMessagesAct = self.__messagesMenu.addAction(
1432 self.tr("Remove Position Marker"),
1433 self.unsetMarkerLine)
1434
1435 self.on_messages_copyAvailable(False)
1436
1437 @pyqtSlot(bool)
1438 def on_messages_copyAvailable(self, yes):
1439 """
1440 Private slot to react to text selection/deselection of the messages
1441 edit.
1442
1443 @param yes flag signaling the availability of selected text (boolean)
1444 """
1445 self.__copyMessagesAct.setEnabled(yes)
1446
1447 @pyqtSlot(QPoint)
1448 def on_messages_customContextMenuRequested(self, pos):
1449 """
1450 Private slot to show the context menu of the messages pane.
1451
1452 @param pos the position of the mouse pointer (QPoint)
1453 """
1454 enable = not self.messages.document().isEmpty()
1455 self.__cutAllMessagesAct.setEnabled(enable)
1456 self.__copyAllMessagesAct.setEnabled(enable)
1457 self.__saveMessagesAct.setEnabled(enable)
1458 self.__setMarkerMessagesAct.setEnabled(self.__markerLine == "")
1459 self.__unsetMarkerMessagesAct.setEnabled(self.__markerLine != "")
1460 self.__messagesMenu.popup(self.messages.mapToGlobal(pos))
1461
1462 def __whoIs(self):
1463 """
1464 Private slot to get information about the selected user.
1465 """
1466 self.__whoIsNick = self.usersList.selectedItems()[0].text()
1467 self.sendData.emit("WHOIS " + self.__whoIsNick)
1468
1469 def __openPrivateChat(self):
1470 """
1471 Private slot to open a chat with the selected user.
1472 """
1473 user = self.usersList.selectedItems()[0].text()
1474 self.openPrivateChat.emit(user)
1475
1476 def __sendUserMessage(self):
1477 """
1478 Private slot to send a private message to a specific user.
1479 """
1480 from E5Gui import E5TextInputDialog
1481
1482 user = self.usersList.selectedItems()[0].text()
1483 ok, message = E5TextInputDialog.getText(
1484 self, self.tr("Send Message"),
1485 self.tr("Enter the message to be sent:"),
1486 minimumWidth=400)
1487 if ok and message:
1488 self.__processUserMessage("/MSG {0} {1}".format(user, message))
1489
1490 def __sendUserQuery(self):
1491 """
1492 Private slot to send a query message to a specific user.
1493 """
1494 from E5Gui import E5TextInputDialog
1495
1496 user = self.usersList.selectedItems()[0].text()
1497 ok, message = E5TextInputDialog.getText(
1498 self, self.tr("Send Query"),
1499 self.tr("Enter the message to be sent:"),
1500 minimumWidth=400)
1501 if ok and message:
1502 self.__processUserMessage("/QUERY {0} {1}".format(user, message))
1503
1504 def __sendUserNotice(self):
1505 """
1506 Private slot to send a notice message to a specific user.
1507 """
1508 from E5Gui import E5TextInputDialog
1509
1510 user = self.usersList.selectedItems()[0].text()
1511 ok, message = E5TextInputDialog.getText(
1512 self, self.tr("Send Notice"),
1513 self.tr("Enter the message to be sent:"),
1514 minimumWidth=400)
1515 if ok and message:
1516 self.__processUserMessage("/NOTICE {0} {1}".format(user, message))
1517
1518 def __pingUser(self):
1519 """
1520 Private slot to send a ping to a specific user.
1521 """
1522 user = self.usersList.selectedItems()[0].text()
1523 self.__processUserMessage("/PING {0}".format(user))
1524
1525 def __ignoreUser(self):
1526 """
1527 Private slot to ignore a specific user.
1528 """
1529 user = self.usersList.selectedItems()[0].text()
1530 self.__processUserMessage("/IGNORE {0}".format(user))
1531
1532 def __initUsersMenu(self):
1533 """
1534 Private slot to initialize the users list context menu.
1535 """
1536 self.__usersMenu = QMenu(self)
1537 self.__whoIsAct = self.__usersMenu.addAction(
1538 self.tr("Who Is"), self.__whoIs)
1539 self.__usersMenu.addSeparator()
1540 self.__privateChatAct = self.__usersMenu.addAction(
1541 self.tr("Private Chat"), self.__openPrivateChat)
1542 self.__usersMenu.addSeparator()
1543 self.__sendUserMessageAct = self.__usersMenu.addAction(
1544 self.tr("Send Message"), self.__sendUserMessage)
1545 self.__sendUserQueryAct = self.__usersMenu.addAction(
1546 self.tr("Send Query"), self.__sendUserQuery)
1547 self.__sendUserNoticeAct = self.__usersMenu.addAction(
1548 self.tr("Send Notice"), self.__sendUserNotice)
1549 self.__usersMenu.addSeparator()
1550 self.__pingUserAct = self.__usersMenu.addAction(
1551 self.tr("Send Ping"), self.__pingUser)
1552 self.__ignoreUserAct = self.__usersMenu.addAction(
1553 self.tr("Ignore User"), self.__ignoreUser)
1554 self.__usersMenu.addSeparator()
1555 self.__usersListRefreshAct = self.__usersMenu.addAction(
1556 self.tr("Refresh"), self.__sendAutoWhoCommand)
1557
1558 @pyqtSlot(QPoint)
1559 def on_usersList_customContextMenuRequested(self, pos):
1560 """
1561 Private slot to show the context menu of the users list.
1562
1563 @param pos the position of the mouse pointer (QPoint)
1564 """
1565 enable = len(self.usersList.selectedItems()) > 0
1566 enablePrivate = enable and not self.__private
1567 itm = self.usersList.itemAt(pos)
1568 if itm and enablePrivate:
1569 enablePrivate = itm.text().lower() not in [
1570 "chanserv", self.__userName.lower()]
1571 self.__whoIsAct.setEnabled(enable)
1572 self.__privateChatAct.setEnabled(enablePrivate)
1573 self.__usersListRefreshAct.setEnabled(
1574 self.usersList.count() <= Preferences.getIrc("AutoUserInfoMax"))
1575 self.__usersMenu.popup(self.usersList.mapToGlobal(pos))
1576
1577 def hideEvent(self, evt):
1578 """
1579 Protected method handling hide events.
1580
1581 @param evt reference to the hide event (QHideEvent)
1582 """
1583 self.__hidden = True
1584
1585 def showEvent(self, evt):
1586 """
1587 Protected method handling show events.
1588
1589 @param evt reference to the show event (QShowEvent)
1590 """
1591 self.__hidden = False
1592
1593 def initAutoWho(self):
1594 """
1595 Public method to initialize the Auto Who system.
1596 """
1597 if Preferences.getIrc("AutoUserInfoLookup"):
1598 self.__autoWhoTimer.setInterval(
1599 Preferences.getIrc("AutoUserInfoInterval") * 1000)
1600 self.__autoWhoTimer.start()
1601
1602 @pyqtSlot()
1603 def __sendAutoWhoCommand(self):
1604 """
1605 Private slot to send the WHO command to update the users list.
1606 """
1607 if self.usersList.count() <= Preferences.getIrc("AutoUserInfoMax"):
1608 self.__autoWhoRequested = True
1609 self.sendData.emit(self.__autoWhoTemplate.format(self.__name))
1610
1611 def __autoWhoEntry(self, match):
1612 """
1613 Private method to handle a WHO entry returned by the server as
1614 requested automatically.
1615
1616 @param match match object that matched the pattern
1617 @return flag indicating whether the message was handled (boolean)
1618 """
1619 # group(1) nick
1620 # group(2) user flags
1621 if self.__autoWhoRequested:
1622 itm = self.__findUser(match.group(1))
1623 if itm:
1624 itm.parseWhoFlags(match.group(2))
1625 return True
1626
1627 return False
1628
1629 def __whoEnd(self, match):
1630 """
1631 Private method to handle the end of the WHO list.
1632
1633 @param match match object that matched the pattern
1634 @return flag indicating whether the message was handled (boolean)
1635 """
1636 if match.group(1).lower() == self.__name.lower():
1637 if self.__autoWhoRequested:
1638 self.__autoWhoRequested = False
1639 self.initAutoWho()
1640 else:
1641 self.__addManagementMessage(
1642 self.tr("Who"),
1643 self.tr("End of WHO list for {0}.").format(
1644 match.group(1)))
1645 return True
1646
1647 return False
1648
1649 def __whoEntry(self, match):
1650 """
1651 Private method to handle a WHO entry returned by the server as
1652 requested manually.
1653
1654 @param match match object that matched the pattern
1655 @return flag indicating whether the message was handled (boolean)
1656 """
1657 # group(1) channel
1658 # group(2) user
1659 # group(3) host
1660 # group(4) nick
1661 # group(5) user flags
1662 # group(6) real name
1663 if match.group(1).lower() == self.__name.lower():
1664 away = self.tr(" (Away)") if match.group(5).startswith("G") \
1665 else ""
1666 self.__addManagementMessage(
1667 self.tr("Who"),
1668 self.tr("{0} is {1}@{2} ({3}){4}").format(
1669 match.group(4), match.group(2), match.group(3),
1670 match.group(6), away))
1671 return True
1672
1673 return False
1674
1675 def __whoIsUser(self, match):
1676 """
1677 Private method to handle the WHOIS user reply.
1678
1679 @param match match object that matched the pattern
1680 @return flag indicating whether the message was handled (boolean)
1681 """
1682 # group(1) nick
1683 # group(2) user
1684 # group(3) host
1685 # group(4) real name
1686 if match.group(1) == self.__whoIsNick:
1687 realName = match.group(4).replace("<", "&lt;").replace(">", "&gt;")
1688 self.__addManagementMessage(
1689 self.tr("Whois"),
1690 self.tr("{0} is {1}@{2} ({3}).").format(
1691 match.group(1), match.group(2), match.group(3), realName))
1692 return True
1693
1694 return False
1695
1696 def __whoIsChannels(self, match):
1697 """
1698 Private method to handle the WHOIS channels reply.
1699
1700 @param match match object that matched the pattern
1701 @return flag indicating whether the message was handled (boolean)
1702 """
1703 # group(1) nick
1704 # group(2) channels
1705 if match.group(1) == self.__whoIsNick:
1706 userChannels = []
1707 voiceChannels = []
1708 opChannels = []
1709 halfopChannels = []
1710 ownerChannels = []
1711 adminChannels = []
1712
1713 # generate the list of channels the user is in
1714 channelList = match.group(2).split()
1715 for channel in channelList:
1716 if channel.startswith(("*", "&")):
1717 adminChannels.append(channel[1:])
1718 elif channel.startswith(("!", "~")) and \
1719 self.__ircWidget.isChannelName(channel[1:]):
1720 ownerChannels.append(channel[1:])
1721 elif channel.startswith("@+"):
1722 opChannels.append(channel[2:])
1723 elif channel.startswith("@"):
1724 opChannels.append(channel[1:])
1725 elif channel.startswith("%"):
1726 halfopChannels.append(channel[1:])
1727 elif channel.startswith("+"):
1728 voiceChannels.append(channel[1:])
1729 else:
1730 userChannels.append(channel)
1731
1732 # show messages
1733 if userChannels:
1734 self.__addManagementMessage(
1735 self.tr("Whois"),
1736 self.tr("{0} is a user on channels: {1}").format(
1737 match.group(1), " ".join(userChannels)))
1738 if voiceChannels:
1739 self.__addManagementMessage(
1740 self.tr("Whois"),
1741 self.tr("{0} has voice on channels: {1}").format(
1742 match.group(1), " ".join(voiceChannels)))
1743 if halfopChannels:
1744 self.__addManagementMessage(
1745 self.tr("Whois"),
1746 self.tr("{0} is a halfop on channels: {1}").format(
1747 match.group(1), " ".join(halfopChannels)))
1748 if opChannels:
1749 self.__addManagementMessage(
1750 self.tr("Whois"),
1751 self.tr("{0} is an operator on channels: {1}").format(
1752 match.group(1), " ".join(opChannels)))
1753 if ownerChannels:
1754 self.__addManagementMessage(
1755 self.tr("Whois"),
1756 self.tr("{0} is owner of channels: {1}").format(
1757 match.group(1), " ".join(ownerChannels)))
1758 if adminChannels:
1759 self.__addManagementMessage(
1760 self.tr("Whois"),
1761 self.tr("{0} is admin on channels: {1}").format(
1762 match.group(1), " ".join(adminChannels)))
1763 return True
1764
1765 return False
1766
1767 def __whoIsServer(self, match):
1768 """
1769 Private method to handle the WHOIS server reply.
1770
1771 @param match match object that matched the pattern
1772 @return flag indicating whether the message was handled (boolean)
1773 """
1774 # group(1) nick
1775 # group(2) server
1776 # group(3) server info
1777 if match.group(1) == self.__whoIsNick:
1778 self.__addManagementMessage(
1779 self.tr("Whois"),
1780 self.tr("{0} is online via {1} ({2}).").format(
1781 match.group(1), match.group(2), match.group(3)))
1782 return True
1783
1784 return False
1785
1786 def __whoIsOperator(self, match):
1787 """
1788 Private method to handle the WHOIS operator reply.
1789
1790 @param match match object that matched the pattern
1791 @return flag indicating whether the message was handled (boolean)
1792 """
1793 # group(1) nick
1794 # group(2) message
1795 if match.group(1) == self.__whoIsNick:
1796 if match.group(2).lower().startswith("is an irc operator"):
1797 self.__addManagementMessage(
1798 self.tr("Whois"),
1799 self.tr("{0} is an IRC Operator.").format(
1800 match.group(1)))
1801 else:
1802 self.__addManagementMessage(
1803 self.tr("Whois"),
1804 "{0} {1}".format(match.group(1), match.group(2)))
1805 return True
1806
1807 return False
1808
1809 def __whoIsIdle(self, match):
1810 """
1811 Private method to handle the WHOIS idle reply.
1812
1813 @param match match object that matched the pattern
1814 @return flag indicating whether the message was handled (boolean)
1815 """
1816 # group(1) nick
1817 # group(2) idle seconds
1818 # group(3) signon time
1819 if match.group(1) == self.__whoIsNick:
1820 seconds = int(match.group(2))
1821 minutes = seconds // 60
1822 hours = minutes // 60
1823 days = hours // 24
1824
1825 signonTimestamp = int(match.group(3))
1826 signonTime = QDateTime()
1827 signonTime.setTime_t(signonTimestamp)
1828
1829 if days:
1830 daysString = self.tr("%n day(s)", "", days)
1831 hoursString = self.tr("%n hour(s)", "", hours)
1832 minutesString = self.tr("%n minute(s)", "", minutes)
1833 secondsString = self.tr("%n second(s)", "", seconds)
1834 self.__addManagementMessage(
1835 self.tr("Whois"),
1836 self.tr(
1837 "{0} has been idle for {1}, {2}, {3}, and {4}.",
1838 "{0} = name of person, {1} = (x days),"
1839 " {2} = (x hours), {3} = (x minutes),"
1840 " {4} = (x seconds)").format(
1841 match.group(1), daysString, hoursString, minutesString,
1842 secondsString))
1843 elif hours:
1844 hoursString = self.tr("%n hour(s)", "", hours)
1845 minutesString = self.tr("%n minute(s)", "", minutes)
1846 secondsString = self.tr("%n second(s)", "", seconds)
1847 self.__addManagementMessage(
1848 self.tr("Whois"),
1849 self.tr(
1850 "{0} has been idle for {1}, {2}, and {3}.",
1851 "{0} = name of person, {1} = (x hours), "
1852 "{2} = (x minutes), {3} = (x seconds)")
1853 .format(match.group(1), hoursString, minutesString,
1854 secondsString))
1855 elif minutes:
1856 minutesString = self.tr("%n minute(s)", "", minutes)
1857 secondsString = self.tr("%n second(s)", "", seconds)
1858 self.__addManagementMessage(
1859 self.tr("Whois"),
1860 self.tr(
1861 "{0} has been idle for {1} and {2}.",
1862 "{0} = name of person, {1} = (x minutes), "
1863 "{3} = (x seconds)")
1864 .format(match.group(1), minutesString, secondsString))
1865 else:
1866 self.__addManagementMessage(
1867 self.tr("Whois"),
1868 self.tr(
1869 "{0} has been idle for %n second(s).", "",
1870 seconds).format(match.group(1)))
1871
1872 if not signonTime.isNull():
1873 self.__addManagementMessage(
1874 self.tr("Whois"),
1875 self.tr("{0} has been online since {1}.").format(
1876 match.group(1),
1877 signonTime.toString("yyyy-MM-dd, hh:mm:ss")))
1878 return True
1879
1880 return False
1881
1882 def __whoIsEnd(self, match):
1883 """
1884 Private method to handle the end of WHOIS reply.
1885
1886 @param match match object that matched the pattern
1887 @return flag indicating whether the message was handled (boolean)
1888 """
1889 # group(1) nick
1890 # group(2) end message
1891 if match.group(1) == self.__whoIsNick:
1892 self.__whoIsNick = ""
1893 self.__addManagementMessage(
1894 self.tr("Whois"),
1895 self.tr("End of WHOIS list for {0}.").format(
1896 match.group(1)))
1897 return True
1898
1899 return False
1900
1901 def __whoIsIdentify(self, match):
1902 """
1903 Private method to handle the WHOIS identify and identified replies.
1904
1905 @param match match object that matched the pattern
1906 @return flag indicating whether the message was handled (boolean)
1907 """
1908 # group(1) nick
1909 # group(2) identified message
1910 if match.group(1) == self.__whoIsNick:
1911 self.__addManagementMessage(
1912 self.tr("Whois"),
1913 self.tr("{0} is an identified user.").format(
1914 match.group(1)))
1915 return True
1916
1917 return False
1918
1919 def __whoIsHelper(self, match):
1920 """
1921 Private method to handle the WHOIS helper reply.
1922
1923 @param match match object that matched the pattern
1924 @return flag indicating whether the message was handled (boolean)
1925 """
1926 # group(1) nick
1927 # group(2) helper message
1928 if match.group(1) == self.__whoIsNick:
1929 self.__addManagementMessage(
1930 self.tr("Whois"),
1931 self.tr("{0} is available for help.").format(
1932 match.group(1)))
1933 return True
1934
1935 return False
1936
1937 def __whoIsAccount(self, match):
1938 """
1939 Private method to handle the WHOIS account reply.
1940
1941 @param match match object that matched the pattern
1942 @return flag indicating whether the message was handled (boolean)
1943 """
1944 # group(1) nick
1945 # group(2) login name
1946 if match.group(1) == self.__whoIsNick:
1947 self.__addManagementMessage(
1948 self.tr("Whois"),
1949 self.tr("{0} is logged in as {1}.").format(
1950 match.group(1), match.group(2)))
1951 return True
1952
1953 return False
1954
1955 def __whoIsActually(self, match):
1956 """
1957 Private method to handle the WHOIS actually reply.
1958
1959 @param match match object that matched the pattern
1960 @return flag indicating whether the message was handled (boolean)
1961 """
1962 # group(1) nick
1963 # group(2) actual user@host
1964 # group(3) actual IP
1965 if match.group(1) == self.__whoIsNick:
1966 self.__addManagementMessage(
1967 self.tr("Whois"),
1968 self.tr(
1969 "{0} is actually using the host {1} (IP: {2}).").format(
1970 match.group(1), match.group(2), match.group(3)))
1971 return True
1972
1973 return False
1974
1975 def __whoIsSecure(self, match):
1976 """
1977 Private method to handle the WHOIS secure reply.
1978
1979 @param match match object that matched the pattern
1980 @return flag indicating whether the message was handled (boolean)
1981 """
1982 # group(1) nick
1983 if match.group(1) == self.__whoIsNick:
1984 self.__addManagementMessage(
1985 self.tr("Whois"),
1986 self.tr("{0} is using a secure connection.").format(
1987 match.group(1)))
1988 return True
1989
1990 return False
1991
1992 def __whoIsConnection(self, match):
1993 """
1994 Private method to handle the WHOIS connection reply.
1995
1996 @param match match object that matched the pattern
1997 @return flag indicating whether the message was handled (boolean)
1998 """
1999 # group(1) nick
2000 # group(2) host name
2001 # group(3) IP
2002 if match.group(1) == self.__whoIsNick:
2003 self.__addManagementMessage(
2004 self.tr("Whois"),
2005 self.tr("{0} is connecting from {1} (IP: {2}).").format(
2006 match.group(1), match.group(2), match.group(3)))
2007 return True
2008
2009 return False
2010
2011 def __setEditTopicButton(self):
2012 """
2013 Private method to set the visibility of the Edit Topic button.
2014 """
2015 itm = self.__findUser(self.__userName)
2016 if itm:
2017 self.editTopicButton.setVisible(itm.canChangeTopic())
2018
2019 @pyqtSlot()
2020 def on_editTopicButton_clicked(self):
2021 """
2022 Private slot to change the topic of the channel.
2023 """
2024 topic, ok = QInputDialog.getText(
2025 self,
2026 self.tr("Edit Channel Topic"),
2027 self.tr("Enter the topic for this channel:"),
2028 QLineEdit.Normal,
2029 self.topicLabel.text())
2030 if ok and topic != "":
2031 self.sendData.emit("TOPIC {0} :{1}".format(
2032 self.__name, topic))
2033
2034 @pyqtSlot(QUrl)
2035 def on_messages_anchorClicked(self, url):
2036 """
2037 Private slot to open links in the default browser.
2038
2039 @param url URL to be opened (QUrl)
2040 """
2041 QDesktopServices.openUrl(url)

eric ide

mercurial