ExtensionIrc/IrcChannelWidget.py

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

eric ide

mercurial