eric6/WebBrowser/Feeds/FeedsManager.py

changeset 6942
2602857055c5
parent 6645
ad476851d7e0
child 7192
a22eee00b052
equal deleted inserted replaced
6941:f99d60d6b59b 6942:2602857055c5
1 # -*- coding: utf-8 -*-
2
3 # Copyright (c) 2011 - 2019 Detlev Offenbach <detlev@die-offenbachs.de>
4 #
5
6 """
7 Module implementing a RSS feeds manager dialog.
8 """
9
10 from __future__ import unicode_literals
11 try:
12 str = unicode # __IGNORE_EXCEPTION__
13 except NameError:
14 pass
15
16 from PyQt5.QtCore import pyqtSlot, pyqtSignal, Qt, QUrl, QXmlStreamReader
17 from PyQt5.QtGui import QCursor
18 from PyQt5.QtWidgets import QDialog, QTreeWidgetItem, QMenu, QApplication
19 from PyQt5.QtNetwork import QNetworkRequest, QNetworkReply
20
21 from E5Gui import E5MessageBox
22
23 from .Ui_FeedsManager import Ui_FeedsManager
24
25 import Preferences
26 import UI.PixmapCache
27
28
29 class FeedsManager(QDialog, Ui_FeedsManager):
30 """
31 Class implementing a RSS feeds manager dialog.
32
33 @signal openUrl(QUrl, str) emitted to open a URL in the current tab
34 @signal newTab(QUrl, str) emitted to open a URL in a new tab
35 @signal newBackgroundTab(QUrl, str) emitted to open a URL in a new
36 background tab
37 @signal newWindow(QUrl, str) emitted to open a URL in a new window
38 @signal newPrivateWindow(QUrl, str) emitted to open a URL in a new
39 private window
40 """
41 openUrl = pyqtSignal(QUrl, str)
42 newTab = pyqtSignal(QUrl, str)
43 newBackgroundTab = pyqtSignal(QUrl, str)
44 newWindow = pyqtSignal(QUrl, str)
45 newPrivateWindow = pyqtSignal(QUrl, str)
46
47 UrlStringRole = Qt.UserRole
48 ErrorDataRole = Qt.UserRole + 1
49
50 def __init__(self, parent=None):
51 """
52 Constructor
53
54 @param parent reference to the parent widget (QWidget)
55 """
56 super(FeedsManager, self).__init__(parent)
57 self.setupUi(self)
58 self.setWindowFlags(Qt.Window)
59
60 self.__wasShown = False
61 self.__loaded = False
62 self.__feeds = []
63 self.__replies = {}
64 # dict key is the id of the request object
65 # dict value is a tuple of request and tree item
66
67 self.feedsTree.setContextMenuPolicy(Qt.CustomContextMenu)
68 self.feedsTree.customContextMenuRequested.connect(
69 self.__customContextMenuRequested)
70 self.feedsTree.itemActivated.connect(self.__itemActivated)
71
72 def show(self):
73 """
74 Public slot to show the feeds manager dialog.
75 """
76 super(FeedsManager, self).show()
77
78 if not self.__wasShown:
79 self.__enableButtons()
80 self.on_reloadAllButton_clicked()
81 self.__wasShown = True
82
83 def addFeed(self, urlString, title, icon):
84 """
85 Public method to add a feed.
86
87 @param urlString URL of the feed (string)
88 @param title title of the feed (string)
89 @param icon icon for the feed (QIcon)
90 @return flag indicating a successful addition of the feed (boolean)
91 """
92 if urlString == "":
93 return False
94
95 if not self.__loaded:
96 self.__load()
97
98 # step 1: check, if feed was already added
99 for feed in self.__feeds:
100 if feed[0] == urlString:
101 return False
102
103 # step 2: add the feed
104 if icon.isNull():
105 icon = UI.PixmapCache.getIcon("rss16.png")
106 feed = (urlString, title, icon)
107 self.__feeds.append(feed)
108 self.__addFeedItem(feed)
109 self.__save()
110
111 return True
112
113 def __addFeedItem(self, feed):
114 """
115 Private slot to add a top level feed item.
116
117 @param feed tuple containing feed info (URL, title, icon)
118 (string, string, QIcon)
119 """
120 itm = QTreeWidgetItem(self.feedsTree, [feed[1]])
121 itm.setIcon(0, feed[2])
122 itm.setData(0, FeedsManager.UrlStringRole, feed[0])
123
124 def __load(self):
125 """
126 Private method to load the feeds data.
127 """
128 self.__feeds = Preferences.getWebBrowser("RssFeeds")
129 self.__loaded = True
130
131 # populate the feeds tree top level with the feeds
132 self.feedsTree.clear()
133 for feed in self.__feeds:
134 self.__addFeedItem(feed)
135
136 def __save(self):
137 """
138 Private method to store the feeds data.
139 """
140 if not self.__loaded:
141 self.__load()
142
143 Preferences.setWebBrowser("RssFeeds", self.__feeds)
144
145 @pyqtSlot()
146 def on_reloadAllButton_clicked(self):
147 """
148 Private slot to reload all feeds.
149 """
150 if not self.__loaded:
151 self.__load()
152
153 for index in range(self.feedsTree.topLevelItemCount()):
154 itm = self.feedsTree.topLevelItem(index)
155 self.__reloadFeed(itm)
156
157 @pyqtSlot()
158 def on_reloadButton_clicked(self):
159 """
160 Private slot to reload the selected feed.
161 """
162 itm = self.feedsTree.selectedItems()[0]
163 self.__reloadFeed(itm)
164
165 @pyqtSlot()
166 def on_editButton_clicked(self):
167 """
168 Private slot to edit the selected feed.
169 """
170 itm = self.feedsTree.selectedItems()[0]
171 origTitle = itm.text(0)
172 origUrlString = itm.data(0, FeedsManager.UrlStringRole)
173
174 feedToChange = None
175 for feed in self.__feeds:
176 if feed[0] == origUrlString:
177 feedToChange = feed
178 break
179 if feedToChange:
180 feedIndex = self.__feeds.index(feedToChange)
181
182 from .FeedEditDialog import FeedEditDialog
183 dlg = FeedEditDialog(origUrlString, origTitle)
184 if dlg.exec_() == QDialog.Accepted:
185 urlString, title = dlg.getData()
186 for feed in self.__feeds:
187 if feed[0] == urlString:
188 E5MessageBox.critical(
189 self,
190 self.tr("Duplicate Feed URL"),
191 self.tr(
192 """A feed with the URL {0} exists already."""
193 """ Aborting...""".format(urlString)))
194 return
195
196 self.__feeds[feedIndex] = (urlString, title, feedToChange[2])
197 self.__save()
198
199 itm.setText(0, title)
200 itm.setData(0, FeedsManager.UrlStringRole, urlString)
201 self.__reloadFeed(itm)
202
203 @pyqtSlot()
204 def on_deleteButton_clicked(self):
205 """
206 Private slot to delete the selected feed.
207 """
208 itm = self.feedsTree.selectedItems()[0]
209 title = itm.text(0)
210 res = E5MessageBox.yesNo(
211 self,
212 self.tr("Delete Feed"),
213 self.tr(
214 """<p>Do you really want to delete the feed"""
215 """ <b>{0}</b>?</p>""".format(title)))
216 if res:
217 urlString = itm.data(0, FeedsManager.UrlStringRole)
218 if urlString:
219 feedToDelete = None
220 for feed in self.__feeds:
221 if feed[0] == urlString:
222 feedToDelete = feed
223 break
224 if feedToDelete:
225 self.__feeds.remove(feedToDelete)
226 self.__save()
227
228 index = self.feedsTree.indexOfTopLevelItem(itm)
229 if index != -1:
230 self.feedsTree.takeTopLevelItem(index)
231 del itm
232
233 @pyqtSlot()
234 def on_feedsTree_itemSelectionChanged(self):
235 """
236 Private slot to enable the various buttons depending on the selection.
237 """
238 self.__enableButtons()
239
240 def __enableButtons(self):
241 """
242 Private slot to disable/enable various buttons.
243 """
244 selItems = self.feedsTree.selectedItems()
245 if len(selItems) == 1 and \
246 self.feedsTree.indexOfTopLevelItem(selItems[0]) != -1:
247 enable = True
248 else:
249 enable = False
250
251 self.reloadButton.setEnabled(enable)
252 self.editButton.setEnabled(enable)
253 self.deleteButton.setEnabled(enable)
254
255 def __reloadFeed(self, itm):
256 """
257 Private method to reload the given feed.
258
259 @param itm feed item to be reloaded (QTreeWidgetItem)
260 """
261 urlString = itm.data(0, FeedsManager.UrlStringRole)
262 if urlString == "":
263 return
264
265 for child in itm.takeChildren():
266 del child
267
268 from WebBrowser.WebBrowserWindow import WebBrowserWindow
269 request = QNetworkRequest(QUrl(urlString))
270 reply = WebBrowserWindow.networkManager().get(request)
271 reply.finished.connect(lambda: self.__feedLoaded(reply))
272 self.__replies[id(reply)] = (reply, itm)
273
274 def __feedLoaded(self, reply):
275 """
276 Private slot to extract the loaded feed data.
277
278 @param reply reference to the network reply
279 @type QNetworkReply
280 """
281 if id(reply) not in self.__replies:
282 return
283
284 topItem = self.__replies[id(reply)][1]
285 del self.__replies[id(reply)]
286
287 if reply.error() == QNetworkReply.NoError:
288 linkString = ""
289 titleString = ""
290
291 xml = QXmlStreamReader()
292 xmlData = reply.readAll()
293 xml.addData(xmlData)
294
295 while not xml.atEnd():
296 xml.readNext()
297 if xml.isStartElement():
298 if xml.name() == "item":
299 linkString = xml.attributes().value("rss:about")
300 elif xml.name() == "link":
301 linkString = xml.attributes().value("href")
302 currentTag = xml.name()
303 elif xml.isEndElement():
304 if xml.name() in ["item", "entry"]:
305 itm = QTreeWidgetItem(topItem)
306 itm.setText(0, titleString)
307 itm.setData(0, FeedsManager.UrlStringRole, linkString)
308 itm.setIcon(0, UI.PixmapCache.getIcon("rss16.png"))
309
310 linkString = ""
311 titleString = ""
312 elif xml.isCharacters() and not xml.isWhitespace():
313 if currentTag == "title":
314 titleString = xml.text()
315 elif currentTag == "link":
316 linkString += xml.text()
317
318 if topItem.childCount() == 0:
319 itm = QTreeWidgetItem(topItem)
320 itm.setText(0, self.tr("Error fetching feed"))
321 itm.setData(0, FeedsManager.UrlStringRole, "")
322 itm.setData(0, FeedsManager.ErrorDataRole,
323 str(xmlData, encoding="utf-8"))
324
325 topItem.setExpanded(True)
326 else:
327 linkString = ""
328 titleString = reply.errorString()
329 itm = QTreeWidgetItem(topItem)
330 itm.setText(0, titleString)
331 itm.setData(0, FeedsManager.UrlStringRole, linkString)
332 topItem.setExpanded(True)
333
334 def __customContextMenuRequested(self, pos):
335 """
336 Private slot to handle the context menu request for the feeds tree.
337
338 @param pos position the context menu was requested (QPoint)
339 """
340 itm = self.feedsTree.currentItem()
341 if itm is None:
342 return
343
344 if self.feedsTree.indexOfTopLevelItem(itm) != -1:
345 return
346
347 urlString = itm.data(0, FeedsManager.UrlStringRole)
348 if urlString:
349 menu = QMenu()
350 menu.addAction(
351 self.tr("&Open"), self.__openMessageInCurrentTab)
352 menu.addAction(
353 self.tr("Open in New &Tab"), self.__openMessageInNewTab)
354 menu.addAction(
355 self.tr("Open in New &Background Tab"),
356 self.__openMessageInNewBackgroundTab)
357 menu.addAction(
358 self.tr("Open in New &Window"), self.__openMessageInNewWindow)
359 menu.addAction(
360 self.tr("Open in New Pri&vate Window"),
361 self.__openMessageInPrivateWindow)
362 menu.addSeparator()
363 menu.addAction(self.tr("&Copy URL to Clipboard"),
364 self.__copyUrlToClipboard)
365 menu.exec_(QCursor.pos())
366 else:
367 errorString = itm.data(0, FeedsManager.ErrorDataRole)
368 if errorString:
369 menu = QMenu()
370 menu.addAction(
371 self.tr("&Show error data"), self.__showError)
372 menu.exec_(QCursor.pos())
373
374 def __itemActivated(self, itm, column):
375 """
376 Private slot to handle the activation of an item.
377
378 @param itm reference to the activated item (QTreeWidgetItem)
379 @param column column of the activation (integer)
380 """
381 if self.feedsTree.indexOfTopLevelItem(itm) != -1:
382 return
383
384 if QApplication.keyboardModifiers() & Qt.ControlModifier:
385 self.__openMessageInNewTab()
386 elif QApplication.keyboardModifiers() & Qt.ShiftModifier:
387 self.__openMessageInNewWindow()
388 else:
389 self.__openMessageInCurrentTab()
390
391 def __openMessageInCurrentTab(self):
392 """
393 Private slot to open a feed message in the current browser tab.
394 """
395 self.__openMessage()
396
397 def __openMessageInNewTab(self):
398 """
399 Private slot to open a feed message in a new browser tab.
400 """
401 self.__openMessage(newTab=True)
402
403 def __openMessageInNewBackgroundTab(self):
404 """
405 Private slot to open a feed message in a new background tab.
406 """
407 self.__openMessage(newTab=True, background=True)
408
409 def __openMessageInNewWindow(self):
410 """
411 Private slot to open a feed message in a new browser window.
412 """
413 self.__openMessage(newWindow=True)
414
415 def __openMessageInPrivateWindow(self):
416 """
417 Private slot to open a feed message in a new private browser window.
418 """
419 self.__openMessage(newWindow=True, privateWindow=True)
420
421 def __openMessage(self, newTab=False, background=False,
422 newWindow=False, privateWindow=False):
423 """
424 Private method to open a feed message.
425
426 @param newTab flag indicating to open the feed message in a new tab
427 @type bool
428 @param background flag indicating to open the bookmark in a new
429 background tab
430 @type bool
431 @param newWindow flag indicating to open the bookmark in a new window
432 @type bool
433 @param privateWindow flag indicating to open the bookmark in a new
434 private window
435 @type bool
436 """
437 itm = self.feedsTree.currentItem()
438 if itm is None:
439 return
440
441 urlString = itm.data(0, FeedsManager.UrlStringRole)
442 if urlString:
443 title = itm.text(0)
444
445 if newTab:
446 if background:
447 self.newBackgroundTab.emit(QUrl(urlString), title)
448 else:
449 self.newTab.emit(QUrl(urlString), title)
450 elif newWindow:
451 if privateWindow:
452 self.newPrivateWindow.emit(QUrl(urlString), title)
453 else:
454 self.newWindow.emit(QUrl(urlString), title)
455 else:
456 self.openUrl.emit(QUrl(urlString), title)
457 else:
458 errorString = itm.data(0, FeedsManager.ErrorDataRole)
459 if errorString:
460 self.__showError()
461
462 def __copyUrlToClipboard(self):
463 """
464 Private slot to copy the URL of the selected item to the clipboard.
465 """
466 itm = self.feedsTree.currentItem()
467 if itm is None:
468 return
469
470 if self.feedsTree.indexOfTopLevelItem(itm) != -1:
471 return
472
473 urlString = itm.data(0, FeedsManager.UrlStringRole)
474 if urlString:
475 QApplication.clipboard().setText(urlString)
476
477 def __showError(self):
478 """
479 Private slot to show error info for a failed load operation.
480 """
481 itm = self.feedsTree.currentItem()
482 if itm is None:
483 return
484
485 errorStr = itm.data(0, FeedsManager.ErrorDataRole)
486 if errorStr:
487 E5MessageBox.critical(
488 self,
489 self.tr("Error loading feed"),
490 "{0}".format(errorStr))

eric ide

mercurial