src/eric7/Network/IRC/IrcNetworkWidget.py

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

eric ide

mercurial