WebBrowser/Feeds/FeedsManager.py

branch
QtWebEngine
changeset 4758
c973eef8fef1
parent 4631
5c1a96925da4
child 4761
9d077d20be75
equal deleted inserted replaced
4754:1ff6d0ecb2fd 4758:c973eef8fef1
1 # -*- coding: utf-8 -*-
2
3 # Copyright (c) 2011 - 2016 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 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():
98 icon = UI.PixmapCache.getIcon("rss16.png")
99 feed = (urlString, title, icon)
100 self.__feeds.append(feed)
101 self.__addFeedItem(feed)
102 self.__save()
103
104 return True
105
106 def __addFeedItem(self, feed):
107 """
108 Private slot to add a top level feed item.
109
110 @param feed tuple containing feed info (URL, title, icon)
111 (string, string, QIcon)
112 """
113 itm = QTreeWidgetItem(self.feedsTree, [feed[1]])
114 itm.setIcon(0, feed[2])
115 itm.setData(0, FeedsManager.UrlStringRole, feed[0])
116
117 def __load(self):
118 """
119 Private method to load the feeds data.
120 """
121 self.__feeds = Preferences.getWebBrowser("RssFeeds")
122 self.__loaded = True
123
124 # populate the feeds tree top level with the feeds
125 self.feedsTree.clear()
126 for feed in self.__feeds:
127 self.__addFeedItem(feed)
128
129 def __save(self):
130 """
131 Private method to store the feeds data.
132 """
133 if not self.__loaded:
134 self.__load()
135
136 Preferences.setWebBrowser("RssFeeds", self.__feeds)
137
138 @pyqtSlot()
139 def on_reloadAllButton_clicked(self):
140 """
141 Private slot to reload all feeds.
142 """
143 if not self.__loaded:
144 self.__load()
145
146 for index in range(self.feedsTree.topLevelItemCount()):
147 itm = self.feedsTree.topLevelItem(index)
148 self.__reloadFeed(itm)
149
150 @pyqtSlot()
151 def on_reloadButton_clicked(self):
152 """
153 Private slot to reload the selected feed.
154 """
155 itm = self.feedsTree.selectedItems()[0]
156 self.__reloadFeed(itm)
157
158 @pyqtSlot()
159 def on_editButton_clicked(self):
160 """
161 Private slot to edit the selected feed.
162 """
163 itm = self.feedsTree.selectedItems()[0]
164 origTitle = itm.text(0)
165 origUrlString = itm.data(0, FeedsManager.UrlStringRole)
166
167 feedToChange = None
168 for feed in self.__feeds:
169 if feed[0] == origUrlString:
170 feedToChange = feed
171 break
172 if feedToChange:
173 feedIndex = self.__feeds.index(feedToChange)
174
175 from .FeedEditDialog import FeedEditDialog
176 dlg = FeedEditDialog(origUrlString, origTitle)
177 if dlg.exec_() == QDialog.Accepted:
178 urlString, title = dlg.getData()
179 for feed in self.__feeds:
180 if feed[0] == urlString:
181 E5MessageBox.critical(
182 self,
183 self.tr("Duplicate Feed URL"),
184 self.tr(
185 """A feed with the URL {0} exists already."""
186 """ Aborting...""".format(urlString)))
187 return
188
189 self.__feeds[feedIndex] = (urlString, title, feedToChange[2])
190 self.__save()
191
192 itm.setText(0, title)
193 itm.setData(0, FeedsManager.UrlStringRole, urlString)
194 self.__reloadFeed(itm)
195
196 @pyqtSlot()
197 def on_deleteButton_clicked(self):
198 """
199 Private slot to delete the selected feed.
200 """
201 itm = self.feedsTree.selectedItems()[0]
202 title = itm.text(0)
203 res = E5MessageBox.yesNo(
204 self,
205 self.tr("Delete Feed"),
206 self.tr(
207 """<p>Do you really want to delete the feed"""
208 """ <b>{0}</b>?</p>""".format(title)))
209 if res:
210 urlString = itm.data(0, FeedsManager.UrlStringRole)
211 if urlString:
212 feedToDelete = None
213 for feed in self.__feeds:
214 if feed[0] == urlString:
215 feedToDelete = feed
216 break
217 if feedToDelete:
218 self.__feeds.remove(feedToDelete)
219 self.__save()
220
221 index = self.feedsTree.indexOfTopLevelItem(itm)
222 if index != -1:
223 self.feedsTree.takeTopLevelItem(index)
224 del itm
225
226 @pyqtSlot()
227 def on_feedsTree_itemSelectionChanged(self):
228 """
229 Private slot to enable the various buttons depending on the selection.
230 """
231 self.__enableButtons()
232
233 def __enableButtons(self):
234 """
235 Private slot to disable/enable various buttons.
236 """
237 selItems = self.feedsTree.selectedItems()
238 if len(selItems) == 1 and \
239 self.feedsTree.indexOfTopLevelItem(selItems[0]) != -1:
240 enable = True
241 else:
242 enable = False
243
244 self.reloadButton.setEnabled(enable)
245 self.editButton.setEnabled(enable)
246 self.deleteButton.setEnabled(enable)
247
248 def __reloadFeed(self, itm):
249 """
250 Private method to reload the given feed.
251
252 @param itm feed item to be reloaded (QTreeWidgetItem)
253 """
254 urlString = itm.data(0, FeedsManager.UrlStringRole)
255 if urlString == "":
256 return
257
258 for child in itm.takeChildren():
259 del child
260
261 from WebBrowser.WebBrowserWindow import WebBrowserWindow
262 request = QNetworkRequest(QUrl(urlString))
263 reply = WebBrowserWindow.networkManager().get(request)
264 reply.finished.connect(self.__feedLoaded)
265 self.__replies[id(reply)] = (reply, itm)
266
267 def __feedLoaded(self):
268 """
269 Private slot to extract the loaded feed data.
270 """
271 reply = self.sender()
272 if id(reply) not in self.__replies:
273 return
274
275 topItem = self.__replies[id(reply)][1]
276 del self.__replies[id(reply)]
277
278 if reply.error() == QNetworkReply.NoError:
279 linkString = ""
280 titleString = ""
281
282 xml = QXmlStreamReader()
283 xmlData = reply.readAll()
284 xml.addData(xmlData)
285
286 while not xml.atEnd():
287 xml.readNext()
288 if xml.isStartElement():
289 if xml.name() == "item":
290 linkString = xml.attributes().value("rss:about")
291 elif xml.name() == "link":
292 linkString = xml.attributes().value("href")
293 currentTag = xml.name()
294 elif xml.isEndElement():
295 if xml.name() in ["item", "entry"]:
296 itm = QTreeWidgetItem(topItem)
297 itm.setText(0, titleString)
298 itm.setData(0, FeedsManager.UrlStringRole, linkString)
299 itm.setIcon(0, UI.PixmapCache.getIcon("rss16.png"))
300
301 linkString = ""
302 titleString = ""
303 elif xml.isCharacters() and not xml.isWhitespace():
304 if currentTag == "title":
305 titleString = xml.text()
306 elif currentTag == "link":
307 linkString += xml.text()
308
309 if topItem.childCount() == 0:
310 itm = QTreeWidgetItem(topItem)
311 itm.setText(0, self.tr("Error fetching feed"))
312 itm.setData(0, FeedsManager.UrlStringRole, "")
313 itm.setData(0, FeedsManager.ErrorDataRole,
314 str(xmlData, encoding="utf-8"))
315
316 topItem.setExpanded(True)
317 else:
318 linkString = ""
319 titleString = reply.errorString()
320 itm = QTreeWidgetItem(topItem)
321 itm.setText(0, titleString)
322 itm.setData(0, FeedsManager.UrlStringRole, linkString)
323 topItem.setExpanded(True)
324
325 def __customContextMenuRequested(self, pos):
326 """
327 Private slot to handle the context menu request for the feeds tree.
328
329 @param pos position the context menu was requested (QPoint)
330 """
331 itm = self.feedsTree.currentItem()
332 if itm is None:
333 return
334
335 if self.feedsTree.indexOfTopLevelItem(itm) != -1:
336 return
337
338 urlString = itm.data(0, FeedsManager.UrlStringRole)
339 if urlString:
340 menu = QMenu()
341 menu.addAction(
342 self.tr("&Open"), self.__openMessageInCurrentTab)
343 menu.addAction(
344 self.tr("Open in New &Tab"), self.__openMessageInNewTab)
345 menu.addSeparator()
346 menu.addAction(self.tr("&Copy URL to Clipboard"),
347 self.__copyUrlToClipboard)
348 menu.exec_(QCursor.pos())
349 else:
350 errorString = itm.data(0, FeedsManager.ErrorDataRole)
351 if errorString:
352 menu = QMenu()
353 menu.addAction(
354 self.tr("&Show error data"), self.__showError)
355 menu.exec_(QCursor.pos())
356
357 def __itemActivated(self, itm, column):
358 """
359 Private slot to handle the activation of an item.
360
361 @param itm reference to the activated item (QTreeWidgetItem)
362 @param column column of the activation (integer)
363 """
364 if self.feedsTree.indexOfTopLevelItem(itm) != -1:
365 return
366
367 self.__openMessage(
368 QApplication.keyboardModifiers() &
369 Qt.ControlModifier == Qt.ControlModifier)
370
371 def __openMessageInCurrentTab(self):
372 """
373 Private slot to open a feed message in the current browser tab.
374 """
375 self.__openMessage(False)
376
377 def __openMessageInNewTab(self):
378 """
379 Private slot to open a feed message in a new browser tab.
380 """
381 self.__openMessage(True)
382
383 def __openMessage(self, newTab):
384 """
385 Private method to open a feed message.
386
387 @param newTab flag indicating to open the feed message in a new tab
388 (boolean)
389 """
390 itm = self.feedsTree.currentItem()
391 if itm is None:
392 return
393
394 urlString = itm.data(0, FeedsManager.UrlStringRole)
395 if urlString:
396 title = itm.text(0)
397
398 if newTab:
399 self.newUrl.emit(QUrl(urlString), title)
400 else:
401 self.openUrl.emit(QUrl(urlString), title)
402 else:
403 errorString = itm.data(0, FeedsManager.ErrorDataRole)
404 if errorString:
405 self.__showError()
406
407 def __copyUrlToClipboard(self):
408 """
409 Private slot to copy the URL of the selected item to the clipboard.
410 """
411 itm = self.feedsTree.currentItem()
412 if itm is None:
413 return
414
415 if self.feedsTree.indexOfTopLevelItem(itm) != -1:
416 return
417
418 urlString = itm.data(0, FeedsManager.UrlStringRole)
419 if urlString:
420 QApplication.clipboard().setText(urlString)
421
422 def __showError(self):
423 """
424 Private slot to show error info for a failed load operation.
425 """
426 itm = self.feedsTree.currentItem()
427 if itm is None:
428 return
429
430 errorStr = itm.data(0, FeedsManager.ErrorDataRole)
431 if errorStr:
432 E5MessageBox.critical(
433 self,
434 self.tr("Error loading feed"),
435 "{0}".format(errorStr))

eric ide

mercurial