Sun, 03 Apr 2016 16:33:37 +0200
Merged the QtWebEngine branch into the default development branch to finalize the port in here.
install.py | file | annotate | diff | comparison | revisions |
--- a/.hgignore Sun Apr 03 12:29:37 2016 +0200 +++ b/.hgignore Sun Apr 03 16:33:37 2016 +0200 @@ -16,3 +16,4 @@ glob:__pycache__ glob:**.DS_Store glob:**.coverage +glob:GPUCache
--- a/DebugClients/Python/DebugClientThreads.py Sun Apr 03 12:29:37 2016 +0200 +++ b/DebugClients/Python/DebugClientThreads.py Sun Apr 03 16:33:37 2016 +0200 @@ -200,4 +200,4 @@ # # eflag: FileType = Python2 -# eflag: noqa = M601, M702 +# eflag: noqa = M601, M702, E402
--- a/DebugClients/Python3/DebugClientThreads.py Sun Apr 03 12:29:37 2016 +0200 +++ b/DebugClients/Python3/DebugClientThreads.py Sun Apr 03 16:33:37 2016 +0200 @@ -199,4 +199,4 @@ debugClient.main() # -# eflag: noqa = M702 +# eflag: noqa = M702, E402
--- a/E5Gui/E5ErrorMessage.py Sun Apr 03 12:29:37 2016 +0200 +++ b/E5Gui/E5ErrorMessage.py Sun Apr 03 16:33:37 2016 +0200 @@ -44,7 +44,9 @@ "QFont::", "QCocoaMenu::removeMenuItem", "QCocoaMenu::insertNative", - ",type id:" + ",type id:", + "Remote debugging server started successfully", + "Uncaught SecurityError:", ] def __filterMessage(self, message):
--- a/Helpviewer/HelpBrowserWV.py Sun Apr 03 12:29:37 2016 +0200 +++ b/Helpviewer/HelpBrowserWV.py Sun Apr 03 16:33:37 2016 +0200 @@ -10,7 +10,7 @@ from __future__ import unicode_literals try: - str = unicode + str = unicode # __IGNORE_EXCEPTION__ except NameError: pass @@ -1617,8 +1617,8 @@ searchUrl = QUrl(self.page().mainFrame().baseUrl().resolved( QUrl(formElement.attribute("action")))) - if searchUrl.scheme() != "http": - return +## if searchUrl.scheme() != "http": +## return if qVersion() >= "5.0.0": from PyQt5.QtCore import QUrlQuery
--- a/Helpviewer/HelpWindow.py Sun Apr 03 12:29:37 2016 +0200 +++ b/Helpviewer/HelpWindow.py Sun Apr 03 16:33:37 2016 +0200 @@ -71,8 +71,6 @@ helpwindows = [] - maxMenuFilePathLen = 75 - _fromEric = False useQtHelp = QTHELP_AVAILABLE
--- a/PluginManager/PluginManager.py Sun Apr 03 12:29:37 2016 +0200 +++ b/PluginManager/PluginManager.py Sun Apr 03 16:33:37 2016 +0200 @@ -144,7 +144,11 @@ self.__networkManager.sslErrors.connect(self.__sslErrors) self.__replies = [] - self.__ui.onlineStateChanged.connect(self.__onlineStateChanged) + try: + self.__ui.onlineStateChanged.connect(self.__onlineStateChanged) + except AttributeError: + # it was not called from eric + pass def finalizeSetup(self): """
--- a/Preferences/ConfigurationDialog.py Sun Apr 03 12:29:37 2016 +0200 +++ b/Preferences/ConfigurationDialog.py Sun Apr 03 16:33:37 2016 +0200 @@ -79,9 +79,10 @@ HelpBrowserMode = 1 TrayStarterMode = 2 HexEditorMode = 3 + WebBrowserMode = 4 def __init__(self, parent=None, fromEric=True, displayMode=DefaultMode, - expandedEntries=[]): + expandedEntries=[], webEngine=False): """ Constructor @@ -89,21 +90,25 @@ @keyparam fromEric flag indicating a dialog generation from within the eric6 ide (boolean) @keyparam displayMode mode of the configuration dialog - (DefaultMode, HelpBrowserMode, TrayStarterMode, HexEditorMode) + (DefaultMode, HelpBrowserMode, TrayStarterMode, HexEditorMode, + WebBrowserMode) @exception RuntimeError raised to indicate an invalid dialog mode @keyparam expandedEntries list of entries to be shown expanded (list of strings) + @keyparam webEngine flag indicating QtWebEngine is used (bool) """ assert displayMode in ( ConfigurationWidget.DefaultMode, ConfigurationWidget.HelpBrowserMode, ConfigurationWidget.TrayStarterMode, ConfigurationWidget.HexEditorMode, + ConfigurationWidget.WebBrowserMode, ) super(ConfigurationWidget, self).__init__(parent) self.fromEric = fromEric self.displayMode = displayMode + self.__webEngine = webEngine self.__setupUi() @@ -315,25 +320,49 @@ [self.tr("Viewmanager"), "preferences-viewmanager.png", "ViewmanagerPage", "0interfacePage", None], } - try: - from PyQt5 import QtWebKit # __IGNORE_WARNING__ + if webEngine: self.configItems.update({ - "helpAppearancePage": + "0webBrowserPage": + [self.tr("Web Browser"), "ericWeb.png", + None, None, None], + "webBrowserAppearancePage": [self.tr("Appearance"), "preferences-styles.png", - "HelpAppearancePage", "0helpPage", None], + "WebBrowserAppearancePage", "0webBrowserPage", None], + "webBrowserPage": + [self.tr("eric6 Web Browser"), "ericWeb.png", + "WebBrowserPage", "0webBrowserPage", None], "helpFlashCookieManagerPage": [self.tr("Flash Cookie Manager"), "flashCookie16.png", - "HelpFlashCookieManagerPage", "0helpPage", None], + "HelpFlashCookieManagerPage", "0webBrowserPage", None], "helpVirusTotalPage": [self.tr("VirusTotal Interface"), "virustotal.png", - "HelpVirusTotalPage", "0helpPage", None], - "helpWebBrowserPage": - [self.tr("eric6 Web Browser"), "ericWeb.png", - "HelpWebBrowserPage", "0helpPage", None], + "HelpVirusTotalPage", "0webBrowserPage", None], }) - except ImportError: - pass + else: + try: + from PyQt5 import QtWebKit # __IGNORE_WARNING__ + self.configItems.update({ + "0helpBrowserPage": + [self.tr("Web Browser"), "ericWeb.png", + None, None, None], + "helpAppearancePage": + [self.tr("Appearance"), "preferences-styles.png", + "HelpAppearancePage", "0helpBrowserPage", None], + "helpWebBrowserPage": + [self.tr("eric6 Web Browser"), "ericWeb.png", + "HelpWebBrowserPage", "0helpBrowserPage", None], + "helpFlashCookieManagerPage": + [self.tr("Flash Cookie Manager"), + "flashCookie16.png", + "HelpFlashCookieManagerPage", "0helpBrowserPage", + None], + "helpVirusTotalPage": + [self.tr("VirusTotal Interface"), "virustotal.png", + "HelpVirusTotalPage", "0helpBrowserPage", None], + }) + except ImportError: + pass self.configItems.update( e5App().getObject("PluginManager").getPluginConfigData()) @@ -359,34 +388,73 @@ [self.tr("Security"), "preferences-security.png", "SecurityPage", None, None], - "0helpPage": - [self.tr("Help"), "preferences-help.png", - None, None, None], "helpDocumentationPage": [self.tr("Help Documentation"), "preferences-helpdocumentation.png", - "HelpDocumentationPage", "0helpPage", None], + "HelpDocumentationPage", None, None], } try: from PyQt5 import QtWebKit # __IGNORE_WARNING__ self.configItems.update({ "helpAppearancePage": [self.tr("Appearance"), "preferences-styles.png", - "HelpAppearancePage", "0helpPage", None], + "HelpAppearancePage", None, None], "helpFlashCookieManagerPage": [self.tr("Flash Cookie Manager"), "flashCookie16.png", - "HelpFlashCookieManagerPage", "0helpPage", None], + "HelpFlashCookieManagerPage", None, None], "helpVirusTotalPage": [self.tr("VirusTotal Interface"), "virustotal.png", - "HelpVirusTotalPage", "0helpPage", None], + "HelpVirusTotalPage", None, None], "helpWebBrowserPage": [self.tr("eric6 Web Browser"), "ericWeb.png", - "HelpWebBrowserPage", "0helpPage", None], + "HelpWebBrowserPage", None, None], }) except ImportError: pass + elif displayMode == ConfigurationWidget.WebBrowserMode: + self.configItems = { + # key : [display string, pixmap name, dialog module name or + # page creation function, parent key, + # reference to configuration page (must always be last)] + # The dialog module must have the module function 'create' to + # create the configuration page. This must have the method + # 'save' to save the settings. + "interfacePage": + [self.tr("Interface"), "preferences-interface.png", + "HelpInterfacePage", None, None], + "networkPage": + [self.tr("Network"), "preferences-network.png", + "NetworkPage", None, None], + "printerPage": + [self.tr("Printer"), "preferences-printer.png", + "PrinterPage", None, None], + "securityPage": + [self.tr("Security"), "preferences-security.png", + "SecurityPage", None, None], + + "helpDocumentationPage": + [self.tr("Help Documentation"), + "preferences-helpdocumentation.png", + "HelpDocumentationPage", None, None], + + "webBrowserAppearancePage": + [self.tr("Appearance"), "preferences-styles.png", + "WebBrowserAppearancePage", None, None], + "webBrowserPage": + [self.tr("eric6 Web Browser"), "ericWeb.png", + "WebBrowserPage", None, None], + + "helpFlashCookieManagerPage": + [self.tr("Flash Cookie Manager"), + "flashCookie16.png", + "HelpFlashCookieManagerPage", None, None], + "helpVirusTotalPage": + [self.tr("VirusTotal Interface"), "virustotal.png", + "HelpVirusTotalPage", None, None], + } + elif displayMode == ConfigurationWidget.TrayStarterMode: self.configItems = { # key : [display string, pixmap name, dialog module name or @@ -446,7 +514,8 @@ if displayMode in [ConfigurationWidget.HelpBrowserMode, ConfigurationWidget.TrayStarterMode, - ConfigurationWidget.HexEditorMode]: + ConfigurationWidget.HexEditorMode, + ConfigurationWidget.WebBrowserMode]: self.configListSearch.hide() if displayMode not in [ConfigurationWidget.TrayStarterMode, @@ -837,6 +906,13 @@ pageName = item.data(0, Qt.UserRole) if pageName not in self.__expandedEntries: self.__expandedEntries.append(pageName) + + def isUsingWebEngine(self): + """ + Public method to get an indication, if QtWebEngine is being used. + """ + return self.__webEngine or \ + self.displayMode == ConfigurationWidget.WebBrowserMode class ConfigurationDialog(QDialog): @@ -854,10 +930,11 @@ HelpBrowserMode = ConfigurationWidget.HelpBrowserMode TrayStarterMode = ConfigurationWidget.TrayStarterMode HexEditorMode = ConfigurationWidget.HexEditorMode + WebBrowserMode = ConfigurationWidget.WebBrowserMode def __init__(self, parent=None, name=None, modal=False, fromEric=True, displayMode=ConfigurationWidget.DefaultMode, - expandedEntries=[]): + expandedEntries=[], webEngine=False): """ Constructor @@ -867,9 +944,11 @@ @keyparam fromEric flag indicating a dialog generation from within the eric6 ide (boolean) @keyparam displayMode mode of the configuration dialog - (DefaultMode, HelpBrowserMode, TrayStarterMode, HexEditorMode) + (DefaultMode, HelpBrowserMode, TrayStarterMode, HexEditorMode, + WebBrowserMode) @keyparam expandedEntries list of entries to be shown expanded (list of strings) + @keyparam webEngine flag indicating QtWebEngine is used (bool) """ super(ConfigurationDialog, self).__init__(parent) if name: @@ -883,7 +962,8 @@ self.cw = ConfigurationWidget(self, fromEric=fromEric, displayMode=displayMode, - expandedEntries=expandedEntries) + expandedEntries=expandedEntries, + webEngine=webEngine) size = self.cw.size() self.layout.addWidget(self.cw) self.resize(size) @@ -951,15 +1031,17 @@ """ Main window class for the standalone dialog. """ - def __init__(self, parent=None): + def __init__(self, parent=None, webEngine=False): """ Constructor @param parent reference to the parent widget (QWidget) + @keyparam webEngine flag indicating QtWebEngine is used (bool) """ super(ConfigurationWindow, self).__init__(parent) - self.cw = ConfigurationWidget(self, fromEric=False) + self.cw = ConfigurationWidget(self, fromEric=False, + webEngine=webEngine) size = self.cw.size() self.setCentralWidget(self.cw) self.resize(size)
--- a/Preferences/ConfigurationPages/HelpViewersPage.py Sun Apr 03 12:29:37 2016 +0200 +++ b/Preferences/ConfigurationPages/HelpViewersPage.py Sun Apr 03 16:33:37 2016 +0200 @@ -9,7 +9,21 @@ from __future__ import unicode_literals +from PyQt5.QtCore import qVersion from PyQt5.QtWidgets import QButtonGroup +try: + from PyQt5 import QtWebKit # __IGNORE_WARNING__ + WEBKIT_AVAILABLE = True +except ImportError: + WEBKIT_AVAILABLE = False +if qVersion() < "5.6.0": + WEBENGINE_AVAILABLE = False +else: + try: + from PyQt5 import QtWebEngineWidgets # __IGNORE_WARNING__ + WEBENGINE_AVAILABLE = True + except ImportError: + WEBENGINE_AVAILABLE = False from E5Gui.E5PathPicker import E5PathPickerModes @@ -40,14 +54,14 @@ self.helpViewerGroup.addButton(self.customViewerButton) # set initial values - hvId = Preferences.getHelp("HelpViewerType") - # check availability of QtWebKit - try: - from PyQt5 import QtWebKit # __IGNORE_WARNING__ - except ImportError: - # not available, reset help viewer to default + if WEBENGINE_AVAILABLE: + hvId = Preferences.getWebBrowser("HelpViewerType") + else: + hvId = Preferences.getHelp("HelpViewerType") + if not WEBENGINE_AVAILABLE and not WEBKIT_AVAILABLE: if hvId == 1: - hvId = Preferences.Prefs.helpDefaults["HelpViewerType"] + hvId = Preferences.Prefs.webBrowserDefaultsDefaults[ + "HelpViewerType"] self.helpBrowserButton.setEnabled(False) if hvId == 1: @@ -74,6 +88,7 @@ elif self.customViewerButton.isChecked(): hvId = 4 Preferences.setHelp("HelpViewerType", hvId) + Preferences.setWebBrowser("HelpViewerType", hvId) Preferences.setHelp( "CustomViewer", self.customViewerPicker.text())
--- a/Preferences/ConfigurationPages/NetworkPage.py Sun Apr 03 12:29:37 2016 +0200 +++ b/Preferences/ConfigurationPages/NetworkPage.py Sun Apr 03 16:33:37 2016 +0200 @@ -25,14 +25,22 @@ """ Class implementing the Network configuration page. """ - def __init__(self): + def __init__(self, configDialog): """ Constructor + + @param configDialog reference to the configuration dialog + (ConfigurationDialog) """ super(NetworkPage, self).__init__() self.setupUi(self) self.setObjectName("NetworkPage") + self.__configDlg = configDialog + self.__displayMode = None + self.__webEngine = False + self.__webKit = False + self.downloadDirPicker.setMode(E5PathPickerModes.DirectoryMode) self.ftpProxyTypeCombo.addItem( @@ -60,17 +68,6 @@ self.downloadDirPicker.setText(Preferences.getUI("DownloadPath")) self.requestFilenameCheckBox.setChecked( Preferences.getUI("RequestDownloadFilename")) - try: - policy = Preferences.getHelp("DownloadManagerRemovePolicy") - from Helpviewer.Download.DownloadManager import DownloadManager - if policy == DownloadManager.RemoveNever: - self.cleanupNeverButton.setChecked(True) - elif policy == DownloadManager.RemoveExit: - self.cleanupExitButton.setChecked(True) - else: - self.cleanupSuccessfulButton.setChecked(True) - except ImportError: - self.cleanupGroup.hide() # HTTP proxy self.httpProxyHostEdit.setText( @@ -111,6 +108,48 @@ self.exceptionsEdit.setText( ", ".join(Preferences.getUI("ProxyExceptions").split(","))) + def setMode(self, displayMode): + """ + Public method to perform mode dependent setups. + + @param displayMode mode of the configuration dialog + (ConfigurationWidget.DefaultMode, + ConfigurationWidget.HelpBrowserMode, + ConfigurationWidget.WebBrowserMode) + """ + from ..ConfigurationDialog import ConfigurationWidget + assert displayMode in ( + ConfigurationWidget.DefaultMode, + ConfigurationWidget.HelpBrowserMode, + ConfigurationWidget.WebBrowserMode + ) + + self.__displayMode = displayMode + if self.__displayMode == ConfigurationWidget.HelpBrowserMode or \ + not self.__configDlg.isUsingWebEngine(): + try: + policy = Preferences.getHelp("DownloadManagerRemovePolicy") + from Helpviewer.Download.DownloadManager import DownloadManager + if policy == DownloadManager.RemoveNever: + self.cleanupNeverButton.setChecked(True) + elif policy == DownloadManager.RemoveExit: + self.cleanupExitButton.setChecked(True) + else: + self.cleanupSuccessfulButton.setChecked(True) + self.__webKit = True + except ImportError: + self.cleanupGroup.hide() + else: + policy = Preferences.getWebBrowser("DownloadManagerRemovePolicy") + from WebBrowser.Download.DownloadManager import DownloadManager + if policy == DownloadManager.RemoveNever: + self.cleanupNeverButton.setChecked(True) + elif policy == DownloadManager.RemoveExit: + self.cleanupExitButton.setChecked(True) + else: + self.cleanupSuccessfulButton.setChecked(True) + self.__webEngine = True + def save(self): """ Public slot to save the Networj configuration. @@ -121,7 +160,7 @@ Preferences.setUI( "RequestDownloadFilename", self.requestFilenameCheckBox.isChecked()) - try: + if self.__webKit: from Helpviewer.Download.DownloadManager import DownloadManager if self.cleanupNeverButton.isChecked(): policy = DownloadManager.RemoveNever @@ -130,9 +169,15 @@ else: policy = DownloadManager.RemoveSuccessFullDownload Preferences.setHelp("DownloadManagerRemovePolicy", policy) - except ImportError: - # ignore it - pass + elif self.__webEngine: + from WebBrowser.Download.DownloadManager import DownloadManager + if self.cleanupNeverButton.isChecked(): + policy = DownloadManager.RemoveNever + elif self.cleanupExitButton.isChecked(): + policy = DownloadManager.RemoveExit + else: + policy = DownloadManager.RemoveSuccessFullDownload + Preferences.setWebBrowser("DownloadManagerRemovePolicy", policy) Preferences.setUI( "UseProxy", @@ -222,5 +267,5 @@ @param dlg reference to the configuration dialog @return reference to the instantiated page (ConfigurationPageBase) """ - page = NetworkPage() + page = NetworkPage(dlg) return page
--- a/Preferences/ConfigurationPages/PrinterPage.py Sun Apr 03 12:29:37 2016 +0200 +++ b/Preferences/ConfigurationPages/PrinterPage.py Sun Apr 03 16:33:37 2016 +0200 @@ -52,6 +52,8 @@ Preferences.getPrinter("TopMargin")) self.bottomMarginSpinBox.setValue( Preferences.getPrinter("BottomMargin")) + self.resolutionSpinBox.setValue( + Preferences.getPrinter("Resolution")) def save(self): """ @@ -84,6 +86,9 @@ Preferences.setPrinter( "BottomMargin", self.bottomMarginSpinBox.value()) + Preferences.setPrinter( + "Resolution", + self.resolutionSpinBox.value()) @pyqtSlot() def on_printheaderFontButton_clicked(self):
--- a/Preferences/ConfigurationPages/PrinterPage.ui Sun Apr 03 12:29:37 2016 +0200 +++ b/Preferences/ConfigurationPages/PrinterPage.ui Sun Apr 03 16:33:37 2016 +0200 @@ -1,56 +1,86 @@ -<ui version="4.0" > +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> <class>PrinterPage</class> - <widget class="QWidget" name="PrinterPage" > - <property name="geometry" > + <widget class="QWidget" name="PrinterPage"> + <property name="geometry"> <rect> <x>0</x> <y>0</y> - <width>446</width> + <width>448</width> <height>568</height> </rect> </property> - <layout class="QVBoxLayout" > + <layout class="QVBoxLayout" name="verticalLayout"> <item> - <widget class="QLabel" name="headerLabel" > - <property name="text" > - <string><b>Configure printer settings</b></string> + <widget class="QLabel" name="headerLabel"> + <property name="text"> + <string><b>Configure printer settings</b></string> </property> </widget> </item> <item> - <widget class="Line" name="line7" > - <property name="frameShape" > + <widget class="Line" name="line7"> + <property name="frameShape"> <enum>QFrame::HLine</enum> </property> - <property name="frameShadow" > + <property name="frameShadow"> <enum>QFrame::Sunken</enum> </property> - <property name="orientation" > + <property name="orientation"> <enum>Qt::Horizontal</enum> </property> </widget> </item> <item> - <layout class="QGridLayout" > - <item row="1" column="1" colspan="2" > - <widget class="QFrame" name="frame_2" > - <property name="frameShape" > + <layout class="QGridLayout" name="gridLayout"> + <item row="0" column="0"> + <widget class="QLabel" name="TextLabel1"> + <property name="text"> + <string>Printername:</string> + </property> + </widget> + </item> + <item row="0" column="1" colspan="3"> + <widget class="QLineEdit" name="printerNameEdit"/> + </item> + <item row="1" column="0"> + <widget class="QLabel" name="TextLabel2"> + <property name="text"> + <string>Colour Mode:</string> + </property> + <property name="alignment"> + <set>Qt::AlignTop</set> + </property> + </widget> + </item> + <item row="1" column="1" colspan="3"> + <widget class="QFrame" name="frame_2"> + <property name="frameShape"> <enum>QFrame::NoFrame</enum> </property> - <layout class="QVBoxLayout" > - <property name="margin" > + <layout class="QVBoxLayout"> + <property name="leftMargin"> + <number>0</number> + </property> + <property name="topMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> <number>0</number> </property> <item> - <widget class="QRadioButton" name="printerColorButton" > - <property name="text" > + <widget class="QRadioButton" name="printerColorButton"> + <property name="text"> <string>Colour</string> </property> </widget> </item> <item> - <widget class="QRadioButton" name="printerGrayscaleButton" > - <property name="text" > + <widget class="QRadioButton" name="printerGrayscaleButton"> + <property name="text"> <string>Gray Scale</string> </property> </widget> @@ -58,28 +88,47 @@ </layout> </widget> </item> - <item row="2" column="1" colspan="2" > - <widget class="QFrame" name="frame" > - <property name="frameShape" > + <item row="2" column="0"> + <widget class="QLabel" name="TextLabel4"> + <property name="text"> + <string>Page Order:</string> + </property> + <property name="alignment"> + <set>Qt::AlignTop</set> + </property> + </widget> + </item> + <item row="2" column="1" colspan="3"> + <widget class="QFrame" name="frame"> + <property name="frameShape"> <enum>QFrame::NoFrame</enum> </property> - <layout class="QVBoxLayout" > - <property name="spacing" > + <layout class="QVBoxLayout"> + <property name="spacing"> <number>6</number> </property> - <property name="margin" > + <property name="leftMargin"> + <number>0</number> + </property> + <property name="topMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> <number>0</number> </property> <item> - <widget class="QRadioButton" name="printFirstPageFirstButton" > - <property name="text" > + <widget class="QRadioButton" name="printFirstPageFirstButton"> + <property name="text"> <string>First Page First</string> </property> </widget> </item> <item> - <widget class="QRadioButton" name="printFirstPageLastButton" > - <property name="text" > + <widget class="QRadioButton" name="printFirstPageLastButton"> + <property name="text"> <string>Last Page First</string> </property> </widget> @@ -87,88 +136,32 @@ </layout> </widget> </item> - <item row="0" column="1" colspan="2" > - <widget class="QLineEdit" name="printerNameEdit" /> - </item> - <item row="0" column="0" > - <widget class="QLabel" name="TextLabel1" > - <property name="text" > - <string>Printername:</string> - </property> - </widget> - </item> - <item row="3" column="0" > - <widget class="QLabel" name="TextLabel3" > - <property name="text" > + <item row="3" column="0"> + <widget class="QLabel" name="TextLabel3"> + <property name="text"> <string>Magnification:</string> </property> </widget> </item> - <item row="4" column="1" colspan="2" > - <widget class="QLineEdit" name="printheaderFontSample" > - <property name="focusPolicy" > - <enum>Qt::NoFocus</enum> - </property> - <property name="text" > - <string>Header Font</string> - </property> - <property name="alignment" > - <set>Qt::AlignHCenter</set> - </property> - <property name="readOnly" > - <bool>true</bool> - </property> - </widget> - </item> - <item row="4" column="0" > - <widget class="QPushButton" name="printheaderFontButton" > - <property name="toolTip" > - <string>Press to select the font for the page headers</string> - </property> - <property name="text" > - <string>Header Font</string> - </property> - </widget> - </item> - <item row="3" column="1" > - <widget class="QSpinBox" name="printMagnificationSpinBox" > - <property name="minimum" > + <item row="3" column="1"> + <widget class="QSpinBox" name="printMagnificationSpinBox"> + <property name="minimum"> <number>-10</number> </property> - <property name="maximum" > + <property name="maximum"> <number>10</number> </property> - <property name="value" > + <property name="value"> <number>-3</number> </property> </widget> </item> - <item row="1" column="0" > - <widget class="QLabel" name="TextLabel2" > - <property name="text" > - <string>Colour Mode:</string> - </property> - <property name="alignment" > - <set>Qt::AlignTop</set> - </property> - </widget> - </item> - <item row="2" column="0" > - <widget class="QLabel" name="TextLabel4" > - <property name="text" > - <string>Page Order:</string> - </property> - <property name="alignment" > - <set>Qt::AlignTop</set> - </property> - </widget> - </item> - <item row="3" column="2" > + <item row="3" column="2" colspan="2"> <spacer> - <property name="orientation" > + <property name="orientation"> <enum>Qt::Horizontal</enum> </property> - <property name="sizeHint" stdset="0" > + <property name="sizeHint" stdset="0"> <size> <width>251</width> <height>20</height> @@ -176,88 +169,153 @@ </property> </spacer> </item> + <item row="4" column="0"> + <widget class="QLabel" name="label"> + <property name="text"> + <string>Resolution:</string> + </property> + </widget> + </item> + <item row="4" column="1" colspan="2"> + <widget class="QSpinBox" name="resolutionSpinBox"> + <property name="toolTip"> + <string>Select the printer resolution </string> + </property> + <property name="alignment"> + <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> + </property> + <property name="suffix"> + <string> DPI</string> + </property> + <property name="maximum"> + <number>6000</number> + </property> + <property name="singleStep"> + <number>50</number> + </property> + </widget> + </item> + <item row="4" column="3"> + <spacer name="horizontalSpacer"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>208</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item row="5" column="0"> + <widget class="QPushButton" name="printheaderFontButton"> + <property name="toolTip"> + <string>Press to select the font for the page headers</string> + </property> + <property name="text"> + <string>Header Font</string> + </property> + </widget> + </item> + <item row="5" column="1" colspan="3"> + <widget class="QLineEdit" name="printheaderFontSample"> + <property name="focusPolicy"> + <enum>Qt::NoFocus</enum> + </property> + <property name="text"> + <string>Header Font</string> + </property> + <property name="alignment"> + <set>Qt::AlignHCenter</set> + </property> + <property name="readOnly"> + <bool>true</bool> + </property> + </widget> + </item> </layout> </item> <item> - <layout class="QHBoxLayout" > + <layout class="QHBoxLayout"> <item> - <widget class="QGroupBox" name="groupBox" > - <property name="title" > + <widget class="QGroupBox" name="groupBox"> + <property name="title"> <string>Margins</string> </property> - <layout class="QGridLayout" > - <item row="0" column="1" > - <widget class="QDoubleSpinBox" name="topMarginSpinBox" > - <property name="toolTip" > + <layout class="QGridLayout"> + <item row="0" column="1"> + <widget class="QDoubleSpinBox" name="topMarginSpinBox"> + <property name="toolTip"> <string>Enter the top margin in cm.</string> </property> - <property name="suffix" > + <property name="suffix"> <string> cm</string> </property> - <property name="decimals" > + <property name="decimals"> <number>1</number> </property> - <property name="maximum" > + <property name="maximum"> <double>9.900000000000000</double> </property> - <property name="singleStep" > + <property name="singleStep"> <double>0.500000000000000</double> </property> </widget> </item> - <item row="1" column="0" > - <widget class="QDoubleSpinBox" name="leftMarginSpinBox" > - <property name="toolTip" > + <item row="1" column="0"> + <widget class="QDoubleSpinBox" name="leftMarginSpinBox"> + <property name="toolTip"> <string>Enter the left margin in cm.</string> </property> - <property name="suffix" > + <property name="suffix"> <string> cm</string> </property> - <property name="decimals" > + <property name="decimals"> <number>1</number> </property> - <property name="maximum" > + <property name="maximum"> <double>9.900000000000000</double> </property> - <property name="singleStep" > + <property name="singleStep"> <double>0.500000000000000</double> </property> </widget> </item> - <item row="1" column="2" > - <widget class="QDoubleSpinBox" name="rightMarginSpinBox" > - <property name="toolTip" > + <item row="1" column="2"> + <widget class="QDoubleSpinBox" name="rightMarginSpinBox"> + <property name="toolTip"> <string>Enter the right margin in cm.</string> </property> - <property name="suffix" > + <property name="suffix"> <string> cm</string> </property> - <property name="decimals" > + <property name="decimals"> <number>1</number> </property> - <property name="maximum" > + <property name="maximum"> <double>9.900000000000000</double> </property> - <property name="singleStep" > + <property name="singleStep"> <double>0.500000000000000</double> </property> </widget> </item> - <item row="2" column="1" > - <widget class="QDoubleSpinBox" name="bottomMarginSpinBox" > - <property name="toolTip" > + <item row="2" column="1"> + <widget class="QDoubleSpinBox" name="bottomMarginSpinBox"> + <property name="toolTip"> <string>Enter the bottom margin in cm.</string> </property> - <property name="suffix" > + <property name="suffix"> <string> cm</string> </property> - <property name="decimals" > + <property name="decimals"> <number>1</number> </property> - <property name="maximum" > + <property name="maximum"> <double>9.900000000000000</double> </property> - <property name="singleStep" > + <property name="singleStep"> <double>0.500000000000000</double> </property> </widget> @@ -267,10 +325,10 @@ </item> <item> <spacer> - <property name="orientation" > + <property name="orientation"> <enum>Qt::Horizontal</enum> </property> - <property name="sizeHint" stdset="0" > + <property name="sizeHint" stdset="0"> <size> <width>40</width> <height>20</height> @@ -282,10 +340,10 @@ </item> <item> <spacer> - <property name="orientation" > + <property name="orientation"> <enum>Qt::Vertical</enum> </property> - <property name="sizeHint" stdset="0" > + <property name="sizeHint" stdset="0"> <size> <width>428</width> <height>61</height> @@ -302,6 +360,7 @@ <tabstop>printFirstPageFirstButton</tabstop> <tabstop>printFirstPageLastButton</tabstop> <tabstop>printMagnificationSpinBox</tabstop> + <tabstop>resolutionSpinBox</tabstop> <tabstop>printheaderFontButton</tabstop> <tabstop>topMarginSpinBox</tabstop> <tabstop>leftMarginSpinBox</tabstop>
--- a/Preferences/ConfigurationPages/SecurityPage.py Sun Apr 03 12:29:37 2016 +0200 +++ b/Preferences/ConfigurationPages/SecurityPage.py Sun Apr 03 16:33:37 2016 +0200 @@ -11,10 +11,6 @@ from PyQt5.QtCore import pyqtSlot from PyQt5.QtWidgets import QDialog -try: - from PyQt5.QtWebKit import QWebSettings -except ImportError: - QWebSettings = None from .ConfigurationPageBase import ConfigurationPageBase from .Ui_SecurityPage import Ui_SecurityPage @@ -38,6 +34,7 @@ self.setObjectName("SecurityPage") self.__configDlg = configDialog + self.__displayMode = None # set initial values self.savePasswordsCheckBox.setChecked( @@ -46,15 +43,41 @@ Preferences.getUser("UseMasterPassword")) self.masterPasswordButton.setEnabled( Preferences.getUser("UseMasterPassword")) - if QWebSettings and hasattr(QWebSettings, "DnsPrefetchEnabled"): - self.dnsPrefetchCheckBox.setChecked( - Preferences.getHelp("DnsPrefetchEnabled")) - else: - self.dnsPrefetchCheckBox.setEnabled(False) self.__newPassword = "" self.__oldUseMasterPassword = Preferences.getUser("UseMasterPassword") + def setMode(self, displayMode): + """ + Public method to perform mode dependent setups. + + @param displayMode mode of the configuration dialog + (ConfigurationWidget.DefaultMode, + ConfigurationWidget.HelpBrowserMode, + ConfigurationWidget.WebBrowserMode) + """ + from ..ConfigurationDialog import ConfigurationWidget + assert displayMode in ( + ConfigurationWidget.DefaultMode, + ConfigurationWidget.HelpBrowserMode, + ConfigurationWidget.WebBrowserMode + ) + + self.__displayMode = displayMode + if self.__displayMode == ConfigurationWidget.HelpBrowserMode: + try: + from PyQt5.QtWebKit import QWebSettings + if QWebSettings and \ + hasattr(QWebSettings, "DnsPrefetchEnabled"): + self.dnsPrefetchCheckBox.setChecked( + Preferences.getHelp("DnsPrefetchEnabled")) + except ImportError: + self.dnsPrefetchCheckBox.setEnabled(False) + else: + if self.__configDlg.isUsingWebEngine(): + self.dnsPrefetchCheckBox.setEnabled(False) + self.dnsGroup.hide() + def save(self): """ Public slot to save the Help Viewers configuration.
--- a/Preferences/ConfigurationPages/SecurityPage.ui Sun Apr 03 12:29:37 2016 +0200 +++ b/Preferences/ConfigurationPages/SecurityPage.ui Sun Apr 03 16:33:37 2016 +0200 @@ -91,7 +91,7 @@ </widget> </item> <item> - <widget class="QGroupBox" name="groupBox_3"> + <widget class="QGroupBox" name="dnsGroup"> <property name="title"> <string>DNS</string> </property>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Preferences/ConfigurationPages/WebBrowserAppearancePage.py Sun Apr 03 16:33:37 2016 +0200 @@ -0,0 +1,174 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2006 - 2016 Detlev Offenbach <detlev@die-offenbachs.de> +# + +""" +Module implementing the Web Browser configuration page. +""" + +from __future__ import unicode_literals + +from PyQt5.QtGui import QFont +from PyQt5.QtWidgets import QFontDialog + +from E5Gui.E5PathPicker import E5PathPickerModes + +from .ConfigurationPageBase import ConfigurationPageBase +from .Ui_WebBrowserAppearancePage import Ui_WebBrowserAppearancePage + +import Preferences + +try: + MonospacedFontsOption = QFontDialog.MonospacedFonts +except AttributeError: + MonospacedFontsOption = QFontDialog.FontDialogOptions(0x10) + + +class WebBrowserAppearancePage(ConfigurationPageBase, + Ui_WebBrowserAppearancePage): + """ + Class implementing the Web Browser Appearance page. + """ + def __init__(self): + """ + Constructor + """ + super(WebBrowserAppearancePage, self).__init__() + self.setupUi(self) + self.setObjectName("WebBrowserAppearancePage") + + self.styleSheetPicker.setMode(E5PathPickerModes.OpenFileMode) + self.styleSheetPicker.setFilters(self.tr( + "Cascading Style Sheets (*.css);;All files (*)")) + + self.__displayMode = None + + # set initial values + defaultFontSize = Preferences.getWebBrowser("DefaultFontSize") + fixedFontSize = Preferences.getWebBrowser("DefaultFixedFontSize") + self.defaultSizeSpinBox.setValue(defaultFontSize) + self.fixedSizeSpinBox.setValue(fixedFontSize) + self.minSizeSpinBox.setValue( + Preferences.getWebBrowser("MinimumFontSize")) + self.minLogicalSizeSpinBox.setValue( + Preferences.getWebBrowser("MinimumLogicalFontSize")) + + self.standardFontCombo.setCurrentFont( + QFont(Preferences.getWebBrowser("StandardFontFamily"), + defaultFontSize, QFont.Normal, False)) + self.fixedFontCombo.setCurrentFont( + QFont(Preferences.getWebBrowser("FixedFontFamily"), + fixedFontSize, QFont.Normal, False)) + self.serifFontCombo.setCurrentFont( + QFont(Preferences.getWebBrowser("SerifFontFamily"), + defaultFontSize, QFont.Normal, False)) + self.sansSerifFontCombo.setCurrentFont( + QFont(Preferences.getWebBrowser("SansSerifFontFamily"), + defaultFontSize, QFont.Normal, False)) + self.cursiveFontCombo.setCurrentFont( + QFont(Preferences.getWebBrowser("CursiveFontFamily"), + defaultFontSize, QFont.Normal, True)) + self.fantasyFontCombo.setCurrentFont( + QFont(Preferences.getWebBrowser("FantasyFontFamily"), + defaultFontSize, QFont.Normal, False)) + + self.initColour("SaveUrlColor", self.secureURLsColourButton, + Preferences.getWebBrowser) + + self.autoLoadImagesCheckBox.setChecked( + Preferences.getWebBrowser("AutoLoadImages")) + + self.styleSheetPicker.setText( + Preferences.getWebBrowser("UserStyleSheet")) + + self.tabsCloseButtonCheckBox.setChecked( + Preferences.getUI("SingleCloseButton")) + self.warnOnMultipleCloseCheckBox.setChecked( + Preferences.getWebBrowser("WarnOnMultipleClose")) + + def setMode(self, displayMode): + """ + Public method to perform mode dependent setups. + + @param displayMode mode of the configuration dialog + (ConfigurationWidget.DefaultMode, + ConfigurationWidget.HelpBrowserMode, + ConfigurationWidget.TrayStarterMode) + """ + from ..ConfigurationDialog import ConfigurationWidget + assert displayMode in ( + ConfigurationWidget.DefaultMode, + ConfigurationWidget.WebBrowserMode, + ) + + self.__displayMode = displayMode + if self.__displayMode != ConfigurationWidget.WebBrowserMode: + self.tabsGroupBox.hide() + + def save(self): + """ + Public slot to save the Help Viewers configuration. + """ + Preferences.setWebBrowser( + "StandardFontFamily", + self.standardFontCombo.currentFont().family()) + Preferences.setWebBrowser( + "FixedFontFamily", + self.fixedFontCombo.currentFont().family()) + Preferences.setWebBrowser( + "SerifFontFamily", + self.serifFontCombo.currentFont().family()) + Preferences.setWebBrowser( + "SansSerifFontFamily", + self.sansSerifFontCombo.currentFont().family()) + Preferences.setWebBrowser( + "CursiveFontFamily", + self.cursiveFontCombo.currentFont().family()) + Preferences.setWebBrowser( + "FantasyFontFamily", + self.fantasyFontCombo.currentFont().family()) + + Preferences.setWebBrowser( + "DefaultFontSize", + self.defaultSizeSpinBox.value()) + Preferences.setWebBrowser( + "DefaultFixedFontSize", + self.fixedSizeSpinBox.value()) + Preferences.setWebBrowser( + "MinimumFontSize", + self.minSizeSpinBox.value()) + Preferences.setWebBrowser( + "MinimumLogicalFontSize", + self.minLogicalSizeSpinBox.value()) + + Preferences.setWebBrowser( + "AutoLoadImages", + self.autoLoadImagesCheckBox.isChecked()) + + Preferences.setWebBrowser( + "UserStyleSheet", + self.styleSheetPicker.text()) + + self.saveColours(Preferences.setWebBrowser) + + from ..ConfigurationDialog import ConfigurationWidget + if self.__displayMode == ConfigurationWidget.WebBrowserMode: + Preferences.setUI( + "SingleCloseButton", + self.tabsCloseButtonCheckBox.isChecked()) + + Preferences.setWebBrowser( + "WarnOnMultipleClose", + self.warnOnMultipleCloseCheckBox.isChecked()) + + +def create(dlg): + """ + Module function to create the configuration page. + + @param dlg reference to the configuration dialog + @return reference to the instantiated page (ConfigurationPageBase) + """ + page = WebBrowserAppearancePage() + return page
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Preferences/ConfigurationPages/WebBrowserAppearancePage.ui Sun Apr 03 16:33:37 2016 +0200 @@ -0,0 +1,410 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>WebBrowserAppearancePage</class> + <widget class="QWidget" name="WebBrowserAppearancePage"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>499</width> + <height>782</height> + </rect> + </property> + <layout class="QVBoxLayout" name="verticalLayout_3"> + <item> + <widget class="QLabel" name="headerLabel"> + <property name="text"> + <string><b>Configure Web Browser appearance</b></string> + </property> + </widget> + </item> + <item> + <widget class="Line" name="line17"> + <property name="frameShape"> + <enum>QFrame::HLine</enum> + </property> + <property name="frameShadow"> + <enum>QFrame::Sunken</enum> + </property> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + </widget> + </item> + <item> + <widget class="QGroupBox" name="groupBox_4"> + <property name="title"> + <string>Fonts</string> + </property> + <layout class="QGridLayout" name="gridLayout_3"> + <item row="0" column="0"> + <widget class="QLabel" name="label_2"> + <property name="text"> + <string>Standard Font:</string> + </property> + </widget> + </item> + <item row="0" column="1"> + <widget class="QFontComboBox" name="standardFontCombo"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="toolTip"> + <string>Select the standard font</string> + </property> + </widget> + </item> + <item row="1" column="0"> + <widget class="QLabel" name="label_3"> + <property name="text"> + <string>Fixed Width Font</string> + </property> + </widget> + </item> + <item row="1" column="1"> + <widget class="QFontComboBox" name="fixedFontCombo"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="toolTip"> + <string>Select the fixed width font</string> + </property> + <property name="fontFilters"> + <set>QFontComboBox::MonospacedFonts</set> + </property> + </widget> + </item> + <item row="2" column="0"> + <widget class="QLabel" name="label_4"> + <property name="text"> + <string>Serif Font:</string> + </property> + </widget> + </item> + <item row="2" column="1"> + <widget class="QFontComboBox" name="serifFontCombo"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="toolTip"> + <string>Select the serif font</string> + </property> + </widget> + </item> + <item row="3" column="0"> + <widget class="QLabel" name="label_5"> + <property name="text"> + <string>Sans Serif Font:</string> + </property> + </widget> + </item> + <item row="3" column="1"> + <widget class="QFontComboBox" name="sansSerifFontCombo"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="toolTip"> + <string>Select the sans serif font</string> + </property> + </widget> + </item> + <item row="4" column="0"> + <widget class="QLabel" name="label_6"> + <property name="text"> + <string>Cursive Font:</string> + </property> + </widget> + </item> + <item row="4" column="1"> + <widget class="QFontComboBox" name="cursiveFontCombo"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="toolTip"> + <string>Select the cursive font</string> + </property> + </widget> + </item> + <item row="5" column="0"> + <widget class="QLabel" name="label_7"> + <property name="text"> + <string>Fantasy Font:</string> + </property> + </widget> + </item> + <item row="5" column="1"> + <widget class="QFontComboBox" name="fantasyFontCombo"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="toolTip"> + <string>Select the fantasy font</string> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QGroupBox" name="groupBox_6"> + <property name="title"> + <string>Font Sizes</string> + </property> + <layout class="QGridLayout" name="gridLayout_4"> + <item row="0" column="0"> + <widget class="QLabel" name="label_8"> + <property name="text"> + <string>Default Font Size:</string> + </property> + </widget> + </item> + <item row="0" column="1"> + <widget class="QSpinBox" name="defaultSizeSpinBox"> + <property name="alignment"> + <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> + </property> + <property name="minimum"> + <number>1</number> + </property> + </widget> + </item> + <item row="1" column="0"> + <widget class="QLabel" name="label_9"> + <property name="text"> + <string>Fixed Font Size:</string> + </property> + </widget> + </item> + <item row="1" column="1"> + <widget class="QSpinBox" name="fixedSizeSpinBox"> + <property name="alignment"> + <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> + </property> + <property name="minimum"> + <number>1</number> + </property> + </widget> + </item> + <item row="2" column="0"> + <widget class="QLabel" name="label_10"> + <property name="text"> + <string>Minimum Font Size:</string> + </property> + </widget> + </item> + <item row="2" column="1"> + <widget class="QSpinBox" name="minSizeSpinBox"> + <property name="alignment"> + <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> + </property> + <property name="minimum"> + <number>1</number> + </property> + </widget> + </item> + <item row="3" column="0"> + <widget class="QLabel" name="label_11"> + <property name="text"> + <string>Minimum Logical Font Size:</string> + </property> + </widget> + </item> + <item row="3" column="1"> + <widget class="QSpinBox" name="minLogicalSizeSpinBox"> + <property name="alignment"> + <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> + </property> + <property name="minimum"> + <number>1</number> + </property> + </widget> + </item> + <item row="3" column="2"> + <spacer name="horizontalSpacer_2"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>230</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QGroupBox" name="groupBox_3"> + <property name="title"> + <string>Colours</string> + </property> + <layout class="QGridLayout" name="gridLayout_2"> + <item row="0" column="0"> + <widget class="QLabel" name="textLabel1_3"> + <property name="text"> + <string>Background colour of secure URLs:</string> + </property> + </widget> + </item> + <item row="0" column="1"> + <widget class="QPushButton" name="secureURLsColourButton"> + <property name="minimumSize"> + <size> + <width>100</width> + <height>0</height> + </size> + </property> + <property name="toolTip"> + <string>Select the background colour for secure URLs.</string> + </property> + <property name="text"> + <string/> + </property> + </widget> + </item> + <item row="0" column="2"> + <spacer name="horizontalSpacer"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>141</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QGroupBox" name="groupBox"> + <property name="title"> + <string>Images</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <widget class="QCheckBox" name="autoLoadImagesCheckBox"> + <property name="toolTip"> + <string>Select to load images</string> + </property> + <property name="text"> + <string>Load images</string> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QGroupBox" name="groupBox_2"> + <property name="title"> + <string>Style Sheet</string> + </property> + <layout class="QHBoxLayout" name="horizontalLayout"> + <item> + <widget class="QLabel" name="label"> + <property name="text"> + <string>User Style Sheet:</string> + </property> + </widget> + </item> + <item> + <widget class="E5PathPicker" name="styleSheetPicker" native="true"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="focusPolicy"> + <enum>Qt::StrongFocus</enum> + </property> + <property name="toolTip"> + <string>Enter the file name of a user style sheet</string> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QGroupBox" name="tabsGroupBox"> + <property name="title"> + <string>Tabs</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout_2"> + <item> + <widget class="QCheckBox" name="tabsCloseButtonCheckBox"> + <property name="text"> + <string>Show only one close button instead of one for each tab</string> + </property> + </widget> + </item> + <item> + <widget class="QCheckBox" name="warnOnMultipleCloseCheckBox"> + <property name="toolTip"> + <string>Select to issue a warning, if multiple tabs are about to be closed</string> + </property> + <property name="text"> + <string>Warn, if multiple tabs are about to be closed</string> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <spacer> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>479</width> + <height>121</height> + </size> + </property> + </spacer> + </item> + </layout> + </widget> + <customwidgets> + <customwidget> + <class>E5PathPicker</class> + <extends>QWidget</extends> + <header>E5Gui/E5PathPicker.h</header> + <container>1</container> + </customwidget> + </customwidgets> + <tabstops> + <tabstop>secureURLsColourButton</tabstop> + <tabstop>autoLoadImagesCheckBox</tabstop> + <tabstop>styleSheetPicker</tabstop> + <tabstop>tabsCloseButtonCheckBox</tabstop> + <tabstop>warnOnMultipleCloseCheckBox</tabstop> + </tabstops> + <resources/> + <connections/> +</ui>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Preferences/ConfigurationPages/WebBrowserPage.py Sun Apr 03 16:33:37 2016 +0200 @@ -0,0 +1,300 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2016 Detlev Offenbach <detlev@die-offenbachs.de> +# + +""" +Module implementing the Web Browser configuration page. +""" + +from PyQt5.QtCore import pyqtSlot, QLocale + +from .ConfigurationPageBase import ConfigurationPageBase +from .Ui_WebBrowserPage import Ui_WebBrowserPage + +import Preferences + + +class WebBrowserPage(ConfigurationPageBase, Ui_WebBrowserPage): + """ + Class documentation goes here. + """ + def __init__(self, configDialog): + """ + Constructor + + @param configDialog reference to the configuration dialog + (ConfigurationDialog) + """ + super(WebBrowserPage, self).__init__() + self.setupUi(self) + self.setObjectName("WebBrowserPage") + + self.__configDlg = configDialog + mw = configDialog.parent().parent() + if hasattr(mw, "helpWindow") and mw.helpWindow is not None: + # IDE + self.__browserWindow = mw.helpWindow + elif hasattr(mw, "currentBrowser"): + # Web Browser + self.__browserWindow = mw + else: + self.__browserWindow = None + self.setCurrentPageButton.setEnabled(self.__browserWindow is not None) + + defaultSchemes = ["file://", "http://", "https://"] + self.defaultSchemeCombo.addItems(defaultSchemes) + + # set initial values + self.singleHelpWindowCheckBox.setChecked( + Preferences.getWebBrowser("SingleWebBrowserWindow")) + self.saveGeometryCheckBox.setChecked( + Preferences.getWebBrowser("SaveGeometry")) + self.webSuggestionsCheckBox.setChecked( + Preferences.getWebBrowser("WebSearchSuggestions")) + self.showTabPreviews.setChecked( + Preferences.getWebBrowser("ShowPreview")) + self.errorPageCheckBox.setChecked( + Preferences.getWebBrowser("ErrorPageEnabled")) + self.scrollingCheckBox.setChecked( + Preferences.getWebBrowser("ScrollAnimatorEnabled")) + self.fullscreenCheckBox.setChecked( + Preferences.getWebBrowser("FullScreenSupportEnabled")) + + self.javaScriptGroup.setChecked( + Preferences.getWebBrowser("JavaScriptEnabled")) + self.jsOpenWindowsCheckBox.setChecked( + Preferences.getWebBrowser("JavaScriptCanOpenWindows")) + # TODO: Qt 5.7? +## self.jsCloseWindowsCheckBox.setChecked( +## Preferences.getWebBrowser("JavaScriptCanCloseWindows")) + self.jsClipboardCheckBox.setChecked( + Preferences.getWebBrowser("JavaScriptCanAccessClipboard")) + self.pluginsCheckBox.setChecked( + Preferences.getWebBrowser("PluginsEnabled")) + self.doNotTrackCheckBox.setChecked( + Preferences.getWebBrowser("DoNotTrack")) + self.sendRefererCheckBox.setChecked( + Preferences.getWebBrowser("SendReferer")) + + self.diskCacheCheckBox.setChecked( + Preferences.getWebBrowser("DiskCacheEnabled")) + self.cacheSizeSpinBox.setValue( + Preferences.getWebBrowser("DiskCacheSize")) + + self.startupCombo.setCurrentIndex( + Preferences.getWebBrowser("StartupBehavior")) + self.homePageEdit.setText( + Preferences.getWebBrowser("HomePage")) + + self.defaultSchemeCombo.setCurrentIndex( + self.defaultSchemeCombo.findText( + Preferences.getWebBrowser("DefaultScheme"))) + + historyLimit = Preferences.getWebBrowser("HistoryLimit") + idx = 0 + if historyLimit == 1: + idx = 0 + elif historyLimit == 7: + idx = 1 + elif historyLimit == 14: + idx = 2 + elif historyLimit == 30: + idx = 3 + elif historyLimit == 365: + idx = 4 + elif historyLimit == -1: + idx = 5 + elif historyLimit == -2: + idx = 6 + else: + idx = 5 + self.expireHistory.setCurrentIndex(idx) + + for language in range(2, QLocale.LastLanguage + 1): + countries = [l.country() for l in QLocale.matchingLocales( + language, QLocale.AnyScript, QLocale.AnyCountry)] + if len(countries) > 0: + self.languageCombo.addItem( + QLocale.languageToString(language), language) + self.languageCombo.model().sort(0) + self.languageCombo.insertSeparator(0) + self.languageCombo.insertItem(0, QLocale.languageToString(0), 0) + index = self.languageCombo.findData( + Preferences.getWebBrowser("SearchLanguage")) + if index > -1: + self.languageCombo.setCurrentIndex(index) + + self.spatialCheckBox.setChecked( + Preferences.getWebBrowser("SpatialNavigationEnabled")) + self.linksInFocusChainCheckBox.setChecked( + Preferences.getWebBrowser("LinksIncludedInFocusChain")) + self.xssAuditingCheckBox.setChecked( + Preferences.getWebBrowser("XSSAuditingEnabled")) + + self.webInspectorGroup.setChecked( + Preferences.getWebBrowser("WebInspectorEnabled")) + self.webInspectorPortSpinBox.setValue( + Preferences.getWebBrowser("WebInspectorPort")) + + # TODO: Qt 5.7? + # Hide entries not yet supported + self.jsCloseWindowsCheckBox.hide() + + def save(self): + """ + Public slot to save the Help Viewers configuration. + """ + Preferences.setWebBrowser( + "SingleWebBrowserWindow", + self.singleHelpWindowCheckBox.isChecked()) + Preferences.setWebBrowser( + "SaveGeometry", + self.saveGeometryCheckBox.isChecked()) + Preferences.setWebBrowser( + "WebSearchSuggestions", + self.webSuggestionsCheckBox.isChecked()) + Preferences.setWebBrowser( + "ShowPreview", + self.showTabPreviews.isChecked()) + Preferences.setWebBrowser( + "ErrorPageEnabled", + self.errorPageCheckBox.isChecked()) + Preferences.setWebBrowser( + "ScrollAnimatorEnabled", + self.scrollingCheckBox.isChecked()) + Preferences.setWebBrowser( + "FullScreenSupportEnabled", + self.fullscreenCheckBox.isChecked()) + + Preferences.setWebBrowser( + "JavaScriptEnabled", + self.javaScriptGroup.isChecked()) + Preferences.setWebBrowser( + "JavaScriptCanOpenWindows", + self.jsOpenWindowsCheckBox.isChecked()) + # TODO: Qt 5.7? +## Preferences.setWebBrowser( +## "JavaScriptCanCloseWindows", +## self.jsCloseWindowsCheckBox.isChecked()) + Preferences.setWebBrowser( + "JavaScriptCanAccessClipboard", + self.jsClipboardCheckBox.isChecked()) + Preferences.setWebBrowser( + "PluginsEnabled", + self.pluginsCheckBox.isChecked()) + Preferences.setWebBrowser( + "DoNotTrack", + self.doNotTrackCheckBox.isChecked()) + Preferences.setWebBrowser( + "SendReferer", + self.sendRefererCheckBox.isChecked()) + + Preferences.setWebBrowser( + "DiskCacheEnabled", + self.diskCacheCheckBox.isChecked()) + Preferences.setWebBrowser( + "DiskCacheSize", + self.cacheSizeSpinBox.value()) + + Preferences.setWebBrowser( + "StartupBehavior", + self.startupCombo.currentIndex()) + Preferences.setWebBrowser( + "HomePage", + self.homePageEdit.text()) + + Preferences.setWebBrowser( + "DefaultScheme", + self.defaultSchemeCombo.currentText()) + + idx = self.expireHistory.currentIndex() + if idx == 0: + historyLimit = 1 + elif idx == 1: + historyLimit = 7 + elif idx == 2: + historyLimit = 14 + elif idx == 3: + historyLimit = 30 + elif idx == 4: + historyLimit = 365 + elif idx == 5: + historyLimit = -1 + elif idx == 6: + historyLimit = -2 + Preferences.setWebBrowser("HistoryLimit", historyLimit) + + languageIndex = self.languageCombo.currentIndex() + if languageIndex > -1: + language = self.languageCombo.itemData(languageIndex) + else: + # fall back to system default + language = QLocale.system().language() + Preferences.setWebBrowser("SearchLanguage", language) + + Preferences.setWebBrowser( + "SpatialNavigationEnabled", + self.spatialCheckBox.isChecked()) + Preferences.setWebBrowser( + "LinksIncludedInFocusChain", + self.linksInFocusChainCheckBox.isChecked()) + Preferences.setWebBrowser( + "XSSAuditingEnabled", + self.xssAuditingCheckBox.isChecked()) + + Preferences.setWebBrowser( + "WebInspectorEnabled", + self.webInspectorGroup.isChecked()) + Preferences.setWebBrowser( + "WebInspectorPort", + self.webInspectorPortSpinBox.value()) + + @pyqtSlot() + def on_setCurrentPageButton_clicked(self): + """ + Private slot to set the current page as the home page. + """ + url = self.__browserWindow.currentBrowser().url() + self.homePageEdit.setText(bytes(url.toEncoded()).decode()) + + @pyqtSlot() + def on_defaultHomeButton_clicked(self): + """ + Private slot to set the default home page. + """ + self.homePageEdit.setText(Preferences.Prefs.helpDefaults["HomePage"]) + + @pyqtSlot(int) + def on_startupCombo_currentIndexChanged(self, index): + """ + Private slot to enable elements depending on the selected startup + entry. + + @param index index of the selected entry (integer) + """ + enable = index == 0 + self.homePageLabel.setEnabled(enable) + self.homePageEdit.setEnabled(enable) + self.defaultHomeButton.setEnabled(enable) + self.setCurrentPageButton.setEnabled(enable) + + @pyqtSlot() + def on_refererWhitelistButton_clicked(self): + """ + Private slot to edit the referer whitelist. + """ + from WebBrowser.Network.SendRefererWhitelistDialog import \ + SendRefererWhitelistDialog + SendRefererWhitelistDialog(self).exec_() + + +def create(dlg): + """ + Module function to create the configuration page. + + @param dlg reference to the configuration dialog + @return reference to the instantiated page (ConfigurationPageBase) + """ + page = WebBrowserPage(dlg) + return page
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Preferences/ConfigurationPages/WebBrowserPage.ui Sun Apr 03 16:33:37 2016 +0200 @@ -0,0 +1,681 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>WebBrowserPage</class> + <widget class="QWidget" name="WebBrowserPage"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>616</width> + <height>1329</height> + </rect> + </property> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <widget class="QLabel" name="headerLabel"> + <property name="text"> + <string><b>Configure Web Browser</b></string> + </property> + </widget> + </item> + <item> + <widget class="Line" name="line17"> + <property name="frameShape"> + <enum>QFrame::HLine</enum> + </property> + <property name="frameShadow"> + <enum>QFrame::Sunken</enum> + </property> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + </widget> + </item> + <item> + <widget class="QGroupBox" name="groupBox_4"> + <property name="title"> + <string>General</string> + </property> + <layout class="QGridLayout" name="gridLayout_5"> + <item row="0" column="0"> + <widget class="QCheckBox" name="singleHelpWindowCheckBox"> + <property name="toolTip"> + <string>Select to use a single web browser window only</string> + </property> + <property name="text"> + <string>Use single web browser window</string> + </property> + </widget> + </item> + <item row="0" column="1"> + <widget class="QCheckBox" name="webSuggestionsCheckBox"> + <property name="toolTip"> + <string>Select to enable suggestions for web searches</string> + </property> + <property name="text"> + <string>Show suggestions for web searches</string> + </property> + </widget> + </item> + <item row="1" column="0"> + <widget class="QCheckBox" name="saveGeometryCheckBox"> + <property name="toolTip"> + <string>Select to save the window size and position</string> + </property> + <property name="text"> + <string>Save size and position upon exit</string> + </property> + </widget> + </item> + <item row="1" column="1"> + <widget class="QCheckBox" name="showTabPreviews"> + <property name="toolTip"> + <string>Select to show a page preview when the mouse hovers over the tab</string> + </property> + <property name="text"> + <string>Show preview when hovering tab</string> + </property> + </widget> + </item> + <item row="2" column="0"> + <widget class="QCheckBox" name="errorPageCheckBox"> + <property name="toolTip"> + <string>Select to enable displaying the built-in Chromium error pages.</string> + </property> + <property name="text"> + <string>Use built-in Chromium error page</string> + </property> + </widget> + </item> + <item row="2" column="1"> + <widget class="QCheckBox" name="scrollingCheckBox"> + <property name="toolTip"> + <string>Select to activate animated scrolling</string> + </property> + <property name="text"> + <string>Enable animated scrolling</string> + </property> + </widget> + </item> + <item row="3" column="0"> + <widget class="QCheckBox" name="fullscreenCheckBox"> + <property name="toolTip"> + <string>Select to enable fullscreen support</string> + </property> + <property name="text"> + <string>Enable Fullscreen Support</string> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QGroupBox" name="startupGroupBox"> + <property name="title"> + <string>Startup</string> + </property> + <layout class="QGridLayout" name="gridLayout_3"> + <item row="0" column="0"> + <widget class="QLabel" name="label"> + <property name="text"> + <string>On startup:</string> + </property> + </widget> + </item> + <item row="0" column="1" colspan="3"> + <widget class="QComboBox" name="startupCombo"> + <property name="toolTip"> + <string>Select the startup behavior</string> + </property> + <item> + <property name="text"> + <string>Show Home Page</string> + </property> + </item> + <item> + <property name="text"> + <string>Show Speed Dial</string> + </property> + </item> + <item> + <property name="text"> + <string>Show Empty Page</string> + </property> + </item> + </widget> + </item> + <item row="1" column="0"> + <widget class="QLabel" name="homePageLabel"> + <property name="text"> + <string>Home Page:</string> + </property> + </widget> + </item> + <item row="1" column="1" colspan="3"> + <widget class="QLineEdit" name="homePageEdit"> + <property name="toolTip"> + <string>Enter the desired home page</string> + </property> + </widget> + </item> + <item row="2" column="1"> + <widget class="QPushButton" name="setCurrentPageButton"> + <property name="toolTip"> + <string>Press to set the current page as the home page</string> + </property> + <property name="text"> + <string>Set to current page</string> + </property> + </widget> + </item> + <item row="2" column="2"> + <widget class="QPushButton" name="defaultHomeButton"> + <property name="toolTip"> + <string>Press to set the default home page</string> + </property> + <property name="text"> + <string>Set to default home page</string> + </property> + </widget> + </item> + <item row="2" column="3"> + <spacer name="horizontalSpacer_2"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>160</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QGroupBox" name="groupBox_2"> + <property name="title"> + <string>Scheme</string> + </property> + <layout class="QGridLayout" name="gridLayout_4"> + <item row="0" column="0"> + <widget class="QLabel" name="label_3"> + <property name="text"> + <string>Default Scheme:</string> + </property> + </widget> + </item> + <item row="0" column="1"> + <widget class="QComboBox" name="defaultSchemeCombo"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="toolTip"> + <string>Select the default scheme</string> + </property> + <property name="whatsThis"> + <string><b>Default Scheme</b><p>Select the default scheme. This scheme is prepended to URLs, that don't contain one.</p></string> + </property> + <property name="editable"> + <bool>false</bool> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QGroupBox" name="privacyGroup"> + <property name="title"> + <string>Privacy</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout_4"> + <item> + <widget class="QGroupBox" name="javaScriptGroup"> + <property name="toolTip"> + <string>Select to enable JavaScript</string> + </property> + <property name="title"> + <string>Enable JavaScript</string> + </property> + <property name="checkable"> + <bool>true</bool> + </property> + <property name="checked"> + <bool>false</bool> + </property> + <layout class="QGridLayout" name="gridLayout"> + <item row="0" column="0"> + <widget class="QCheckBox" name="jsOpenWindowsCheckBox"> + <property name="toolTip"> + <string>Select to allow JavaScript to open windows</string> + </property> + <property name="text"> + <string>JavaScript can open windows</string> + </property> + </widget> + </item> + <item row="0" column="1"> + <widget class="QCheckBox" name="jsCloseWindowsCheckBox"> + <property name="toolTip"> + <string>Select to allow JavaScript to close windows</string> + </property> + <property name="text"> + <string>JavaScript can close windows</string> + </property> + </widget> + </item> + <item row="1" column="0"> + <widget class="QCheckBox" name="jsClipboardCheckBox"> + <property name="toolTip"> + <string>Select to allow JavaScript to access the clipboard</string> + </property> + <property name="text"> + <string>JavaScript can access clipboard</string> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QCheckBox" name="pluginsCheckBox"> + <property name="toolTip"> + <string>Select to enable plugins in web pages</string> + </property> + <property name="text"> + <string>Enable Plug-ins</string> + </property> + </widget> + </item> + <item> + <widget class="Line" name="line"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + </widget> + </item> + <item> + <widget class="QCheckBox" name="doNotTrackCheckBox"> + <property name="toolTip"> + <string>Select to enabled the "Do Not Track" feature</string> + </property> + <property name="text"> + <string>Tell web sites I do not want to be tracked</string> + </property> + </widget> + </item> + <item> + <layout class="QHBoxLayout" name="horizontalLayout_4"> + <item> + <widget class="QCheckBox" name="sendRefererCheckBox"> + <property name="toolTip"> + <string>Select to send referer headers to the server</string> + </property> + <property name="text"> + <string>Send Referer header to servers</string> + </property> + </widget> + </item> + <item> + <spacer name="horizontalSpacer_6"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QPushButton" name="refererWhitelistButton"> + <property name="toolTip"> + <string>Press to edit the list of whitelisted hosts</string> + </property> + <property name="text"> + <string>Edit Referer Whitelist ...</string> + </property> + </widget> + </item> + </layout> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QGroupBox" name="groupBox_6"> + <property name="title"> + <string>Security</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout_2"> + <item> + <widget class="QCheckBox" name="xssAuditingCheckBox"> + <property name="toolTip"> + <string>Select to enable XSS auditing</string> + </property> + <property name="whatsThis"> + <string><b>Enable XSS Auditing</b> +<p>This selects whether load requests should be monitored for cross-site scripting attempts. Suspicious scripts will be blocked. These will be reported in the JavaScript console. Enabling this feature might have an impact on performance.</p></string> + </property> + <property name="text"> + <string>Enable XSS Auditing</string> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QGroupBox" name="groupBox"> + <property name="title"> + <string>History</string> + </property> + <layout class="QHBoxLayout" name="horizontalLayout"> + <item> + <widget class="QLabel" name="label_4"> + <property name="text"> + <string>Remove history items:</string> + </property> + </widget> + </item> + <item> + <widget class="QComboBox" name="expireHistory"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="toolTip"> + <string>Select the period for expiration of history entries</string> + </property> + <item> + <property name="text"> + <string>After one day</string> + </property> + </item> + <item> + <property name="text"> + <string>After one week</string> + </property> + </item> + <item> + <property name="text"> + <string>After two weeks</string> + </property> + </item> + <item> + <property name="text"> + <string>After one month</string> + </property> + </item> + <item> + <property name="text"> + <string>After one year</string> + </property> + </item> + <item> + <property name="text"> + <string>Manually</string> + </property> + </item> + <item> + <property name="text"> + <string>On application exit</string> + </property> + </item> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QGroupBox" name="cacheGroup"> + <property name="title"> + <string>Browser Cache</string> + </property> + <layout class="QGridLayout" name="gridLayout_2"> + <item row="0" column="0" colspan="3"> + <widget class="QCheckBox" name="diskCacheCheckBox"> + <property name="text"> + <string>Enable disk cache</string> + </property> + </widget> + </item> + <item row="1" column="0"> + <widget class="QLabel" name="label_7"> + <property name="text"> + <string>Cache size:</string> + </property> + </widget> + </item> + <item row="1" column="1"> + <widget class="QSpinBox" name="cacheSizeSpinBox"> + <property name="toolTip"> + <string>Enter the maximum size of the disk cache</string> + </property> + <property name="alignment"> + <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> + </property> + <property name="suffix"> + <string> MB</string> + </property> + <property name="minimum"> + <number>1</number> + </property> + <property name="maximum"> + <number>999</number> + </property> + </widget> + </item> + <item row="1" column="2"> + <spacer name="horizontalSpacer"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>410</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QGroupBox" name="groupBox_5"> + <property name="title"> + <string>Web Search</string> + </property> + <layout class="QHBoxLayout" name="horizontalLayout_2"> + <item> + <widget class="QLabel" name="label_5"> + <property name="text"> + <string>Language:</string> + </property> + </widget> + </item> + <item> + <widget class="QComboBox" name="languageCombo"> + <property name="toolTip"> + <string>Select the language to be used for web searches</string> + </property> + <property name="sizeAdjustPolicy"> + <enum>QComboBox::AdjustToContents</enum> + </property> + </widget> + </item> + <item> + <spacer name="horizontalSpacer_3"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>450</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QGroupBox" name="groupBox_3"> + <property name="title"> + <string>Navigation</string> + </property> + <layout class="QGridLayout" name="gridLayout_6"> + <item row="0" column="0"> + <widget class="QCheckBox" name="spatialCheckBox"> + <property name="toolTip"> + <string>Select to enable the spatial navigation feature</string> + </property> + <property name="whatsThis"> + <string><b>Enable Spatial Navigation</b> +<p>This enables or disables the Spatial Navigation feature, which consists in the ability to navigate between focusable elements in a Web page, such as hyperlinks and form controls, by using Left, Right, Up and Down arrow keys. For example, if a user presses the Right key, heuristics determine whether there is an element he might be trying to reach towards the right and which element he probably wants.</p></string> + </property> + <property name="text"> + <string>Enable Spatial Navigation</string> + </property> + </widget> + </item> + <item row="0" column="1"> + <widget class="QCheckBox" name="linksInFocusChainCheckBox"> + <property name="toolTip"> + <string>Select to include links in focus chain</string> + </property> + <property name="whatsThis"> + <string><b>Include Links in Focus Chain</b> +<p>This selects whether hyperlinks should be included in the keyboard focus chain.</p></string> + </property> + <property name="text"> + <string>Include Links in Focus Chain</string> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QGroupBox" name="webInspectorGroup"> + <property name="toolTip"> + <string>Select to enable the Web Inspector tool</string> + </property> + <property name="title"> + <string>Enable Web Development (Web Inspector)</string> + </property> + <property name="checkable"> + <bool>true</bool> + </property> + <property name="checked"> + <bool>false</bool> + </property> + <layout class="QGridLayout" name="gridLayout_8"> + <item row="0" column="0"> + <widget class="QLabel" name="label_2"> + <property name="text"> + <string>Web Inspector Port:</string> + </property> + </widget> + </item> + <item row="0" column="1"> + <widget class="QSpinBox" name="webInspectorPortSpinBox"> + <property name="toolTip"> + <string>Enter the port to be used by the web inspector</string> + </property> + <property name="alignment"> + <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> + </property> + <property name="minimum"> + <number>1025</number> + </property> + <property name="maximum"> + <number>65535</number> + </property> + </widget> + </item> + <item row="0" column="2"> + <spacer name="horizontalSpacer_4"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>372</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item row="1" column="0" colspan="3"> + <widget class="QLabel" name="label_6"> + <property name="text"> + <string><font color="red"><b>Note:</b> Web Inspector settings are activated after a restart of the application.</font></string> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <spacer> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>479</width> + <height>121</height> + </size> + </property> + </spacer> + </item> + </layout> + </widget> + <tabstops> + <tabstop>singleHelpWindowCheckBox</tabstop> + <tabstop>webSuggestionsCheckBox</tabstop> + <tabstop>saveGeometryCheckBox</tabstop> + <tabstop>showTabPreviews</tabstop> + <tabstop>errorPageCheckBox</tabstop> + <tabstop>scrollingCheckBox</tabstop> + <tabstop>fullscreenCheckBox</tabstop> + <tabstop>startupCombo</tabstop> + <tabstop>homePageEdit</tabstop> + <tabstop>setCurrentPageButton</tabstop> + <tabstop>defaultHomeButton</tabstop> + <tabstop>defaultSchemeCombo</tabstop> + <tabstop>javaScriptGroup</tabstop> + <tabstop>jsOpenWindowsCheckBox</tabstop> + <tabstop>jsCloseWindowsCheckBox</tabstop> + <tabstop>jsClipboardCheckBox</tabstop> + <tabstop>pluginsCheckBox</tabstop> + <tabstop>doNotTrackCheckBox</tabstop> + <tabstop>sendRefererCheckBox</tabstop> + <tabstop>refererWhitelistButton</tabstop> + <tabstop>xssAuditingCheckBox</tabstop> + <tabstop>expireHistory</tabstop> + <tabstop>diskCacheCheckBox</tabstop> + <tabstop>cacheSizeSpinBox</tabstop> + <tabstop>languageCombo</tabstop> + <tabstop>spatialCheckBox</tabstop> + <tabstop>linksInFocusChainCheckBox</tabstop> + <tabstop>webInspectorGroup</tabstop> + <tabstop>webInspectorPortSpinBox</tabstop> + </tabstops> + <resources/> + <connections/> +</ui>
--- a/Preferences/Shortcuts.py Sun Apr 03 12:29:37 2016 +0200 +++ b/Preferences/Shortcuts.py Sun Apr 03 16:33:37 2016 +0200 @@ -37,7 +37,8 @@ act.setAlternateShortcut(QKeySequence(accel), removeEmpty=True) -def readShortcuts(prefClass=Prefs, helpViewer=None, pluginName=None): +def readShortcuts(prefClass=Prefs, helpViewer=None, pluginName=None, + helpViewerCategory=""): """ Module function to read the keyboard shortcuts for the defined QActions. @@ -45,6 +46,7 @@ @keyparam helpViewer reference to the help window object @keyparam pluginName name of the plugin for which to load shortcuts (string) + @keyparam helpViewerCategory name of the help viewer category (string) """ if helpViewer is None and pluginName is None: for act in e5App().getObject("Project").getActions(): @@ -92,8 +94,10 @@ __readShortcut(act, category, prefClass) if helpViewer is not None: + if not helpViewerCategory: + helpViewerCategory = "HelpViewer" for act in helpViewer.getActions(): - __readShortcut(act, "HelpViewer", prefClass) + __readShortcut(act, helpViewerCategory, prefClass) if pluginName is not None: try:
--- a/Preferences/__init__.py Sun Apr 03 12:29:37 2016 +0200 +++ b/Preferences/__init__.py Sun Apr 03 16:33:37 2016 +0200 @@ -37,6 +37,10 @@ from PyQt5.QtWebKit import QWebSettings except ImportError: QWebSettings = None +try: + from PyQt5.QtWebEngineWidgets import QWebEngineSettings +except ImportError: + QWebEngineSettings = None from PyQt5.Qsci import QsciScintilla, QsciLexerPython from E5Gui import E5FileDialog @@ -51,9 +55,12 @@ ResourcesBrowserFlag, TranslationsBrowserFlag, InterfacesBrowserFlag, \ OthersBrowserFlag, AllBrowsersFlag -from Helpviewer.FlashCookieManager.FlashCookieUtilities import \ - flashDataPathForOS - +try: + from Helpviewer.FlashCookieManager.FlashCookieUtilities import \ + flashDataPathForOS +except ImportError: + from WebBrowser.FlashCookieManager.FlashCookieUtilities import \ + flashDataPathForOS class Prefs(object): """ @@ -705,6 +712,7 @@ "RightMargin": 1.0, "TopMargin": 1.0, "BottomMargin": 1.0, + "Resolution": 150, # printer resolution in DPI } # defaults for the project settings @@ -835,7 +843,7 @@ "StartupBehavior": 1, # show speed dial "HomePage": "eric:home", "HistoryLimit": 30, - "DefaultScheme": "file://", + "DefaultScheme": "https://", "OfflineStorageDatabaseQuota": 50, # 50 MB "UserAgent": "", "ShowPreview": True, @@ -998,6 +1006,148 @@ cls.webSettingsIntitialized = True webSettingsIntitialized = False + + # defaults for the web browser settings + webBrowserDefaults = { + "SingleWebBrowserWindow": True, + "SaveGeometry": True, + "WebBrowserState": QByteArray(), + "StartupBehavior": 1, # show speed dial + "HomePage": "eric:home", + "WarnOnMultipleClose": True, + "DefaultScheme": "https://", + "UserStyleSheet": "", + "ZoomValuesDB": "{}", # empty JSON dictionary + "HistoryLimit": 30, + "WebSearchSuggestions": True, + "WebSearchEngine": "DuckDuckGo", + "WebSearchKeywords": [], # array of two tuples (keyword, + # search engine name) + "SearchLanguage": QLocale().language(), + "RssFeeds": [], + "ShowPreview": True, + "WebInspectorPort": 42024, + "WebInspectorEnabled": False, + "DiskCacheEnabled": True, + "DiskCacheSize": 50, # 50 MB + "SslExceptionsDB": "{}", # empty JSON dictionary + "DoNotTrack": False, + "SendReferer": True, + "SendRefererWhitelist": ["qt-apps.org", "kde-apps.org"], + "AcceptCookies": 2, # CookieJar.AcceptOnlyFromSitesNavigatedTo + "KeepCookiesUntil": 0, # CookieJar.KeepUntilExpire + "FilterTrackingCookies": True, + "SaveUrlColor": QColor(184, 248, 169), + "UserAgent": "", + # Grease Monkey + "GreaseMonkeyDisabledScripts": [], + # Downloads + "DownloadManagerRemovePolicy": 0, # never delete downloads + "DownloadManagerSize": QSize(400, 300), + "DownloadManagerPosition": QPoint(), + "DownloadManagerDownloads": [], + # Sync + "SyncEnabled": False, + "SyncBookmarks": True, + "SyncHistory": True, + "SyncPasswords": False, + "SyncUserAgents": True, + "SyncSpeedDial": True, + "SyncEncryptData": False, + "SyncEncryptionKey": "", + "SyncEncryptionKeyLength": 32, # 16, 24 or 32 + "SyncEncryptPasswordsOnly": False, + "SyncType": 0, + "SyncFtpServer": "", + "SyncFtpUser": "", + "SyncFtpPassword": "", + "SyncFtpPath": "", + "SyncFtpPort": 21, + "SyncFtpIdleTimeout": 30, + "SyncDirectoryPath": "", + # AdBlock + "AdBlockEnabled": False, + "AdBlockSubscriptions": [], + "AdBlockUpdatePeriod": 1, + "AdBlockExceptions": [], + "AdBlockUseLimitedEasyList": True, + # Flash Cookie Manager: identical to helpDefaults + # PIM: identical to helpDefaults + # VirusTotal: identical to helpDefaults + } + if QWebEngineSettings: + webBrowserDefaults["HelpViewerType"] = 1 # eric browser + else: + webBrowserDefaults["HelpViewerType"] = 2 # Qt Assistant + + @classmethod + def initWebEngineSettingsDefaults(cls): + """ + Class method to initialize the web engine settings related defaults. + """ + if QWebEngineSettings is None: + return + + webEngineSettings = QWebEngineSettings.globalSettings() + cls.webBrowserDefaults.update({ + "StandardFontFamily": webEngineSettings.fontFamily( + QWebEngineSettings.StandardFont), + "FixedFontFamily": webEngineSettings.fontFamily( + QWebEngineSettings.FixedFont), + "SerifFontFamily": webEngineSettings.fontFamily( + QWebEngineSettings.StandardFont), + "SansSerifFontFamily": webEngineSettings.fontFamily( + QWebEngineSettings.SansSerifFont), + "CursiveFontFamily": webEngineSettings.fontFamily( + QWebEngineSettings.CursiveFont), + "FantasyFontFamily": webEngineSettings.fontFamily( + QWebEngineSettings.FantasyFont), + "DefaultFontSize": webEngineSettings.fontSize( + QWebEngineSettings.DefaultFontSize), + "DefaultFixedFontSize": webEngineSettings.fontSize( + QWebEngineSettings.DefaultFixedFontSize), + "MinimumFontSize": webEngineSettings.fontSize( + QWebEngineSettings.MinimumFontSize), + "MinimumLogicalFontSize": webEngineSettings.fontSize( + QWebEngineSettings.MinimumLogicalFontSize), + + "AutoLoadImages": webEngineSettings.testAttribute( + QWebEngineSettings.AutoLoadImages), + "JavaScriptEnabled": webEngineSettings.testAttribute( + QWebEngineSettings.JavascriptEnabled), + "JavaScriptCanOpenWindows": webEngineSettings.testAttribute( + QWebEngineSettings.JavascriptCanOpenWindows), + # TODO: Qt 5.7? +## "JavaScriptCanCloseWindows": webEngineSettings.testAttribute( +## QWebEngineSettings.JavascriptCanCloseWindows), + "JavaScriptCanAccessClipboard": webEngineSettings.testAttribute( + QWebEngineSettings.JavascriptCanAccessClipboard), + "PluginsEnabled": webEngineSettings.testAttribute( + QWebEngineSettings.PluginsEnabled), + "LocalStorageEnabled": webEngineSettings.testAttribute( + QWebEngineSettings.LocalStorageEnabled), + "DefaultTextEncoding": webEngineSettings.defaultTextEncoding(), + "SpatialNavigationEnabled": webEngineSettings.testAttribute( + QWebEngineSettings.SpatialNavigationEnabled), + "LinksIncludedInFocusChain": webEngineSettings.testAttribute( + QWebEngineSettings.LinksIncludedInFocusChain), + "LocalContentCanAccessRemoteUrls": webEngineSettings.testAttribute( + QWebEngineSettings.LocalContentCanAccessRemoteUrls), + "LocalContentCanAccessFileUrls": webEngineSettings.testAttribute( + QWebEngineSettings.LocalContentCanAccessFileUrls), + "XSSAuditingEnabled": webEngineSettings.testAttribute( + QWebEngineSettings.XSSAuditingEnabled), + "ScrollAnimatorEnabled": webEngineSettings.testAttribute( + QWebEngineSettings.ScrollAnimatorEnabled), + "ErrorPageEnabled": webEngineSettings.testAttribute( + QWebEngineSettings.ErrorPageEnabled), + "FullScreenSupportEnabled": webEngineSettings.testAttribute( + QWebEngineSettings.FullScreenSupportEnabled), + }) + + cls.webEngineSettingsIntitialized = True + + webEngineSettingsIntitialized = False # defaults for system settings sysDefaults = { @@ -1128,10 +1278,12 @@ geometryDefaults = { "HelpViewerGeometry": QByteArray(), "HelpInspectorGeometry": QByteArray(), + "WebBrowserGeometry": QByteArray(), "IconEditorGeometry": QByteArray(), "HexEditorGeometry": QByteArray(), "MainGeometry": QByteArray(), "MainMaximized": False, + "WebInspectorGeometry": QByteArray(), } # if true, revert layouts to factory defaults @@ -2141,7 +2293,7 @@ if key in ["ColorMode", "FirstPageFirst"]: return toBool(prefClass.settings.value( "Printer/" + key, prefClass.printerDefaults[key])) - elif key in ["Magnification", "Orientation", "PageSize"]: + elif key in ["Magnification", "Orientation", "PageSize", "Resolution"]: return int(prefClass.settings.value( "Printer/" + key, prefClass.printerDefaults[key])) elif key in ["LeftMargin", "RightMargin", "TopMargin", "BottomMargin"]: @@ -2543,6 +2695,191 @@ "Help/" + key, pwConvert(value, encode=True)) else: prefClass.settings.setValue("Help/" + key, value) + + +def getWebBrowser(key, prefClass=Prefs): + """ + Module function to retrieve the various web browser settings. + + @param key the key of the value to get + @param prefClass preferences class used as the storage area + @return the requested help setting + """ + # the following entries are identical to the ones of the QtWebKit based + # help viewer and are being redirected there + if key.startswith(("FlashCookie", "Pim", "VirusTotal")): + return getHelp(key, prefClass) + + # Web inspector stuff must come before initializing web engine settings + # because that starts the chromium web process + if key == "WebInspectorPort": + return int(prefClass.settings.value( + "WebBrowser/" + key, prefClass.webBrowserDefaults[key])) + elif key == "WebInspectorEnabled": + return toBool(prefClass.settings.value( + "WebBrowser/" + key, prefClass.webBrowserDefaults[key])) + + if not prefClass.webEngineSettingsIntitialized: + prefClass.initWebEngineSettingsDefaults() + + if key in ["StandardFont", "FixedFont"]: + f = QFont() + f.fromString(prefClass.settings.value( + "WebBrowser/" + key, prefClass.webBrowserDefaults[key])) + return f + elif key in ["SaveUrlColor"]: + col = prefClass.settings.value("WebBrowser/" + key) + if col is not None: + return QColor(col) + else: + return prefClass.webBrowserDefaults[key] + elif key in ["WebSearchKeywords"]: + # return a list of tuples of (keyword, engine name) + keywords = [] + size = prefClass.settings.beginReadArray("WebBrowser/" + key) + for index in range(size): + prefClass.settings.setArrayIndex(index) + keyword = prefClass.settings.value("Keyword") + engineName = prefClass.settings.value("Engine") + keywords.append((keyword, engineName)) + prefClass.settings.endArray() + return keywords + elif key in ["DownloadManagerDownloads"]: + # return a list of tuples of (URL, save location, done flag, page url) + downloads = [] + length = prefClass.settings.beginReadArray("WebBrowser/" + key) + for index in range(length): + prefClass.settings.setArrayIndex(index) + url = prefClass.settings.value("URL") + location = prefClass.settings.value("Location") + done = toBool(prefClass.settings.value("Done")) + pageUrl = prefClass.settings.value("PageURL") + if pageUrl is None: + pageUrl = QUrl() + 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 in ["SyncFtpPassword", "SyncEncryptionKey"]: + from Utilities.crypto import pwConvert + return pwConvert(prefClass.settings.value( + "WebBrowser/" + key, prefClass.helpDefaults[key]), encode=False) + elif key == "HelpViewerType": + # special treatment to adjust for missing QtWebEngine + value = int(prefClass.settings.value( + "WebBrowser/" + key, prefClass.helpDefaults[key])) + if QWebEngineSettings is None: + value = prefClass.helpDefaults[key] + return value + elif key in ["StartupBehavior", "HistoryLimit", + "DownloadManagerRemovePolicy","SyncType", "SyncFtpPort", + "SyncFtpIdleTimeout", "SyncEncryptionKeyLength", + "SearchLanguage", "WebInspectorPort", + "DefaultFontSize", "DefaultFixedFontSize", + "MinimumFontSize", "MinimumLogicalFontSize", + "DiskCacheSize", "AcceptCookies", "KeepCookiesUntil", + "AdBlockUpdatePeriod", + ]: + return int(prefClass.settings.value( + "WebBrowser/" + key, prefClass.webBrowserDefaults[key])) + elif key in ["SingleWebBrowserWindow", "SaveGeometry", "JavaScriptEnabled", + "JavaScriptCanOpenWindows", "JavaScriptCanAccessClipboard", + "AutoLoadImages", "LocalStorageEnabled", + "SpatialNavigationEnabled", "LinksIncludedInFocusChain", + "LocalContentCanAccessRemoteUrls", + "LocalContentCanAccessFileUrls", "XSSAuditingEnabled", + "ScrollAnimatorEnabled", "ErrorPageEnabled", + "WarnOnMultipleClose", "WebSearchSuggestions", + "SyncEnabled", "SyncBookmarks", "SyncHistory", + "SyncPasswords", "SyncUserAgents", "SyncSpeedDial", + "SyncEncryptData", "SyncEncryptPasswordsOnly", + "ShowPreview", "WebInspectorEnabled", "DiskCacheEnabled", + "DoNotTrack", "SendReferer", "FilterTrackingCookies", + "AdBlockEnabled", "AdBlockUseLimitedEasyList", + "PluginsEnabled", "FullScreenSupportEnabled", + ]: + return toBool(prefClass.settings.value( + "WebBrowser/" + key, prefClass.webBrowserDefaults[key])) + elif key in ["GreaseMonkeyDisabledScripts", "SendRefererWhitelist", + "AdBlockSubscriptions", "AdBlockExceptions", + ]: + return toList(prefClass.settings.value( + "WebBrowser/" + key, prefClass.helpDefaults[key])) + else: + return prefClass.settings.value("WebBrowser/" + key, + prefClass.webBrowserDefaults[key]) + + +def setWebBrowser(key, value, prefClass=Prefs): + """ + Module function to store the various web browser settings. + + @param key the key of the setting to be set + @param value the value to be set + @param prefClass preferences class used as the storage area + """ + # the following entries are identical to the ones of the QtWebKit based + # help viewer and are being redirected there + if key.startswith(("FlashCookie", "Pim", "VirusTotal")): + setHelp(key, value, prefClass) + + if key in ["StandardFont", "FixedFont"]: + prefClass.settings.setValue("WebBrowser/" + key, value.toString()) + elif key == "SaveUrlColor": + prefClass.settings.setValue("WebBrowser/" + key, value.name()) + elif key == "WebSearchKeywords": + # value is list of tuples of (keyword, engine name) + prefClass.settings.remove("WebBrowser/" + key) + prefClass.settings.beginWriteArray("WebBrowser/" + key, len(value)) + index = 0 + for v in value: + prefClass.settings.setArrayIndex(index) + prefClass.settings.setValue("Keyword", v[0]) + prefClass.settings.setValue("Engine", v[1]) + index += 1 + prefClass.settings.endArray() + elif key == "DownloadManagerDownloads": + # value is list of tuples of (URL, save location, done flag, page url) + prefClass.settings.remove("Help/" + key) + prefClass.settings.beginWriteArray("WebBrowser/" + key, len(value)) + index = 0 + for v in value: + prefClass.settings.setArrayIndex(index) + prefClass.settings.setValue("URL", v[0]) + prefClass.settings.setValue("Location", v[1]) + prefClass.settings.setValue("Done", v[2]) + 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("WebBrowser/" + 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( + "WebBrowser/" + key, pwConvert(value, encode=True)) + else: + prefClass.settings.setValue("WebBrowser/" + key, value) def getSystem(key, prefClass=Prefs): @@ -3198,6 +3535,16 @@ newPassword ) ) + for key in ["SyncFtpPassword", "SyncEncryptionKey"]: + prefClass.settings.setValue( + "WebBrowser/" + key, + pwRecode( + prefClass.settings.value("WebBrowser/" + key, + prefClass.webBrowserDefaults[key]), + oldPassword, + newPassword + ) + ) initPreferences()
--- a/UI/UserInterface.py Sun Apr 03 12:29:37 2016 +0200 +++ b/UI/UserInterface.py Sun Apr 03 16:33:37 2016 +0200 @@ -9,7 +9,7 @@ from __future__ import unicode_literals try: - str = unicode + str = unicode # __IGNORE_EXCEPTION__ except NameError: pass @@ -32,6 +32,14 @@ WEBKIT_AVAILABLE = True except ImportError: WEBKIT_AVAILABLE = False +if qVersion() < "5.6.0": + WEBENGINE_AVAILABLE = False +else: + try: + from PyQt5 import QtWebEngineWidgets # __IGNORE_WARNING__ + WEBENGINE_AVAILABLE = True + except ImportError: + WEBENGINE_AVAILABLE = False from .Info import Version, BugAddress, Program, FeatureAddress from . import Config @@ -468,10 +476,16 @@ self.__initExternalToolsActions() # create a dummy help window for shortcuts handling - if WEBKIT_AVAILABLE: + if WEBENGINE_AVAILABLE: + from WebBrowser.WebBrowserWindow import WebBrowserWindow + self.dummyHelpViewer = \ + WebBrowserWindow(None, '.', None, 'web_browser', True, True) + elif WEBKIT_AVAILABLE: from Helpviewer.HelpWindow import HelpWindow self.dummyHelpViewer = \ HelpWindow(None, '.', None, 'help viewer', True, True) + else: + self.dummyHelpViewer = None # register all relevant objects splash.showMessage(self.tr("Registering Objects...")) @@ -486,7 +500,7 @@ e5App().registerObject("TaskViewer", self.taskViewer) e5App().registerObject("TemplateViewer", self.templateViewer) e5App().registerObject("Shell", self.shell) - if WEBKIT_AVAILABLE: + if self.dummyHelpViewer is not None: e5App().registerObject("DummyHelpViewer", self.dummyHelpViewer) e5App().registerObject("PluginManager", self.pluginManager) e5App().registerObject("ToolbarManager", self.toolbarManager) @@ -1596,7 +1610,7 @@ self.whatsThisAct.triggered.connect(self.__whatsThis) self.actions.append(self.whatsThisAct) - if WEBKIT_AVAILABLE: + if WEBENGINE_AVAILABLE or WEBKIT_AVAILABLE: self.helpviewerAct = E5Action( self.tr('Helpviewer'), UI.PixmapCache.getIcon("help.png"), @@ -1925,7 +1939,7 @@ self.hexEditorAct.triggered.connect(self.__openHexEditor) self.actions.append(self.hexEditorAct) - if WEBKIT_AVAILABLE: + if WEBENGINE_AVAILABLE or WEBKIT_AVAILABLE: self.webBrowserAct = E5Action( self.tr('eric6 Web Browser'), UI.PixmapCache.getIcon("ericWeb.png"), @@ -3002,12 +3016,16 @@ .format(sip_version_str) versionText += """<tr><td><b>QScintilla</b></td><td>{0}</td></tr>"""\ .format(QSCINTILLA_VERSION_STR) - try: + if WEBENGINE_AVAILABLE: + from WebBrowser.Tools import WebBrowserTools + chromeVersion = WebBrowserTools.getWebEngineVersions()[0] + versionText += \ + """<tr><td><b>WebEngine</b></td><td>{0}</td></tr>"""\ + .format(chromeVersion) + if WEBKIT_AVAILABLE: from PyQt5.QtWebKit import qWebKitVersion versionText += """<tr><td><b>WebKit</b></td><td>{0}</td></tr>"""\ .format(qWebKitVersion()) - except ImportError: - pass versionText += """<tr><td><b>{0}</b></td><td>{1}</td></tr>"""\ .format(Program, Version) versionText += self.tr("""</table>""") @@ -4846,7 +4864,10 @@ if home.endswith(".chm"): self.__chmViewer(home) else: - hvType = Preferences.getHelp("HelpViewerType") + if WEBENGINE_AVAILABLE: + hvType = Preferences.getWebBrowser("HelpViewerType") + else: + hvType = Preferences.getHelp("HelpViewerType") if hvType == 1: self.launchHelpViewer(home) elif hvType == 2: @@ -4905,7 +4926,10 @@ if home.endswith(".chm"): self.__chmViewer(home) else: - hvType = Preferences.getHelp("HelpViewerType") + if WEBENGINE_AVAILABLE: + hvType = Preferences.getWebBrowser("HelpViewerType") + else: + hvType = Preferences.getHelp("HelpViewerType") if hvType == 1: self.launchHelpViewer(home) elif hvType == 2: @@ -4971,7 +4995,10 @@ else: home = "file://" + home - hvType = Preferences.getHelp("HelpViewerType") + if WEBENGINE_AVAILABLE: + hvType = Preferences.getWebBrowser("HelpViewerType") + else: + hvType = Preferences.getHelp("HelpViewerType") if hvType == 1: self.launchHelpViewer(home) elif hvType == 2: @@ -5033,7 +5060,10 @@ else: home = pyqt4DocDir - hvType = Preferences.getHelp("HelpViewerType") + if WEBENGINE_AVAILABLE: + hvType = Preferences.getWebBrowser("HelpViewerType") + else: + hvType = Preferences.getHelp("HelpViewerType") if hvType == 1: self.launchHelpViewer(home) elif hvType == 2: @@ -5097,7 +5127,10 @@ else: home = pyqt5DocDir - hvType = Preferences.getHelp("HelpViewerType") + if WEBENGINE_AVAILABLE: + hvType = Preferences.getWebBrowser("HelpViewerType") + else: + hvType = Preferences.getHelp("HelpViewerType") if hvType == 1: self.launchHelpViewer(home) elif hvType == 2: @@ -5134,7 +5167,10 @@ else: home = "file://" + home - hvType = Preferences.getHelp("HelpViewerType") + if WEBENGINE_AVAILABLE: + hvType = Preferences.getWebBrowser("HelpViewerType") + else: + hvType = Preferences.getHelp("HelpViewerType") if hvType == 1: self.launchHelpViewer(home) elif hvType == 2: @@ -5186,7 +5222,10 @@ else: home = pysideDocDir - hvType = Preferences.getHelp("HelpViewerType") + if WEBENGINE_AVAILABLE: + hvType = Preferences.getWebBrowser("HelpViewerType") + else: + hvType = Preferences.getHelp("HelpViewerType") if hvType == 1: self.launchHelpViewer(home) elif hvType == 2: @@ -5213,12 +5252,22 @@ if not homeUrl.scheme(): home = QUrl.fromLocalFile(home).toString() - if WEBKIT_AVAILABLE: - if not (useSingle or Preferences.getHelp("SingleHelpWindow")) or \ - self.helpWindow is None: - from Helpviewer.HelpWindow import HelpWindow - help = HelpWindow(home, '.', None, 'help viewer', True, - searchWord=searchWord) + if WEBENGINE_AVAILABLE or WEBKIT_AVAILABLE: + single = useSingle + if WEBENGINE_AVAILABLE: + single = single or \ + Preferences.getWebBrowser("SingleWebBrowserWindow") + elif WEBKIT_AVAILABLE: + single = single or Preferences.getHelp("SingleHelpWindow") + if not single or self.helpWindow is None: + if WEBENGINE_AVAILABLE: + from WebBrowser.WebBrowserWindow import WebBrowserWindow + help = WebBrowserWindow(home, '.', None, 'web_browser', + True, searchWord=searchWord) + elif WEBKIT_AVAILABLE: + from Helpviewer.HelpWindow import HelpWindow + help = HelpWindow(home, '.', None, 'help viewer', True, + searchWord=searchWord) if QApplication.desktop().width() > 400 and \ QApplication.desktop().height() > 500: @@ -5226,9 +5275,13 @@ else: help.showMaximized() - if useSingle or Preferences.getHelp("SingleHelpWindow"): + if single: self.helpWindow = help - self.helpWindow.helpClosed.connect(self.__helpClosed) + try: + self.helpWindow.webBrowserClosed.connect( + self.__helpClosed) + except AttributeError: + self.helpWindow.helpClosed.connect(self.__helpClosed) self.preferencesChanged.connect( self.helpWindow.preferencesChanged) self.masterPasswordChanged.connect( @@ -5246,7 +5299,11 @@ """ Private slot to handle the helpClosed signal of the help window. """ - if Preferences.getHelp("SingleHelpWindow"): + if WEBENGINE_AVAILABLE: + single = Preferences.getWebBrowser("SingleWebBrowserWindow") + elif WEBKIT_AVAILABLE: + single = Preferences.getHelp("SingleHelpWindow") + if single: self.preferencesChanged.disconnect( self.helpWindow.preferencesChanged) self.masterPasswordChanged.disconnect( @@ -5284,7 +5341,7 @@ (boolean) @return reference to the help window instance (HelpWindow) """ - if WEBKIT_AVAILABLE: + if WEBENGINE_AVAILABLE or WEBKIT_AVAILABLE: if self.helpWindow is None: self.launchHelpViewer("", useSingle=True) self.helpWindow.raise_() @@ -5304,6 +5361,7 @@ dlg = ConfigurationDialog( self, 'Configuration', expandedEntries=self.__expandedConfigurationEntries, + webEngine=WEBENGINE_AVAILABLE, ) dlg.preferencesChanged.connect(self.__preferencesChanged) dlg.masterPasswordChanged.connect(self.__masterPasswordChanged)
--- a/Utilities/__init__.py Sun Apr 03 12:29:37 2016 +0200 +++ b/Utilities/__init__.py Sun Apr 03 16:33:37 2016 +0200 @@ -1682,6 +1682,14 @@ qVersion(), linesep, PYQT_VERSION_STR, linesep) info += " sip {0}{1} QScintilla {2}{3}".format( sip_version_str, linesep, QSCINTILLA_VERSION_STR, linesep) + if qVersion() >= "5.6.0": + try: + from PyQt5 import QtWebEngineWidgets # __IGNORE_WARNING__ + from WebBrowser.Tools import WebBrowserTools + chromeVersion = WebBrowserTools.getWebEngineVersions()[0] + info += " WebEngine {0}{1}".format(chromeVersion, linesep) + except ImportError: + pass try: from PyQt5.QtWebKit import qWebKitVersion info += " WebKit {0}{1}".format(qWebKitVersion(), linesep)
--- a/ViewManager/ViewManager.py Sun Apr 03 12:29:37 2016 +0200 +++ b/ViewManager/ViewManager.py Sun Apr 03 16:33:37 2016 +0200 @@ -11,7 +11,7 @@ import os -from PyQt5.QtCore import pyqtSignal, pyqtSlot, QSignalMapper, QTimer, \ +from PyQt5.QtCore import pyqtSignal, QSignalMapper, QTimer, \ QFileInfo, QRegExp, Qt, QCoreApplication from PyQt5.QtGui import QColor, QKeySequence, QPalette, QPixmap from PyQt5.QtWidgets import QLineEdit, QToolBar, QWidgetAction, QDialog, \ @@ -6574,7 +6574,6 @@ if editor: self.editorRenamedEd.emit(editor) -## @pyqtSlot(str, int, int) def __cursorChanged(self, fn, line, pos): """ Private slot to handle the cursorChanged signal.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/WebBrowser/AdBlock/AdBlockDialog.py Sun Apr 03 16:33:37 2016 +0200 @@ -0,0 +1,346 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2009 - 2016 Detlev Offenbach <detlev@die-offenbachs.de> +# + +""" +Module implementing the AdBlock configuration dialog. +""" + +from __future__ import unicode_literals + +from PyQt5.QtCore import pyqtSlot, Qt, QTimer, QCoreApplication +from PyQt5.QtWidgets import QDialog, QMenu, QToolButton + +from E5Gui import E5MessageBox + +from .Ui_AdBlockDialog import Ui_AdBlockDialog + +import UI.PixmapCache +import Preferences + + +class AdBlockDialog(QDialog, Ui_AdBlockDialog): + """ + Class implementing the AdBlock configuration dialog. + """ + def __init__(self, manager, parent=None): + """ + Constructor + + @param manager reference to the AdBlock manager (AdBlockManager) + @param parent reference to the parent object (QWidget) + """ + super(AdBlockDialog, self).__init__(parent) + self.setupUi(self) + self.setWindowFlags(Qt.Window) + + self.__manager = manager + + self.iconLabel.setPixmap(UI.PixmapCache.getPixmap("adBlockPlus48.png")) + + self.updateSpinBox.setValue( + Preferences.getWebBrowser("AdBlockUpdatePeriod")) + + self.useLimitedEasyListCheckBox.setChecked(Preferences.getWebBrowser( + "AdBlockUseLimitedEasyList")) + + self.searchEdit.setInactiveText(self.tr("Search...")) + + self.adBlockGroup.setChecked(self.__manager.isEnabled()) + self.__manager.requiredSubscriptionLoaded.connect(self.addSubscription) + + self.__currentTreeWidget = None + self.__currentSubscription = None + self.__loaded = False + + menu = QMenu(self) + menu.aboutToShow.connect(self.__aboutToShowActionMenu) + self.actionButton.setMenu(menu) + self.actionButton.setIcon(UI.PixmapCache.getIcon("adBlockAction.png")) + self.actionButton.setPopupMode(QToolButton.InstantPopup) + + self.__load() + + self.buttonBox.setFocus() + + def __loadSubscriptions(self): + """ + Private slot to load the AdBlock subscription rules. + """ + for index in range(self.subscriptionsTabWidget.count()): + tree = self.subscriptionsTabWidget.widget(index) + tree.refresh() + + def __load(self): + """ + Private slot to populate the tab widget with subscriptions. + """ + if self.__loaded or not self.adBlockGroup.isChecked(): + return + + from .AdBlockTreeWidget import AdBlockTreeWidget + for subscription in self.__manager.subscriptions(): + tree = AdBlockTreeWidget(subscription, self.subscriptionsTabWidget) + if subscription.isEnabled(): + icon = UI.PixmapCache.getIcon("adBlockPlus.png") + else: + icon = UI.PixmapCache.getIcon("adBlockPlusDisabled.png") + self.subscriptionsTabWidget.addTab( + tree, icon, subscription.title()) + + self.__loaded = True + QCoreApplication.processEvents() + + QTimer.singleShot(50, self.__loadSubscriptions) + + def addSubscription(self, subscription, refresh=True): + """ + Public slot adding a subscription to the list. + + @param subscription reference to the subscription to be + added (AdBlockSubscription) + @param refresh flag indicating to refresh the tree (boolean) + """ + from .AdBlockTreeWidget import AdBlockTreeWidget + tree = AdBlockTreeWidget(subscription, self.subscriptionsTabWidget) + index = self.subscriptionsTabWidget.insertTab( + self.subscriptionsTabWidget.count() - 1, tree, + subscription.title()) + self.subscriptionsTabWidget.setCurrentIndex(index) + QCoreApplication.processEvents() + if refresh: + tree.refresh() + self.__setSubscriptionEnabled(subscription, True) + + def __aboutToShowActionMenu(self): + """ + Private slot to show the actions menu. + """ + subscriptionEditable = self.__currentSubscription and \ + self.__currentSubscription.canEditRules() + subscriptionRemovable = self.__currentSubscription and \ + self.__currentSubscription.canBeRemoved() + subscriptionEnabled = self.__currentSubscription and \ + self.__currentSubscription.isEnabled() + + menu = self.actionButton.menu() + menu.clear() + + menu.addAction(self.tr("Add Rule"), self.__addCustomRule)\ + .setEnabled(subscriptionEditable) + menu.addAction(self.tr("Remove Rule"), self.__removeCustomRule)\ + .setEnabled(subscriptionEditable) + menu.addSeparator() + menu.addAction( + self.tr("Browse Subscriptions..."), self.__browseSubscriptions) + menu.addAction( + self.tr("Remove Subscription"), self.__removeSubscription)\ + .setEnabled(subscriptionRemovable) + if self.__currentSubscription: + menu.addSeparator() + if subscriptionEnabled: + txt = self.tr("Disable Subscription") + else: + txt = self.tr("Enable Subscription") + menu.addAction(txt, self.__switchSubscriptionEnabled) + menu.addSeparator() + menu.addAction( + self.tr("Update Subscription"), self.__updateSubscription)\ + .setEnabled(not subscriptionEditable) + menu.addAction( + self.tr("Update All Subscriptions"), + self.__updateAllSubscriptions) + menu.addSeparator() + menu.addAction(self.tr("Learn more about writing rules..."), + self.__learnAboutWritingFilters) + + def addCustomRule(self, filter): + """ + Public slot to add a custom AdBlock rule. + + @param filter filter to be added (string) + """ + self.subscriptionsTabWidget.setCurrentIndex( + self.subscriptionsTabWidget.count() - 1) + self.__currentTreeWidget.addRule(filter) + + def __addCustomRule(self): + """ + Private slot to add a custom AdBlock rule. + """ + self.__currentTreeWidget.addRule() + + def __removeCustomRule(self): + """ + Private slot to remove a custom AdBlock rule. + """ + self.__currentTreeWidget.removeRule() + + def __updateSubscription(self): + """ + Private slot to update the selected subscription. + """ + self.__currentSubscription.updateNow() + + def __updateAllSubscriptions(self): + """ + Private slot to update all subscriptions. + """ + self.__manager.updateAllSubscriptions() + + def __browseSubscriptions(self): + """ + Private slot to browse the list of available AdBlock subscriptions. + """ + from WebBrowser.WebBrowserWindow import WebBrowserWindow + mw = WebBrowserWindow.mainWindow() + mw.newTab("http://adblockplus.org/en/subscriptions") + mw.raise_() + + def __learnAboutWritingFilters(self): + """ + Private slot to show the web page about how to write filters. + """ + from WebBrowser.WebBrowserWindow import WebBrowserWindow + mw = WebBrowserWindow.mainWindow() + mw.newTab("http://adblockplus.org/en/filters") + mw.raise_() + + def __removeSubscription(self): + """ + Private slot to remove the selected subscription. + """ + requiresTitles = [] + requiresSubscriptions = \ + self.__manager.getRequiresSubscriptions(self.__currentSubscription) + for subscription in requiresSubscriptions: + requiresTitles.append(subscription.title()) + if requiresTitles: + message = self.tr( + "<p>Do you really want to remove subscription" + " <b>{0}</b> and all subscriptions requiring it?</p>" + "<ul><li>{1}</li></ul>").format( + self.__currentSubscription.title(), + "</li><li>".join(requiresTitles)) + else: + message = self.tr( + "<p>Do you really want to remove subscription" + " <b>{0}</b>?</p>").format(self.__currentSubscription.title()) + res = E5MessageBox.yesNo( + self, + self.tr("Remove Subscription"), + message) + + if res: + removeSubscription = self.__currentSubscription + removeTrees = [self.__currentTreeWidget] + for index in range(self.subscriptionsTabWidget.count()): + tree = self.subscriptionsTabWidget.widget(index) + if tree.subscription() in requiresSubscriptions: + removeTrees.append(tree) + for tree in removeTrees: + self.subscriptionsTabWidget.removeTab( + self.subscriptionsTabWidget.indexOf(tree)) + self.__manager.removeSubscription(removeSubscription) + + def __switchSubscriptionEnabled(self): + """ + Private slot to switch the enabled state of the selected subscription. + """ + newState = not self.__currentSubscription.isEnabled() + self.__setSubscriptionEnabled(self.__currentSubscription, newState) + + def __setSubscriptionEnabled(self, subscription, enable): + """ + Private slot to set the enabled state of a subscription. + + @param subscription subscription to set the state for + (AdBlockSubscription) + @param enable state to set to (boolean) + """ + if enable: + # enable required one as well + sub = self.__manager.subscription(subscription.requiresLocation()) + requiresSubscriptions = [] if sub is None else [sub] + icon = UI.PixmapCache.getIcon("adBlockPlus.png") + else: + # disable dependent ones as well + requiresSubscriptions = \ + self.__manager.getRequiresSubscriptions(subscription) + icon = UI.PixmapCache.getIcon("adBlockPlusDisabled.png") + requiresSubscriptions.append(subscription) + for sub in requiresSubscriptions: + sub.setEnabled(enable) + + for index in range(self.subscriptionsTabWidget.count()): + tree = self.subscriptionsTabWidget.widget(index) + if tree.subscription() in requiresSubscriptions: + self.subscriptionsTabWidget.setTabIcon( + self.subscriptionsTabWidget.indexOf(tree), icon) + + @pyqtSlot(int) + def on_updateSpinBox_valueChanged(self, value): + """ + Private slot to handle changes of the update period. + + @param value update period (integer) + """ + if value != Preferences.getWebBrowser("AdBlockUpdatePeriod"): + Preferences.setWebBrowser("AdBlockUpdatePeriod", value) + + from WebBrowser.WebBrowserWindow import WebBrowserWindow + manager = WebBrowserWindow.adBlockManager() + for subscription in manager.subscriptions(): + subscription.checkForUpdate() + + @pyqtSlot(int) + def on_subscriptionsTabWidget_currentChanged(self, index): + """ + Private slot handling the selection of another tab. + + @param index index of the new current tab (integer) + """ + if index != -1: + self.__currentTreeWidget = \ + self.subscriptionsTabWidget.widget(index) + self.__currentSubscription = \ + self.__currentTreeWidget.subscription() + + isEasyList = \ + self.__currentSubscription.url().toString().startswith( + self.__manager.getDefaultSubscriptionUrl()) + self.useLimitedEasyListCheckBox.setVisible(isEasyList) + + @pyqtSlot(str) + def on_searchEdit_textChanged(self, filter): + """ + Private slot to set a new filter on the current widget. + + @param filter filter to be set (string) + """ + if self.__currentTreeWidget and self.adBlockGroup.isChecked(): + self.__currentTreeWidget.filterString(filter) + + @pyqtSlot(bool) + def on_adBlockGroup_toggled(self, state): + """ + Private slot handling the enabling/disabling of AdBlock. + + @param state state of the toggle (boolean) + """ + self.__manager.setEnabled(state) + + if state: + self.__load() + + @pyqtSlot(bool) + def on_useLimitedEasyListCheckBox_clicked(self, checked): + """ + Private slot handling the selection of the limited EasyList. + + @param checked flag indicating the state of the check box + @type bool + """ + self.__manager.setUseLimitedEasyList( + self.useLimitedEasyListCheckBox.isChecked())
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/WebBrowser/AdBlock/AdBlockDialog.ui Sun Apr 03 16:33:37 2016 +0200 @@ -0,0 +1,204 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>AdBlockDialog</class> + <widget class="QDialog" name="AdBlockDialog"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>650</width> + <height>600</height> + </rect> + </property> + <property name="windowTitle"> + <string>AdBlock Configuration</string> + </property> + <property name="sizeGripEnabled"> + <bool>true</bool> + </property> + <layout class="QVBoxLayout" name="verticalLayout_2"> + <item> + <widget class="QGroupBox" name="adBlockGroup"> + <property name="title"> + <string>Enable AdBlock</string> + </property> + <property name="checkable"> + <bool>true</bool> + </property> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <layout class="QGridLayout" name="gridLayout"> + <property name="horizontalSpacing"> + <number>20</number> + </property> + <item row="0" column="0" rowspan="2"> + <widget class="QLabel" name="iconLabel"> + <property name="minimumSize"> + <size> + <width>48</width> + <height>48</height> + </size> + </property> + <property name="text"> + <string notr="true">Icon</string> + </property> + </widget> + </item> + <item row="0" column="1"> + <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> + <item row="1" column="1"> + <widget class="E5ClearableLineEdit" name="searchEdit"> + <property name="toolTip"> + <string>Enter search term for subscriptions and rules</string> + </property> + </widget> + </item> + </layout> + </item> + <item> + <widget class="QTabWidget" name="subscriptionsTabWidget"> + <property name="documentMode"> + <bool>true</bool> + </property> + </widget> + </item> + <item> + <layout class="QHBoxLayout" name="horizontalLayout"> + <item> + <widget class="QToolButton" name="actionButton"> + <property name="text"> + <string>Actions</string> + </property> + </widget> + </item> + <item> + <spacer name="horizontalSpacer_2"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QLabel" name="label"> + <property name="text"> + <string>Default Update Period (days):</string> + </property> + </widget> + </item> + <item> + <widget class="QSpinBox" name="updateSpinBox"> + <property name="toolTip"> + <string>Enter the update period (1 to 14 days)</string> + </property> + <property name="alignment"> + <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> + </property> + <property name="suffix"> + <string/> + </property> + <property name="minimum"> + <number>1</number> + </property> + <property name="maximum"> + <number>14</number> + </property> + <property name="value"> + <number>7</number> + </property> + </widget> + </item> + </layout> + </item> + <item> + <widget class="QCheckBox" name="useLimitedEasyListCheckBox"> + <property name="toolTip"> + <string/> + </property> + <property name="text"> + <string>Use only essential part of EasyList (for performance reasons)</string> + </property> + </widget> + </item> + </layout> + </widget> + </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> + <customwidgets> + <customwidget> + <class>E5ClearableLineEdit</class> + <extends>QLineEdit</extends> + <header>E5Gui/E5LineEdit.h</header> + </customwidget> + </customwidgets> + <tabstops> + <tabstop>adBlockGroup</tabstop> + <tabstop>searchEdit</tabstop> + <tabstop>subscriptionsTabWidget</tabstop> + <tabstop>actionButton</tabstop> + <tabstop>updateSpinBox</tabstop> + <tabstop>buttonBox</tabstop> + </tabstops> + <resources/> + <connections> + <connection> + <sender>buttonBox</sender> + <signal>accepted()</signal> + <receiver>AdBlockDialog</receiver> + <slot>accept()</slot> + <hints> + <hint type="sourcelabel"> + <x>252</x> + <y>445</y> + </hint> + <hint type="destinationlabel"> + <x>157</x> + <y>274</y> + </hint> + </hints> + </connection> + <connection> + <sender>buttonBox</sender> + <signal>rejected()</signal> + <receiver>AdBlockDialog</receiver> + <slot>reject()</slot> + <hints> + <hint type="sourcelabel"> + <x>320</x> + <y>445</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/AdBlock/AdBlockExceptionsDialog.py Sun Apr 03 16:33:37 2016 +0200 @@ -0,0 +1,95 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2012 - 2016 Detlev Offenbach <detlev@die-offenbachs.de> +# + +""" +Module implementing a dialog to configure the AdBlock exceptions. +""" + +from __future__ import unicode_literals + +from PyQt5.QtCore import pyqtSlot, Qt +from PyQt5.QtWidgets import QDialog + +from .Ui_AdBlockExceptionsDialog import Ui_AdBlockExceptionsDialog + +import UI.PixmapCache + + +class AdBlockExceptionsDialog(QDialog, Ui_AdBlockExceptionsDialog): + """ + Class implementing a dialog to configure the AdBlock exceptions. + """ + def __init__(self, parent=None): + """ + Constructor + + @param parent reference to the parent widget (QWidget) + """ + super(AdBlockExceptionsDialog, self).__init__(parent) + self.setupUi(self) + self.setWindowFlags(Qt.Window) + + self.iconLabel.setPixmap( + UI.PixmapCache.getPixmap("adBlockPlusGreen48.png")) + + self.hostEdit.setInactiveText(self.tr("Enter host to be added...")) + + self.buttonBox.setFocus() + + def load(self, hosts): + """ + Public slot to load the list of excepted hosts. + + @param hosts list of excepted hosts + """ + self.hostList.clear() + self.hostList.addItems(hosts) + + @pyqtSlot(str) + def on_hostEdit_textChanged(self, txt): + """ + Private slot to handle changes of the host edit. + + @param txt text of the edit (string) + """ + self.addButton.setEnabled(bool(txt)) + + @pyqtSlot() + def on_addButton_clicked(self): + """ + Private slot to handle a click of the add button. + """ + self.hostList.addItem(self.hostEdit.text()) + self.hostEdit.clear() + + @pyqtSlot() + def on_hostList_itemSelectionChanged(self): + """ + Private slot handling a change of the number of selected items. + """ + self.deleteButton.setEnabled(len(self.hostList.selectedItems()) > 0) + + @pyqtSlot() + def on_deleteButton_clicked(self): + """ + Private slot handling a click of the delete button. + """ + for itm in self.hostList.selectedItems(): + row = self.hostList.row(itm) + removedItem = self.hostList.takeItem(row) + del removedItem + + def accept(self): + """ + Public slot handling the acceptance of the dialog. + """ + hosts = [] + for row in range(self.hostList.count()): + hosts.append(self.hostList.item(row).text()) + + from WebBrowser.WebBrowserWindow import WebBrowserWindow + WebBrowserWindow.adBlockManager().setExceptions(hosts) + + super(AdBlockExceptionsDialog, self).accept()
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/WebBrowser/AdBlock/AdBlockExceptionsDialog.ui Sun Apr 03 16:33:37 2016 +0200 @@ -0,0 +1,167 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>AdBlockExceptionsDialog</class> + <widget class="QDialog" name="AdBlockExceptionsDialog"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>550</width> + <height>450</height> + </rect> + </property> + <property name="windowTitle"> + <string>AdBlock Exceptions</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" rowspan="2"> + <widget class="QLabel" name="iconLabel"> + <property name="minimumSize"> + <size> + <width>48</width> + <height>48</height> + </size> + </property> + <property name="text"> + <string notr="true">Icon</string> + </property> + </widget> + </item> + <item row="0" column="1"> + <spacer name="horizontalSpacer"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>188</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item row="1" column="1"> + <widget class="E5ClearableLineEdit" name="hostEdit"> + <property name="toolTip"> + <string>Enter a host to block AdBlock for</string> + </property> + </widget> + </item> + <item row="1" column="2"> + <widget class="QPushButton" name="addButton"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="toolTip"> + <string>Press to add the host</string> + </property> + <property name="text"> + <string>&Add</string> + </property> + </widget> + </item> + <item row="2" column="0" rowspan="2" colspan="2"> + <widget class="QListWidget" name="hostList"> + <property name="alternatingRowColors"> + <bool>true</bool> + </property> + <property name="sortingEnabled"> + <bool>true</bool> + </property> + </widget> + </item> + <item row="2" column="2"> + <widget class="QPushButton" name="deleteButton"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="toolTip"> + <string>Press to delete the selected hosts</string> + </property> + <property name="text"> + <string>&Delete</string> + </property> + </widget> + </item> + <item row="3" column="2"> + <spacer name="verticalSpacer"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>148</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::Cancel|QDialogButtonBox::Ok</set> + </property> + </widget> + </item> + </layout> + </widget> + <customwidgets> + <customwidget> + <class>E5ClearableLineEdit</class> + <extends>QLineEdit</extends> + <header>E5Gui/E5LineEdit.h</header> + </customwidget> + </customwidgets> + <tabstops> + <tabstop>hostEdit</tabstop> + <tabstop>addButton</tabstop> + <tabstop>hostList</tabstop> + <tabstop>deleteButton</tabstop> + <tabstop>buttonBox</tabstop> + </tabstops> + <resources/> + <connections> + <connection> + <sender>buttonBox</sender> + <signal>accepted()</signal> + <receiver>AdBlockExceptionsDialog</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>AdBlockExceptionsDialog</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/AdBlock/AdBlockIcon.py Sun Apr 03 16:33:37 2016 +0200 @@ -0,0 +1,188 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2012 - 2016 Detlev Offenbach <detlev@die-offenbachs.de> +# + +""" +Module implementing the AdBlock icon for the main window status bar. +""" + +from __future__ import unicode_literals + +from PyQt5.QtCore import Qt +from PyQt5.QtWidgets import QAction, QMenu + +from E5Gui.E5ClickableLabel import E5ClickableLabel + +import UI.PixmapCache + + +class AdBlockIcon(E5ClickableLabel): + """ + Class implementing the AdBlock icon for the main window status bar. + """ + def __init__(self, parent): + """ + Constructor + + @param parent reference to the parent widget (HelpWindow) + """ + super(AdBlockIcon, self).__init__(parent) + + self.__mw = parent + self.__menuAction = None + self.__enabled = False + + self.setMaximumHeight(16) + self.setCursor(Qt.PointingHandCursor) + self.setToolTip(self.tr( + "AdBlock lets you block unwanted content on web pages.")) + + self.clicked.connect(self.__showMenu) + + def setEnabled(self, enabled): + """ + Public slot to set the enabled state. + + @param enabled enabled state (boolean) + """ + self.__enabled = enabled + if enabled: + self.currentChanged() + else: + self.setPixmap( + UI.PixmapCache.getPixmap("adBlockPlusDisabled16.png")) + + def __createMenu(self, menu=None): + """ + Private slot to create the context menu. + + @param menu parent menu (QMenu) + """ + if menu is None: + menu = self.sender() + if menu is None: + return + + menu.clear() + + manager = self.__mw.adBlockManager() + + if manager.isEnabled(): + menu.addAction( + UI.PixmapCache.getIcon("adBlockPlusDisabled.png"), + self.tr("Disable AdBlock"), + self.__enableAdBlock).setData(False) + else: + menu.addAction( + UI.PixmapCache.getIcon("adBlockPlus.png"), + self.tr("Enable AdBlock"), + self.__enableAdBlock).setData(True) + menu.addSeparator() + if manager.isEnabled() and self.__mw.currentBrowser().url().host(): + if self.__isCurrentHostExcepted(): + menu.addAction( + UI.PixmapCache.getIcon("adBlockPlus.png"), + self.tr("Remove AdBlock Exception"), + self.__setException).setData(False) + else: + menu.addAction( + UI.PixmapCache.getIcon("adBlockPlusGreen.png"), + self.tr("Add AdBlock Exception"), + self.__setException).setData(True) + menu.addAction( + UI.PixmapCache.getIcon("adBlockPlusGreen.png"), + self.tr("AdBlock Exceptions..."), manager.showExceptionsDialog) + menu.addSeparator() + menu.addAction( + UI.PixmapCache.getIcon("adBlockPlus.png"), + self.tr("AdBlock Configuration..."), manager.showDialog) + + def menuAction(self): + """ + Public method to get a reference to the menu action. + + @return reference to the menu action (QAction) + """ + if not self.__menuAction: + self.__menuAction = QAction(self.tr("AdBlock"), self) + self.__menuAction.setMenu(QMenu()) + self.__menuAction.menu().aboutToShow.connect(self.__createMenu) + + if self.__enabled: + self.__menuAction.setIcon( + UI.PixmapCache.getIcon("adBlockPlus.png")) + else: + self.__menuAction.setIcon( + UI.PixmapCache.getIcon("adBlockPlusDisabled.png")) + + return self.__menuAction + + def __showMenu(self, pos): + """ + Private slot to show the context menu. + + @param pos position the context menu should be shown (QPoint) + """ + menu = QMenu() + self.__createMenu(menu) + menu.exec_(pos) + + def __enableAdBlock(self): + """ + Private slot to enable or disable AdBlock. + """ + act = self.sender() + if act is not None: + self.__mw.adBlockManager().setEnabled(act.data()) + + def __isCurrentHostExcepted(self): + """ + Private method to check, if the host of the current browser is + excepted. + + @return flag indicating an exception (boolean) + """ + browser = self.__mw.currentBrowser() + if browser is None: + return False + + urlHost = browser.page().url().host() + + return urlHost and \ + self.__mw.adBlockManager().isHostExcepted(urlHost) + + def currentChanged(self): + """ + Public slot to handle a change of the current browser tab. + """ + if self.__enabled: + if self.__isCurrentHostExcepted(): + self.setPixmap( + UI.PixmapCache.getPixmap("adBlockPlusGreen16.png")) + else: + self.setPixmap(UI.PixmapCache.getPixmap("adBlockPlus16.png")) + + def __setException(self): + """ + Private slot to add or remove the current host from the list of + exceptions. + """ + act = self.sender() + if act is not None: + urlHost = self.__mw.currentBrowser().url().host() + if act.data(): + self.__mw.adBlockManager().addException(urlHost) + else: + self.__mw.adBlockManager().removeException(urlHost) + self.currentChanged() + + def sourceChanged(self, browser, url): + """ + Public slot to handle URL changes. + + @param browser reference to the browser (HelpBrowser) + @param url new URL (QUrl) + """ + if browser == self.__mw.currentBrowser(): + self.currentChanged()
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/WebBrowser/AdBlock/AdBlockManager.py Sun Apr 03 16:33:37 2016 +0200 @@ -0,0 +1,609 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2009 - 2016 Detlev Offenbach <detlev@die-offenbachs.de> +# + +""" +Module implementing the AdBlock manager. +""" + +from __future__ import unicode_literals + +import os + +from PyQt5.QtCore import pyqtSignal, QObject, QUrl, QUrlQuery, QFile, \ + QByteArray +from PyQt5.QtWebEngineCore import QWebEngineUrlRequestInfo + +from E5Gui import E5MessageBox + +from .AdBlockSubscription import AdBlockSubscription +from .AdBlockUrlInterceptor import AdBlockUrlInterceptor + +from Utilities.AutoSaver import AutoSaver +import Utilities +import Preferences + + +class AdBlockManager(QObject): + """ + Class implementing the AdBlock manager. + + @signal rulesChanged() emitted after some rule has changed + """ + rulesChanged = pyqtSignal() + requiredSubscriptionLoaded = pyqtSignal(AdBlockSubscription) + + def __init__(self, parent=None): + """ + Constructor + + @param parent reference to the parent object (QObject) + """ + super(AdBlockManager, self).__init__(parent) + + self.__loaded = False + self.__subscriptionsLoaded = False + self.__enabled = False + self.__adBlockDialog = None + self.__adBlockExceptionsDialog = None + self.__adBlockNetwork = None + self.__adBlockPage = None + self.__subscriptions = [] + self.__exceptedHosts = Preferences.getWebBrowser("AdBlockExceptions") + self.__saveTimer = AutoSaver(self, self.save) + self.__limitedEasyList = Preferences.getWebBrowser( + "AdBlockUseLimitedEasyList") + + self.__defaultSubscriptionUrlString = \ + "abp:subscribe?location=" \ + "https://easylist-downloads.adblockplus.org/easylist.txt&"\ + "title=EasyList" + self.__customSubscriptionUrlString = \ + bytes(self.__customSubscriptionUrl().toEncoded()).decode() + + self.rulesChanged.connect(self.__saveTimer.changeOccurred) + self.rulesChanged.connect(self.__rulesChanged) + + self.__interceptor = AdBlockUrlInterceptor(self) + + from WebBrowser.WebBrowserWindow import WebBrowserWindow + WebBrowserWindow.networkManager().installUrlInterceptor( + self.__interceptor) + + def __rulesChanged(self): + """ + Private slot handling a change of the AdBlock rules. + """ + from WebBrowser.WebBrowserWindow import WebBrowserWindow + WebBrowserWindow.mainWindow().reloadUserStyleSheet() + + def close(self): + """ + Public method to close the open search engines manager. + """ + self.__adBlockDialog and self.__adBlockDialog.close() + self.__adBlockExceptionsDialog and \ + self.__adBlockExceptionsDialog.close() + + self.__saveTimer.saveIfNeccessary() + + def isEnabled(self): + """ + Public method to check, if blocking ads is enabled. + + @return flag indicating the enabled state (boolean) + """ + if not self.__loaded: + self.load() + + return self.__enabled + + def setEnabled(self, enabled): + """ + Public slot to set the enabled state. + + @param enabled flag indicating the enabled state (boolean) + """ + if self.isEnabled() == enabled: + return + + from WebBrowser.WebBrowserWindow import WebBrowserWindow + self.__enabled = enabled + for mainWindow in WebBrowserWindow.mainWindows(): + mainWindow.adBlockIcon().setEnabled(enabled) + if enabled: + self.__loadSubscriptions() + self.rulesChanged.emit() + + def block(self, info): + """ + Public method to check, if a request should be blocked. + + @param info request info aobject + @type QWebEngineUrlRequestInfo + @return flag indicating to block the request + @rtype bool + """ + urlString = bytes(info.requestUrl().toEncoded()).decode().lower() + urlDomain = info.requestUrl().host().lower() + urlScheme = info.requestUrl().scheme().lower() + refererHost = info.firstPartyUrl().host().lower() + + if not self.isEnabled() or not self.__canRunOnScheme(urlScheme): + return False + + if self.isHostExcepted(urlDomain) or self.isHostExcepted(refererHost): + return False + + res = False + + for subscription in self.subscriptions(): + if subscription.isEnabled(): + if subscription.adBlockDisabledForUrl(info.requestUrl()): + continue + + blockedRule = subscription.match(info, urlDomain, urlString) + if blockedRule: + res = True + if info.resourceType() == \ + QWebEngineUrlRequestInfo.ResourceTypeMainFrame: + url = QUrl("eric:adblock") + query = QUrlQuery() + query.addQueryItem("rule", blockedRule.filter()) + query.addQueryItem( + "subscription", blockedRule.subscription().title()) + url.setQuery(query) + info.redirect(url) + res = False + else: + info.block(True) + break + + return res + + def __canRunOnScheme(self, scheme): + """ + Private method to check, if AdBlock can be performed on the scheme. + + @param scheme scheme to check (string) + @return flag indicating, that AdBlock can be performed (boolean) + """ + return scheme not in ["data", "eric", "qthelp", "qrc", "file", "abp"] + + def page(self): + """ + Public method to get a reference to the page block object. + + @return reference to the page block object (AdBlockPage) + """ + if self.__adBlockPage is None: + from .AdBlockPage import AdBlockPage + self.__adBlockPage = AdBlockPage(self) + return self.__adBlockPage + + def __customSubscriptionLocation(self): + """ + Private method to generate the path for custom subscriptions. + + @return URL for custom subscriptions (QUrl) + """ + dataDir = os.path.join(Utilities.getConfigDir(), "web_browser", + "subscriptions") + if not os.path.exists(dataDir): + os.makedirs(dataDir) + fileName = os.path.join(dataDir, "adblock_subscription_custom") + return QUrl.fromLocalFile(fileName) + + def __customSubscriptionUrl(self): + """ + Private method to generate the URL for custom subscriptions. + + @return URL for custom subscriptions (QUrl) + """ + location = self.__customSubscriptionLocation() + encodedUrl = bytes(location.toEncoded()).decode() + url = QUrl("abp:subscribe?location={0}&title={1}".format( + encodedUrl, self.tr("Custom Rules"))) + return url + + def customRules(self): + """ + Public method to get a subscription for custom rules. + + @return subscription object for custom rules (AdBlockSubscription) + """ + location = self.__customSubscriptionLocation() + for subscription in self.__subscriptions: + if subscription.location() == location: + return subscription + + url = self.__customSubscriptionUrl() + customAdBlockSubscription = AdBlockSubscription(url, True, self) + self.addSubscription(customAdBlockSubscription) + return customAdBlockSubscription + + def subscriptions(self): + """ + Public method to get all subscriptions. + + @return list of subscriptions (list of AdBlockSubscription) + """ + if not self.__loaded: + self.load() + + return self.__subscriptions[:] + + def subscription(self, location): + """ + Public method to get a subscription based on its location. + + @param location location of the subscription to search for (string) + @return subscription or None (AdBlockSubscription) + """ + if location != "": + for subscription in self.__subscriptions: + if subscription.location().toString() == location: + return subscription + + return None + + def updateAllSubscriptions(self): + """ + Public method to update all subscriptions. + """ + for subscription in self.__subscriptions: + subscription.updateNow() + + def removeSubscription(self, subscription, emitSignal=True): + """ + Public method to remove an AdBlock subscription. + + @param subscription AdBlock subscription to be removed + (AdBlockSubscription) + @param emitSignal flag indicating to send a signal (boolean) + """ + if subscription is None: + return + + if subscription.url().toString().startswith( + (self.__defaultSubscriptionUrlString, + self.__customSubscriptionUrlString)): + return + + try: + self.__subscriptions.remove(subscription) + rulesFileName = subscription.rulesFileName() + QFile.remove(rulesFileName) + requiresSubscriptions = self.getRequiresSubscriptions(subscription) + for requiresSubscription in requiresSubscriptions: + self.removeSubscription(requiresSubscription, False) + if emitSignal: + self.rulesChanged.emit() + except ValueError: + pass + + def addSubscriptionFromUrl(self, url): + """ + Public method to ad an AdBlock subscription given the abp URL: + + @param url URL to subscribe an AdBlock subscription + @type QUrl + @return flag indicating success + @rtype bool + """ + if url.path() != "subscribe": + return False + + title = QUrl.fromPercentEncoding( + QByteArray(QUrlQuery(url).queryItemValue("title").encode())) + if not title: + return False + + res = E5MessageBox.yesNo( + None, + self.tr("Subscribe?"), + self.tr( + """<p>Subscribe to this AdBlock subscription?</p>""" + """<p>{0}</p>""").format(title)) + if res: + from .AdBlockSubscription import AdBlockSubscription + from WebBrowser.WebBrowserWindow import WebBrowserWindow + + dlg = WebBrowserWindow.adBlockManager().showDialog() + subscription = AdBlockSubscription( + url, False, + WebBrowserWindow.adBlockManager()) + WebBrowserWindow.adBlockManager().addSubscription(subscription) + dlg.addSubscription(subscription, False) + dlg.setFocus() + dlg.raise_() + + def addSubscription(self, subscription): + """ + Public method to add an AdBlock subscription. + + @param subscription AdBlock subscription to be added + (AdBlockSubscription) + """ + if subscription is None: + return + + self.__subscriptions.insert(-1, subscription) + + subscription.rulesChanged.connect(self.rulesChanged) + subscription.changed.connect(self.rulesChanged) + subscription.enabledChanged.connect(self.rulesChanged) + + self.rulesChanged.emit() + + def save(self): + """ + Public method to save the AdBlock subscriptions. + """ + if not self.__loaded: + return + + Preferences.setWebBrowser("AdBlockEnabled", self.__enabled) + if self.__subscriptionsLoaded: + subscriptions = [] + requiresSubscriptions = [] + # intermediate store for subscription requiring others + for subscription in self.__subscriptions: + if subscription is None: + continue + urlString = bytes(subscription.url().toEncoded()).decode() + if "requiresLocation" in urlString: + requiresSubscriptions.append(urlString) + else: + subscriptions.append(urlString) + subscription.saveRules() + for subscription in requiresSubscriptions: + subscriptions.insert(-1, subscription) # custom should be last + Preferences.setWebBrowser("AdBlockSubscriptions", subscriptions) + + def load(self): + """ + Public method to load the AdBlock subscriptions. + """ + if self.__loaded: + return + + self.__loaded = True + + self.__enabled = Preferences.getWebBrowser("AdBlockEnabled") + if self.__enabled: + self.__loadSubscriptions() + + def __loadSubscriptions(self): + """ + Private method to load the set of subscriptions. + """ + if self.__subscriptionsLoaded: + return + + subscriptions = Preferences.getWebBrowser("AdBlockSubscriptions") + if subscriptions: + for subscription in subscriptions: + if subscription.startswith( + self.__defaultSubscriptionUrlString): + break + else: + subscriptions.insert(0, self.__defaultSubscriptionUrlString) + for subscription in subscriptions: + if subscription.startswith(self.__customSubscriptionUrlString): + break + else: + subscriptions.append(self.__customSubscriptionUrlString) + else: + subscriptions = [self.__defaultSubscriptionUrlString, + self.__customSubscriptionUrlString] + for subscription in subscriptions: + url = QUrl.fromEncoded(subscription.encode("utf-8")) + adBlockSubscription = AdBlockSubscription( + url, + subscription.startswith(self.__customSubscriptionUrlString), + self, + subscription.startswith(self.__defaultSubscriptionUrlString)) + adBlockSubscription.rulesChanged.connect(self.rulesChanged) + adBlockSubscription.changed.connect(self.rulesChanged) + adBlockSubscription.enabledChanged.connect(self.rulesChanged) + self.__subscriptions.append(adBlockSubscription) + + self.__subscriptionsLoaded = True + + def loadRequiredSubscription(self, location, title): + """ + Public method to load a subscription required by another one. + + @param location location of the required subscription (string) + @param title title of the required subscription (string) + """ + # Step 1: check, if the subscription is in the list of subscriptions + urlString = "abp:subscribe?location={0}&title={1}".format( + location, title) + for subscription in self.__subscriptions: + if subscription.url().toString().startswith(urlString): + # We found it! + return + + # Step 2: if it is not, get it + url = QUrl.fromEncoded(urlString.encode("utf-8")) + adBlockSubscription = AdBlockSubscription(url, False, self) + self.addSubscription(adBlockSubscription) + self.requiredSubscriptionLoaded.emit(adBlockSubscription) + + def getRequiresSubscriptions(self, subscription): + """ + Public method to get a list of subscriptions, that require the given + one. + + @param subscription subscription to check for (AdBlockSubscription) + @return list of subscription requiring the given one (list of + AdBlockSubscription) + """ + subscriptions = [] + location = subscription.location().toString() + for subscription in self.__subscriptions: + if subscription.requiresLocation() == location: + subscriptions.append(subscription) + + return subscriptions + + def showDialog(self): + """ + Public slot to show the AdBlock subscription management dialog. + + @return reference to the dialog (AdBlockDialog) + """ + if self.__adBlockDialog is None: + from .AdBlockDialog import AdBlockDialog + self.__adBlockDialog = AdBlockDialog(self) + + self.__adBlockDialog.show() + return self.__adBlockDialog + + def elementHidingRules(self): + """ + Public method to get the element hiding rules. + + @return element hiding rules (string) + """ + if not self.isEnabled(): + return "" + + rules = "" + + for subscription in self.__subscriptions: + rules += subscription.elementHidingRules() + + if rules: + # remove last ", + rules = rules[:-1] + + return rules + + def elementHidingRulesForDomain(self, url): + """ + Public method to get the element hiding rules for a domain. + + @param url URL to get hiding rules for (QUrl) + @return element hiding rules (string) + """ + if not self.isEnabled(): + return "" + + rules = "" + + for subscription in self.__subscriptions: + if subscription.elemHideDisabledForUrl(url): + continue + + rules += subscription.elementHidingRulesForDomain(url.host()) + + if rules: + # remove last "," + rules = rules[:-1] + + rules += "{display:none !important;}\n" + + return rules + + def exceptions(self): + """ + Public method to get a list of excepted hosts. + + @return list of excepted hosts (list of string) + """ + return self.__exceptedHosts + + def setExceptions(self, hosts): + """ + Public method to set the list of excepted hosts. + + @param hosts list of excepted hosts (list of string) + """ + self.__exceptedHosts = [host.lower() for host in hosts] + Preferences.setWebBrowser("AdBlockExceptions", self.__exceptedHosts) + + def addException(self, host): + """ + Public method to add an exception. + + @param host to be excepted (string) + """ + host = host.lower() + if host and host not in self.__exceptedHosts: + self.__exceptedHosts.append(host) + Preferences.setWebBrowser( + "AdBlockExceptions", self.__exceptedHosts) + + def removeException(self, host): + """ + Public method to remove an exception. + + @param host to be removed from the list of exceptions (string) + """ + host = host.lower() + if host in self.__exceptedHosts: + self.__exceptedHosts.remove(host) + Preferences.setWebBrowser( + "AdBlockExceptions", self.__exceptedHosts) + + def isHostExcepted(self, host): + """ + Public slot to check, if a host is excepted. + + @param host host to check (string) + @return flag indicating an exception (boolean) + """ + host = host.lower() + return host in self.__exceptedHosts + + def showExceptionsDialog(self): + """ + Public method to show the AdBlock Exceptions dialog. + + @return reference to the exceptions dialog (AdBlockExceptionsDialog) + """ + if self.__adBlockExceptionsDialog is None: + from .AdBlockExceptionsDialog import AdBlockExceptionsDialog + self.__adBlockExceptionsDialog = AdBlockExceptionsDialog() + + self.__adBlockExceptionsDialog.load(self.__exceptedHosts) + self.__adBlockExceptionsDialog.show() + return self.__adBlockExceptionsDialog + + def useLimitedEasyList(self): + """ + Public method to test, if limited EasyList rules shall be used. + + @return flag indicating limited EasyList rules + @rtype bool + """ + return self.__limitedEasyList + + def setUseLimitedEasyList(self, limited): + """ + Public method to set the limited EasyList flag. + + @param limited flag indicating to use limited EasyList + @type bool + """ + self.__limitedEasyList = limited + + for subscription in self.__subscriptions: + if subscription.url().toString().startswith( + self.__defaultSubscriptionUrlString): + subscription.updateNow() + + Preferences.setWebBrowser("AdBlockUseLimitedEasyList", limited) + + def getDefaultSubscriptionUrl(self): + """ + Public method to get the default subscription URL. + + @return default subscription URL + @rtype str + """ + return self.__defaultSubscriptionUrlString
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/WebBrowser/AdBlock/AdBlockPage.py Sun Apr 03 16:33:37 2016 +0200 @@ -0,0 +1,39 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2009 - 2016 Detlev Offenbach <detlev@die-offenbachs.de> +# + +""" +Module implementing a class to apply AdBlock rules to a web page. +""" + +from __future__ import unicode_literals + +from PyQt5.QtCore import QObject + +from ..Tools import Scripts + + +class AdBlockPage(QObject): + """ + Class to apply AdBlock rules to a web page. + """ + def hideBlockedPageEntries(self, page): + """ + Public method to apply AdBlock rules to a web page. + + @param page reference to the web page (HelpWebPage) + """ + if page is None: + return + + from WebBrowser.WebBrowserWindow import WebBrowserWindow + manager = WebBrowserWindow.adBlockManager() + if not manager.isEnabled(): + return + + # apply domain specific element hiding rules + elementHiding = manager.elementHidingRulesForDomain(page.url()) + if elementHiding: + script = Scripts.setCss(elementHiding) + page.runJavaScript(script)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/WebBrowser/AdBlock/AdBlockRule.py Sun Apr 03 16:33:37 2016 +0200 @@ -0,0 +1,681 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2009 - 2016 Detlev Offenbach <detlev@die-offenbachs.de> +# + +""" +Module implementing the AdBlock rule class. +""" + +from __future__ import unicode_literals + +import re + +from PyQt5.QtCore import Qt, QRegExp +from PyQt5.QtWebEngineCore import QWebEngineUrlRequestInfo + + +def toSecondLevelDomain(url): + """ + Module function to get a second level domain from the given URL. + + @param url URL to extract domain from (QUrl) + @return name of second level domain (string) + """ + topLevelDomain = url.topLevelDomain() + urlHost = url.host() + + if not topLevelDomain or not urlHost: + return "" + + domain = urlHost[:len(urlHost) - len(topLevelDomain)] + if domain.count(".") == 0: + return urlHost + + while domain.count(".") != 0: + domain = domain[domain.find(".") + 1:] + + return domain + topLevelDomain + + +class AdBlockRule(object): + """ + Class implementing the AdBlock rule. + """ + def __init__(self, filter="", subscription=None): + """ + Constructor + + @param filter filter string of the rule (string) + @param subscription reference to the subscription object + (AdBlockSubscription) + """ + self.__subscription = subscription + + self.__regExp = QRegExp() + self.__options = [] + self.__blockedDomains = [] + self.__allowedDomains = [] + + self.__enabled = True + self.__cssRule = False + self.__exception = False + self.__internalDisabled = False + self.__domainRestricted = False + self.__useRegExp = False + self.__useDomainMatch = False + self.__useEndsMatch = False + self.__thirdParty = False + self.__thirdPartyException = False + self.__object = False + self.__objectException = False + self.__subdocument = False + self.__subdocumentException = False + self.__xmlhttprequest = False + self.__xmlhttprequestException = False + self.__document = False + self.__elemhide = False + self.__caseSensitivity = Qt.CaseInsensitive + self.__image = False + self.__imageException = False + self.__script = False + self.__scriptException = False + self.__stylesheet = False + self.__stylesheetException = False + self.__objectSubrequest = False + self.__objectSubrequestException = False + self.__stringMatchRule = False + + self.setFilter(filter) + + def subscription(self): + """ + Public method to get the subscription this rule belongs to. + + @return subscription of the rule (AdBlockSubscription) + """ + return self.__subscription + + def filter(self): + """ + Public method to get the rule filter string. + + @return rule filter string (string) + """ + return self.__filter + + def setFilter(self, filter): + """ + Public method to set the rule filter string. + + @param filter rule filter string (string) + """ + self.__filter = filter + self.__parseFilter() + + def __parseFilter(self): + """ + Private method to parse the filter pattern. + """ + parsedLine = self.__filter + + # empty rule or just a comment + if not parsedLine.strip() or parsedLine.startswith(("!", "[Adblock")): + self.__enabled = False + return + + # CSS element hiding rule + if "##" in parsedLine or "#@#" in parsedLine: + self.__cssRule = True + pos = parsedLine.find("#") + + # domain restricted rule + if not parsedLine.startswith("##"): + domains = parsedLine[:pos] + self.__parseDomains(domains, ",") + + self.__exception = parsedLine[pos + 1] == "@" + + if self.__exception: + self.__cssSelector = parsedLine[pos + 3:] + else: + self.__cssSelector = parsedLine[pos + 2:] + # CSS rule cannot have more options -> stop parsing + return + + # Exception always starts with @@ + if parsedLine.startswith("@@"): + self.__exception = True + parsedLine = parsedLine[2:] + + # Parse all options following '$' character + optionsIndex = parsedLine.find("$") + if optionsIndex >= 0: + options = parsedLine[optionsIndex + 1:].split(",") + + handledOptions = 0 + for option in options: + if option.startswith("domain="): + self.__parseDomains(option[7:], "|") + handledOptions += 1 + elif option == "match-case": + self.__caseSensitivity = Qt.CaseSensitive + handledOptions += 1 + elif option.endswith("third-party"): + self.__thirdParty = True + self.__thirdPartyException = option.startswith("~") + handledOptions += 1 + elif option.endswith("object"): + self.__object = True + self.__objectException = option.startswith("~") + handledOptions += 1 + elif option.endswith("subdocument"): + self.__subdocument = True + self.__subdocumentException = option.startswith("~") + handledOptions += 1 + elif option.endswith("xmlhttprequest"): + self.__xmlhttprequest = True + self.__xmlhttprequestException = option.startswith("~") + handledOptions += 1 + elif option.endswith("image"): + self.__image = True + self.__imageException = option.startswith("~") + elif option.endswith("script"): + self.__script = True + self.__scriptException = option.startswith("~") + elif option.endswith("stylesheet"): + self.__stylesheet = True + self.__stylesheetException = option.startswith("~") + elif option.endswith("object-subrequest"): + self.__objectSubrequest = True + self.__objectSubrequestException = option.startswith("~") + elif option == "document" and self.__exception: + self.__document = True + handledOptions += 1 + elif option == "elemhide" and self.__exception: + self.__elemhide = True + handledOptions += 1 + elif option == "collapse": + # Hiding placeholders of blocked elements + handledOptions += 1 + + # If we don't handle all options, it's safer to just disable + # this rule + if handledOptions != len(options): + self.__internalDisabled = True + return + + parsedLine = parsedLine[:optionsIndex] + + # Rule is classic regexp + if parsedLine.startswith("/") and parsedLine.endswith("/"): + parsedLine = parsedLine[1:-1] + self.__useRegExp = True + self.__regExp = QRegExp(parsedLine, self.__caseSensitivity, + QRegExp.RegExp) + return + + # Remove starting / ending wildcards + if parsedLine.startswith("*"): + parsedLine = parsedLine[1:] + if parsedLine.endswith("*"): + parsedLine = parsedLine[:-1] + + # Fast string matching for domain can be used + if parsedLine.startswith("||") and \ + parsedLine.endswith("^") and \ + QRegExp("[/:?=&\\*]").indexIn(parsedLine) == -1: + parsedLine = parsedLine[2:-1] + self.__useDomainMatch = True + self.__matchString = parsedLine + return + + # If rule contains '|' only at the end, string matching can be used + if parsedLine.endswith("|") and \ + QRegExp("[\\^\\*]").indexIn(parsedLine) == -1 and \ + parsedLine.count("|") == 1: + parsedLine = parsedLine[:-1] + self.__useEndsMatch = True + self.__matchString = parsedLine + return + + # If there is still a wildcard (*) or separator (^) or (|), + # the rule must be modified to comply with QRegExp. + if "*" in parsedLine or "^" in parsedLine or "|" in parsedLine: + pattern = self.__convertPatternToRegExp(parsedLine) + self.__useRegExp = True + self.__regExp = QRegExp(pattern, self.__caseSensitivity, + QRegExp.RegExp) + return + + # no regexp required + self.__useRegExp = False + self.__matchString = parsedLine + self.__stringMatchRule = True + + def __parseDomains(self, domains, separator): + """ + Private method to parse a string with a domain list. + + @param domains list of domains (string) + @param separator separator character used by the list (string) + """ + domainsList = domains.split(separator) + + for domain in domainsList: + if not domain: + continue + if domain.startswith("~"): + self.__blockedDomains.append(domain[1:]) + else: + self.__allowedDomains.append(domain) + + self.__domainRestricted = \ + bool(self.__blockedDomains) or bool(self.__allowedDomains) + + def networkMatch(self, request, domain, encodedUrl): + """ + Public method to check the rule for a match. + + @param request reference to the network request + @type QWebEngineUrlRequestInfo + @param domain domain name + @type str + @param encodedUrl string encoded URL to be checked + @type str + @return flag indicating a match + @rtype bool + """ + if self.__cssRule or not self.__enabled or self.__internalDisabled: + return False + + matched = self.__stringMatch(domain, encodedUrl) + + if matched: + # check domain restrictions + if self.__domainRestricted and \ + not self.matchDomain(request.firstPartyUrl().host()): + return False + + # check third-party restrictions + if self.__thirdParty and not self.matchThirdParty(request): + return False + + # check object restrictions + if self.__object and not self.matchObject(request): + return False + + # check subdocument restrictions + if self.__subdocument and not self.matchSubdocument(request): + return False + + # check xmlhttprequest restriction + if self.__xmlhttprequest and not self.matchXmlHttpRequest(request): + return False + + # check image restriction + if self.__image and not self.matchImage(request): + return False + + # check script restriction + if self.__script and not self.matchScript(request): + return False + + # check stylesheet restriction + if self.__stylesheet and not self.matchStyleSheet(request): + return False + + # check object-subrequest restriction + if self.__objectSubrequest and \ + not self.matchObjectSubrequest(request): + return False + + return matched + + def urlMatch(self, url): + """ + Public method to check an URL against the rule. + + @param url URL to check (QUrl) + @return flag indicating a match (boolean) + """ + if not self.__document and not self.__elemhide: + return False + + encodedUrl = bytes(url.toEncoded()).decode() + domain = url.host() + return self.__stringMatch(domain, encodedUrl) + + def __stringMatch(self, domain, encodedUrl): + """ + Private method to match a domain string. + + @param domain domain to match + @type str + @param encodedUrl URL in encoded form + @type str + @return flag indicating a match + @rtype bool + """ + if self.__cssRule or not self.__enabled or self.__internalDisabled: + return False + + matched = False + + if self.__useRegExp: + matched = self.__regExp.indexIn(encodedUrl) != -1 + elif self.__useDomainMatch: + matched = domain.endswith(self.__matchString) + elif self.__useEndsMatch: + if self.__caseSensitivity == Qt.CaseInsensitive: + matched = encodedUrl.lower().endswith( + self.__matchString.lower()) + else: + matched = encodedUrl.endswith(self.__matchString) + else: + if self.__caseSensitivity == Qt.CaseInsensitive: + matched = self.__matchString.lower() in encodedUrl.lower() + else: + matched = self.__matchString in encodedUrl + + return matched + + def matchDomain(self, domain): + """ + Public method to match a domain. + + @param domain domain name to check (string) + @return flag indicating a match (boolean) + """ + if not self.__enabled: + return False + + if not self.__domainRestricted: + return True + + if len(self.__blockedDomains) == 0: + for dom in self.__allowedDomains: + if domain.endswith(dom): + return True + elif len(self.__allowedDomains) == 0: + for dom in self.__blockedDomains: + if domain.endswith(dom): + return False + return True + else: + for dom in self.__blockedDomains: + if domain.endswith(dom): + return False + for dom in self.__allowedDomains: + if domain.endswith(dom): + return True + + return False + + def matchThirdParty(self, req): + """ + Public slot to match a third-party rule. + + @param req request object to check (QWebEngineUrlRequestInfo) + @return flag indicating a match (boolean) + """ + # Third-party matching should be performed on second-level domains + firstPartyHost = toSecondLevelDomain(req.firstPartyUrl()) + host = toSecondLevelDomain(req.requestUrl()) + + match = firstPartyHost != host + + if self.__thirdPartyException: + return not match + else: + return match + + def matchObject(self, req): + """ + Public slot to match an object rule. + + @param req request object to check (QWebEngineUrlRequestInfo) + @return flag indicating a match (boolean) + """ + match = ( + req.resourceType() == QWebEngineUrlRequestInfo.ResourceTypeObject) + + if self.__objectException: + return not match + else: + return match + + def matchSubdocument(self, req): + """ + Public slot to match a sub-document rule. + + @param req request object to check (QWebEngineUrlRequestInfo) + @return flag indicating a match (boolean) + """ + match = ( + req.resourceType() == + QWebEngineUrlRequestInfo.ResourceTypeSubFrame) + + if self.__subdocumentException: + return not match + else: + return match + + def matchXmlHttpRequest(self, req): + """ + Public slot to match a XmlHttpRequest rule. + + @param req request object to check (QWebEngineUrlRequestInfo) + @return flag indicating a match (boolean) + """ + match = ( + req.resourceType() == QWebEngineUrlRequestInfo.ResourceTypeXhr) + + if self.__xmlhttprequestException: + return not match + else: + return match + + def matchImage(self, req): + """ + Public slot to match an Image rule. + + @param req request object to check (QWebEngineUrlRequestInfo) + @return flag indicating a match (boolean) + """ + match = ( + req.resourceType() == QWebEngineUrlRequestInfo.ResourceTypeImage) + + if self.__imageException: + return not match + else: + return match + + def matchScript(self, req): + """ + Public slot to match a Script rule. + + @param req request object to check (QWebEngineUrlRequestInfo) + @return flag indicating a match (boolean) + """ + match = ( + req.resourceType() == QWebEngineUrlRequestInfo.ResourceTypeScript) + + if self.__scriptException: + return not match + else: + return match + + def matchStyleSheet(self, req): + """ + Public slot to match a StyleSheet rule. + + @param req request object to check (QWebEngineUrlRequestInfo) + @return flag indicating a match (boolean) + """ + match = ( + req.resourceType() == + QWebEngineUrlRequestInfo.ResourceTypeStylesheet) + + if self.__stylesheetException: + return not match + else: + return match + + def matchObjectSubrequest(self, req): + """ + Public slot to match an Object Subrequest rule. + + @param req request object to check (QWebEngineUrlRequestInfo) + @return flag indicating a match (boolean) + """ + match = ( + req.resourceType() == + QWebEngineUrlRequestInfo.ResourceTypeSubResource) + + if self.__objectSubrequestException: + return not match + else: + return match + + def isException(self): + """ + Public method to check, if the rule defines an exception. + + @return flag indicating an exception (boolean) + """ + return self.__exception + + def setException(self, exception): + """ + Public method to set the rule's exception flag. + + @param exception flag indicating an exception rule (boolean) + """ + self.__exception = exception + + def isEnabled(self): + """ + Public method to check, if the rule is enabled. + + @return flag indicating enabled state (boolean) + """ + return self.__enabled + + def setEnabled(self, enabled): + """ + Public method to set the rule's enabled state. + + @param enabled flag indicating the new enabled state (boolean) + """ + self.__enabled = enabled + if not enabled: + self.__filter = "!" + self.__filter + else: + self.__filter = self.__filter[1:] + + def isCSSRule(self): + """ + Public method to check, if the rule is a CSS rule. + + @return flag indicating a CSS rule (boolean) + """ + return self.__cssRule + + def cssSelector(self): + """ + Public method to get the CSS selector of the rule. + + @return CSS selector (string) + """ + return self.__cssSelector + + def isDocument(self): + """ + Public method to check, if this is a document rule. + + @return flag indicating a document rule (boolean) + """ + return self.__document + + def isElementHiding(self): + """ + Public method to check, if this is an element hiding rule. + + @return flag indicating an element hiding rule (boolean) + """ + return self.__elemhide + + def isDomainRestricted(self): + """ + Public method to check, if this rule is restricted by domain. + + @return flag indicating a domain restriction (boolean) + """ + return self.__domainRestricted + + def isComment(self): + """ + Public method to check, if this is a comment. + + @return flag indicating a comment (boolean) + """ + return self.__filter.startswith("!") + + def isHeader(self): + """ + Public method to check, if this is a header. + + @return flag indicating a header (boolean) + """ + return self.__filter.startswith("[Adblock") + + def isSlow(self): + """ + Public method to check, if this is a slow rule. + + @return flag indicating a slow rule (boolean) + """ + return self.__useRegExp + + def isInternalDisabled(self): + """ + Public method to check, if this rule was disabled internally. + + @return flag indicating an internally disabled rule (boolean) + """ + return self.__internalDisabled + + def __convertPatternToRegExp(self, wildcardPattern): + """ + Private method to convert a wildcard pattern to a regular expression. + + @param wildcardPattern string containing the wildcard pattern (string) + @return string containing a regular expression (string) + """ + pattern = wildcardPattern + + # remove multiple wildcards + pattern = re.sub(r"\*+", "*", pattern) + # remove anchors following separator placeholder + pattern = re.sub(r"\^\|$", "^", pattern) + # remove leading wildcards + pattern = re.sub(r"^(\*)", "", pattern) + # remove trailing wildcards + pattern = re.sub(r"(\*)$", "", pattern) + # escape special symbols + pattern = re.sub(r"(\W)", r"\\\1", pattern) + # process extended anchor at expression start + pattern = re.sub( + r"^\\\|\\\|", + r"^[\w\-]+:\/+(?!\/)(?:[^\/]+\.)?", pattern) + # process separator placeholders + pattern = re.sub(r"\\\^", r"(?:[^\w\d\-.%]|$)", pattern) + # process anchor at expression start + pattern = re.sub(r"^\\\|", "^", pattern) + # process anchor at expression end + pattern = re.sub(r"\\\|$", "$", pattern) + # replace wildcards by .* + pattern = re.sub(r"\\\*", ".*", pattern) + + return pattern
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/WebBrowser/AdBlock/AdBlockSubscription.py Sun Apr 03 16:33:37 2016 +0200 @@ -0,0 +1,716 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2009 - 2016 Detlev Offenbach <detlev@die-offenbachs.de> +# + +""" +Module implementing the AdBlock subscription class. +""" + +from __future__ import unicode_literals + +import os +import re +import hashlib +import base64 + +from PyQt5.QtCore import pyqtSignal, Qt, QObject, QByteArray, QDateTime, \ + QUrl, QUrlQuery, QCryptographicHash, QFile, QIODevice, QTextStream, \ + QDate, QTime +from PyQt5.QtNetwork import QNetworkReply, QNetworkRequest + +from E5Gui import E5MessageBox + +import Utilities +import Preferences + + +class AdBlockSubscription(QObject): + """ + Class implementing the AdBlock subscription. + + @signal changed() emitted after the subscription has changed + @signal rulesChanged() emitted after the subscription's rules have changed + @signal enabledChanged(bool) emitted after the enabled state was changed + """ + changed = pyqtSignal() + rulesChanged = pyqtSignal() + enabledChanged = pyqtSignal(bool) + + def __init__(self, url, custom, parent=None, default=False): + """ + Constructor + + @param url AdBlock URL for the subscription (QUrl) + @param custom flag indicating a custom subscription (boolean) + @param parent reference to the parent object (QObject) + @param default flag indicating a default subscription (boolean) + """ + super(AdBlockSubscription, self).__init__(parent) + + self.__custom = custom + self.__url = url.toEncoded() + self.__enabled = False + self.__downloading = None + self.__defaultSubscription = default + + self.__title = "" + self.__location = QByteArray() + self.__lastUpdate = QDateTime() + self.__requiresLocation = "" + self.__requiresTitle = "" + + self.__updatePeriod = 0 # update period in hours, 0 = use default + self.__remoteModified = QDateTime() + + self.__rules = [] # list containing all AdBlock rules + + self.__networkExceptionRules = [] + self.__networkBlockRules = [] + self.__domainRestrictedCssRules = [] + self.__elementHidingRules = "" + self.__documentRules = [] + self.__elemhideRules = [] + + self.__checksumRe = re.compile( + r"""^\s*!\s*checksum[\s\-:]+([\w\+\/=]+).*\n""", + re.IGNORECASE | re.MULTILINE) + self.__expiresRe = re.compile( + r"""(?:expires:|expires after)\s*(\d+)\s*(hour|h)?""", + re.IGNORECASE) + self.__remoteModifiedRe = re.compile( + r"""!\s*(?:Last modified|Updated):\s*(\d{1,2})\s*""" + r"""(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\s*""" + r"""(\d{2,4})\s*((\d{1,2}):(\d{2}))?""", + re.IGNORECASE) + + self.__monthNameToNumber = { + "Jan": 1, + "Feb": 2, + "Mar": 3, + "Apr": 4, + "May": 5, + "Jun": 6, + "Jul": 7, + "Aug": 8, + "Sep": 9, + "Oct": 10, + "Nov": 11, + "Dec": 12 + } + + self.__parseUrl(url) + + def __parseUrl(self, url): + """ + Private method to parse the AdBlock URL for the subscription. + + @param url AdBlock URL for the subscription (QUrl) + """ + if url.scheme() != "abp": + return + + if url.path() != "subscribe": + return + + urlQuery = QUrlQuery(url) + self.__title = QUrl.fromPercentEncoding( + QByteArray(urlQuery.queryItemValue("title").encode())) + self.__enabled = urlQuery.queryItemValue("enabled") != "false" + self.__location = QByteArray(QUrl.fromPercentEncoding( + QByteArray(urlQuery.queryItemValue("location").encode())) + .encode("utf-8")) + + # Check for required subscription + self.__requiresLocation = QUrl.fromPercentEncoding( + QByteArray(urlQuery.queryItemValue( + "requiresLocation").encode())) + self.__requiresTitle = QUrl.fromPercentEncoding( + QByteArray(urlQuery.queryItemValue("requiresTitle").encode())) + if self.__requiresLocation and self.__requiresTitle: + from WebBrowser.WebBrowserWindow import WebBrowserWindow + WebBrowserWindow.adBlockManager().loadRequiredSubscription( + self.__requiresLocation, self.__requiresTitle) + + lastUpdateString = urlQuery.queryItemValue("lastUpdate") + self.__lastUpdate = QDateTime.fromString(lastUpdateString, + Qt.ISODate) + + self.__loadRules() + + def url(self): + """ + Public method to generate the URL for this subscription. + + @return AdBlock URL for the subscription (QUrl) + """ + url = QUrl() + url.setScheme("abp") + url.setPath("subscribe") + + queryItems = [] + queryItems.append(("location", bytes(self.__location).decode())) + queryItems.append(("title", self.__title)) + if self.__requiresLocation and self.__requiresTitle: + queryItems.append(("requiresLocation", self.__requiresLocation)) + queryItems.append(("requiresTitle", self.__requiresTitle)) + if not self.__enabled: + queryItems.append(("enabled", "false")) + if self.__lastUpdate.isValid(): + queryItems.append(("lastUpdate", + self.__lastUpdate.toString(Qt.ISODate))) + + query = QUrlQuery() + query.setQueryItems(queryItems) + url.setQuery(query) + return url + + def isEnabled(self): + """ + Public method to check, if the subscription is enabled. + + @return flag indicating the enabled status (boolean) + """ + return self.__enabled + + def setEnabled(self, enabled): + """ + Public method to set the enabled status. + + @param enabled flag indicating the enabled status (boolean) + """ + if self.__enabled == enabled: + return + + self.__enabled = enabled + self.enabledChanged.emit(enabled) + + def title(self): + """ + Public method to get the subscription title. + + @return subscription title (string) + """ + return self.__title + + def setTitle(self, title): + """ + Public method to set the subscription title. + + @param title subscription title (string) + """ + if self.__title == title: + return + + self.__title = title + self.changed.emit() + + def location(self): + """ + Public method to get the subscription location. + + @return URL of the subscription location (QUrl) + """ + return QUrl.fromEncoded(self.__location) + + def setLocation(self, url): + """ + Public method to set the subscription location. + + @param url URL of the subscription location (QUrl) + """ + if url == self.location(): + return + + self.__location = url.toEncoded() + self.__lastUpdate = QDateTime() + self.changed.emit() + + def requiresLocation(self): + """ + Public method to get the location of a required subscription. + + @return location of a required subscription (string) + """ + return self.__requiresLocation + + def lastUpdate(self): + """ + Public method to get the date and time of the last update. + + @return date and time of the last update (QDateTime) + """ + return self.__lastUpdate + + def rulesFileName(self): + """ + Public method to get the name of the rules file. + + @return name of the rules file (string) + """ + if self.location().scheme() == "file": + return self.location().toLocalFile() + + if self.__location.isEmpty(): + return "" + + sha1 = bytes(QCryptographicHash.hash( + self.__location, QCryptographicHash.Sha1).toHex()).decode() + dataDir = os.path.join( + Utilities.getConfigDir(), "web_browser", "subscriptions") + if not os.path.exists(dataDir): + os.makedirs(dataDir) + fileName = os.path.join( + dataDir, "adblock_subscription_{0}".format(sha1)) + return fileName + + def __loadRules(self): + """ + Private method to load the rules of the subscription. + """ + fileName = self.rulesFileName() + f = QFile(fileName) + if f.exists(): + if not f.open(QIODevice.ReadOnly): + E5MessageBox.warning( + None, + self.tr("Load subscription rules"), + self.tr( + """Unable to open AdBlock file '{0}' for reading.""") + .format(fileName)) + else: + textStream = QTextStream(f) + header = textStream.readLine(1024) + if not header.startswith("[Adblock"): + E5MessageBox.warning( + None, + self.tr("Load subscription rules"), + self.tr("""AdBlock file '{0}' does not start""" + """ with [Adblock.""") + .format(fileName)) + f.close() + f.remove() + self.__lastUpdate = QDateTime() + else: + from .AdBlockRule import AdBlockRule + + self.__updatePeriod = 0 + self.__remoteModified = QDateTime() + self.__rules = [] + self.__rules.append(AdBlockRule(header, self)) + while not textStream.atEnd(): + line = textStream.readLine() + self.__rules.append(AdBlockRule(line, self)) + expires = self.__expiresRe.search(line) + if expires: + period, kind = expires.groups() + if kind: + # hours + self.__updatePeriod = int(period) + else: + # days + self.__updatePeriod = int(period) * 24 + remoteModified = self.__remoteModifiedRe.search(line) + if remoteModified: + day, month, year, time, hour, minute = \ + remoteModified.groups() + self.__remoteModified.setDate( + QDate(int(year), + self.__monthNameToNumber[month], + int(day)) + ) + if time: + self.__remoteModified.setTime( + QTime(int(hour), int(minute))) + self.__populateCache() + self.changed.emit() + elif not fileName.endswith("_custom"): + self.__lastUpdate = QDateTime() + + self.checkForUpdate() + + def checkForUpdate(self): + """ + Public method to check for an update. + """ + if self.__updatePeriod: + updatePeriod = self.__updatePeriod + else: + updatePeriod = \ + Preferences.getWebBrowser("AdBlockUpdatePeriod") * 24 + if not self.__lastUpdate.isValid() or \ + (self.__remoteModified.isValid() and + self.__remoteModified.addSecs(updatePeriod * 3600) < + QDateTime.currentDateTime()) or \ + self.__lastUpdate.addSecs(updatePeriod * 3600) < \ + QDateTime.currentDateTime(): + self.updateNow() + + def updateNow(self): + """ + Public method to update the subscription immediately. + """ + if self.__downloading is not None: + return + + if not self.location().isValid(): + return + + if self.location().scheme() == "file": + self.__lastUpdate = QDateTime.currentDateTime() + self.__loadRules() + return + + from WebBrowser.WebBrowserWindow import WebBrowserWindow + self.__downloading = WebBrowserWindow.networkManager().get( + QNetworkRequest(self.location())) + self.__downloading.finished.connect(self.__rulesDownloaded) + + def __rulesDownloaded(self): + """ + Private slot to deal with the downloaded rules. + """ + reply = self.sender() + + response = reply.readAll() + reply.close() + self.__downloading = None + + if reply.error() != QNetworkReply.NoError: + if not self.__defaultSubscription: + # don't show error if we try to load the default + E5MessageBox.warning( + None, + self.tr("Downloading subscription rules"), + self.tr( + """<p>Subscription rules could not be""" + """ downloaded.</p><p>Error: {0}</p>""") + .format(reply.errorString())) + else: + # reset after first download attempt + self.__defaultSubscription = False + return + + if response.isEmpty(): + E5MessageBox.warning( + None, + self.tr("Downloading subscription rules"), + self.tr("""Got empty subscription rules.""")) + return + + fileName = self.rulesFileName() + QFile.remove(fileName) + f = QFile(fileName) + if not f.open(QIODevice.ReadWrite): + E5MessageBox.warning( + None, + self.tr("Downloading subscription rules"), + self.tr( + """Unable to open AdBlock file '{0}' for writing.""") + .file(fileName)) + return + + from WebBrowser.WebBrowserWindow import WebBrowserWindow + if WebBrowserWindow.adBlockManager().useLimitedEasyList() and \ + self.url().toString().startswith( + WebBrowserWindow.adBlockManager().getDefaultSubscriptionUrl()): + limited = True + # ignore Third-party advertisers rules for performance + # whitelist rules at the end will be used + index = response.indexOf( + "!---------------------------" + "Third-party advertisers" + "---------------------------!") + part1 = response.left(index) + index = response.indexOf( + "!-----------------------" + "Whitelists to fix broken sites" + "------------------------!") + part2 = response.mid(index) + f.write(part1) + f.write(part2) + else: + limited = False + f.write(response) + f.close() + self.__lastUpdate = QDateTime.currentDateTime() + if limited or self.__validateCheckSum(fileName): + self.__loadRules() + else: + QFile.remove(fileName) + self.__downloading = None + reply.deleteLater() + + def __validateCheckSum(self, fileName): + """ + Private method to check the subscription file's checksum. + + @param fileName name of the file containing the subscription (string) + @return flag indicating a valid file (boolean). A file is considered + valid, if the checksum is OK, the file does not contain a + checksum (i.e. cannot be checked) or we are using the limited + EasyList (because we fiddled with the original). + """ + try: + f = open(fileName, "r", encoding="utf-8") + data = f.read() + f.close() + except (IOError, OSError): + return False + + match = re.search(self.__checksumRe, data) + if match: + expectedChecksum = match.group(1) + else: + # consider it as valid + return True + + # normalize the data + data = re.sub(r"\r", "", data) # normalize eol + data = re.sub(r"\n+", "\n", data) # remove empty lines + data = re.sub(self.__checksumRe, "", data) # remove checksum line + + # calculate checksum + md5 = hashlib.md5() + md5.update(data.encode("utf-8")) + calculatedChecksum = base64.b64encode(md5.digest()).decode()\ + .rstrip("=") + if calculatedChecksum == expectedChecksum: + return True + else: + res = E5MessageBox.yesNo( + None, + self.tr("Downloading subscription rules"), + self.tr( + """<p>AdBlock subscription <b>{0}</b> has a wrong""" + """ checksum.<br/>""" + """Found: {1}<br/>""" + """Calculated: {2}<br/>""" + """Use it anyway?</p>""") + .format(self.__title, expectedChecksum, + calculatedChecksum)) + return res + + def saveRules(self): + """ + Public method to save the subscription rules. + """ + fileName = self.rulesFileName() + if not fileName: + return + + f = QFile(fileName) + if not f.open(QIODevice.ReadWrite | QIODevice.Truncate): + E5MessageBox.warning( + None, + self.tr("Saving subscription rules"), + self.tr( + """Unable to open AdBlock file '{0}' for writing.""") + .format(fileName)) + return + + textStream = QTextStream(f) + if not self.__rules or not self.__rules[0].isHeader(): + textStream << "[Adblock Plus 1.1.1]\n" + for rule in self.__rules: + textStream << rule.filter() << "\n" + + def match(self, req, urlDomain, urlString): + """ + Public method to check the subscription for a matching rule. + + @param req reference to the network request (QWebEngineUrlRequestInfo) + @param urlDomain domain of the URL (string) + @param urlString URL (string) + @return reference to the rule object or None (AdBlockRule) + """ + for rule in self.__networkExceptionRules: + if rule.networkMatch(req, urlDomain, urlString): + return None + + for rule in self.__networkBlockRules: + if rule.networkMatch(req, urlDomain, urlString): + return rule + + return None + + def adBlockDisabledForUrl(self, url): + """ + Public method to check, if AdBlock is disabled for the given URL. + + @param url URL to check (QUrl) + @return flag indicating disabled state (boolean) + """ + for rule in self.__documentRules: + if rule.urlMatch(url): + return True + + return False + + def elemHideDisabledForUrl(self, url): + """ + Public method to check, if element hiding is disabled for the given + URL. + + @param url URL to check (QUrl) + @return flag indicating disabled state (boolean) + """ + if self.adBlockDisabledForUrl(url): + return True + + for rule in self.__elemhideRules: + if rule.urlMatch(url): + return True + + return False + + def elementHidingRules(self): + """ + Public method to get the element hiding rules. + + @return element hiding rules (string) + """ + return self.__elementHidingRules + + def elementHidingRulesForDomain(self, domain): + """ + Public method to get the element hiding rules for the given domain. + + @param domain domain name (string) + @return element hiding rules (string) + """ + rules = "" + + for rule in self.__domainRestrictedCssRules: + if rule.matchDomain(domain): + rules += rule.cssSelector() + "," + + return rules + + def rule(self, offset): + """ + Public method to get a specific rule. + + @param offset offset of the rule (integer) + @return requested rule (AdBlockRule) + """ + if offset >= len(self.__rules): + return None + + return self.__rules[offset] + + def allRules(self): + """ + Public method to get the list of rules. + + @return list of rules (list of AdBlockRule) + """ + return self.__rules[:] + + def addRule(self, rule): + """ + Public method to add a rule. + + @param rule reference to the rule to add (AdBlockRule) + @return offset of the rule (integer) + """ + self.__rules.append(rule) + self.__populateCache() + self.rulesChanged.emit() + + return len(self.__rules) - 1 + + def removeRule(self, offset): + """ + Public method to remove a rule given the offset. + + @param offset offset of the rule to remove (integer) + """ + if offset < 0 or offset > len(self.__rules): + return + + del self.__rules[offset] + self.__populateCache() + self.rulesChanged.emit() + + def replaceRule(self, rule, offset): + """ + Public method to replace a rule given the offset. + + @param rule reference to the rule to set (AdBlockRule) + @param offset offset of the rule to remove (integer) + @return requested rule (AdBlockRule) + """ + if offset >= len(self.__rules): + return None + + self.__rules[offset] = rule + self.__populateCache() + self.rulesChanged.emit() + + return self.__rules[offset] + + def __populateCache(self): + """ + Private method to populate the various rule caches. + """ + self.__networkExceptionRules = [] + self.__networkBlockRules = [] + self.__domainRestrictedCssRules = [] + self.__elementHidingRules = "" + self.__documentRules = [] + self.__elemhideRules = [] + + for rule in self.__rules: + if not rule.isEnabled(): + continue + + if rule.isCSSRule(): + if rule.isDomainRestricted(): + self.__domainRestrictedCssRules.append(rule) + else: + self.__elementHidingRules += rule.cssSelector() + "," + elif rule.isDocument(): + self.__documentRules.append(rule) + elif rule.isElementHiding(): + self.__elemhideRules.append(rule) + elif rule.isException(): + self.__networkExceptionRules.append(rule) + else: + self.__networkBlockRules.append(rule) + + def canEditRules(self): + """ + Public method to check, if rules can be edited. + + @return flag indicating rules may be edited (boolean) + """ + return self.__custom + + def canBeRemoved(self): + """ + Public method to check, if the subscription can be removed. + + @return flag indicating removal is allowed (boolean) + """ + return not self.__custom and not self.__defaultSubscription + + def setRuleEnabled(self, offset, enabled): + """ + Public method to enable a specific rule. + + @param offset offset of the rule (integer) + @param enabled new enabled state (boolean) + @return reference to the changed rule (AdBlockRule) + """ + if offset >= len(self.__rules): + return None + + rule = self.__rules[offset] + rule.setEnabled(enabled) + if rule.isCSSRule(): + from WebBrowser.WebBrowserWindow import WebBrowserWindow + self.__populateCache() + WebBrowserWindow.mainWindow().reloadUserStyleSheet() + + return rule
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/WebBrowser/AdBlock/AdBlockTreeWidget.py Sun Apr 03 16:33:37 2016 +0200 @@ -0,0 +1,262 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2012 - 2016 Detlev Offenbach <detlev@die-offenbachs.de> +# + +""" +Module implementing a tree widget for the AdBlock configuration dialog. +""" + +from __future__ import unicode_literals + +from PyQt5.QtCore import Qt +from PyQt5.QtGui import QFont, QColor +from PyQt5.QtWidgets import QAbstractItemView, QTreeWidgetItem, QInputDialog, \ + QLineEdit, QMenu, QApplication + +from E5Gui.E5TreeWidget import E5TreeWidget + + +class AdBlockTreeWidget(E5TreeWidget): + """ + Class implementing a tree widget for the AdBlock configuration dialog. + """ + def __init__(self, subscription, parent=None): + """ + Constructor + + @param subscription reference to the subscription (AdBlockSubscription) + @param parent reference to the parent widget (QWidget) + """ + super(AdBlockTreeWidget, self).__init__(parent) + + self.__subscription = subscription + self.__topItem = None + self.__ruleToBeSelected = "" + self.__itemChangingBlock = False + + self.setContextMenuPolicy(Qt.CustomContextMenu) + self.setDefaultItemShowMode(E5TreeWidget.ItemsExpanded) + self.setHeaderHidden(True) + self.setAlternatingRowColors(True) + + self.customContextMenuRequested.connect(self.__contextMenuRequested) + self.itemChanged.connect(self.__itemChanged) + self.__subscription.changed.connect(self.__subscriptionChanged) + self.__subscription.rulesChanged.connect(self.__subscriptionChanged) + + def subscription(self): + """ + Public method to get a reference to the subscription. + + @return reference to the subscription (AdBlockSubscription) + """ + return self.__subscription + + def showRule(self, rule): + """ + Public method to highlight the given rule. + + @param rule AdBlock rule to be shown (AdBlockRule) + """ + if rule: + self.__ruleToBeSelected = rule.filter() + if not self.__topItem: + return + if self.__ruleToBeSelected: + items = self.findItems(self.__ruleToBeSelected, Qt.MatchRecursive) + if items: + item = items[0] + self.setCurrentItem(item) + self.scrollToItem(item, QAbstractItemView.PositionAtCenter) + + self.__ruleToBeSelected = "" + + def refresh(self): + """ + Public method to refresh the tree. + """ + QApplication.setOverrideCursor(Qt.WaitCursor) + self.__itemChangingBlock = True + self.clear() + + boldFont = QFont() + boldFont.setBold(True) + + self.__topItem = QTreeWidgetItem(self) + self.__topItem.setText(0, self.__subscription.title()) + self.__topItem.setFont(0, boldFont) + self.addTopLevelItem(self.__topItem) + + allRules = self.__subscription.allRules() + + index = 0 + for rule in allRules: + item = QTreeWidgetItem(self.__topItem) + item.setText(0, rule.filter()) + item.setData(0, Qt.UserRole, index) + if self.__subscription.canEditRules(): + item.setFlags(item.flags() | Qt.ItemIsEditable) + self.__adjustItemFeatures(item, rule) + index += 1 + + self.expandAll() + self.showRule(None) + self.__itemChangingBlock = False + QApplication.restoreOverrideCursor() + QApplication.processEvents() + + def addRule(self, filter=""): + """ + Public slot to add a new rule. + + @param filter filter to be added (string) + """ + if not self.__subscription.canEditRules(): + return + + if not filter: + filter, ok = QInputDialog.getText( + self, + self.tr("Add Custom Rule"), + self.tr("Write your rule here:"), + QLineEdit.Normal) + if not ok or filter == "": + return + + from .AdBlockRule import AdBlockRule + rule = AdBlockRule(filter, self.__subscription) + self.__subscription.addRule(rule) + + def removeRule(self): + """ + Public slot to remove the current rule. + """ + item = self.currentItem() + if item is None or \ + not self.__subscription.canEditRules() or \ + item == self.__topItem: + return + + offset = item.data(0, Qt.UserRole) + self.__subscription.removeRule(offset) + self.deleteItem(item) + + def __contextMenuRequested(self, pos): + """ + Private slot to show the context menu. + + @param pos position for the menu (QPoint) + """ + if not self.__subscription.canEditRules(): + return + + item = self.itemAt(pos) + if item is None: + return + + menu = QMenu() + menu.addAction(self.tr("Add Rule"), self.addRule) + menu.addSeparator() + act = menu.addAction(self.tr("Remove Rule"), self.removeRule) + if item.parent() is None: + act.setDisabled(True) + + menu.exec_(self.viewport().mapToGlobal(pos)) + + def __itemChanged(self, itm): + """ + Private slot to handle the change of an item. + + @param itm changed item (QTreeWidgetItem) + """ + if itm is None or self.__itemChangingBlock: + return + + self.__itemChangingBlock = True + + offset = itm.data(0, Qt.UserRole) + oldRule = self.__subscription.rule(offset) + + if itm.checkState(0) == Qt.Unchecked and oldRule.isEnabled(): + # Disable rule + rule = self.__subscription.setRuleEnabled(offset, False) + self.__adjustItemFeatures(itm, rule) + elif itm.checkState(0) == Qt.Checked and not oldRule.isEnabled(): + # Enable rule + rule = self.__subscription.setRuleEnabled(offset, True) + self.__adjustItemFeatures(itm, rule) + elif self.__subscription.canEditRules(): + from .AdBlockRule import AdBlockRule + # Custom rule has been changed + rule = self.__subscription.replaceRule( + AdBlockRule(itm.text(0), self.__subscription), offset) + self.__adjustItemFeatures(itm, rule) + + self.__itemChangingBlock = False + + def __copyFilter(self): + """ + Private slot to copy the current filter to the clipboard. + """ + item = self.currentItem() + if item is not None: + QApplication.clipboard().setText(item.text(0)) + + def __subscriptionChanged(self): + """ + Private slot handling a subscription change. + """ + self.refresh() + + self.__itemChangingBlock = True + self.__topItem.setText( + 0, self.tr("{0} (recently updated)").format( + self.__subscription.title())) + self.__itemChangingBlock = False + + def __adjustItemFeatures(self, itm, rule): + """ + Private method to adjust an item. + + @param itm item to be adjusted (QTreeWidgetItem) + @param rule rule for the adjustment (AdBlockRule) + """ + if not rule.isEnabled(): + font = QFont() + font.setItalic(True) + itm.setForeground(0, QColor(Qt.gray)) + + if not rule.isComment() and not rule.isHeader(): + itm.setFlags(itm.flags() | Qt.ItemIsUserCheckable) + itm.setCheckState(0, Qt.Unchecked) + itm.setFont(0, font) + + return + + itm.setFlags(itm.flags() | Qt.ItemIsUserCheckable) + itm.setCheckState(0, Qt.Checked) + + if rule.isCSSRule(): + itm.setForeground(0, QColor(Qt.darkBlue)) + itm.setFont(0, QFont()) + elif rule.isException(): + itm.setForeground(0, QColor(Qt.darkGreen)) + itm.setFont(0, QFont()) + else: + itm.setForeground(0, QColor()) + itm.setFont(0, QFont()) + + def keyPressEvent(self, evt): + """ + Protected method handling key presses. + + @param evt key press event (QKeyEvent) + """ + if evt.key() == Qt.Key_C and \ + evt.modifiers() & Qt.ControlModifier: + self.__copyFilter() + elif evt.key() == Qt.Key_Delete: + self.removeRule() + else: + super(AdBlockTreeWidget, self).keyPressEvent(evt)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/WebBrowser/AdBlock/AdBlockUrlInterceptor.py Sun Apr 03 16:33:37 2016 +0200 @@ -0,0 +1,39 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2016 Detlev Offenbach <detlev@die-offenbachs.de> +# + +""" +Module implementing an URL interceptor base class. +""" + +from __future__ import unicode_literals + +from ..Network.UrlInterceptor import UrlInterceptor + +class AdBlockUrlInterceptor(UrlInterceptor): + """ + Class implementing an URL interceptor for AdBlock. + """ + def __init__(self, manager, parent=None): + """ + Constructor + + @param manager reference to the AdBlock manager + @type AdBlockManager + @param parent referemce to the parent object + @type QObject + """ + super(AdBlockUrlInterceptor, self).__init__(parent) + + self.__manager = manager + + def interceptRequest(self, info): + """ + Public method to intercept a request. + + @param info request info object + @type QWebEngineUrlRequestInfo + """ + if self.__manager.block(info): + info.block(True)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/WebBrowser/AdBlock/__init__.py Sun Apr 03 16:33:37 2016 +0200 @@ -0,0 +1,8 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2016 Detlev Offenbach <detlev@die-offenbachs.de> +# + +""" +Package implementing the advertisements blocker functionality. +"""
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/WebBrowser/Bookmarks/AddBookmarkDialog.py Sun Apr 03 16:33:37 2016 +0200 @@ -0,0 +1,248 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2009 - 2016 Detlev Offenbach <detlev@die-offenbachs.de> +# + +""" +Module implementing a dialog to add a bookmark or a bookmark folder. +""" + +from __future__ import unicode_literals + +from PyQt5.QtCore import QModelIndex, QSortFilterProxyModel +from PyQt5.QtWidgets import QDialog, QTreeView + +from .Ui_AddBookmarkDialog import Ui_AddBookmarkDialog + + +class AddBookmarkProxyModel(QSortFilterProxyModel): + """ + Class implementing a proxy model used by the AddBookmarkDialog dialog. + """ + def __init__(self, parent=None): + """ + Constructor + + @param parent reference to the parent object (QObject) + """ + super(AddBookmarkProxyModel, self).__init__(parent) + + def columnCount(self, parent): + """ + Public method to return the number of columns. + + @param parent index of the parent (QModelIndex) + @return number of columns (integer) + """ + return min(1, QSortFilterProxyModel.columnCount(self, parent)) + + def filterAcceptsRow(self, sourceRow, sourceParent): + """ + Public method to determine, if the row is acceptable. + + @param sourceRow row number in the source model (integer) + @param sourceParent index of the source item (QModelIndex) + @return flag indicating acceptance (boolean) + """ + idx = self.sourceModel().index(sourceRow, 0, sourceParent) + return self.sourceModel().hasChildren(idx) + + def filterAcceptsColumn(self, sourceColumn, sourceParent): + """ + Public method to determine, if the column is acceptable. + + @param sourceColumn column number in the source model (integer) + @param sourceParent index of the source item (QModelIndex) + @return flag indicating acceptance (boolean) + """ + return sourceColumn == 0 + + def hasChildren(self, parent=QModelIndex()): + """ + Public method to check, if a parent node has some children. + + @param parent index of the parent node (QModelIndex) + @return flag indicating the presence of children (boolean) + """ + sindex = self.mapToSource(parent) + return self.sourceModel().hasChildren(sindex) + + +class AddBookmarkDialog(QDialog, Ui_AddBookmarkDialog): + """ + Class implementing a dialog to add a bookmark or a bookmark folder. + """ + def __init__(self, parent=None, bookmarksManager=None): + """ + Constructor + + @param parent reference to the parent widget (QWidget) + @param bookmarksManager reference to the bookmarks manager + object (BookmarksManager) + """ + super(AddBookmarkDialog, self).__init__(parent) + self.setupUi(self) + + self.__bookmarksManager = bookmarksManager + self.__addedNode = None + self.__addFolder = False + + if self.__bookmarksManager is None: + import WebBrowser.WebBrowserWindow + self.__bookmarksManager = \ + WebBrowser.WebBrowserWindow.WebBrowserWindow.bookmarksManager() + + self.__proxyModel = AddBookmarkProxyModel(self) + model = self.__bookmarksManager.bookmarksModel() + self.__proxyModel.setSourceModel(model) + + self.__treeView = QTreeView(self) + self.__treeView.setModel(self.__proxyModel) + self.__treeView.expandAll() + self.__treeView.header().setStretchLastSection(True) + self.__treeView.header().hide() + self.__treeView.setItemsExpandable(False) + self.__treeView.setRootIsDecorated(False) + self.__treeView.setIndentation(10) + self.__treeView.show() + + self.locationCombo.setModel(self.__proxyModel) + self.locationCombo.setView(self.__treeView) + + self.addressEdit.setInactiveText(self.tr("Url")) + self.nameEdit.setInactiveText(self.tr("Title")) + + self.resize(self.sizeHint()) + + def setUrl(self, url): + """ + Public slot to set the URL of the new bookmark. + + @param url URL of the bookmark (string) + """ + self.addressEdit.setText(url) + self.resize(self.sizeHint()) + + def url(self): + """ + Public method to get the URL of the bookmark. + + @return URL of the bookmark (string) + """ + return self.addressEdit.text() + + def setTitle(self, title): + """ + Public method to set the title of the new bookmark. + + @param title title of the bookmark (string) + """ + self.nameEdit.setText(title) + + def title(self): + """ + Public method to get the title of the bookmark. + + @return title of the bookmark (string) + """ + return self.nameEdit.text() + + def setDescription(self, description): + """ + Public method to set the description of the new bookmark. + + @param description description of the bookamrk (string) + """ + self.descriptionEdit.setPlainText(description) + + def description(self): + """ + Public method to get the description of the bookmark. + + @return description of the bookamrk (string) + """ + return self.descriptionEdit.toPlainText() + + def setCurrentIndex(self, idx): + """ + Public method to set the current index. + + @param idx current index to be set (QModelIndex) + """ + proxyIndex = self.__proxyModel.mapFromSource(idx) + self.__treeView.setCurrentIndex(proxyIndex) + self.locationCombo.setCurrentIndex(proxyIndex.row()) + + def currentIndex(self): + """ + Public method to get the current index. + + @return current index (QModelIndex) + """ + idx = self.locationCombo.view().currentIndex() + idx = self.__proxyModel.mapToSource(idx) + return idx + + def setFolder(self, folder): + """ + Public method to set the dialog to "Add Folder" mode. + + @param folder flag indicating "Add Folder" mode (boolean) + """ + self.__addFolder = folder + + if folder: + self.setWindowTitle(self.tr("Add Folder")) + self.addressEdit.setVisible(False) + else: + self.setWindowTitle(self.tr("Add Bookmark")) + self.addressEdit.setVisible(True) + + self.resize(self.sizeHint()) + + def isFolder(self): + """ + Public method to test, if the dialog is in "Add Folder" mode. + + @return flag indicating "Add Folder" mode (boolean) + """ + return self.__addFolder + + def addedNode(self): + """ + Public method to get a reference to the added bookmark node. + + @return reference to the added bookmark node (BookmarkNode) + """ + return self.__addedNode + + def accept(self): + """ + Public slot handling the acceptance of the dialog. + """ + if (not self.__addFolder and not self.addressEdit.text()) or \ + not self.nameEdit.text(): + super(AddBookmarkDialog, self).accept() + return + + from .BookmarkNode import BookmarkNode + + idx = self.currentIndex() + if not idx.isValid(): + idx = self.__bookmarksManager.bookmarksModel().index(0, 0) + parent = self.__bookmarksManager.bookmarksModel().node(idx) + + if self.__addFolder: + type_ = BookmarkNode.Folder + else: + type_ = BookmarkNode.Bookmark + bookmark = BookmarkNode(type_) + bookmark.title = self.nameEdit.text() + if not self.__addFolder: + bookmark.url = self.addressEdit.text() + bookmark.desc = self.descriptionEdit.toPlainText() + + self.__bookmarksManager.addBookmark(parent, bookmark) + self.__addedNode = bookmark + + super(AddBookmarkDialog, self).accept()
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/WebBrowser/Bookmarks/AddBookmarkDialog.ui Sun Apr 03 16:33:37 2016 +0200 @@ -0,0 +1,148 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>AddBookmarkDialog</class> + <widget class="QDialog" name="AddBookmarkDialog"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>500</width> + <height>230</height> + </rect> + </property> + <property name="minimumSize"> + <size> + <width>500</width> + <height>0</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>500</width> + <height>250</height> + </size> + </property> + <property name="windowTitle"> + <string>Add Bookmark</string> + </property> + <property name="sizeGripEnabled"> + <bool>true</bool> + </property> + <layout class="QGridLayout" name="gridLayout"> + <item row="0" column="0"> + <widget class="QLabel" name="label_2"> + <property name="text"> + <string>Name:</string> + </property> + </widget> + </item> + <item row="0" column="1"> + <widget class="E5LineEdit" name="nameEdit"> + <property name="toolTip"> + <string>Enter the name</string> + </property> + </widget> + </item> + <item row="1" column="0"> + <widget class="QLabel" name="label_3"> + <property name="text"> + <string>Address:</string> + </property> + </widget> + </item> + <item row="1" column="1"> + <widget class="E5LineEdit" name="addressEdit"> + <property name="toolTip"> + <string>Enter the address</string> + </property> + </widget> + </item> + <item row="2" column="0"> + <widget class="QLabel" name="label"> + <property name="text"> + <string>Description:</string> + </property> + <property name="alignment"> + <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set> + </property> + </widget> + </item> + <item row="2" column="1"> + <widget class="QPlainTextEdit" name="descriptionEdit"> + <property name="toolTip"> + <string>Enter a description</string> + </property> + </widget> + </item> + <item row="3" column="0"> + <widget class="QLabel" name="label_4"> + <property name="text"> + <string>Folder:</string> + </property> + </widget> + </item> + <item row="3" column="1"> + <widget class="QComboBox" name="locationCombo"/> + </item> + <item row="4" column="0" colspan="2"> + <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> + <customwidgets> + <customwidget> + <class>E5LineEdit</class> + <extends>QLineEdit</extends> + <header>E5Gui/E5LineEdit.h</header> + </customwidget> + </customwidgets> + <tabstops> + <tabstop>nameEdit</tabstop> + <tabstop>addressEdit</tabstop> + <tabstop>descriptionEdit</tabstop> + <tabstop>locationCombo</tabstop> + <tabstop>buttonBox</tabstop> + </tabstops> + <resources/> + <connections> + <connection> + <sender>buttonBox</sender> + <signal>accepted()</signal> + <receiver>AddBookmarkDialog</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>AddBookmarkDialog</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/Bookmarks/BookmarkNode.py Sun Apr 03 16:33:37 2016 +0200 @@ -0,0 +1,111 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2009 - 2016 Detlev Offenbach <detlev@die-offenbachs.de> +# + +""" +Module implementing the bookmark node. +""" + +from __future__ import unicode_literals + +from PyQt5.QtCore import QDateTime + + +class BookmarkNode(object): + """ + Class implementing the bookmark node type. + """ + # possible bookmark node types + Root = 0 + Folder = 1 + Bookmark = 2 + Separator = 3 + + # possible timestamp types + TsAdded = 0 + TsModified = 1 + TsVisited = 2 + + def __init__(self, type_=Root, parent=None): + """ + Constructor + + @param type_ type of the bookmark node (BookmarkNode.Type) + @param parent reference to the parent node (BookmarkNode) + """ + self.url = "" + self.title = "" + self.desc = "" + self.expanded = False + self.added = QDateTime() + self.modified = QDateTime() + self.visited = QDateTime() + + self._children = [] + self._parent = parent + self._type = type_ + + if parent is not None: + parent.add(self) + + def type(self): + """ + Public method to get the bookmark's type. + + @return bookmark type (BookmarkNode.Type) + """ + return self._type + + def setType(self, type_): + """ + Public method to set the bookmark's type. + + @param type_ type of the bookmark node (BookmarkNode.Type) + """ + self._type = type_ + + def children(self): + """ + Public method to get the list of child nodes. + + @return list of all child nodes (list of BookmarkNode) + """ + return self._children[:] + + def parent(self): + """ + Public method to get a reference to the parent node. + + @return reference to the parent node (BookmarkNode) + """ + return self._parent + + def add(self, child, offset=-1): + """ + Public method to add/insert a child node. + + @param child reference to the node to add (BookmarkNode) + @param offset position where to insert child (integer, -1 = append) + """ + if child._type == BookmarkNode.Root: + return + + if child._parent is not None: + child._parent.remove(child) + + child._parent = self + if offset == -1: + self._children.append(child) + else: + self._children.insert(offset, child) + + def remove(self, child): + """ + Public method to remove a child node. + + @param child reference to the child node (BookmarkNode) + """ + child._parent = None + if child in self._children: + self._children.remove(child)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/WebBrowser/Bookmarks/BookmarkPropertiesDialog.py Sun Apr 03 16:33:37 2016 +0200 @@ -0,0 +1,68 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2012 - 2016 Detlev Offenbach <detlev@die-offenbachs.de> +# + +""" +Module implementing a dialog to show and edit bookmark properties. +""" + +from __future__ import unicode_literals + +from PyQt5.QtWidgets import QDialog + +from .Ui_BookmarkPropertiesDialog import Ui_BookmarkPropertiesDialog + + +class BookmarkPropertiesDialog(QDialog, Ui_BookmarkPropertiesDialog): + """ + Class implementing a dialog to show and edit bookmark properties. + """ + def __init__(self, node, parent=None): + """ + Constructor + + @param node reference to the bookmark (BookmarkNode) + @param parent reference to the parent widget (QWidget) + """ + super(BookmarkPropertiesDialog, self).__init__(parent) + self.setupUi(self) + + from .BookmarkNode import BookmarkNode + self.__node = node + if self.__node.type() == BookmarkNode.Folder: + self.addressLabel.hide() + self.addressEdit.hide() + + self.nameEdit.setText(self.__node.title) + self.descriptionEdit.setPlainText(self.__node.desc) + self.addressEdit.setText(self.__node.url) + + def accept(self): + """ + Public slot handling the acceptance of the dialog. + """ + from .BookmarkNode import BookmarkNode + + if (self.__node.type() == BookmarkNode.Bookmark and + not self.addressEdit.text()) or \ + not self.nameEdit.text(): + super(BookmarkPropertiesDialog, self).accept() + return + + import WebBrowser.WebBrowserWindow + bookmarksManager = WebBrowser.WebBrowserWindow.WebBrowserWindow\ + .bookmarksManager() + title = self.nameEdit.text() + if title != self.__node.title: + bookmarksManager.setTitle(self.__node, title) + if self.__node.type() == BookmarkNode.Bookmark: + url = self.addressEdit.text() + if url != self.__node.url: + bookmarksManager.setUrl(self.__node, url) + description = self.descriptionEdit.toPlainText() + if description != self.__node.desc: + self.__node.desc = description + bookmarksManager.setNodeChanged(self.__node) + + super(BookmarkPropertiesDialog, self).accept()
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/WebBrowser/Bookmarks/BookmarkPropertiesDialog.ui Sun Apr 03 16:33:37 2016 +0200 @@ -0,0 +1,137 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>BookmarkPropertiesDialog</class> + <widget class="QDialog" name="BookmarkPropertiesDialog"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>500</width> + <height>221</height> + </rect> + </property> + <property name="minimumSize"> + <size> + <width>500</width> + <height>0</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>500</width> + <height>250</height> + </size> + </property> + <property name="windowTitle"> + <string>Bookmark Properties</string> + </property> + <property name="sizeGripEnabled"> + <bool>true</bool> + </property> + <layout class="QGridLayout" name="gridLayout"> + <item row="0" column="0"> + <widget class="QLabel" name="label_2"> + <property name="text"> + <string>Name:</string> + </property> + </widget> + </item> + <item row="0" column="1"> + <widget class="E5LineEdit" name="nameEdit"> + <property name="toolTip"> + <string>Enter the name</string> + </property> + </widget> + </item> + <item row="1" column="0"> + <widget class="QLabel" name="addressLabel"> + <property name="text"> + <string>Address:</string> + </property> + </widget> + </item> + <item row="1" column="1"> + <widget class="E5LineEdit" name="addressEdit"> + <property name="toolTip"> + <string>Enter the address</string> + </property> + </widget> + </item> + <item row="2" column="0"> + <widget class="QLabel" name="label"> + <property name="text"> + <string>Description:</string> + </property> + <property name="alignment"> + <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set> + </property> + </widget> + </item> + <item row="2" column="1"> + <widget class="QPlainTextEdit" name="descriptionEdit"> + <property name="toolTip"> + <string>Enter a description</string> + </property> + </widget> + </item> + <item row="3" column="0" colspan="2"> + <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> + <customwidgets> + <customwidget> + <class>E5LineEdit</class> + <extends>QLineEdit</extends> + <header>E5Gui/E5LineEdit.h</header> + </customwidget> + </customwidgets> + <tabstops> + <tabstop>nameEdit</tabstop> + <tabstop>addressEdit</tabstop> + <tabstop>descriptionEdit</tabstop> + <tabstop>buttonBox</tabstop> + </tabstops> + <resources/> + <connections> + <connection> + <sender>buttonBox</sender> + <signal>accepted()</signal> + <receiver>BookmarkPropertiesDialog</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>BookmarkPropertiesDialog</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/Bookmarks/BookmarksDialog.py Sun Apr 03 16:33:37 2016 +0200 @@ -0,0 +1,266 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2009 - 2016 Detlev Offenbach <detlev@die-offenbachs.de> +# + +""" +Module implementing a dialog to manage bookmarks. +""" + +from __future__ import unicode_literals + +from PyQt5.QtCore import pyqtSignal, Qt, QUrl, QModelIndex +from PyQt5.QtGui import QFontMetrics, QCursor +from PyQt5.QtWidgets import QDialog, QMenu, QApplication + +from E5Gui.E5TreeSortFilterProxyModel import E5TreeSortFilterProxyModel + +from .Ui_BookmarksDialog import Ui_BookmarksDialog + + +class BookmarksDialog(QDialog, Ui_BookmarksDialog): + """ + Class implementing a dialog to manage bookmarks. + + @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) + + def __init__(self, parent=None, manager=None): + """ + Constructor + + @param parent reference to the parent widget (QWidget + @param manager reference to the bookmarks manager object + (BookmarksManager) + """ + super(BookmarksDialog, self).__init__(parent) + self.setupUi(self) + self.setWindowFlags(Qt.Window) + + self.__bookmarksManager = manager + if self.__bookmarksManager is None: + import WebBrowser.WebBrowserWindow + self.__bookmarksManager = WebBrowser.WebBrowserWindow\ + .WebBrowserWindow.bookmarksManager() + + self.__bookmarksModel = self.__bookmarksManager.bookmarksModel() + self.__proxyModel = E5TreeSortFilterProxyModel(self) + self.__proxyModel.setFilterKeyColumn(-1) + self.__proxyModel.setSourceModel(self.__bookmarksModel) + + self.searchEdit.textChanged.connect( + self.__proxyModel.setFilterFixedString) + + self.bookmarksTree.setModel(self.__proxyModel) + self.bookmarksTree.setExpanded(self.__proxyModel.index(0, 0), True) + fm = QFontMetrics(self.font()) + header = fm.width("m") * 40 + self.bookmarksTree.header().resizeSection(0, header) + self.bookmarksTree.header().setStretchLastSection(True) + self.bookmarksTree.setContextMenuPolicy(Qt.CustomContextMenu) + + self.bookmarksTree.activated.connect(self.__activated) + self.bookmarksTree.customContextMenuRequested.connect( + self.__customContextMenuRequested) + + self.removeButton.clicked.connect( + self.bookmarksTree.removeSelected) + self.addFolderButton.clicked.connect(self.__newFolder) + + self.__expandNodes(self.__bookmarksManager.bookmarks()) + + def closeEvent(self, evt): + """ + Protected method to handle the closing of the dialog. + + @param evt reference to the event object (QCloseEvent) (ignored) + """ + self.__shutdown() + + def reject(self): + """ + Public method called when the dialog is rejected. + """ + self.__shutdown() + super(BookmarksDialog, self).reject() + + def __shutdown(self): + """ + Private method to perform shutdown actions for the dialog. + """ + if self.__saveExpandedNodes(self.bookmarksTree.rootIndex()): + self.__bookmarksManager.changeExpanded() + + def __saveExpandedNodes(self, parent): + """ + Private method to save the child nodes of an expanded node. + + @param parent index of the parent node (QModelIndex) + @return flag indicating a change (boolean) + """ + changed = False + for row in range(self.__proxyModel.rowCount(parent)): + child = self.__proxyModel.index(row, 0, parent) + sourceIndex = self.__proxyModel.mapToSource(child) + childNode = self.__bookmarksModel.node(sourceIndex) + wasExpanded = childNode.expanded + if self.bookmarksTree.isExpanded(child): + childNode.expanded = True + changed |= self.__saveExpandedNodes(child) + else: + childNode.expanded = False + changed |= (wasExpanded != childNode.expanded) + + return changed + + def __expandNodes(self, node): + """ + Private method to expand all child nodes of a node. + + @param node reference to the bookmark node to expand (BookmarkNode) + """ + for childNode in node.children(): + if childNode.expanded: + idx = self.__bookmarksModel.nodeIndex(childNode) + idx = self.__proxyModel.mapFromSource(idx) + self.bookmarksTree.setExpanded(idx, True) + self.__expandNodes(childNode) + + def __customContextMenuRequested(self, pos): + """ + Private slot to handle the context menu request for the bookmarks tree. + + @param pos position the context menu was requested (QPoint) + """ + from .BookmarkNode import BookmarkNode + + menu = QMenu() + idx = self.bookmarksTree.indexAt(pos) + idx = idx.sibling(idx.row(), 0) + sourceIndex = self.__proxyModel.mapToSource(idx) + node = self.__bookmarksModel.node(sourceIndex) + if idx.isValid() and node.type() != BookmarkNode.Folder: + menu.addAction( + self.tr("&Open"), self.__openBookmarkInCurrentTab) + menu.addAction( + self.tr("Open in New &Tab"), self.__openBookmarkInNewTab) + menu.addSeparator() + act = menu.addAction(self.tr("Edit &Name"), self.__editName) + act.setEnabled(idx.flags() & Qt.ItemIsEditable) + if idx.isValid() and node.type() != BookmarkNode.Folder: + menu.addAction(self.tr("Edit &Address"), self.__editAddress) + menu.addSeparator() + act = menu.addAction( + self.tr("&Delete"), self.bookmarksTree.removeSelected) + act.setEnabled(idx.flags() & Qt.ItemIsDragEnabled) + menu.addSeparator() + act = menu.addAction(self.tr("&Properties..."), self.__edit) + act.setEnabled(idx.flags() & Qt.ItemIsEditable) + menu.exec_(QCursor.pos()) + + def __activated(self, idx): + """ + Private slot to handle the activation of an entry. + + @param idx reference to the entry index (QModelIndex) + """ + self.__openBookmark( + QApplication.keyboardModifiers() & Qt.ControlModifier) + + def __openBookmarkInCurrentTab(self): + """ + Private slot to open a bookmark in the current browser tab. + """ + self.__openBookmark(False) + + def __openBookmarkInNewTab(self): + """ + Private slot to open a bookmark in a new browser tab. + """ + self.__openBookmark(True) + + def __openBookmark(self, newTab): + """ + Private method to open a bookmark. + + @param newTab flag indicating to open the bookmark in a new tab + (boolean) + """ + from .BookmarkNode import BookmarkNode + from .BookmarksModel import BookmarksModel + + idx = self.bookmarksTree.currentIndex() + sourceIndex = self.__proxyModel.mapToSource(idx) + node = self.__bookmarksModel.node(sourceIndex) + if not idx.parent().isValid() or \ + node is None or \ + node.type() == BookmarkNode.Folder: + return + if newTab: + self.newUrl.emit( + idx.sibling(idx.row(), 1).data(BookmarksModel.UrlRole), + idx.sibling(idx.row(), 0).data(Qt.DisplayRole)) + else: + self.openUrl.emit( + idx.sibling(idx.row(), 1).data(BookmarksModel.UrlRole), + idx.sibling(idx.row(), 0).data(Qt.DisplayRole)) + + def __editName(self): + """ + Private slot to edit the name part of a bookmark. + """ + idx = self.bookmarksTree.currentIndex() + idx = idx.sibling(idx.row(), 0) + self.bookmarksTree.edit(idx) + + def __editAddress(self): + """ + Private slot to edit the address part of a bookmark. + """ + idx = self.bookmarksTree.currentIndex() + idx = idx.sibling(idx.row(), 1) + self.bookmarksTree.edit(idx) + + def __edit(self): + """ + Private slot to edit a bookmarks properties. + """ + from .BookmarkPropertiesDialog import BookmarkPropertiesDialog + + idx = self.bookmarksTree.currentIndex() + sourceIndex = self.__proxyModel.mapToSource(idx) + node = self.__bookmarksModel.node(sourceIndex) + dlg = BookmarkPropertiesDialog(node) + dlg.exec_() + + def __newFolder(self): + """ + Private slot to add a new bookmarks folder. + """ + from .BookmarkNode import BookmarkNode + + currentIndex = self.bookmarksTree.currentIndex() + idx = QModelIndex(currentIndex) + sourceIndex = self.__proxyModel.mapToSource(idx) + sourceNode = self.__bookmarksModel.node(sourceIndex) + row = -1 # append new folder as the last item per default + + if sourceNode is not None and \ + sourceNode.type() != BookmarkNode.Folder: + # If the selected item is not a folder, add a new folder to the + # parent folder, but directly below the selected item. + idx = idx.parent() + row = currentIndex.row() + 1 + + if not idx.isValid(): + # Select bookmarks menu as default. + idx = self.__proxyModel.index(1, 0) + + idx = self.__proxyModel.mapToSource(idx) + parent = self.__bookmarksModel.node(idx) + node = BookmarkNode(BookmarkNode.Folder) + node.title = self.tr("New Folder") + self.__bookmarksManager.addBookmark(parent, node, row)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/WebBrowser/Bookmarks/BookmarksDialog.ui Sun Apr 03 16:33:37 2016 +0200 @@ -0,0 +1,179 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>BookmarksDialog</class> + <widget class="QDialog" name="BookmarksDialog"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>750</width> + <height>450</height> + </rect> + </property> + <property name="windowTitle"> + <string>Manage Bookmarks</string> + </property> + <property name="sizeGripEnabled"> + <bool>true</bool> + </property> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <layout class="QHBoxLayout" name="horizontalLayout_2"> + <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> + <item> + <layout class="QHBoxLayout" name="horizontalLayout"> + <property name="spacing"> + <number>0</number> + </property> + <item> + <widget class="E5ClearableLineEdit" name="searchEdit"> + <property name="toolTip"> + <string>Enter search term for bookmarks</string> + </property> + </widget> + </item> + </layout> + </item> + </layout> + </item> + <item> + <widget class="E5TreeView" name="bookmarksTree"> + <property name="dragDropMode"> + <enum>QAbstractItemView::InternalMove</enum> + </property> + <property name="alternatingRowColors"> + <bool>true</bool> + </property> + <property name="selectionMode"> + <enum>QAbstractItemView::ExtendedSelection</enum> + </property> + <property name="textElideMode"> + <enum>Qt::ElideMiddle</enum> + </property> + <property name="uniformRowHeights"> + <bool>true</bool> + </property> + </widget> + </item> + <item> + <layout class="QHBoxLayout" name="horizontalLayout_3"> + <item> + <widget class="QPushButton" name="removeButton"> + <property name="toolTip"> + <string>Press to delete the selected entries</string> + </property> + <property name="text"> + <string>&Delete</string> + </property> + <property name="autoDefault"> + <bool>false</bool> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="addFolderButton"> + <property name="toolTip"> + <string>Press to add a new bookmarks folder</string> + </property> + <property name="text"> + <string>Add &Folder</string> + </property> + <property name="autoDefault"> + <bool>false</bool> + </property> + </widget> + </item> + <item> + <spacer name="spacer"> + <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> + <customwidgets> + <customwidget> + <class>E5ClearableLineEdit</class> + <extends>QLineEdit</extends> + <header>E5Gui/E5LineEdit.h</header> + </customwidget> + <customwidget> + <class>E5TreeView</class> + <extends>QTreeView</extends> + <header>E5Gui/E5TreeView.h</header> + </customwidget> + </customwidgets> + <tabstops> + <tabstop>searchEdit</tabstop> + <tabstop>bookmarksTree</tabstop> + <tabstop>removeButton</tabstop> + <tabstop>addFolderButton</tabstop> + <tabstop>buttonBox</tabstop> + </tabstops> + <resources/> + <connections> + <connection> + <sender>buttonBox</sender> + <signal>accepted()</signal> + <receiver>BookmarksDialog</receiver> + <slot>accept()</slot> + <hints> + <hint type="sourcelabel"> + <x>252</x> + <y>445</y> + </hint> + <hint type="destinationlabel"> + <x>157</x> + <y>274</y> + </hint> + </hints> + </connection> + <connection> + <sender>buttonBox</sender> + <signal>rejected()</signal> + <receiver>BookmarksDialog</receiver> + <slot>reject()</slot> + <hints> + <hint type="sourcelabel"> + <x>320</x> + <y>445</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/Bookmarks/BookmarksImportDialog.py Sun Apr 03 16:33:37 2016 +0200 @@ -0,0 +1,155 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2012 - 2016 Detlev Offenbach <detlev@die-offenbachs.de> +# + +""" +Module implementing a dialog for importing bookmarks from other sources. +""" + +from __future__ import unicode_literals + +import os + +from PyQt5.QtCore import pyqtSlot, Qt, QSize +from PyQt5.QtWidgets import QDialog, QListWidgetItem + +from E5Gui import E5MessageBox +from E5Gui.E5PathPicker import E5PathPickerModes + +from .Ui_BookmarksImportDialog import Ui_BookmarksImportDialog + +from . import BookmarksImporters + +import Globals + + +class BookmarksImportDialog(QDialog, Ui_BookmarksImportDialog): + """ + Class implementing a dialog for importing bookmarks from other sources. + """ + SourcesListIdRole = Qt.UserRole + + def __init__(self, parent=None): + """ + Constructor + + @param parent reference to the parent widget (QWidget) + """ + super(BookmarksImportDialog, self).__init__(parent) + self.setupUi(self) + + self.filePicker.setMode(E5PathPickerModes.OpenFileMode) + + self.sourcesList.setIconSize(QSize(48, 48)) + for icon, displayText, idText in BookmarksImporters.getImporters(): + itm = QListWidgetItem(icon, displayText, self.sourcesList) + itm.setData(self.SourcesListIdRole, idText) + + self.__currentPage = 0 + self.__selectedSource = "" + self.__topLevelBookmarkNode = None + self.__sourceFile = "" + self.__sourceDir = "" + + self.pagesWidget.setCurrentIndex(self.__currentPage) + self.__enableNextButton() + + def __enableNextButton(self): + """ + Private slot to set the enabled state of the next button. + """ + if self.__currentPage == 0: + self.nextButton.setEnabled( + len(self.sourcesList.selectedItems()) == 1) + elif self.__currentPage == 1: + self.nextButton.setEnabled(self.filePicker.text() != "") + + @pyqtSlot() + def on_sourcesList_itemSelectionChanged(self): + """ + Private slot to handle changes of the selection of the import source. + """ + self.__enableNextButton() + + @pyqtSlot(str) + def on_filePicker_textChanged(self, txt): + """ + Private slot handling changes of the file to import bookmarks form. + + @param txt text of the line edit (string) + """ + self.__enableNextButton() + + @pyqtSlot() + def on_nextButton_clicked(self): + """ + Private slot to switch to the next page. + """ + if self.sourcesList.currentItem() is None: + return + + if self.__currentPage == 0: + self.__selectedSource = self.sourcesList.currentItem().data( + self.SourcesListIdRole) + (pixmap, sourceName, self.__sourceFile, info, prompt, + self.__sourceDir) = BookmarksImporters.getImporterInfo( + self.__selectedSource) + + self.iconLabel.setPixmap(pixmap) + self.importingFromLabel.setText( + self.tr("<b>Importing from {0}</b>").format(sourceName)) + self.fileLabel1.setText(info) + self.fileLabel2.setText(prompt) + self.standardDirLabel.setText( + "<i>{0}</i>".format(self.__sourceDir)) + + self.nextButton.setText(self.tr("Finish")) + + self.__currentPage += 1 + self.pagesWidget.setCurrentIndex(self.__currentPage) + self.__enableNextButton() + + if self.__selectedSource == "ie": + self.filePicker.setMode(E5PathPickerModes.DirectoryMode) + else: + self.filePicker.setMode(E5PathPickerModes.OpenFileMode) + if Globals.isMacPlatform(): + filter = "*{0}".format( + os.path.splitext(self.__sourceFile)[1]) + else: + filter = self.__sourceFile + self.filePicker.setFilters(filter) + self.filePicker.setDefaultDirectory(self.__sourceDir) + + elif self.__currentPage == 1: + if self.filePicker.text() == "": + return + + importer = BookmarksImporters.getImporter(self.__selectedSource) + importer.setPath(self.filePicker.text()) + if importer.open(): + self.__topLevelBookmarkNode = importer.importedBookmarks() + if importer.error(): + E5MessageBox.critical( + self, + self.tr("Error importing bookmarks"), + importer.errorString()) + return + + self.accept() + + @pyqtSlot() + def on_cancelButton_clicked(self): + """ + Private slot documentation goes here. + """ + self.reject() + + def getImportedBookmarks(self): + """ + Public method to get the imported bookmarks. + + @return top level bookmark (BookmarkNode) + """ + return self.__topLevelBookmarkNode
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/WebBrowser/Bookmarks/BookmarksImportDialog.ui Sun Apr 03 16:33:37 2016 +0200 @@ -0,0 +1,211 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>BookmarksImportDialog</class> + <widget class="QDialog" name="BookmarksImportDialog"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>550</width> + <height>354</height> + </rect> + </property> + <property name="minimumSize"> + <size> + <width>550</width> + <height>350</height> + </size> + </property> + <property name="windowTitle"> + <string>Import Bookmarks</string> + </property> + <property name="sizeGripEnabled"> + <bool>true</bool> + </property> + <layout class="QVBoxLayout" name="verticalLayout_3"> + <item> + <widget class="QStackedWidget" name="pagesWidget"> + <property name="currentIndex"> + <number>0</number> + </property> + <widget class="QWidget" name="sourcePage"> + <layout class="QVBoxLayout" name="verticalLayout"> + <property name="leftMargin"> + <number>0</number> + </property> + <property name="topMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> + <item> + <widget class="QLabel" name="label_2"> + <property name="text"> + <string>Choose source from which you want to import bookmarks:</string> + </property> + </widget> + </item> + <item> + <widget class="QListWidget" name="sourcesList"> + <property name="toolTip"> + <string>Choose the source to import from</string> + </property> + </widget> + </item> + </layout> + </widget> + <widget class="QWidget" name="filePage"> + <layout class="QVBoxLayout" name="verticalLayout_2"> + <item> + <layout class="QHBoxLayout" name="horizontalLayout_2"> + <item> + <widget class="QLabel" name="iconLabel"> + <property name="minimumSize"> + <size> + <width>48</width> + <height>48</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>48</width> + <height>48</height> + </size> + </property> + </widget> + </item> + <item> + <widget class="QLabel" name="importingFromLabel"/> + </item> + </layout> + </item> + <item> + <spacer name="verticalSpacer"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>44</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QLabel" name="fileLabel1"> + <property name="wordWrap"> + <bool>true</bool> + </property> + </widget> + </item> + <item> + <widget class="QLabel" name="standardDirLabel"> + <property name="textInteractionFlags"> + <set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set> + </property> + </widget> + </item> + <item> + <widget class="QLabel" name="fileLabel2"/> + </item> + <item> + <spacer name="verticalSpacer_2"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>44</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="E5PathPicker" name="filePicker" native="true"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="focusPolicy"> + <enum>Qt::StrongFocus</enum> + </property> + <property name="toolTip"> + <string>Enter the name of the bookmarks file or directory</string> + </property> + </widget> + </item> + <item> + <spacer name="verticalSpacer_3"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>44</height> + </size> + </property> + </spacer> + </item> + </layout> + </widget> + </widget> + </item> + <item> + <layout class="QHBoxLayout" name="horizontalLayout"> + <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> + <item> + <widget class="QPushButton" name="nextButton"> + <property name="text"> + <string>Next ></string> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="cancelButton"> + <property name="text"> + <string>Cancel</string> + </property> + </widget> + </item> + </layout> + </item> + </layout> + </widget> + <customwidgets> + <customwidget> + <class>E5PathPicker</class> + <extends>QWidget</extends> + <header>E5Gui/E5PathPicker.h</header> + <container>1</container> + </customwidget> + </customwidgets> + <tabstops> + <tabstop>sourcesList</tabstop> + <tabstop>nextButton</tabstop> + <tabstop>cancelButton</tabstop> + <tabstop>filePicker</tabstop> + </tabstops> + <resources/> + <connections/> +</ui>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/WebBrowser/Bookmarks/BookmarksImporters/BookmarksImporter.py Sun Apr 03 16:33:37 2016 +0200 @@ -0,0 +1,80 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2012 - 2016 Detlev Offenbach <detlev@die-offenbachs.de> +# + +""" +Module implementing a base class for the bookmarks importers. +""" + +from __future__ import unicode_literals + +from PyQt5.QtCore import QObject + + +class BookmarksImporter(QObject): + """ + Class implementing the base class for the bookmarks importers. + """ + def __init__(self, id="", parent=None): + """ + Constructor + + @param id source ID (string) + @param parent reference to the parent object (QObject) + """ + super(BookmarksImporter, self).__init__(parent) + + self._path = "" + self._file = "" + self._error = False + self._errorString = "" + self._id = id + + def setPath(self, path): + """ + Public method to set the path of the bookmarks file or directory. + + @param path bookmarks file or directory (string) + @exception NotImplementedError raised to indicate this method must + be implemented by a subclass + """ + raise NotImplementedError + + def open(self): + """ + Public method to open the bookmarks file. + + It must return a flag indicating success (boolean). + + @exception NotImplementedError raised to indicate this method must + be implemented by a subclass + """ + raise NotImplementedError + + def importedBookmarks(self): + """ + Public method to get the imported bookmarks. + + It must return the imported bookmarks (BookmarkNode). + + @exception NotImplementedError raised to indicate this method must + be implemented by a subclass + """ + raise NotImplementedError + + def error(self): + """ + Public method to check for an error. + + @return flag indicating an error (boolean) + """ + return self._error + + def errorString(self): + """ + Public method to get the error description. + + @return error description (string) + """ + return self._errorString
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/WebBrowser/Bookmarks/BookmarksImporters/ChromeImporter.py Sun Apr 03 16:33:37 2016 +0200 @@ -0,0 +1,188 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2012 - 2016 Detlev Offenbach <detlev@die-offenbachs.de> +# + +""" +Module implementing an importer for Chrome bookmarks. +""" + +from __future__ import unicode_literals + +import os +import json + +from PyQt5.QtCore import QCoreApplication, QDate, Qt + +from .BookmarksImporter import BookmarksImporter + +import UI.PixmapCache +import Globals + + +def getImporterInfo(id): + """ + Module function to get information for the given source id. + + @param id id of the browser ("chrome" or "chromium") + @return tuple with an icon (QPixmap), readable name (string), name of + the default bookmarks file (string), an info text (string), + a prompt (string) and the default directory of the bookmarks file + (string) + @exception ValueError raised to indicate an invalid browser ID + """ + if id == "chrome": + if Globals.isWindowsPlatform(): + standardDir = os.path.expandvars( + "%USERPROFILE%\\AppData\\Local\\Google\\Chrome\\" + "User Data\\Default") + elif Globals.isMacPlatform(): + standardDir = os.path.expanduser( + "~/Library/Application Support/Google/Chrome/Default") + else: + standardDir = os.path.expanduser("~/.config/google-chrome/Default") + return ( + UI.PixmapCache.getPixmap("chrome.png"), + "Google Chrome", + "Bookmarks", + QCoreApplication.translate( + "ChromeImporter", + """Google Chrome stores its bookmarks in the""" + """ <b>Bookmarks</b> text file. This file is usually""" + """ located in"""), + QCoreApplication.translate( + "ChromeImporter", + """Please choose the file to begin importing bookmarks."""), + standardDir, + ) + elif id == "chromium": + if Globals.isWindowsPlatform(): + standardDir = os.path.expandvars( + "%USERPROFILE%\\AppData\\Local\\Google\\Chrome\\" + "User Data\\Default") + else: + standardDir = os.path.expanduser("~/.config/chromium/Default") + return ( + UI.PixmapCache.getPixmap("chromium.png"), + "Chromium", + "Bookmarks", + QCoreApplication.translate( + "ChromeImporter", + """Chromium stores its bookmarks in the <b>Bookmarks</b>""" + """ text file. This file is usually located in"""), + QCoreApplication.translate( + "ChromeImporter", + """Please choose the file to begin importing bookmarks."""), + standardDir, + ) + else: + raise ValueError("Unsupported browser ID given ({0}).".format(id)) + + +class ChromeImporter(BookmarksImporter): + """ + Class implementing the Chrome bookmarks importer. + """ + def __init__(self, id="", parent=None): + """ + Constructor + + @param id source ID (string) + @param parent reference to the parent object (QObject) + """ + super(ChromeImporter, self).__init__(id, parent) + + self.__fileName = "" + + def setPath(self, path): + """ + Public method to set the path of the bookmarks file or directory. + + @param path bookmarks file or directory (string) + """ + self.__fileName = path + + def open(self): + """ + Public method to open the bookmarks file. + + @return flag indicating success (boolean) + """ + if not os.path.exists(self.__fileName): + self._error = True + self._errorString = self.tr( + "File '{0}' does not exist.").format(self.__fileName) + return False + return True + + def importedBookmarks(self): + """ + Public method to get the imported bookmarks. + + @return imported bookmarks (BookmarkNode) + """ + try: + f = open(self.__fileName, "r", encoding="utf-8") + contents = json.load(f) + f.close() + except IOError as err: + self._error = True + self._errorString = self.tr( + "File '{0}' cannot be read.\nReason: {1}")\ + .format(self.__fileName, str(err)) + return None + + from ..BookmarkNode import BookmarkNode + importRootNode = BookmarkNode(BookmarkNode.Folder) + if contents["version"] == 1: + self.__processRoots(contents["roots"], importRootNode) + + if self._id == "chrome": + importRootNode.title = self.tr("Google Chrome Import") + elif self._id == "chromium": + importRootNode.title = self.tr("Chromium Import") + else: + importRootNode.title = self.tr("Imported {0}")\ + .format(QDate.currentDate().toString(Qt.SystemLocaleShortDate)) + return importRootNode + + def __processRoots(self, data, rootNode): + """ + Private method to process the bookmark roots. + + @param data dictionary with the bookmarks data (dict) + @param rootNode node to add the bookmarks to (BookmarkNode) + """ + for node in data.values(): + if node["type"] == "folder": + self.__generateFolderNode(node, rootNode) + elif node["type"] == "url": + self.__generateUrlNode(node, rootNode) + + def __generateFolderNode(self, data, rootNode): + """ + Private method to process a bookmarks folder. + + @param data dictionary with the bookmarks data (dict) + @param rootNode node to add the bookmarks to (BookmarkNode) + """ + from ..BookmarkNode import BookmarkNode + folder = BookmarkNode(BookmarkNode.Folder, rootNode) + folder.title = data["name"].replace("&", "&&") + for node in data["children"]: + if node["type"] == "folder": + self.__generateFolderNode(node, folder) + elif node["type"] == "url": + self.__generateUrlNode(node, folder) + + def __generateUrlNode(self, data, rootNode): + """ + Private method to process a bookmarks node. + + @param data dictionary with the bookmarks data (dict) + @param rootNode node to add the bookmarks to (BookmarkNode) + """ + from ..BookmarkNode import BookmarkNode + bookmark = BookmarkNode(BookmarkNode.Bookmark, rootNode) + bookmark.url = data["url"] + bookmark.title = data["name"].replace("&", "&&")
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/WebBrowser/Bookmarks/BookmarksImporters/FirefoxImporter.py Sun Apr 03 16:33:37 2016 +0200 @@ -0,0 +1,181 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2012 - 2016 Detlev Offenbach <detlev@die-offenbachs.de> +# + +""" +Module implementing an importer for Firefox bookmarks. +""" + +from __future__ import unicode_literals + +import os +import sqlite3 + +from PyQt5.QtCore import QCoreApplication, QDate, Qt, QUrl + +from .BookmarksImporter import BookmarksImporter + +import UI.PixmapCache +import Globals + + +def getImporterInfo(id): + """ + Module function to get information for the given source id. + + @param id id of the browser ("chrome" or "chromium") + @return tuple with an icon (QPixmap), readable name (string), name of + the default bookmarks file (string), an info text (string), + a prompt (string) and the default directory of the bookmarks file + (string) + @exception ValueError raised to indicate an invalid browser ID + """ + if id == "firefox": + if Globals.isWindowsPlatform(): + standardDir = os.path.expandvars( + "%APPDATA%\\Mozilla\\Firefox\\Profiles") + elif Globals.isMacPlatform(): + standardDir = os.path.expanduser( + "~/Library/Application Support/Firefox/Profiles") + else: + standardDir = os.path.expanduser("~/.mozilla/firefox") + return ( + UI.PixmapCache.getPixmap("chrome.png"), + "Mozilla Firefox", + "places.sqlite", + QCoreApplication.translate( + "FirefoxImporter", + """Mozilla Firefox stores its bookmarks in the""" + """ <b>places.sqlite</b> SQLite database. This file is""" + """ usually located in"""), + QCoreApplication.translate( + "FirefoxImporter", + """Please choose the file to begin importing bookmarks."""), + standardDir, + ) + else: + raise ValueError("Unsupported browser ID given ({0}).".format(id)) + + +class FirefoxImporter(BookmarksImporter): + """ + Class implementing the Chrome bookmarks importer. + """ + def __init__(self, id="", parent=None): + """ + Constructor + + @param id source ID (string) + @param parent reference to the parent object (QObject) + """ + super(FirefoxImporter, self).__init__(id, parent) + + self.__fileName = "" + self.__db = None + + def setPath(self, path): + """ + Public method to set the path of the bookmarks file or directory. + + @param path bookmarks file or directory (string) + """ + self.__fileName = path + + def open(self): + """ + Public method to open the bookmarks file. + + @return flag indicating success (boolean) + """ + if not os.path.exists(self.__fileName): + self._error = True + self._errorString = self.tr("File '{0}' does not exist.")\ + .format(self.__fileName) + return False + + try: + self.__db = sqlite3.connect(self.__fileName) + except sqlite3.DatabaseError as err: + self._error = True + self._errorString = self.tr( + "Unable to open database.\nReason: {0}").format(str(err)) + return False + + return True + + def importedBookmarks(self): + """ + Public method to get the imported bookmarks. + + @return imported bookmarks (BookmarkNode) + """ + from ..BookmarkNode import BookmarkNode + importRootNode = BookmarkNode(BookmarkNode.Root) + + # step 1: build the hierarchy of bookmark folders + folders = {} + + try: + cursor = self.__db.cursor() + cursor.execute( + "SELECT id, parent, title FROM moz_bookmarks " + "WHERE type = 2 and title !=''") + for row in cursor: + id_ = row[0] + parent = row[1] + title = row[2] + if parent in folders: + folder = BookmarkNode(BookmarkNode.Folder, folders[parent]) + else: + folder = BookmarkNode(BookmarkNode.Folder, importRootNode) + folder.title = title.replace("&", "&&") + folders[id_] = folder + except sqlite3.DatabaseError as err: + self._error = True + self._errorString = self.tr( + "Unable to open database.\nReason: {0}").format(str(err)) + return None + + try: + cursor = self.__db.cursor() + cursor.execute( + "SELECT parent, title, fk, position FROM moz_bookmarks" + " WHERE type = 1 and title != '' ORDER BY position") + for row in cursor: + parent = row[0] + title = row[1] + placesId = row[2] + + cursor2 = self.__db.cursor() + cursor2.execute( + "SELECT url FROM moz_places WHERE id = {0}" + .format(placesId)) + row2 = cursor2.fetchone() + if row2: + url = QUrl(row2[0]) + if not title or url.isEmpty() or \ + url.scheme() in ["place", "about"]: + continue + + if parent in folders: + bookmark = BookmarkNode(BookmarkNode.Bookmark, + folders[parent]) + else: + bookmark = BookmarkNode(BookmarkNode.Bookmark, + importRootNode) + bookmark.url = url.toString() + bookmark.title = title.replace("&", "&&") + except sqlite3.DatabaseError as err: + self._error = True + self._errorString = self.tr( + "Unable to open database.\nReason: {0}").format(str(err)) + return None + + importRootNode.setType(BookmarkNode.Folder) + if self._id == "firefox": + importRootNode.title = self.tr("Mozilla Firefox Import") + else: + importRootNode.title = self.tr("Imported {0}")\ + .format(QDate.currentDate().toString(Qt.SystemLocaleShortDate)) + return importRootNode
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/WebBrowser/Bookmarks/BookmarksImporters/HtmlImporter.py Sun Apr 03 16:33:37 2016 +0200 @@ -0,0 +1,108 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2012 - 2016 Detlev Offenbach <detlev@die-offenbachs.de> +# + +""" +Module implementing an importer for HTML bookmark files. +""" + +from __future__ import unicode_literals + +import os + +from PyQt5.QtCore import QCoreApplication, QDate, Qt + +from .BookmarksImporter import BookmarksImporter + +import UI.PixmapCache + + +def getImporterInfo(id): + """ + Module function to get information for the given HTML source id. + + @param id id of the browser ("chrome" or "chromium") + @return tuple with an icon (QPixmap), readable name (string), name of + the default bookmarks file (string), an info text (string), + a prompt (string) and the default directory of the bookmarks file + (string) + @exception ValueError raised to indicate an invalid browser ID + """ + if id == "html": + return ( + UI.PixmapCache.getPixmap("html.png"), + "HTML Netscape Bookmarks", + QCoreApplication.translate( + "HtmlImporter", + "HTML Netscape Bookmarks") + " (*.htm *.html)", + QCoreApplication.translate( + "HtmlImporter", + """You can import bookmarks from any browser that supports""" + """ HTML exporting. This file has usually the extension""" + """ .htm or .html."""), + QCoreApplication.translate( + "HtmlImporter", + """Please choose the file to begin importing bookmarks."""), + "", + ) + else: + raise ValueError("Unsupported browser ID given ({0}).".format(id)) + + +class HtmlImporter(BookmarksImporter): + """ + Class implementing the HTML bookmarks importer. + """ + def __init__(self, id="", parent=None): + """ + Constructor + + @param id source ID (string) + @param parent reference to the parent object (QObject) + """ + super(HtmlImporter, self).__init__(id, parent) + + self.__fileName = "" + self.__inFile = None + + def setPath(self, path): + """ + Public method to set the path of the bookmarks file or directory. + + @param path bookmarks file or directory (string) + """ + self.__fileName = path + + def open(self): + """ + Public method to open the bookmarks file. + + @return flag indicating success (boolean) + """ + if not os.path.exists(self.__fileName): + self._error = True + self._errorString = self.tr("File '{0}' does not exist.")\ + .format(self.__fileName) + return False + return True + + def importedBookmarks(self): + """ + Public method to get the imported bookmarks. + + @return imported bookmarks (BookmarkNode) + """ + from ..BookmarkNode import BookmarkNode + from ..NsHtmlReader import NsHtmlReader + + reader = NsHtmlReader() + importRootNode = reader.read(self.__fileName) + + importRootNode.setType(BookmarkNode.Folder) + if self._id == "html": + importRootNode.title = self.tr("HTML Import") + else: + importRootNode.title = self.tr("Imported {0}")\ + .format(QDate.currentDate().toString(Qt.SystemLocaleShortDate)) + return importRootNode
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/WebBrowser/Bookmarks/BookmarksImporters/IExplorerImporter.py Sun Apr 03 16:33:37 2016 +0200 @@ -0,0 +1,150 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2012 - 2016 Detlev Offenbach <detlev@die-offenbachs.de> +# + +""" +Module implementing an importer for Internet Explorer bookmarks. +""" + +from __future__ import unicode_literals + +import os + +from PyQt5.QtCore import QCoreApplication, QDate, Qt + +from .BookmarksImporter import BookmarksImporter + +import UI.PixmapCache +import Globals + + +def getImporterInfo(id): + """ + Module function to get information for the given source id. + + @param id id of the browser ("chrome" or "chromium") + @return tuple with an icon (QPixmap), readable name (string), name of + the default bookmarks file (string), an info text (string), + a prompt (string) and the default directory of the bookmarks file + (string) + @exception ValueError raised to indicate an invalid browser ID + """ + if id == "ie": + if Globals.isWindowsPlatform(): + standardDir = os.path.expandvars( + "%USERPROFILE%\\Favorites") + else: + standardDir = "" + return ( + UI.PixmapCache.getPixmap("internet_explorer.png"), + "Internet Explorer", + "", + QCoreApplication.translate( + "IExplorerImporter", + """Internet Explorer stores its bookmarks in the""" + """ <b>Favorites</b> folder This folder is usually""" + """ located in"""), + QCoreApplication.translate( + "IExplorerImporter", + """Please choose the folder to begin importing bookmarks."""), + standardDir, + ) + else: + raise ValueError("Unsupported browser ID given ({0}).".format(id)) + + +class IExplorerImporter(BookmarksImporter): + """ + Class implementing the Chrome bookmarks importer. + """ + def __init__(self, id="", parent=None): + """ + Constructor + + @param id source ID (string) + @param parent reference to the parent object (QObject) + """ + super(IExplorerImporter, self).__init__(id, parent) + + self.__fileName = "" + + def setPath(self, path): + """ + Public method to set the path of the bookmarks file or directory. + + @param path bookmarks file or directory (string) + """ + self.__fileName = path + + def open(self): + """ + Public method to open the bookmarks file. + + @return flag indicating success (boolean) + """ + if not os.path.exists(self.__fileName): + self._error = True + self._errorString = self.tr("Folder '{0}' does not exist.")\ + .format(self.__fileName) + return False + if not os.path.isdir(self.__fileName): + self._error = True + self._errorString = self.tr("'{0}' is not a folder.")\ + .format(self.__fileName) + return True + + def importedBookmarks(self): + """ + Public method to get the imported bookmarks. + + @return imported bookmarks (BookmarkNode) + """ + from ..BookmarkNode import BookmarkNode + + folders = {} + + importRootNode = BookmarkNode(BookmarkNode.Folder) + folders[self.__fileName] = importRootNode + + for dir, subdirs, files in os.walk(self.__fileName): + for subdir in subdirs: + path = os.path.join(dir, subdir) + if dir in folders: + folder = BookmarkNode(BookmarkNode.Folder, folders[dir]) + else: + folder = BookmarkNode(BookmarkNode.Folder, importRootNode) + folder.title = subdir.replace("&", "&&") + folders[path] = folder + + for file in files: + name, ext = os.path.splitext(file) + if ext.lower() == ".url": + path = os.path.join(dir, file) + try: + f = open(path, "r") + contents = f.read() + f.close() + except IOError: + continue + url = "" + for line in contents.splitlines(): + if line.startswith("URL="): + url = line.replace("URL=", "") + break + if url: + if dir in folders: + bookmark = BookmarkNode(BookmarkNode.Bookmark, + folders[dir]) + else: + bookmark = BookmarkNode(BookmarkNode.Bookmark, + importRootNode) + bookmark.url = url + bookmark.title = name.replace("&", "&&") + + if self._id == "ie": + importRootNode.title = self.tr("Internet Explorer Import") + else: + importRootNode.title = self.tr("Imported {0}")\ + .format(QDate.currentDate().toString(Qt.SystemLocaleShortDate)) + return importRootNode
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/WebBrowser/Bookmarks/BookmarksImporters/OperaImporter.py Sun Apr 03 16:33:37 2016 +0200 @@ -0,0 +1,136 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2012 - 2016 Detlev Offenbach <detlev@die-offenbachs.de> +# + +""" +Module implementing an importer for Opera bookmarks. +""" + +from __future__ import unicode_literals + +import os + +from PyQt5.QtCore import QCoreApplication, QDate, Qt + +from .BookmarksImporter import BookmarksImporter + +import UI.PixmapCache +import Globals + + +def getImporterInfo(id): + """ + Module function to get information for the given source id. + + @param id id of the browser ("chrome" or "chromium") + @return tuple with an icon (QPixmap), readable name (string), name of + the default bookmarks file (string), an info text (string), + a prompt (string) and the default directory of the bookmarks file + (string) + @exception ValueError raised to indicate an invalid browser ID + """ + if id == "opera": + if Globals.isWindowsPlatform(): + standardDir = os.path.expandvars("%APPDATA%\\Opera\\Opera") + elif Globals.isMacPlatform(): + standardDir = os.path.expanduser( + "~/Library/Opera") + else: + standardDir = os.path.expanduser("~/.opera") + return ( + UI.PixmapCache.getPixmap("opera.png"), + "Opera", + "bookmarks.adr", + QCoreApplication.translate( + "OperaImporter", + """Opera stores its bookmarks in the <b>bookmarks.adr</b> """ + """text file. This file is usually located in"""), + QCoreApplication.translate( + "OperaImporter", + """Please choose the file to begin importing bookmarks."""), + standardDir, + ) + else: + raise ValueError("Unsupported browser ID given ({0}).".format(id)) + + +class OperaImporter(BookmarksImporter): + """ + Class implementing the Opera bookmarks importer. + """ + def __init__(self, id="", parent=None): + """ + Constructor + + @param id source ID (string) + @param parent reference to the parent object (QObject) + """ + super(OperaImporter, self).__init__(id, parent) + + self.__fileName = "" + + def setPath(self, path): + """ + Public method to set the path of the bookmarks file or directory. + + @param path bookmarks file or directory (string) + """ + self.__fileName = path + + def open(self): + """ + Public method to open the bookmarks file. + + @return flag indicating success (boolean) + """ + if not os.path.exists(self.__fileName): + self._error = True + self._errorString = self.tr("File '{0}' does not exist.")\ + .format(self.__fileName) + return False + return True + + def importedBookmarks(self): + """ + Public method to get the imported bookmarks. + + @return imported bookmarks (BookmarkNode) + """ + try: + f = open(self.__fileName, "r", encoding="utf-8") + contents = f.read() + f.close() + except IOError as err: + self._error = True + self._errorString = self.tr( + "File '{0}' cannot be read.\nReason: {1}")\ + .format(self.__fileName, str(err)) + return None + + folderStack = [] + + from ..BookmarkNode import BookmarkNode + importRootNode = BookmarkNode(BookmarkNode.Folder) + folderStack.append(importRootNode) + + for line in contents.splitlines(): + line = line.strip() + if line == "#FOLDER": + node = BookmarkNode(BookmarkNode.Folder, folderStack[-1]) + folderStack.append(node) + elif line == "#URL": + node = BookmarkNode(BookmarkNode.Bookmark, folderStack[-1]) + elif line == "-": + folderStack.pop() + elif line.startswith("NAME="): + node.title = line.replace("NAME=", "").replace("&", "&&") + elif line.startswith("URL="): + node.url = line.replace("URL=", "") + + if self._id == "opera": + importRootNode.title = self.tr("Opera Import") + else: + importRootNode.title = self.tr("Imported {0}")\ + .format(QDate.currentDate().toString(Qt.SystemLocaleShortDate)) + return importRootNode
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/WebBrowser/Bookmarks/BookmarksImporters/SafariImporter.py Sun Apr 03 16:33:37 2016 +0200 @@ -0,0 +1,146 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2012 - 2016 Detlev Offenbach <detlev@die-offenbachs.de> +# + +""" +Module implementing an importer for Apple Safari bookmarks. +""" + +from __future__ import unicode_literals + +import os + +from PyQt5.QtCore import QCoreApplication, QDate, Qt + +from .BookmarksImporter import BookmarksImporter + +import UI.PixmapCache +import Globals + +from Utilities import binplistlib + + +def getImporterInfo(id): + """ + Module function to get information for the given source id. + + @param id id of the browser ("chrome" or "chromium") + @return tuple with an icon (QPixmap), readable name (string), name of + the default bookmarks file (string), an info text (string), + a prompt (string) and the default directory of the bookmarks file + (string) + @exception ValueError raised to indicate an invalid browser ID + """ + if id == "safari": + if Globals.isWindowsPlatform(): + standardDir = os.path.expandvars( + "%APPDATA%\\Apple Computer\\Safari") + elif Globals.isMacPlatform(): + standardDir = os.path.expanduser("~/Library/Safari") + else: + standardDir = "" + return ( + UI.PixmapCache.getPixmap("safari.png"), + "Apple Safari", + "Bookmarks.plist", + QCoreApplication.translate( + "SafariImporter", + """Apple Safari stores its bookmarks in the""" + """ <b>Bookmarks.plist</b> file. This file is usually""" + """ located in"""), + QCoreApplication.translate( + "SafariImporter", + """Please choose the file to begin importing bookmarks."""), + standardDir, + ) + else: + raise ValueError("Unsupported browser ID given ({0}).".format(id)) + + +class SafariImporter(BookmarksImporter): + """ + Class implementing the Apple Safari bookmarks importer. + """ + def __init__(self, id="", parent=None): + """ + Constructor + + @param id source ID (string) + @param parent reference to the parent object (QObject) + """ + super(SafariImporter, self).__init__(id, parent) + + self.__fileName = "" + + def setPath(self, path): + """ + Public method to set the path of the bookmarks file or directory. + + @param path bookmarks file or directory (string) + """ + self.__fileName = path + + def open(self): + """ + Public method to open the bookmarks file. + + @return flag indicating success (boolean) + """ + if not os.path.exists(self.__fileName): + self._error = True + self._errorString = self.tr("File '{0}' does not exist.")\ + .format(self.__fileName) + return False + return True + + def importedBookmarks(self): + """ + Public method to get the imported bookmarks. + + @return imported bookmarks (BookmarkNode) + """ + try: + bookmarksDict = binplistlib.readPlist(self.__fileName) + except binplistlib.InvalidPlistException as err: + self._error = True + self._errorString = self.tr( + "Bookmarks file cannot be read.\nReason: {0}".format(str(err))) + return None + + from ..BookmarkNode import BookmarkNode + importRootNode = BookmarkNode(BookmarkNode.Folder) + if bookmarksDict["WebBookmarkFileVersion"] == 1 and \ + bookmarksDict["WebBookmarkType"] == "WebBookmarkTypeList": + self.__processChildren(bookmarksDict["Children"], importRootNode) + + if self._id == "safari": + importRootNode.title = self.tr("Apple Safari Import") + else: + importRootNode.title = self.tr("Imported {0}")\ + .format(QDate.currentDate().toString(Qt.SystemLocaleShortDate)) + return importRootNode + + def __processChildren(self, children, rootNode): + """ + Private method to process the list of children. + + @param children list of child nodes to be processed (list of dict) + @param rootNode node to add the bookmarks to (BookmarkNode) + """ + from ..BookmarkNode import BookmarkNode + for child in children: + if child["WebBookmarkType"] == "WebBookmarkTypeList": + folder = BookmarkNode(BookmarkNode.Folder, rootNode) + folder.title = child["Title"].replace("&", "&&") + if "Children" in child: + self.__processChildren(child["Children"], folder) + elif child["WebBookmarkType"] == "WebBookmarkTypeLeaf": + url = child["URLString"] + if url.startswith(("place:", "about:")): + continue + + bookmark = BookmarkNode(BookmarkNode.Bookmark, rootNode) + bookmark.url = url + bookmark.title = child["URIDictionary"]["title"]\ + .replace("&", "&&")
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/WebBrowser/Bookmarks/BookmarksImporters/XbelImporter.py Sun Apr 03 16:33:37 2016 +0200 @@ -0,0 +1,158 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2012 - 2016 Detlev Offenbach <detlev@die-offenbachs.de> +# + +""" +Module implementing an importer for XBEL files. +""" + +from __future__ import unicode_literals + +import os + +from PyQt5.QtCore import QCoreApplication, QXmlStreamReader, QDate, Qt + +from .BookmarksImporter import BookmarksImporter + +import UI.PixmapCache + + +def getImporterInfo(id): + """ + Module function to get information for the given XBEL source id. + + @param id id of the browser ("chrome" or "chromium") + @return tuple with an icon (QPixmap), readable name (string), name of + the default bookmarks file (string), an info text (string), + a prompt (string) and the default directory of the bookmarks file + (string) + @exception ValueError raised to indicate an invalid browser ID + """ + if id == "e5browser": + from ..BookmarksManager import BookmarksManager + bookmarksFile = BookmarksManager.getFileName() + return ( + UI.PixmapCache.getPixmap("ericWeb48.png"), + "eric6 Web Browser", + os.path.basename(bookmarksFile), + QCoreApplication.translate( + "XbelImporter", + """eric6 Web Browser stores its bookmarks in the""" + """ <b>{0}</b> XML file. This file is usually located in""" + ).format(os.path.basename(bookmarksFile)), + QCoreApplication.translate( + "XbelImporter", + """Please choose the file to begin importing bookmarks."""), + os.path.dirname(bookmarksFile), + ) + elif id == "konqueror": + if os.path.exists(os.path.expanduser("~/.kde4")): + standardDir = os.path.expanduser("~/.kde4/share/apps/konqueror") + elif os.path.exists(os.path.expanduser("~/.kde")): + standardDir = os.path.expanduser("~/.kde/share/apps/konqueror") + else: + standardDir = "" + return ( + UI.PixmapCache.getPixmap("konqueror.png"), + "Konqueror", + "bookmarks.xml", + QCoreApplication.translate( + "XbelImporter", + """Konqueror stores its bookmarks in the""" + """ <b>bookmarks.xml</b> XML file. This file is usually""" + """ located in"""), + QCoreApplication.translate( + "XbelImporter", + """Please choose the file to begin importing bookmarks."""), + standardDir, + ) + elif id == "xbel": + return ( + UI.PixmapCache.getPixmap("xbel.png"), + "XBEL Bookmarks", + QCoreApplication.translate( + "XbelImporter", "XBEL Bookmarks") + " (*.xbel *.xml)", + QCoreApplication.translate( + "XbelImporter", + """You can import bookmarks from any browser that supports""" + """ XBEL exporting. This file has usually the extension""" + """ .xbel or .xml."""), + QCoreApplication.translate( + "XbelImporter", + """Please choose the file to begin importing bookmarks."""), + "", + ) + else: + raise ValueError("Unsupported browser ID given ({0}).".format(id)) + + +class XbelImporter(BookmarksImporter): + """ + Class implementing the XBEL bookmarks importer. + """ + def __init__(self, id="", parent=None): + """ + Constructor + + @param id source ID (string) + @param parent reference to the parent object (QObject) + """ + super(XbelImporter, self).__init__(id, parent) + + self.__fileName = "" + + def setPath(self, path): + """ + Public method to set the path of the bookmarks file or directory. + + @param path bookmarks file or directory (string) + """ + self.__fileName = path + + def open(self): + """ + Public method to open the bookmarks file. + + @return flag indicating success (boolean) + """ + if not os.path.exists(self.__fileName): + self._error = True + self._errorString = self.tr("File '{0}' does not exist.")\ + .format(self.__fileName) + return False + return True + + def importedBookmarks(self): + """ + Public method to get the imported bookmarks. + + @return imported bookmarks (BookmarkNode) + """ + from ..XbelReader import XbelReader + + reader = XbelReader() + importRootNode = reader.read(self.__fileName) + + if reader.error() != QXmlStreamReader.NoError: + self._error = True + self._errorString = self.tr( + """Error when importing bookmarks on line {0},""" + """ column {1}:\n{2}""")\ + .format(reader.lineNumber(), + reader.columnNumber(), + reader.errorString()) + return None + + from ..BookmarkNode import BookmarkNode + importRootNode.setType(BookmarkNode.Folder) + if self._id == "e5browser": + importRootNode.title = self.tr("eric6 Web Browser Import") + elif self._id == "konqueror": + importRootNode.title = self.tr("Konqueror Import") + elif self._id == "xbel": + importRootNode.title = self.tr("XBEL Import") + else: + importRootNode.title = self.tr("Imported {0}")\ + .format(QDate.currentDate().toString(Qt.SystemLocaleShortDate)) + return importRootNode
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/WebBrowser/Bookmarks/BookmarksImporters/__init__.py Sun Apr 03 16:33:37 2016 +0200 @@ -0,0 +1,125 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2012 - 2016 Detlev Offenbach <detlev@die-offenbachs.de> +# + +""" +Package implementing bookmarks importers for various sources. +""" + +from __future__ import unicode_literals + +from PyQt5.QtCore import QCoreApplication + +import UI.PixmapCache +import Globals + + +def getImporters(): + """ + Module function to get a list of supported importers. + + @return list of tuples with an icon (QIcon), readable name (string) and + internal name (string) + """ + importers = [] + importers.append( + (UI.PixmapCache.getIcon("ericWeb48.png"), "eric6 Web Browser", + "e5browser")) + importers.append( + (UI.PixmapCache.getIcon("firefox.png"), "Mozilla Firefox", "firefox")) + importers.append( + (UI.PixmapCache.getIcon("chrome.png"), "Google Chrome", "chrome")) + if Globals.isLinuxPlatform(): + importers.append( + (UI.PixmapCache.getIcon("chromium.png"), "Chromium", "chromium")) + importers.append( + (UI.PixmapCache.getIcon("konqueror.png"), "Konqueror", + "konqueror")) + importers.append( + (UI.PixmapCache.getIcon("opera.png"), "Opera", "opera")) + importers.append( + (UI.PixmapCache.getIcon("safari.png"), "Apple Safari", "safari")) + if Globals.isWindowsPlatform(): + importers.append( + (UI.PixmapCache.getIcon("internet_explorer.png"), + "Internet Explorer", "ie")) + importers.append( + (UI.PixmapCache.getIcon("xbel.png"), + QCoreApplication.translate("BookmarksImporters", "XBEL File"), + "xbel")) + importers.append( + (UI.PixmapCache.getIcon("html.png"), + QCoreApplication.translate("BookmarksImporters", "HTML File"), + "html")) + return importers + + +def getImporterInfo(id): + """ + Module function to get information for the given source id. + + @param id source id to get info for (string) + @return tuple with an icon (QPixmap), readable name (string), name of + the default bookmarks file (string), an info text (string), + a prompt (string) and the default directory of the bookmarks + file (string) + @exception ValueError raised to indicate an unsupported importer