ExtensionIrc/IrcNetworkWidget.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 network part of the IRC widget.
8 """
9
10 import pathlib
11
12 from PyQt6.QtCore import QPoint, QThread, QUrl, pyqtSignal, pyqtSlot
13 from PyQt6.QtGui import QDesktopServices
14 from PyQt6.QtWidgets import QApplication, QMenu, QWidget
15
16 from eric7.EricGui import EricPixmapCache
17 from eric7.EricWidgets import EricFileDialog, EricMessageBox
18 from eric7.EricWidgets.EricApplication import ericApp
19 from eric7.SystemUtilities import OSUtilities
20 from PluginExtensionIrc import ircExtensionPluginObject
21
22 from .IrcUtilities import ircFilter, ircTimestamp
23 from .Ui_IrcNetworkWidget import Ui_IrcNetworkWidget
24
25
26 class IrcNetworkWidget(QWidget, Ui_IrcNetworkWidget):
27 """
28 Class implementing the network part of the IRC widget.
29
30 @signal connectNetwork(str,bool,bool) emitted to connect or disconnect from
31 a network
32 @signal editNetwork(str) emitted to edit a network configuration
33 @signal joinChannel(str) emitted to join a channel
34 @signal nickChanged(str) emitted to change the nick name
35 @signal sendData(str) emitted to send a message to the channel
36 @signal away(bool) emitted to indicate the away status
37 @signal autoConnected() emitted after an automatic connection was initiated
38 """
39
40 connectNetwork = pyqtSignal(str, bool, bool)
41 editNetwork = pyqtSignal(str)
42 joinChannel = pyqtSignal(str)
43 nickChanged = pyqtSignal(str)
44 sendData = pyqtSignal(str)
45 away = pyqtSignal(bool)
46 autoConnected = pyqtSignal()
47
48 def __init__(self, parent=None):
49 """
50 Constructor
51
52 @param parent reference to the parent widget
53 @type QWidget
54 """
55 super().__init__(parent)
56 self.setupUi(self)
57
58 self.joinButton.setEnabled(False)
59 self.nickCombo.setEnabled(False)
60 self.awayButton.setEnabled(False)
61
62 self.channelCombo.lineEdit().returnPressed.connect(self.on_joinButton_clicked)
63 self.nickCombo.lineEdit().returnPressed.connect(
64 self.on_nickCombo_currentIndexChanged
65 )
66
67 self.__manager = None
68 self.__connected = False
69 self.__registered = False
70 self.__away = False
71
72 self.setConnected(False)
73
74 self.__initMessagesMenu()
75
76 self.connectButton.setIcon(ircExtensionPluginObject.getIcon("ircConnect"))
77 self.editButton.setIcon(ircExtensionPluginObject.getIcon("ircConfigure"))
78 self.joinButton.setIcon(ircExtensionPluginObject.getIcon("ircJoinChannel"))
79 self.awayButton.setIcon(ircExtensionPluginObject.getIcon("ircUserPresent"))
80
81 def initialize(self, manager):
82 """
83 Public method to initialize the widget.
84
85 @param manager reference to the network manager
86 @type IrcNetworkManager
87 """
88 self.__manager = manager
89
90 self.networkCombo.addItems(self.__manager.getNetworkNames())
91
92 self.__manager.networksChanged.connect(self.__refreshNetworks)
93 self.__manager.identitiesChanged.connect(self.__refreshNetworks)
94
95 def autoConnect(self):
96 """
97 Public method to perform the IRC auto connection.
98 """
99 userInterface = ericApp().getObject("UserInterface")
100 online = userInterface.isOnline()
101 self.connectButton.setEnabled(online)
102 userInterface.onlineStateChanged.connect(self.__onlineStateChanged)
103 if online:
104 self.__autoConnect()
105
106 def __autoConnect(self):
107 """
108 Private method to perform the IRC auto connection.
109 """
110 for networkName in self.__manager.getNetworkNames():
111 if self.__manager.getNetwork(networkName).autoConnect():
112 row = self.networkCombo.findText(networkName)
113 self.networkCombo.setCurrentIndex(row)
114 self.on_connectButton_clicked()
115 self.autoConnected.emit()
116 break
117
118 @pyqtSlot(bool)
119 def __onlineStateChanged(self, online):
120 """
121 Private slot handling online state changes.
122
123 @param online online state
124 @type bool
125 """
126 self.connectButton.setEnabled(online)
127 if online:
128 # delay a bit because the signal seems to be sent before the
129 # network interface is fully up
130 QThread.msleep(200)
131 self.__autoConnect()
132 else:
133 network = self.networkCombo.currentText()
134 self.connectNetwork.emit(network, online, True)
135
136 @pyqtSlot()
137 def __refreshNetworks(self):
138 """
139 Private slot to refresh all network related widgets.
140 """
141 currentNetwork = self.networkCombo.currentText()
142 currentNick = self.nickCombo.currentText()
143 currentChannel = self.channelCombo.currentText()
144 blocked = self.networkCombo.blockSignals(True)
145 self.networkCombo.clear()
146 self.networkCombo.addItems(self.__manager.getNetworkNames())
147 self.networkCombo.blockSignals(blocked)
148 row = self.networkCombo.findText(currentNetwork)
149 if row == -1:
150 row = 0
151 blocked = self.nickCombo.blockSignals(True)
152 self.networkCombo.setCurrentIndex(row)
153 self.nickCombo.setEditText(currentNick)
154 self.nickCombo.blockSignals(blocked)
155 self.channelCombo.setEditText(currentChannel)
156
157 @pyqtSlot()
158 def on_connectButton_clicked(self):
159 """
160 Private slot to connect to a network.
161 """
162 network = self.networkCombo.currentText()
163 self.connectNetwork.emit(network, not self.__connected, False)
164
165 @pyqtSlot()
166 def on_awayButton_clicked(self):
167 """
168 Private slot to toggle the away status.
169 """
170 if self.__away:
171 self.handleAwayCommand("")
172 else:
173 networkName = self.networkCombo.currentText()
174 identityName = self.__manager.getNetwork(networkName).getIdentityName()
175 identity = self.__manager.getIdentity(identityName)
176 if identity:
177 awayMessage = identity.getAwayMessage()
178 else:
179 awayMessage = ""
180 self.handleAwayCommand(awayMessage)
181
182 @pyqtSlot(str)
183 def handleAwayCommand(self, awayMessage):
184 """
185 Public slot to process an away command.
186
187 @param awayMessage message to be set for being away
188 @type str
189 """
190 if awayMessage and not self.__away:
191 # set being away
192 # don't send away, if the status is already set
193 self.sendData.emit("AWAY :" + awayMessage)
194 self.awayButton.setIcon(ircExtensionPluginObject.getIcon("ircUserAway"))
195 self.__away = True
196 self.away.emit(self.__away)
197 elif not awayMessage and self.__away:
198 # cancel being away
199 self.sendData.emit("AWAY")
200 self.awayButton.setIcon(ircExtensionPluginObject.getIcon("ircUserPresent"))
201 self.__away = False
202 self.away.emit(self.__away)
203
204 @pyqtSlot()
205 def on_editButton_clicked(self):
206 """
207 Private slot to edit a network.
208 """
209 network = self.networkCombo.currentText()
210 self.editNetwork.emit(network)
211
212 @pyqtSlot(str)
213 def on_channelCombo_editTextChanged(self, txt):
214 """
215 Private slot to react upon changes of the channel.
216
217 @param txt current text of the channel combo
218 @type str
219 """
220 on = bool(txt) and self.__registered
221 self.joinButton.setEnabled(on)
222
223 @pyqtSlot()
224 def on_joinButton_clicked(self):
225 """
226 Private slot to join a channel.
227 """
228 channel = self.channelCombo.currentText()
229 self.joinChannel.emit(channel)
230
231 @pyqtSlot(int)
232 def on_networkCombo_currentIndexChanged(self, index):
233 """
234 Private slot to handle selections of a network.
235
236 @param index index of the selected entry
237 @type int
238 """
239 networkName = self.networkCombo.itemText(index)
240 network = self.__manager.getNetwork(networkName)
241 self.nickCombo.clear()
242 self.channelCombo.clear()
243 if network:
244 channels = network.getChannelNames()
245 self.channelCombo.addItems(channels)
246 self.channelCombo.setEnabled(True)
247 identity = self.__manager.getIdentity(network.getIdentityName())
248 if identity:
249 self.nickCombo.addItems(identity.getNickNames())
250 else:
251 self.channelCombo.setEnabled(False)
252
253 def getNetworkChannels(self):
254 """
255 Public method to get the list of channels associated with the
256 selected network.
257
258 @return associated channels
259 @rtype list of IrcChannel
260 """
261 networkName = self.networkCombo.currentText()
262 network = self.__manager.getNetwork(networkName)
263 return network.getChannels()
264
265 @pyqtSlot(int)
266 @pyqtSlot()
267 def on_nickCombo_currentIndexChanged(self, nick=0):
268 """
269 Private slot to use another nick name.
270
271 @param nick index of the selected nick name (unused)
272 @type int
273 """
274 if self.__connected:
275 self.nickChanged.emit(self.nickCombo.currentText())
276
277 def getNickname(self):
278 """
279 Public method to get the currently selected nick name.
280
281 @return selected nick name
282 @rtype str
283 """
284 return self.nickCombo.currentText()
285
286 def setNickName(self, nick):
287 """
288 Public slot to set the nick name in use.
289
290 @param nick nick name in use
291 @type str
292 """
293 self.nickCombo.blockSignals(True)
294 self.nickCombo.setEditText(nick)
295 self.nickCombo.blockSignals(False)
296
297 def addMessage(self, msg):
298 """
299 Public method to add a message.
300
301 @param msg message to be added
302 @type str
303 """
304 s = '<font color="{0}">{1} {2}</font>'.format(
305 ircExtensionPluginObject.getPreferences("NetworkMessageColour"),
306 ircTimestamp(),
307 msg,
308 )
309 self.messages.append(s)
310
311 def addServerMessage(self, msgType, msg, filterMsg=True):
312 """
313 Public method to add a server message.
314
315 @param msgType txpe of the message
316 @type str
317 @param msg message to be added
318 @type str
319 @param filterMsg flag indicating to filter the message
320 @type bool
321 """
322 if filterMsg:
323 msg = ircFilter(msg)
324 s = '<font color="{0}">{1} <b>[</b>{2}<b>]</b> {3}</font>'.format(
325 ircExtensionPluginObject.getPreferences("ServerMessageColour"),
326 ircTimestamp(),
327 msgType,
328 msg,
329 )
330 self.messages.append(s)
331
332 def addErrorMessage(self, msgType, msg):
333 """
334 Public method to add an error message.
335
336 @param msgType txpe of the message
337 @type str
338 @param msg message to be added
339 @type str
340 """
341 s = '<font color="{0}">{1} <b>[</b>{2}<b>]</b> {3}</font>'.format(
342 ircExtensionPluginObject.getPreferences("ErrorMessageColour"),
343 ircTimestamp(),
344 msgType,
345 msg,
346 )
347 self.messages.append(s)
348
349 def setConnected(self, connected):
350 """
351 Public slot to set the connection state.
352
353 @param connected flag indicating the connection state
354 @type bool
355 """
356 self.__connected = connected
357 if self.__connected:
358 self.connectButton.setIcon(
359 ircExtensionPluginObject.getIcon("ircDisconnect")
360 )
361 self.connectButton.setToolTip(
362 self.tr("Press to disconnect from the network")
363 )
364 else:
365 self.connectButton.setIcon(ircExtensionPluginObject.getIcon("ircConnect"))
366 self.connectButton.setToolTip(
367 self.tr("Press to connect to the selected network")
368 )
369
370 def isConnected(self):
371 """
372 Public method to check, if the network is connected.
373
374 @return flag indicating a connected network
375 @rtype bool
376 """
377 return self.__connected
378
379 def setRegistered(self, registered):
380 """
381 Public slot to set the registered state.
382
383 @param registered flag indicating the registration state
384 @type bool
385 """
386 self.__registered = registered
387 on = bool(self.channelCombo.currentText()) and self.__registered
388 self.joinButton.setEnabled(on)
389 self.nickCombo.setEnabled(registered)
390 self.awayButton.setEnabled(registered)
391 if registered:
392 self.awayButton.setIcon(ircExtensionPluginObject.getIcon("ircUserPresent"))
393 self.__away = False
394
395 def __clearMessages(self):
396 """
397 Private slot to clear the contents of the messages display.
398 """
399 self.messages.clear()
400
401 def __copyMessages(self):
402 """
403 Private slot to copy the selection of the messages display to
404 the clipboard.
405 """
406 self.messages.copy()
407
408 def __copyAllMessages(self):
409 """
410 Private slot to copy the contents of the messages display to
411 the clipboard.
412 """
413 txt = self.messages.toPlainText()
414 if txt:
415 cb = QApplication.clipboard()
416 cb.setText(txt)
417
418 def __cutAllMessages(self):
419 """
420 Private slot to cut the contents of the messages display to
421 the clipboard.
422 """
423 txt = self.messages.toPlainText()
424 if txt:
425 cb = QApplication.clipboard()
426 cb.setText(txt)
427 self.messages.clear()
428
429 def __saveMessages(self):
430 """
431 Private slot to save the contents of the messages display.
432 """
433 hasText = not self.messages.document().isEmpty()
434 if hasText:
435 if OSUtilities.isWindowsPlatform():
436 htmlExtension = "htm"
437 else:
438 htmlExtension = "html"
439 fname, selectedFilter = EricFileDialog.getSaveFileNameAndFilter(
440 self,
441 self.tr("Save Messages"),
442 "",
443 self.tr("HTML Files (*.{0});;Text Files (*.txt);;All Files (*)").format(
444 htmlExtension
445 ),
446 None,
447 EricFileDialog.DontConfirmOverwrite,
448 )
449 if fname:
450 fpath = pathlib.Path(fname)
451 if not fpath.suffix:
452 ex = selectedFilter.split("(*")[1].split(")")[0]
453 if ex:
454 fpath = fpath.with_suffix(ex)
455 if fpath.exists():
456 res = EricMessageBox.yesNo(
457 self,
458 self.tr("Save Messages"),
459 self.tr(
460 "<p>The file <b>{0}</b> already exists."
461 " Overwrite it?</p>"
462 ).format(fpath),
463 icon=EricMessageBox.Warning,
464 )
465 if not res:
466 return
467
468 try:
469 txt = (
470 self.messages.toHtml()
471 if fpath.suffix.lower() in [".htm", ".html"]
472 else self.messages.toPlainText()
473 )
474 with fpath.open("w", encoding="utf-8") as f:
475 f.write(txt)
476 except OSError as err:
477 EricMessageBox.critical(
478 self,
479 self.tr("Error saving Messages"),
480 self.tr(
481 """<p>The messages contents could not be written"""
482 """ to <b>{0}</b></p><p>Reason: {1}</p>"""
483 ).format(fpath, str(err)),
484 )
485
486 def __initMessagesMenu(self):
487 """
488 Private slot to initialize the context menu of the messages pane.
489 """
490 self.__messagesMenu = QMenu(self)
491 self.__copyMessagesAct = self.__messagesMenu.addAction(
492 EricPixmapCache.getIcon("editCopy"), self.tr("Copy"), self.__copyMessages
493 )
494 self.__messagesMenu.addSeparator()
495 self.__cutAllMessagesAct = self.__messagesMenu.addAction(
496 EricPixmapCache.getIcon("editCut"),
497 self.tr("Cut all"),
498 self.__cutAllMessages,
499 )
500 self.__copyAllMessagesAct = self.__messagesMenu.addAction(
501 EricPixmapCache.getIcon("editCopy"),
502 self.tr("Copy all"),
503 self.__copyAllMessages,
504 )
505 self.__messagesMenu.addSeparator()
506 self.__clearMessagesAct = self.__messagesMenu.addAction(
507 EricPixmapCache.getIcon("editDelete"),
508 self.tr("Clear"),
509 self.__clearMessages,
510 )
511 self.__messagesMenu.addSeparator()
512 self.__saveMessagesAct = self.__messagesMenu.addAction(
513 EricPixmapCache.getIcon("fileSave"), self.tr("Save"), self.__saveMessages
514 )
515
516 self.on_messages_copyAvailable(False)
517
518 @pyqtSlot(bool)
519 def on_messages_copyAvailable(self, yes):
520 """
521 Private slot to react to text selection/deselection of the
522 messages edit.
523
524 @param yes flag signaling the availability of selected text
525 @type bool
526 """
527 self.__copyMessagesAct.setEnabled(yes)
528
529 @pyqtSlot(QPoint)
530 def on_messages_customContextMenuRequested(self, pos):
531 """
532 Private slot to show the context menu of the messages pane.
533
534 @param pos position the menu should be opened at
535 @type QPoint
536 """
537 enable = not self.messages.document().isEmpty()
538 self.__cutAllMessagesAct.setEnabled(enable)
539 self.__copyAllMessagesAct.setEnabled(enable)
540 self.__saveMessagesAct.setEnabled(enable)
541 self.__messagesMenu.popup(self.messages.mapToGlobal(pos))
542
543 @pyqtSlot(QUrl)
544 def on_messages_anchorClicked(self, url):
545 """
546 Private slot to open links in the default browser.
547
548 @param url URL to be opened
549 @type QUrl
550 """
551 QDesktopServices.openUrl(url)

eric ide

mercurial