Sat, 20 Feb 2016 17:31:34 +0100
Continued porting the web browser.
- added the RSS Feeds manager
--- a/Preferences/__init__.py Sat Feb 20 15:25:51 2016 +0100 +++ b/Preferences/__init__.py Sat Feb 20 17:31:34 2016 +0100 @@ -1025,6 +1025,7 @@ "WebSearchKeywords": [], # array of two tuples (keyword, # search engine name) "SearchLanguage": QLocale().language(), + "RssFeeds": [], # Flash Cookie Manager: identical to helpDefaults # PIM: identical to helpDefaults # VirusTotal: identical to helpDefaults @@ -1054,7 +1055,7 @@ cls.webBrowserDefaults.update({ "AutoLoadImages": webEngineSettings.testAttribute( QWebEngineSettings.AutoLoadImages), - "SaveUrlColor": QColor(248, 248, 210), + "SaveUrlColor": QColor(184, 248, 169), ## "JavaEnabled": ## websettings.testAttribute(QWebSettings.JavaEnabled), "JavaScriptEnabled": webEngineSettings.testAttribute( @@ -2695,18 +2696,18 @@ ## downloads.append((url, location, done, pageUrl)) ## prefClass.settings.endArray() ## return downloads -## elif key == "RssFeeds": -## # return a list of tuples of (URL, title, icon) -## feeds = [] -## length = prefClass.settings.beginReadArray("WebBrowser/" + key) -## for index in range(length): -## prefClass.settings.setArrayIndex(index) -## url = prefClass.settings.value("URL") -## title = prefClass.settings.value("Title") -## icon = prefClass.settings.value("Icon") -## feeds.append((url, title, icon)) -## prefClass.settings.endArray() -## return feeds + elif key == "RssFeeds": + # return a list of tuples of (URL, title, icon) + feeds = [] + length = prefClass.settings.beginReadArray("WebBrowser/" + key) + for index in range(length): + prefClass.settings.setArrayIndex(index) + url = prefClass.settings.value("URL") + title = prefClass.settings.value("Title") + icon = prefClass.settings.value("Icon") + feeds.append((url, title, icon)) + prefClass.settings.endArray() + return feeds ## elif key in ["SyncFtpPassword", "SyncEncryptionKey"]: ## from Utilities.crypto import pwConvert ## return pwConvert(prefClass.settings.value( @@ -2815,18 +2816,18 @@ ## prefClass.settings.setValue("PageURL", v[3]) ## index += 1 ## prefClass.settings.endArray() -## elif key == "RssFeeds": -## # value is list of tuples of (URL, title, icon) -## prefClass.settings.remove("WebBrowser/" + key) -## prefClass.settings.beginWriteArray("Help/" + key, len(value)) -## index = 0 -## for v in value: -## prefClass.settings.setArrayIndex(index) -## prefClass.settings.setValue("URL", v[0]) -## prefClass.settings.setValue("Title", v[1]) -## prefClass.settings.setValue("Icon", v[2]) -## index += 1 -## prefClass.settings.endArray() + elif key == "RssFeeds": + # value is list of tuples of (URL, title, icon) + prefClass.settings.remove("WebBrowser/" + key) + prefClass.settings.beginWriteArray("Help/" + key, len(value)) + index = 0 + for v in value: + prefClass.settings.setArrayIndex(index) + prefClass.settings.setValue("URL", v[0]) + prefClass.settings.setValue("Title", v[1]) + prefClass.settings.setValue("Icon", v[2]) + index += 1 + prefClass.settings.endArray() ## elif key in ["SyncFtpPassword", "SyncEncryptionKey"]: ## from Utilities.crypto import pwConvert ## prefClass.settings.setValue(
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/WebBrowser/Feeds/FeedEditDialog.py Sat Feb 20 17:31:34 2016 +0100 @@ -0,0 +1,83 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2011 - 2016 Detlev Offenbach <detlev@die-offenbachs.de> +# + +""" +Module implementing a dialog to edit feed data. +""" + +from __future__ import unicode_literals + +from PyQt5.QtCore import pyqtSlot, QUrl +from PyQt5.QtWidgets import QDialog, QDialogButtonBox + +from .Ui_FeedEditDialog import Ui_FeedEditDialog + + +class FeedEditDialog(QDialog, Ui_FeedEditDialog): + """ + Class implementing a dialog to edit feed data. + """ + def __init__(self, urlString, title, parent=None): + """ + Constructor + + @param urlString feed URL (string) + @param title feed title (string) + @param parent reference to the parent widget (QWidget) + """ + super(FeedEditDialog, self).__init__(parent) + self.setupUi(self) + + self.buttonBox.button(QDialogButtonBox.Ok).setEnabled(False) + + self.titleEdit.setText(title) + self.urlEdit.setText(urlString) + + msh = self.minimumSizeHint() + self.resize(max(self.width(), msh.width()), msh.height()) + + def __setOkButton(self): + """ + Private slot to enable or disable the OK button. + """ + enable = True + + enable = enable and bool(self.titleEdit.text()) + + urlString = self.urlEdit.text() + enable = enable and bool(urlString) + if urlString: + url = QUrl(urlString) + enable = enable and bool(url.scheme()) + enable = enable and bool(url.host()) + + self.buttonBox.button(QDialogButtonBox.Ok).setEnabled(enable) + + @pyqtSlot(str) + def on_titleEdit_textChanged(self, txt): + """ + Private slot to handle changes of the feed title. + + @param txt new feed title (string) + """ + self.__setOkButton() + + @pyqtSlot(str) + def on_urlEdit_textChanged(self, txt): + """ + Private slot to handle changes of the feed URL. + + @param txt new feed URL (string) + """ + self.__setOkButton() + + def getData(self): + """ + Public method to get the entered feed data. + + @return tuple of two strings giving the feed URL and feed title + (string, string) + """ + return (self.urlEdit.text(), self.titleEdit.text())
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/WebBrowser/Feeds/FeedEditDialog.ui Sat Feb 20 17:31:34 2016 +0100 @@ -0,0 +1,111 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>FeedEditDialog</class> + <widget class="QDialog" name="FeedEditDialog"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>475</width> + <height>114</height> + </rect> + </property> + <property name="windowTitle"> + <string>Edit Feed Data</string> + </property> + <property name="sizeGripEnabled"> + <bool>true</bool> + </property> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <layout class="QGridLayout" name="gridLayout"> + <item row="0" column="0" colspan="2"> + <widget class="QLabel" name="label"> + <property name="text"> + <string>Fill title and URL of a feed:</string> + </property> + </widget> + </item> + <item row="1" column="0"> + <widget class="QLabel" name="label_2"> + <property name="text"> + <string>Feed title:</string> + </property> + </widget> + </item> + <item row="1" column="1"> + <widget class="QLineEdit" name="titleEdit"> + <property name="toolTip"> + <string>Enter the title of the feed</string> + </property> + </widget> + </item> + <item row="2" column="0"> + <widget class="QLabel" name="label_3"> + <property name="text"> + <string>Feed URL:</string> + </property> + </widget> + </item> + <item row="2" column="1"> + <widget class="QLineEdit" name="urlEdit"> + <property name="toolTip"> + <string>Enter the URL of the feed</string> + </property> + </widget> + </item> + </layout> + </item> + <item> + <widget class="QDialogButtonBox" name="buttonBox"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="standardButtons"> + <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set> + </property> + </widget> + </item> + </layout> + </widget> + <tabstops> + <tabstop>titleEdit</tabstop> + <tabstop>urlEdit</tabstop> + <tabstop>buttonBox</tabstop> + </tabstops> + <resources/> + <connections> + <connection> + <sender>buttonBox</sender> + <signal>accepted()</signal> + <receiver>FeedEditDialog</receiver> + <slot>accept()</slot> + <hints> + <hint type="sourcelabel"> + <x>224</x> + <y>122</y> + </hint> + <hint type="destinationlabel"> + <x>157</x> + <y>143</y> + </hint> + </hints> + </connection> + <connection> + <sender>buttonBox</sender> + <signal>rejected()</signal> + <receiver>FeedEditDialog</receiver> + <slot>reject()</slot> + <hints> + <hint type="sourcelabel"> + <x>292</x> + <y>128</y> + </hint> + <hint type="destinationlabel"> + <x>286</x> + <y>143</y> + </hint> + </hints> + </connection> + </connections> +</ui>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/WebBrowser/Feeds/FeedsDialog.py Sat Feb 20 17:31:34 2016 +0100 @@ -0,0 +1,101 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2011 - 2016 Detlev Offenbach <detlev@die-offenbachs.de> +# + +""" +Module implementing a dialog to add RSS feeds. +""" + +from __future__ import unicode_literals + +from PyQt5.QtCore import QUrl +from PyQt5.QtWidgets import QDialog, QPushButton, QLabel + +from E5Gui import E5MessageBox + +from .Ui_FeedsDialog import Ui_FeedsDialog + +import UI.PixmapCache + + +class FeedsDialog(QDialog, Ui_FeedsDialog): + """ + Class implementing a dialog to add RSS feeds. + """ + def __init__(self, availableFeeds, browser, parent=None): + """ + Constructor + + @param availableFeeds list of available RSS feeds (list of tuple of + two strings) + @param browser reference to the browser widget (WebBrowserView) + @param parent reference to the parent widget (QWidget) + """ + super(FeedsDialog, self).__init__(parent) + self.setupUi(self) + + self.iconLabel.setPixmap(UI.PixmapCache.getPixmap("rss48.png")) + + self.__browser = browser + + self.__availableFeeds = availableFeeds[:] + for row in range(len(self.__availableFeeds)): + feed = self.__availableFeeds[row] + button = QPushButton(self) + button.setText(self.tr("Add")) + button.feed = feed + label = QLabel(self) + label.setText(feed[0]) + self.feedsLayout.addWidget(label, row, 0) + self.feedsLayout.addWidget(button, row, 1) + button.clicked.connect(self.__addFeed) + + msh = self.minimumSizeHint() + self.resize(max(self.width(), msh.width()), msh.height()) + + def __addFeed(self): + """ + Private slot to add a RSS feed. + """ + button = self.sender() + urlString = button.feed[1] + url = QUrl(urlString) + if not url.host(): + if not urlString.startswith("/"): + urlString = "/" + urlString + urlString = self.__browser.url().host() + urlString + tmpUrl = QUrl(urlString) + if not tmpUrl.scheme(): + urlString = "http://" + urlString + tmpUrl = QUrl(urlString) + if not tmpUrl.scheme() or not tmpUrl.host(): + return + if not url.isValid(): + return + + if button.feed[0]: + title = button.feed[0] + else: + title = self.__browser.url().host() + + from WebBrowser.WebBrowserWindow import WebBrowserWindow + feedsManager = WebBrowserWindow.feedsManager() + if feedsManager.addFeed(urlString, title, self.__browser.icon()): + if WebBrowserWindow.notificationsEnabled(): + WebBrowserWindow.showNotification( + UI.PixmapCache.getPixmap("rss48.png"), + self.tr("Add RSS Feed"), + self.tr("""The feed was added successfully.""")) + else: + E5MessageBox.information( + self, + self.tr("Add RSS Feed"), + self.tr("""The feed was added successfully.""")) + else: + E5MessageBox.warning( + self, + self.tr("Add RSS Feed"), + self.tr("""The feed was already added before.""")) + + self.close()
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/WebBrowser/Feeds/FeedsDialog.ui Sat Feb 20 17:31:34 2016 +0100 @@ -0,0 +1,94 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>FeedsDialog</class> + <widget class="QDialog" name="FeedsDialog"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>352</width> + <height>94</height> + </rect> + </property> + <property name="windowTitle"> + <string>Add Feed</string> + </property> + <property name="sizeGripEnabled"> + <bool>true</bool> + </property> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <layout class="QHBoxLayout" name="horizontalLayout"> + <item> + <widget class="QLabel" name="iconLabel"> + <property name="text"> + <string notr="true"/> + </property> + </widget> + </item> + <item> + <widget class="QLabel" name="label"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>Add Feeds from this site</string> + </property> + </widget> + </item> + </layout> + </item> + <item> + <layout class="QGridLayout" name="feedsLayout"/> + </item> + <item> + <widget class="QDialogButtonBox" name="buttonBox"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="standardButtons"> + <set>QDialogButtonBox::Close</set> + </property> + </widget> + </item> + </layout> + </widget> + <resources/> + <connections> + <connection> + <sender>buttonBox</sender> + <signal>accepted()</signal> + <receiver>FeedsDialog</receiver> + <slot>accept()</slot> + <hints> + <hint type="sourcelabel"> + <x>248</x> + <y>254</y> + </hint> + <hint type="destinationlabel"> + <x>157</x> + <y>274</y> + </hint> + </hints> + </connection> + <connection> + <sender>buttonBox</sender> + <signal>rejected()</signal> + <receiver>FeedsDialog</receiver> + <slot>reject()</slot> + <hints> + <hint type="sourcelabel"> + <x>316</x> + <y>260</y> + </hint> + <hint type="destinationlabel"> + <x>286</x> + <y>274</y> + </hint> + </hints> + </connection> + </connections> +</ui>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/WebBrowser/Feeds/FeedsManager.py Sat Feb 20 17:31:34 2016 +0100 @@ -0,0 +1,435 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2011 - 2016 Detlev Offenbach <detlev@die-offenbachs.de> +# + +""" +Module implementing a RSS feeds manager dialog. +""" + +from __future__ import unicode_literals +try: + str = unicode # __IGNORE_EXCEPTION__ +except NameError: + pass + +from PyQt5.QtCore import pyqtSlot, pyqtSignal, Qt, QUrl, QXmlStreamReader +from PyQt5.QtGui import QIcon, QCursor +from PyQt5.QtWidgets import QDialog, QTreeWidgetItem, QMenu, QApplication +from PyQt5.QtNetwork import QNetworkRequest, QNetworkReply +##from PyQt5.QtWebKit import QWebSettings + +from E5Gui import E5MessageBox + +from .Ui_FeedsManager import Ui_FeedsManager + +import Preferences +import UI.PixmapCache + + +class FeedsManager(QDialog, Ui_FeedsManager): + """ + Class implementing a RSS feeds manager dialog. + + @signal openUrl(QUrl, str) emitted to open a URL in the current tab + @signal newUrl(QUrl, str) emitted to open a URL in a new tab + """ + openUrl = pyqtSignal(QUrl, str) + newUrl = pyqtSignal(QUrl, str) + + UrlStringRole = Qt.UserRole + ErrorDataRole = Qt.UserRole + 1 + + def __init__(self, parent=None): + """ + Constructor + + @param parent reference to the parent widget (QWidget) + """ + super(FeedsManager, self).__init__(parent) + self.setupUi(self) + self.setWindowFlags(Qt.Window) + + self.__wasShown = False + self.__loaded = False + self.__feeds = [] + self.__replies = {} + # dict key is the id of the request object + # dict value is a tuple of request and tree item + + self.feedsTree.setContextMenuPolicy(Qt.CustomContextMenu) + self.feedsTree.customContextMenuRequested.connect( + self.__customContextMenuRequested) + self.feedsTree.itemActivated.connect(self.__itemActivated) + + def show(self): + """ + Public slot to show the feeds manager dialog. + """ + super(FeedsManager, self).show() + + if not self.__wasShown: + self.__enableButtons() + self.on_reloadAllButton_clicked() + self.__wasShown = True + + def addFeed(self, urlString, title, icon): + """ + Public method to add a feed. + + @param urlString URL of the feed (string) + @param title title of the feed (string) + @param icon icon for the feed (QIcon) + @return flag indicating a successful addition of the feed (boolean) + """ + if urlString == "": + return False + + if not self.__loaded: + self.__load() + + # step 1: check, if feed was already added + for feed in self.__feeds: + if feed[0] == urlString: + return False + + # step 2: add the feed + if icon.isNull(): + icon = UI.PixmapCache.getIcon("rss16.png") + feed = (urlString, title, icon) + self.__feeds.append(feed) + self.__addFeedItem(feed) + self.__save() + + return True + + def __addFeedItem(self, feed): + """ + Private slot to add a top level feed item. + + @param feed tuple containing feed info (URL, title, icon) + (string, string, QIcon) + """ + itm = QTreeWidgetItem(self.feedsTree, [feed[1]]) + itm.setIcon(0, feed[2]) + itm.setData(0, FeedsManager.UrlStringRole, feed[0]) + + def __load(self): + """ + Private method to load the feeds data. + """ + self.__feeds = Preferences.getWebBrowser("RssFeeds") + self.__loaded = True + + # populate the feeds tree top level with the feeds + self.feedsTree.clear() + for feed in self.__feeds: + self.__addFeedItem(feed) + + def __save(self): + """ + Private method to store the feeds data. + """ + if not self.__loaded: + self.__load() + + Preferences.setWebBrowser("RssFeeds", self.__feeds) + + @pyqtSlot() + def on_reloadAllButton_clicked(self): + """ + Private slot to reload all feeds. + """ + if not self.__loaded: + self.__load() + + for index in range(self.feedsTree.topLevelItemCount()): + itm = self.feedsTree.topLevelItem(index) + self.__reloadFeed(itm) + + @pyqtSlot() + def on_reloadButton_clicked(self): + """ + Private slot to reload the selected feed. + """ + itm = self.feedsTree.selectedItems()[0] + self.__reloadFeed(itm) + + @pyqtSlot() + def on_editButton_clicked(self): + """ + Private slot to edit the selected feed. + """ + itm = self.feedsTree.selectedItems()[0] + origTitle = itm.text(0) + origUrlString = itm.data(0, FeedsManager.UrlStringRole) + + feedToChange = None + for feed in self.__feeds: + if feed[0] == origUrlString: + feedToChange = feed + break + if feedToChange: + feedIndex = self.__feeds.index(feedToChange) + + from .FeedEditDialog import FeedEditDialog + dlg = FeedEditDialog(origUrlString, origTitle) + if dlg.exec_() == QDialog.Accepted: + urlString, title = dlg.getData() + for feed in self.__feeds: + if feed[0] == urlString: + E5MessageBox.critical( + self, + self.tr("Duplicate Feed URL"), + self.tr( + """A feed with the URL {0} exists already.""" + """ Aborting...""".format(urlString))) + return + + self.__feeds[feedIndex] = (urlString, title, feedToChange[2]) + self.__save() + + itm.setText(0, title) + itm.setData(0, FeedsManager.UrlStringRole, urlString) + self.__reloadFeed(itm) + + @pyqtSlot() + def on_deleteButton_clicked(self): + """ + Private slot to delete the selected feed. + """ + itm = self.feedsTree.selectedItems()[0] + title = itm.text(0) + res = E5MessageBox.yesNo( + self, + self.tr("Delete Feed"), + self.tr( + """<p>Do you really want to delete the feed""" + """ <b>{0}</b>?</p>""".format(title))) + if res: + urlString = itm.data(0, FeedsManager.UrlStringRole) + if urlString: + feedToDelete = None + for feed in self.__feeds: + if feed[0] == urlString: + feedToDelete = feed + break + if feedToDelete: + self.__feeds.remove(feedToDelete) + self.__save() + + index = self.feedsTree.indexOfTopLevelItem(itm) + if index != -1: + self.feedsTree.takeTopLevelItem(index) + del itm + + @pyqtSlot() + def on_feedsTree_itemSelectionChanged(self): + """ + Private slot to enable the various buttons depending on the selection. + """ + self.__enableButtons() + + def __enableButtons(self): + """ + Private slot to disable/enable various buttons. + """ + selItems = self.feedsTree.selectedItems() + if len(selItems) == 1 and \ + self.feedsTree.indexOfTopLevelItem(selItems[0]) != -1: + enable = True + else: + enable = False + + self.reloadButton.setEnabled(enable) + self.editButton.setEnabled(enable) + self.deleteButton.setEnabled(enable) + + def __reloadFeed(self, itm): + """ + Private method to reload the given feed. + + @param itm feed item to be reloaded (QTreeWidgetItem) + """ + urlString = itm.data(0, FeedsManager.UrlStringRole) + if urlString == "": + return + + for child in itm.takeChildren(): + del child + + from WebBrowser.WebBrowserWindow import WebBrowserWindow + request = QNetworkRequest(QUrl(urlString)) + reply = WebBrowserWindow.networkManager().get(request) + reply.finished.connect(self.__feedLoaded) + self.__replies[id(reply)] = (reply, itm) + + def __feedLoaded(self): + """ + Private slot to extract the loaded feed data. + """ + reply = self.sender() + if id(reply) not in self.__replies: + return + + topItem = self.__replies[id(reply)][1] + del self.__replies[id(reply)] + + if reply.error() == QNetworkReply.NoError: + linkString = "" + titleString = "" + + xml = QXmlStreamReader() + xmlData = reply.readAll() + xml.addData(xmlData) + + while not xml.atEnd(): + xml.readNext() + if xml.isStartElement(): + if xml.name() == "item": + linkString = xml.attributes().value("rss:about") + elif xml.name() == "link": + linkString = xml.attributes().value("href") + currentTag = xml.name() + elif xml.isEndElement(): + if xml.name() in ["item", "entry"]: + itm = QTreeWidgetItem(topItem) + itm.setText(0, titleString) + itm.setData(0, FeedsManager.UrlStringRole, linkString) + itm.setIcon(0, UI.PixmapCache.getIcon("rss16.png")) + + linkString = "" + titleString = "" + elif xml.isCharacters() and not xml.isWhitespace(): + if currentTag == "title": + titleString = xml.text() + elif currentTag == "link": + linkString += xml.text() + + if topItem.childCount() == 0: + itm = QTreeWidgetItem(topItem) + itm.setText(0, self.tr("Error fetching feed")) + itm.setData(0, FeedsManager.UrlStringRole, "") + itm.setData(0, FeedsManager.ErrorDataRole, + str(xmlData, encoding="utf-8")) + + topItem.setExpanded(True) + else: + linkString = "" + titleString = reply.errorString() + itm = QTreeWidgetItem(topItem) + itm.setText(0, titleString) + itm.setData(0, FeedsManager.UrlStringRole, linkString) + topItem.setExpanded(True) + + def __customContextMenuRequested(self, pos): + """ + Private slot to handle the context menu request for the feeds tree. + + @param pos position the context menu was requested (QPoint) + """ + itm = self.feedsTree.currentItem() + if itm is None: + return + + if self.feedsTree.indexOfTopLevelItem(itm) != -1: + return + + urlString = itm.data(0, FeedsManager.UrlStringRole) + if urlString: + menu = QMenu() + menu.addAction( + self.tr("&Open"), self.__openMessageInCurrentTab) + menu.addAction( + self.tr("Open in New &Tab"), self.__openMessageInNewTab) + menu.addSeparator() + menu.addAction(self.tr("&Copy URL to Clipboard"), + self.__copyUrlToClipboard) + menu.exec_(QCursor.pos()) + else: + errorString = itm.data(0, FeedsManager.ErrorDataRole) + if errorString: + menu = QMenu() + menu.addAction( + self.tr("&Show error data"), self.__showError) + menu.exec_(QCursor.pos()) + + def __itemActivated(self, itm, column): + """ + Private slot to handle the activation of an item. + + @param itm reference to the activated item (QTreeWidgetItem) + @param column column of the activation (integer) + """ + if self.feedsTree.indexOfTopLevelItem(itm) != -1: + return + + self.__openMessage( + QApplication.keyboardModifiers() & + Qt.ControlModifier == Qt.ControlModifier) + + def __openMessageInCurrentTab(self): + """ + Private slot to open a feed message in the current browser tab. + """ + self.__openMessage(False) + + def __openMessageInNewTab(self): + """ + Private slot to open a feed message in a new browser tab. + """ + self.__openMessage(True) + + def __openMessage(self, newTab): + """ + Private method to open a feed message. + + @param newTab flag indicating to open the feed message in a new tab + (boolean) + """ + itm = self.feedsTree.currentItem() + if itm is None: + return + + urlString = itm.data(0, FeedsManager.UrlStringRole) + if urlString: + title = itm.text(0) + + if newTab: + self.newUrl.emit(QUrl(urlString), title) + else: + self.openUrl.emit(QUrl(urlString), title) + else: + errorString = itm.data(0, FeedsManager.ErrorDataRole) + if errorString: + self.__showError() + + def __copyUrlToClipboard(self): + """ + Private slot to copy the URL of the selected item to the clipboard. + """ + itm = self.feedsTree.currentItem() + if itm is None: + return + + if self.feedsTree.indexOfTopLevelItem(itm) != -1: + return + + urlString = itm.data(0, FeedsManager.UrlStringRole) + if urlString: + QApplication.clipboard().setText(urlString) + + def __showError(self): + """ + Private slot to show error info for a failed load operation. + """ + itm = self.feedsTree.currentItem() + if itm is None: + return + + errorStr = itm.data(0, FeedsManager.ErrorDataRole) + if errorStr: + E5MessageBox.critical( + self, + self.tr("Error loading feed"), + "{0}".format(errorStr))
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/WebBrowser/Feeds/FeedsManager.ui Sat Feb 20 17:31:34 2016 +0100 @@ -0,0 +1,165 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>FeedsManager</class> + <widget class="QDialog" name="FeedsManager"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>750</width> + <height>500</height> + </rect> + </property> + <property name="windowTitle"> + <string>Feeds Manager</string> + </property> + <property name="sizeGripEnabled"> + <bool>true</bool> + </property> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <widget class="QTreeWidget" name="feedsTree"> + <property name="alternatingRowColors"> + <bool>true</bool> + </property> + <property name="allColumnsShowFocus"> + <bool>true</bool> + </property> + <property name="wordWrap"> + <bool>true</bool> + </property> + <attribute name="headerVisible"> + <bool>false</bool> + </attribute> + <column> + <property name="text"> + <string>News</string> + </property> + </column> + </widget> + </item> + <item> + <layout class="QHBoxLayout" name="horizontalLayout"> + <item> + <widget class="QPushButton" name="reloadAllButton"> + <property name="toolTip"> + <string>Press to reload all feeds</string> + </property> + <property name="text"> + <string>Reload &All</string> + </property> + <property name="autoDefault"> + <bool>false</bool> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="reloadButton"> + <property name="toolTip"> + <string>Press to reload the selected feed</string> + </property> + <property name="text"> + <string>&Reload</string> + </property> + <property name="autoDefault"> + <bool>false</bool> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="editButton"> + <property name="toolTip"> + <string>Press to edit the selected feed</string> + </property> + <property name="text"> + <string>&Edit Feed</string> + </property> + <property name="autoDefault"> + <bool>false</bool> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="deleteButton"> + <property name="toolTip"> + <string>Press to delete the selected feed</string> + </property> + <property name="text"> + <string>&Delete Feed</string> + </property> + <property name="autoDefault"> + <bool>false</bool> + </property> + </widget> + </item> + <item> + <spacer name="horizontalSpacer"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + </layout> + </item> + <item> + <widget class="QDialogButtonBox" name="buttonBox"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="standardButtons"> + <set>QDialogButtonBox::Close</set> + </property> + </widget> + </item> + </layout> + </widget> + <tabstops> + <tabstop>feedsTree</tabstop> + <tabstop>reloadAllButton</tabstop> + <tabstop>reloadButton</tabstop> + <tabstop>editButton</tabstop> + <tabstop>deleteButton</tabstop> + <tabstop>buttonBox</tabstop> + </tabstops> + <resources/> + <connections> + <connection> + <sender>buttonBox</sender> + <signal>accepted()</signal> + <receiver>FeedsManager</receiver> + <slot>accept()</slot> + <hints> + <hint type="sourcelabel"> + <x>248</x> + <y>254</y> + </hint> + <hint type="destinationlabel"> + <x>157</x> + <y>274</y> + </hint> + </hints> + </connection> + <connection> + <sender>buttonBox</sender> + <signal>rejected()</signal> + <receiver>FeedsManager</receiver> + <slot>reject()</slot> + <hints> + <hint type="sourcelabel"> + <x>316</x> + <y>260</y> + </hint> + <hint type="destinationlabel"> + <x>286</x> + <y>274</y> + </hint> + </hints> + </connection> + </connections> +</ui>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/WebBrowser/Feeds/__init__.py Sat Feb 20 17:31:34 2016 +0100 @@ -0,0 +1,8 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2011 - 2016 Detlev Offenbach <detlev@die-offenbachs.de> +# + +""" +Package implementing all RSS feed related modules. +"""
--- a/WebBrowser/Network/NetworkManager.py Sat Feb 20 15:25:51 2016 +0100 +++ b/WebBrowser/Network/NetworkManager.py Sat Feb 20 17:31:34 2016 +0100 @@ -16,6 +16,8 @@ from E5Network.E5NetworkProxyFactory import proxyAuthenticationRequired +from WebBrowser.WebBrowserWindow import WebBrowserWindow + import Preferences @@ -31,6 +33,14 @@ """ super(NetworkManager, self).__init__(parent) + if not WebBrowserWindow.mainWindow().fromEric(): + from PyQt5.QtNetwork import QNetworkProxyFactory + from E5Network.E5NetworkProxyFactory import E5NetworkProxyFactory + + self.__proxyFactory = E5NetworkProxyFactory() + QNetworkProxyFactory.setApplicationProxyFactory( + self.__proxyFactory) + self.languagesChanged() self.__ignoredSslErrors = {}
--- a/WebBrowser/Tools/Scripts.py Sat Feb 20 15:25:51 2016 +0100 +++ b/WebBrowser/Tools/Scripts.py Sat Feb 20 17:31:34 2016 +0100 @@ -351,3 +351,36 @@ data = bytes(data).decode("utf-8") data = data.replace("'", "\\'") return source.format(data) + +########################################################################### +## scripts below are specific for eric +########################################################################### + + +def getFeedLinks(): + """ + Function generating a script to extract all RSS and Atom feed links. + + @return script to extract all RSS and Atom feed links + @rtype str + """ + source = """ + (function() { + var out = []; + var links = document.getElementsByTagName('link'); + for (var i = 0; i < links.length; ++i) { + var e = links[i]; + if ((e.rel == 'alternate') && + ((e.type == 'application/atom+xml') || + (e.type == 'application/rss+xml') + ) + ) { + out.push({ + url: e.getAttribute('href'), + title: e.getAttribute('title') + }); + } + } + return out; + })()""" + return source
--- a/WebBrowser/UrlBar/UrlBar.py Sat Feb 20 15:25:51 2016 +0100 +++ b/WebBrowser/UrlBar/UrlBar.py Sat Feb 20 17:31:34 2016 +0100 @@ -13,7 +13,8 @@ except NameError: pass -from PyQt5.QtCore import pyqtSlot, Qt, QPointF, QUrl, QDateTime, qVersion +from PyQt5.QtCore import pyqtSlot, Qt, QPointF, QUrl, QDateTime, QTimer, \ + qVersion from PyQt5.QtGui import QColor, QPalette, QLinearGradient, QIcon from PyQt5.QtWidgets import QDialog, QApplication try: @@ -74,11 +75,10 @@ ## self.addWidget(self.__privacyButton, E5LineEdit.RightSide) ## self.__privacyButton.setVisible(self.__privateMode) - # TODO: RSS -## self.__rssButton = E5LineEditButton(self) -## self.__rssButton.setIcon(UI.PixmapCache.getIcon("rss16.png")) -## self.addWidget(self.__rssButton, E5LineEdit.RightSide) -## self.__rssButton.setVisible(False) + self.__rssButton = E5LineEditButton(self) + self.__rssButton.setIcon(UI.PixmapCache.getIcon("rss16.png")) + self.addWidget(self.__rssButton, E5LineEdit.RightSide) + self.__rssButton.setVisible(False) self.__bookmarkButton = E5LineEditButton(self) self.addWidget(self.__bookmarkButton, E5LineEdit.RightSide) @@ -90,8 +90,7 @@ self.__clearButton.setVisible(False) self.__bookmarkButton.clicked.connect(self.__showBookmarkInfo) - # TODO: RSS -## self.__rssButton.clicked.connect(self.__rssClicked) + self.__rssButton.clicked.connect(self.__rssClicked) # TODO: Privacy ## self.__privacyButton.clicked.connect(self.__privacyClicked) self.__clearButton.clicked.connect(self.clear) @@ -156,6 +155,7 @@ # TODO: SSL ## self.__sslLabel.setVisible(False) self.__bookmarkButton.setVisible(False) + self.__rssButton.setVisible(False) def __checkBookmark(self): """ @@ -189,9 +189,8 @@ self.__checkBookmark() self.__bookmarkButton.setVisible(True) - # TODO: RSS -## if ok: -## self.__rssButton.setVisible(self.__browser.checkRSS()) + if ok: + QTimer.singleShot(0, self.__setRssButton) # TODO: SSL certificate stuff (if possible) ## if ok and \ @@ -456,12 +455,18 @@ self.selectAll() evt.acceptProposedAction() -## -## def __rssClicked(self): -## """ -## Private slot to handle clicking the RSS icon. -## """ -## from WebBrowser.Feeds.FeedsDialog import FeedsDialog -## feeds = self.__browser.getRSS() -## dlg = FeedsDialog(feeds, self.__browser) -## dlg.exec_() + + def __setRssButton(self): + """ + Private slot to show the RSS button. + """ + self.__rssButton.setVisible(self.__browser.checkRSS()) + + def __rssClicked(self): + """ + Private slot to handle clicking the RSS icon. + """ + from WebBrowser.Feeds.FeedsDialog import FeedsDialog + feeds = self.__browser.getRSS() + dlg = FeedsDialog(feeds, self.__browser) + dlg.exec_()
--- a/WebBrowser/WebBrowserPage.py Sat Feb 20 15:25:51 2016 +0100 +++ b/WebBrowser/WebBrowserPage.py Sat Feb 20 17:31:34 2016 +0100 @@ -146,7 +146,7 @@ ## self.__proxy = NetworkAccessManagerProxy(self) ## self.__proxy.setWebPage(self) ## self.__proxy.setPrimaryNetworkAccessManager( -## WebBrowser.WebBrowserWindow.WebBrowserWindow.networkAccessManager()) +## WebBrowser.WebBrowserWindow.WebBrowserWindow.networkManager()) ## self.setNetworkAccessManager(self.__proxy) self.__sslConfiguration = None
--- a/WebBrowser/WebBrowserView.py Sat Feb 20 15:25:51 2016 +0100 +++ b/WebBrowser/WebBrowserView.py Sat Feb 20 17:31:34 2016 +0100 @@ -1887,14 +1887,20 @@ ########################################################################### # TODO: RSS, extract links from page to implement RSS stuff -## def checkRSS(self): -## """ -## Public method to check, if the loaded page contains feed links. -## -## @return flag indicating the existence of feed links (boolean) -## """ -## self.__rss = [] -## + def checkRSS(self): + """ + Public method to check, if the loaded page contains feed links. + + @return flag indicating the existence of feed links (boolean) + """ + self.__rss = [] + + script = Scripts.getFeedLinks() + feeds = self.page().execJavaScript(script) + + for feed in feeds: + if feed["url"] and feed["title"]: + self.__rss.append((feed["title"], feed["url"])) ## frame = self.page() ## linkElementsList = frame.findAllElements("link").toList() ## @@ -1910,8 +1916,8 @@ ## if href == "" or title == "": ## continue ## self.__rss.append((title, href)) -## -## return len(self.__rss) > 0 + + return len(self.__rss) > 0 def getRSS(self): """
--- a/WebBrowser/WebBrowserWindow.py Sat Feb 20 15:25:51 2016 +0100 +++ b/WebBrowser/WebBrowserWindow.py Sat Feb 20 17:31:34 2016 +0100 @@ -89,7 +89,7 @@ _passwordManager = None ## _adblockManager = None ## _downloadManager = None -## _feedsManager = None + _feedsManager = None ## _userAgentsManager = None ## _syncManager = None ## _speedDial = None @@ -243,6 +243,8 @@ else: self.restoreGeometry(g) + WebBrowserWindow.BrowserWindows.append(self) + self.__setIconDatabasePath() self.__initWebEngineSettings() @@ -262,8 +264,6 @@ self.__tabWidget.newBrowser(home) self.__tabWidget.currentBrowser().setFocus() - WebBrowserWindow.BrowserWindows.append(self) - # TODO: AdBlock ## self.__adBlockIcon = AdBlockIcon(self) ## self.statusBar().addPermanentWidget(self.__adBlockIcon) @@ -1639,24 +1639,23 @@ ## self.__showDownloadsWindow) ## self.__actions.append(self.showDownloadManagerAct) - # TODO: RSS Feeds Manager -## self.feedsManagerAct = E5Action( -## self.tr('RSS Feeds Dialog'), -## UI.PixmapCache.getIcon("rss22.png"), -## self.tr('&RSS Feeds Dialog...'), -## QKeySequence(self.tr("Ctrl+Shift+F", "Help|RSS Feeds Dialog")), -## 0, self, 'webbrowser_rss_feeds') -## self.feedsManagerAct.setStatusTip(self.tr( -## 'Open a dialog showing the configured RSS feeds.')) -## self.feedsManagerAct.setWhatsThis(self.tr( -## """<b>RSS Feeds Dialog...</b>""" -## """<p>Open a dialog to show the configured RSS feeds.""" -## """ It can be used to mange the feeds and to show their""" -## """ contents.</p>""" -## )) -## if not self.__initShortcutsOnly: -## self.feedsManagerAct.triggered.connect(self.__showFeedsManager) -## self.__actions.append(self.feedsManagerAct) + self.feedsManagerAct = E5Action( + self.tr('RSS Feeds Dialog'), + UI.PixmapCache.getIcon("rss22.png"), + self.tr('&RSS Feeds Dialog...'), + QKeySequence(self.tr("Ctrl+Shift+F", "Help|RSS Feeds Dialog")), + 0, self, 'webbrowser_rss_feeds') + self.feedsManagerAct.setStatusTip(self.tr( + 'Open a dialog showing the configured RSS feeds.')) + self.feedsManagerAct.setWhatsThis(self.tr( + """<b>RSS Feeds Dialog...</b>""" + """<p>Open a dialog to show the configured RSS feeds.""" + """ It can be used to mange the feeds and to show their""" + """ contents.</p>""" + )) + if not self.__initShortcutsOnly: + self.feedsManagerAct.triggered.connect(self.__showFeedsManager) + self.__actions.append(self.feedsManagerAct) # TODO: Site Info ## self.siteInfoAct = E5Action( @@ -1884,7 +1883,7 @@ menu = mb.addMenu(self.tr("&Tools")) menu.setTearOffEnabled(True) -## menu.addAction(self.feedsManagerAct) + menu.addAction(self.feedsManagerAct) ## menu.addAction(self.siteInfoAct) ## menu.addSeparator() ## menu.addAction(self.synchronizationAct) @@ -1995,7 +1994,7 @@ toolstb = self.addToolBar(self.tr("Tools")) toolstb.setObjectName("ToolsToolBar") toolstb.setIconSize(UI.Config.ToolBarIconSize) -## toolstb.addAction(self.feedsManagerAct) + toolstb.addAction(self.feedsManagerAct) ## toolstb.addAction(self.siteInfoAct) ## toolstb.addSeparator() ## toolstb.addAction(self.synchronizationAct) @@ -2789,7 +2788,7 @@ self.__initWebEngineSettings() # TODO: NetworkManager -## self.networkAccessManager().preferencesChanged() +## self.networkManager().preferencesChanged() ## self.historyManager().preferencesChanged() @@ -2908,7 +2907,7 @@ ## """ ## from .CookieJar.CookieJar import CookieJar ## cls._cookieJar = CookieJar() -## return cls.networkAccessManager().cookieJar() +## return cls.networkManager().cookieJar() ## def __clearIconsDatabase(self): """ @@ -3301,7 +3300,7 @@ # TODO: Cache Cleaning ## if cache: ## try: -## self.networkAccessManager().cache().clear() +## self.networkManager().cache().clear() ## except AttributeError: ## pass # TODO: Cookies @@ -3403,7 +3402,7 @@ ## Private slot to show the network monitor dialog. ## """ ## from E5Network.E5NetworkMonitor import E5NetworkMonitor -## monitor = E5NetworkMonitor.instance(self.networkAccessManager()) +## monitor = E5NetworkMonitor.instance(self.networkManager()) ## monitor.show() ## ## def __showDownloadsWindow(self): @@ -3768,39 +3767,38 @@ else: super(WebBrowserWindow, self).mousePressEvent(evt) - # TODO: RSS -## @classmethod -## def feedsManager(cls): -## """ -## Class method to get a reference to the RSS feeds manager. -## -## @return reference to the RSS feeds manager (FeedsManager) -## """ -## if cls._feedsManager is None: -## from .Feeds.FeedsManager import FeedsManager -## cls._feedsManager = FeedsManager() -## -## return cls._feedsManager -## -## def __showFeedsManager(self): -## """ -## Private slot to show the feeds manager dialog. -## """ -## feedsManager = self.feedsManager() -## feedsManager.openUrl.connect(self.openUrl) -## feedsManager.newUrl.connect(self.openUrlNewTab) -## feedsManager.rejected.connect(self.__feedsManagerClosed) -## feedsManager.show() -## -## def __feedsManagerClosed(self): -## """ -## Private slot to handle closing the feeds manager dialog. -## """ -## feedsManager = self.sender() -## feedsManager.openUrl.disconnect(self.openUrl) -## feedsManager.newUrl.disconnect(self.openUrlNewTab) -## feedsManager.rejected.disconnect(self.__feedsManagerClosed) -## + @classmethod + def feedsManager(cls): + """ + Class method to get a reference to the RSS feeds manager. + + @return reference to the RSS feeds manager (FeedsManager) + """ + if cls._feedsManager is None: + from .Feeds.FeedsManager import FeedsManager + cls._feedsManager = FeedsManager() + + return cls._feedsManager + + def __showFeedsManager(self): + """ + Private slot to show the feeds manager dialog. + """ + feedsManager = self.feedsManager() + feedsManager.openUrl.connect(self.openUrl) + feedsManager.newUrl.connect(self.openUrlNewTab) + feedsManager.rejected.connect(self.__feedsManagerClosed) + feedsManager.show() + + def __feedsManagerClosed(self): + """ + Private slot to handle closing the feeds manager dialog. + """ + feedsManager = self.sender() + feedsManager.openUrl.disconnect(self.openUrl) + feedsManager.newUrl.disconnect(self.openUrlNewTab) + feedsManager.rejected.disconnect(self.__feedsManagerClosed) + # TODO: Site Info ## def __showSiteinfoDialog(self): ## """
--- a/eric6.e4p Sat Feb 20 15:25:51 2016 +0100 +++ b/eric6.e4p Sat Feb 20 17:31:34 2016 +0100 @@ -1293,6 +1293,10 @@ <Source>WebBrowser/FeaturePermissions/FeaturePermissionManager.py</Source> <Source>WebBrowser/FeaturePermissions/FeaturePermissionsDialog.py</Source> <Source>WebBrowser/FeaturePermissions/__init__.py</Source> + <Source>WebBrowser/Feeds/FeedEditDialog.py</Source> + <Source>WebBrowser/Feeds/FeedsDialog.py</Source> + <Source>WebBrowser/Feeds/FeedsManager.py</Source> + <Source>WebBrowser/Feeds/__init__.py</Source> <Source>WebBrowser/FlashCookieManager/FlashCookie.py</Source> <Source>WebBrowser/FlashCookieManager/FlashCookieManager.py</Source> <Source>WebBrowser/FlashCookieManager/FlashCookieManagerDialog.py</Source> @@ -1769,6 +1773,9 @@ <Form>WebBrowser/Bookmarks/BookmarksDialog.ui</Form> <Form>WebBrowser/Bookmarks/BookmarksImportDialog.ui</Form> <Form>WebBrowser/FeaturePermissions/FeaturePermissionsDialog.ui</Form> + <Form>WebBrowser/Feeds/FeedEditDialog.ui</Form> + <Form>WebBrowser/Feeds/FeedsDialog.ui</Form> + <Form>WebBrowser/Feeds/FeedsManager.ui</Form> <Form>WebBrowser/FlashCookieManager/FlashCookieManagerDialog.ui</Form> <Form>WebBrowser/History/HistoryDialog.ui</Form> <Form>WebBrowser/OpenSearch/OpenSearchDialog.ui</Form>