eric6/Helpviewer/Feeds/FeedsManager.py

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

eric ide

mercurial