Helpviewer/Feeds/FeedsManager.py

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

eric ide

mercurial